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).
325 lines
24 KiB
SQL
325 lines
24 KiB
SQL
-- ============================================================================
|
||
-- 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 拒绝
|
||
-- ============================================================================
|