fix: lead_requests table never existed, and reference_numbers migration was unconditionally broken
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.
This commit is contained in:
parent
b5dea58ed4
commit
70e63c6aff
6 changed files with 155 additions and 60 deletions
|
|
@ -1,27 +1,19 @@
|
|||
DROP TRIGGER IF EXISTS trg_verifications_reference_number ON verifications;
|
||||
DROP TRIGGER IF EXISTS trg_support_tickets_reference_number ON support_tickets;
|
||||
DROP TRIGGER IF EXISTS trg_payments_reference_number ON payments;
|
||||
DROP TRIGGER IF EXISTS trg_job_applications_reference_number ON job_applications;
|
||||
DROP TRIGGER IF EXISTS trg_lead_requests_reference_number ON lead_requests;
|
||||
DROP TRIGGER IF EXISTS trg_users_reference_number ON users;
|
||||
|
||||
DROP FUNCTION IF EXISTS set_verification_reference_number();
|
||||
DROP FUNCTION IF EXISTS set_ticket_reference_number();
|
||||
DROP FUNCTION IF EXISTS set_payment_reference_number();
|
||||
DROP FUNCTION IF EXISTS set_job_application_reference_number();
|
||||
DROP FUNCTION IF EXISTS set_lead_request_reference_number();
|
||||
DROP FUNCTION IF EXISTS set_user_reference_number();
|
||||
|
||||
ALTER TABLE verifications DROP COLUMN IF EXISTS reference_number;
|
||||
ALTER TABLE support_tickets DROP COLUMN IF EXISTS reference_number;
|
||||
ALTER TABLE payments DROP COLUMN IF EXISTS reference_number;
|
||||
ALTER TABLE job_applications DROP COLUMN IF EXISTS reference_number;
|
||||
ALTER TABLE lead_requests DROP COLUMN IF EXISTS reference_number;
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS reference_number;
|
||||
|
||||
DROP SEQUENCE IF EXISTS verification_number_seq;
|
||||
DROP SEQUENCE IF EXISTS ticket_number_seq;
|
||||
DROP SEQUENCE IF EXISTS payment_number_seq;
|
||||
DROP SEQUENCE IF EXISTS job_application_number_seq;
|
||||
DROP SEQUENCE IF EXISTS lead_request_number_seq;
|
||||
DROP SEQUENCE IF EXISTS user_number_seq;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
-- 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;
|
||||
|
|
@ -57,42 +62,6 @@ CREATE TRIGGER trg_payments_reference_number
|
|||
BEFORE INSERT ON payments
|
||||
FOR EACH ROW EXECUTE FUNCTION set_payment_reference_number();
|
||||
|
||||
-- ============ job_applications ============
|
||||
CREATE SEQUENCE IF NOT EXISTS job_application_number_seq;
|
||||
ALTER TABLE job_applications ADD COLUMN IF NOT EXISTS reference_number VARCHAR;
|
||||
|
||||
CREATE OR REPLACE FUNCTION set_job_application_reference_number() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.reference_number IS NULL THEN
|
||||
NEW.reference_number := 'NXT-JOB-' || to_char(NOW(), 'YY') || '-' || lpad(nextval('job_application_number_seq')::text, 6, '0');
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_job_applications_reference_number ON job_applications;
|
||||
CREATE TRIGGER trg_job_applications_reference_number
|
||||
BEFORE INSERT ON job_applications
|
||||
FOR EACH ROW EXECUTE FUNCTION set_job_application_reference_number();
|
||||
|
||||
-- ============ lead_requests ============
|
||||
CREATE SEQUENCE IF NOT EXISTS lead_request_number_seq;
|
||||
ALTER TABLE lead_requests ADD COLUMN IF NOT EXISTS reference_number VARCHAR;
|
||||
|
||||
CREATE OR REPLACE FUNCTION set_lead_request_reference_number() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.reference_number IS NULL THEN
|
||||
NEW.reference_number := 'NXT-LED-' || to_char(NOW(), 'YY') || '-' || lpad(nextval('lead_request_number_seq')::text, 6, '0');
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_lead_requests_reference_number ON lead_requests;
|
||||
CREATE TRIGGER trg_lead_requests_reference_number
|
||||
BEFORE INSERT ON lead_requests
|
||||
FOR EACH ROW EXECUTE FUNCTION set_lead_request_reference_number();
|
||||
|
||||
-- ============ users ============
|
||||
CREATE SEQUENCE IF NOT EXISTS user_number_seq;
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS reference_number VARCHAR;
|
||||
|
|
@ -127,14 +96,6 @@ BEGIN
|
|||
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 job_applications WHERE reference_number IS NULL ORDER BY created_at, id LOOP
|
||||
UPDATE job_applications SET reference_number = 'NXT-JOB-' || to_char(r.created_at, 'YY') || '-' || lpad(nextval('job_application_number_seq')::text, 6, '0') WHERE id = r.id;
|
||||
END LOOP;
|
||||
|
||||
FOR r IN SELECT id, created_at FROM lead_requests WHERE reference_number IS NULL ORDER BY created_at, id LOOP
|
||||
UPDATE lead_requests SET reference_number = 'NXT-LED-' || to_char(r.created_at, 'YY') || '-' || lpad(nextval('lead_request_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;
|
||||
|
|
@ -144,8 +105,6 @@ END $$;
|
|||
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 job_applications ALTER COLUMN reference_number SET NOT NULL;
|
||||
ALTER TABLE lead_requests ALTER COLUMN reference_number SET NOT NULL;
|
||||
ALTER TABLE users ALTER COLUMN reference_number SET NOT NULL;
|
||||
|
||||
DO $$
|
||||
|
|
@ -159,12 +118,6 @@ BEGIN
|
|||
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 = 'job_applications_reference_number_key') THEN
|
||||
ALTER TABLE job_applications ADD CONSTRAINT job_applications_reference_number_key UNIQUE (reference_number);
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'lead_requests_reference_number_key') THEN
|
||||
ALTER TABLE lead_requests ADD CONSTRAINT lead_requests_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;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
DROP TABLE IF EXISTS job_seeker_documents;
|
||||
DROP TABLE IF EXISTS job_applications;
|
||||
DROP FUNCTION IF EXISTS set_job_application_reference_number();
|
||||
DROP SEQUENCE IF EXISTS job_application_number_seq;
|
||||
|
||||
ALTER TABLE job_seeker_profiles
|
||||
DROP COLUMN IF EXISTS full_name,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,45 @@ CREATE INDEX IF NOT EXISTS idx_job_applications_job_id ON job_applications(job_i
|
|||
CREATE INDEX IF NOT EXISTS idx_job_applications_applicant_user_id ON job_applications(applicant_user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_job_applications_status ON job_applications(status);
|
||||
|
||||
-- Human-readable reference number (NXT-JOB-YY-000001), same scheme as
|
||||
-- verifications/payments/etc — kept here instead of in
|
||||
-- 20260719120000_reference_numbers.up.sql because that migration ran before
|
||||
-- this table existed and unconditionally ALTERing a nonexistent table broke
|
||||
-- the whole migration chain.
|
||||
CREATE SEQUENCE IF NOT EXISTS job_application_number_seq;
|
||||
ALTER TABLE job_applications ADD COLUMN IF NOT EXISTS reference_number VARCHAR;
|
||||
|
||||
CREATE OR REPLACE FUNCTION set_job_application_reference_number() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.reference_number IS NULL THEN
|
||||
NEW.reference_number := 'NXT-JOB-' || to_char(NOW(), 'YY') || '-' || lpad(nextval('job_application_number_seq')::text, 6, '0');
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_job_applications_reference_number ON job_applications;
|
||||
CREATE TRIGGER trg_job_applications_reference_number
|
||||
BEFORE INSERT ON job_applications
|
||||
FOR EACH ROW EXECUTE FUNCTION set_job_application_reference_number();
|
||||
|
||||
DO $$
|
||||
DECLARE r RECORD;
|
||||
BEGIN
|
||||
FOR r IN SELECT id, created_at FROM job_applications WHERE reference_number IS NULL ORDER BY created_at, id LOOP
|
||||
UPDATE job_applications SET reference_number = 'NXT-JOB-' || to_char(r.created_at, 'YY') || '-' || lpad(nextval('job_application_number_seq')::text, 6, '0') WHERE id = r.id;
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE job_applications ALTER COLUMN reference_number SET NOT NULL;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'job_applications_reference_number_key') THEN
|
||||
ALTER TABLE job_applications ADD CONSTRAINT job_applications_reference_number_key UNIQUE (reference_number);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- job_seeker_documents backs JobSeekerRepository::create_document/list_documents/
|
||||
-- delete_document, referenced from apps/job_seekers but never migrated in.
|
||||
CREATE TABLE IF NOT EXISTS job_seeker_documents (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
DROP TABLE IF EXISTS lead_requests;
|
||||
DROP FUNCTION IF EXISTS set_lead_request_reference_number();
|
||||
DROP SEQUENCE IF EXISTS lead_request_number_seq;
|
||||
-- tracecoin_wallets/tracecoin_ledger intentionally left in place — an
|
||||
-- already-deployed migration (20260318233000_tracecoin_ledger_immutable)
|
||||
-- has triggers depending on tracecoin_ledger, and other data may reference
|
||||
-- these tables by now.
|
||||
102
crates/db/migrations/20260721030000_create_lead_requests.up.sql
Normal file
102
crates/db/migrations/20260721030000_create_lead_requests.up.sql
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
-- lead_requests backs the "professional sends a request to contact a
|
||||
-- customer's requirement" flow (LeadRequestRepository, crates/db/src/models/
|
||||
-- lead_request.rs; send_lead_request in crates/contracts/src/profession_shared.rs;
|
||||
-- list_requests/approve_request/reject_request in apps/customers/src/handlers.rs).
|
||||
-- The table was only ever defined in disabled (.skip) migrations, and those
|
||||
-- versions didn't match the columns the current Rust code actually reads/writes
|
||||
-- (professional_user_id, customer_user_id, remarks, requested_at, resolved_at) —
|
||||
-- so this flow has never worked. Schema below matches the live Rust code exactly.
|
||||
CREATE TABLE IF NOT EXISTS lead_requests (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
lead_id UUID REFERENCES leads(id) ON DELETE CASCADE,
|
||||
user_role_profile_id UUID,
|
||||
professional_user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
customer_user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
||||
tracecoins_reserved INTEGER NOT NULL DEFAULT 25,
|
||||
remarks TEXT,
|
||||
expires_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '7 days'),
|
||||
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
resolved_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(lead_id, professional_user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_lead_requests_lead_id ON lead_requests(lead_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_lead_requests_user_role_profile_id ON lead_requests(user_role_profile_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_lead_requests_professional_user_id ON lead_requests(professional_user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_lead_requests_customer_user_id ON lead_requests(customer_user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_lead_requests_status ON lead_requests(status);
|
||||
|
||||
-- Human-readable reference number (NXT-LED-YY-000001), same scheme as
|
||||
-- verifications/payments/job_applications/etc.
|
||||
CREATE SEQUENCE IF NOT EXISTS lead_request_number_seq;
|
||||
ALTER TABLE lead_requests ADD COLUMN IF NOT EXISTS reference_number VARCHAR;
|
||||
|
||||
CREATE OR REPLACE FUNCTION set_lead_request_reference_number() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.reference_number IS NULL THEN
|
||||
NEW.reference_number := 'NXT-LED-' || to_char(NOW(), 'YY') || '-' || lpad(nextval('lead_request_number_seq')::text, 6, '0');
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_lead_requests_reference_number ON lead_requests;
|
||||
CREATE TRIGGER trg_lead_requests_reference_number
|
||||
BEFORE INSERT ON lead_requests
|
||||
FOR EACH ROW EXECUTE FUNCTION set_lead_request_reference_number();
|
||||
|
||||
DO $$
|
||||
DECLARE r RECORD;
|
||||
BEGIN
|
||||
FOR r IN SELECT id, created_at FROM lead_requests WHERE reference_number IS NULL ORDER BY created_at, id LOOP
|
||||
UPDATE lead_requests SET reference_number = 'NXT-LED-' || to_char(r.created_at, 'YY') || '-' || lpad(nextval('lead_request_number_seq')::text, 6, '0') WHERE id = r.id;
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE lead_requests ALTER COLUMN reference_number SET NOT NULL;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'lead_requests_reference_number_key') THEN
|
||||
ALTER TABLE lead_requests ADD CONSTRAINT lead_requests_reference_number_key UNIQUE (reference_number);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- tracecoin_wallets/tracecoin_ledger back the Tracecoin reservation that
|
||||
-- happens when a lead request is sent (TracecoinWalletRepository). Like
|
||||
-- lead_requests, they were only ever defined in disabled (.skip) migrations
|
||||
-- — and even those used stale column names (`type`/`reason` instead of the
|
||||
-- `transaction_type`/`reference_type` the current Rust code writes). An
|
||||
-- earlier active migration (20260318233000_tracecoin_ledger_immutable,
|
||||
-- already deployed) creates triggers ON tracecoin_ledger assuming it already
|
||||
-- exists; CREATE TABLE IF NOT EXISTS here is a safe no-op if that migration
|
||||
-- already succeeded against a table created some other way, and fills the
|
||||
-- gap if it didn't.
|
||||
CREATE TABLE IF NOT EXISTS tracecoin_wallets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
|
||||
balance INTEGER NOT NULL DEFAULT 0,
|
||||
reserved INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tracecoin_ledger (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
wallet_id UUID NOT NULL REFERENCES tracecoin_wallets(id),
|
||||
transaction_type VARCHAR(20) NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
reference_type VARCHAR(50),
|
||||
reference_id UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- In case tracecoin_ledger already existed under the older, stale column
|
||||
-- names from the disabled migration's design.
|
||||
ALTER TABLE tracecoin_ledger ADD COLUMN IF NOT EXISTS transaction_type VARCHAR(20);
|
||||
ALTER TABLE tracecoin_ledger ADD COLUMN IF NOT EXISTS reference_type VARCHAR(50);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tracecoin_ledger_wallet_id ON tracecoin_ledger(wallet_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_tracecoin_ledger_reference_id ON tracecoin_ledger(reference_id);
|
||||
Loading…
Add table
Reference in a new issue