fix: JSONB space-after-colon in all registry LIKE/regex patterns

PostgreSQL JSONB value::text serializes JSON with spaces after colons
("number": "B-2026-00001") but all LIKE patterns searched for the
no-space format ("number":"B-2026-00001"), causing zero matches and
every new entry getting sequence #1.

Fixed in allocateSequenceNumber, recalculateSequence, and debug-sequences.
Added PATCH handler to migrate old-format entries (BTG/SDT/USW/GRP)
to new single-letter format (B/S/U/G).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-11 22:22:33 +02:00
parent 4b61d07ffd
commit 5cb438ef67
2 changed files with 88 additions and 20 deletions
@@ -140,3 +140,69 @@ export async function POST() {
message: "All counters reset from actual entries (both old and new format).",
});
}
/**
* PATCH — Migrate old-format entries (BTG/SDT/USW/GRP) to new format (B/S/U/G).
* Rewrites the "number" field inside the JSONB value for matching entries.
*/
export async function PATCH() {
const session = await getAuthSession();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Map old 3-letter prefixes to new single-letter
const migrations: Array<{ old: string; new: string }> = [
{ old: "BTG", new: "B" },
{ old: "SDT", new: "S" },
{ old: "USW", new: "U" },
{ old: "GRP", new: "G" },
];
const results: Array<{ prefix: string; updated: number }> = [];
for (const m of migrations) {
// Find entries with old-format numbers: BTG-2026-IN-00001, SDT-2026-OUT-00002, etc.
const entries = await prisma.$queryRawUnsafe<
Array<{ key: string; num: string }>
>(`
SELECT key,
SUBSTRING(value::text FROM '"number": "([^"]+)"') AS num
FROM "KeyValueStore"
WHERE namespace = 'registratura'
AND key LIKE 'entry:%'
AND value::text ~ '"number": "${m.old}-[0-9]{4}-(IN|OUT|INT)-[0-9]{5}"'
`);
let updated = 0;
for (const entry of entries) {
if (!entry.num) continue;
// Parse: SDT-2026-OUT-00001 → S-2026-00001
const match = entry.num.match(
new RegExp(`^${m.old}-(\\d{4})-(?:IN|OUT|INT)-(\\d{5})$`)
);
if (!match) continue;
const newNumber = `${m.new}-${match[1]}-${match[2]}`;
// Update the JSONB value — replace the number field
await prisma.$executeRawUnsafe(`
UPDATE "KeyValueStore"
SET value = jsonb_set(value, '{number}', $1::jsonb)
WHERE namespace = 'registratura'
AND key = $2
`, JSON.stringify(newNumber), entry.key);
updated++;
}
results.push({ prefix: m.old, updated });
}
return NextResponse.json({
success: true,
migrations: results,
message: "Old-format entries migrated to new format. Run POST to reset counters.",
});
}
@@ -258,6 +258,7 @@ export async function allocateSequenceNumber(
// New format: B-2026-00001
// NOTE: Use [0-9] instead of \d — PostgreSQL POSIX regex may not support \d
// NOTE: JSONB value::text serializes with a space after colons ("number": "...")
const newLike = `%"number": "${companyPrefix}-${yr}-%"%`;
const newRegex = `${companyPrefix}-${yr}-([0-9]{5})`;
const newMaxRows = await tx.$queryRaw<Array<{ maxSeq: number | null }>>`
@@ -335,6 +336,7 @@ export async function recalculateSequence(
// Find max from new format (B-2026-00001)
// NOTE: Use [0-9] instead of \d — PostgreSQL POSIX regex may not support \d
// NOTE: JSONB value::text serializes with a space after colons ("number": "...")
const newLike = `%"number": "${companyPrefix}-${yr}-%"%`;
const newRegex = `${companyPrefix}-${yr}-([0-9]{5})`;
const newRows = await prisma.$queryRaw<Array<{ maxSeq: number | null }>>`