-- 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);