fix(ST-511): retry on auto-code unique conflict in story and pbi create
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c62de4c0d0
commit
2663819eef
2 changed files with 59 additions and 37 deletions
|
|
@ -8,7 +8,7 @@ import { prisma } from '@/lib/prisma'
|
|||
import { SessionData, sessionOptions } from '@/lib/session'
|
||||
import { getAccessibleProduct } from '@/lib/product-access'
|
||||
import { isValidCode, MAX_CODE_LENGTH, normalizeCode } from '@/lib/code'
|
||||
import { generateNextPbiCode } from '@/lib/code-server'
|
||||
import { createWithCodeRetry, generateNextPbiCode } from '@/lib/code-server'
|
||||
|
||||
async function getSession() {
|
||||
return getIronSession<SessionData>(await cookies(), sessionOptions)
|
||||
|
|
@ -49,14 +49,12 @@ export async function createPbiAction(_prevState: unknown, formData: FormData) {
|
|||
const product = await getAccessibleProduct(parsed.data.productId, session.userId)
|
||||
if (!product) return { error: 'Product niet gevonden' }
|
||||
|
||||
let code = normalizeCode(parsed.data.code)
|
||||
if (code !== null && !isValidCode(code)) {
|
||||
const manualCode = normalizeCode(parsed.data.code)
|
||||
if (manualCode !== null && !isValidCode(manualCode)) {
|
||||
return { error: { code: ['Code mag alleen letters, cijfers, punten, koppeltekens of underscores bevatten'] } }
|
||||
}
|
||||
if (code === null) {
|
||||
code = await generateNextPbiCode(parsed.data.productId)
|
||||
} else {
|
||||
const dup = await prisma.pbi.findFirst({ where: { product_id: parsed.data.productId, code } })
|
||||
if (manualCode) {
|
||||
const dup = await prisma.pbi.findFirst({ where: { product_id: parsed.data.productId, code: manualCode } })
|
||||
if (dup) return { error: { code: ['Deze code is al in gebruik binnen dit product'] } }
|
||||
}
|
||||
|
||||
|
|
@ -66,16 +64,29 @@ export async function createPbiAction(_prevState: unknown, formData: FormData) {
|
|||
})
|
||||
const sort_order = (last?.sort_order ?? 0) + 1.0
|
||||
|
||||
const pbi = await prisma.pbi.create({
|
||||
data: {
|
||||
product_id: parsed.data.productId,
|
||||
code,
|
||||
title: parsed.data.title,
|
||||
description: parsed.data.description ?? null,
|
||||
priority: parsed.data.priority,
|
||||
sort_order,
|
||||
},
|
||||
})
|
||||
const insert = (code: string | null) =>
|
||||
prisma.pbi.create({
|
||||
data: {
|
||||
product_id: parsed.data.productId,
|
||||
code,
|
||||
title: parsed.data.title,
|
||||
description: parsed.data.description ?? null,
|
||||
priority: parsed.data.priority,
|
||||
sort_order,
|
||||
},
|
||||
})
|
||||
|
||||
let pbi
|
||||
try {
|
||||
pbi = manualCode
|
||||
? await insert(manualCode)
|
||||
: await createWithCodeRetry(
|
||||
() => generateNextPbiCode(parsed.data.productId),
|
||||
(code) => insert(code),
|
||||
)
|
||||
} catch {
|
||||
return { error: { code: ['Kon geen unieke code genereren — probeer opnieuw'] } }
|
||||
}
|
||||
|
||||
revalidatePath(`/products/${parsed.data.productId}`)
|
||||
return { success: true, pbi }
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { SessionData, sessionOptions } from '@/lib/session'
|
|||
import { getAccessibleProduct, productAccessFilter } from '@/lib/product-access'
|
||||
import { requireProductWriter } from '@/lib/auth'
|
||||
import { isValidCode, MAX_CODE_LENGTH, normalizeCode } from '@/lib/code'
|
||||
import { generateNextStoryCode } from '@/lib/code-server'
|
||||
import { createWithCodeRetry, generateNextStoryCode } from '@/lib/code-server'
|
||||
|
||||
async function getSession() {
|
||||
return getIronSession<SessionData>(await cookies(), sessionOptions)
|
||||
|
|
@ -68,14 +68,12 @@ export async function createStoryAction(_prevState: unknown, formData: FormData)
|
|||
})
|
||||
if (!pbi) return { error: 'PBI niet gevonden' }
|
||||
|
||||
let code = normalizeCode(parsed.data.code)
|
||||
if (code !== null && !isValidCode(code)) {
|
||||
const manualCode = normalizeCode(parsed.data.code)
|
||||
if (manualCode !== null && !isValidCode(manualCode)) {
|
||||
return { error: { code: ['Code mag alleen letters, cijfers, punten, koppeltekens of underscores bevatten'] } }
|
||||
}
|
||||
if (code === null) {
|
||||
code = await generateNextStoryCode(pbi.product_id)
|
||||
} else {
|
||||
const dup = await prisma.story.findFirst({ where: { product_id: pbi.product_id, code } })
|
||||
if (manualCode) {
|
||||
const dup = await prisma.story.findFirst({ where: { product_id: pbi.product_id, code: manualCode } })
|
||||
if (dup) return { error: { code: ['Deze code is al in gebruik binnen dit product'] } }
|
||||
}
|
||||
|
||||
|
|
@ -85,19 +83,32 @@ export async function createStoryAction(_prevState: unknown, formData: FormData)
|
|||
})
|
||||
const sort_order = (last?.sort_order ?? 0) + 1.0
|
||||
|
||||
const story = await prisma.story.create({
|
||||
data: {
|
||||
pbi_id: parsed.data.pbiId,
|
||||
product_id: pbi.product_id,
|
||||
code,
|
||||
title: parsed.data.title,
|
||||
description: parsed.data.description ?? null,
|
||||
acceptance_criteria: parsed.data.acceptance_criteria ?? null,
|
||||
priority: parsed.data.priority,
|
||||
sort_order,
|
||||
status: 'OPEN',
|
||||
},
|
||||
})
|
||||
const insert = (code: string | null) =>
|
||||
prisma.story.create({
|
||||
data: {
|
||||
pbi_id: parsed.data.pbiId,
|
||||
product_id: pbi.product_id,
|
||||
code,
|
||||
title: parsed.data.title,
|
||||
description: parsed.data.description ?? null,
|
||||
acceptance_criteria: parsed.data.acceptance_criteria ?? null,
|
||||
priority: parsed.data.priority,
|
||||
sort_order,
|
||||
status: 'OPEN',
|
||||
},
|
||||
})
|
||||
|
||||
let story
|
||||
try {
|
||||
story = manualCode
|
||||
? await insert(manualCode)
|
||||
: await createWithCodeRetry(
|
||||
() => generateNextStoryCode(pbi.product_id),
|
||||
(code) => insert(code),
|
||||
)
|
||||
} catch {
|
||||
return { error: { code: ['Kon geen unieke code genereren — probeer opnieuw'] } }
|
||||
}
|
||||
|
||||
revalidatePath(`/products/${pbi.product_id}`)
|
||||
return { success: true, story }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue