"""发货模型:发货单与发货产品明细。 - 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 | None] = mapped_column( Numeric(14, 2), nullable=True, 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="行金额 = 数量×单价(服务层计算固化,不信任前端传值)", )