feat(stocktake): 明细去 SKU 改显批号/序列号,备注可选可填,列宽适配平板
【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` 判断,只改数量时不会清空备注)。 未扫过的行没有草稿记录可挂,备注框同样禁用。 实测: 备注落库='包装破损';只改数量后备注仍在(未被清空)。
This commit is contained in:
@ -2079,23 +2079,32 @@ def get_draft_merged_list():
|
|||||||
# 否则工人扫到货架上账面为 0 的实物(盘盈)时,「已盘」计数会 +1,
|
# 否则工人扫到货架上账面为 0 的实物(盘盈)时,「已盘」计数会 +1,
|
||||||
# 明细里却找不到这一行,看起来像系统丢了数据。
|
# 明细里却找不到这一行,看起来像系统丢了数据。
|
||||||
# 注意条件必须限定在**本会话**的草稿,不能是库容里存在过草稿就放行。
|
# 注意条件必须限定在**本会话**的草稿,不能是库容里存在过草稿就放行。
|
||||||
|
# ★ 批次/序列号:stock_product 表**没有 batch_number 列**(只有 serial_number),
|
||||||
|
# 所以第三个分支必须给 batch_number 补一个空串占位,否则 UNION 两边
|
||||||
|
# 列数不齐 / 报 column does not exist。前端据此显示为 '-'。
|
||||||
union_sql = """
|
union_sql = """
|
||||||
SELECT id, 'stock_buy' AS source_table, sku,
|
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
|
FROM stock_buy
|
||||||
WHERE stock_quantity > 0
|
WHERE stock_quantity > 0
|
||||||
OR id IN (SELECT sd.stock_id FROM stocktake_draft sd
|
OR id IN (SELECT sd.stock_id FROM stocktake_draft sd
|
||||||
WHERE sd.source_table = 'stock_buy' AND sd.session_id = :sid)
|
WHERE sd.source_table = 'stock_buy' AND sd.session_id = :sid)
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT id, 'stock_semi', sku,
|
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
|
FROM stock_semi
|
||||||
WHERE stock_quantity > 0
|
WHERE stock_quantity > 0
|
||||||
OR id IN (SELECT sd.stock_id FROM stocktake_draft sd
|
OR id IN (SELECT sd.stock_id FROM stocktake_draft sd
|
||||||
WHERE sd.source_table = 'stock_semi' AND sd.session_id = :sid)
|
WHERE sd.source_table = 'stock_semi' AND sd.session_id = :sid)
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT id, 'stock_product', sku,
|
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
|
FROM stock_product
|
||||||
WHERE stock_quantity > 0
|
WHERE stock_quantity > 0
|
||||||
OR id IN (SELECT sd.stock_id FROM stocktake_draft sd
|
OR id IN (SELECT sd.stock_id FROM stocktake_draft sd
|
||||||
@ -2156,6 +2165,7 @@ def get_draft_merged_list():
|
|||||||
data_sql = f"""
|
data_sql = f"""
|
||||||
SELECT
|
SELECT
|
||||||
cs.id AS stock_id, cs.source_table, cs.sku,
|
cs.id AS stock_id, cs.source_table, cs.sku,
|
||||||
|
cs.batch_number, cs.serial_number,
|
||||||
cs.stock_qty, cs.warehouse_location,
|
cs.stock_qty, cs.warehouse_location,
|
||||||
mb.name AS mat_name, mb.spec_model AS mat_spec,
|
mb.name AS mat_name, mb.spec_model AS mat_spec,
|
||||||
sd.id AS draft_id, sd.quantity AS draft_qty,
|
sd.id AS draft_id, sd.quantity AS draft_qty,
|
||||||
@ -2193,6 +2203,8 @@ def get_draft_merged_list():
|
|||||||
'source_table': row.source_table,
|
'source_table': row.source_table,
|
||||||
'uniqueKey': f"{row.source_table}_{row.stock_id}",
|
'uniqueKey': f"{row.source_table}_{row.stock_id}",
|
||||||
'sku': row.sku or '',
|
'sku': row.sku or '',
|
||||||
|
'batch_number': row.batch_number or '',
|
||||||
|
'serial_number': row.serial_number or '',
|
||||||
'material_name': row.mat_name or '',
|
'material_name': row.mat_name or '',
|
||||||
'spec_model': row.mat_spec or '',
|
'spec_model': row.mat_spec or '',
|
||||||
# ★ 盲盘:账面数置空,真实值不得出现在 HTTP 响应里
|
# ★ 盲盘:账面数置空,真实值不得出现在 HTTP 响应里
|
||||||
@ -2371,6 +2383,8 @@ def update_stocktake_quantity():
|
|||||||
source_table = data.get('source_table')
|
source_table = data.get('source_table')
|
||||||
session_id = data.get('session_id') # ★ 修复:按会话隔离,防止跨会话误改
|
session_id = data.get('session_id') # ★ 修复:按会话隔离,防止跨会话误改
|
||||||
quantity = float(data.get('quantity', 0))
|
quantity = float(data.get('quantity', 0))
|
||||||
|
# 备注为**可选**字段:只有显式传入时才覆盖,避免改实盘数时把备注清空
|
||||||
|
remark = data.get('remark', None)
|
||||||
|
|
||||||
if not stock_id or not source_table:
|
if not stock_id or not source_table:
|
||||||
return jsonify({'code': 400, 'msg': '缺少必要参数'}), 400
|
return jsonify({'code': 400, 'msg': '缺少必要参数'}), 400
|
||||||
@ -2406,6 +2420,9 @@ def update_stocktake_quantity():
|
|||||||
# 计算差异(★ 这是写库计算,不是报表输出,绝不能用 _qty —— 那会在盲盘时
|
# 计算差异(★ 这是写库计算,不是报表输出,绝不能用 _qty —— 那会在盲盘时
|
||||||
# 返回占位符 '—' 导致减法报错。账面数在这里始终按真实值参与计算。)
|
# 返回占位符 '—' 导致减法报错。账面数在这里始终按真实值参与计算。)
|
||||||
draft.diff_qty = quantity - float(draft.stock_qty or 0)
|
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()
|
db.session.commit()
|
||||||
|
|
||||||
|
|||||||
@ -125,7 +125,7 @@ export function getAllStocktakeItems(params?: {
|
|||||||
// 更新盘点实盘数(手动修改)
|
// 更新盘点实盘数(手动修改)
|
||||||
// company 走 query string(后端 get_current_company_filter 只读 request.args,不读 body)
|
// company 走 query string(后端 get_current_company_filter 只读 request.args,不读 body)
|
||||||
export function updateStocktakeQuantity(
|
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
|
company?: string
|
||||||
) {
|
) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@ -443,17 +443,25 @@
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
:row-class-name="(row) => row._justScanned ? 'just-scanned-row' : ''"
|
:row-class-name="(row) => row._justScanned ? 'just-scanned-row' : ''"
|
||||||
>
|
>
|
||||||
<el-table-column prop="sku" label="SKU" width="140" show-overflow-tooltip />
|
<!-- ★ 刻意不展示 SKU:SKU 是账务口径的物料主键,现场照着它抄数
|
||||||
|
等于把账面信息递给盘点人。改用批次号/序列号做实物辨识。
|
||||||
|
(搜索框仍支持按 SKU 搜,只是列表不直接展示。) -->
|
||||||
|
<el-table-column label="批次号" width="110" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">{{ row.batch_number || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="序列号" width="130" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">{{ row.serial_number || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="material_name" label="名称" min-width="120" show-overflow-tooltip />
|
<el-table-column prop="material_name" label="名称" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column prop="spec_model" label="规格" width="120" show-overflow-tooltip />
|
<el-table-column prop="spec_model" label="规格" min-width="120" show-overflow-tooltip />
|
||||||
<!-- ★ 库位:找货刚需,放在名称/规格后面。
|
<!-- ★ 库位:找货刚需。固定宽度,超长路径靠 tooltip 看 -->
|
||||||
后端 merged-list 已下发(SELECT cs.warehouse_location → 组装进 JSON) -->
|
<el-table-column prop="warehouse_location" label="库位" width="120" show-overflow-tooltip />
|
||||||
<el-table-column prop="warehouse_location" label="库位" min-width="120" show-overflow-tooltip />
|
|
||||||
<!-- ★ 账面数与差异由**后端**按会话 mode 决定是否下发:
|
<!-- ★ 账面数与差异由**后端**按会话 mode 决定是否下发:
|
||||||
盲盘时这两个字段返回 null,前端不再做任何屏蔽。
|
盲盘时这两个字段返回 null,前端不再做任何屏蔽。
|
||||||
注意 null 必须显示为「—」而不是 0 —— 用 `|| 0` 会把「未下发」
|
注意 null 必须显示为「—」而不是 0 —— 用 `|| 0` 会把「未下发」
|
||||||
误渲染成「无差异」,工人会以为账实相符。 -->
|
误渲染成「无差异」,工人会以为账实相符。 -->
|
||||||
<el-table-column label="账面数" width="80" align="center">
|
<!-- 账面数/实盘数/差异:固定 100px —— 平板点按输入框要有足够触控面积 -->
|
||||||
|
<el-table-column label="账面数" width="100" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row.stock_qty === null || row.stock_qty === undefined" class="cell-blinded">—</span>
|
<span v-if="row.stock_qty === null || row.stock_qty === undefined" class="cell-blinded">—</span>
|
||||||
<span v-else>{{ row.stock_qty }}</span>
|
<span v-else>{{ row.stock_qty }}</span>
|
||||||
@ -479,7 +487,7 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="差异" width="80" align="center">
|
<el-table-column label="差异" width="100" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row.diff_qty === null || row.diff_qty === undefined" class="cell-blinded">—</span>
|
<span v-if="row.diff_qty === null || row.diff_qty === undefined" class="cell-blinded">—</span>
|
||||||
<span v-else :style="{ color: row.diff_qty > 0 ? '#67C23A' : row.diff_qty < 0 ? '#F56C6C' : '#909399' }">
|
<span v-else :style="{ color: row.diff_qty > 0 ? '#67C23A' : row.diff_qty < 0 ? '#F56C6C' : '#909399' }">
|
||||||
@ -487,9 +495,24 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip>
|
<!-- ★ 备注:可下拉选高频原因,也可直接输入新内容。
|
||||||
|
与实盘数同理,未扫过的行没有草稿记录可挂,故禁用。 -->
|
||||||
|
<el-table-column label="备注" min-width="160">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input v-model="row.remark" placeholder="备注" size="small" @blur="handleRemarkChange(row)" />
|
<el-select
|
||||||
|
v-model="row.remark"
|
||||||
|
placeholder="选择或输入"
|
||||||
|
size="small"
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
:disabled="!row.draft_id || !userStore.hasPermission('inventory_stocktake:operation')"
|
||||||
|
@change="() => handleRemarkChange(row)"
|
||||||
|
>
|
||||||
|
<el-option v-for="opt in REMARK_OPTIONS" :key="opt" :label="opt" :value="opt" />
|
||||||
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@ -774,6 +797,9 @@ const creatingNew = ref(false) // 已有活跃会话时,是否展开创建
|
|||||||
const newMode = ref<'open' | 'blind'>('open') // 盘点模式,默认明盘
|
const newMode = ref<'open' | 'blind'>('open') // 盘点模式,默认明盘
|
||||||
const newScopeType = ref<'full' | 'active'>('full') // 盘点范围,默认全仓
|
const newScopeType = ref<'full' | 'active'>('full') // 盘点范围,默认全仓
|
||||||
|
|
||||||
|
// 备注高频选项:可直接点选,也可在输入框里创建新条目(el-select allow-create)
|
||||||
|
const REMARK_OPTIONS = ['包装破损', '找不到实物', '标签丢失', '账实不符', '实物变质']
|
||||||
|
|
||||||
const MODE_LABELS: Record<string, string> = { open: '明盘', blind: '盲盘' }
|
const MODE_LABELS: Record<string, string> = { open: '明盘', blind: '盲盘' }
|
||||||
const SCOPE_LABELS: Record<string, string> = { full: '全仓盘点', active: '活跃库位抽盘' }
|
const SCOPE_LABELS: Record<string, string> = { full: '全仓盘点', active: '活跃库位抽盘' }
|
||||||
const modeLabel = (m?: string | null) => (m ? MODE_LABELS[m] || m : '')
|
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) => {
|
const handleRemarkChange = async (row: any) => {
|
||||||
// 备注更新可以批量处理或直接调用后端接口
|
if (!row.draft_id) return // 未扫过的行没有草稿记录可挂
|
||||||
// 这里暂时只更新本地数据,实际项目中可以调用后端保存
|
try {
|
||||||
console.log('备注更新:', row.remark, row.id)
|
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,跳过二次确认弹窗
|
// ★ 修改:结束盘点按钮直接调用 finishStocktake,跳过二次确认弹窗
|
||||||
|
|||||||
Reference in New Issue
Block a user