新增三类证据与双人复核的人工未提交、逐原因冻结、晚到付款逐借款补记和实际退款追回;补齐对账与补证恢复界面,保护已消费依据、最终归属重验及全局交易唯一性。 验证:81 迁移、HTTP 95 组、浏览器 13 组、数据库约束 9 组;13 项有效变异被捕获,部分由下游或数据库保护检出。构建、类型、合同和治理检查通过;修复后同模型只读静态复核通过。 真实回单待验、整体业务验收未通过;S0 存量、人员、银行材料与业务独立验收未完成。3 个新增迁移须纳入获批发布清单。本次仅本地提交,不推送、不部署。
29 行
2.1 KiB
PL/PgSQL
29 行
2.1 KiB
PL/PgSQL
-- S5: each cause closes separately. No administrator unfreeze switch.
|
|
CREATE TABLE loan_freeze_reasons (
|
|
id UUID PRIMARY KEY, lender_id UUID NOT NULL REFERENCES loan_lenders(id) ON DELETE RESTRICT,
|
|
payment_batch_id UUID NOT NULL REFERENCES payment_batches(id) ON DELETE RESTRICT,
|
|
kind VARCHAR(32) NOT NULL CHECK(kind IN ('late_success','reservation_conflict','receivable')),
|
|
source_key VARCHAR(160) NOT NULL, status VARCHAR(16) NOT NULL DEFAULT 'active' CHECK(status IN ('active','closed')),
|
|
created_by UUID NOT NULL REFERENCES employees(id) ON DELETE RESTRICT, created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
closed_by UUID REFERENCES employees(id) ON DELETE RESTRICT, closed_at TIMESTAMP(3), closure_basis JSONB,
|
|
UNIQUE(kind,source_key), CHECK((status='closed')=(closed_by IS NOT NULL AND closed_at IS NOT NULL AND closure_basis IS NOT NULL))
|
|
);
|
|
CREATE INDEX loan_freeze_reasons_lender_id_status_idx ON loan_freeze_reasons(lender_id,status);
|
|
CREATE FUNCTION guard_loan_freeze_reason() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
DECLARE lender UUID;
|
|
BEGIN
|
|
IF TG_OP='DELETE' THEN RAISE EXCEPTION 'freeze history retained' USING ERRCODE='23514'; END IF;
|
|
SELECT r.lender_id INTO lender FROM payment_batches b JOIN loan_repayments r ON r.id=b.loan_repayment_id WHERE b.id=NEW.payment_batch_id;
|
|
IF lender IS DISTINCT FROM NEW.lender_id THEN RAISE EXCEPTION 'freeze source mismatch' USING ERRCODE='23514'; END IF;
|
|
PERFORM id FROM loan_lenders WHERE id=lender FOR UPDATE;
|
|
IF TG_OP='INSERT' THEN
|
|
IF NEW.status<>'active' OR NEW.closed_by IS NOT NULL OR NEW.closed_at IS NOT NULL OR NEW.closure_basis IS NOT NULL THEN
|
|
RAISE EXCEPTION 'new freeze must be active' USING ERRCODE='23514'; END IF;
|
|
ELSIF OLD.status<>'active' OR NEW.status<>'closed' OR
|
|
(to_jsonb(NEW)-ARRAY['status','closed_by','closed_at','closure_basis']) IS DISTINCT FROM (to_jsonb(OLD)-ARRAY['status','closed_by','closed_at','closure_basis']) THEN
|
|
RAISE EXCEPTION 'freeze history immutable' USING ERRCODE='23514';
|
|
END IF;
|
|
RETURN NEW;
|
|
END $$;
|
|
CREATE TRIGGER loan_freeze_history BEFORE INSERT OR UPDATE OR DELETE ON loan_freeze_reasons FOR EACH ROW EXECUTE FUNCTION guard_loan_freeze_reason();
|