from datetime import datetime from sqlalchemy import ( JSON, BigInteger, CheckConstraint, Column, Date, DateTime, ForeignKey, Integer, String, UniqueConstraint, ) from config.database import Base from config.env import DataBaseConfig from utils.common_util import SqlalchemyUtil class BizInboundOrder(Base): """首次入库单。""" __tablename__ = 'biz_inbound_order' __table_args__ = ( UniqueConstraint( 'warehouse_id', 'confirm_idempotency_key', name='uq_biz_inbound_order_warehouse_confirm_idempotency', ), CheckConstraint("status IN ('DRAFT', 'CONFIRMED')", name='ck_biz_inbound_order_status'), {'comment': '首次入库单'}, ) inbound_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='入库单ID') test_batch_id = Column( BigInteger, ForeignKey('biz_test_batch.batch_id'), nullable=True, index=True, comment='测试数据批次ID;NULL表示正式数据', ) inbound_no = Column(String(64), nullable=False, unique=True, comment='入库单号') warehouse_id = Column( BigInteger, ForeignKey('biz_warehouse.warehouse_id'), nullable=False, comment='仓库ID', ) status = Column(String(32), nullable=False, server_default='DRAFT', comment='单据状态') confirm_idempotency_key = Column(String(64), nullable=True, comment='确认幂等键') confirm_request_hash = Column(String(64), nullable=True, comment='确认请求摘要') confirmed_by = Column(BigInteger, ForeignKey('sys_user.user_id'), nullable=True, comment='确认人ID') confirmed_at = Column(DateTime, nullable=True, comment='确认时间') create_by = Column(String(64), nullable=True, server_default='', comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间') update_by = Column(String(64), nullable=True, server_default='', comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now, comment='更新时间') remark = Column( String(500), nullable=True, server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), comment='备注', ) class BizInboundItem(Base): """首次入库单托盘明细。""" __tablename__ = 'biz_inbound_item' __table_args__ = ( UniqueConstraint('inbound_id', 'pallet_id', name='uq_biz_inbound_item_order_pallet'), {'comment': '首次入库单托盘明细'}, ) inbound_item_id = Column( BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='入库明细ID', ) test_batch_id = Column( BigInteger, ForeignKey('biz_test_batch.batch_id'), nullable=True, index=True, comment='测试数据批次ID;NULL表示正式数据', ) inbound_id = Column( BigInteger, ForeignKey('biz_inbound_order.inbound_id'), nullable=False, comment='入库单ID', ) pallet_id = Column(BigInteger, ForeignKey('biz_pallet.pallet_id'), nullable=False, comment='托盘ID') create_by = Column(String(64), nullable=True, server_default='', comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间') class BizFillingRecord(Base): """托盘装填记录。""" __tablename__ = 'biz_filling_record' __table_args__ = ( UniqueConstraint( 'warehouse_id', 'idempotency_key', name='uq_biz_filling_record_warehouse_idempotency', ), UniqueConstraint('pallet_id', 'active_slot', name='uq_biz_filling_record_pallet_active_slot'), CheckConstraint('initial_packages > 0', name='ck_biz_filling_record_initial_packages'), CheckConstraint( '(production_date IS NULL AND expiry_date IS NULL) OR ' '(production_date IS NOT NULL AND expiry_date IS NOT NULL AND expiry_date >= production_date)', name='ck_biz_filling_record_material_dates', ), CheckConstraint( "active_slot IS NULL OR active_slot = 'ACTIVE'", name='ck_biz_filling_record_active_slot', ), {'comment': '托盘装填记录'}, ) filling_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='装填记录ID') test_batch_id = Column( BigInteger, ForeignKey('biz_test_batch.batch_id'), nullable=True, index=True, comment='测试数据批次ID;NULL表示正式数据', ) filling_no = Column(String(64), nullable=False, unique=True, comment='装填单号') pallet_id = Column(BigInteger, ForeignKey('biz_pallet.pallet_id'), nullable=False, comment='托盘ID') warehouse_id = Column( BigInteger, ForeignKey('biz_warehouse.warehouse_id'), nullable=False, comment='仓库ID', ) strain_id = Column(BigInteger, ForeignKey('biz_strain.strain_id'), nullable=False, comment='菌种ID') strain_code_snapshot = Column(String(64), nullable=False, comment='菌种编码快照') strain_name_snapshot = Column(String(100), nullable=False, comment='菌种名称快照') batch_no = Column(String(64), nullable=False, comment='生产批次') production_date = Column(Date, nullable=True, comment='来源生产日期;历史未登记为空') expiry_date = Column(Date, nullable=True, comment='有效期至;历史未登记为空') package_spec = Column(String(64), nullable=False, comment='包装规格') initial_packages = Column(Integer, nullable=False, comment='初始包数') active_slot = Column(String(16), nullable=True, comment='活动装填槽') closed_at = Column(DateTime, nullable=True, comment='装填周期关闭时间') idempotency_key = Column(String(64), nullable=False, comment='幂等键') request_hash = Column(String(64), nullable=False, comment='请求摘要') operator_id = Column(BigInteger, ForeignKey('sys_user.user_id'), nullable=False, comment='操作人ID') operated_at = Column(DateTime, nullable=False, comment='操作时间') create_by = Column(String(64), nullable=True, server_default='', comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间') update_by = Column(String(64), nullable=True, server_default='', comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now, comment='更新时间') remark = Column( String(500), nullable=True, server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), comment='备注', ) class BizShipmentOrder(Base): """整托发货单。""" __tablename__ = 'biz_shipment_order' __table_args__ = ( UniqueConstraint( 'source_warehouse_id', 'target_site_id', 'confirm_idempotency_key', name='uq_biz_shipment_order_route_confirm_idempotency', ), CheckConstraint("status IN ('DRAFT', 'CONFIRMED')", name='ck_biz_shipment_order_status'), CheckConstraint("receipt_state IN ('PENDING', 'RECEIVED', 'LEGACY')", name='ck_biz_shipment_order_receipt_state'), UniqueConstraint('target_site_id', 'receipt_idempotency_key', name='uq_biz_shipment_receipt_site_key'), {'comment': '整托发货单'}, ) shipment_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='发货单ID') test_batch_id = Column( BigInteger, ForeignKey('biz_test_batch.batch_id'), nullable=True, index=True, comment='测试数据批次ID;NULL表示正式数据', ) shipment_no = Column(String(64), nullable=False, unique=True, comment='发货单号') source_warehouse_id = Column( BigInteger, ForeignKey('biz_warehouse.warehouse_id'), nullable=False, comment='来源仓库ID', ) target_site_id = Column( BigInteger, ForeignKey('biz_customer_site.site_id'), nullable=False, comment='目标现场ID', ) status = Column(String(32), nullable=False, server_default='DRAFT', comment='单据状态') confirm_idempotency_key = Column(String(64), nullable=True, comment='确认幂等键') confirm_request_hash = Column(String(64), nullable=True, comment='确认请求摘要') confirmed_by = Column(BigInteger, ForeignKey('sys_user.user_id'), nullable=True, comment='确认人ID') confirmed_at = Column(DateTime, nullable=True, comment='确认时间') receipt_state = Column(String(16), nullable=False, server_default='PENDING', comment='收货状态;LEGACY表示历史未登记') received_by = Column(BigInteger, ForeignKey('sys_user.user_id'), nullable=True, comment='收货核对人ID') received_at = Column(DateTime, nullable=True, comment='实际收货核对时间') receipt_idempotency_key = Column(String(64), nullable=True, comment='收货确认幂等键') receipt_request_hash = Column(String(64), nullable=True, comment='收货核对请求摘要') create_by = Column(String(64), nullable=True, server_default='', comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间') update_by = Column(String(64), nullable=True, server_default='', comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now, comment='更新时间') remark = Column( String(500), nullable=True, server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), comment='备注', ) class BizShipmentItem(Base): """整托发货明细。""" __tablename__ = 'biz_shipment_item' __table_args__ = ( UniqueConstraint('shipment_id', 'pallet_id', name='uq_biz_shipment_item_order_pallet'), CheckConstraint( 'package_qty IS NULL OR package_qty > 0', name='ck_biz_shipment_item_package_qty', ), {'comment': '整托发货明细'}, ) shipment_item_id = Column( BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='发货明细ID', ) test_batch_id = Column( BigInteger, ForeignKey('biz_test_batch.batch_id'), nullable=True, index=True, comment='测试数据批次ID;NULL表示正式数据', ) shipment_id = Column( BigInteger, ForeignKey('biz_shipment_order.shipment_id'), nullable=False, comment='发货单ID', ) pallet_id = Column(BigInteger, ForeignKey('biz_pallet.pallet_id'), nullable=False, comment='托盘ID') active_filling_id = Column( BigInteger, ForeignKey('biz_filling_record.filling_id'), nullable=True, comment='确认时活动装填记录ID', ) package_qty = Column(Integer, nullable=True, comment='确认时包数快照') create_by = Column(String(64), nullable=True, server_default='', comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间') class BizUsageRecord(Base): """客户现场使用事实记录。""" __tablename__ = 'biz_usage_record' __table_args__ = ( UniqueConstraint('site_id', 'idempotency_key', name='uq_biz_usage_record_site_idempotency'), CheckConstraint('used_packages > 0', name='ck_biz_usage_record_used_packages'), CheckConstraint( 'remaining_packages_after >= 0', name='ck_biz_usage_record_remaining_packages_after', ), {'comment': '客户现场使用事实记录'}, ) usage_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='使用记录ID') test_batch_id = Column( BigInteger, ForeignKey('biz_test_batch.batch_id'), nullable=True, index=True, comment='测试数据批次ID;NULL表示正式数据', ) usage_no = Column(String(64), nullable=False, unique=True, comment='使用记录号') pallet_id = Column(BigInteger, ForeignKey('biz_pallet.pallet_id'), nullable=False, comment='托盘ID') active_filling_id = Column( BigInteger, ForeignKey('biz_filling_record.filling_id'), nullable=False, comment='活动装填记录ID', ) site_id = Column(BigInteger, ForeignKey('biz_customer_site.site_id'), nullable=False, comment='现场ID') tunnel_id = Column( BigInteger, ForeignKey('biz_tunnel.tunnel_id'), nullable=True, comment='隧道ID;历史记录可能为空', ) trace_no = Column(String(64), nullable=True, comment='隧道物料内部溯源号;历史记录可能为空') batch_no_snapshot = Column(String(64), nullable=True, comment='生产批次快照;历史记录可能为空') line_id = Column(BigInteger, ForeignKey('biz_production_line.line_id'), nullable=True, comment='产线ID(可选)') used_packages = Column(Integer, nullable=False, comment='本次使用包数') remaining_packages_after = Column(Integer, nullable=False, comment='使用后剩余包数') idempotency_key = Column(String(64), nullable=False, comment='幂等键') request_hash = Column(String(64), nullable=False, comment='请求摘要') operator_id = Column(BigInteger, ForeignKey('sys_user.user_id'), nullable=False, comment='操作人ID') occurred_at = Column(DateTime, nullable=False, comment='发生时间') fifo_skip_reason = Column(String(500), nullable=True, comment='跳过先到托盘原因') fifo_recommendation = Column(JSON, nullable=True, comment='跳过时先到托盘身份及到货时间快照') class BizReturnOrder(Base): """托盘退回单。""" __tablename__ = 'biz_return_order' __table_args__ = ( UniqueConstraint( 'source_site_id', 'target_warehouse_id', 'request_idempotency_key', name='uq_biz_return_order_route_request_idempotency', ), UniqueConstraint( 'source_site_id', 'target_warehouse_id', 'confirm_idempotency_key', name='uq_biz_return_order_route_confirm_idempotency', ), CheckConstraint("status IN ('REQUESTED', 'CONFIRMED')", name='ck_biz_return_order_status'), {'comment': '托盘退回单'}, ) return_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='退回单ID') test_batch_id = Column( BigInteger, ForeignKey('biz_test_batch.batch_id'), nullable=True, index=True, comment='测试数据批次ID;NULL表示正式数据', ) return_no = Column(String(64), nullable=False, unique=True, comment='退回单号') source_site_id = Column( BigInteger, ForeignKey('biz_customer_site.site_id'), nullable=False, comment='来源现场ID', ) target_warehouse_id = Column( BigInteger, ForeignKey('biz_warehouse.warehouse_id'), nullable=False, comment='目标仓库ID', ) status = Column(String(32), nullable=False, server_default='REQUESTED', comment='退回状态') request_idempotency_key = Column(String(64), nullable=False, comment='申请幂等键') request_hash = Column(String(64), nullable=False, comment='申请请求摘要') confirm_idempotency_key = Column(String(64), nullable=True, comment='确认幂等键') confirm_request_hash = Column(String(64), nullable=True, comment='确认请求摘要') requested_by = Column(BigInteger, ForeignKey('sys_user.user_id'), nullable=False, comment='申请人ID') requested_at = Column(DateTime, nullable=False, comment='申请时间') confirmed_by = Column(BigInteger, ForeignKey('sys_user.user_id'), nullable=True, comment='确认人ID') confirmed_at = Column(DateTime, nullable=True, comment='确认时间') create_by = Column(String(64), nullable=True, server_default='', comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间') update_by = Column(String(64), nullable=True, server_default='', comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now, comment='更新时间') remark = Column( String(500), nullable=True, server_default=SqlalchemyUtil.get_server_default_null(DataBaseConfig.db_type), comment='备注', ) class BizReturnItem(Base): """托盘退回明细。""" __tablename__ = 'biz_return_item' __table_args__ = ( UniqueConstraint('return_id', 'pallet_id', name='uq_biz_return_item_order_pallet'), CheckConstraint( 'remaining_packages_snapshot >= 0', name='ck_biz_return_item_remaining_packages_snapshot', ), {'comment': '托盘退回明细'}, ) return_item_id = Column( BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='退回明细ID', ) test_batch_id = Column( BigInteger, ForeignKey('biz_test_batch.batch_id'), nullable=True, index=True, comment='测试数据批次ID;NULL表示正式数据', ) return_id = Column( BigInteger, ForeignKey('biz_return_order.return_id'), nullable=False, comment='退回单ID', ) pallet_id = Column(BigInteger, ForeignKey('biz_pallet.pallet_id'), nullable=False, comment='托盘ID') active_filling_id_snapshot = Column( BigInteger, ForeignKey('biz_filling_record.filling_id'), nullable=True, comment='申请时活动装填记录快照', ) tunnel_id_snapshot = Column( BigInteger, ForeignKey('biz_tunnel.tunnel_id'), nullable=True, comment='申请时隧道ID快照', ) trace_no_snapshot = Column(String(64), nullable=True, comment='申请时物料溯源号快照') batch_no_snapshot = Column(String(64), nullable=True, comment='申请时生产批次快照') remaining_packages_snapshot = Column(Integer, nullable=False, comment='申请时剩余包数快照') create_by = Column(String(64), nullable=True, server_default='', comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间') class BizPalletInventory(Base): """托盘当前库存、位置和流转状态。""" __tablename__ = 'biz_pallet_inventory' __table_args__ = ( CheckConstraint( "flow_state IN ('UNSTOCKED', 'EMPTY_IN_WAREHOUSE', 'FILLED_IN_WAREHOUSE', " "'IN_TRANSIT', 'AT_SITE_UNUSED', 'AT_SITE_PARTIAL', 'AT_SITE_EMPTY', 'RETURNING')", name='ck_biz_pallet_inventory_flow_state', ), CheckConstraint('remaining_packages >= 0', name='ck_biz_pallet_inventory_remaining_packages'), CheckConstraint('version >= 0', name='ck_biz_pallet_inventory_version'), CheckConstraint( 'NOT (warehouse_id IS NOT NULL AND site_id IS NOT NULL)', name='ck_biz_pallet_inventory_location', ), {'comment': '托盘当前库存、位置和流转状态'}, ) pallet_id = Column( BigInteger, ForeignKey('biz_pallet.pallet_id'), primary_key=True, nullable=False, autoincrement=False, comment='托盘ID', ) test_batch_id = Column( BigInteger, ForeignKey('biz_test_batch.batch_id'), nullable=True, index=True, comment='测试数据批次ID;NULL表示正式数据', ) active_filling_id = Column( BigInteger, ForeignKey('biz_filling_record.filling_id'), nullable=True, comment='当前活动装填记录ID', ) warehouse_id = Column( BigInteger, ForeignKey('biz_warehouse.warehouse_id'), nullable=True, comment='当前仓库ID', ) site_id = Column( BigInteger, ForeignKey('biz_customer_site.site_id'), nullable=True, comment='当前现场ID', ) tunnel_id = Column( BigInteger, ForeignKey('biz_tunnel.tunnel_id'), nullable=True, comment='当前隧道ID', ) trace_no = Column(String(64), nullable=True, comment='当前隧道物料溯源号') current_line_id = Column( BigInteger, ForeignKey('biz_production_line.line_id'), nullable=True, comment='当前驻场周期绑定产线ID', ) flow_state = Column(String(32), nullable=False, server_default='UNSTOCKED', comment='托盘流转状态') remaining_packages = Column(Integer, nullable=False, server_default='0', comment='当前剩余包数') site_arrived_at = Column(DateTime, nullable=True, comment='本轮实际收货核对时间;历史未登记为空') last_event_id = Column( BigInteger, ForeignKey('biz_pallet_event.event_id'), nullable=True, comment='最后事件ID', ) version = Column(Integer, nullable=False, server_default='0', comment='并发版本号') updated_at = Column(DateTime, nullable=False, default=datetime.now, comment='更新时间') class BizPalletEvent(Base): """托盘只追加流转事件。""" __tablename__ = 'biz_pallet_event' __table_args__ = ( UniqueConstraint('pallet_id', 'event_seq', name='uq_biz_pallet_event_pallet_seq'), UniqueConstraint('pallet_id', 'idempotency_key', name='uq_biz_pallet_event_pallet_idempotency'), CheckConstraint( "event_type IN ('INBOUND_CONFIRMED', 'FILLING_CONFIRMED', 'SHIPMENT_CONFIRMED', " "'SHIPMENT_RECEIVED', 'TUNNEL_ENTERED', 'USAGE_RECORDED', 'RETURN_REQUESTED', 'RETURN_CONFIRMED')", name='ck_biz_pallet_event_event_type', ), {'comment': '托盘只追加流转事件'}, ) event_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='事件ID') test_batch_id = Column( BigInteger, ForeignKey('biz_test_batch.batch_id'), nullable=True, index=True, comment='测试数据批次ID;NULL表示正式数据', ) pallet_id = Column(BigInteger, ForeignKey('biz_pallet.pallet_id'), nullable=False, comment='托盘ID') event_seq = Column(Integer, nullable=False, comment='托盘内事件序号') event_type = Column(String(32), nullable=False, comment='事件类型') source_type = Column(String(32), nullable=False, comment='来源类型') source_id = Column(BigInteger, nullable=False, comment='来源记录ID') before_state = Column(String(32), nullable=False, comment='变更前状态') after_state = Column(String(32), nullable=False, comment='变更后状态') before_warehouse_id = Column( BigInteger, ForeignKey('biz_warehouse.warehouse_id'), nullable=True, comment='变更前仓库ID', ) before_site_id = Column( BigInteger, ForeignKey('biz_customer_site.site_id'), nullable=True, comment='变更前现场ID', ) before_tunnel_id = Column( BigInteger, ForeignKey('biz_tunnel.tunnel_id'), nullable=True, comment='变更前隧道ID', ) before_line_id = Column( BigInteger, ForeignKey('biz_production_line.line_id'), nullable=True, comment='变更前产线ID', ) after_warehouse_id = Column( BigInteger, ForeignKey('biz_warehouse.warehouse_id'), nullable=True, comment='变更后仓库ID', ) after_site_id = Column( BigInteger, ForeignKey('biz_customer_site.site_id'), nullable=True, comment='变更后现场ID', ) after_tunnel_id = Column( BigInteger, ForeignKey('biz_tunnel.tunnel_id'), nullable=True, comment='变更后隧道ID', ) after_line_id = Column( BigInteger, ForeignKey('biz_production_line.line_id'), nullable=True, comment='变更后产线ID', ) before_remaining_packages = Column(Integer, nullable=False, comment='变更前剩余包数') after_remaining_packages = Column(Integer, nullable=False, comment='变更后剩余包数') trace_no = Column(String(64), nullable=True, comment='事件关联物料溯源号') batch_no_snapshot = Column(String(64), nullable=True, comment='事件生产批次快照') fifo_skip_reason = Column(String(500), nullable=True, comment='跳过先到托盘原因') fifo_recommendation = Column(JSON, nullable=True, comment='跳过时先到托盘身份及到货时间快照') operator_id = Column(BigInteger, ForeignKey('sys_user.user_id'), nullable=False, comment='操作人ID') occurred_at = Column(DateTime, nullable=False, comment='发生时间') idempotency_key = Column(String(64), nullable=False, comment='幂等键') request_hash = Column(String(64), nullable=False, comment='请求摘要')