feat(T+): 完成数据贯通与自动接入闭环

打通 T+ 快照、业务、发货、回款和核算准备链路,保留来源差异并提供待补状态。
新增业务自动接入任务、跨平台读取服务托管、权限复核、来源关联和前端核对页面。
补充迁移、隔离集成测试、浏览器模拟验收及角色授权边界修复。
This commit is contained in:
liuwenxin committed 2026-11-05 18:47:13 +08:00
1 parent 3efe72c344
commit 1e53792ad6
151 files changed
+8842 -401

No files matched your search

@@ -0,0 +1,47 @@
"""V29 回款独立人工确认及默认开启开关;不自动确认历史回款。"""
from pathlib import Path
from alembic import op
from sqlalchemy import inspect, text
revision = "29b0c1d2e3f4"
down_revision = "28a9b0c1d2e3"
branch_labels = None
depends_on = None
def upgrade():
sql = Path(__file__).resolve().parents[3] / "db/migration/V29__receipt_manual_confirmation.sql"
columns = {column["name"]: column for column in inspect(op.get_bind()).get_columns("receipts")}
existing = {name for name in ("confirmation_status", "confirmed_by", "confirmed_at", "confirmed_revision") if name in columns}
if existing and len(existing) != 4:
raise RuntimeError("回款确认字段不完整,请先核对数据库结构")
if existing:
expected = {"confirmation_status": ("TINYINT", None, False), "confirmed_by": ("VARCHAR", 64, True),
"confirmed_at": ("DATETIME", None, True), "confirmed_revision": ("VARCHAR", 64, True)}
for name, (kind, length, nullable) in expected.items():
column = columns[name]
actual_type = column["type"]
if (actual_type.__visit_name__.upper() != kind or getattr(actual_type, "length", None) != length
or column["nullable"] != nullable):
raise RuntimeError(f"回款确认字段 {name} 与迁移定义不一致,请先核对数据库结构")
if str(columns["confirmation_status"]["default"]).strip("'\"") != "0":
raise RuntimeError("回款确认状态默认值不是待确认,请先核对数据库结构")
for statement in sql.read_text(encoding="utf-8").split(";\n"):
clean = "\n".join(line for line in statement.splitlines() if not line.lstrip().startswith("--")).strip()
if clean and not (existing and clean.startswith("ALTER TABLE receipts ADD COLUMN")):
op.get_bind().execute(text(clean))
def downgrade():
bind = op.get_bind()
if bind.scalar(text("SELECT COUNT(*) FROM receipts WHERE confirmed_at IS NOT NULL OR confirmation_status<>0")):
raise RuntimeError("已有回款人工确认记录,禁止删除确认依据;请仅回退应用并保留结构")
if bind.scalar(text("SELECT COUNT(*) FROM param_values v JOIN param_definitions d ON d.id=v.param_id WHERE d.param_code='receipt.confirmation.enabled'")):
raise RuntimeError("已维护人工确认开关版本,禁止删除历史参数")
bind.execute(text("DELETE FROM param_definitions WHERE param_code='receipt.confirmation.enabled'"))
bind.execute(text("UPDATE permissions SET status=0 WHERE perm_code='business:receipts:confirm'"))
for column in ("confirmed_revision", "confirmed_at", "confirmed_by", "confirmation_status"):
op.drop_column("receipts", column)
@@ -0,0 +1,40 @@
"""V30 业务接入与核算准备解耦;无默认回填。"""
from pathlib import Path
from alembic import op
from sqlalchemy import text
revision = "30c1d2e3f4a5"
down_revision = "29b0c1d2e3f4"
branch_labels = None
depends_on = None
def upgrade():
script = Path(__file__).resolve().parents[3] / "db/migration/V30__tplus_business_sync.sql"
for statement in script.read_text(encoding="utf-8").split(";\n"):
sql = "\n".join(line for line in statement.splitlines() if not line.lstrip().startswith("--")).strip()
if sql:
op.get_bind().execute(text(sql))
def downgrade():
connection = op.get_bind()
for table, condition in (("tplus_business_documents", "1=1"), ("products", "product_category IS NULL"),
("customers", "customer_type IS NULL"), ("business_items", "product_category IS NULL"),
("shipments", "freight_amount IS NULL")):
if connection.scalar(text(f"SELECT COUNT(*) FROM {table} WHERE {condition}")):
raise RuntimeError("已有接入或待补资料,请关闭接入并回退兼容版本;不删除业务数据以降级结构")
op.drop_table("tplus_business_documents")
op.drop_table("tplus_business_sync_configs")
for index in ("idx_tplus_source_heads_parent", "idx_tplus_source_heads_customer_date", "idx_tplus_source_heads_date"):
op.drop_index(index, "tplus_source_heads")
for column in ("source_parent_id", "source_customer_id", "source_date"):
op.drop_column("tplus_source_heads", column)
op.drop_column("businesses", "integration_json")
for table, column, sql_type, default in (("products", "product_category", "VARCHAR(32)", ""),
("customers", "customer_type", "VARCHAR(32)", " DEFAULT 'personal'"),
("business_items", "product_category", "VARCHAR(32)", ""),
("shipments", "freight_amount", "DECIMAL(14,2)", " DEFAULT 0")):
connection.execute(text(f"ALTER TABLE {table} MODIFY {column} {sql_type} NOT NULL{default}"))
@@ -0,0 +1,14 @@
"""合并业务接入和回款确认迁移分支。"""
revision = "31d2e3f4a5b6"
down_revision = ("29b0c1d2e3f4", "30c1d2e3f4a5")
branch_labels = None
depends_on = None
def upgrade():
pass
def downgrade():
pass