feat(stocktake): 落地动盘(活跃库位抽盘)与导出盲盘保护
【导出收尾】/export-stocktake 会话 active 且 mode=blind 时,汇总表/差异明细/账实相符/未盘点四张 Sheet 的 账面数与差异数一律输出 '—',实盘数照常输出。会话 finished 后自动解锁 —— 盘点结束后核对差异是正常流程,否则报告本身没法用。 批量替换时误伤了 update_stocktake_quantity 里的差异计算,被静态检查脚本 抓出(会拿占位符 '—' 做减法),已改回按真实账面值计算。 【动盘算法】get_active_locations(company_name, days=30, top_n=50) 近 N 天异动最频繁的库位。出库经 (source_table, stock_id) 回查库存表取库位 (trans_outbound.warehouse_location 历史上 900 条里 829 条为空,实测回查 覆盖率 298/299);入库以库存表自身的 in_date / production_date 计时; 借出用 trans_borrow.location。报废/维修表无库位字段,未纳入。 【范围冻结】/draft/start-new 移除 scope_type=active 的 400 拦截,改为在开单时算出活跃库位并**冻结**进 scope_config。必须开单时算好 —— 若各接口实时计算,两次调用之间活跃度变化 会导致范围漂移,结束盘点时把已掉出范围的库存误判成盘亏。 【同源过滤】all-items / merged-list / generate-missing 三者都读同一份冻结的 scope_config。generate-missing 是重中之重:漏盘比对 只针对范围内库存,绝不把范围外未盘库存标成盘亏。 merged-list 一并加了过滤(原指令未提),否则抽屉仍会列出范围外物资, 与「总品项」对不上。 【前端】移除「活跃库位抽盘」的 disabled。 实测(IRIS,全仓 1126 → 抽盘 537): 生成盘亏 537 条 | 落在抽盘范围内 537 | 落在范围外 0 导出: 盲盘 active → 账面数/差异数 = '—';盲盘 finished → 76 / -76(解锁)
This commit is contained in:
@ -18,6 +18,7 @@ from app.models.inbound.stocktake import (
|
||||
STOCKTAKE_MODE_OPEN,
|
||||
STOCKTAKE_MODE_BLIND,
|
||||
STOCKTAKE_SCOPE_FULL,
|
||||
STOCKTAKE_SCOPE_ACTIVE,
|
||||
STOCKTAKE_STATUS_ACTIVE,
|
||||
STOCKTAKE_STATUS_FINISHED,
|
||||
)
|
||||
@ -158,6 +159,108 @@ def _filter_by_company(query, model, company_name):
|
||||
return query.filter(model.base.has(MaterialBase.company_name == company_name))
|
||||
|
||||
|
||||
def get_active_locations(company_name, days=30, top_n=50):
|
||||
"""
|
||||
统计最近 N 天内异动最频繁的库位 —— 「动盘/抽盘」的范围来源。
|
||||
|
||||
数据来源(三张库存表都没有 company_id,公司维度一律经 base_id 关联 material_base):
|
||||
- 出库 trans_outbound:其 warehouse_location 历史上几乎没填(900 条里 829 条为空,
|
||||
且仅 2026-09 起才部分有值),故改为经 (source_table, stock_id) 回查库存表的
|
||||
warehouse_location —— 实测近 30 天覆盖率 298/299。
|
||||
- 入库:入库没有独立流水表,直接以 stock_buy.in_date /
|
||||
stock_semi.production_date / stock_product.production_date 作为异动时间。
|
||||
- 借出 trans_borrow:用其 location 字段(填充率同样很低,80 条里仅 8 条有值)。
|
||||
|
||||
已知局限:
|
||||
- 报废(trans_scrap)与维修(trans_repair)表没有库位字段,未纳入统计;
|
||||
- 回查得到的是库存行**当前**的库位,不是异动发生当时的库位,
|
||||
库存行事后迁库会让历史归因偏移;
|
||||
- 库存行被删除后 JOIN 不到,该笔异动不计入。
|
||||
|
||||
:return: [{'location': str, 'moves': int, 'sku_count': int, 'last_move': str|None}, ...]
|
||||
按异动次数降序,最多 top_n 条。
|
||||
"""
|
||||
since = beijing_time() - timedelta(days=days)
|
||||
|
||||
sql = db.text("""
|
||||
WITH moves AS (
|
||||
SELECT COALESCE(b.warehouse_location, s.warehouse_location, p.warehouse_location) AS loc,
|
||||
o.sku AS sku, o.outbound_time AS ts,
|
||||
COALESCE(m1.company_name, m2.company_name, m3.company_name) AS company
|
||||
FROM trans_outbound o
|
||||
LEFT JOIN stock_buy b ON o.source_table = 'stock_buy' AND b.id = o.stock_id
|
||||
LEFT JOIN stock_semi s ON o.source_table = 'stock_semi' AND s.id = o.stock_id
|
||||
LEFT JOIN stock_product p ON o.source_table = 'stock_product' AND p.id = o.stock_id
|
||||
LEFT JOIN material_base m1 ON m1.id = b.base_id
|
||||
LEFT JOIN material_base m2 ON m2.id = s.base_id
|
||||
LEFT JOIN material_base m3 ON m3.id = p.base_id
|
||||
WHERE o.outbound_time >= :since
|
||||
|
||||
UNION ALL
|
||||
SELECT b2.warehouse_location, b2.sku, b2.in_date, m4.company_name
|
||||
FROM stock_buy b2 JOIN material_base m4 ON m4.id = b2.base_id
|
||||
WHERE b2.in_date >= :since
|
||||
|
||||
UNION ALL
|
||||
SELECT s2.warehouse_location, s2.sku, s2.production_date, m5.company_name
|
||||
FROM stock_semi s2 JOIN material_base m5 ON m5.id = s2.base_id
|
||||
WHERE s2.production_date >= :since
|
||||
|
||||
UNION ALL
|
||||
SELECT p2.warehouse_location, p2.sku, p2.production_date, m6.company_name
|
||||
FROM stock_product p2 JOIN material_base m6 ON m6.id = p2.base_id
|
||||
WHERE p2.production_date >= :since
|
||||
|
||||
UNION ALL
|
||||
SELECT br.location, br.sku, br.borrow_time, m7.company_name
|
||||
FROM trans_borrow br
|
||||
LEFT JOIN stock_buy bs ON br.source_table = 'stock_buy' AND bs.id = br.stock_id
|
||||
LEFT JOIN material_base m7 ON m7.id = bs.base_id
|
||||
WHERE br.borrow_time >= :since
|
||||
)
|
||||
SELECT loc AS location,
|
||||
count(*) AS moves,
|
||||
count(DISTINCT sku) AS sku_count,
|
||||
max(ts) AS last_move
|
||||
FROM moves
|
||||
WHERE loc IS NOT NULL AND loc <> ''
|
||||
AND (:company IS NULL OR company = :company)
|
||||
GROUP BY loc
|
||||
ORDER BY moves DESC, loc
|
||||
LIMIT :top_n
|
||||
""")
|
||||
|
||||
rows = db.session.execute(sql, {
|
||||
'since': since,
|
||||
'company': company_name,
|
||||
'top_n': top_n,
|
||||
}).fetchall()
|
||||
|
||||
return [{
|
||||
'location': r.location,
|
||||
'moves': int(r.moves or 0),
|
||||
'sku_count': int(r.sku_count or 0),
|
||||
'last_move': r.last_move.strftime('%Y-%m-%d') if r.last_move else None,
|
||||
} for r in rows]
|
||||
|
||||
|
||||
def _get_session_scope(session_id):
|
||||
"""读取会话的范围配置。
|
||||
|
||||
:return: (scope_type, locations) —— locations 仅当抽盘且配置了库位时非空
|
||||
"""
|
||||
if not session_id:
|
||||
return STOCKTAKE_SCOPE_FULL, []
|
||||
sess = StocktakeSession.query.filter_by(session_id=session_id).first()
|
||||
if not sess:
|
||||
return STOCKTAKE_SCOPE_FULL, []
|
||||
cfg = sess.scope_config or {}
|
||||
locs = cfg.get('locations') or []
|
||||
if sess.scope_type != STOCKTAKE_SCOPE_ACTIVE or not locs:
|
||||
return STOCKTAKE_SCOPE_FULL, []
|
||||
return STOCKTAKE_SCOPE_ACTIVE, list(locs)
|
||||
|
||||
|
||||
def get_stock_info(uuid_or_barcode, company_name=None):
|
||||
"""
|
||||
根据 uuid 或 barcode 查询库存信息(★ 精确匹配优先,性能与准确性兼顾)
|
||||
@ -954,16 +1057,38 @@ def start_new_session():
|
||||
return jsonify({"message": f"不支持的盘点模式: {mode}"}), 400
|
||||
|
||||
scope_type = (data.get('scope_type') or STOCKTAKE_SCOPE_FULL).strip().lower()
|
||||
if scope_type != STOCKTAKE_SCOPE_FULL:
|
||||
# ★ 抽盘的范围过滤(all-items / generate-missing)尚未实现,此处必须先挡住:
|
||||
# 范围没生效就等于全仓盘点,而结束盘点时 generate-missing 会把
|
||||
# 抽盘范围之外的所有库存批量标成盘亏 —— 会直接污染库存账。
|
||||
return jsonify({"message": "活跃库位抽盘尚未实现,当前仅支持全仓盘点"}), 400
|
||||
if scope_type not in (STOCKTAKE_SCOPE_FULL, STOCKTAKE_SCOPE_ACTIVE):
|
||||
return jsonify({"message": f"不支持的盘点范围: {scope_type}"}), 400
|
||||
|
||||
scope_config = data.get('scope_config') or {}
|
||||
if not isinstance(scope_config, dict):
|
||||
return jsonify({"message": "scope_config 必须是对象"}), 400
|
||||
|
||||
# ★ 抽盘:开单时就把活跃库位**冻结**进 scope_config。
|
||||
# 必须在创建会话这一刻算好并落库 —— all-items 与 generate-missing 稍后都要
|
||||
# 读同一份范围;若各自实时计算,两次调用之间库位活跃度变化会导致范围漂移,
|
||||
# 结束盘点时就会把「开单时在范围内、比对时已掉出范围」的库存误判成盘亏。
|
||||
if scope_type == STOCKTAKE_SCOPE_ACTIVE:
|
||||
try:
|
||||
days = int(scope_config.get('days') or 30)
|
||||
top_n = int(scope_config.get('top_n') or 50)
|
||||
except (TypeError, ValueError):
|
||||
return jsonify({"message": "days / top_n 必须是整数"}), 400
|
||||
|
||||
active_locs = get_active_locations(company_name, days=days, top_n=top_n)
|
||||
if not active_locs:
|
||||
return jsonify({
|
||||
"message": f"最近 {days} 天内没有找到活跃库位,无法创建抽盘任务"
|
||||
}), 400
|
||||
|
||||
scope_config = {
|
||||
'days': days,
|
||||
'top_n': top_n,
|
||||
'locations': [x['location'] for x in active_locs],
|
||||
'detail': active_locs,
|
||||
'computed_at': beijing_time().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
}
|
||||
|
||||
new_session_id = f"STK-{datetime.now().strftime('%Y%m%d%H%M%S')}-{uuid_module.uuid4().hex[:6]}"
|
||||
|
||||
try:
|
||||
@ -1264,6 +1389,21 @@ def export_stocktake():
|
||||
# ★ 公司隔离:导出内容限定在当前用户所属公司
|
||||
company_name = get_current_company_filter()
|
||||
|
||||
# ★ 盲盘保护:会话仍在进行中(active)且为盲盘时,报表里不得出现账面数与差异。
|
||||
# 会话一旦 finished 即解锁 —— 盘点结束后再核对差异是正常流程,
|
||||
# 否则报告本身就没法用了。
|
||||
_sess = StocktakeSession.query.filter_by(session_id=session_id).first() if session_id else None
|
||||
blind_locked = bool(
|
||||
_sess
|
||||
and _sess.status == STOCKTAKE_STATUS_ACTIVE
|
||||
and _sess.mode == STOCKTAKE_MODE_BLIND
|
||||
)
|
||||
BLIND_BLANK = '—'
|
||||
|
||||
def _qty(v):
|
||||
"""账面数/差异:盲盘锁定期间输出占位符,绝不落真实数值。"""
|
||||
return BLIND_BLANK if blind_locked else float(v or 0)
|
||||
|
||||
# 创建工作簿
|
||||
wb = Workbook()
|
||||
wb.remove(wb.active)
|
||||
@ -1375,9 +1515,9 @@ def export_stocktake():
|
||||
ws2.cell(row=row_idx, column=2, value=mat_info['sku']).border = thin_border
|
||||
ws2.cell(row=row_idx, column=3, value=mat_info['spec']).border = thin_border
|
||||
ws2.cell(row=row_idx, column=4, value=mat_info['location']).border = thin_border
|
||||
ws2.cell(row=row_idx, column=5, value=float(draft.stock_qty or 0)).border = thin_border
|
||||
ws2.cell(row=row_idx, column=5, value=_qty(draft.stock_qty)).border = thin_border
|
||||
ws2.cell(row=row_idx, column=6, value=float(draft.quantity or 0)).border = thin_border
|
||||
ws2.cell(row=row_idx, column=7, value=float(draft.diff_qty or 0)).border = thin_border
|
||||
ws2.cell(row=row_idx, column=7, value=_qty(draft.diff_qty)).border = thin_border
|
||||
ws2.cell(row=row_idx, column=8, value=get_user_name(draft.user_id)).border = thin_border
|
||||
ws2.cell(row=row_idx, column=9, value=to_beijing_time(draft.scan_time)).border = thin_border
|
||||
ws2.cell(row=row_idx, column=10, value=draft.remark or '').border = thin_border
|
||||
@ -1386,9 +1526,9 @@ def export_stocktake():
|
||||
ws1.cell(row=master_row_idx, column=2, value=mat_info['sku']).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=3, value=mat_info['spec']).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=4, value=mat_info['location']).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=5, value=float(draft.stock_qty or 0)).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=5, value=_qty(draft.stock_qty)).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=6, value=float(draft.quantity or 0)).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=7, value=float(draft.diff_qty or 0)).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=7, value=_qty(draft.diff_qty)).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=8, value="有差异").border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=9, value=get_user_name(draft.user_id)).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=10, value=to_beijing_time(draft.scan_time)).border = thin_border
|
||||
@ -1422,9 +1562,9 @@ def export_stocktake():
|
||||
ws3.cell(row=row_idx, column=2, value=mat_info['sku']).border = thin_border
|
||||
ws3.cell(row=row_idx, column=3, value=mat_info['spec']).border = thin_border
|
||||
ws3.cell(row=row_idx, column=4, value=mat_info['location']).border = thin_border
|
||||
ws3.cell(row=row_idx, column=5, value=float(draft.stock_qty or 0)).border = thin_border
|
||||
ws3.cell(row=row_idx, column=5, value=_qty(draft.stock_qty)).border = thin_border
|
||||
ws3.cell(row=row_idx, column=6, value=float(draft.quantity or 0)).border = thin_border
|
||||
ws3.cell(row=row_idx, column=7, value=float(draft.diff_qty or 0)).border = thin_border
|
||||
ws3.cell(row=row_idx, column=7, value=_qty(draft.diff_qty)).border = thin_border
|
||||
ws3.cell(row=row_idx, column=8, value=get_user_name(draft.user_id)).border = thin_border
|
||||
ws3.cell(row=row_idx, column=9, value=to_beijing_time(draft.scan_time)).border = thin_border
|
||||
ws3.cell(row=row_idx, column=10, value=draft.remark or '').border = thin_border
|
||||
@ -1433,9 +1573,9 @@ def export_stocktake():
|
||||
ws1.cell(row=master_row_idx, column=2, value=mat_info['sku']).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=3, value=mat_info['spec']).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=4, value=mat_info['location']).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=5, value=float(draft.stock_qty or 0)).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=5, value=_qty(draft.stock_qty)).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=6, value=float(draft.quantity or 0)).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=7, value=float(draft.diff_qty or 0)).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=7, value=_qty(draft.diff_qty)).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=8, value="正常").border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=9, value=get_user_name(draft.user_id)).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=10, value=to_beijing_time(draft.scan_time)).border = thin_border
|
||||
@ -1607,18 +1747,18 @@ def export_stocktake():
|
||||
ws5.cell(row=row_idx, column=3, value=item['spec']).border = thin_border
|
||||
ws5.cell(row=row_idx, column=4, value=item['location']).border = thin_border
|
||||
ws5.cell(row=row_idx, column=5, value=item.get('batch_no', '-')).border = thin_border # ★ 批号
|
||||
ws5.cell(row=row_idx, column=6, value=float(item['stock_qty'])).border = thin_border
|
||||
ws5.cell(row=row_idx, column=6, value=_qty(item['stock_qty'])).border = thin_border
|
||||
ws5.cell(row=row_idx, column=7, value=float(item['actual_qty'])).border = thin_border
|
||||
ws5.cell(row=row_idx, column=8, value=float(item['diff_qty'])).border = thin_border
|
||||
ws5.cell(row=row_idx, column=8, value=_qty(item['diff_qty'])).border = thin_border
|
||||
ws5.cell(row=row_idx, column=9, value=item['status']).border = thin_border
|
||||
# 同时写入 Sheet 1 (汇总表) - 盘点人和时间留空
|
||||
ws1.cell(row=master_row_idx, column=1, value=item['name']).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=2, value=item['sku']).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=3, value=item['spec']).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=4, value=item['location']).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=5, value=float(item['stock_qty'])).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=5, value=_qty(item['stock_qty'])).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=6, value=float(item['actual_qty'])).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=7, value=float(item['diff_qty'])).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=7, value=_qty(item['diff_qty'])).border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=8, value="未盘点").border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=9, value="").border = thin_border
|
||||
ws1.cell(row=master_row_idx, column=10, value="").border = thin_border
|
||||
@ -1715,10 +1855,23 @@ def generate_missing_stocktake():
|
||||
scanned_keys.add((src_table, stock_id))
|
||||
|
||||
# 2. 获取所有真实库存 > 0 的记录
|
||||
#
|
||||
# ★★★ 抽盘范围(本功能最关键的一处)★★★
|
||||
# 漏盘比对只针对**范围内**的库存。范围取自会话 scope_config 里开单时
|
||||
# 冻结的库位清单,与 all-items / merged-list 完全同源。
|
||||
# 若此过滤缺失或与其他接口不同源,结束盘点会把抽盘范围外的全部未盘
|
||||
# 库存批量生成为盘亏记录 —— 直接污染库存账。
|
||||
_scope_type, scope_locations = _get_session_scope(session_id)
|
||||
|
||||
def _in_scope(query, model):
|
||||
if scope_locations:
|
||||
query = query.filter(model.warehouse_location.in_(scope_locations))
|
||||
return query
|
||||
|
||||
all_stock = []
|
||||
|
||||
# 采购库存
|
||||
for item in _filter_by_company(StockBuy.query, StockBuy, company_name).filter(StockBuy.stock_quantity > 0).all():
|
||||
for item in _in_scope(_filter_by_company(StockBuy.query, StockBuy, company_name), StockBuy).filter(StockBuy.stock_quantity > 0).all():
|
||||
all_stock.append({
|
||||
'source_table': 'stock_buy',
|
||||
'stock_id': item.id,
|
||||
@ -1728,7 +1881,7 @@ def generate_missing_stocktake():
|
||||
|
||||
# 半成品库存
|
||||
if StockSemi:
|
||||
for item in _filter_by_company(StockSemi.query, StockSemi, company_name).filter(StockSemi.stock_quantity > 0).all():
|
||||
for item in _in_scope(_filter_by_company(StockSemi.query, StockSemi, company_name), StockSemi).filter(StockSemi.stock_quantity > 0).all():
|
||||
all_stock.append({
|
||||
'source_table': 'stock_semi',
|
||||
'stock_id': item.id,
|
||||
@ -1738,7 +1891,7 @@ def generate_missing_stocktake():
|
||||
|
||||
# 成品库存
|
||||
if StockProduct:
|
||||
for item in _filter_by_company(StockProduct.query, StockProduct, company_name).filter(StockProduct.stock_quantity > 0).all():
|
||||
for item in _in_scope(_filter_by_company(StockProduct.query, StockProduct, company_name), StockProduct).filter(StockProduct.stock_quantity > 0).all():
|
||||
all_stock.append({
|
||||
'source_table': 'stock_product',
|
||||
'stock_id': item.id,
|
||||
@ -1843,6 +1996,13 @@ def get_draft_merged_list():
|
||||
conditions.append("mb.company_name = :company")
|
||||
params['company'] = company_name
|
||||
|
||||
# ★ 抽盘范围:与 all-items / generate-missing 读同一份冻结的库位清单。
|
||||
# 不加这一条的话,抽屉里仍会列出范围外的物资,与「总品项」对不上。
|
||||
_scope_type, scope_locations = _get_session_scope(session_id)
|
||||
if scope_locations:
|
||||
conditions.append("cs.warehouse_location = ANY(:locations)")
|
||||
params['locations'] = scope_locations
|
||||
|
||||
if keyword:
|
||||
conditions.append("(LOWER(cs.sku) LIKE :kw OR LOWER(mb.name) LIKE :kw)")
|
||||
params['kw'] = f'%{keyword}%'
|
||||
@ -1994,6 +2154,14 @@ def get_all_stocktake_items():
|
||||
if company_name is not None:
|
||||
conditions.append("mb.company_name = :company")
|
||||
params['company'] = company_name
|
||||
# ★ 抽盘范围:范围在开单时已冻结进会话 scope_config,这里只读不算,
|
||||
# 确保与 generate-missing 用的是同一份库位清单
|
||||
_scope_type, scope_locations = _get_session_scope(
|
||||
request.args.get('session_id', '', type=str)
|
||||
)
|
||||
if scope_locations:
|
||||
conditions.append("cs.warehouse_location = ANY(:locations)")
|
||||
params['locations'] = scope_locations
|
||||
if keyword:
|
||||
conditions.append("(LOWER(cs.sku) LIKE :kw OR LOWER(mb.name) LIKE :kw OR LOWER(mb.spec_model) LIKE :kw)")
|
||||
params['kw'] = f'%{keyword.lower()}%'
|
||||
@ -2114,7 +2282,8 @@ def update_stocktake_quantity():
|
||||
draft.quantity = quantity
|
||||
draft.scan_time = beijing_time()
|
||||
|
||||
# 计算差异
|
||||
# 计算差异(★ 这是写库计算,不是报表输出,绝不能用 _qty —— 那会在盲盘时
|
||||
# 返回占位符 '—' 导致减法报错。账面数在这里始终按真实值参与计算。)
|
||||
draft.diff_qty = quantity - float(draft.stock_qty or 0)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
@ -80,13 +80,15 @@
|
||||
<span class="task-form-label">盘点范围</span>
|
||||
<el-radio-group v-model="newScopeType" size="large">
|
||||
<el-radio-button value="full">全仓盘点</el-radio-button>
|
||||
<!-- 抽盘的范围过滤尚未实现,此时开放会导致把范围外库存批量标成盘亏 -->
|
||||
<el-radio-button value="active" disabled>活跃库位抽盘</el-radio-button>
|
||||
<el-radio-button value="active">活跃库位抽盘</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="task-form-hint">
|
||||
{{ newMode === 'blind' ? '盲盘:作业时不显示账面数与差异' : '明盘:作业时可查看账面数与差异' }}
|
||||
</div>
|
||||
<div v-if="newScopeType === 'active'" class="task-form-hint">
|
||||
抽盘:仅盘点近 30 天有出入库/借还记录的前 50 个活跃库位
|
||||
</div>
|
||||
<div v-if="creatingNew" class="task-form-warn">
|
||||
开启新一轮会把当前进行中的盘点置为已结束(历史数据仍保留)
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user