import { prisma } from '@/lib/prisma' const STORY_AUTO_RE = /^ST-(\d+)$/ const PBI_AUTO_RE = /^PBI-(\d+)$/ const VALID_CODE_RE = /^[A-Za-z0-9._-]+$/ export const MAX_CODE_LENGTH = 30 export function isValidCode(code: string): boolean { return code.length > 0 && code.length <= MAX_CODE_LENGTH && VALID_CODE_RE.test(code) } export function normalizeCode(input: string | null | undefined): string | null { if (input == null) return null const trimmed = input.trim() return trimmed === '' ? null : trimmed } 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}` } export function deriveTaskCode(storyCode: string | null, indexOneBased: number): string | null { if (!storyCode) return null return `${storyCode}.${indexOneBased}` }