From b695592178d23fbc0bd2e2a78d68db50d1c1d1bb Mon Sep 17 00:00:00 2001 From: yueli Date: Mon, 14 Sep 2026 09:45:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(stocktake):=20=E6=98=8E=E7=BB=86=E5=8E=BB?= =?UTF-8?q?=20SKU=20=E6=94=B9=E6=98=BE=E6=89=B9=E5=8F=B7/=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8F=B7=EF=BC=8C=E5=A4=87=E6=B3=A8=E5=8F=AF=E9=80=89?= =?UTF-8?q?=E5=8F=AF=E5=A1=AB=EF=BC=8C=E5=88=97=E5=AE=BD=E9=80=82=E9=85=8D?= =?UTF-8?q?=E5=B9=B3=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【1. 去 SKU】明细表删除 SKU 列,新增「批次号」「序列号」。 SKU 是账务口径的物料主键,现场照着它抄数等于把账面信息递给盘点人。 (搜索框仍支持按 SKU 搜,只是列表不展示。) 后端 merged-list 的 UNION ALL 需要小心:stock_product 表**没有 batch_number 列**(只有 serial_number),故该分支必须写 '' AS batch_number 占位,否则列数不齐 / column does not exist。实测成品行 batch='' serial='205'。 【2. 列宽】名称/规格改 min-width=120(超长省略+tooltip);库位固定 120; 账面数/实盘数/差异固定 100,保证平板上输入框有足够触控面积;备注 min-width=160。 【3. 备注升级为可选项】el-input 换成 el-select(filterable allow-create default-first-option),预设 ['包装破损','找不到实物','标签丢失','账实不符','实物变质'], 可点选也可自行输入。 ★ 顺带修一个真 bug:handleRemarkChange 原本是个空函数,只 console.log, 备注填了**根本没落库**。现复用 update-quantity 接口保存,后端新增可选的 remark 字段(用 `remark is not None` 判断,只改数量时不会清空备注)。 未扫过的行没有草稿记录可挂,备注框同样禁用。 实测: 备注落库='包装破损';只改数量后备注仍在(未被清空)。 --- inventory-backend/app/api/v1/inbound/stock.py | 23 ++++++- inventory-web/src/api/inbound/stock.ts | 2 +- .../src/views/stock/stocktake/index.vue | 64 +++++++++++++++---- 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/inventory-backend/app/api/v1/inbound/stock.py b/inventory-backend/app/api/v1/inbound/stock.py index 0b612b4..64f78e9 100644 --- a/inventory-backend/app/api/v1/inbound/stock.py +++ b/inventory-backend/app/api/v1/inbound/stock.py @@ -2079,23 +2079,32 @@ def get_draft_merged_list(): # 否则工人扫到货架上账面为 0 的实物(盘盈)时,「已盘」计数会 +1, # 明细里却找不到这一行,看起来像系统丢了数据。 # 注意条件必须限定在**本会话**的草稿,不能是库容里存在过草稿就放行。 + # ★ 批次/序列号:stock_product 表**没有 batch_number 列**(只有 serial_number), + # 所以第三个分支必须给 batch_number 补一个空串占位,否则 UNION 两边 + # 列数不齐 / 报 column does not exist。前端据此显示为 '-'。 union_sql = """ SELECT id, 'stock_buy' AS source_table, sku, - stock_quantity AS stock_qty, warehouse_location, base_id + stock_quantity AS stock_qty, warehouse_location, base_id, + COALESCE(batch_number, '') AS batch_number, + COALESCE(serial_number, '') AS serial_number 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 + stock_quantity, warehouse_location, base_id, + COALESCE(batch_number, ''), + COALESCE(serial_number, '') 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 + stock_quantity, warehouse_location, base_id, + '' AS batch_number, -- stock_product 无 batch_number 列 + COALESCE(serial_number, '') FROM stock_product WHERE stock_quantity > 0 OR id IN (SELECT sd.stock_id FROM stocktake_draft sd @@ -2156,6 +2165,7 @@ def get_draft_merged_list(): data_sql = f""" SELECT cs.id AS stock_id, cs.source_table, cs.sku, + cs.batch_number, cs.serial_number, cs.stock_qty, cs.warehouse_location, mb.name AS mat_name, mb.spec_model AS mat_spec, sd.id AS draft_id, sd.quantity AS draft_qty, @@ -2193,6 +2203,8 @@ def get_draft_merged_list(): 'source_table': row.source_table, 'uniqueKey': f"{row.source_table}_{row.stock_id}", 'sku': row.sku or '', + 'batch_number': row.batch_number or '', + 'serial_number': row.serial_number or '', 'material_name': row.mat_name or '', 'spec_model': row.mat_spec or '', # ★ 盲盘:账面数置空,真实值不得出现在 HTTP 响应里 @@ -2371,6 +2383,8 @@ def update_stocktake_quantity(): source_table = data.get('source_table') session_id = data.get('session_id') # ★ 修复:按会话隔离,防止跨会话误改 quantity = float(data.get('quantity', 0)) + # 备注为**可选**字段:只有显式传入时才覆盖,避免改实盘数时把备注清空 + remark = data.get('remark', None) if not stock_id or not source_table: return jsonify({'code': 400, 'msg': '缺少必要参数'}), 400 @@ -2406,6 +2420,9 @@ def update_stocktake_quantity(): # 计算差异(★ 这是写库计算,不是报表输出,绝不能用 _qty —— 那会在盲盘时 # 返回占位符 '—' 导致减法报错。账面数在这里始终按真实值参与计算。) draft.diff_qty = quantity - float(draft.stock_qty or 0) + + if remark is not None: + draft.remark = remark.strip() if isinstance(remark, str) else remark db.session.commit() diff --git a/inventory-web/src/api/inbound/stock.ts b/inventory-web/src/api/inbound/stock.ts index 0226a47..44ee6c6 100644 --- a/inventory-web/src/api/inbound/stock.ts +++ b/inventory-web/src/api/inbound/stock.ts @@ -125,7 +125,7 @@ export function getAllStocktakeItems(params?: { // 更新盘点实盘数(手动修改) // company 走 query string(后端 get_current_company_filter 只读 request.args,不读 body) export function updateStocktakeQuantity( - data: { stock_id: number; source_table: string; quantity: number; session_id?: string }, + data: { stock_id: number; source_table: string; quantity: number; session_id?: string; remark?: string }, company?: string ) { return request({ diff --git a/inventory-web/src/views/stock/stocktake/index.vue b/inventory-web/src/views/stock/stocktake/index.vue index dddd2dc..1a7ecea 100644 --- a/inventory-web/src/views/stock/stocktake/index.vue +++ b/inventory-web/src/views/stock/stocktake/index.vue @@ -443,17 +443,25 @@ style="width: 100%" :row-class-name="(row) => row._justScanned ? 'just-scanned-row' : ''" > - + + + + + + + - - - + + + - + + - + - + + @@ -774,6 +797,9 @@ const creatingNew = ref(false) // 已有活跃会话时,是否展开创建 const newMode = ref<'open' | 'blind'>('open') // 盘点模式,默认明盘 const newScopeType = ref<'full' | 'active'>('full') // 盘点范围,默认全仓 +// 备注高频选项:可直接点选,也可在输入框里创建新条目(el-select allow-create) +const REMARK_OPTIONS = ['包装破损', '找不到实物', '标签丢失', '账实不符', '实物变质'] + const MODE_LABELS: Record = { open: '明盘', blind: '盲盘' } const SCOPE_LABELS: Record = { full: '全仓盘点', active: '活跃库位抽盘' } const modeLabel = (m?: string | null) => (m ? MODE_LABELS[m] || m : '') @@ -1680,10 +1706,24 @@ const handleQuantityChange = async (row: any, val: number) => { } // ★ 新增: 更新备注 +// ★ 备注保存。原实现只 console.log,备注**根本没落库** —— 填了等于没填。 +// 现复用 update-quantity(后端已支持可选 remark),quantity 必填故一并带上。 const handleRemarkChange = async (row: any) => { - // 备注更新可以批量处理或直接调用后端接口 - // 这里暂时只更新本地数据,实际项目中可以调用后端保存 - console.log('备注更新:', row.remark, row.id) + if (!row.draft_id) return // 未扫过的行没有草稿记录可挂 + try { + await updateStocktakeQuantity({ + stock_id: row.stock_id, + source_table: row.source_table, + quantity: Number(row.quantity || 0), + session_id: currentSessionId.value, + remark: row.remark || '' + }, selectedCompany.value || undefined) + ElMessage.success('备注已保存') + } catch (e: any) { + console.error('保存备注失败', e) + ElMessage.error(e?.response?.data?.msg || e?.msg || '备注保存失败,已还原') + fetchInventoryList(true) + } } // ★ 修改:结束盘点按钮直接调用 finishStocktake,跳过二次确认弹窗