60 lines
2.5 KiB
Python
60 lines
2.5 KiB
Python
|
|
import json
|
|||
|
|
|
|||
|
|
from app.extensions import db, beijing_time
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ScanDraft(db.Model):
|
|||
|
|
"""
|
|||
|
|
扫码草稿(出库 / 借库作业中途暂停用)
|
|||
|
|
|
|||
|
|
场景:一张单几十项,工人扫到一半需要去处理更紧急的单据,事后回来继续。
|
|||
|
|
库存已在申请审批通过时预占,暂停期间不会被他人抢走,因此草稿只需记录
|
|||
|
|
「扫到哪了」,不涉及库存操作。
|
|||
|
|
|
|||
|
|
隔离粒度 (user_id, biz_type, request_id):一人一单;同一人可同时持有
|
|||
|
|
多张单据的草稿(正是「暂停 A 去出 B」的场景)。
|
|||
|
|
|
|||
|
|
为什么整单存 JSON 而非逐条明细行:
|
|||
|
|
恢复时需要物料名称/规格/库位等展示字段。逐行存方案只能回查申请单的
|
|||
|
|
items_json —— 而历史单据的 items_json 不含 stock_id,回查会错配。
|
|||
|
|
整单快照把展示字段一并存下,恢复时零依赖,对老单据同样可靠。
|
|||
|
|
"""
|
|||
|
|
__tablename__ = 'scan_draft'
|
|||
|
|
__table_args__ = (
|
|||
|
|
db.UniqueConstraint('user_id', 'biz_type', 'request_id', name='uq_scan_draft'),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
user_id = db.Column(db.Integer, nullable=False, index=True)
|
|||
|
|
biz_type = db.Column(db.String(20), nullable=False) # outbound / borrow
|
|||
|
|
request_id = db.Column(db.Integer, nullable=False)
|
|||
|
|
request_no = db.Column(db.String(100)) # 单号快照,展示免联表
|
|||
|
|
# ★ 必须用 db.JSON 而非 db.Text —— 数据库列是 jsonb
|
|||
|
|
# (见 db_migrations/add_scan_draft.sql)。
|
|||
|
|
# db.Text 会让 psycopg2 拿到 Python list/dict 时无法适配,
|
|||
|
|
# 报 "can't adapt type 'dict'";而用 db.Text 存 json.dumps 的字符串
|
|||
|
|
# 又会与 jsonb 列的类型语义打架。类型一致才是正解。
|
|||
|
|
items_json = db.Column(db.JSON, nullable=False, default=list)
|
|||
|
|
updated_at = db.Column(db.DateTime, default=beijing_time, onupdate=beijing_time, nullable=False)
|
|||
|
|
created_at = db.Column(db.DateTime, default=beijing_time, nullable=False)
|
|||
|
|
|
|||
|
|
def set_items(self, items):
|
|||
|
|
self.items_json = items or []
|
|||
|
|
|
|||
|
|
def get_items(self):
|
|||
|
|
v = self.items_json
|
|||
|
|
if not v:
|
|||
|
|
return []
|
|||
|
|
if isinstance(v, list):
|
|||
|
|
return v
|
|||
|
|
# 兼容历史数据(若该列曾被写成 JSON 字符串)
|
|||
|
|
try:
|
|||
|
|
parsed = json.loads(v)
|
|||
|
|
return parsed if isinstance(parsed, list) else []
|
|||
|
|
except (ValueError, TypeError):
|
|||
|
|
return []
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def item_count(self):
|
|||
|
|
return len(self.get_items())
|