feat(sort-order): derive story/task sort_order from parseCodeNumber(code)
All create paths (createStoryAction, saveTask, createTaskAction, materializeIdeaPlanAction) and code-edit paths (updateStoryAction, saveTask update) now set sort_order = parseCodeNumber(code) instead of last+1. Removes stale last-record queries from create paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5085fac31d
commit
fec03dd257
3 changed files with 14 additions and 28 deletions
|
|
@ -21,6 +21,7 @@ import { canTransition, isGrillMdEditable, isIdeaEditable, isPlanMdEditable } fr
|
||||||
import { nextIdeaCode } from '@/lib/idea-code-server'
|
import { nextIdeaCode } from '@/lib/idea-code-server'
|
||||||
import { parsePlanMd } from '@/lib/idea-plan-parser'
|
import { parsePlanMd } from '@/lib/idea-plan-parser'
|
||||||
import { ACTIVE_JOB_STATUSES } from '@/lib/job-status'
|
import { ACTIVE_JOB_STATUSES } from '@/lib/job-status'
|
||||||
|
import { parseCodeNumber } from '@/lib/code'
|
||||||
|
|
||||||
import type { ClaudeJobKind, Idea, IdeaStatus } from '@prisma/client'
|
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++) {
|
for (let si = 0; si < plan.stories.length; si++) {
|
||||||
const s = plan.stories[si]
|
const s = plan.stories[si]
|
||||||
|
const storyCode = `ST-${String(nextStoryN++).padStart(3, '0')}`
|
||||||
const story = await tx.story.create({
|
const story = await tx.story.create({
|
||||||
data: {
|
data: {
|
||||||
pbi_id: pbi.id,
|
pbi_id: pbi.id,
|
||||||
product_id: productId,
|
product_id: productId,
|
||||||
code: `ST-${String(nextStoryN++).padStart(3, '0')}`,
|
code: storyCode,
|
||||||
title: s.title,
|
title: s.title,
|
||||||
description: s.description ?? null,
|
description: s.description ?? null,
|
||||||
acceptance_criteria: s.acceptance_criteria ?? null,
|
acceptance_criteria: s.acceptance_criteria ?? null,
|
||||||
priority: s.priority,
|
priority: s.priority,
|
||||||
sort_order: si + 1, // sequential within PBI
|
sort_order: parseCodeNumber(storyCode),
|
||||||
status: 'OPEN',
|
status: 'OPEN',
|
||||||
},
|
},
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
|
|
@ -738,11 +740,12 @@ export async function materializeIdeaPlanAction(
|
||||||
|
|
||||||
for (let ti = 0; ti < s.tasks.length; ti++) {
|
for (let ti = 0; ti < s.tasks.length; ti++) {
|
||||||
const t = s.tasks[ti]
|
const t = s.tasks[ti]
|
||||||
|
const taskCode = `T-${nextTaskN++}`
|
||||||
const task = await tx.task.create({
|
const task = await tx.task.create({
|
||||||
data: {
|
data: {
|
||||||
story_id: story.id,
|
story_id: story.id,
|
||||||
product_id: productId,
|
product_id: productId,
|
||||||
code: `T-${nextTaskN++}`,
|
code: taskCode,
|
||||||
title: t.title,
|
title: t.title,
|
||||||
description: t.description ?? null,
|
description: t.description ?? null,
|
||||||
implementation_plan: t.implementation_plan ?? null,
|
implementation_plan: t.implementation_plan ?? null,
|
||||||
|
|
@ -751,7 +754,7 @@ export async function materializeIdeaPlanAction(
|
||||||
// gemixte task-priorities binnen één story zouden anders de
|
// gemixte task-priorities binnen één story zouden anders de
|
||||||
// YAML-volgorde verstoren (zie plan-fix task-volgorde-na-upload).
|
// YAML-volgorde verstoren (zie plan-fix task-volgorde-na-upload).
|
||||||
priority: s.priority,
|
priority: s.priority,
|
||||||
sort_order: ti + 1,
|
sort_order: parseCodeNumber(taskCode),
|
||||||
status: 'TO_DO',
|
status: 'TO_DO',
|
||||||
verify_required: t.verify_required ?? 'ALIGNED_OR_PARTIAL',
|
verify_required: t.verify_required ?? 'ALIGNED_OR_PARTIAL',
|
||||||
verify_only: t.verify_only ?? false,
|
verify_only: t.verify_only ?? false,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { prisma } from '@/lib/prisma'
|
||||||
import { SessionData, sessionOptions } from '@/lib/session'
|
import { SessionData, sessionOptions } from '@/lib/session'
|
||||||
import { getAccessibleProduct, productAccessFilter } from '@/lib/product-access'
|
import { getAccessibleProduct, productAccessFilter } from '@/lib/product-access'
|
||||||
import { requireProductWriter } from '@/lib/auth'
|
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 { createWithCodeRetry, generateNextStoryCode } from '@/lib/code-server'
|
||||||
import { createStorySchema, updateStorySchema } from '@/lib/schemas/story'
|
import { createStorySchema, updateStorySchema } from '@/lib/schemas/story'
|
||||||
import { enforceUserRateLimit } from '@/lib/rate-limit'
|
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) =>
|
const insert = (code: string) =>
|
||||||
prisma.story.create({
|
prisma.story.create({
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -94,7 +88,7 @@ export async function createStoryAction(_prevState: unknown, formData: FormData)
|
||||||
description: parsed.data.description ?? null,
|
description: parsed.data.description ?? null,
|
||||||
acceptance_criteria: parsed.data.acceptance_criteria ?? null,
|
acceptance_criteria: parsed.data.acceptance_criteria ?? null,
|
||||||
priority: parsed.data.priority,
|
priority: parsed.data.priority,
|
||||||
sort_order,
|
sort_order: parseCodeNumber(code),
|
||||||
status: 'OPEN',
|
status: 'OPEN',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -167,7 +161,7 @@ export async function updateStoryAction(_prevState: unknown, formData: FormData)
|
||||||
await prisma.story.update({
|
await prisma.story.update({
|
||||||
where: { id: parsed.data.id },
|
where: { id: parsed.data.id },
|
||||||
data: {
|
data: {
|
||||||
...(code ? { code } : {}),
|
...(code ? { code, sort_order: parseCodeNumber(code) } : {}),
|
||||||
title: parsed.data.title,
|
title: parsed.data.title,
|
||||||
description: parsed.data.description ?? null,
|
description: parsed.data.description ?? null,
|
||||||
acceptance_criteria: parsed.data.acceptance_criteria ?? null,
|
acceptance_criteria: parsed.data.acceptance_criteria ?? null,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { productAccessFilter } from '@/lib/product-access'
|
||||||
import { requireProductWriter } from '@/lib/auth'
|
import { requireProductWriter } from '@/lib/auth'
|
||||||
import { taskSchema as sharedTaskSchema, type TaskInput } from '@/lib/schemas/task'
|
import { taskSchema as sharedTaskSchema, type TaskInput } from '@/lib/schemas/task'
|
||||||
import { propagateStatusUpwards } from '@/lib/tasks-status-update'
|
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 { createWithCodeRetry, generateNextTaskCode, isCodeUniqueConflict } from '@/lib/code-server'
|
||||||
import { enforceUserRateLimit } from '@/lib/rate-limit'
|
import { enforceUserRateLimit } from '@/lib/rate-limit'
|
||||||
|
|
||||||
|
|
@ -80,6 +80,7 @@ export async function saveTask(
|
||||||
description: description ?? null,
|
description: description ?? null,
|
||||||
implementation_plan: implementation_plan ?? null,
|
implementation_plan: implementation_plan ?? null,
|
||||||
priority,
|
priority,
|
||||||
|
...(inputCode ? { code: inputCode, sort_order: parseCodeNumber(inputCode) } : {}),
|
||||||
},
|
},
|
||||||
select: { id: true, title: true, status: true },
|
select: { id: true, title: true, status: true },
|
||||||
})
|
})
|
||||||
|
|
@ -106,15 +107,8 @@ export async function saveTask(
|
||||||
})
|
})
|
||||||
if (!story) return { ok: false, code: 403, error: 'forbidden' }
|
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 productId = story.product_id
|
||||||
const sprintId = story.sprint_id ?? null
|
const sprintId = story.sprint_id ?? null
|
||||||
const sortOrder = (last?.sort_order ?? 0) + 1.0
|
|
||||||
const storyId = context.storyId
|
const storyId = context.storyId
|
||||||
|
|
||||||
const task = await createWithCodeRetry(
|
const task = await createWithCodeRetry(
|
||||||
|
|
@ -130,7 +124,7 @@ export async function saveTask(
|
||||||
description: description ?? null,
|
description: description ?? null,
|
||||||
implementation_plan: implementation_plan ?? null,
|
implementation_plan: implementation_plan ?? null,
|
||||||
priority,
|
priority,
|
||||||
sort_order: sortOrder,
|
sort_order: parseCodeNumber(code),
|
||||||
status: 'TO_DO',
|
status: 'TO_DO',
|
||||||
},
|
},
|
||||||
select: { id: true, title: true, status: true },
|
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' }
|
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 productId = story.product_id
|
||||||
const task = await createWithCodeRetry(
|
const task = await createWithCodeRetry(
|
||||||
() => generateNextTaskCode(productId),
|
() => generateNextTaskCode(productId),
|
||||||
|
|
@ -225,7 +214,7 @@ export async function createTaskAction(_prevState: unknown, formData: FormData)
|
||||||
title: parsed.data.title,
|
title: parsed.data.title,
|
||||||
description: parsed.data.description ?? null,
|
description: parsed.data.description ?? null,
|
||||||
priority: parsed.data.priority,
|
priority: parsed.data.priority,
|
||||||
sort_order: (last?.sort_order ?? 0) + 1.0,
|
sort_order: parseCodeNumber(code),
|
||||||
status: 'TO_DO',
|
status: 'TO_DO',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue