feat(核算)!: 添加 T+ 正式核算与采集托管
补齐源快照、资料预演、正式导入及工资核算的依赖链路。 新增版本化输入、逐段 FIFO、生成锁和结构化当月调整, 并提供默认关闭的周期采集、有限重试及 Windows 托管。 原始客户资料、私有配置及首页改动不纳入本次提交。 BREAKING CHANGE: 升级前需应用新增迁移,并通过 PAYROLL_MAKER_USER_ID 绑定实际工资制作人,否则业务只读。 旧单批次核算默认关闭;历史兼容需显式启用 PAYROLL_ALLOW_LEGACY_CALCULATION。生产启用和真实金额待验收。
This commit is contained in:
1 parent
1b074576fe
commit
108bf87225
154 files changed
+9515
-645
No files matched your search
@@ -0,0 +1,27 @@
|
||||
"""V17 对齐 V16 已应用工作区约束的完整表名约定。"""
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision: str = "07b8c9d0e1f2"
|
||||
down_revision: str | None = "f6a708b9c0d1"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
RENAMES = (
|
||||
("tplus_source_heads", "uk_tplus_heads_source_entity_id", "uk_tplus_source_heads_source_entity_id"),
|
||||
("tplus_source_heads", "idx_tplus_heads_batch_entity", "idx_tplus_source_heads_batch_entity"),
|
||||
("tplus_source_links", "uk_tplus_links_source_entity_id", "uk_tplus_source_links_source_entity_id"),
|
||||
("tplus_source_links", "uk_tplus_links_entity_target", "uk_tplus_source_links_entity_target"),
|
||||
)
|
||||
|
||||
|
||||
def upgrade():
|
||||
"""只重命名固定索引,不改变任何数据。"""
|
||||
for table, old, new in RENAMES:
|
||||
op.execute(f"ALTER TABLE {table} RENAME INDEX {old} TO {new}")
|
||||
|
||||
|
||||
def downgrade():
|
||||
"""恢复 V16 固定索引名。"""
|
||||
for table, old, new in reversed(RENAMES):
|
||||
op.execute(f"ALTER TABLE {table} RENAME INDEX {new} TO {old}")
|
||||
@@ -0,0 +1,31 @@
|
||||
"""正式工资输入、FIFO明细和月份生成锁。"""
|
||||
from alembic import op
|
||||
|
||||
revision: str = "18c9d0e1f2a3"
|
||||
down_revision: str | None = "07b8c9d0e1f2"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade():
|
||||
op.execute('CREATE INDEX idx_performance_ledgers_receipt_id ON performance_ledgers (receipt_id)')
|
||||
op.execute("ALTER TABLE performance_ledgers ADD COLUMN allocation_key VARCHAR(64) NOT NULL DEFAULT '' COMMENT '正式FIFO事件键;历史单笔核算为空串'")
|
||||
op.execute('ALTER TABLE performance_ledgers DROP INDEX uk_performance_ledgers_receipt_id_calc_version_personnel')
|
||||
op.execute('ALTER TABLE performance_ledgers ADD CONSTRAINT uk_performance_ledgers_receipt_id_calc_version_personnel UNIQUE (receipt_id, calc_version, personnel_id, allocation_key)')
|
||||
|
||||
op.execute("CREATE TABLE payroll_months (\n id BIGINT UNSIGNED NOT NULL COMMENT '主键ID' AUTO_INCREMENT, \n month VARCHAR(7) NOT NULL COMMENT '工资月份', \n maker_id BIGINT UNSIGNED NOT NULL COMMENT '实际工资制作人账号ID', \n maker_name VARCHAR(64) NOT NULL COMMENT '制作人用户名快照', \n generated_at DATETIME COMMENT '首次成功生成时间(不可解锁)', \n create_time DATETIME NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \n update_time DATETIME NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \n CONSTRAINT pk_payroll_months PRIMARY KEY (id), \n CONSTRAINT uk_payroll_months_month UNIQUE (month)\n)ENGINE=InnoDB CHARSET=utf8mb4 COMMENT='工资月份控制表(首次生成即锁定分配,审批不解锁)' COLLATE utf8mb4_general_ci")
|
||||
op.execute("CREATE TABLE payroll_inputs (\n id BIGINT UNSIGNED NOT NULL COMMENT '主键ID' AUTO_INCREMENT, \n business_id BIGINT UNSIGNED NOT NULL COMMENT '正式业务ID', \n version INTEGER NOT NULL COMMENT '输入版本', \n source_database VARCHAR(128) NOT NULL COMMENT '源账套或人工来源', \n group_key VARCHAR(128) NOT NULL COMMENT '独立业务平账组', \n fingerprint VARCHAR(64) NOT NULL COMMENT '来源及事实指纹', \n data_json JSON NOT NULL COMMENT '经核实的源数据及人工事实快照', \n requested_by BIGINT UNSIGNED NOT NULL COMMENT '录入人账号ID', \n create_time DATETIME NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \n update_time DATETIME NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \n CONSTRAINT pk_payroll_inputs PRIMARY KEY (id), \n CONSTRAINT uk_payroll_inputs_business_version UNIQUE (business_id, version)\n)ENGINE=InnoDB CHARSET=utf8mb4 COMMENT='正式工资输入版本表' COLLATE utf8mb4_general_ci")
|
||||
op.execute('CREATE INDEX idx_payroll_inputs_source_group ON payroll_inputs (source_database, group_key)')
|
||||
op.execute("CREATE TABLE payroll_allocations (\n id BIGINT UNSIGNED NOT NULL COMMENT '主键ID' AUTO_INCREMENT, \n ledger_id BIGINT UNSIGNED NOT NULL COMMENT '所属正式台账ID', \n business_id BIGINT UNSIGNED NOT NULL COMMENT '正式业务ID', \n line_no INTEGER NOT NULL COMMENT '本台账稳定明细序号', \n input_id BIGINT UNSIGNED NOT NULL COMMENT '所用输入版本ID', \n amount NUMERIC(14, 2) NOT NULL COMMENT '分配回款金额', \n data_json JSON NOT NULL COMMENT '单据、成本、归属、参数和逐段计算依据', \n create_time DATETIME NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \n update_time DATETIME NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \n CONSTRAINT pk_payroll_allocations PRIMARY KEY (id), \n CONSTRAINT uk_payroll_allocations_ledger_line UNIQUE (ledger_id, line_no)\n)ENGINE=InnoDB CHARSET=utf8mb4 COMMENT='正式FIFO分配明细表' COLLATE utf8mb4_general_ci")
|
||||
op.execute('CREATE INDEX idx_payroll_allocations_business_id ON payroll_allocations (business_id)')
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
if bind.exec_driver_sql("SELECT COUNT(*) FROM payroll_inputs").scalar() or bind.exec_driver_sql("SELECT COUNT(*) FROM payroll_allocations").scalar():
|
||||
raise RuntimeError("已有正式核算输入或分配记录,拒绝破坏性回退;请先备份并使用应用回退方案")
|
||||
op.execute('ALTER TABLE performance_ledgers DROP INDEX uk_performance_ledgers_receipt_id_calc_version_personnel')
|
||||
op.execute("ALTER TABLE performance_ledgers DROP COLUMN allocation_key")
|
||||
op.execute("ALTER TABLE performance_ledgers ADD CONSTRAINT uk_performance_ledgers_receipt_id_calc_version_personnel UNIQUE (receipt_id, calc_version, personnel_id)")
|
||||
op.execute("DROP INDEX idx_performance_ledgers_receipt_id ON performance_ledgers")
|
||||
op.drop_table('payroll_allocations')
|
||||
op.drop_table('payroll_inputs')
|
||||
op.drop_table('payroll_months')
|
||||
@@ -0,0 +1,18 @@
|
||||
"""分配调整的原台账与业务分管归属。"""
|
||||
from alembic import op
|
||||
revision: str = "19d0e1f2a3b4"
|
||||
down_revision: str | None = "18c9d0e1f2a3"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade():
|
||||
op.execute("ALTER TABLE adjustment_orders ADD COLUMN adjustment_type VARCHAR(32) NOT NULL DEFAULT 'GENERAL' COMMENT '调整类型:GENERAL普通 ALLOCATION分配差额'")
|
||||
op.execute("ALTER TABLE adjustment_orders ADD COLUMN source_ledger_id BIGINT UNSIGNED NULL COMMENT '分配差额对应的原台账ID'")
|
||||
op.execute("ALTER TABLE adjustment_orders ADD COLUMN attribution_json JSON NULL COMMENT '原台账业务与分管归属快照'")
|
||||
|
||||
def downgrade():
|
||||
if op.get_bind().exec_driver_sql("SELECT COUNT(*) FROM adjustment_orders WHERE source_ledger_id IS NOT NULL").scalar():
|
||||
raise RuntimeError("已有分配调整依据,拒绝删除归属信息;请使用应用版本回退")
|
||||
op.drop_column("adjustment_orders", "attribution_json")
|
||||
op.drop_column("adjustment_orders", "source_ledger_id")
|
||||
op.drop_column("adjustment_orders", "adjustment_type")
|
||||
@@ -0,0 +1,18 @@
|
||||
"""自动采集周期、重试与心跳。"""
|
||||
from alembic import op
|
||||
revision: str = "20e1f2a3b4c5"
|
||||
down_revision: str | None = "19d0e1f2a3b4"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade():
|
||||
op.execute("CREATE TABLE tplus_automation_states (\n id BIGINT UNSIGNED NOT NULL COMMENT '主键ID' AUTO_INCREMENT, \n source_key VARCHAR(64) NOT NULL COMMENT '源连接身份摘要(不含凭据)', \n source_database VARCHAR(128) NOT NULL COMMENT '源账套', \n scheduled_period VARCHAR(16) COMMENT '最近已排队ISO周', \n root_batch_no VARCHAR(32) COMMENT '本周期原始采集批次', \n current_batch_no VARCHAR(32) COMMENT '当前原始或重试批次', \n retry_attempts INTEGER NOT NULL COMMENT '本周期自动重试次数' DEFAULT '0', \n next_retry_at DATETIME COMMENT '下一次自动重试时间', \n last_success_at DATETIME COMMENT '最近成功采集时间', \n maintenance_at DATETIME COMMENT '最近周期检查时间', \n heartbeat_at DATETIME COMMENT '独立进程心跳时间', \n worker_owner VARCHAR(36) COMMENT '当前持锁进程身份', \n worker_phase VARCHAR(16) NOT NULL COMMENT '进程状态:IDLE/RUNNING/STOPPED' DEFAULT '''STOPPED''', \n create_time DATETIME NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \n update_time DATETIME NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \n CONSTRAINT pk_tplus_automation_states PRIMARY KEY (id), \n CONSTRAINT uk_tplus_automation_states_source_key UNIQUE (source_key)\n)ENGINE=InnoDB CHARSET=utf8mb4 COMMENT='T+自动采集周期与进程状态(不自动导入正式业务)' COLLATE utf8mb4_general_ci")
|
||||
op.execute("ALTER TABLE tplus_sync_batches ADD COLUMN trigger_type VARCHAR(16) NOT NULL DEFAULT 'MANUAL' COMMENT '触发来源:MANUAL/SCHEDULED/AUTO_RETRY'")
|
||||
op.execute("ALTER TABLE tplus_sync_batches ADD COLUMN automatic_attempt INTEGER NOT NULL DEFAULT 0 COMMENT '自动重试序号(0为原始任务)'")
|
||||
|
||||
def downgrade():
|
||||
if op.get_bind().exec_driver_sql("SELECT COUNT(*) FROM tplus_automation_states").scalar():
|
||||
raise RuntimeError("存在调度状态,请先停用并备份后采用应用回退,不删除运行依据")
|
||||
op.drop_column("tplus_sync_batches", "automatic_attempt")
|
||||
op.drop_column("tplus_sync_batches", "trigger_type")
|
||||
op.drop_table("tplus_automation_states")
|
||||
@@ -0,0 +1,17 @@
|
||||
"""修正进程状态默认值的多余引号,保留已应用V20。"""
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision: str = "21f2a3b4c5d6"
|
||||
down_revision: str | None = "20e1f2a3b4c5"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.execute("ALTER TABLE tplus_automation_states ALTER COLUMN worker_phase SET DEFAULT 'STOPPED'")
|
||||
op.execute("UPDATE tplus_automation_states SET worker_phase='STOPPED' WHERE worker_phase='''STOPPED'''")
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.execute("ALTER TABLE tplus_automation_states ALTER COLUMN worker_phase SET DEFAULT '''STOPPED'''")
|
||||
@@ -0,0 +1,31 @@
|
||||
"""V16 基础导入和核算预演工作区;结构固定,不引用演进中的 ORM。"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision: str = "f6a708b9c0d1"
|
||||
down_revision: str | None = "e5f607a8b9c0"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
STATEMENTS = [
|
||||
"CREATE TABLE tplus_source_heads (\n\tid BIGINT UNSIGNED NOT NULL COMMENT '主键ID' AUTO_INCREMENT, \n\tsource_database VARCHAR(128) NOT NULL COMMENT '源账套', \n\tentity_type VARCHAR(32) NOT NULL COMMENT '实体类型', \n\tsource_id VARCHAR(64) NOT NULL COMMENT '源内部ID', \n\trecord_id BIGINT UNSIGNED NOT NULL COMMENT '原始快照ID', \n\tbatch_no VARCHAR(32) NOT NULL COMMENT '最后观察批次', \n\tCONSTRAINT pk_tplus_source_heads PRIMARY KEY (id), \n\tCONSTRAINT uk_tplus_heads_source_entity_id UNIQUE (source_database, entity_type, source_id)\n)ENGINE=InnoDB CHARSET=utf8mb4 COMMENT='T+ 源当前快照指针(按成功全量批次读取,删除不自动回写业务表)' COLLATE utf8mb4_general_ci",
|
||||
"CREATE INDEX idx_tplus_heads_batch_entity ON tplus_source_heads (batch_no, entity_type)",
|
||||
"CREATE TABLE tplus_facts (\n\tid BIGINT UNSIGNED NOT NULL COMMENT '主键ID' AUTO_INCREMENT, \n\tsource_database VARCHAR(128) NOT NULL COMMENT '源账套', \n\tkind VARCHAR(32) NOT NULL COMMENT '事实类别', \n\tfact_key VARCHAR(128) NOT NULL COMMENT '事实业务键', \n\tversion INTEGER NOT NULL COMMENT '版本号', \n\tdata_json JSON NOT NULL COMMENT '经校验的事实内容', \n\tevidence VARCHAR(500) NOT NULL COMMENT '依据和修改原因', \n\trequested_by BIGINT UNSIGNED NOT NULL COMMENT '操作者ID', \n\tcreate_time DATETIME NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \n\tupdate_time DATETIME NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \n\tCONSTRAINT pk_tplus_facts PRIMARY KEY (id), \n\tCONSTRAINT uk_tplus_facts_source_kind_key_version UNIQUE (source_database, kind, fact_key, version)\n)ENGINE=InnoDB CHARSET=utf8mb4 COMMENT='T+ 人工补充事实版本表' COLLATE utf8mb4_general_ci",
|
||||
"CREATE TABLE tplus_source_links (\n\tid BIGINT UNSIGNED NOT NULL COMMENT '主键ID' AUTO_INCREMENT, \n\tsource_database VARCHAR(128) NOT NULL COMMENT '源账套', \n\tentity_type VARCHAR(32) NOT NULL COMMENT '实体类型', \n\tsource_id VARCHAR(64) NOT NULL COMMENT '源内部ID', \n\ttarget_id BIGINT UNSIGNED NOT NULL COMMENT '业务目标ID', \n\tcreate_time DATETIME NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \n\tupdate_time DATETIME NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \n\tCONSTRAINT pk_tplus_source_links PRIMARY KEY (id), \n\tCONSTRAINT uk_tplus_links_source_entity_id UNIQUE (source_database, entity_type, source_id), \n\tCONSTRAINT uk_tplus_links_entity_target UNIQUE (entity_type, target_id)\n)ENGINE=InnoDB CHARSET=utf8mb4 COMMENT='T+ 基础数据源目标关联' COLLATE utf8mb4_general_ci",
|
||||
"CREATE TABLE tplus_work_runs (\n\tid BIGINT UNSIGNED NOT NULL COMMENT '主键ID' AUTO_INCREMENT, \n\trun_no VARCHAR(32) NOT NULL COMMENT '运行编号', \n\trequest_key VARCHAR(36) NOT NULL COMMENT '请求幂等键', \n\tkind VARCHAR(32) NOT NULL COMMENT 'MASTER_PREVIEW/MASTER_APPLY/CALC_PREVIEW', \n\tstatus VARCHAR(16) NOT NULL COMMENT '运行状态', \n\tsource_database VARCHAR(128) NOT NULL COMMENT '固定源账套', \n\trequested_by BIGINT UNSIGNED NOT NULL COMMENT '操作者ID', \n\tinput_json JSON NOT NULL COMMENT '请求输入', \n\tsnapshot_json JSON COMMENT '规则和补充资料的固定快照', \n\tsnapshot_hash VARCHAR(64) COMMENT '输入指纹', \n\tsummary_json JSON COMMENT '结果汇总', \n\terror_code VARCHAR(64) COMMENT '固定错误编码', \n\tstarted_at DATETIME COMMENT '开始时间', \n\tfinished_at DATETIME COMMENT '结束时间', \n\tcreate_time DATETIME NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP, \n\tupdate_time DATETIME NOT NULL COMMENT '更新时间' DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, \n\tCONSTRAINT pk_tplus_work_runs PRIMARY KEY (id), \n\tCONSTRAINT uk_tplus_work_runs_run_no UNIQUE (run_no), \n\tCONSTRAINT uk_tplus_work_runs_request_key UNIQUE (request_key)\n)ENGINE=InnoDB CHARSET=utf8mb4 COMMENT='T+ 导入与预演运行表' COLLATE utf8mb4_general_ci",
|
||||
"CREATE INDEX idx_tplus_work_runs_status_id ON tplus_work_runs (status, id)",
|
||||
"CREATE TABLE tplus_work_lines (\n\tid BIGINT UNSIGNED NOT NULL COMMENT '主键ID' AUTO_INCREMENT, \n\trun_no VARCHAR(32) NOT NULL COMMENT '运行编号', \n\tline_no INTEGER NOT NULL COMMENT '稳定行序号', \n\tentity_type VARCHAR(32) NOT NULL COMMENT '实体或分组类型', \n\tsource_id VARCHAR(128) NOT NULL COMMENT '源ID或平账组', \n\taction VARCHAR(32) NOT NULL COMMENT '新增/更新/跳过/待补/冲突/分配', \n\treason VARCHAR(128) COMMENT '固定原因编码', \n\tdata_json JSON NOT NULL COMMENT '最小必要的业务明细', \n\tCONSTRAINT pk_tplus_work_lines PRIMARY KEY (id), \n\tCONSTRAINT uk_tplus_work_lines_run_line UNIQUE (run_no, line_no)\n)ENGINE=InnoDB CHARSET=utf8mb4 COMMENT='T+ 导入预览与核算预演明细' COLLATE utf8mb4_general_ci"
|
||||
]
|
||||
|
||||
|
||||
def upgrade():
|
||||
"""只新增隔离工作区表,不写已有业务或工资记录。"""
|
||||
for statement in STATEMENTS:
|
||||
op.execute(sa.text(statement))
|
||||
|
||||
|
||||
def downgrade():
|
||||
"""只移除本次工作区表,执行前应保留资料和预演备份。"""
|
||||
for name in ("tplus_work_lines", "tplus_work_runs", "tplus_source_links", "tplus_facts", "tplus_source_heads"):
|
||||
op.drop_table(name)
|
||||
Reference in new issue
Block a user