Files
cma-management/backend/scripts/treasury_ddl.sql
T
Hermes CI Fix e5eb092d20 feat(cma): treasury stage-1 MVP - migration, models, endpoints, rules, 2 pages
G1 review fixes P0-1/P0-2/P1-3/P1-4/P1-5/P2-6/P2-7/P2-8 applied:
- up() filters comment lines then asserts missing_tables == [] (CREATE hits 6/6)
- created counter = new-object diff via information_schema (idempotent second run = 0)
- down() drops columns per-column, dumps via write-path creds + fail-closed
- seeds expanded to 8 rules + 6 limits; data_status DEFAULT estimated
- no physical FK (app-layer checks), amount FLOAT wan-yuan, round(x,4)

Scope: 6 treasury_* tables + cash_plans 4 added columns; cash.py untouched.
Not pushed: deploy is approval-gated (de851257).
2026-09-15 20:02:56 +08:00

325 lines
24 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- ============================================================================
-- CMA 司库(资金)体系 · 阶段一 MVP —— DDL(阶段 1 落盘版)
-- ============================================================================
-- 状态 : 落地版(2026-09-15 阶段 1)。G1 = 放行(有条件),见
-- wecom-project/pending-tasks/_verification/fullstack-202609151735-cma-treasury-g1-review.md
-- 评审稿出处 : wecom-fullstack/pending-tasks/_reports/treasury-stage1-ddl-draft-20260915.sql
-- (md5 3aa4d944691b49cfda9b1f08f6bea549 —— 归档件,不回改)
-- 本版修正 : P2-6 data_status 默认 fail-safe / D-8 submitter 语义 / 调整2 detail 结构化
-- / P2-8 金额纪律注释 / 无物理外键的应用层覆盖声明(§F)
-- 依据 : PRD v1.1 §3.2/§3.3(md5 cc9ca839b7fd6f633f4066ba385819be)
-- 财务口径签认 v1(md5 74b43cfdf25abf5b7654eba54e82d099)
-- 财务侧前置交付 F1/F2/F3(md5 a49ae4340c05dfa20aba7daad25cc25f)
-- 目标环境 : MySQL 8.0(InnoDB),charset/collation 以只读探针实测为准
-- 范围 : 6 张新表 + cash_plans 加 4 列(向后兼容,只加列不改列)
-- 阶段三的 4 张表(pools/pool_members/credits/internal_transfers)不建
-- 幂等契约 : 同一脚本重复执行结果不变(见 §E)
-- 命名约定 : 约束名与 PRD §3.2 字面一致(唯一键 uq_* / 普通索引 ix_*)
-- 执行方式 : 仅由 backend/scripts/treasury_migrate.py --apply 执行(本文件不含裸 ALTER 执行逻辑)
-- ============================================================================
-- ============================================================================
-- §A. 建表(6 张,CREATE TABLE IF NOT EXISTS 天然幂等)
-- 应用层软链(不建物理外键,G1 §3-2 裁定)覆盖声明见 §F
-- ============================================================================
-- A1. 资金账户主数据(统一管理:台账唯一入口)
-- tier 口径(财务签认 口径1 / PRD M1):按【账户性质】判定,禁止按 org_nodes.level 派生
-- hq : 法人银行户 / 收款通道(1002xx、微信、支付宝、友赞商户)
-- grassroots: 现金 / 备用金 / 备用卡 / 中间账户(100101/100104/100105/100106/100107)
-- platform : 分公司独立法人账户(当前两实体均无,禁止造虚层级)
CREATE TABLE IF NOT EXISTS treasury_accounts (
id INT AUTO_INCREMENT PRIMARY KEY,
entity_id INT NOT NULL COMMENT '企业ID(多租户铁律,必带)',
account_no VARCHAR(64) NOT NULL COMMENT '账号/虚拟账户号(掩码)',
account_name VARCHAR(100) NOT NULL COMMENT '账户名(如 建设银行(货款账户))',
account_type VARCHAR(20) NOT NULL COMMENT 'cash现金/bank银行/e_wallet微信支付宝/other',
bank_name VARCHAR(100) NULL,
subject_code VARCHAR(20) NULL COMMENT '对应科目(1001xx/1002xx)— 流水/头寸归集用',
currency VARCHAR(10) DEFAULT 'CNY',
tier VARCHAR(20) NOT NULL COMMENT 'hq总部/platform平台/grassroots基层(按账户性质判定)',
tier_basis VARCHAR(20) DEFAULT 'nature' COMMENT '[D-1] tier 判定依据:nature账户性质/level派生/manual人工;口径1 要求 accounts 一律 nature',
owner_node_id INT NULL COMMENT '使用/保管部门标注 org_nodes.id(不参与 tier 派生、不作授权依据)— 软链,应用层校验见 §F',
open_balance FLOAT NULL COMMENT '期初余额(万元);F2 到位前留 NULL。金额纪律见 §F-1',
open_period VARCHAR(20) NULL COMMENT '期初期间 YYYY-MM',
is_pool_member TINYINT DEFAULT 0 COMMENT '阶段三资金池用',
status VARCHAR(20) DEFAULT 'active' COMMENT 'active/inactive/closed',
remark VARCHAR(500) NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_tacct_entity_no (entity_id, account_no),
KEY ix_tacct_entity (entity_id, status),
KEY ix_tacct_subject (entity_id, subject_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='司库·资金账户主数据';
-- A2. 账户头寸(日/期末)
-- 铁律:余额 = 期初 + 流水(期初缺失时 data_status='estimated');
-- 禁止把 voucher_details 的"流水净额累计"当余额(PRD §2.2-1 实证)
CREATE TABLE IF NOT EXISTS treasury_balances (
id INT AUTO_INCREMENT PRIMARY KEY,
entity_id INT NOT NULL,
account_id INT NOT NULL COMMENT 'treasury_accounts.id(软链,应用层校验见 §F)',
period VARCHAR(20) NOT NULL COMMENT 'YYYY-MM-DD 日头寸 / YYYY-MM 月末头寸',
balance FLOAT NOT NULL COMMENT '账面余额(万元)。金额纪律见 §F-1',
available FLOAT NULL COMMENT '可用余额(万元)(NULL=未采集,勿以 balance 冒充)',
frozen FLOAT DEFAULT 0 COMMENT '冻结/在途(万元)',
source VARCHAR(30) NOT NULL COMMENT 'ledger期初+流水/import导入/manual手工/pool归集',
data_status VARCHAR(20) DEFAULT 'estimated' COMMENT 'verified/estimated —— [P2-6] 默认 estimated(fail-safe):verified 必须显式写入(仅期初已落定/对账通过);estimated 头寸禁止 block 规则生效',
batch VARCHAR(100) NULL COMMENT '导入批次指纹(幂等用,对齐 cash_vouchers_json._content_fingerprint 范式)',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_tbal_acct_period (account_id, period),
KEY ix_tbal_entity_period (entity_id, period),
KEY ix_tbal_entity_status (entity_id, data_status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='司库·账户头寸(日/期末)';
-- A3. 资金内控规则库(P0 核心)
-- params JSON 结构与口径映射:
-- NP-001 budget_required {"judge":"budget_plans","entity1":"block","entity2":"warn",
-- "occupancy":"paid+approved+pending_approval+this",
-- "unmatched":"specify_budget_flow"}
-- AU-001 authorization {"tiers":[{"from":0,"to":3,"required_approvals":0},...],
-- "daily_cum_limit":9,"max_strict":true,"bounds":"[from,to)"}
-- BL-001 dual_sign {"min_amount":30,"distinct_natural_person":true}
-- LQ-001 liquidity {"target":26,"warn":12,"block":6,"block_enabled":false,
-- "skip_when":"data_status=estimated"}
CREATE TABLE IF NOT EXISTS treasury_control_rules (
id INT AUTO_INCREMENT PRIMARY KEY,
entity_id INT NOT NULL,
rule_code VARCHAR(20) NOT NULL COMMENT 'NP-001/AU-001/BL-001/LQ-001/LQ-002/TF-001/AC-001',
rule_name VARCHAR(100) NOT NULL,
rule_type VARCHAR(30) NOT NULL COMMENT 'budget_required/authorization/dual_sign/liquidity/two_funds/account_valid',
severity VARCHAR(10) NOT NULL COMMENT 'block拦截/warn预警',
params JSON NULL COMMENT '阈值参数(金额段/下限/比例等,不硬编码)',
rule_version VARCHAR(20) DEFAULT 'v1' COMMENT '[调整2] 规则版本,写入 control_hits.detail.rule_version(审计可自证)',
enabled TINYINT DEFAULT 1,
remark VARCHAR(500) NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_tcr_entity_code (entity_id, rule_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='司库·资金内控规则库';
-- A4. 支付单(内控闸门载体)
-- 拆单累计穿透(口径2/M4)所需索引:ix_tpay_daysplit(同提交人×同收款方×同自然日累计)
-- [D-8] submitter / created_by 语义(G1 复算发现的未披露增补,追认采纳):
-- submitter = 支付单提交人 —— AU-001 拆单累计穿透的"同一提交人"判据(业务语义字段)
-- created_by = 建单人 —— 历史兼容列(与既有表 created_by 命名一致)
-- 首版两者同值(提交即建单);保留双列以便后续"审批链与建单分离"时语义不破坏
CREATE TABLE IF NOT EXISTS treasury_payments (
id INT AUTO_INCREMENT PRIMARY KEY,
entity_id INT NOT NULL,
pay_no VARCHAR(50) NOT NULL COMMENT '支付单号',
from_account_id INT NOT NULL COMMENT '付款账户(必须已登记,禁未入台账账户)——软链,应用层校验见 §F',
to_name VARCHAR(200) NOT NULL COMMENT '收款方',
to_account_no VARCHAR(64) NULL,
amount FLOAT NOT NULL COMMENT '金额(万元)。金额纪律见 §F-1',
currency VARCHAR(10) DEFAULT 'CNY',
purpose VARCHAR(500) NOT NULL COMMENT '用途(必填,内控留痕)',
pay_type VARCHAR(30) NULL COMMENT '货款/费用/税费/薪酬/融资/内部划转',
pay_date DATE NULL COMMENT '业务付款日(拆单累计穿透按此自然日归集;口径2)',
budget_plan_id INT NULL COMMENT '关联预算计划(NP-001 授权性判据首选)——软链',
kpi_id INT NULL,
related_plan_id INT NULL COMMENT '关联 cash_plans.id(不新建表,复用收付计划)——软链',
status VARCHAR(20) DEFAULT 'draft' COMMENT 'draft/precheck/pending_approval/approved/rejected/paid/cancelled',
check_result VARCHAR(20) NULL COMMENT 'pass/blocked/warn',
check_detail JSON NULL COMMENT '[{rule_code,verdict,detail,matched_scope}] 命中档位写在 matched_scope(口径2-③)',
required_approvals TINYINT NULL COMMENT '[D-2] 本单实际所需审批级数(取最严档结果,落库便于审计复现)',
day_cum_amount FLOAT NULL COMMENT '[D-3] 判定时 同提交人×同收款方×同自然日 累计金额(万元,含本单)',
approvals JSON NULL COMMENT '[{account,role,person_name,at,comment}] — 双签须记账号+角色+自然人姓名(口径4)',
dual_sign_effective TINYINT NULL COMMENT 'BL-001:两账号是否不同自然人;0=形式双签(放行无效,审计标红)',
is_waived TINYINT DEFAULT 0 COMMENT '[D-4] 是否经豁免通道放行(豁免人/事由落 treasury_control_hits)',
submitter VARCHAR(100) NOT NULL COMMENT '[D-8] 提交人(AU-001 拆单累计穿透"同一提交人"判据)',
submitted_at DATETIME NULL COMMENT '[D-5] 提交时间(口径2 日累计/占用口径需要审批链时间锚)',
approved_at DATETIME NULL COMMENT '[D-5] 放行时间',
paid_at DATETIME NULL,
created_by VARCHAR(100) NOT NULL COMMENT '[D-8] 建单人(历史兼容列;首版与 submitter 同值)',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_tpay_entity_no (entity_id, pay_no),
KEY ix_tpay_entity_status (entity_id, status),
KEY ix_tpay_daysplit (entity_id, pay_date, to_name, submitter),
KEY ix_tpay_acct (entity_id, from_account_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='司库·支付单(资金内控闸门)';
-- A5. 规则命中留痕(审计,append-only,不可省)
-- [调整2 / D-6] detail 固定结构化字段(规则引擎按此写入,审计追问要能自证):
-- {"rule_code":..,"rule_version":..,"matched_scope":..,"threshold":..,
-- "cumulative":..,"verdict_basis":".."}
CREATE TABLE IF NOT EXISTS treasury_control_hits (
id INT AUTO_INCREMENT PRIMARY KEY,
entity_id INT NOT NULL,
target_type VARCHAR(20) NOT NULL COMMENT 'payment/plan/internal_transfer',
target_id INT NOT NULL,
rule_code VARCHAR(20) NOT NULL,
verdict VARCHAR(20) NOT NULL COMMENT 'pass/blocked/waived/warn',
detail JSON NULL COMMENT '[调整2] 结构化判据快照,固定字段 {rule_code, rule_version, matched_scope, threshold, cumulative, verdict_basis}',
waived_by VARCHAR(100) NULL COMMENT '豁免审批人(豁免级别不得低于该笔所需审批级别,口径4)',
waived_reason VARCHAR(500) NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
KEY ix_tch_target (target_type, target_id),
KEY ix_tch_entity_rule (entity_id, rule_code, created_at),
KEY ix_tch_waived (entity_id, verdict, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='司库·内控规则命中留痕(append-only 审计)';
-- A6. 分级授权矩阵(不硬编码;A/B 数值生效前需任总过目)
-- 边界口径(口径2-④ 左闭右开):amount_from 含、amount_to 不含;amount_to IS NULL = 无上限
CREATE TABLE IF NOT EXISTS treasury_limits (
id INT AUTO_INCREMENT PRIMARY KEY,
entity_id INT NOT NULL,
scope_type VARCHAR(20) NOT NULL COMMENT 'role/account/tier',
scope_value VARCHAR(50) NOT NULL COMMENT 'finance/ceo/…/account_id/tier',
amount_from FLOAT NOT NULL COMMENT '段下限(万元,含)。金额纪律见 §F-1',
amount_to FLOAT NULL COMMENT '段上限(万元,不含),NULL=无上限',
required_approvals TINYINT DEFAULT 1 COMMENT '所需审批级数(≥2=双签)',
approver_roles JSON NOT NULL COMMENT '["ceo","finance"](顺序=先复核后批准,口径4)',
require_budget TINYINT DEFAULT 1,
daily_cum_limit FLOAT NULL COMMENT '自主档日累计上限(=3×A;超限升一级审批,口径2-②)',
status VARCHAR(20) DEFAULT 'active',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '[D-9] 审计必需(PRD 该表无此列,G1 追认采纳)',
UNIQUE KEY uq_tlim_scope (entity_id, scope_type, scope_value, amount_from),
KEY ix_tlim_entity (entity_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='司库·分级授权矩阵';
-- ============================================================================
-- §B. 扩展既有表(只加列、不改列、默认 NULL —— 存量数据零影响)
-- 注意:MySQL 8.0 不支持 ADD COLUMN IF NOT EXISTS,
-- 裸 ALTER 重复执行会报 1060 Duplicate column name。
-- 故 §B 由 migrate 脚本按 information_schema 预检结果条件执行(见 §E-②)。
-- ============================================================================
-- B0. 幂等预检(migrate 脚本执行前必跑;输出缺失列清单,全 0 即跳过)
-- SELECT COLUMN_NAME FROM information_schema.COLUMNS
-- WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'cash_plans'
-- AND COLUMN_NAME IN ('account_id','tier','pool_id','is_locked');
ALTER TABLE cash_plans
ADD COLUMN account_id INT NULL COMMENT '资金来源/去向账户 treasury_accounts.id(软链,无物理外键)',
ADD COLUMN tier VARCHAR(20) NULL COMMENT 'hq/platform/grassroots(三位一体视角;可由 org_nodes.level 派生)',
ADD COLUMN pool_id INT NULL COMMENT '归属资金池(阶段三启用,本阶段恒 NULL)',
ADD COLUMN is_locked TINYINT DEFAULT 0 COMMENT '是否已被预算/内控锁定';
-- B1. 可选索引 [D-7](G1 已随 D-1~D-7 全部采纳;与 §B 同批执行)
ALTER TABLE cash_plans ADD KEY ix_cashplans_entity_acct (entity_id, account_id);
-- ============================================================================
-- §C. 数据种子(首版数值 = 财务建议值;任总拍板后只改数据行、不改代码)
-- 幂等:INSERT ... ON DUPLICATE KEY UPDATE(唯一键 = uq_tcr_entity_code / uq_tlim_scope)
-- 行数契约:规则 8 行(e1 4 + e2 4)+ 授权矩阵 6 行
-- ============================================================================
-- C1. 三条 block 规则 + LQ-001(entity1 / entity2 各一套)
-- NP-001:entity1=block,entity2=warn(分期上线,过渡期 1 个月后切 block)
-- AU-001:tiers 采用签认首版 A/B(酣客 3/30 万;博海 5/50 万)—— 待任总过目
-- BL-001:distinct_natural_person=true(两账号须属不同自然人)
-- LQ-001:首版一律 warn、block_enabled=false(阶段二接预警)
INSERT INTO treasury_control_rules (entity_id, rule_code, rule_name, rule_type, severity, params, rule_version, enabled)
VALUES
(1,'NP-001','无预算不开支(授权性判据)','budget_required','block',
JSON_OBJECT('judge','budget_plans','severity','block',
'occupancy','paid+approved+pending_approval+this',
'unmatched','specify_budget_flow','transition_until','2026-10-15'),'v1',1),
(1,'AU-001','超授权拦截(含拆单累计穿透+日累计升级)','authorization','block',
JSON_OBJECT('tiers',JSON_ARRAY(JSON_OBJECT('from',0,'to',3,'required_approvals',0),
JSON_OBJECT('from',3,'to',30,'required_approvals',1),
JSON_OBJECT('from',30,'to',NULL,'required_approvals',2)),
'daily_cum_limit',9,'merge_same_payee_same_day',true,'max_strict',true,'bounds','[from,to)'),'v1',1),
(1,'BL-001','大额支付双签(须不同自然人)','dual_sign','block',
JSON_OBJECT('min_amount',30,'required_approvals',2,'distinct_natural_person',true,
'record_fields',JSON_ARRAY('account','role','person_name'),'order','review_then_approve'),'v1',1),
(1,'LQ-001','流动性下限(首版 warn-only 页面并列对照)','liquidity','warn',
JSON_OBJECT('target',26,'warn',12,'block',6,'block_enabled',false,
'skip_when','data_status=estimated'),'v1',1),
(2,'NP-001','无预算不开支(博海首版 warn+强制补录)','budget_required','warn',
JSON_OBJECT('judge','budget_plans','severity','warn','occupancy','paid+approved+pending_approval+this',
'unmatched','specify_budget_flow','transition_until','2026-10-15'),'v1',1),
(2,'AU-001','超授权拦截(含拆单累计穿透+日累计升级)','authorization','block',
JSON_OBJECT('tiers',JSON_ARRAY(JSON_OBJECT('from',0,'to',5,'required_approvals',0),
JSON_OBJECT('from',5,'to',50,'required_approvals',1),
JSON_OBJECT('from',50,'to',NULL,'required_approvals',2)),
'daily_cum_limit',15,'merge_same_payee_same_day',true,'max_strict',true,'bounds','[from,to)'),'v1',1),
(2,'BL-001','大额支付双签(须不同自然人)','dual_sign','block',
JSON_OBJECT('min_amount',50,'required_approvals',2,'distinct_natural_person',true,
'record_fields',JSON_ARRAY('account','role','person_name'),'order','review_then_approve'),'v1',1),
(2,'LQ-001','流动性下限(博海首版默认 warn/关闭)','liquidity','warn',
JSON_OBJECT('target',50,'warn',10,'block',5,'block_enabled',false,
'skip_when','data_status=estimated'),'v1',0)
ON DUPLICATE KEY UPDATE rule_name=VALUES(rule_name), rule_type=VALUES(rule_type),
severity=VALUES(severity), params=VALUES(params), rule_version=VALUES(rule_version);
-- C2. 授权矩阵(scope_type='role',按 entity 生效;双签档 approver_roles 顺序=先复核后批准)
INSERT INTO treasury_limits (entity_id, scope_type, scope_value, amount_from, amount_to,
required_approvals, approver_roles, require_budget, daily_cum_limit)
VALUES
(1,'role','finance',0, 3, 0, JSON_ARRAY(), 1, 9),
(1,'role','ceo', 3, 30, 1, JSON_ARRAY('ceo'), 1, NULL),
(1,'role','ceo', 30, NULL,2, JSON_ARRAY('finance','ceo'),1, NULL),
(2,'role','finance',0, 5, 0, JSON_ARRAY(), 1, 15),
(2,'role','ceo', 5, 50, 1, JSON_ARRAY('ceo'), 1, NULL),
(2,'role','ceo', 50, NULL,2, JSON_ARRAY('finance','ceo'),1, NULL)
ON DUPLICATE KEY UPDATE amount_to=VALUES(amount_to), required_approvals=VALUES(required_approvals),
approver_roles=VALUES(approver_roles), daily_cum_limit=VALUES(daily_cum_limit);
-- ============================================================================
-- §D. 回滚 / DOWN(由 treasury_migrate.py --down 执行;顺序与 UP 相反)
-- ============================================================================
-- D1. 回滚 cash_plans 扩展列:[P1-3] 按列存在性**逐列** DROP(部分态不再 1091)
-- 回滚前 [P1-4] 必先 mysqldump cash_plans(写路径账号 + 临时 defaults-file 注入口令,
-- 不硬编码、不回显;dump 失败 fail-closed —— 先 dump 成功才允许删列)
-- D2. 回滚新表(先删留痕/引用方,再删主数据):DROP TABLE IF EXISTS 逐张
-- D3. 回滚安全性:6 张新表首版为空表(DROP 无损);一旦有业务数据,
-- 回滚须经人工确认(--yes),不提供 --force 免确认开关
-- D4. 回滚不触碰 cash.py 既有表结构,11 端点契约不受影响
-- ============================================================================
-- §E. 幂等与验证(由 treasury_migrate.py 逐条实现)
-- ============================================================================
-- E1. 幂等三件套 + 计数口径([P0-2] 修正)
-- ① CREATE TABLE IF NOT EXISTS(表级幂等)
-- ② §B 列扩展按 information_schema 预检(列级幂等)
-- ③ §C 种子走 ON DUPLICATE KEY UPDATE(数据级幂等)
-- ⇒ 计数口径 = **新增对象数**(apply 前后 information_schema diff),不是"执行语句数":
-- 首跑:created_new=6 / added_cols=4 / 种子 8 规则行 + 6 授权行
-- 二跑:created_new=0 / added_cols=0 / 种子行数不变
-- ④ up() 结尾断言 check()["missing_tables"] == [],否则非零退出(禁静默成功)
-- ⑤ 指纹行写入 import_logs(batch=treasury-ddl-<sha1[:10]>),[P2-7] entity_id=1 为约定:
-- 迁移指纹属实体无关元数据,import_logs.entity_id NOT NULL 约束下以 1 占位;
-- 幂等按 (import_type,batch) 先查后写,不依赖表上是否有唯一键
-- E2. 表级自检(落库后)
-- SHOW CREATE TABLE treasury_accounts; -- 逐表核对 entity_id/唯一键/引擎字符集
-- SELECT TABLE_NAME FROM information_schema.TABLES
-- WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME LIKE 'treasury%'; -- 期望 6 行
-- SELECT TABLE_NAME, COLUMN_NAME, IS_NULLABLE FROM information_schema.COLUMNS
-- WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME LIKE 'treasury%'
-- AND COLUMN_NAME='entity_id'; -- 期望 6 行且全 NOT NULL
-- E3. 存量零影响证明
-- SELECT COUNT(*) FROM cash_plans; -- 迁移前后一致(基线以探针实测为准)
-- ============================================================================
-- §F. 应用层约束声明(G1 裁定"不建物理外键"的附条件覆盖说明 + 金额纪律)
-- ============================================================================
-- F-1 金额纪律([P2-8] / G1 §3-3 附条件):
-- * 全部金额列为 FLOAT(万元)—— 与 cash_plans.amount / budget_plans.budget_value 同口径;
-- FLOAT 非精确货币类型(技术债登记:迁 DECIMAL(18,4) 须与 cash_plans/budget_plans 同批)
-- * 写库前一律 round(x, 4);比较一律用容差 TOL=1e-4,禁止浮点等值比较(引擎实现见 treasury.py)
-- F-2 软链的应用层校验覆盖(逐表说明,G1 §3-2 附条件):
-- ① treasury_balances.account_id → 写入/查询前校验 account 存在 且 account.entity_id == 传入 entity_id(防跨实体误挂)
-- ② treasury_payments.from_account_id → AC-001:必须命中 treasury_accounts 且 status='active'(账外账户拦截)
-- ③ treasury_payments.budget_plan_id → NP-001:命中 budget_plans 且 entity_id 一致;未命中走"指定/补录预算"流程
-- ④ treasury_payments.related_plan_id → 校验 cash_plans 同 entity_id(只读复用,不改 cash.py 契约)
-- ⑤ treasury_payments.kpi_id → 校验 kpis 同 entity_id(NP-001 兜底匹配用)
-- ⑥ treasury_control_hits.target_id → target_type 限定取值域(payment/plan/internal_transfer),
-- 写入前校验目标单据存在且同 entity_id
-- ⑦ treasury_accounts.owner_node_id → 校验 org_nodes 同 entity_id(不存在则留空,禁造虚层级)
-- * 唯一键 + 应用层校验 + AC-001 覆盖主要一致性风险;跨实体写入在应用层一律 403/400 拒绝
-- ============================================================================