53 lines
1.7 KiB
SQL
53 lines
1.7 KiB
SQL
-- Payments invariants + audit log
|
|
-- Adds:
|
|
-- - payments.source / payments.entered_by / payments.idempotency_key
|
|
-- - payment_audit_events table
|
|
|
|
ALTER TABLE payments
|
|
ADD COLUMN IF NOT EXISTS source VARCHAR(32) NOT NULL DEFAULT 'manual_manager_entry',
|
|
ADD COLUMN IF NOT EXISTS entered_by UUID NULL,
|
|
ADD COLUMN IF NOT EXISTS idempotency_key VARCHAR(128) NULL;
|
|
|
|
-- Foreign key for entered_by
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint WHERE conname = 'payments_entered_by_fkey'
|
|
) THEN
|
|
ALTER TABLE payments
|
|
ADD CONSTRAINT payments_entered_by_fkey
|
|
FOREIGN KEY (entered_by) REFERENCES users(id)
|
|
ON DELETE SET NULL;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- Idempotency key uniqueness (global). If you prefer per-manager scoping,
|
|
-- change this to a composite unique index.
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_indexes WHERE indexname = 'payments_idempotency_key_unique'
|
|
) THEN
|
|
CREATE UNIQUE INDEX payments_idempotency_key_unique ON payments(idempotency_key) WHERE idempotency_key IS NOT NULL;
|
|
END IF;
|
|
END $$;
|
|
|
|
CREATE TABLE IF NOT EXISTS payment_audit_events (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
payment_id UUID NOT NULL REFERENCES payments(id) ON DELETE CASCADE,
|
|
actor_user_id UUID NULL REFERENCES users(id) ON DELETE SET NULL,
|
|
action VARCHAR(32) NOT NULL,
|
|
reason TEXT NULL,
|
|
"before" JSONB NULL,
|
|
"after" JSONB NULL,
|
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS payment_audit_events_payment_id_created_at_idx
|
|
ON payment_audit_events(payment_id, created_at DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS payment_audit_events_actor_user_id_created_at_idx
|
|
ON payment_audit_events(actor_user_id, created_at DESC);
|
|
|