/** * Phase Dispatch Router — queue, dispatch, state, child tasks */ import { TRPCError } from '@trpc/server'; import { z } from 'zod'; import type { Task } from '../../db/schema.js'; import type { ProcedureBuilder } from '../trpc.js'; import { requirePhaseDispatchManager, requirePhaseRepository, requireTaskRepository } from './_helpers.js'; export function phaseDispatchProcedures(publicProcedure: ProcedureBuilder) { return { queuePhase: publicProcedure .input(z.object({ phaseId: z.string().min(1) })) .mutation(async ({ ctx, input }) => { const phaseDispatchManager = requirePhaseDispatchManager(ctx); await phaseDispatchManager.queuePhase(input.phaseId); return { success: true }; }), queueAllPhases: publicProcedure .input(z.object({ initiativeId: z.string().min(1) })) .mutation(async ({ ctx, input }) => { const phaseDispatchManager = requirePhaseDispatchManager(ctx); const phaseRepo = requirePhaseRepository(ctx); const phases = await phaseRepo.findByInitiativeId(input.initiativeId); let queued = 0; for (const phase of phases) { if (phase.status === 'approved') { await phaseDispatchManager.queuePhase(phase.id); queued++; } } return { success: true, queued }; }), dispatchNextPhase: publicProcedure .mutation(async ({ ctx }) => { const phaseDispatchManager = requirePhaseDispatchManager(ctx); return phaseDispatchManager.dispatchNextPhase(); }), getPhaseQueueState: publicProcedure .query(async ({ ctx }) => { const phaseDispatchManager = requirePhaseDispatchManager(ctx); return phaseDispatchManager.getPhaseQueueState(); }), createChildTasks: publicProcedure .input(z.object({ parentTaskId: z.string().min(1), tasks: z.array(z.object({ number: z.number().int().positive(), name: z.string().min(1), description: z.string(), type: z.enum(['auto']).default('auto'), dependencies: z.array(z.number().int().positive()).optional(), })), })) .mutation(async ({ ctx, input }) => { const taskRepo = requireTaskRepository(ctx); const parentTask = await taskRepo.findById(input.parentTaskId); if (!parentTask) { throw new TRPCError({ code: 'NOT_FOUND', message: `Parent task '${input.parentTaskId}' not found`, }); } if (parentTask.category !== 'detail') { throw new TRPCError({ code: 'BAD_REQUEST', message: `Parent task must have category 'detail', got '${parentTask.category}'`, }); } const numberToId = new Map(); const created: Task[] = []; for (const taskInput of input.tasks) { const task = await taskRepo.create({ parentTaskId: input.parentTaskId, phaseId: parentTask.phaseId, initiativeId: parentTask.initiativeId, name: taskInput.name, description: taskInput.description, type: taskInput.type, order: taskInput.number, status: 'pending', }); numberToId.set(taskInput.number, task.id); created.push(task); } for (const taskInput of input.tasks) { if (taskInput.dependencies && taskInput.dependencies.length > 0) { const taskId = numberToId.get(taskInput.number)!; for (const depNumber of taskInput.dependencies) { const dependsOnTaskId = numberToId.get(depNumber); if (dependsOnTaskId) { await taskRepo.createDependency(taskId, dependsOnTaskId); } } } } return created; }), }; }