import { Prisma } from '@prisma/client' import { prisma } from '@/lib/prisma' const MAX_AUTO_CODE_ATTEMPTS = 3 function isCodeUniqueConflict(error: unknown): boolean { if (!(error instanceof Prisma.PrismaClientKnownRequestError)) return false if (error.code !== 'P2002') return false const target = (error.meta as { target?: string[] | string } | undefined)?.target if (!target) return false if (Array.isArray(target)) return target.includes('code') return target.includes('code') } /** * Generate an auto code, then run the create. If the insert collides with an * existing code (P2002 on the code column), regenerate and retry up to a small * number of attempts. Protects against the SELECT-MAX → INSERT race when two * concurrent requests pick the same next number. */ export async function createWithCodeRetry( generate: () => Promise, create: (code: string) => Promise, ): Promise { let lastError: unknown for (let attempt = 0; attempt < MAX_AUTO_CODE_ATTEMPTS; attempt++) { const code = await generate() try { return await create(code) } catch (e) { if (isCodeUniqueConflict(e)) { lastError = e continue } throw e } } throw lastError ?? new Error('Kon geen unieke code genereren') } const STORY_AUTO_RE = /^ST-(\d+)$/ const PBI_AUTO_RE = /^PBI-(\d+)$/ function nextSequential(existing: (string | null)[], pattern: RegExp): number { let max = 0 for (const c of existing) { if (!c) continue const m = c.match(pattern) if (m) { const n = Number.parseInt(m[1], 10) if (!Number.isNaN(n) && n > max) max = n } } return max + 1 } export async function generateNextStoryCode(productId: string): Promise { const stories = await prisma.story.findMany({ where: { product_id: productId }, select: { code: true }, }) const next = nextSequential(stories.map((s) => s.code), STORY_AUTO_RE) return `ST-${String(next).padStart(3, '0')}` } export async function generateNextPbiCode(productId: string): Promise { const pbis = await prisma.pbi.findMany({ where: { product_id: productId }, select: { code: true }, }) const next = nextSequential(pbis.map((p) => p.code), PBI_AUTO_RE) return `PBI-${next}` }