feat(business): 新增业务数据七表及 V5 迁移(业务/明细/状态留痕/发货/回款/评级配置)
- businesses 五项汇总冗余列服务层事务内维护;毛利润不落库(派生值,M3 台账固化) - 行级 product_category 折算锚点(#14);回款 settlement_status 预置为 M3 防重锚点 - 评级等级种子四条(迁移+init-dev 双写);编号前缀常量 YW/SH/HK - 双轨 SQL 同步(V4/V5 重导,schema_sync+test_models 164 例全绿) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
ca88ee7a0b
commit
20fcf06f51
12 files changed
+2138
-138
No files matched your search
@@ -0,0 +1,696 @@
|
||||
"""业务数据管理七表:业务/业务明细/状态留痕/发货单/发货明细/回款/评级等级配置(M2)
|
||||
|
||||
- businesses:业务编号自动生成(报表 2.4.1「合同编号」),五项汇总冗余列
|
||||
由服务层事务内维护;毛利润相关值不落库(派生值,M3 台账才是固化点)
|
||||
- business_items:行级 product_category 为折算锚点(对照表 #14),指导价
|
||||
人工录入、T+ 同步不覆盖
|
||||
- business_status_logs:append-only,创建时写首条(old=new=1)
|
||||
- shipments/shipment_items:发货日为回款天数基准日(对照表 #1)
|
||||
- receipts:手动指定发货批次(#18);settlement_status 为 M3 核算防重锚点
|
||||
- customer_rating_grades:等级映射可配置,种子四条随迁移初始化
|
||||
|
||||
Revision ID: 8efc189b131f
|
||||
Revises: e1133a7c670a
|
||||
Create Date: 2026-10-13 11:14:17.545320
|
||||
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "8efc189b131f"
|
||||
down_revision: str | Sequence[str] | None = "e1133a7c670a"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
# 评级等级种子(与 init-dev.sql 双写一致;口径见模型 docstring)
|
||||
_GRADE_SEED = [
|
||||
{"grade_name": "A", "min_score": 90, "max_score": 100, "sort_order": 1, "remark": "优秀"},
|
||||
{"grade_name": "B", "min_score": 75, "max_score": 89, "sort_order": 2, "remark": "良好"},
|
||||
{"grade_name": "C", "min_score": 60, "max_score": 74, "sort_order": 3, "remark": "合格"},
|
||||
{"grade_name": "D", "min_score": 0, "max_score": 59, "sort_order": 4, "remark": "待改进"},
|
||||
]
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""应用迁移。"""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"customer_rating_grades",
|
||||
sa.Column(
|
||||
"id", mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment="主键ID"
|
||||
),
|
||||
sa.Column(
|
||||
"grade_name", sa.String(length=32), nullable=False, comment="等级名称(如 A/优秀)"
|
||||
),
|
||||
sa.Column(
|
||||
"min_score",
|
||||
sa.Numeric(precision=5, scale=1),
|
||||
nullable=False,
|
||||
comment="总分下限(含);判定=按 sort_order 升序首个 min_score ≤ 总分",
|
||||
),
|
||||
sa.Column(
|
||||
"max_score",
|
||||
sa.Numeric(precision=5, scale=1),
|
||||
nullable=False,
|
||||
comment="总分上限(展示用)",
|
||||
),
|
||||
sa.Column(
|
||||
"sort_order",
|
||||
sa.Integer(),
|
||||
server_default="0",
|
||||
nullable=False,
|
||||
comment="排序(判定顺序,升序)",
|
||||
),
|
||||
sa.Column("remark", sa.String(length=255), nullable=True, comment="说明"),
|
||||
sa.Column(
|
||||
"create_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="创建时间",
|
||||
),
|
||||
sa.Column(
|
||||
"update_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="更新时间",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_customer_rating_grades")),
|
||||
sa.UniqueConstraint("grade_name", name=op.f("uk_customer_rating_grades_grade_name")),
|
||||
comment="客户评级等级配置表(等级映射可配置;判定按 sort_order 升序首个 min_score≤总分)",
|
||||
mysql_charset="utf8mb4",
|
||||
mysql_collate="utf8mb4_general_ci",
|
||||
mysql_engine="InnoDB",
|
||||
)
|
||||
op.create_table(
|
||||
"business_status_logs",
|
||||
sa.Column(
|
||||
"id", mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment="主键ID"
|
||||
),
|
||||
sa.Column(
|
||||
"business_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=False,
|
||||
comment="业务ID(固化,无外键;删除保护由服务层承担)",
|
||||
),
|
||||
sa.Column(
|
||||
"old_status", mysql.TINYINT(), nullable=False, comment="变更前状态(创建首条为 1)"
|
||||
),
|
||||
sa.Column("new_status", mysql.TINYINT(), nullable=False, comment="变更后状态"),
|
||||
sa.Column(
|
||||
"change_reason", sa.String(length=255), nullable=True, comment="变更原因(终止时必填)"
|
||||
),
|
||||
sa.Column(
|
||||
"operator_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="操作人ID(账号删除后置空)",
|
||||
),
|
||||
sa.Column(
|
||||
"operator_name",
|
||||
sa.String(length=64),
|
||||
nullable=False,
|
||||
comment="操作人用户名(冗余固化)",
|
||||
),
|
||||
sa.Column(
|
||||
"create_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="创建时间",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["operator_id"],
|
||||
["users.id"],
|
||||
name=op.f("fk_business_status_logs_operator_id"),
|
||||
ondelete="SET NULL",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_business_status_logs")),
|
||||
comment="业务状态变更记录表(append-only)",
|
||||
mysql_charset="utf8mb4",
|
||||
mysql_collate="utf8mb4_general_ci",
|
||||
mysql_engine="InnoDB",
|
||||
)
|
||||
op.create_index(
|
||||
"idx_business_status_logs_business_id",
|
||||
"business_status_logs",
|
||||
["business_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"idx_business_status_logs_create_time",
|
||||
"business_status_logs",
|
||||
["create_time"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_table(
|
||||
"businesses",
|
||||
sa.Column(
|
||||
"id", mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment="主键ID"
|
||||
),
|
||||
sa.Column(
|
||||
"business_no",
|
||||
sa.String(length=32),
|
||||
nullable=False,
|
||||
comment="业务编号,自动生成 YW{YYYYMMDD}-{4位当日序号}(报表合同编号)",
|
||||
),
|
||||
sa.Column(
|
||||
"customer_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=False,
|
||||
comment="客户ID(customers.id)",
|
||||
),
|
||||
sa.Column("business_date", sa.Date(), nullable=True, comment="业务日期(T+ 同步口径)"),
|
||||
sa.Column(
|
||||
"status",
|
||||
mysql.TINYINT(),
|
||||
server_default="1",
|
||||
nullable=False,
|
||||
comment="业务状态:1执行中 2已完成 3已终止(终态不可变更;T+同步不覆盖)",
|
||||
),
|
||||
sa.Column(
|
||||
"owner_personnel_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="负责营销人员ID(核算归属锚点;NULL=待指定,M3 核算前置校验非空;变更留痕不回溯)",
|
||||
),
|
||||
sa.Column(
|
||||
"assistant_personnel_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="协助营销人员ID(一期核算不使用,对照表 #15;仅记录事实)",
|
||||
),
|
||||
sa.Column(
|
||||
"is_special",
|
||||
mysql.TINYINT(),
|
||||
server_default="0",
|
||||
nullable=False,
|
||||
comment="特殊业务标记:1是 0否(公司参与等)",
|
||||
),
|
||||
sa.Column("special_note", sa.String(length=255), nullable=True, comment="特殊业务说明"),
|
||||
sa.Column(
|
||||
"total_sale_amount",
|
||||
sa.Numeric(precision=14, scale=2),
|
||||
server_default="0",
|
||||
nullable=True,
|
||||
comment="销售额合计(明细行汇总冗余,服务层事务内维护,禁手工直改;不含运费口径)",
|
||||
),
|
||||
sa.Column(
|
||||
"total_cost_amount",
|
||||
sa.Numeric(precision=14, scale=2),
|
||||
server_default="0",
|
||||
nullable=True,
|
||||
comment="成本合计(明细行汇总冗余,服务层事务内维护,禁手工直改)",
|
||||
),
|
||||
sa.Column(
|
||||
"total_ship_amount",
|
||||
sa.Numeric(precision=14, scale=2),
|
||||
server_default="0",
|
||||
nullable=True,
|
||||
comment="发货金额合计(发货明细汇总冗余,服务层事务内维护,禁手工直改)",
|
||||
),
|
||||
sa.Column(
|
||||
"total_received_amount",
|
||||
sa.Numeric(precision=14, scale=2),
|
||||
server_default="0",
|
||||
nullable=True,
|
||||
comment="回款合计(回款记录汇总冗余,服务层事务内维护;M3 起付判定直接消费)",
|
||||
),
|
||||
sa.Column(
|
||||
"total_freight_amount",
|
||||
sa.Numeric(precision=14, scale=2),
|
||||
server_default="0",
|
||||
nullable=True,
|
||||
comment="运费合计(发货单汇总冗余,仅记录展示;不计入毛利润口径,对照表 #2)",
|
||||
),
|
||||
sa.Column("remark", sa.String(length=255), nullable=True, comment="备注"),
|
||||
sa.Column(
|
||||
"create_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="创建时间",
|
||||
),
|
||||
sa.Column(
|
||||
"update_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="更新时间",
|
||||
),
|
||||
sa.Column(
|
||||
"source_no",
|
||||
sa.String(length=64),
|
||||
nullable=True,
|
||||
comment="T+ 原始单据号(同步幂等去重锚点;唯一键待 IT 定键后升级)",
|
||||
),
|
||||
sa.Column("t_plus_id", sa.String(length=64), nullable=True, comment="T+ 系统内部标识"),
|
||||
sa.Column(
|
||||
"sync_batch_no",
|
||||
sa.String(length=32),
|
||||
nullable=True,
|
||||
comment="同步批次号(同步模块写入)",
|
||||
),
|
||||
sa.Column("synced_at", sa.DateTime(), nullable=True, comment="最近同步时间"),
|
||||
sa.ForeignKeyConstraint(
|
||||
["assistant_personnel_id"],
|
||||
["personnel.id"],
|
||||
name=op.f("fk_businesses_assistant_personnel_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["customer_id"],
|
||||
["customers.id"],
|
||||
name=op.f("fk_businesses_customer_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["owner_personnel_id"],
|
||||
["personnel.id"],
|
||||
name=op.f("fk_businesses_owner_personnel_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_businesses")),
|
||||
sa.UniqueConstraint("business_no", name=op.f("uk_businesses_business_no")),
|
||||
comment="业务表(业务编号自动生成;T+同步不覆盖业务状态)",
|
||||
mysql_charset="utf8mb4",
|
||||
mysql_collate="utf8mb4_general_ci",
|
||||
mysql_engine="InnoDB",
|
||||
)
|
||||
op.create_index("idx_businesses_business_date", "businesses", ["business_date"], unique=False)
|
||||
op.create_index("idx_businesses_customer_id", "businesses", ["customer_id"], unique=False)
|
||||
op.create_index(
|
||||
"idx_businesses_owner_personnel_id", "businesses", ["owner_personnel_id"], unique=False
|
||||
)
|
||||
op.create_index(op.f("idx_businesses_source_no"), "businesses", ["source_no"], unique=False)
|
||||
op.create_index("idx_businesses_status", "businesses", ["status"], unique=False)
|
||||
op.create_table(
|
||||
"business_items",
|
||||
sa.Column(
|
||||
"id", mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment="主键ID"
|
||||
),
|
||||
sa.Column(
|
||||
"business_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=False,
|
||||
comment="业务ID(businesses.id)",
|
||||
),
|
||||
sa.Column(
|
||||
"product_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="产品ID(products.id;NULL=自由录入,未入产品库)",
|
||||
),
|
||||
sa.Column(
|
||||
"product_name",
|
||||
sa.String(length=128),
|
||||
nullable=False,
|
||||
comment="产品名称(引用产品时冗余固化)",
|
||||
),
|
||||
sa.Column(
|
||||
"spec_model",
|
||||
sa.String(length=128),
|
||||
server_default="",
|
||||
nullable=False,
|
||||
comment="规格型号(冗余固化;空串对齐 products 组合唯一键口径)",
|
||||
),
|
||||
sa.Column("unit", sa.String(length=16), nullable=True, comment="计量单位"),
|
||||
sa.Column(
|
||||
"product_category",
|
||||
sa.String(length=32),
|
||||
nullable=False,
|
||||
comment="产品类别(行级折算锚点,混合业务按产品逐条计算,对照表 #14;值域同 PRODUCT_CATEGORIES)",
|
||||
),
|
||||
sa.Column(
|
||||
"quantity", sa.Numeric(precision=14, scale=2), nullable=False, comment="数量(>0)"
|
||||
),
|
||||
sa.Column(
|
||||
"sale_price",
|
||||
sa.Numeric(precision=14, scale=4),
|
||||
nullable=True,
|
||||
comment="销售单价(元;T+ 同步或人工)",
|
||||
),
|
||||
sa.Column(
|
||||
"sale_amount",
|
||||
sa.Numeric(precision=14, scale=2),
|
||||
nullable=False,
|
||||
comment="销售额 = 数量×销售单价(服务层计算固化;不含运费口径,毛利润率分母)",
|
||||
),
|
||||
sa.Column(
|
||||
"cost_amount",
|
||||
sa.Numeric(precision=14, scale=2),
|
||||
nullable=True,
|
||||
comment="成本额(T+ 同步或人工录入,来源待 T+ 确认;毛利润额 = 销售额 − 成本额)",
|
||||
),
|
||||
sa.Column(
|
||||
"guided_price",
|
||||
sa.Numeric(precision=14, scale=4),
|
||||
nullable=True,
|
||||
comment="指导价(人工录入;T+同步不覆盖,需规 2.2.1/2.2.4)",
|
||||
),
|
||||
sa.Column("remark", sa.String(length=255), nullable=True, comment="备注"),
|
||||
sa.Column(
|
||||
"create_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="创建时间",
|
||||
),
|
||||
sa.Column(
|
||||
"update_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="更新时间",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["business_id"],
|
||||
["businesses.id"],
|
||||
name=op.f("fk_business_items_business_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["product_id"],
|
||||
["products.id"],
|
||||
name=op.f("fk_business_items_product_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_business_items")),
|
||||
comment="业务产品明细表(毛利润行级源数据;指导价 T+同步不覆盖)",
|
||||
mysql_charset="utf8mb4",
|
||||
mysql_collate="utf8mb4_general_ci",
|
||||
mysql_engine="InnoDB",
|
||||
)
|
||||
op.create_index(
|
||||
"idx_business_items_business_id", "business_items", ["business_id"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
"idx_business_items_product_category", "business_items", ["product_category"], unique=False
|
||||
)
|
||||
op.create_index("idx_business_items_product_id", "business_items", ["product_id"], unique=False)
|
||||
op.create_table(
|
||||
"shipments",
|
||||
sa.Column(
|
||||
"id", mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment="主键ID"
|
||||
),
|
||||
sa.Column(
|
||||
"shipment_no",
|
||||
sa.String(length=32),
|
||||
nullable=False,
|
||||
comment="发货单号,自动生成 SH{YYYYMMDD}-{4位当日序号}(内部追溯用)",
|
||||
),
|
||||
sa.Column(
|
||||
"business_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=False,
|
||||
comment="业务ID(businesses.id;终态业务禁止新发货)",
|
||||
),
|
||||
sa.Column(
|
||||
"ship_date",
|
||||
sa.Date(),
|
||||
nullable=False,
|
||||
comment="发货日期(回款天数基准日,发货日首日=第 1 天)",
|
||||
),
|
||||
sa.Column(
|
||||
"freight_amount",
|
||||
sa.Numeric(precision=14, scale=2),
|
||||
server_default="0",
|
||||
nullable=False,
|
||||
comment="运费金额(仅记录展示;不计入毛利润口径,对照表 #2)",
|
||||
),
|
||||
sa.Column(
|
||||
"total_amount",
|
||||
sa.Numeric(precision=14, scale=2),
|
||||
server_default="0",
|
||||
nullable=False,
|
||||
comment="发货总金额(明细行汇总冗余,服务层事务内维护,禁手工直改)",
|
||||
),
|
||||
sa.Column("remark", sa.String(length=255), nullable=True, comment="备注"),
|
||||
sa.Column(
|
||||
"create_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="创建时间",
|
||||
),
|
||||
sa.Column(
|
||||
"update_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="更新时间",
|
||||
),
|
||||
sa.Column(
|
||||
"source_no",
|
||||
sa.String(length=64),
|
||||
nullable=True,
|
||||
comment="T+ 原始单据号(同步幂等去重锚点;唯一键待 IT 定键后升级)",
|
||||
),
|
||||
sa.Column("t_plus_id", sa.String(length=64), nullable=True, comment="T+ 系统内部标识"),
|
||||
sa.Column(
|
||||
"sync_batch_no",
|
||||
sa.String(length=32),
|
||||
nullable=True,
|
||||
comment="同步批次号(同步模块写入)",
|
||||
),
|
||||
sa.Column("synced_at", sa.DateTime(), nullable=True, comment="最近同步时间"),
|
||||
sa.ForeignKeyConstraint(
|
||||
["business_id"],
|
||||
["businesses.id"],
|
||||
name=op.f("fk_shipments_business_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_shipments")),
|
||||
sa.UniqueConstraint("shipment_no", name=op.f("uk_shipments_shipment_no")),
|
||||
comment="发货单表(发货日为回款天数基准日,发货日首日计第 1 天)",
|
||||
mysql_charset="utf8mb4",
|
||||
mysql_collate="utf8mb4_general_ci",
|
||||
mysql_engine="InnoDB",
|
||||
)
|
||||
op.create_index("idx_shipments_business_id", "shipments", ["business_id"], unique=False)
|
||||
op.create_index("idx_shipments_ship_date", "shipments", ["ship_date"], unique=False)
|
||||
op.create_index(op.f("idx_shipments_source_no"), "shipments", ["source_no"], unique=False)
|
||||
op.create_table(
|
||||
"receipts",
|
||||
sa.Column(
|
||||
"id", mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment="主键ID"
|
||||
),
|
||||
sa.Column(
|
||||
"receipt_batch_no",
|
||||
sa.String(length=32),
|
||||
nullable=False,
|
||||
comment="回款批次号,自动生成 HK{YYYYMMDD}-{4位当日序号}(系统内唯一;T+ 同步回款以 source_no 幂等)",
|
||||
),
|
||||
sa.Column(
|
||||
"business_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=False,
|
||||
comment="业务ID(businesses.id)",
|
||||
),
|
||||
sa.Column(
|
||||
"shipment_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="发货单ID(shipments.id;手动指定的回款对应批次,须与本行业务一致;NULL=暂未指定)",
|
||||
),
|
||||
sa.Column(
|
||||
"receipt_date",
|
||||
sa.Date(),
|
||||
nullable=False,
|
||||
comment="回款到账日(M3 按此日匹配参数版本,对照表 #11)",
|
||||
),
|
||||
sa.Column(
|
||||
"amount",
|
||||
sa.Numeric(precision=14, scale=2),
|
||||
nullable=False,
|
||||
comment="回款金额(≥0;一期禁负数/红字)",
|
||||
),
|
||||
sa.Column(
|
||||
"is_barter",
|
||||
mysql.TINYINT(),
|
||||
server_default="0",
|
||||
nullable=False,
|
||||
comment="以物抵顶标记:1是 0否(M3 核算按应发绩效×barter.discount 折算,M2 仅标记)",
|
||||
),
|
||||
sa.Column(
|
||||
"settlement_status",
|
||||
mysql.TINYINT(),
|
||||
server_default="0",
|
||||
nullable=False,
|
||||
comment="绩效核算状态:0未核算 1已核算(M3 核算防重锚点;已核算回款禁改禁删)",
|
||||
),
|
||||
sa.Column("remark", sa.String(length=255), nullable=True, comment="备注"),
|
||||
sa.Column(
|
||||
"create_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="创建时间",
|
||||
),
|
||||
sa.Column(
|
||||
"update_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="更新时间",
|
||||
),
|
||||
sa.Column(
|
||||
"source_no",
|
||||
sa.String(length=64),
|
||||
nullable=True,
|
||||
comment="T+ 原始单据号(同步幂等去重锚点;唯一键待 IT 定键后升级)",
|
||||
),
|
||||
sa.Column("t_plus_id", sa.String(length=64), nullable=True, comment="T+ 系统内部标识"),
|
||||
sa.Column(
|
||||
"sync_batch_no",
|
||||
sa.String(length=32),
|
||||
nullable=True,
|
||||
comment="同步批次号(同步模块写入)",
|
||||
),
|
||||
sa.Column("synced_at", sa.DateTime(), nullable=True, comment="最近同步时间"),
|
||||
sa.ForeignKeyConstraint(
|
||||
["business_id"],
|
||||
["businesses.id"],
|
||||
name=op.f("fk_receipts_business_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["shipment_id"],
|
||||
["shipments.id"],
|
||||
name=op.f("fk_receipts_shipment_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_receipts")),
|
||||
sa.UniqueConstraint("receipt_batch_no", name=op.f("uk_receipts_receipt_batch_no")),
|
||||
comment="回款表(手动指定发货批次;已核算回款禁改禁删)",
|
||||
mysql_charset="utf8mb4",
|
||||
mysql_collate="utf8mb4_general_ci",
|
||||
mysql_engine="InnoDB",
|
||||
)
|
||||
op.create_index("idx_receipts_business_id", "receipts", ["business_id"], unique=False)
|
||||
op.create_index("idx_receipts_receipt_date", "receipts", ["receipt_date"], unique=False)
|
||||
op.create_index(
|
||||
"idx_receipts_settlement_status", "receipts", ["settlement_status"], unique=False
|
||||
)
|
||||
op.create_index("idx_receipts_shipment_id", "receipts", ["shipment_id"], unique=False)
|
||||
op.create_index(op.f("idx_receipts_source_no"), "receipts", ["source_no"], unique=False)
|
||||
op.create_table(
|
||||
"shipment_items",
|
||||
sa.Column(
|
||||
"id", mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment="主键ID"
|
||||
),
|
||||
sa.Column(
|
||||
"shipment_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=False,
|
||||
comment="发货单ID(shipments.id)",
|
||||
),
|
||||
sa.Column(
|
||||
"product_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="产品ID(products.id;NULL=自由录入)",
|
||||
),
|
||||
sa.Column(
|
||||
"product_name",
|
||||
sa.String(length=128),
|
||||
nullable=False,
|
||||
comment="产品名称(引用产品时冗余固化)",
|
||||
),
|
||||
sa.Column(
|
||||
"spec_model",
|
||||
sa.String(length=128),
|
||||
server_default="",
|
||||
nullable=False,
|
||||
comment="规格型号(冗余固化;空串对齐 products 组合唯一键口径)",
|
||||
),
|
||||
sa.Column("unit", sa.String(length=16), nullable=True, comment="计量单位"),
|
||||
sa.Column(
|
||||
"quantity", sa.Numeric(precision=14, scale=2), nullable=False, comment="发货数量(>0)"
|
||||
),
|
||||
sa.Column(
|
||||
"sale_price",
|
||||
sa.Numeric(precision=14, scale=4),
|
||||
nullable=True,
|
||||
comment="发货单价(元;T+ 同步或人工,口径待 IT 确认)",
|
||||
),
|
||||
sa.Column(
|
||||
"amount",
|
||||
sa.Numeric(precision=14, scale=2),
|
||||
nullable=False,
|
||||
comment="行金额 = 数量×单价(服务层计算固化,不信任前端传值)",
|
||||
),
|
||||
sa.Column(
|
||||
"create_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="创建时间",
|
||||
),
|
||||
sa.Column(
|
||||
"update_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="更新时间",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["product_id"],
|
||||
["products.id"],
|
||||
name=op.f("fk_shipment_items_product_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["shipment_id"],
|
||||
["shipments.id"],
|
||||
name=op.f("fk_shipment_items_shipment_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_shipment_items")),
|
||||
comment="发货产品明细表",
|
||||
mysql_charset="utf8mb4",
|
||||
mysql_collate="utf8mb4_general_ci",
|
||||
mysql_engine="InnoDB",
|
||||
)
|
||||
op.create_index("idx_shipment_items_product_id", "shipment_items", ["product_id"], unique=False)
|
||||
op.create_index(
|
||||
"idx_shipment_items_shipment_id", "shipment_items", ["shipment_id"], unique=False
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
# 评级等级种子(等级映射可配置;双轨:init-dev.sql 同步写入)
|
||||
op.bulk_insert(
|
||||
sa.table(
|
||||
"customer_rating_grades",
|
||||
sa.column("grade_name", sa.String),
|
||||
sa.column("min_score", sa.Numeric),
|
||||
sa.column("max_score", sa.Numeric),
|
||||
sa.column("sort_order", sa.Integer),
|
||||
sa.column("remark", sa.String),
|
||||
),
|
||||
_GRADE_SEED,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""回滚迁移。
|
||||
|
||||
**注意(autogenerate 不会替你处理)**:生成的 `op.drop_index` 与
|
||||
`op.drop_table` 顺序需人工检查 —— 若某索引是外键的唯一支撑索引,
|
||||
单独 drop_index 会报 MySQL 1553(needed in a foreign key constraint)。
|
||||
**整表回滚应只保留 `op.drop_table`**,DROP TABLE 会连带删除索引与外键。
|
||||
"""
|
||||
# 整表回滚只保留 drop_table(DROP TABLE 连带删除索引与外键);
|
||||
# 逐条 drop_index 会先于 drop_table 摘掉外键支撑索引而报 MySQL 1553
|
||||
op.drop_table("shipment_items")
|
||||
op.drop_table("receipts")
|
||||
op.drop_table("shipments")
|
||||
op.drop_table("business_items")
|
||||
op.drop_table("businesses")
|
||||
op.drop_table("business_status_logs")
|
||||
op.drop_table("customer_rating_grades")
|
||||
@@ -11,6 +11,7 @@ Revises: bfbda9c3191d
|
||||
Create Date: 2026-10-13 09:36:02.219248
|
||||
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from alembic import op
|
||||
@@ -18,8 +19,8 @@ import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'e1133a7c670a'
|
||||
down_revision: str | Sequence[str] | None = 'bfbda9c3191d'
|
||||
revision: str = "e1133a7c670a"
|
||||
down_revision: str | Sequence[str] | None = "bfbda9c3191d"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
@@ -27,141 +28,449 @@ depends_on: str | Sequence[str] | None = None
|
||||
def upgrade() -> None:
|
||||
"""应用迁移。"""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('products',
|
||||
sa.Column('id', mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment='主键ID'),
|
||||
sa.Column('product_name', sa.String(length=128), nullable=False, comment='产品名称'),
|
||||
sa.Column('spec_model', sa.String(length=128), server_default='', nullable=False, comment='规格型号(空串而非 NULL:参与组合唯一键,MySQL 组合索引对 NULL 不去重)'),
|
||||
sa.Column('unit', sa.String(length=16), nullable=True, comment='计量单位'),
|
||||
sa.Column('stock_quantity', sa.Numeric(precision=14, scale=2), nullable=True, comment='数量(T+ 同步原样存储,语义待 IT 确认)'),
|
||||
sa.Column('sale_price', sa.Numeric(precision=14, scale=4), nullable=True, comment='销售单价(元,T+ 同步)'),
|
||||
sa.Column('product_category', sa.String(length=32), nullable=False, comment='产品类别:municipal_water市政水利 rail_transit轨道交通(决定折算阈值 30%/20%;T+同步不覆盖)'),
|
||||
sa.Column('status', mysql.TINYINT(), server_default='1', nullable=False, comment='状态:1启用 0停用'),
|
||||
sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'),
|
||||
sa.Column('create_time', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False, comment='创建时间'),
|
||||
sa.Column('update_time', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=False, comment='更新时间'),
|
||||
sa.Column('source_no', sa.String(length=64), nullable=True, comment='T+ 原始单据号(同步幂等去重锚点;唯一键待 IT 定键后升级)'),
|
||||
sa.Column('t_plus_id', sa.String(length=64), nullable=True, comment='T+ 系统内部标识'),
|
||||
sa.Column('sync_batch_no', sa.String(length=32), nullable=True, comment='同步批次号(同步模块写入)'),
|
||||
sa.Column('synced_at', sa.DateTime(), nullable=True, comment='最近同步时间'),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('pk_products')),
|
||||
sa.UniqueConstraint('product_name', 'spec_model', name='uk_products_name_spec'),
|
||||
comment='产品表(类别锚定折算阈值参数;T+同步不覆盖产品类别)',
|
||||
mysql_charset='utf8mb4',
|
||||
mysql_collate='utf8mb4_general_ci',
|
||||
mysql_engine='InnoDB'
|
||||
op.create_table(
|
||||
"products",
|
||||
sa.Column(
|
||||
"id", mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment="主键ID"
|
||||
),
|
||||
sa.Column("product_name", sa.String(length=128), nullable=False, comment="产品名称"),
|
||||
sa.Column(
|
||||
"spec_model",
|
||||
sa.String(length=128),
|
||||
server_default="",
|
||||
nullable=False,
|
||||
comment="规格型号(空串而非 NULL:参与组合唯一键,MySQL 组合索引对 NULL 不去重)",
|
||||
),
|
||||
sa.Column("unit", sa.String(length=16), nullable=True, comment="计量单位"),
|
||||
sa.Column(
|
||||
"stock_quantity",
|
||||
sa.Numeric(precision=14, scale=2),
|
||||
nullable=True,
|
||||
comment="数量(T+ 同步原样存储,语义待 IT 确认)",
|
||||
),
|
||||
sa.Column(
|
||||
"sale_price",
|
||||
sa.Numeric(precision=14, scale=4),
|
||||
nullable=True,
|
||||
comment="销售单价(元,T+ 同步)",
|
||||
),
|
||||
sa.Column(
|
||||
"product_category",
|
||||
sa.String(length=32),
|
||||
nullable=False,
|
||||
comment="产品类别:municipal_water市政水利 rail_transit轨道交通(决定折算阈值 30%/20%;T+同步不覆盖)",
|
||||
),
|
||||
sa.Column(
|
||||
"status",
|
||||
mysql.TINYINT(),
|
||||
server_default="1",
|
||||
nullable=False,
|
||||
comment="状态:1启用 0停用",
|
||||
),
|
||||
sa.Column("remark", sa.String(length=255), nullable=True, comment="备注"),
|
||||
sa.Column(
|
||||
"create_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="创建时间",
|
||||
),
|
||||
sa.Column(
|
||||
"update_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="更新时间",
|
||||
),
|
||||
sa.Column(
|
||||
"source_no",
|
||||
sa.String(length=64),
|
||||
nullable=True,
|
||||
comment="T+ 原始单据号(同步幂等去重锚点;唯一键待 IT 定键后升级)",
|
||||
),
|
||||
sa.Column("t_plus_id", sa.String(length=64), nullable=True, comment="T+ 系统内部标识"),
|
||||
sa.Column(
|
||||
"sync_batch_no",
|
||||
sa.String(length=32),
|
||||
nullable=True,
|
||||
comment="同步批次号(同步模块写入)",
|
||||
),
|
||||
sa.Column("synced_at", sa.DateTime(), nullable=True, comment="最近同步时间"),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_products")),
|
||||
sa.UniqueConstraint("product_name", "spec_model", name="uk_products_name_spec"),
|
||||
comment="产品表(类别锚定折算阈值参数;T+同步不覆盖产品类别)",
|
||||
mysql_charset="utf8mb4",
|
||||
mysql_collate="utf8mb4_general_ci",
|
||||
mysql_engine="InnoDB",
|
||||
)
|
||||
op.create_index(op.f('idx_products_source_no'), 'products', ['source_no'], unique=False)
|
||||
op.create_table('ownership_change_logs',
|
||||
sa.Column('id', mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment='主键ID'),
|
||||
sa.Column('owner_type', mysql.TINYINT(), nullable=False, comment='归属类型:1客户归属 2业务归属'),
|
||||
sa.Column('customer_id', mysql.BIGINT(unsigned=True), nullable=True, comment='客户ID(固化,owner_type=1 时非空)'),
|
||||
sa.Column('business_id', mysql.BIGINT(unsigned=True), nullable=True, comment='业务ID(固化,owner_type=2 时非空)'),
|
||||
sa.Column('old_owner_personnel_id', mysql.BIGINT(unsigned=True), nullable=True, comment='变更前负责营销人员ID(固化)'),
|
||||
sa.Column('new_owner_personnel_id', mysql.BIGINT(unsigned=True), nullable=True, comment='变更后负责营销人员ID(固化)'),
|
||||
sa.Column('old_assistant_personnel_id', mysql.BIGINT(unsigned=True), nullable=True, comment='变更前协助营销人员ID(固化,仅业务归属使用)'),
|
||||
sa.Column('new_assistant_personnel_id', mysql.BIGINT(unsigned=True), nullable=True, comment='变更后协助营销人员ID(固化,仅业务归属使用)'),
|
||||
sa.Column('change_reason', sa.String(length=255), nullable=True, comment='变更原因'),
|
||||
sa.Column('operator_id', mysql.BIGINT(unsigned=True), nullable=True, comment='操作人ID(账号删除后置空)'),
|
||||
sa.Column('operator_name', sa.String(length=64), nullable=False, comment='操作人用户名(冗余固化)'),
|
||||
sa.Column('create_time', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False, comment='创建时间'),
|
||||
sa.ForeignKeyConstraint(['operator_id'], ['users.id'], name=op.f('fk_ownership_change_logs_operator_id'), ondelete='SET NULL'),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('pk_ownership_change_logs')),
|
||||
comment='归属变更记录表(append-only;客户/业务归属变更留痕,不回溯历史)',
|
||||
mysql_charset='utf8mb4',
|
||||
mysql_collate='utf8mb4_general_ci',
|
||||
mysql_engine='InnoDB'
|
||||
op.create_index(op.f("idx_products_source_no"), "products", ["source_no"], unique=False)
|
||||
op.create_table(
|
||||
"ownership_change_logs",
|
||||
sa.Column(
|
||||
"id", mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment="主键ID"
|
||||
),
|
||||
sa.Column(
|
||||
"owner_type", mysql.TINYINT(), nullable=False, comment="归属类型:1客户归属 2业务归属"
|
||||
),
|
||||
sa.Column(
|
||||
"customer_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="客户ID(固化,owner_type=1 时非空)",
|
||||
),
|
||||
sa.Column(
|
||||
"business_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="业务ID(固化,owner_type=2 时非空)",
|
||||
),
|
||||
sa.Column(
|
||||
"old_owner_personnel_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="变更前负责营销人员ID(固化)",
|
||||
),
|
||||
sa.Column(
|
||||
"new_owner_personnel_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="变更后负责营销人员ID(固化)",
|
||||
),
|
||||
sa.Column(
|
||||
"old_assistant_personnel_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="变更前协助营销人员ID(固化,仅业务归属使用)",
|
||||
),
|
||||
sa.Column(
|
||||
"new_assistant_personnel_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="变更后协助营销人员ID(固化,仅业务归属使用)",
|
||||
),
|
||||
sa.Column("change_reason", sa.String(length=255), nullable=True, comment="变更原因"),
|
||||
sa.Column(
|
||||
"operator_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="操作人ID(账号删除后置空)",
|
||||
),
|
||||
sa.Column(
|
||||
"operator_name",
|
||||
sa.String(length=64),
|
||||
nullable=False,
|
||||
comment="操作人用户名(冗余固化)",
|
||||
),
|
||||
sa.Column(
|
||||
"create_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="创建时间",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["operator_id"],
|
||||
["users.id"],
|
||||
name=op.f("fk_ownership_change_logs_operator_id"),
|
||||
ondelete="SET NULL",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_ownership_change_logs")),
|
||||
comment="归属变更记录表(append-only;客户/业务归属变更留痕,不回溯历史)",
|
||||
mysql_charset="utf8mb4",
|
||||
mysql_collate="utf8mb4_general_ci",
|
||||
mysql_engine="InnoDB",
|
||||
)
|
||||
op.create_index(op.f('idx_ownership_change_logs_customer_id'), 'ownership_change_logs', ['customer_id'], unique=False)
|
||||
op.create_index(op.f('idx_ownership_change_logs_business_id'), 'ownership_change_logs', ['business_id'], unique=False)
|
||||
op.create_index(op.f('idx_ownership_change_logs_create_time'), 'ownership_change_logs', ['create_time'], unique=False)
|
||||
op.create_table('personnel',
|
||||
sa.Column('id', mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment='主键ID'),
|
||||
sa.Column('personnel_name', sa.String(length=64), nullable=False, comment='姓名'),
|
||||
sa.Column('business_role_id', mysql.BIGINT(unsigned=True), nullable=False, comment='岗位角色ID(决定计提公式模板;T+同步不覆盖)'),
|
||||
sa.Column('manager_id', mysql.BIGINT(unsigned=True), nullable=True, comment='直属营销经理ID(NULL=经理本人或未分配;团队成员关系锚点)'),
|
||||
sa.Column('phone', sa.String(length=20), nullable=True, comment='联系电话'),
|
||||
sa.Column('hire_date', sa.Date(), nullable=True, comment='入职日期'),
|
||||
sa.Column('is_probation', mysql.TINYINT(), server_default='0', nullable=False, comment='是否试用期:1是 0否(试用期绩效照算不发放)'),
|
||||
sa.Column('salary_standard', sa.Numeric(precision=12, scale=2), nullable=True, comment='月工资标准(元;T+同步不覆盖)'),
|
||||
sa.Column('status', mysql.TINYINT(), server_default='1', nullable=False, comment='状态:1在职 0离职(离职人员不可再被业务/客户归属引用)'),
|
||||
sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'),
|
||||
sa.Column('create_time', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False, comment='创建时间'),
|
||||
sa.Column('update_time', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=False, comment='更新时间'),
|
||||
sa.Column('source_no', sa.String(length=64), nullable=True, comment='T+ 原始单据号(同步幂等去重锚点;唯一键待 IT 定键后升级)'),
|
||||
sa.Column('t_plus_id', sa.String(length=64), nullable=True, comment='T+ 系统内部标识'),
|
||||
sa.Column('sync_batch_no', sa.String(length=32), nullable=True, comment='同步批次号(同步模块写入)'),
|
||||
sa.Column('synced_at', sa.DateTime(), nullable=True, comment='最近同步时间'),
|
||||
sa.ForeignKeyConstraint(['business_role_id'], ['business_roles.id'], name=op.f('fk_personnel_business_role_id'), ondelete='RESTRICT'),
|
||||
sa.ForeignKeyConstraint(['manager_id'], ['personnel.id'], name=op.f('fk_personnel_manager_id'), ondelete='RESTRICT'),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('pk_personnel')),
|
||||
comment='营销人员表(T+同步不覆盖岗位与工资标准)',
|
||||
mysql_charset='utf8mb4',
|
||||
mysql_collate='utf8mb4_general_ci',
|
||||
mysql_engine='InnoDB'
|
||||
op.create_index(
|
||||
op.f("idx_ownership_change_logs_customer_id"),
|
||||
"ownership_change_logs",
|
||||
["customer_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(op.f('idx_personnel_business_role_id'), 'personnel', ['business_role_id'], unique=False)
|
||||
op.create_index(op.f('idx_personnel_manager_id'), 'personnel', ['manager_id'], unique=False)
|
||||
op.create_index(op.f('idx_personnel_source_no'), 'personnel', ['source_no'], unique=False)
|
||||
op.create_table('customers',
|
||||
sa.Column('id', mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment='主键ID'),
|
||||
sa.Column('company_name', sa.String(length=128), nullable=False, comment='公司名称'),
|
||||
sa.Column('owner_name', sa.String(length=64), nullable=False, comment='客户负责人姓名(客户公司负责人的人名,非系统人员)'),
|
||||
sa.Column('contact_person', sa.String(length=64), nullable=True, comment='联系人'),
|
||||
sa.Column('contact_phone', sa.String(length=20), nullable=True, comment='联系电话'),
|
||||
sa.Column('address', sa.String(length=255), nullable=True, comment='地址'),
|
||||
sa.Column('industry', sa.String(length=64), nullable=True, comment='所属行业(自由文本)'),
|
||||
sa.Column('customer_type', sa.String(length=32), server_default='personal', nullable=False, comment='客户分类:personal个人户 manager_personal经理个人户 enterprise企业户(计提规则匹配锚点;T+同步不覆盖)'),
|
||||
sa.Column('owner_personnel_id', mysql.BIGINT(unsigned=True), nullable=True, comment='归属负责营销人员ID(手动修改不回溯历史,变更留痕见 ownership_change_logs)'),
|
||||
sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'),
|
||||
sa.Column('create_time', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False, comment='创建时间'),
|
||||
sa.Column('update_time', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=False, comment='更新时间'),
|
||||
sa.Column('source_no', sa.String(length=64), nullable=True, comment='T+ 原始单据号(同步幂等去重锚点;唯一键待 IT 定键后升级)'),
|
||||
sa.Column('t_plus_id', sa.String(length=64), nullable=True, comment='T+ 系统内部标识'),
|
||||
sa.Column('sync_batch_no', sa.String(length=32), nullable=True, comment='同步批次号(同步模块写入)'),
|
||||
sa.Column('synced_at', sa.DateTime(), nullable=True, comment='最近同步时间'),
|
||||
sa.ForeignKeyConstraint(['owner_personnel_id'], ['personnel.id'], name=op.f('fk_customers_owner_personnel_id'), ondelete='RESTRICT'),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('pk_customers')),
|
||||
sa.UniqueConstraint('company_name', 'owner_name', name='uk_customers_company_owner'),
|
||||
comment='客户表(公司名称+负责人为业务唯一标识;T+同步不覆盖客户分类)',
|
||||
mysql_charset='utf8mb4',
|
||||
mysql_collate='utf8mb4_general_ci',
|
||||
mysql_engine='InnoDB'
|
||||
op.create_index(
|
||||
op.f("idx_ownership_change_logs_business_id"),
|
||||
"ownership_change_logs",
|
||||
["business_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(op.f('idx_customers_customer_type'), 'customers', ['customer_type'], unique=False)
|
||||
op.create_index(op.f('idx_customers_owner_personnel_id'), 'customers', ['owner_personnel_id'], unique=False)
|
||||
op.create_index(op.f('idx_customers_source_no'), 'customers', ['source_no'], unique=False)
|
||||
op.create_table('personnel_plans',
|
||||
sa.Column('id', mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment='主键ID'),
|
||||
sa.Column('personnel_id', mysql.BIGINT(unsigned=True), nullable=False, comment='人员ID(personnel.id)'),
|
||||
sa.Column('plan_period', sa.String(length=32), nullable=False, comment='计划期间(如 2026Q1、2026-05)'),
|
||||
sa.Column('plan_content', sa.String(length=500), nullable=False, comment='计划内容'),
|
||||
sa.Column('target_desc', sa.String(length=255), nullable=True, comment='指标说明(文字描述)'),
|
||||
sa.Column('source_no', sa.String(length=64), nullable=True, comment='T+ 原始条目号(预留)'),
|
||||
sa.Column('create_time', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False, comment='创建时间'),
|
||||
sa.Column('update_time', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=False, comment='更新时间'),
|
||||
sa.ForeignKeyConstraint(['personnel_id'], ['personnel.id'], name=op.f('fk_personnel_plans_personnel_id'), ondelete='RESTRICT'),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('pk_personnel_plans')),
|
||||
comment='市场拓展计划表',
|
||||
mysql_charset='utf8mb4',
|
||||
mysql_collate='utf8mb4_general_ci',
|
||||
mysql_engine='InnoDB'
|
||||
op.create_index(
|
||||
op.f("idx_ownership_change_logs_create_time"),
|
||||
"ownership_change_logs",
|
||||
["create_time"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(op.f('idx_personnel_plans_personnel_id'), 'personnel_plans', ['personnel_id'], unique=False)
|
||||
op.create_table('personnel_regions',
|
||||
sa.Column('id', mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment='主键ID'),
|
||||
sa.Column('personnel_id', mysql.BIGINT(unsigned=True), nullable=False, comment='人员ID(personnel.id)'),
|
||||
sa.Column('region_name', sa.String(length=128), nullable=False, comment='区域名称(产品辐射地理范围)'),
|
||||
sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'),
|
||||
sa.Column('create_time', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False, comment='创建时间'),
|
||||
sa.Column('update_time', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=False, comment='更新时间'),
|
||||
sa.ForeignKeyConstraint(['personnel_id'], ['personnel.id'], name=op.f('fk_personnel_regions_personnel_id'), ondelete='RESTRICT'),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('pk_personnel_regions')),
|
||||
comment='产品辐射区域表',
|
||||
mysql_charset='utf8mb4',
|
||||
mysql_collate='utf8mb4_general_ci',
|
||||
mysql_engine='InnoDB'
|
||||
op.create_table(
|
||||
"personnel",
|
||||
sa.Column(
|
||||
"id", mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment="主键ID"
|
||||
),
|
||||
sa.Column("personnel_name", sa.String(length=64), nullable=False, comment="姓名"),
|
||||
sa.Column(
|
||||
"business_role_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=False,
|
||||
comment="岗位角色ID(决定计提公式模板;T+同步不覆盖)",
|
||||
),
|
||||
sa.Column(
|
||||
"manager_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="直属营销经理ID(NULL=经理本人或未分配;团队成员关系锚点)",
|
||||
),
|
||||
sa.Column("phone", sa.String(length=20), nullable=True, comment="联系电话"),
|
||||
sa.Column("hire_date", sa.Date(), nullable=True, comment="入职日期"),
|
||||
sa.Column(
|
||||
"is_probation",
|
||||
mysql.TINYINT(),
|
||||
server_default="0",
|
||||
nullable=False,
|
||||
comment="是否试用期:1是 0否(试用期绩效照算不发放)",
|
||||
),
|
||||
sa.Column(
|
||||
"salary_standard",
|
||||
sa.Numeric(precision=12, scale=2),
|
||||
nullable=True,
|
||||
comment="月工资标准(元;T+同步不覆盖)",
|
||||
),
|
||||
sa.Column(
|
||||
"status",
|
||||
mysql.TINYINT(),
|
||||
server_default="1",
|
||||
nullable=False,
|
||||
comment="状态:1在职 0离职(离职人员不可再被业务/客户归属引用)",
|
||||
),
|
||||
sa.Column("remark", sa.String(length=255), nullable=True, comment="备注"),
|
||||
sa.Column(
|
||||
"create_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="创建时间",
|
||||
),
|
||||
sa.Column(
|
||||
"update_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="更新时间",
|
||||
),
|
||||
sa.Column(
|
||||
"source_no",
|
||||
sa.String(length=64),
|
||||
nullable=True,
|
||||
comment="T+ 原始单据号(同步幂等去重锚点;唯一键待 IT 定键后升级)",
|
||||
),
|
||||
sa.Column("t_plus_id", sa.String(length=64), nullable=True, comment="T+ 系统内部标识"),
|
||||
sa.Column(
|
||||
"sync_batch_no",
|
||||
sa.String(length=32),
|
||||
nullable=True,
|
||||
comment="同步批次号(同步模块写入)",
|
||||
),
|
||||
sa.Column("synced_at", sa.DateTime(), nullable=True, comment="最近同步时间"),
|
||||
sa.ForeignKeyConstraint(
|
||||
["business_role_id"],
|
||||
["business_roles.id"],
|
||||
name=op.f("fk_personnel_business_role_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["manager_id"],
|
||||
["personnel.id"],
|
||||
name=op.f("fk_personnel_manager_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_personnel")),
|
||||
comment="营销人员表(T+同步不覆盖岗位与工资标准)",
|
||||
mysql_charset="utf8mb4",
|
||||
mysql_collate="utf8mb4_general_ci",
|
||||
mysql_engine="InnoDB",
|
||||
)
|
||||
op.create_index(
|
||||
op.f("idx_personnel_business_role_id"), "personnel", ["business_role_id"], unique=False
|
||||
)
|
||||
op.create_index(op.f("idx_personnel_manager_id"), "personnel", ["manager_id"], unique=False)
|
||||
op.create_index(op.f("idx_personnel_source_no"), "personnel", ["source_no"], unique=False)
|
||||
op.create_table(
|
||||
"customers",
|
||||
sa.Column(
|
||||
"id", mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment="主键ID"
|
||||
),
|
||||
sa.Column("company_name", sa.String(length=128), nullable=False, comment="公司名称"),
|
||||
sa.Column(
|
||||
"owner_name",
|
||||
sa.String(length=64),
|
||||
nullable=False,
|
||||
comment="客户负责人姓名(客户公司负责人的人名,非系统人员)",
|
||||
),
|
||||
sa.Column("contact_person", sa.String(length=64), nullable=True, comment="联系人"),
|
||||
sa.Column("contact_phone", sa.String(length=20), nullable=True, comment="联系电话"),
|
||||
sa.Column("address", sa.String(length=255), nullable=True, comment="地址"),
|
||||
sa.Column("industry", sa.String(length=64), nullable=True, comment="所属行业(自由文本)"),
|
||||
sa.Column(
|
||||
"customer_type",
|
||||
sa.String(length=32),
|
||||
server_default="personal",
|
||||
nullable=False,
|
||||
comment="客户分类:personal个人户 manager_personal经理个人户 enterprise企业户(计提规则匹配锚点;T+同步不覆盖)",
|
||||
),
|
||||
sa.Column(
|
||||
"owner_personnel_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=True,
|
||||
comment="归属负责营销人员ID(手动修改不回溯历史,变更留痕见 ownership_change_logs)",
|
||||
),
|
||||
sa.Column("remark", sa.String(length=255), nullable=True, comment="备注"),
|
||||
sa.Column(
|
||||
"create_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="创建时间",
|
||||
),
|
||||
sa.Column(
|
||||
"update_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="更新时间",
|
||||
),
|
||||
sa.Column(
|
||||
"source_no",
|
||||
sa.String(length=64),
|
||||
nullable=True,
|
||||
comment="T+ 原始单据号(同步幂等去重锚点;唯一键待 IT 定键后升级)",
|
||||
),
|
||||
sa.Column("t_plus_id", sa.String(length=64), nullable=True, comment="T+ 系统内部标识"),
|
||||
sa.Column(
|
||||
"sync_batch_no",
|
||||
sa.String(length=32),
|
||||
nullable=True,
|
||||
comment="同步批次号(同步模块写入)",
|
||||
),
|
||||
sa.Column("synced_at", sa.DateTime(), nullable=True, comment="最近同步时间"),
|
||||
sa.ForeignKeyConstraint(
|
||||
["owner_personnel_id"],
|
||||
["personnel.id"],
|
||||
name=op.f("fk_customers_owner_personnel_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_customers")),
|
||||
sa.UniqueConstraint("company_name", "owner_name", name="uk_customers_company_owner"),
|
||||
comment="客户表(公司名称+负责人为业务唯一标识;T+同步不覆盖客户分类)",
|
||||
mysql_charset="utf8mb4",
|
||||
mysql_collate="utf8mb4_general_ci",
|
||||
mysql_engine="InnoDB",
|
||||
)
|
||||
op.create_index(
|
||||
op.f("idx_customers_customer_type"), "customers", ["customer_type"], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f("idx_customers_owner_personnel_id"), "customers", ["owner_personnel_id"], unique=False
|
||||
)
|
||||
op.create_index(op.f("idx_customers_source_no"), "customers", ["source_no"], unique=False)
|
||||
op.create_table(
|
||||
"personnel_plans",
|
||||
sa.Column(
|
||||
"id", mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment="主键ID"
|
||||
),
|
||||
sa.Column(
|
||||
"personnel_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=False,
|
||||
comment="人员ID(personnel.id)",
|
||||
),
|
||||
sa.Column(
|
||||
"plan_period",
|
||||
sa.String(length=32),
|
||||
nullable=False,
|
||||
comment="计划期间(如 2026Q1、2026-05)",
|
||||
),
|
||||
sa.Column("plan_content", sa.String(length=500), nullable=False, comment="计划内容"),
|
||||
sa.Column(
|
||||
"target_desc", sa.String(length=255), nullable=True, comment="指标说明(文字描述)"
|
||||
),
|
||||
sa.Column(
|
||||
"source_no", sa.String(length=64), nullable=True, comment="T+ 原始条目号(预留)"
|
||||
),
|
||||
sa.Column(
|
||||
"create_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="创建时间",
|
||||
),
|
||||
sa.Column(
|
||||
"update_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="更新时间",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["personnel_id"],
|
||||
["personnel.id"],
|
||||
name=op.f("fk_personnel_plans_personnel_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_personnel_plans")),
|
||||
comment="市场拓展计划表",
|
||||
mysql_charset="utf8mb4",
|
||||
mysql_collate="utf8mb4_general_ci",
|
||||
mysql_engine="InnoDB",
|
||||
)
|
||||
op.create_index(
|
||||
op.f("idx_personnel_plans_personnel_id"), "personnel_plans", ["personnel_id"], unique=False
|
||||
)
|
||||
op.create_table(
|
||||
"personnel_regions",
|
||||
sa.Column(
|
||||
"id", mysql.BIGINT(unsigned=True), autoincrement=True, nullable=False, comment="主键ID"
|
||||
),
|
||||
sa.Column(
|
||||
"personnel_id",
|
||||
mysql.BIGINT(unsigned=True),
|
||||
nullable=False,
|
||||
comment="人员ID(personnel.id)",
|
||||
),
|
||||
sa.Column(
|
||||
"region_name",
|
||||
sa.String(length=128),
|
||||
nullable=False,
|
||||
comment="区域名称(产品辐射地理范围)",
|
||||
),
|
||||
sa.Column("remark", sa.String(length=255), nullable=True, comment="备注"),
|
||||
sa.Column(
|
||||
"create_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="创建时间",
|
||||
),
|
||||
sa.Column(
|
||||
"update_time",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
|
||||
nullable=False,
|
||||
comment="更新时间",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["personnel_id"],
|
||||
["personnel.id"],
|
||||
name=op.f("fk_personnel_regions_personnel_id"),
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_personnel_regions")),
|
||||
comment="产品辐射区域表",
|
||||
mysql_charset="utf8mb4",
|
||||
mysql_collate="utf8mb4_general_ci",
|
||||
mysql_engine="InnoDB",
|
||||
)
|
||||
op.create_index(
|
||||
op.f("idx_personnel_regions_personnel_id"),
|
||||
"personnel_regions",
|
||||
["personnel_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(op.f('idx_personnel_regions_personnel_id'), 'personnel_regions', ['personnel_id'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
@@ -173,9 +482,9 @@ def downgrade() -> None:
|
||||
单独 drop_index 会报 MySQL 1553(needed in a foreign key constraint)。
|
||||
**整表回滚应只保留 `op.drop_table`**,DROP TABLE 会连带删除索引与外键。
|
||||
"""
|
||||
op.drop_table('personnel_regions')
|
||||
op.drop_table('personnel_plans')
|
||||
op.drop_table('customers')
|
||||
op.drop_table('personnel')
|
||||
op.drop_table('ownership_change_logs')
|
||||
op.drop_table('products')
|
||||
op.drop_table("personnel_regions")
|
||||
op.drop_table("personnel_plans")
|
||||
op.drop_table("customers")
|
||||
op.drop_table("personnel")
|
||||
op.drop_table("ownership_change_logs")
|
||||
op.drop_table("products")
|
||||
Reference in new issue
Block a user