feat(stocktake): 明/盲盘由后端强制驱动,前端解除屏蔽
原实现是前端把「账面数」「差异」两列用 HTML 注释藏掉 —— 数据仍在 HTTP 响应里明文下发,抓包或用改过的客户端即可看到,等于没有屏蔽。 【后端】/draft/merged-list 先查该 session_id 的 StocktakeSession.mode: - blind:stock_qty 与 diff_qty 一律置 None,真实账面数不出现在响应中 - open :正常下发 差异必须一并置空 —— diff_qty 由账面数算出,下发差异等于变相泄漏账面数。 响应新增 data.mode 供前端提示。 Fail-Safe:查不到会话行时按盲盘处理。宁可界面少显示两列,也不能因为 会话记录缺失就把账面数泄漏出去。 【前端】删除屏蔽用的 HTML 注释,恢复两列正常渲染,不再承担数据屏蔽职责。 但 null 必须渲染为「—」而非 0:原 差异 模板用 `row.diff_qty || 0`, 会把「未下发」显示成「无差异」,工人会误以为账实相符。 另在抽屉表头加模式标签,便于核对。 实测: 盲盘 → 账面数=None 差异=None mode=blind 明盘 → 账面数=1.0 差异=-1.0 mode=open 未知 session_id → 同盲盘(Fail-Safe 生效)
This commit is contained in:
@ -1900,6 +1900,12 @@ def get_draft_merged_list():
|
|||||||
params['offset'] = offset
|
params['offset'] = offset
|
||||||
rows = db.session.execute(db.text(data_sql), params).fetchall()
|
rows = db.session.execute(db.text(data_sql), params).fetchall()
|
||||||
|
|
||||||
|
# ── 明/盲盘控制:账面数与差异由后端强制决定是否下发 ──
|
||||||
|
# ★ Fail-Safe:查不到会话配置时按盲盘处理。宁可让界面少显示两列,
|
||||||
|
# 也不能因为会话行缺失就把真实账面数泄漏出去。
|
||||||
|
session = StocktakeSession.query.filter_by(session_id=session_id).first()
|
||||||
|
is_blind = True if session is None else (session.mode == STOCKTAKE_MODE_BLIND)
|
||||||
|
|
||||||
# ── 组装结果 ──
|
# ── 组装结果 ──
|
||||||
items = []
|
items = []
|
||||||
for row in rows:
|
for row in rows:
|
||||||
@ -1915,9 +1921,12 @@ def get_draft_merged_list():
|
|||||||
'sku': row.sku or '',
|
'sku': row.sku 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 '',
|
||||||
'stock_qty': stock_qty,
|
# ★ 盲盘:账面数置空,真实值不得出现在 HTTP 响应里
|
||||||
|
'stock_qty': None if is_blind else stock_qty,
|
||||||
'quantity': draft_qty,
|
'quantity': draft_qty,
|
||||||
'diff_qty': round(diff_qty, 4),
|
# ★ 盲盘:差异同样置空 —— diff_qty 由账面数算出,
|
||||||
|
# 下发差异等于变相泄漏账面数
|
||||||
|
'diff_qty': None if is_blind else round(diff_qty, 4),
|
||||||
'remark': row.draft_remark or '',
|
'remark': row.draft_remark or '',
|
||||||
'warehouse_location': row.warehouse_location or '',
|
'warehouse_location': row.warehouse_location or '',
|
||||||
})
|
})
|
||||||
@ -1927,7 +1936,9 @@ def get_draft_merged_list():
|
|||||||
'data': {
|
'data': {
|
||||||
'list': items, 'total': total,
|
'list': items, 'total': total,
|
||||||
'total_scanned': total_scanned,
|
'total_scanned': total_scanned,
|
||||||
'page': page, 'pageSize': page_size
|
'page': page, 'pageSize': page_size,
|
||||||
|
# 前端据此决定表头提示;真正的数据屏蔽已经在上面做完了
|
||||||
|
'mode': session.mode if session is not None else STOCKTAKE_MODE_BLIND,
|
||||||
}
|
}
|
||||||
}), 200
|
}), 200
|
||||||
|
|
||||||
|
|||||||
@ -321,6 +321,9 @@
|
|||||||
<el-radio-button value="counted">已盘</el-radio-button>
|
<el-radio-button value="counted">已盘</el-radio-button>
|
||||||
<el-radio-button value="uncounted">未盘</el-radio-button>
|
<el-radio-button value="uncounted">未盘</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
|
<el-tag v-if="listMode" :type="listMode === 'blind' ? 'warning' : 'success'" size="small" effect="plain">
|
||||||
|
{{ modeLabel(listMode) }}
|
||||||
|
</el-tag>
|
||||||
<span style="margin-left: auto; color: #909399; font-size: 13px;">
|
<span style="margin-left: auto; color: #909399; font-size: 13px;">
|
||||||
总品项: {{ stats.total }} | 已盘: {{ stats.scanned }} | 未盘: {{ stats.total - stats.scanned }}
|
总品项: {{ stats.total }} | 已盘: {{ stats.scanned }} | 未盘: {{ stats.total - stats.scanned }}
|
||||||
</span>
|
</span>
|
||||||
@ -339,8 +342,16 @@
|
|||||||
<el-table-column prop="sku" label="SKU" width="140" show-overflow-tooltip />
|
<el-table-column prop="sku" label="SKU" width="140" show-overflow-tooltip />
|
||||||
<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="规格" width="120" show-overflow-tooltip />
|
||||||
<!-- 盲盘:隐藏账面数和差异列 -->
|
<!-- ★ 账面数与差异由**后端**按会话 mode 决定是否下发:
|
||||||
<!-- <el-table-column prop="stock_qty" label="账面数" width="80" align="center" /> -->
|
盲盘时这两个字段返回 null,前端不再做任何屏蔽。
|
||||||
|
注意 null 必须显示为「—」而不是 0 —— 用 `|| 0` 会把「未下发」
|
||||||
|
误渲染成「无差异」,工人会以为账实相符。 -->
|
||||||
|
<el-table-column label="账面数" width="80" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.stock_qty === null || row.stock_qty === undefined" class="cell-blinded">—</span>
|
||||||
|
<span v-else>{{ row.stock_qty }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="实盘数" width="100" align="center">
|
<el-table-column label="实盘数" width="100" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
@ -354,13 +365,14 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<!-- <el-table-column label="差异" width="80" align="center">
|
<el-table-column label="差异" width="80" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span :style="{ color: row.diff_qty > 0 ? '#67C23A' : row.diff_qty < 0 ? '#F56C6C' : '#909399' }">
|
<span v-if="row.diff_qty === null || row.diff_qty === undefined" class="cell-blinded">—</span>
|
||||||
{{ row.diff_qty > 0 ? '+' : '' }}{{ row.diff_qty || 0 }}
|
<span v-else :style="{ color: row.diff_qty > 0 ? '#67C23A' : row.diff_qty < 0 ? '#F56C6C' : '#909399' }">
|
||||||
|
{{ row.diff_qty > 0 ? '+' : '' }}{{ row.diff_qty }}
|
||||||
</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 prop="remark" label="备注" min-width="120" show-overflow-tooltip>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input v-model="row.remark" placeholder="备注" size="small" @blur="handleRemarkChange(row)" />
|
<el-input v-model="row.remark" placeholder="备注" size="small" @blur="handleRemarkChange(row)" />
|
||||||
@ -576,6 +588,8 @@ const listLimit = ref(20)
|
|||||||
const listKeyword = ref('')
|
const listKeyword = ref('')
|
||||||
const listLoading = ref(false)
|
const listLoading = ref(false)
|
||||||
const listData = ref<any[]>([])
|
const listData = ref<any[]>([])
|
||||||
|
// 当前列表的盘点模式(由后端下发),用于表头提示;数据屏蔽已在后端完成
|
||||||
|
const listMode = ref<string | null>(null)
|
||||||
const listStatusFilter = ref<'all' | 'counted' | 'uncounted'>('all')
|
const listStatusFilter = ref<'all' | 'counted' | 'uncounted'>('all')
|
||||||
const allStockItems = ref<any[]>([]) // 全量应盘物资(盘点基数)
|
const allStockItems = ref<any[]>([]) // 全量应盘物资(盘点基数)
|
||||||
const totalStockCount = ref(0) // ★ 全量应盘物资总数(不受limit限制)
|
const totalStockCount = ref(0) // ★ 全量应盘物资总数(不受limit限制)
|
||||||
@ -1334,6 +1348,7 @@ const fetchInventoryList = async (silent = false) => {
|
|||||||
listData.value = data.list || []
|
listData.value = data.list || []
|
||||||
listTotalFiltered.value = data.total || 0
|
listTotalFiltered.value = data.total || 0
|
||||||
totalScannedCount.value = data.total_scanned || 0
|
totalScannedCount.value = data.total_scanned || 0
|
||||||
|
listMode.value = data.mode || null
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!silent) ElMessage.error('获取盘点清单失败')
|
if (!silent) ElMessage.error('获取盘点清单失败')
|
||||||
@ -1566,6 +1581,9 @@ const goToVarianceReview = () => {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
/* 盲盘时后端不下发账面数/差异,用「—」表示未下发(区别于真实的 0) */
|
||||||
|
.cell-blinded { color: #c0c4cc; }
|
||||||
|
|
||||||
/* ★ 创建盘点任务表单(模式 / 范围) */
|
/* ★ 创建盘点任务表单(模式 / 范围) */
|
||||||
.task-form {
|
.task-form {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
Reference in New Issue
Block a user