feat(stocktake): 跨公司扫码给精准报错,0 库存盘盈在明细中可见

【跨公司拦截】
get_stock_info 带 company_name 过滤后,扫到别家公司的货会直接查不到,
返回 404「未找到物料」—— 与「条码根本不存在」完全无法区分,现场人员
既不知道是扫错了还是扫了别人的货。

新增 find_stock_owner_company():不施加公司过滤地全局查条码归属;
get_stock_info 未命中时由 _classify_missing_stock() 复核,区分两种情形:
  · 条码存在但属于别家公司 → 403「条码 [X] 属于【LICA】,请勿跨公司盘点」
  · 条码不存在             → 404「未找到该物料库存: X」

该方案的前提「SKU/条码全系统唯一」已核验成立:三张库存表的表内重复、
跨公司重复、跨表重复六项检查均为 0,故归属至多命中一条,无歧义。
/scan 与 /draft/add 两个入口均已接入。

【0 库存盘盈可见性】
merged-list 的 union_sql 原本严格要求 stock_quantity > 0,导致账面为 0、
但已被扫入的盘盈物料不出现在明细抽屉里,而 total_scanned 却把它计入
「已盘」—— 工人看到已盘 +1 却在明细里找不到,以为系统丢了数据。

现改为 stock_quantity > 0 OR id IN (本会话该表的草稿 stock_id)。
条件严格限定在**本会话**的草稿,不会把全库 0 库存物料都放出来。

实测:
  IRIS 扫 LICA 条码 → 403「条码 [0000002097] 属于【LICA】,请勿跨公司盘点」
  扫不存在条码      → 404「未找到该物料库存: NOPE-99999」
  已扫的 0 库存物料 → 明细可见(账面 0 / 实盘 5 / 差异 +5 / 库位 Y3/3/3/4)
  未扫的 0 库存物料 → 明细 0 行(范围正确)
This commit is contained in:
yueli
2026-09-11 14:05:59 +08:00
parent 325cc44469
commit fc95357662

View File

@ -261,6 +261,50 @@ def _get_session_scope(session_id):
return STOCKTAKE_SCOPE_ACTIVE, list(locs)
def find_stock_owner_company(uuid_or_barcode):
"""全局查找该条码/SKU 属于哪个公司 —— **不施加任何公司过滤**。
只在 get_stock_info 未命中时调用,用来区分两种「查不到」:
· 条码根本不存在 → 让工人核对是否扫错 / 敲错
· 条码存在但属于别家公司 → 明确告知跨公司,而不是笼统的「未找到物料」
前提:SKU / 条码在全系统全局唯一。已核验三张库存表「表内重复、跨公司重复、
跨表重复」六项检查均为 0,因此这里至多命中一条,不存在归属歧义。
:return: 公司名;查不到则 None
"""
code = str(uuid_or_barcode).strip()
if not code:
return None
checks = [
(StockProduct, lambda m, c: db.or_(m.barcode == c, m.sku == c, m.serial_number == c)),
(StockSemi, lambda m, c: db.or_(m.barcode == c, m.sku == c, m.serial_number == c)),
(StockBuy, lambda m, c: db.or_(m.barcode == c, m.sku == c)),
]
for model, cond_fn in checks:
if not model:
continue
row = db.session.query(MaterialBase.company_name).join(
model, model.base_id == MaterialBase.id
).filter(cond_fn(model, code)).first()
if row and row[0]:
return row[0]
return None
def _classify_missing_stock(code, company_name):
"""把「查不到库存」细分为「不存在」与「跨公司」。
:return: (msg, http_status) —— 调用方按自己的响应结构包装
"""
if company_name and company_name != '__NO_COMPANY__':
owner = find_stock_owner_company(code)
if owner and owner != company_name:
return f'条码 [{code}] 属于【{owner}】,请勿跨公司盘点', 403
return f'未找到该物料库存: {code}', 404
def get_stock_info(uuid_or_barcode, company_name=None):
"""
根据 uuid 或 barcode 查询库存信息(★ 精确匹配优先,性能与准确性兼顾)
@ -631,9 +675,12 @@ def scan_stock_by_barcode():
if not barcode:
return jsonify({'code': 400, 'msg': 'barcode 不能为空'}), 400
item, source_table, stock_id = get_stock_info(barcode, get_current_company_filter())
company_limit = get_current_company_filter()
item, source_table, stock_id = get_stock_info(barcode, company_limit)
if not item:
return jsonify({'code': 404, 'msg': f'未找到该物料库存: {barcode}'}), 404
# 区分「条码不存在」与「属于别家公司」,给现场人员可执行的提示
msg, status = _classify_missing_stock(barcode, company_limit)
return jsonify({'code': status, 'msg': msg}), status
d = item.to_dict()
d['stock_type'] = source_table.replace('stock_', '')
@ -796,7 +843,9 @@ def add_draft():
# 获取库存信息
item, source_table, stock_id = get_stock_info(uuid, company_name)
if not item:
return jsonify({"message": "未找到对应的库存记录"}), 404
# 区分「条码不存在」与「属于别家公司」
msg, status = _classify_missing_stock(uuid, company_name)
return jsonify({"message": msg}), status
# 账面总库存
stock_qty = float(item.stock_quantity) if item.stock_quantity else 0
@ -1974,18 +2023,31 @@ def get_draft_merged_list():
company_cond_bare = ' AND company_name = :company' if company_name is not None else ''
# ── 公共 CTE / 子查询片段 ──
# ★ 0 库存盘盈可见性:账面为 0、但本会话已被扫入的物料也要进明细列表。
# 否则工人扫到货架上账面为 0 的实物(盘盈)时,「已盘」计数会 +1,
# 明细里却找不到这一行,看起来像系统丢了数据。
# 注意条件必须限定在**本会话**的草稿,不能是库容里存在过草稿就放行。
union_sql = """
SELECT id, 'stock_buy' AS source_table, sku,
stock_quantity AS stock_qty, warehouse_location, base_id
FROM stock_buy WHERE stock_quantity > 0
FROM stock_buy
WHERE stock_quantity > 0
OR id IN (SELECT sd.stock_id FROM stocktake_draft sd
WHERE sd.source_table = 'stock_buy' AND sd.session_id = :sid)
UNION ALL
SELECT id, 'stock_semi', sku,
stock_quantity, warehouse_location, base_id
FROM stock_semi WHERE stock_quantity > 0
FROM stock_semi
WHERE stock_quantity > 0
OR id IN (SELECT sd.stock_id FROM stocktake_draft sd
WHERE sd.source_table = 'stock_semi' AND sd.session_id = :sid)
UNION ALL
SELECT id, 'stock_product', sku,
stock_quantity, warehouse_location, base_id
FROM stock_product WHERE stock_quantity > 0
FROM stock_product
WHERE stock_quantity > 0
OR id IN (SELECT sd.stock_id FROM stocktake_draft sd
WHERE sd.source_table = 'stock_product' AND sd.session_id = :sid)
"""
# ── 构建 WHERE 条件 ──