Task #735: booking reference digits-only (no letters)
- makeBookingReference in artifacts/api-server/src/routes/protocol.ts now
returns `${year}-${pad4(id)}` instead of `RB-${year}-${pad4(id)}`.
- Backfilled 7 existing dev DB rows: stripped "RB-" prefix via
regexp_replace; verified 0 rows contain letters, unique/NOT NULL intact.
- lib/db/scripts/pre-push-cleanup.ts updated (architect-review finding):
NULL backfill now uses digits-only format, plus a new idempotent
normalization step that strips legacy "RB-" prefixes on every push.
- Frontend unchanged: bookingNumber() already strips non-digits; details
card and public booking success page display the raw reference which is
now digits-only. ilike search still works.
- tsc clean for tx-os and lib/db; API workflow restarted.
This commit is contained in:
@@ -442,10 +442,10 @@ async function lockRoomRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build a human-friendly booking reference (رقم الحجز) from a row id and its
|
// Build a human-friendly booking reference (رقم الحجز) from a row id and its
|
||||||
// creation date, e.g. "RB-2026-0042". Deterministic + unique because the id
|
// creation date, e.g. "2026-0042". Digits only (no letters, per user
|
||||||
// is unique.
|
// request). Deterministic + unique because the id is unique.
|
||||||
function makeBookingReference(id: number, when: Date): string {
|
function makeBookingReference(id: number, when: Date): string {
|
||||||
return `RB-${when.getFullYear()}-${String(id).padStart(4, "0")}`;
|
return `${when.getFullYear()}-${String(id).padStart(4, "0")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reserve the next booking id from the table's serial sequence so we can
|
// Reserve the next booking id from the table's serial sequence so we can
|
||||||
|
|||||||
@@ -85,8 +85,9 @@ async function main(): Promise<void> {
|
|||||||
// NOT NULL + unique constraint on protocol_room_bookings.booking_reference.
|
// NOT NULL + unique constraint on protocol_room_bookings.booking_reference.
|
||||||
// Without this, pushing the constraint against a populated table (any
|
// Without this, pushing the constraint against a populated table (any
|
||||||
// pre-existing booking predates the column) would fail. Reference format
|
// pre-existing booking predates the column) would fail. Reference format
|
||||||
// mirrors makeBookingReference() in the API: RB-{year}-{pad4(id)}, which
|
// mirrors makeBookingReference() in the API: {year}-{pad4(id)} (digits
|
||||||
// is unique because id is unique. Idempotent: only touches NULL rows.
|
// only, no letters), which is unique because id is unique. Idempotent:
|
||||||
|
// fills NULL rows and normalizes any legacy "RB-" prefixed rows.
|
||||||
if (await tableExists(client, "protocol_room_bookings")) {
|
if (await tableExists(client, "protocol_room_bookings")) {
|
||||||
const hasColumn = await client.query(
|
const hasColumn = await client.query(
|
||||||
`SELECT 1 FROM information_schema.columns
|
`SELECT 1 FROM information_schema.columns
|
||||||
@@ -98,10 +99,21 @@ async function main(): Promise<void> {
|
|||||||
const backfilled = await client.query(
|
const backfilled = await client.query(
|
||||||
`UPDATE protocol_room_bookings
|
`UPDATE protocol_room_bookings
|
||||||
SET booking_reference =
|
SET booking_reference =
|
||||||
'RB-' || EXTRACT(YEAR FROM created_at)::int
|
EXTRACT(YEAR FROM created_at)::int
|
||||||
|| '-' || LPAD(id::text, 4, '0')
|
|| '-' || LPAD(id::text, 4, '0')
|
||||||
WHERE booking_reference IS NULL`,
|
WHERE booking_reference IS NULL`,
|
||||||
);
|
);
|
||||||
|
const normalized = await client.query(
|
||||||
|
`UPDATE protocol_room_bookings
|
||||||
|
SET booking_reference = regexp_replace(booking_reference, '^RB-', '')
|
||||||
|
WHERE booking_reference LIKE 'RB-%'`,
|
||||||
|
);
|
||||||
|
const stripped = normalized.rowCount ?? 0;
|
||||||
|
if (stripped > 0) {
|
||||||
|
console.log(
|
||||||
|
`[pre-push] Normalized ${stripped} legacy RB- booking_reference value(s) to digits-only.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
const filled = backfilled.rowCount ?? 0;
|
const filled = backfilled.rowCount ?? 0;
|
||||||
if (filled > 0) {
|
if (filled > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
|
|||||||
Reference in New Issue
Block a user