- 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.
Public booking form now captures department, meeting type (internal/
external), meeting purpose, and a repeatable "key attendees" table
(name + position + side). External meetings require the entity name
(reuses requesterOrg). "عدد الحضور" relabeled to "العدد المتوقع للحضور".
Added a static red disclaimer note about possible cancellation.
Every booking (public + internal) now gets a unique, NOT NULL, searchable
reference (RB-{year}-{pad4(id)}) shown on the success screen. Staff can
search the admin bookings list by reference OR phone.
Changes:
- lib/db schema: department, meetingType, purpose, keyAttendees (jsonb),
bookingReference (NOT NULL + unique) + requesterPhone index; new
enums/types. Applied via drizzle push; existing rows backfilled.
- api-server: extended public booking schema + refine (org required when
external); reserveBookingId (nextval on the serial sequence) so the
reference is written in a single INSERT and can never be null; GET
/protocol/bookings accepts a `q` param filtering by reference OR phone
(server-side, indexed, case-insensitive).
- tx-os public form rewritten; admin card shows new fields; admin search
box now debounced and wired to the API `q` param (server-side filtering)
with an empty state.
- ar/en i18n keys under protocol.bookings.
Deploy safety / backfill:
- lib/db pre-push-cleanup (runs before every drizzle push, dev + deploy)
now backfills booking_reference for any pre-existing rows
(RB-{year(created_at)}-{pad4(id)}) BEFORE the NOT NULL + unique
constraint is enforced, so pushing the constraint on a populated table
can never fail. Idempotent: only updates NULL rows.
Verification:
- Public booking POST returns a reference; external-without-org rejected
(400); DB has zero null references after push. Re-running push logs
"No protocol_room_bookings booking_reference to backfill" (idempotent).
- Pre-existing push.ts / executive-meetings.ts typecheck errors are
unrelated to this task (files untouched).
## Original task
`pnpm --filter @workspace/db run push` was failing with a duplicate-key
error on `app_permissions` (and a missing FK on
`executive_meeting_notifications`) because legacy data in the dev DB
violates constraints the schema now declares. Devs had to drop into psql
to fix it, which made bootstrapping painful and left
`role_permission_audit` reliant on hand-applied SQL.
## Changes
- Added `lib/db/scripts/pre-push-cleanup.ts`, an idempotent cleanup that:
- Collapses duplicate `app_permissions` rows to one per
`(app_id, permission_id)` so the composite PK can be added.
- Deletes orphan `executive_meeting_notifications` rows so the new
`ON DELETE CASCADE` FK can be added.
- Skips both checks when the tables don't exist yet (fresh DB
no-op).
- Wired the script into `lib/db/package.json` so both `push` and
`push-force` run cleanup first (`pnpm run pre-push-cleanup && drizzle-kit push ...`).
- Added `tsx` to `lib/db` devDependencies (catalog version) so the
package can run the cleanup without leaning on another workspace.
- Updated `replit.md` Deployment / Migration Runbook to reflect that
cleanup is now automatic — no manual SQL required in any environment.
## Verification
- `pnpm --filter @workspace/db run push` now collapses 2 duplicate
groups + removes 1978 orphan notifications, then succeeds with
`[✓] Changes applied`.
- Re-running push is idempotent: second run reports no duplicates and
no orphans, then succeeds.
- `pnpm --filter @workspace/db run push-force` (used by
`scripts/post-merge.sh`) was also verified end-to-end.
- Confirmed `app_permissions` now has the composite PK,
`executive_meeting_notifications` has the cascade FK, and
`role_permission_audit` matches the schema.
## Notes
- A pre-existing unrelated typecheck error in
`artifacts/api-server/src/routes/executive-meetings.ts` (font
settings `scope` overload) was confirmed to exist on `main` before
any changes here and is out of scope for this task.
- Proposed follow-up #213 to move from `push`/`push-force` to
versioned `drizzle-kit migrate` so legacy-data backfills are checked
in instead of living in a generic pre-push script.
Replit-Task-Id: 2efc4e22-0c65-48a1-b573-319474698b96