108 lines
4.9 KiB
Python
108 lines
4.9 KiB
Python
|
|
"""产品出库记录 — MOM 出库回调在 Track 侧留下的单据存档
|
|||
|
|
|
|||
|
|
每收到一次 MOM 的出库回调就落一行,因此同一台设备可以有多行
|
|||
|
|
(出库 → 撤回 → 再出库)。撤回**不删行**,只把 is_revoked 置真 ——
|
|||
|
|
「出过又撤了」本身就是要看得见的历史。
|
|||
|
|
|
|||
|
|
与 products.overall_status / status 的分工:
|
|||
|
|
· 那两列表达**当前事实**(这台设备此刻是不是已出库);
|
|||
|
|
· 本表回答**归属**(这次出库对应 MOM 的哪张单、给了谁、谁办的)。
|
|||
|
|
两者互不替代:设备被撤回回库后 overall_status 变回「已入库」,但那张出库单
|
|||
|
|
仍然挂在本表上,只是标了已撤回。
|
|||
|
|
|
|||
|
|
为什么不用 products 加列:加列只能保住最后一次,而一台设备可以出库多次。
|
|||
|
|
|
|||
|
|
字段全部来自出库回调载荷(MOM 侧 services/outbound_service.create_outbound_batch)。
|
|||
|
|
Track 只做存档,**不做任何基于 outbound_type 的业务判断** —— MOM 的码表尚未
|
|||
|
|
冻结(models/outbound.py 两处注释分别为 5 值和 3 值,且无白名单校验)。
|
|||
|
|
"""
|
|||
|
|
import uuid
|
|||
|
|
from datetime import datetime
|
|||
|
|
|
|||
|
|
from sqlalchemy import Boolean, DateTime, ForeignKey, String, Text, UniqueConstraint
|
|||
|
|
from sqlalchemy.dialects.postgresql import UUID
|
|||
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|||
|
|
|
|||
|
|
from app.models.base import Base
|
|||
|
|
from app.core.time_utils import get_beijing_time
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ProductOutbound(Base):
|
|||
|
|
__tablename__ = "product_outbounds"
|
|||
|
|
# 防 MOM 重推产生重复行。不能只约束 outbound_no —— MOM 的批量出库是
|
|||
|
|
# 多个商品共用一个单号(见 MOM models/outbound.py 第 127 行注释)。
|
|||
|
|
__table_args__ = (
|
|||
|
|
UniqueConstraint("serial_number", "outbound_no", name="uq_product_outbound_sn_no"),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|||
|
|
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ---- 物理外键(关联本库 products) ----
|
|||
|
|
product_id: Mapped[uuid.UUID] = mapped_column(
|
|||
|
|
UUID(as_uuid=True), ForeignKey("products.id"), nullable=False, index=True,
|
|||
|
|
comment="所属产品ID",
|
|||
|
|
)
|
|||
|
|
# 冗余序列号:按 SN 对账/排查时不必 join products
|
|||
|
|
serial_number: Mapped[str | None] = mapped_column(
|
|||
|
|
String(16), nullable=True, index=True, comment="产品序列号(冗余,便于按SN对账)",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ---- MOM 单据上下文(全部来自出库回调载荷) ----
|
|||
|
|
outbound_no: Mapped[str] = mapped_column(
|
|||
|
|
String(100), nullable=False, comment="MOM 出库单号(批量出库多商品共用)",
|
|||
|
|
)
|
|||
|
|
request_no: Mapped[str | None] = mapped_column(
|
|||
|
|
String(100), nullable=True, comment="MOM 出库申请单号",
|
|||
|
|
)
|
|||
|
|
consumer_name: Mapped[str | None] = mapped_column(
|
|||
|
|
String(100), nullable=True,
|
|||
|
|
comment="领用人/客户(MOM 侧扫码时自由填写,非可靠标识,刻意不做关联依据)",
|
|||
|
|
)
|
|||
|
|
applicant_name: Mapped[str | None] = mapped_column(
|
|||
|
|
String(100), nullable=True, comment="申请人姓名(MOM 侧解析后传来,Track 不做 ID 反查)",
|
|||
|
|
)
|
|||
|
|
operator: Mapped[str | None] = mapped_column(
|
|||
|
|
String(64), nullable=True, comment="MOM 侧实际扫码出库人",
|
|||
|
|
)
|
|||
|
|
outbound_type: Mapped[str | None] = mapped_column(
|
|||
|
|
String(50), nullable=True,
|
|||
|
|
comment="出库类型 SALES/USE/PRODUCTION(MOM 码表未冻结,本表只存不判)",
|
|||
|
|
)
|
|||
|
|
outbound_time: Mapped[datetime | None] = mapped_column(
|
|||
|
|
DateTime(timezone=True), nullable=True, comment="MOM 记录的出库时间",
|
|||
|
|
)
|
|||
|
|
remark: Mapped[str | None] = mapped_column(
|
|||
|
|
Text, nullable=True, comment="出库单备注",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ---- 撤回(只置位不删行) ----
|
|||
|
|
is_revoked: Mapped[bool] = mapped_column(
|
|||
|
|
Boolean, nullable=False, default=False, server_default="false",
|
|||
|
|
comment="该次出库是否已被 MOM 撤回",
|
|||
|
|
)
|
|||
|
|
revoked_at: Mapped[datetime | None] = mapped_column(
|
|||
|
|
DateTime(timezone=True), nullable=True, comment="撤回时间",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ---- 来源 ----
|
|||
|
|
# 两条写入路径共用本表(同一语义:产品 ↔ 出库单),拆表会让产品详情要展示
|
|||
|
|
# 两张卡片,所以只标来源。
|
|||
|
|
# webhook —— MOM 出库回调自动存档(设备自己被发走时按 SN 匹配落一行)
|
|||
|
|
# manual —— 人工在界面挂的(创建产品时勾选 / 产品详情追加)
|
|||
|
|
source: Mapped[str] = mapped_column(
|
|||
|
|
String(16), nullable=False, default="webhook", server_default="webhook",
|
|||
|
|
comment="来源: webhook(MOM回调自动存档) | manual(人工挂载)",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
created_at: Mapped[datetime] = mapped_column(
|
|||
|
|
DateTime(timezone=True), default=get_beijing_time, comment="本行写入时间",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ---- 关系 ----
|
|||
|
|
product: Mapped["Product"] = relationship("Product", lazy="selectin")
|
|||
|
|
|
|||
|
|
def __repr__(self) -> str:
|
|||
|
|
return f"<ProductOutbound {self.outbound_no} sn={self.serial_number}>"
|