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
@@ -66,6 +66,16 @@ MODULE_PARAM = "计提参数"
|
||||
MODULE_PERSONNEL = "人员管理"
|
||||
MODULE_PRODUCT = "产品管理"
|
||||
MODULE_CUSTOMER = "客户管理"
|
||||
MODULE_BUSINESS = "业务管理"
|
||||
MODULE_SHIPMENT = "发货管理"
|
||||
MODULE_RECEIPT = "回款管理"
|
||||
MODULE_RATING = "客户评级"
|
||||
|
||||
# ===== 单据编号前缀({前缀}{YYYYMMDD}-{当日4位序号},序号按自然日重置)=====
|
||||
# 一期为代码常量(业务未提出配置诉求);如需可配置,走 M1 参数体系新增参数组
|
||||
NO_PREFIX_BUSINESS = "YW"
|
||||
NO_PREFIX_SHIPMENT = "SH"
|
||||
NO_PREFIX_RECEIPT = "HK"
|
||||
|
||||
ACTION_CREATE = "新增"
|
||||
ACTION_UPDATE = "修改"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"""
|
||||
|
||||
from app.models.base import Base, TimestampMixin
|
||||
from app.models.business import Business, BusinessItem, BusinessStatusLog
|
||||
from app.models.business_role import BusinessRole
|
||||
from app.models.customer import Customer, OwnershipChangeLog
|
||||
from app.models.login_log import LoginLog
|
||||
@@ -17,13 +18,20 @@ from app.models.operation_log import OperationLog
|
||||
from app.models.param import ParamChangeBatch, ParamDefinition, ParamValue
|
||||
from app.models.personnel import Personnel, PersonnelPlan, PersonnelRegion
|
||||
from app.models.product import Product
|
||||
from app.models.rating import CustomerRatingGrade
|
||||
from app.models.receipt import Receipt
|
||||
from app.models.role import Permission, Role, RolePermission
|
||||
from app.models.shipment import Shipment, ShipmentItem
|
||||
from app.models.user import User, UserRole
|
||||
|
||||
__all__ = [
|
||||
"Base",
|
||||
"Business",
|
||||
"BusinessItem",
|
||||
"BusinessRole",
|
||||
"BusinessStatusLog",
|
||||
"Customer",
|
||||
"CustomerRatingGrade",
|
||||
"LoginLog",
|
||||
"OperationLog",
|
||||
"OwnershipChangeLog",
|
||||
@@ -35,8 +43,11 @@ __all__ = [
|
||||
"PersonnelPlan",
|
||||
"PersonnelRegion",
|
||||
"Product",
|
||||
"Receipt",
|
||||
"Role",
|
||||
"RolePermission",
|
||||
"Shipment",
|
||||
"ShipmentItem",
|
||||
"TimestampMixin",
|
||||
"User",
|
||||
"UserRole",
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
"""业务数据模型:业务单、产品明细与状态流转留痕。
|
||||
|
||||
- 汇总冗余列(total_* 五列)由服务层在明细/发货/回款增删改时**事务内单点
|
||||
重算**,禁手工直改 —— 列表页与 M3 起付判定直接读冗余列,避免每次 JOIN
|
||||
聚合;代价是所有写路径必须重算(business_service 统一收口)
|
||||
- 毛利润相关值(毛利润额/毛利润率/折算毛利润额)**不落库**:它们是
|
||||
sale_amount/cost_amount + 时点化阈值参数的派生值,落库会在成本调整或
|
||||
参数改版后漂移出「假快照」;M2 以只读毛利润视图实时计算(复用
|
||||
formula.profit_rate/converted_profit),M3 核算台账才是法定固化点
|
||||
- product_category 存在明细行级:混合业务按产品逐条计算(对照表 #14)
|
||||
- 运费仅记录展示,不计入毛利润口径(对照表 #2);协助营销人员一期核算
|
||||
不使用(对照表 #15 剔除协助 25% 分成),字段保留仅记录事实(需规 2.2.1)
|
||||
"""
|
||||
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy import Date, DateTime, ForeignKey, Index, Numeric, String, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.models.base import (
|
||||
BIGINT_UNSIGNED,
|
||||
TINYINT_TYPE,
|
||||
Base,
|
||||
TimestampMixin,
|
||||
TPlusSyncMixin,
|
||||
mysql_table_args,
|
||||
)
|
||||
|
||||
# 业务状态(businesses.status,流转规则见 business_service:终态不可变更)
|
||||
BUSINESS_STATUS_ACTIVE = 1
|
||||
BUSINESS_STATUS_DONE = 2
|
||||
BUSINESS_STATUS_TERMINATED = 3
|
||||
|
||||
|
||||
class Business(Base, TimestampMixin, TPlusSyncMixin):
|
||||
"""业务表(需求 2.2.1):业务编号自动生成,即报表 2.4.1 的「合同编号」。"""
|
||||
|
||||
__tablename__ = "businesses"
|
||||
__table_args__ = mysql_table_args(
|
||||
"业务表(业务编号自动生成;T+同步不覆盖业务状态)",
|
||||
Index("idx_businesses_customer_id", "customer_id"),
|
||||
Index("idx_businesses_owner_personnel_id", "owner_personnel_id"),
|
||||
Index("idx_businesses_status", "status"),
|
||||
Index("idx_businesses_business_date", "business_date"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID"
|
||||
)
|
||||
business_no: Mapped[str] = mapped_column(
|
||||
String(32),
|
||||
nullable=False,
|
||||
unique=True,
|
||||
comment="业务编号,自动生成 YW{YYYYMMDD}-{4位当日序号}(报表合同编号)",
|
||||
)
|
||||
customer_id: Mapped[int] = mapped_column(
|
||||
BIGINT_UNSIGNED,
|
||||
ForeignKey("customers.id", ondelete="RESTRICT"),
|
||||
nullable=False,
|
||||
comment="客户ID(customers.id)",
|
||||
)
|
||||
business_date: Mapped[date | None] = mapped_column(Date, comment="业务日期(T+ 同步口径)")
|
||||
status: Mapped[int] = mapped_column(
|
||||
TINYINT_TYPE,
|
||||
nullable=False,
|
||||
server_default="1",
|
||||
comment="业务状态:1执行中 2已完成 3已终止(终态不可变更;T+同步不覆盖)",
|
||||
)
|
||||
owner_personnel_id: Mapped[int | None] = mapped_column(
|
||||
BIGINT_UNSIGNED,
|
||||
ForeignKey("personnel.id", ondelete="RESTRICT"),
|
||||
comment="负责营销人员ID(核算归属锚点;NULL=待指定,M3 核算前置校验非空;变更留痕不回溯)",
|
||||
)
|
||||
assistant_personnel_id: Mapped[int | None] = mapped_column(
|
||||
BIGINT_UNSIGNED,
|
||||
ForeignKey("personnel.id", ondelete="RESTRICT"),
|
||||
comment="协助营销人员ID(一期核算不使用,对照表 #15;仅记录事实)",
|
||||
)
|
||||
is_special: Mapped[int] = mapped_column(
|
||||
TINYINT_TYPE,
|
||||
nullable=False,
|
||||
server_default="0",
|
||||
comment="特殊业务标记:1是 0否(公司参与等)",
|
||||
)
|
||||
special_note: Mapped[str | None] = mapped_column(String(255), comment="特殊业务说明")
|
||||
total_sale_amount: Mapped[Decimal | None] = mapped_column(
|
||||
Numeric(14, 2),
|
||||
server_default="0",
|
||||
comment="销售额合计(明细行汇总冗余,服务层事务内维护,禁手工直改;不含运费口径)",
|
||||
)
|
||||
total_cost_amount: Mapped[Decimal | None] = mapped_column(
|
||||
Numeric(14, 2),
|
||||
server_default="0",
|
||||
comment="成本合计(明细行汇总冗余,服务层事务内维护,禁手工直改)",
|
||||
)
|
||||
total_ship_amount: Mapped[Decimal | None] = mapped_column(
|
||||
Numeric(14, 2),
|
||||
server_default="0",
|
||||
comment="发货金额合计(发货明细汇总冗余,服务层事务内维护,禁手工直改)",
|
||||
)
|
||||
total_received_amount: Mapped[Decimal | None] = mapped_column(
|
||||
Numeric(14, 2),
|
||||
server_default="0",
|
||||
comment="回款合计(回款记录汇总冗余,服务层事务内维护;M3 起付判定直接消费)",
|
||||
)
|
||||
total_freight_amount: Mapped[Decimal | None] = mapped_column(
|
||||
Numeric(14, 2),
|
||||
server_default="0",
|
||||
comment="运费合计(发货单汇总冗余,仅记录展示;不计入毛利润口径,对照表 #2)",
|
||||
)
|
||||
remark: Mapped[str | None] = mapped_column(String(255), comment="备注")
|
||||
|
||||
|
||||
class BusinessItem(Base, TimestampMixin):
|
||||
"""业务产品明细表(需求 2.2.1/2.2.4):毛利润的行级源数据。
|
||||
|
||||
product_id 可空=自由录入产品(配合「数据提取」一键入产品库);
|
||||
产品名称/规格冗余固化,防产品改名影响历史业务。
|
||||
sale_amount 由服务层按 数量×单价 计算固化,不信任前端传值。
|
||||
cost_amount 来源待 T+ 对接确认(可空,人工可录先行)。
|
||||
"""
|
||||
|
||||
__tablename__ = "business_items"
|
||||
__table_args__ = mysql_table_args(
|
||||
"业务产品明细表(毛利润行级源数据;指导价 T+同步不覆盖)",
|
||||
Index("idx_business_items_business_id", "business_id"),
|
||||
Index("idx_business_items_product_id", "product_id"),
|
||||
Index("idx_business_items_product_category", "product_category"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID"
|
||||
)
|
||||
business_id: Mapped[int] = mapped_column(
|
||||
BIGINT_UNSIGNED,
|
||||
ForeignKey("businesses.id", ondelete="RESTRICT"),
|
||||
nullable=False,
|
||||
comment="业务ID(businesses.id)",
|
||||
)
|
||||
product_id: Mapped[int | None] = mapped_column(
|
||||
BIGINT_UNSIGNED,
|
||||
ForeignKey("products.id", ondelete="RESTRICT"),
|
||||
comment="产品ID(products.id;NULL=自由录入,未入产品库)",
|
||||
)
|
||||
product_name: Mapped[str] = mapped_column(
|
||||
String(128), nullable=False, comment="产品名称(引用产品时冗余固化)"
|
||||
)
|
||||
spec_model: Mapped[str] = mapped_column(
|
||||
String(128),
|
||||
nullable=False,
|
||||
server_default="''",
|
||||
comment="规格型号(冗余固化;空串对齐 products 组合唯一键口径)",
|
||||
)
|
||||
unit: Mapped[str | None] = mapped_column(String(16), comment="计量单位")
|
||||
product_category: Mapped[str] = mapped_column(
|
||||
String(32),
|
||||
nullable=False,
|
||||
comment="产品类别(行级折算锚点,混合业务按产品逐条计算,对照表 #14;值域同 PRODUCT_CATEGORIES)",
|
||||
)
|
||||
quantity: Mapped[Decimal] = mapped_column(Numeric(14, 2), nullable=False, comment="数量(>0)")
|
||||
sale_price: Mapped[Decimal | None] = mapped_column(
|
||||
Numeric(14, 4), comment="销售单价(元;T+ 同步或人工)"
|
||||
)
|
||||
sale_amount: Mapped[Decimal] = mapped_column(
|
||||
Numeric(14, 2),
|
||||
nullable=False,
|
||||
comment="销售额 = 数量×销售单价(服务层计算固化;不含运费口径,毛利润率分母)",
|
||||
)
|
||||
cost_amount: Mapped[Decimal | None] = mapped_column(
|
||||
Numeric(14, 2),
|
||||
comment="成本额(T+ 同步或人工录入,来源待 T+ 确认;毛利润额 = 销售额 − 成本额)",
|
||||
)
|
||||
guided_price: Mapped[Decimal | None] = mapped_column(
|
||||
Numeric(14, 4), comment="指导价(人工录入;T+同步不覆盖,需规 2.2.1/2.2.4)"
|
||||
)
|
||||
remark: Mapped[str | None] = mapped_column(String(255), comment="备注")
|
||||
|
||||
|
||||
class BusinessStatusLog(Base):
|
||||
"""业务状态变更记录表(append-only,需求 2.2.1「支持状态变更记录」)。
|
||||
|
||||
不继承 TimestampMixin(留痕无更新语义);business_id 为无外键的固化 ID
|
||||
(businesses 建表在本表之后的迁移批次内 —— 实为同批,但留痕统一口径:
|
||||
被引用方删除后记录仍完整可读,删除保护由服务层检查承担)。
|
||||
首条记录在业务创建时写入(old=new=1 执行中),详情页时间线从创建起展示。
|
||||
"""
|
||||
|
||||
__tablename__ = "business_status_logs"
|
||||
__table_args__ = mysql_table_args(
|
||||
"业务状态变更记录表(append-only)",
|
||||
Index("idx_business_status_logs_business_id", "business_id"),
|
||||
Index("idx_business_status_logs_create_time", "create_time"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID"
|
||||
)
|
||||
business_id: Mapped[int] = mapped_column(
|
||||
BIGINT_UNSIGNED,
|
||||
comment="业务ID(固化,无外键;删除保护由服务层承担)",
|
||||
)
|
||||
old_status: Mapped[int] = mapped_column(
|
||||
TINYINT_TYPE, nullable=False, comment="变更前状态(创建首条为 1)"
|
||||
)
|
||||
new_status: Mapped[int] = mapped_column(TINYINT_TYPE, nullable=False, comment="变更后状态")
|
||||
change_reason: Mapped[str | None] = mapped_column(String(255), comment="变更原因(终止时必填)")
|
||||
operator_id: Mapped[int | None] = mapped_column(
|
||||
BIGINT_UNSIGNED,
|
||||
ForeignKey("users.id", ondelete="SET NULL"),
|
||||
comment="操作人ID(账号删除后置空)",
|
||||
)
|
||||
operator_name: Mapped[str] = mapped_column(
|
||||
String(64), nullable=False, comment="操作人用户名(冗余固化)"
|
||||
)
|
||||
create_time: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"), comment="创建时间"
|
||||
)
|
||||
@@ -0,0 +1,46 @@
|
||||
"""客户评级等级配置模型(需求 2.2.5)。
|
||||
|
||||
评级计算器定位为「仅打分工具,不影响绩效金额」(对照表 #25):评分记录
|
||||
不落库,等级**映射关系**可配置由本表承载。判定规则:按 sort_order 升序取
|
||||
第一个 min_score ≤ 总分的等级 —— 单边判定避免区间边界(如 75 分)在
|
||||
「≥75 且 ≤89」与「>74 且 <75」两套写法间的重叠/遗漏歧义;max_score 仅展示。
|
||||
"""
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy import Integer, Numeric, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.models.base import BIGINT_UNSIGNED, Base, TimestampMixin, mysql_table_args
|
||||
|
||||
|
||||
class CustomerRatingGrade(Base, TimestampMixin):
|
||||
"""客户评级等级配置表(需求 2.2.5「等级对应关系可配置」)。
|
||||
|
||||
种子四条随迁移初始化:A 优秀 90-100 / B 良好 75-89 / C 合格 60-74 /
|
||||
D 待改进 0-59。区间不重叠与 0≤min≤max≤100 由应用层校验。
|
||||
"""
|
||||
|
||||
__tablename__ = "customer_rating_grades"
|
||||
__table_args__ = mysql_table_args(
|
||||
"客户评级等级配置表(等级映射可配置;判定按 sort_order 升序首个 min_score≤总分)"
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID"
|
||||
)
|
||||
grade_name: Mapped[str] = mapped_column(
|
||||
String(32), nullable=False, unique=True, comment="等级名称(如 A/优秀)"
|
||||
)
|
||||
min_score: Mapped[Decimal] = mapped_column(
|
||||
Numeric(5, 1),
|
||||
nullable=False,
|
||||
comment="总分下限(含);判定=按 sort_order 升序首个 min_score ≤ 总分",
|
||||
)
|
||||
max_score: Mapped[Decimal] = mapped_column(
|
||||
Numeric(5, 1), nullable=False, comment="总分上限(展示用)"
|
||||
)
|
||||
sort_order: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, server_default="0", comment="排序(判定顺序,升序)"
|
||||
)
|
||||
remark: Mapped[str | None] = mapped_column(String(255), comment="说明")
|
||||
@@ -0,0 +1,83 @@
|
||||
"""回款模型:回款记录与批次追溯。
|
||||
|
||||
- shipment_id 为**手动指定**的发货批次(对照表 #18):可空=暂未指定;
|
||||
必须与 business_id 同属一个业务(服务层强校验,无数据库级约束可表达
|
||||
跨表同属关系);指定后回款天数方可由 formula.payment_days 派生
|
||||
- settlement_status 是 **M3 核算防重锚点**(需规 2.3.3「每笔回款仅核算一次,
|
||||
台账记录核销状态」):预置于本表避免 M3 反向迁移改 M2 表;M3 台账建立后
|
||||
由核算服务写入,M2 仅维护 0(未核算)
|
||||
- receipt_date 是 M3 参数版本匹配日(对照表 #11:按回款到账日取时点参数)
|
||||
- 一期禁负数金额(≥0):红字/退款冲销需求出现时先补设计再放开
|
||||
"""
|
||||
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy import Date, ForeignKey, Index, Numeric, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.models.base import (
|
||||
BIGINT_UNSIGNED,
|
||||
TINYINT_TYPE,
|
||||
Base,
|
||||
TimestampMixin,
|
||||
TPlusSyncMixin,
|
||||
mysql_table_args,
|
||||
)
|
||||
|
||||
# 绩效核算状态(receipts.settlement_status)
|
||||
SETTLEMENT_PENDING = 0
|
||||
SETTLEMENT_DONE = 1
|
||||
|
||||
|
||||
class Receipt(Base, TimestampMixin, TPlusSyncMixin):
|
||||
"""回款表(需求 2.2.3):按业务登记到账款项,批次号系统内唯一。"""
|
||||
|
||||
__tablename__ = "receipts"
|
||||
__table_args__ = mysql_table_args(
|
||||
"回款表(手动指定发货批次;已核算回款禁改禁删)",
|
||||
Index("idx_receipts_business_id", "business_id"),
|
||||
Index("idx_receipts_receipt_date", "receipt_date"),
|
||||
Index("idx_receipts_settlement_status", "settlement_status"),
|
||||
Index("idx_receipts_shipment_id", "shipment_id"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID"
|
||||
)
|
||||
receipt_batch_no: Mapped[str] = mapped_column(
|
||||
String(32),
|
||||
nullable=False,
|
||||
unique=True,
|
||||
comment="回款批次号,自动生成 HK{YYYYMMDD}-{4位当日序号}(系统内唯一;T+ 同步回款以 source_no 幂等)",
|
||||
)
|
||||
business_id: Mapped[int] = mapped_column(
|
||||
BIGINT_UNSIGNED,
|
||||
ForeignKey("businesses.id", ondelete="RESTRICT"),
|
||||
nullable=False,
|
||||
comment="业务ID(businesses.id)",
|
||||
)
|
||||
shipment_id: Mapped[int | None] = mapped_column(
|
||||
BIGINT_UNSIGNED,
|
||||
ForeignKey("shipments.id", ondelete="RESTRICT"),
|
||||
comment="发货单ID(shipments.id;手动指定的回款对应批次,须与本行业务一致;NULL=暂未指定)",
|
||||
)
|
||||
receipt_date: Mapped[date] = mapped_column(
|
||||
Date, nullable=False, comment="回款到账日(M3 按此日匹配参数版本,对照表 #11)"
|
||||
)
|
||||
amount: Mapped[Decimal] = mapped_column(
|
||||
Numeric(14, 2), nullable=False, comment="回款金额(≥0;一期禁负数/红字)"
|
||||
)
|
||||
is_barter: Mapped[int] = mapped_column(
|
||||
TINYINT_TYPE,
|
||||
nullable=False,
|
||||
server_default="0",
|
||||
comment="以物抵顶标记:1是 0否(M3 核算按应发绩效×barter.discount 折算,M2 仅标记)",
|
||||
)
|
||||
settlement_status: Mapped[int] = mapped_column(
|
||||
TINYINT_TYPE,
|
||||
nullable=False,
|
||||
server_default="0",
|
||||
comment="绩效核算状态:0未核算 1已核算(M3 核算防重锚点;已核算回款禁改禁删)",
|
||||
)
|
||||
remark: Mapped[str | None] = mapped_column(String(255), comment="备注")
|
||||
@@ -0,0 +1,115 @@
|
||||
"""发货模型:发货单与发货产品明细。
|
||||
|
||||
- ship_date 是**回款天数计算基准日**(对照表 #1:发货日首日计第 1 天),
|
||||
回款指定本发货单后由 formula.payment_days 派生回款天数,不在本表落库
|
||||
- 运费仅记录展示,不计入毛利润口径(对照表 #2)
|
||||
- total_amount 为明细行汇总冗余,服务层事务内维护
|
||||
"""
|
||||
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy import Date, ForeignKey, Index, Numeric, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.models.base import (
|
||||
BIGINT_UNSIGNED,
|
||||
Base,
|
||||
TimestampMixin,
|
||||
TPlusSyncMixin,
|
||||
mysql_table_args,
|
||||
)
|
||||
|
||||
|
||||
class Shipment(Base, TimestampMixin, TPlusSyncMixin):
|
||||
"""发货单表(需求 2.2.2):每笔发货对应一个业务,支持分批发货。"""
|
||||
|
||||
__tablename__ = "shipments"
|
||||
__table_args__ = mysql_table_args(
|
||||
"发货单表(发货日为回款天数基准日,发货日首日计第 1 天)",
|
||||
Index("idx_shipments_business_id", "business_id"),
|
||||
Index("idx_shipments_ship_date", "ship_date"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID"
|
||||
)
|
||||
shipment_no: Mapped[str] = mapped_column(
|
||||
String(32),
|
||||
nullable=False,
|
||||
unique=True,
|
||||
comment="发货单号,自动生成 SH{YYYYMMDD}-{4位当日序号}(内部追溯用)",
|
||||
)
|
||||
business_id: Mapped[int] = mapped_column(
|
||||
BIGINT_UNSIGNED,
|
||||
ForeignKey("businesses.id", ondelete="RESTRICT"),
|
||||
nullable=False,
|
||||
comment="业务ID(businesses.id;终态业务禁止新发货)",
|
||||
)
|
||||
ship_date: Mapped[date] = mapped_column(
|
||||
Date, nullable=False, comment="发货日期(回款天数基准日,发货日首日=第 1 天)"
|
||||
)
|
||||
freight_amount: Mapped[Decimal] = mapped_column(
|
||||
Numeric(14, 2),
|
||||
nullable=False,
|
||||
server_default="0",
|
||||
comment="运费金额(仅记录展示;不计入毛利润口径,对照表 #2)",
|
||||
)
|
||||
total_amount: Mapped[Decimal] = mapped_column(
|
||||
Numeric(14, 2),
|
||||
nullable=False,
|
||||
server_default="0",
|
||||
comment="发货总金额(明细行汇总冗余,服务层事务内维护,禁手工直改)",
|
||||
)
|
||||
remark: Mapped[str | None] = mapped_column(String(255), comment="备注")
|
||||
|
||||
|
||||
class ShipmentItem(Base, TimestampMixin):
|
||||
"""发货产品明细表(需求 2.2.2):记录发货的产品名称、规格、数量等。
|
||||
|
||||
价格口径两可(T+ 供价或人工录入,待 IT 确认):sale_price 可空人工补,
|
||||
amount 由服务层按 数量×单价 计算固化。
|
||||
"""
|
||||
|
||||
__tablename__ = "shipment_items"
|
||||
__table_args__ = mysql_table_args(
|
||||
"发货产品明细表",
|
||||
Index("idx_shipment_items_shipment_id", "shipment_id"),
|
||||
Index("idx_shipment_items_product_id", "product_id"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(
|
||||
BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID"
|
||||
)
|
||||
shipment_id: Mapped[int] = mapped_column(
|
||||
BIGINT_UNSIGNED,
|
||||
ForeignKey("shipments.id", ondelete="RESTRICT"),
|
||||
nullable=False,
|
||||
comment="发货单ID(shipments.id)",
|
||||
)
|
||||
product_id: Mapped[int | None] = mapped_column(
|
||||
BIGINT_UNSIGNED,
|
||||
ForeignKey("products.id", ondelete="RESTRICT"),
|
||||
comment="产品ID(products.id;NULL=自由录入)",
|
||||
)
|
||||
product_name: Mapped[str] = mapped_column(
|
||||
String(128), nullable=False, comment="产品名称(引用产品时冗余固化)"
|
||||
)
|
||||
spec_model: Mapped[str] = mapped_column(
|
||||
String(128),
|
||||
nullable=False,
|
||||
server_default="''",
|
||||
comment="规格型号(冗余固化;空串对齐 products 组合唯一键口径)",
|
||||
)
|
||||
unit: Mapped[str | None] = mapped_column(String(16), comment="计量单位")
|
||||
quantity: Mapped[Decimal] = mapped_column(
|
||||
Numeric(14, 2), nullable=False, comment="发货数量(>0)"
|
||||
)
|
||||
sale_price: Mapped[Decimal | None] = mapped_column(
|
||||
Numeric(14, 4), comment="发货单价(元;T+ 同步或人工,口径待 IT 确认)"
|
||||
)
|
||||
amount: Mapped[Decimal] = mapped_column(
|
||||
Numeric(14, 2),
|
||||
nullable=False,
|
||||
comment="行金额 = 数量×单价(服务层计算固化,不信任前端传值)",
|
||||
)
|
||||
Reference in new issue
Block a user