汇总已落盘的借款本金与还款、兼容历史编号的周期化编号、付款责任与回单核验、报销进度和排序、PWA/采购事务、错误原因与日期校验及ERP退役。 统一共享预算池锁序和锁后判断,恢复差旅原预算选择,补原因码固定夹具及反向完整性检查。 纳入配套迁移、测试、受管QA、分层验收合同、规约及任务记录;原始证据留在ignored artifacts。 关联任务:09-17-company-loan-principal-repayment、09-17-erp-exclusive-retirement、09-18-reimbursement-periodic-numbering、09-08-reimbursement-payment-closeout、09-18-local-concurrency-product-fixes、09-19-budget-pool-concurrency、09-05-error-explainability-payment-readiness。 预算补审由Claude定向验证2文件928项通过,原4446/28及r6结果仍仅对应各自历史输入;本轮未重跑测试。 完整新候选门禁、R1真实入口接入与12项公网义务仍开放。生产编号未启用、差旅仍冻结;不推送、不部署,不宣称发布资格。
340 行
18 KiB
PL/PgSQL
340 行
18 KiB
PL/PgSQL
-- AlterEnum
|
|
ALTER TYPE "PaymentBatchEntityType" ADD VALUE 'loan_repayment';
|
|
|
|
-- AlterTable
|
|
ALTER TABLE "payment_batches" ADD COLUMN "loan_repayment_id" UUID;
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "loan_lenders" (
|
|
"id" UUID NOT NULL,
|
|
"legal_entity_id" UUID NOT NULL,
|
|
"name" VARCHAR(120) NOT NULL,
|
|
"employee_id" UUID,
|
|
"identity_status" VARCHAR(16) NOT NULL DEFAULT 'unverified',
|
|
"identity_version" INTEGER NOT NULL DEFAULT 1,
|
|
"account_version" INTEGER NOT NULL DEFAULT 1,
|
|
"identity_confirmed_by" UUID,
|
|
"identity_confirmed_at" TIMESTAMP(3),
|
|
"identity_confirmation" TEXT,
|
|
"bank_institution_id" UUID NOT NULL,
|
|
"bank_name" VARCHAR(200) NOT NULL,
|
|
"bank_account_encrypted" TEXT NOT NULL,
|
|
"account_name_encrypted" TEXT NOT NULL,
|
|
"archived_at" TIMESTAMP(3),
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"created_by" UUID NOT NULL,
|
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
|
"updated_by" UUID NOT NULL,
|
|
"request_key" UUID NOT NULL,
|
|
|
|
CONSTRAINT "loan_lenders_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "company_loans" (
|
|
"id" UUID NOT NULL,
|
|
"loan_no" VARCHAR(64) NOT NULL,
|
|
"origin_reference" VARCHAR(200) NOT NULL,
|
|
"legal_entity_id" UUID NOT NULL,
|
|
"lender_id" UUID NOT NULL,
|
|
"currency_code" VARCHAR(8) NOT NULL DEFAULT 'CNY',
|
|
"original_principal" DECIMAL(14,2) NOT NULL,
|
|
"opening_outstanding_principal" DECIMAL(14,2) NOT NULL,
|
|
"received_date" DATE NOT NULL,
|
|
"balance_as_of" DATE NOT NULL,
|
|
"historical_entry" BOOLEAN NOT NULL,
|
|
"confirmation_status" VARCHAR(16) NOT NULL DEFAULT 'unconfirmed',
|
|
"version" INTEGER NOT NULL DEFAULT 1,
|
|
"confirmed_by" UUID,
|
|
"confirmed_at" TIMESTAMP(3),
|
|
"accounting_confirmation_ref" VARCHAR(200),
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"created_by" UUID NOT NULL,
|
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
|
"updated_by" UUID NOT NULL,
|
|
"request_key" UUID NOT NULL,
|
|
|
|
CONSTRAINT "company_loans_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "loan_repayments" (
|
|
"id" UUID NOT NULL,
|
|
"repayment_no" VARCHAR(64) NOT NULL,
|
|
"legal_entity_id" UUID NOT NULL,
|
|
"lender_id" UUID NOT NULL,
|
|
"currency_code" VARCHAR(8) NOT NULL DEFAULT 'CNY',
|
|
"reason" VARCHAR(500) NOT NULL,
|
|
"amount" DECIMAL(14,2) NOT NULL,
|
|
"version" INTEGER NOT NULL DEFAULT 1,
|
|
"submitted_by" UUID NOT NULL,
|
|
"submitted_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"lender_identity_version" INTEGER NOT NULL,
|
|
"lender_account_version" INTEGER NOT NULL,
|
|
"payee_snapshot_json" JSONB NOT NULL,
|
|
"bank_result_state" VARCHAR(24) NOT NULL DEFAULT 'not_sent',
|
|
"bank_result_ref" VARCHAR(500),
|
|
"accounting_handoff_by" UUID,
|
|
"accounting_handoff_at" TIMESTAMP(3),
|
|
"accounting_handoff_ref" VARCHAR(200),
|
|
"request_key" UUID NOT NULL,
|
|
|
|
CONSTRAINT "loan_repayments_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "loan_repayment_allocations" (
|
|
"id" UUID NOT NULL,
|
|
"repayment_id" UUID NOT NULL,
|
|
"loan_id" UUID NOT NULL,
|
|
"amount" DECIMAL(14,2) NOT NULL,
|
|
"loan_version" INTEGER NOT NULL,
|
|
"reservation_state" VARCHAR(16) NOT NULL DEFAULT 'reserved',
|
|
"settled_at" TIMESTAMP(3),
|
|
"released_at" TIMESTAMP(3),
|
|
"release_reason" VARCHAR(500),
|
|
|
|
CONSTRAINT "loan_repayment_allocations_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "loan_evidences" (
|
|
"id" UUID NOT NULL,
|
|
"loan_id" UUID NOT NULL,
|
|
"kind" VARCHAR(32) NOT NULL,
|
|
"original_name" VARCHAR(255) NOT NULL,
|
|
"storage_path" TEXT NOT NULL,
|
|
"mime_type" VARCHAR(100) NOT NULL,
|
|
"file_size" INTEGER NOT NULL,
|
|
"file_sha256" VARCHAR(64) NOT NULL,
|
|
"governance_status" VARCHAR(16) NOT NULL DEFAULT 'pending',
|
|
"uploaded_by" UUID NOT NULL,
|
|
"uploaded_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"archived_at" TIMESTAMP(3),
|
|
"archive_reason" VARCHAR(500),
|
|
|
|
CONSTRAINT "loan_evidences_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "loan_lenders_employee_id_idx" ON "loan_lenders"("employee_id");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "loan_lenders_legal_entity_id_id_key" ON "loan_lenders"("legal_entity_id", "id");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "loan_lenders_created_by_request_key_key" ON "loan_lenders"("created_by", "request_key");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "company_loans_loan_no_key" ON "company_loans"("loan_no");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "company_loans_lender_id_confirmation_status_received_date_idx" ON "company_loans"("lender_id", "confirmation_status", "received_date");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "company_loans_created_by_request_key_key" ON "company_loans"("created_by", "request_key");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "loan_repayments_repayment_no_key" ON "loan_repayments"("repayment_no");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "loan_repayments_lender_id_submitted_at_idx" ON "loan_repayments"("lender_id", "submitted_at");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "loan_repayments_submitted_by_request_key_key" ON "loan_repayments"("submitted_by", "request_key");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "loan_repayment_allocations_loan_id_reservation_state_idx" ON "loan_repayment_allocations"("loan_id", "reservation_state");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "loan_repayment_allocations_repayment_id_loan_id_key" ON "loan_repayment_allocations"("repayment_id", "loan_id");
|
|
|
|
-- CreateIndex
|
|
CREATE UNIQUE INDEX "loan_evidences_loan_id_file_sha256_kind_key" ON "loan_evidences"("loan_id", "file_sha256", "kind");
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "payment_batches" ADD CONSTRAINT "payment_batches_loan_repayment_id_fkey" FOREIGN KEY ("loan_repayment_id") REFERENCES "loan_repayments"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "loan_lenders" ADD CONSTRAINT "loan_lenders_legal_entity_id_fkey" FOREIGN KEY ("legal_entity_id") REFERENCES "legal_entities"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "loan_lenders" ADD CONSTRAINT "loan_lenders_employee_id_fkey" FOREIGN KEY ("employee_id") REFERENCES "employees"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "loan_lenders" ADD CONSTRAINT "loan_lenders_bank_institution_id_fkey" FOREIGN KEY ("bank_institution_id") REFERENCES "bank_institutions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "company_loans" ADD CONSTRAINT "company_loans_legal_entity_id_lender_id_fkey" FOREIGN KEY ("legal_entity_id", "lender_id") REFERENCES "loan_lenders"("legal_entity_id", "id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "loan_repayments" ADD CONSTRAINT "loan_repayments_legal_entity_id_lender_id_fkey" FOREIGN KEY ("legal_entity_id", "lender_id") REFERENCES "loan_lenders"("legal_entity_id", "id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "loan_repayment_allocations" ADD CONSTRAINT "loan_repayment_allocations_repayment_id_fkey" FOREIGN KEY ("repayment_id") REFERENCES "loan_repayments"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "loan_repayment_allocations" ADD CONSTRAINT "loan_repayment_allocations_loan_id_fkey" FOREIGN KEY ("loan_id") REFERENCES "company_loans"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "loan_evidences" ADD CONSTRAINT "loan_evidences_loan_id_fkey" FOREIGN KEY ("loan_id") REFERENCES "company_loans"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
-- AddForeignKey
|
|
ALTER TABLE "loan_evidences" ADD CONSTRAINT "loan_evidences_uploaded_by_fkey" FOREIGN KEY ("uploaded_by") REFERENCES "employees"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
|
|
|
-- A historical incoming reference may be registered only once for this lender.
|
|
CREATE UNIQUE INDEX "company_loans_legal_entity_id_lender_id_origin_reference_key"
|
|
ON company_loans(legal_entity_id, lender_id, origin_reference);
|
|
|
|
ALTER TABLE loan_lenders ADD CONSTRAINT loan_lenders_identity_check CHECK (
|
|
identity_status IN ('unverified','verified') AND identity_version > 0 AND account_version > 0
|
|
AND (identity_status <> 'verified' OR (identity_confirmed_by IS NOT NULL
|
|
AND identity_confirmed_at IS NOT NULL AND NULLIF(trim(identity_confirmation),'') IS NOT NULL))
|
|
);
|
|
ALTER TABLE company_loans ADD CONSTRAINT company_loans_balance_check CHECK (
|
|
original_principal > 0 AND opening_outstanding_principal >= 0
|
|
AND opening_outstanding_principal <= original_principal AND currency_code = 'CNY'
|
|
AND received_date <= balance_as_of AND version > 0
|
|
AND confirmation_status IN ('unconfirmed','confirmed')
|
|
AND (confirmation_status <> 'confirmed' OR (confirmed_by IS NOT NULL AND confirmed_at IS NOT NULL
|
|
AND NULLIF(trim(accounting_confirmation_ref),'') IS NOT NULL))
|
|
);
|
|
ALTER TABLE loan_repayments ADD CONSTRAINT loan_repayments_amount_state_check CHECK (
|
|
amount > 0 AND currency_code = 'CNY' AND version > 0
|
|
AND lender_identity_version > 0 AND lender_account_version > 0
|
|
AND bank_result_state IN ('not_sent','unknown','confirmed_failed','confirmed_success')
|
|
AND (bank_result_state NOT IN ('confirmed_failed','confirmed_success') OR NULLIF(trim(bank_result_ref),'') IS NOT NULL)
|
|
AND ((accounting_handoff_by IS NULL AND accounting_handoff_at IS NULL AND accounting_handoff_ref IS NULL)
|
|
OR (accounting_handoff_by IS NOT NULL AND accounting_handoff_at IS NOT NULL AND NULLIF(trim(accounting_handoff_ref),'') IS NOT NULL))
|
|
);
|
|
ALTER TABLE loan_repayment_allocations ADD CONSTRAINT loan_allocation_amount_state_check CHECK (
|
|
amount > 0 AND loan_version > 0 AND reservation_state IN ('reserved','settled','released')
|
|
AND (reservation_state <> 'settled' OR settled_at IS NOT NULL)
|
|
AND (reservation_state <> 'released' OR (released_at IS NOT NULL AND NULLIF(trim(release_reason),'') IS NOT NULL))
|
|
);
|
|
ALTER TABLE loan_evidences ADD CONSTRAINT loan_evidence_kind_check CHECK (
|
|
kind IN ('agreement','incoming','accounting_confirmation','adjustment') AND file_size > 0
|
|
AND governance_status IN ('pending','approved','blocked') AND file_sha256 ~ '^[0-9a-f]{64}$'
|
|
);
|
|
ALTER TABLE payment_batches ADD CONSTRAINT payment_batch_loan_source_check CHECK (
|
|
(entity_type::text = 'loan_repayment') = (loan_repayment_id IS NOT NULL)
|
|
);
|
|
CREATE UNIQUE INDEX payment_batch_one_active_loan_repayment ON payment_batches(loan_repayment_id)
|
|
WHERE loan_repayment_id IS NOT NULL AND status <> 'cancelled';
|
|
|
|
-- Confirmed opening balances are immutable; correcting history needs a separately
|
|
-- reviewed adjustment, not an UPDATE that silently erases old loan evidence.
|
|
CREATE FUNCTION guard_confirmed_loan_balance() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
BEGIN
|
|
IF OLD.confirmation_status = 'confirmed' AND (
|
|
NEW.legal_entity_id, NEW.lender_id, NEW.currency_code, NEW.original_principal,
|
|
NEW.opening_outstanding_principal, NEW.received_date, NEW.balance_as_of,
|
|
NEW.historical_entry, NEW.origin_reference, NEW.confirmation_status,
|
|
NEW.confirmed_by, NEW.confirmed_at, NEW.accounting_confirmation_ref
|
|
) IS DISTINCT FROM (
|
|
OLD.legal_entity_id, OLD.lender_id, OLD.currency_code, OLD.original_principal,
|
|
OLD.opening_outstanding_principal, OLD.received_date, OLD.balance_as_of,
|
|
OLD.historical_entry, OLD.origin_reference, OLD.confirmation_status,
|
|
OLD.confirmed_by, OLD.confirmed_at, OLD.accounting_confirmation_ref
|
|
) THEN RAISE EXCEPTION 'confirmed loan balance is immutable' USING ERRCODE='23514'; END IF;
|
|
RETURN NEW;
|
|
END $$;
|
|
CREATE TRIGGER company_loan_confirmed_immutable BEFORE UPDATE ON company_loans
|
|
FOR EACH ROW EXECUTE FUNCTION guard_confirmed_loan_balance();
|
|
|
|
-- Deferred checks validate the complete atomic repayment/batch/allocation write.
|
|
-- Writers still follow the application lock order; the loan-row locks below also
|
|
-- serialize aggregate validation across concurrent transactions.
|
|
CREATE FUNCTION assert_loan_repayment_integrity(repayment_uuid uuid) RETURNS void LANGUAGE plpgsql AS $$
|
|
DECLARE r loan_repayments%ROWTYPE; b payment_batches%ROWTYPE; n integer; loan_uuid uuid;
|
|
BEGIN
|
|
SELECT * INTO r FROM loan_repayments WHERE id=repayment_uuid;
|
|
IF NOT FOUND THEN RETURN; END IF;
|
|
SELECT count(*) INTO n FROM payment_batches WHERE loan_repayment_id=r.id;
|
|
IF n <> 1 THEN RAISE EXCEPTION 'repayment requires one retained batch' USING ERRCODE='23514'; END IF;
|
|
SELECT * INTO b FROM payment_batches WHERE loan_repayment_id=r.id;
|
|
IF b.entity_type::text <> 'loan_repayment' OR b.legal_entity_id IS DISTINCT FROM r.legal_entity_id
|
|
OR b.default_payment_channel IS DISTINCT FROM 'bank'
|
|
OR (SELECT count(*) FROM payment_batch_items WHERE payment_batch_id=b.id) <> 1
|
|
OR NOT EXISTS(SELECT 1 FROM payment_batch_items WHERE payment_batch_id=b.id
|
|
AND entity_id=r.id AND amount=r.amount AND currency_code=r.currency_code)
|
|
THEN RAISE EXCEPTION 'repayment payment identity mismatch' USING ERRCODE='23514'; END IF;
|
|
IF (SELECT coalesce(sum(amount),0) FROM loan_repayment_allocations WHERE repayment_id=r.id) <> r.amount
|
|
OR EXISTS(SELECT 1 FROM loan_repayment_allocations a JOIN company_loans l ON l.id=a.loan_id
|
|
WHERE a.repayment_id=r.id AND (l.lender_id<>r.lender_id OR l.legal_entity_id<>r.legal_entity_id
|
|
OR l.currency_code<>r.currency_code OR l.confirmation_status<>'confirmed' OR l.version<>a.loan_version))
|
|
THEN RAISE EXCEPTION 'repayment allocation mismatch' USING ERRCODE='23514'; END IF;
|
|
IF EXISTS(SELECT 1 FROM loan_repayment_allocations WHERE repayment_id=r.id AND reservation_state <>
|
|
CASE WHEN b.status='executed' THEN 'settled' WHEN b.status='cancelled' THEN 'released' ELSE 'reserved' END)
|
|
OR (b.status='executed' AND r.bank_result_state<>'confirmed_success')
|
|
OR (b.status='cancelled' AND r.bank_result_state NOT IN ('not_sent','confirmed_failed'))
|
|
THEN RAISE EXCEPTION 'repayment release or settlement state mismatch' USING ERRCODE='23514'; END IF;
|
|
FOR loan_uuid IN SELECT loan_id FROM loan_repayment_allocations WHERE repayment_id=r.id ORDER BY loan_id LOOP
|
|
PERFORM 1 FROM company_loans WHERE id=loan_uuid FOR UPDATE;
|
|
IF (SELECT coalesce(sum(amount),0) FROM loan_repayment_allocations
|
|
WHERE loan_id=loan_uuid AND reservation_state IN ('reserved','settled')) >
|
|
(SELECT opening_outstanding_principal FROM company_loans WHERE id=loan_uuid)
|
|
THEN RAISE EXCEPTION 'loan principal capacity exceeded' USING ERRCODE='23514'; END IF;
|
|
END LOOP;
|
|
END $$;
|
|
|
|
CREATE FUNCTION check_loan_repayment_integrity() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
DECLARE old_id uuid; new_id uuid;
|
|
BEGIN
|
|
IF TG_TABLE_NAME='loan_repayments' THEN
|
|
IF TG_OP<>'INSERT' THEN old_id=OLD.id; END IF;
|
|
IF TG_OP<>'DELETE' THEN new_id=NEW.id; END IF;
|
|
ELSIF TG_TABLE_NAME='loan_repayment_allocations' THEN
|
|
IF TG_OP<>'INSERT' THEN old_id=OLD.repayment_id; END IF;
|
|
IF TG_OP<>'DELETE' THEN new_id=NEW.repayment_id; END IF;
|
|
ELSIF TG_TABLE_NAME='payment_batches' THEN
|
|
IF TG_OP<>'INSERT' THEN old_id=OLD.loan_repayment_id; END IF;
|
|
IF TG_OP<>'DELETE' THEN new_id=NEW.loan_repayment_id; END IF;
|
|
ELSE
|
|
IF TG_OP<>'INSERT' THEN SELECT loan_repayment_id INTO old_id FROM payment_batches WHERE id=OLD.payment_batch_id; END IF;
|
|
IF TG_OP<>'DELETE' THEN SELECT loan_repayment_id INTO new_id FROM payment_batches WHERE id=NEW.payment_batch_id; END IF;
|
|
END IF;
|
|
IF old_id IS NOT NULL THEN PERFORM assert_loan_repayment_integrity(old_id); END IF;
|
|
IF new_id IS NOT NULL AND new_id IS DISTINCT FROM old_id THEN PERFORM assert_loan_repayment_integrity(new_id); END IF;
|
|
RETURN NULL;
|
|
END $$;
|
|
CREATE CONSTRAINT TRIGGER loan_repayment_integrity AFTER INSERT OR UPDATE OR DELETE ON loan_repayments
|
|
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION check_loan_repayment_integrity();
|
|
CREATE CONSTRAINT TRIGGER loan_allocation_integrity AFTER INSERT OR UPDATE OR DELETE ON loan_repayment_allocations
|
|
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION check_loan_repayment_integrity();
|
|
CREATE CONSTRAINT TRIGGER loan_batch_integrity AFTER INSERT OR UPDATE OR DELETE ON payment_batches
|
|
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION check_loan_repayment_integrity();
|
|
CREATE CONSTRAINT TRIGGER loan_payment_item_integrity AFTER INSERT OR UPDATE OR DELETE ON payment_batch_items
|
|
DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION check_loan_repayment_integrity();
|
|
|
|
CREATE FUNCTION guard_loan_repayment_history() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
BEGIN
|
|
IF TG_TABLE_NAME='loan_repayment_allocations' THEN
|
|
IF TG_OP='DELETE' THEN RAISE EXCEPTION 'loan allocation history must be retained' USING ERRCODE='23514'; END IF;
|
|
IF (NEW.repayment_id,NEW.loan_id,NEW.amount,NEW.loan_version) IS DISTINCT FROM
|
|
(OLD.repayment_id,OLD.loan_id,OLD.amount,OLD.loan_version)
|
|
OR (OLD.reservation_state IN ('settled','released') AND NEW IS DISTINCT FROM OLD)
|
|
THEN RAISE EXCEPTION 'loan allocation history is immutable' USING ERRCODE='23514'; END IF;
|
|
ELSE
|
|
IF TG_OP='DELETE' THEN RAISE EXCEPTION 'loan repayment history must be retained' USING ERRCODE='23514'; END IF;
|
|
IF (NEW.repayment_no,NEW.legal_entity_id,NEW.lender_id,NEW.currency_code,NEW.reason,NEW.amount,
|
|
NEW.submitted_by,NEW.submitted_at,NEW.lender_identity_version,NEW.lender_account_version,NEW.payee_snapshot_json,NEW.request_key)
|
|
IS DISTINCT FROM
|
|
(OLD.repayment_no,OLD.legal_entity_id,OLD.lender_id,OLD.currency_code,OLD.reason,OLD.amount,
|
|
OLD.submitted_by,OLD.submitted_at,OLD.lender_identity_version,OLD.lender_account_version,OLD.payee_snapshot_json,OLD.request_key)
|
|
OR (OLD.bank_result_state IN ('confirmed_failed','confirmed_success') AND
|
|
(NEW.bank_result_state,NEW.bank_result_ref) IS DISTINCT FROM (OLD.bank_result_state,OLD.bank_result_ref))
|
|
OR (OLD.bank_result_state='unknown' AND NEW.bank_result_state='not_sent')
|
|
OR (OLD.accounting_handoff_at IS NOT NULL AND
|
|
(NEW.accounting_handoff_at,NEW.accounting_handoff_by,NEW.accounting_handoff_ref) IS DISTINCT FROM
|
|
(OLD.accounting_handoff_at,OLD.accounting_handoff_by,OLD.accounting_handoff_ref))
|
|
THEN RAISE EXCEPTION 'loan repayment history is immutable' USING ERRCODE='23514'; END IF;
|
|
END IF;
|
|
RETURN NEW;
|
|
END $$;
|
|
CREATE TRIGGER loan_repayment_history BEFORE UPDATE OR DELETE ON loan_repayments
|
|
FOR EACH ROW EXECUTE FUNCTION guard_loan_repayment_history();
|
|
CREATE TRIGGER loan_allocation_history BEFORE UPDATE OR DELETE ON loan_repayment_allocations
|
|
FOR EACH ROW EXECUTE FUNCTION guard_loan_repayment_history();
|