from app.extensions import db, beijing_time from app.models.system import SysUser from datetime import datetime import json class OutboundApproval(db.Model): """ 出库审批单模型 用于管理出库申请的多级审批流程 """ __tablename__ = 'outbound_approval' id = db.Column(db.Integer, primary_key=True) # 审批单号 request_no = db.Column(db.String(100), unique=True, nullable=False, index=True) # 申请人ID applicant_id = db.Column(db.Integer, nullable=False, index=True) # 申请说明 remark = db.Column(db.Text) # 状态: 0-待审批, 1-已通过, 2-已驳回, 3-已完成(已出库) status = db.Column(db.Integer, default=0, nullable=False) # 允许审批的人员列表 (JSON格式: [{"type": "role", "value": "admin"}, {"type": "user", "value": "123"}]) allowed_approvers = db.Column(db.Text) # 实际审批人ID (多人审批时记录第一个通过的) actual_approver_id = db.Column(db.Integer, index=True) # 审批时间 approved_at = db.Column(db.DateTime) # 驳回原因 reject_reason = db.Column(db.Text) # 明细快照 (存储出库物品的名称、规格、库位、数量等信息,无SKU字段) items_json = db.Column(db.Text) # 创建时间和更新时间 created_at = db.Column(db.DateTime, default=beijing_time, nullable=False) updated_at = db.Column(db.DateTime, default=beijing_time, onupdate=beijing_time, nullable=False) def _safe_parse_json(self, value): """ 安全解析 JSON 字段: - 如果 value 已是 list/dict,直接返回 - 如果是 str,尝试 json.loads() - 解析失败或为 None/空,均返回 [] """ if value is None: return [] if isinstance(value, (list, dict)): return value if isinstance(value, str): val = value.strip() if not val: return [] try: parsed = json.loads(val) return parsed if isinstance(parsed, list) else [] except (json.JSONDecodeError, TypeError, ValueError): return [] return [] def get_items(self): """解析 items_json,返回物品列表""" return self._safe_parse_json(self.items_json) def set_items(self, items): """设置 items_json""" self.items_json = json.dumps(items, ensure_ascii=False) if items else '[]' def get_allowed_approvers(self): """解析 allowed_approvers,返回审批人列表""" return self._safe_parse_json(self.allowed_approvers) def set_allowed_approvers(self, approvers): """设置 allowed_approvers""" self.allowed_approvers = json.dumps(approvers, ensure_ascii=False) if approvers else '[]' def to_dict(self): return { 'id': self.id, 'request_no': self.request_no, 'applicant_id': self.applicant_id, 'applicant_name': self._get_user_name(self.applicant_id), 'remark': self.remark, 'status': self.status, 'status_text': ['待审批', '已通过', '已驳回', '已完成'][self.status] if self.status in [0, 1, 2, 3] else '未知', 'allowed_approvers': self.get_allowed_approvers(), 'actual_approver_id': self.actual_approver_id, 'approver_name': self._get_user_name(self.actual_approver_id) if self.actual_approver_id else None, 'approved_at': self.approved_at.strftime('%Y-%m-%d %H:%M:%S') if self.approved_at else None, 'reject_reason': self.reject_reason, 'items': self.get_items(), 'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None, 'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S') if self.updated_at else None, } def _get_user_name(self, user_id): """根据用户ID获取用户名""" if not user_id: return "" from app.models.system import SysUser try: # ★ 必须用 .get() 按主键 ID 查询,千万不能用 username=user_id 去查 user = SysUser.query.get(user_id) return user.username if user else f"未知用户({user_id})" except Exception as e: return f"用户({user_id})" class TransOutbound(db.Model): __tablename__ = 'trans_outbound' id = db.Column(db.Integer, primary_key=True) # 修改:不再唯一,因为批量出库时多个商品共用一个单号 outbound_no = db.Column(db.String(100), nullable=False) # 关联源库存信息 sku = db.Column(db.String(100)) source_table = db.Column(db.String(50)) # 'stock_buy', 'stock_product', 'stock_semi' stock_id = db.Column(db.Integer) # 对应源表的主键ID barcode = db.Column(db.String(100)) # 实际扫码内容 # 业务信息 outbound_type = db.Column(db.String(50), default='SALES') # SALES(销售), USE(领用), PRODUCTION(生产) quantity = db.Column(db.Numeric(19, 4), nullable=False) # [新增] 出库时的单价,用于计算金额 unit_price = db.Column(db.Numeric(19, 2), default=0) # 签字与追溯 consumer_name = db.Column(db.String(100)) # 领用人/客户 signature_path = db.Column(db.Text) # 电子签名图片路径 outbound_time = db.Column(db.DateTime, default=beijing_time) operator_name = db.Column(db.String(100)) # 操作员 remark = db.Column(db.Text) def to_dict(self): return { 'id': self.id, 'outbound_no': self.outbound_no, 'sku': self.sku, 'source_table': self.source_table, 'outbound_type': self.outbound_type, 'quantity': float(self.quantity) if self.quantity else 0, 'unit_price': float(self.unit_price) if self.unit_price else 0, 'consumer_name': self.consumer_name, 'signature_path': self.signature_path, 'outbound_time': self.outbound_time.strftime('%Y-%m-%d %H:%M:%S') if self.outbound_time else None, 'operator_name': self.operator_name, 'remark': self.remark }