diff --git a/actions/ideas.ts b/actions/ideas.ts index 62dc4e2..63bae6d 100644 --- a/actions/ideas.ts +++ b/actions/ideas.ts @@ -21,6 +21,7 @@ import { canTransition, isGrillMdEditable, isIdeaEditable, isPlanMdEditable } fr import { nextIdeaCode } from '@/lib/idea-code-server' import { parsePlanMd } from '@/lib/idea-plan-parser' import { ACTIVE_JOB_STATUSES } from '@/lib/job-status' +import { parseCodeNumber } from '@/lib/code' import type { ClaudeJobKind, Idea, IdeaStatus } from '@prisma/client' @@ -720,16 +721,17 @@ export async function materializeIdeaPlanAction( for (let si = 0; si < plan.stories.length; si++) { const s = plan.stories[si] + const storyCode = `ST-${String(nextStoryN++).padStart(3, '0')}` const story = await tx.story.create({ data: { pbi_id: pbi.id, product_id: productId, - code: `ST-${String(nextStoryN++).padStart(3, '0')}`, + code: storyCode, title: s.title, description: s.description ?? null, acceptance_criteria: s.acceptance_criteria ?? null, priority: s.priority, - sort_order: si + 1, // sequential within PBI + sort_order: parseCodeNumber(storyCode), status: 'OPEN', }, select: { id: true }, @@ -738,11 +740,12 @@ export async function materializeIdeaPlanAction( for (let ti = 0; ti < s.tasks.length; ti++) { const t = s.tasks[ti] + const taskCode = `T-${nextTaskN++}` const task = await tx.task.create({ data: { story_id: story.id, product_id: productId, - code: `T-${nextTaskN++}`, + code: taskCode, title: t.title, description: t.description ?? null, implementation_plan: t.implementation_plan ?? null, @@ -751,7 +754,7 @@ export async function materializeIdeaPlanAction( // gemixte task-priorities binnen één story zouden anders de // YAML-volgorde verstoren (zie plan-fix task-volgorde-na-upload). priority: s.priority, - sort_order: ti + 1, + sort_order: parseCodeNumber(taskCode), status: 'TO_DO', verify_required: t.verify_required ?? 'ALIGNED_OR_PARTIAL', verify_only: t.verify_only ?? false, diff --git a/actions/stories.ts b/actions/stories.ts index bcc88fc..2bc30c5 100644 --- a/actions/stories.ts +++ b/actions/stories.ts @@ -7,7 +7,7 @@ import { prisma } from '@/lib/prisma' import { SessionData, sessionOptions } from '@/lib/session' import { getAccessibleProduct, productAccessFilter } from '@/lib/product-access' import { requireProductWriter } from '@/lib/auth' -import { isValidCode, normalizeCode } from '@/lib/code' +import { isValidCode, normalizeCode, parseCodeNumber } from '@/lib/code' import { createWithCodeRetry, generateNextStoryCode } from '@/lib/code-server' import { createStorySchema, updateStorySchema } from '@/lib/schemas/story' import { enforceUserRateLimit } from '@/lib/rate-limit' @@ -78,12 +78,6 @@ export async function createStoryAction(_prevState: unknown, formData: FormData) } } - const last = await prisma.story.findFirst({ - where: { pbi_id: parsed.data.pbiId, priority: parsed.data.priority }, - orderBy: { sort_order: 'desc' }, - }) - const sort_order = (last?.sort_order ?? 0) + 1.0 - const insert = (code: string) => prisma.story.create({ data: { @@ -94,7 +88,7 @@ export async function createStoryAction(_prevState: unknown, formData: FormData) description: parsed.data.description ?? null, acceptance_criteria: parsed.data.acceptance_criteria ?? null, priority: parsed.data.priority, - sort_order, + sort_order: parseCodeNumber(code), status: 'OPEN', }, }) @@ -167,7 +161,7 @@ export async function updateStoryAction(_prevState: unknown, formData: FormData) await prisma.story.update({ where: { id: parsed.data.id }, data: { - ...(code ? { code } : {}), + ...(code ? { code, sort_order: parseCodeNumber(code) } : {}), title: parsed.data.title, description: parsed.data.description ?? null, acceptance_criteria: parsed.data.acceptance_criteria ?? null, diff --git a/actions/tasks.ts b/actions/tasks.ts index 7b83e03..56cca67 100644 --- a/actions/tasks.ts +++ b/actions/tasks.ts @@ -10,7 +10,7 @@ import { productAccessFilter } from '@/lib/product-access' import { requireProductWriter } from '@/lib/auth' import { taskSchema as sharedTaskSchema, type TaskInput } from '@/lib/schemas/task' import { propagateStatusUpwards } from '@/lib/tasks-status-update' -import { normalizeCode } from '@/lib/code' +import { normalizeCode, parseCodeNumber } from '@/lib/code' import { createWithCodeRetry, generateNextTaskCode, isCodeUniqueConflict } from '@/lib/code-server' import { enforceUserRateLimit } from '@/lib/rate-limit' @@ -80,6 +80,7 @@ export async function saveTask( description: description ?? null, implementation_plan: implementation_plan ?? null, priority, + ...(inputCode ? { code: inputCode, sort_order: parseCodeNumber(inputCode) } : {}), }, select: { id: true, title: true, status: true }, }) @@ -106,15 +107,8 @@ export async function saveTask( }) if (!story) return { ok: false, code: 403, error: 'forbidden' } - const last = await prisma.task.findFirst({ - where: { story_id: context.storyId }, - orderBy: { sort_order: 'desc' }, - select: { sort_order: true }, - }) - const productId = story.product_id const sprintId = story.sprint_id ?? null - const sortOrder = (last?.sort_order ?? 0) + 1.0 const storyId = context.storyId const task = await createWithCodeRetry( @@ -130,7 +124,7 @@ export async function saveTask( description: description ?? null, implementation_plan: implementation_plan ?? null, priority, - sort_order: sortOrder, + sort_order: parseCodeNumber(code), status: 'TO_DO', }, select: { id: true, title: true, status: true }, @@ -207,11 +201,6 @@ export async function createTaskAction(_prevState: unknown, formData: FormData) }) if (!story) return { error: 'Story niet gevonden' } - const last = await prisma.task.findFirst({ - where: { story_id: storyId }, - orderBy: { sort_order: 'desc' }, - }) - const productId = story.product_id const task = await createWithCodeRetry( () => generateNextTaskCode(productId), @@ -225,7 +214,7 @@ export async function createTaskAction(_prevState: unknown, formData: FormData) title: parsed.data.title, description: parsed.data.description ?? null, priority: parsed.data.priority, - sort_order: (last?.sort_order ?? 0) + 1.0, + sort_order: parseCodeNumber(code), status: 'TO_DO', }, }),