Tracing "can a professional send a request to contact a customer" surfaced two more breaks in the same family as everything else fixed this session: 1. crates/db/migrations/20260719120000_reference_numbers.up.sql (not yet pushed) unconditionally ran `ALTER TABLE job_applications ...` and `ALTER TABLE lead_requests ...`. Neither table was ever created by any active (non .skip) migration, so this migration would fail outright on a clean apply and block every migration after it in the chain — including all of this session's other fixes. Moved the reference_number column/trigger/backfill logic for job_applications and lead_requests into their own table-creation migrations instead, where the tables are guaranteed to exist. 2. lead_requests (LeadRequestRepository, send_lead_request in profession_shared.rs, list_requests/approve_request/reject_request in apps/customers/src/handlers.rs) was only ever defined in disabled .skip migrations, and even those didn't match the columns the current Rust code reads/writes (missing professional_user_id and reference_number; `message`/`customer_user_id`-only shape instead of `remarks`/`requested_at`/`resolved_at`). Added a migration with the schema the live code actually needs. 3. Same root cause found for tracecoin_wallets/tracecoin_ledger — only ever defined in .skip migrations, and with stale column names (`type`/`reason` vs. the `transaction_type`/`reference_type` the Tracecoin reservation code in TracecoinWalletRepository actually uses. These back the credit reservation that happens when a lead request is sent. Created them with CREATE TABLE IF NOT EXISTS (safe no-op if they already exist under any name/history) plus a best-effort ADD COLUMN IF NOT EXISTS fallback for the stale-name scenario. NOTE: an already-deployed migration (20260318233000_tracecoin_ledger_immutable, already on origin) creates triggers directly on tracecoin_ledger, assuming it already exists. That migration was NOT touched here since editing already-pushed migration history is out of scope for a blind fix — if tracecoin_ledger doesn't already exist in the real database, that migration has been failing since it was deployed and needs a human to check `_sqlx_migrations` / actual schema state directly.
124 lines
5.7 KiB
PL/PgSQL
124 lines
5.7 KiB
PL/PgSQL
-- Human-readable, sequential, unique reference numbers for user-facing entities.
|
|
-- Format: NXT-{TYPE}-{YY}-{000001}. Generated via BEFORE INSERT trigger so every
|
|
-- existing and future insert path (there are many, scattered across apps) picks
|
|
-- one up automatically without needing per-call-site application changes.
|
|
--
|
|
-- job_applications and lead_requests are handled in their own creation
|
|
-- migrations instead of here — this file used to ALTER those tables
|
|
-- unconditionally, which broke the whole migration chain because neither
|
|
-- table was ever actually created by any active (non .skip) migration.
|
|
|
|
-- ============ verifications ============
|
|
CREATE SEQUENCE IF NOT EXISTS verification_number_seq;
|
|
ALTER TABLE verifications ADD COLUMN IF NOT EXISTS reference_number VARCHAR;
|
|
|
|
CREATE OR REPLACE FUNCTION set_verification_reference_number() RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.reference_number IS NULL THEN
|
|
NEW.reference_number := 'NXT-VER-' || to_char(NOW(), 'YY') || '-' || lpad(nextval('verification_number_seq')::text, 6, '0');
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS trg_verifications_reference_number ON verifications;
|
|
CREATE TRIGGER trg_verifications_reference_number
|
|
BEFORE INSERT ON verifications
|
|
FOR EACH ROW EXECUTE FUNCTION set_verification_reference_number();
|
|
|
|
-- ============ support_tickets ============
|
|
CREATE SEQUENCE IF NOT EXISTS ticket_number_seq;
|
|
ALTER TABLE support_tickets ADD COLUMN IF NOT EXISTS reference_number VARCHAR;
|
|
|
|
CREATE OR REPLACE FUNCTION set_ticket_reference_number() RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.reference_number IS NULL THEN
|
|
NEW.reference_number := 'NXT-TCK-' || to_char(NOW(), 'YY') || '-' || lpad(nextval('ticket_number_seq')::text, 6, '0');
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS trg_support_tickets_reference_number ON support_tickets;
|
|
CREATE TRIGGER trg_support_tickets_reference_number
|
|
BEFORE INSERT ON support_tickets
|
|
FOR EACH ROW EXECUTE FUNCTION set_ticket_reference_number();
|
|
|
|
-- ============ payments ============
|
|
CREATE SEQUENCE IF NOT EXISTS payment_number_seq;
|
|
ALTER TABLE payments ADD COLUMN IF NOT EXISTS reference_number VARCHAR;
|
|
|
|
CREATE OR REPLACE FUNCTION set_payment_reference_number() RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.reference_number IS NULL THEN
|
|
NEW.reference_number := 'NXT-PAY-' || to_char(NOW(), 'YY') || '-' || lpad(nextval('payment_number_seq')::text, 6, '0');
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS trg_payments_reference_number ON payments;
|
|
CREATE TRIGGER trg_payments_reference_number
|
|
BEFORE INSERT ON payments
|
|
FOR EACH ROW EXECUTE FUNCTION set_payment_reference_number();
|
|
|
|
-- ============ users ============
|
|
CREATE SEQUENCE IF NOT EXISTS user_number_seq;
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS reference_number VARCHAR;
|
|
|
|
CREATE OR REPLACE FUNCTION set_user_reference_number() RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.reference_number IS NULL THEN
|
|
NEW.reference_number := 'NXT-USR-' || to_char(NOW(), 'YY') || '-' || lpad(nextval('user_number_seq')::text, 6, '0');
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS trg_users_reference_number ON users;
|
|
CREATE TRIGGER trg_users_reference_number
|
|
BEFORE INSERT ON users
|
|
FOR EACH ROW EXECUTE FUNCTION set_user_reference_number();
|
|
|
|
-- ============ backfill existing rows, oldest first ============
|
|
DO $$
|
|
DECLARE r RECORD;
|
|
BEGIN
|
|
FOR r IN SELECT id, created_at FROM verifications WHERE reference_number IS NULL ORDER BY created_at, id LOOP
|
|
UPDATE verifications SET reference_number = 'NXT-VER-' || to_char(r.created_at, 'YY') || '-' || lpad(nextval('verification_number_seq')::text, 6, '0') WHERE id = r.id;
|
|
END LOOP;
|
|
|
|
FOR r IN SELECT id, created_at FROM support_tickets WHERE reference_number IS NULL ORDER BY created_at, id LOOP
|
|
UPDATE support_tickets SET reference_number = 'NXT-TCK-' || to_char(r.created_at, 'YY') || '-' || lpad(nextval('ticket_number_seq')::text, 6, '0') WHERE id = r.id;
|
|
END LOOP;
|
|
|
|
FOR r IN SELECT id, created_at FROM payments WHERE reference_number IS NULL ORDER BY created_at, id LOOP
|
|
UPDATE payments SET reference_number = 'NXT-PAY-' || to_char(r.created_at, 'YY') || '-' || lpad(nextval('payment_number_seq')::text, 6, '0') WHERE id = r.id;
|
|
END LOOP;
|
|
|
|
FOR r IN SELECT id, created_at FROM users WHERE reference_number IS NULL ORDER BY created_at, id LOOP
|
|
UPDATE users SET reference_number = 'NXT-USR-' || to_char(r.created_at, 'YY') || '-' || lpad(nextval('user_number_seq')::text, 6, '0') WHERE id = r.id;
|
|
END LOOP;
|
|
END $$;
|
|
|
|
-- ============ enforce NOT NULL + UNIQUE now that every row has one ============
|
|
ALTER TABLE verifications ALTER COLUMN reference_number SET NOT NULL;
|
|
ALTER TABLE support_tickets ALTER COLUMN reference_number SET NOT NULL;
|
|
ALTER TABLE payments ALTER COLUMN reference_number SET NOT NULL;
|
|
ALTER TABLE users ALTER COLUMN reference_number SET NOT NULL;
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'verifications_reference_number_key') THEN
|
|
ALTER TABLE verifications ADD CONSTRAINT verifications_reference_number_key UNIQUE (reference_number);
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'support_tickets_reference_number_key') THEN
|
|
ALTER TABLE support_tickets ADD CONSTRAINT support_tickets_reference_number_key UNIQUE (reference_number);
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'payments_reference_number_key') THEN
|
|
ALTER TABLE payments ADD CONSTRAINT payments_reference_number_key UNIQUE (reference_number);
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_reference_number_key') THEN
|
|
ALTER TABLE users ADD CONSTRAINT users_reference_number_key UNIQUE (reference_number);
|
|
END IF;
|
|
END $$;
|