"""物料入库/出库系统 - 数据模型 (SQLAlchemy 2.0) 实体: User 账号(admin / manager / operator) Material 物料主数据 Inbound 入库流水(含"期初"来源的首笔) Outbound 出库流水(含生产领用) Bom BOM 清单(产品型号) BomItem BOM 组成(某物料单套用量) ProdOrder 生产订单(接单量) -> 按 BOM 展开领料需求 OperationLog 操作日志(谁/何时/做了什么) 库存口径: 在库 = SUM(入库) - SUM(出库), 由 app 实时聚合, 不冗余存储。 """ import os from datetime import datetime, date from flask_sqlalchemy import SQLAlchemy from sqlalchemy import UniqueConstraint db = SQLAlchemy() class User(db.Model): __tablename__ = "user" id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, nullable=False, index=True) password = db.Column(db.String(128), nullable=False) # sha256 hex role = db.Column(db.String(32), default="operator") # admin / manager / operator display_name = db.Column(db.String(64), default="") active = db.Column(db.Boolean, default=True) class Material(db.Model): __tablename__ = "material" id = db.Column(db.Integer, primary_key=True) part_number = db.Column(db.String(64), unique=True, nullable=False, index=True) manufacturer = db.Column(db.String(255), default="") # 型号 / 规格 unit = db.Column(db.String(16), default="个") safety_stock = db.Column(db.Float, default=0) # 安全库存 note = db.Column(db.String(255), default="") created_at = db.Column(db.DateTime, default=datetime.now) class Inbound(db.Model): __tablename__ = "inbound" id = db.Column(db.Integer, primary_key=True) material_id = db.Column(db.Integer, db.ForeignKey("material.id"), nullable=False, index=True) factory_id = db.Column(db.Integer, db.ForeignKey("factory.id"), nullable=True, index=True) # 所属工厂/仓库 batch_no = db.Column(db.String(64), default="") # 批次号 qty = db.Column(db.Float, nullable=False) # 入库数量(正) operator = db.Column(db.String(64), default="") # 经手人 source = db.Column(db.String(255), default="") # 来源 / 供应商 order_ref = db.Column(db.String(64), default="") # 关联生产单号 / 调拨单号 occur_date = db.Column(db.Date, default=date.today) delivery_date = db.Column(db.Date, default=None) # 送货日期(供应商到货日, 可空) remark = db.Column(db.String(255), default="") created_at = db.Column(db.DateTime, default=datetime.now) class Outbound(db.Model): __tablename__ = "outbound" id = db.Column(db.Integer, primary_key=True) material_id = db.Column(db.Integer, db.ForeignKey("material.id"), nullable=False, index=True) factory_id = db.Column(db.Integer, db.ForeignKey("factory.id"), nullable=True, index=True) # 所属工厂/仓库 batch_no = db.Column(db.String(64), default="") # 批次号 qty = db.Column(db.Float, nullable=False) # 出库数量(正) operator = db.Column(db.String(64), default="") # 经手人 purpose = db.Column(db.String(255), default="") # 用途 order_ref = db.Column(db.String(64), default="") # 关联生产单号 occur_date = db.Column(db.Date, default=date.today) remark = db.Column(db.String(255), default="") created_at = db.Column(db.DateTime, default=datetime.now) # ---------------- M2: BOM 与生产订单 ---------------- class Bom(db.Model): __tablename__ = "bom" id = db.Column(db.Integer, primary_key=True) code = db.Column(db.String(64), unique=True, nullable=False, index=True) # BOM 型号简码 name = db.Column(db.String(255), default="") note = db.Column(db.String(255), default="") created_at = db.Column(db.DateTime, default=datetime.now) class BomItem(db.Model): __tablename__ = "bom_item" id = db.Column(db.Integer, primary_key=True) bom_id = db.Column(db.Integer, db.ForeignKey("bom.id"), nullable=False, index=True) material_id = db.Column(db.Integer, db.ForeignKey("material.id"), nullable=False, index=True) qty_per_unit = db.Column(db.Float, nullable=False, default=0) # 单套用量 class ProdOrder(db.Model): __tablename__ = "prod_order" id = db.Column(db.Integer, primary_key=True) order_no = db.Column(db.String(64), unique=True, nullable=False, index=True) bom_id = db.Column(db.Integer, db.ForeignKey("bom.id"), nullable=False, index=True) factory_id = db.Column(db.Integer, db.ForeignKey("factory.id"), nullable=True, index=True) # 生产工厂/车间 qty = db.Column(db.Float, nullable=False, default=0) # 接单量(台/套) status = db.Column(db.String(32), default="planned") # planned / picked / closed operator = db.Column(db.String(64), default="") remark = db.Column(db.String(255), default="") created_at = db.Column(db.DateTime, default=datetime.now) class ProdOrderAlloc(db.Model): """生产订单「调入数量」手动录入: 每个物料的调入计划与已入账标记。 - alloc_qty: 该物料计划调入(转入)生产工厂的数量, 由用户在订单详情页手动填写。 - posted: 是否已据此生成入库流水(调入工厂物料)。生成后该行只读, 避免重复入账。 """ __tablename__ = "prod_order_alloc" id = db.Column(db.Integer, primary_key=True) order_id = db.Column(db.Integer, db.ForeignKey("prod_order.id"), nullable=False, index=True) material_id = db.Column(db.Integer, db.ForeignKey("material.id"), nullable=False, index=True) alloc_qty = db.Column(db.Float, default=0) # 调入数量(手动录入) posted = db.Column(db.Boolean, default=False) # 是否已生成入库流水 created_at = db.Column(db.DateTime, default=datetime.now) class Substitute(db.Model): """替代料关系: 某主料(main)可由某替代料(sub)替代使用。 - 仅描述"谁可替代谁"的参考关系, 不直接参与库存计算。 - 用于出库 / BOM 领料时, 若主料库存不足, 自动提示可用的替代料。 - 唯一约束 (main_id, sub_id), 避免重复登记; main 与 sub 不可相同。 """ __tablename__ = "substitute" id = db.Column(db.Integer, primary_key=True) main_id = db.Column(db.Integer, db.ForeignKey("material.id"), nullable=False, index=True) sub_id = db.Column(db.Integer, db.ForeignKey("material.id"), nullable=False, index=True) note = db.Column(db.String(255), default="") created_at = db.Column(db.DateTime, default=datetime.now) __table_args__ = (UniqueConstraint("main_id", "sub_id", name="uq_sub_main_sub"),) # ---------------- M3: 操作日志 ---------------- class OperationLog(db.Model): __tablename__ = "operation_log" id = db.Column(db.Integer, primary_key=True) operator = db.Column(db.String(64), default="") action = db.Column(db.String(64), default="") # 动作分类 target = db.Column(db.String(255), default="") # 对象 detail = db.Column(db.String(512), default="") # 详情 created_at = db.Column(db.DateTime, default=datetime.now) # ---------------- M2-1: 多工厂 / 代工厂 ---------------- class Factory(db.Model): __tablename__ = "factory" id = db.Column(db.Integer, primary_key=True) code = db.Column(db.String(32), unique=True, nullable=False, index=True) # 工厂编码, 如 SELF / SUB01 name = db.Column(db.String(128), default="") # 工厂名称 type = db.Column(db.String(16), default="self") # self 自厂 / subcontract 代工厂 enabled = db.Column(db.Boolean, default=True) # 是否启用 note = db.Column(db.String(255), default="") created_at = db.Column(db.DateTime, default=datetime.now) # ---------------- M2-2: 厂间调拨 ---------------- class Transfer(db.Model): """厂间调拨: 从源工厂调出、调入目标工厂(同一颗物料)。 一条调拨记录对应两条流水: - Outbound(factory_id=from_factory_id) : 源厂库存扣减 - Inbound (factory_id=to_factory_id) : 目标厂库存增加 两张流水均带 source/purpose = 厂间调拨, 便于追溯和对账。 """ __tablename__ = "transfer" id = db.Column(db.Integer, primary_key=True) no = db.Column(db.String(64), unique=True, nullable=False, index=True) # 调拨单号, 如 TR20260928xxx from_factory_id = db.Column(db.Integer, db.ForeignKey("factory.id"), nullable=False, index=True) to_factory_id = db.Column(db.Integer, db.ForeignKey("factory.id"), nullable=False, index=True) material_id = db.Column(db.Integer, db.ForeignKey("material.id"), nullable=False, index=True) qty = db.Column(db.Float, nullable=False) # 调拨数量(正) operator = db.Column(db.String(64), default="") occur_date = db.Column(db.Date, default=date.today) remark = db.Column(db.String(255), default="") created_at = db.Column(db.DateTime, default=datetime.now)