|
|
|
@ -466,10 +466,13 @@ export default { |
|
|
|
}, |
|
|
|
pendingIssueParams: null, |
|
|
|
shipmentLine: [], |
|
|
|
// shipmentline 物料清单弹窗 |
|
|
|
// shipmentline 物料清单(进入页面时由父页预加载并缓存) |
|
|
|
showShipmentLineDialogVisible: false, |
|
|
|
shipmentLineLoading: false, |
|
|
|
shipmentLineList: [], |
|
|
|
shipmentLineRaw: [], // IFS ShipmentLine 原始返回,扫描校验 / 对比发料复用 |
|
|
|
shipmentPartNoSet: {}, // partNo -> true,本地校验用 |
|
|
|
shipmentLineInitialized: false, // 是否已完成 ShipmentLine 初始化 |
|
|
|
} |
|
|
|
}, |
|
|
|
computed: { |
|
|
|
@ -551,6 +554,14 @@ export default { |
|
|
|
|
|
|
|
// 验证标签并添加到列表(保留客户发料功能) |
|
|
|
validateAndAddLabel(labelCode) { |
|
|
|
if (!this.shipmentLineInitialized) { |
|
|
|
this.$message.warning('发货单物料清单未加载,请稍候再扫') |
|
|
|
return |
|
|
|
} |
|
|
|
if (!Object.keys(this.shipmentPartNoSet).length) { |
|
|
|
this.$message.warning('该发货单暂无物料行,无法添加') |
|
|
|
return |
|
|
|
} |
|
|
|
const params = { |
|
|
|
scannedLabel: labelCode, |
|
|
|
workOrderNo: this.orderNo, |
|
|
|
@ -564,16 +575,26 @@ export default { |
|
|
|
return |
|
|
|
} |
|
|
|
// 客户发料标签验证 |
|
|
|
// 注意:后端接口会先落库再返回,不属于本 shipment 的物料必须回滚删除 |
|
|
|
scanCustomerIssueMaterialLabel(params) |
|
|
|
.then(({ data }) => { |
|
|
|
if (data.code === 0 && data) { |
|
|
|
// 添加到列表,保存更多字段信息 |
|
|
|
// 后端返回的是 ShipmentHuDetail 对象 |
|
|
|
const labelInfo = data.labelInfo |
|
|
|
const labelInfo = data.labelInfo || {} |
|
|
|
const partNo = String( |
|
|
|
this.pickFieldValue(labelInfo, ['partNo', 'PART_NO', 'part_no'], '') || |
|
|
|
'' |
|
|
|
).trim() |
|
|
|
if (!partNo || !this.shipmentPartNoSet[partNo]) { |
|
|
|
this.rollbackInvalidScan(labelInfo, labelCode) |
|
|
|
this.$message.warning( |
|
|
|
`物料 ${partNo || '-'} 不属于当前发货单,无法添加` |
|
|
|
) |
|
|
|
return |
|
|
|
} |
|
|
|
this.labelList.push({ |
|
|
|
id: Date.now(), |
|
|
|
labelCode: labelCode, |
|
|
|
partNo: labelInfo.partNo || '', |
|
|
|
partNo: partNo, |
|
|
|
qtyToIssue: Number(labelInfo.qtyToIssue) || 0, |
|
|
|
batchNo: labelInfo.batchNo || '', |
|
|
|
locationId: labelInfo.locationId || '', |
|
|
|
@ -581,7 +602,7 @@ export default { |
|
|
|
wdrNo: labelInfo.wdr || '', |
|
|
|
engChgLevel: labelInfo.engChgLevel || '1', |
|
|
|
detailId: labelInfo.detailId || '', |
|
|
|
unitId: labelInfo.unitId || '', |
|
|
|
unitId: labelInfo.unitId || labelCode, |
|
|
|
site: labelInfo.site || this.site, |
|
|
|
height: labelInfo.height || null, |
|
|
|
}) |
|
|
|
@ -596,6 +617,59 @@ export default { |
|
|
|
}) |
|
|
|
}, |
|
|
|
|
|
|
|
// 扫描接口已落库但不属于本 shipment 时,回滚删除 |
|
|
|
rollbackInvalidScan(labelInfo, labelCode) { |
|
|
|
const unitId = (labelInfo && (labelInfo.unitId || labelInfo.unit_id)) || labelCode |
|
|
|
const detailId = labelInfo && labelInfo.detailId |
|
|
|
if (!unitId) { |
|
|
|
return |
|
|
|
} |
|
|
|
removeShipmentHuDetail({ |
|
|
|
site: this.site, |
|
|
|
unitId, |
|
|
|
detailId, |
|
|
|
}).catch(() => { |
|
|
|
// 回滚失败不阻断提示,避免误导操作员 |
|
|
|
}) |
|
|
|
}, |
|
|
|
|
|
|
|
isPartBelongToShipment(partNo) { |
|
|
|
const normalized = String(partNo || '').trim() |
|
|
|
if (!normalized) { |
|
|
|
return false |
|
|
|
} |
|
|
|
return !!this.shipmentPartNoSet[normalized] |
|
|
|
}, |
|
|
|
|
|
|
|
// 过滤不属于当前 shipmentline 的历史扫描记录 |
|
|
|
filterLabelsByShipmentLine(details = []) { |
|
|
|
if (!this.shipmentLineInitialized || !Object.keys(this.shipmentPartNoSet).length) { |
|
|
|
return details || [] |
|
|
|
} |
|
|
|
const valid = [] |
|
|
|
const invalid = [] |
|
|
|
;(details || []).forEach((item) => { |
|
|
|
const partNo = String( |
|
|
|
this.pickFieldValue(item, ['partNo', 'PART_NO', 'part_no'], '') || '' |
|
|
|
).trim() |
|
|
|
if (this.isPartBelongToShipment(partNo)) { |
|
|
|
valid.push(item) |
|
|
|
} else { |
|
|
|
invalid.push(item) |
|
|
|
} |
|
|
|
}) |
|
|
|
// 清理历史误扫入库的记录,避免下次进入再次出现 |
|
|
|
invalid.forEach((item) => { |
|
|
|
this.rollbackInvalidScan(item, item.unitId) |
|
|
|
}) |
|
|
|
if (invalid.length > 0) { |
|
|
|
this.$message.warning( |
|
|
|
`已自动移除 ${invalid.length} 条不属于当前发货单的历史扫描记录` |
|
|
|
) |
|
|
|
} |
|
|
|
return valid |
|
|
|
}, |
|
|
|
|
|
|
|
// 通过条码移除标签 |
|
|
|
removeLabelByCode(labelCode) { |
|
|
|
const exists = this.labelList.find((item) => item.unitId === labelCode) |
|
|
|
@ -775,79 +849,77 @@ export default { |
|
|
|
site: this.site, |
|
|
|
workOrderNo: this.orderNo, |
|
|
|
} |
|
|
|
await getCustomerIssueNotifyHeaderOrderMaterialList(params).then( |
|
|
|
({ data }) => { |
|
|
|
if (!data || data.code !== 0) { |
|
|
|
throw new Error((data && data.msg) || '获取申请单物料失败') |
|
|
|
if (!materials.length) { |
|
|
|
throw new Error('发货单物料清单为空,无法对比') |
|
|
|
} |
|
|
|
this.$set(this, 'shipmentLine', materials) |
|
|
|
const labelMap = this.aggregateMaterials( |
|
|
|
this.labelList, |
|
|
|
['partNo'], |
|
|
|
['qtyToIssue', 'quantity', 'issueQty'] |
|
|
|
) |
|
|
|
// 手动聚合订单物料,计算 INVENTORY_QTY - QTY_ASSIGNED 作为需求数量 |
|
|
|
const orderMap = {} |
|
|
|
materials.forEach((item) => { |
|
|
|
const partNo = this.pickFieldValue(item, ['INVENTORY_PART_NO']) |
|
|
|
const normalizedKey = |
|
|
|
typeof partNo === 'string' ? partNo.trim() : partNo |
|
|
|
if (!normalizedKey) { |
|
|
|
return |
|
|
|
} |
|
|
|
const inventoryQty = |
|
|
|
Number(this.pickFieldValue(item, ['INVENTORY_QTY'], 0)) || 0 |
|
|
|
const qtyAssigned = |
|
|
|
Number(this.pickFieldValue(item, ['QTY_ASSIGNED'], 0)) || 0 |
|
|
|
const requiredQty = this.safeAdd(inventoryQty, -qtyAssigned) |
|
|
|
|
|
|
|
if (!orderMap[normalizedKey]) { |
|
|
|
orderMap[normalizedKey] = { |
|
|
|
partNo: normalizedKey, |
|
|
|
qty: 0, |
|
|
|
} |
|
|
|
const materials = data.data || [] |
|
|
|
this.$set(this,'shipmentLine',data.data) |
|
|
|
const labelMap = this.aggregateMaterials( |
|
|
|
this.labelList, |
|
|
|
['partNo'], |
|
|
|
['qtyToIssue', 'quantity', 'issueQty'] |
|
|
|
) |
|
|
|
// 手动聚合订单物料,计算 INVENTORY_QTY - QTY_ASSIGNED 作为需求数量 |
|
|
|
const orderMap = {} |
|
|
|
materials.forEach((item) => { |
|
|
|
const partNo = this.pickFieldValue(item, ['INVENTORY_PART_NO']) |
|
|
|
const normalizedKey = |
|
|
|
typeof partNo === 'string' ? partNo.trim() : partNo |
|
|
|
if (!normalizedKey) { |
|
|
|
return |
|
|
|
} |
|
|
|
const inventoryQty = Number(this.pickFieldValue(item, ['INVENTORY_QTY'], 0)) || 0 |
|
|
|
const qtyAssigned = Number(this.pickFieldValue(item, ['QTY_ASSIGNED'], 0)) || 0 |
|
|
|
const requiredQty = this.safeAdd(inventoryQty, -qtyAssigned) |
|
|
|
|
|
|
|
if (!orderMap[normalizedKey]) { |
|
|
|
orderMap[normalizedKey] = { |
|
|
|
partNo: normalizedKey, |
|
|
|
qty: 0, |
|
|
|
} |
|
|
|
} |
|
|
|
orderMap[normalizedKey].qty = this.safeAdd(orderMap[normalizedKey].qty, requiredQty) |
|
|
|
}) |
|
|
|
} |
|
|
|
orderMap[normalizedKey].qty = this.safeAdd( |
|
|
|
orderMap[normalizedKey].qty, |
|
|
|
requiredQty |
|
|
|
) |
|
|
|
}) |
|
|
|
|
|
|
|
const existing = [] |
|
|
|
const missing = [] |
|
|
|
Object.keys(orderMap).forEach((partNo) => { |
|
|
|
console.log('Comparing partNo:', orderMap[partNo]) |
|
|
|
|
|
|
|
const requiredQty = orderMap[partNo].qty |
|
|
|
if (labelMap[partNo]) { |
|
|
|
const scannedQty = labelMap[partNo].qty |
|
|
|
existing.push({ |
|
|
|
partNo, |
|
|
|
requiredQty, |
|
|
|
scannedQty, |
|
|
|
difference: scannedQty - requiredQty, |
|
|
|
}) |
|
|
|
} else { |
|
|
|
missing.push({ |
|
|
|
partNo, |
|
|
|
requiredQty, |
|
|
|
}) |
|
|
|
} |
|
|
|
const existing = [] |
|
|
|
const missing = [] |
|
|
|
Object.keys(orderMap).forEach((partNo) => { |
|
|
|
const requiredQty = orderMap[partNo].qty |
|
|
|
if (labelMap[partNo]) { |
|
|
|
const scannedQty = labelMap[partNo].qty |
|
|
|
existing.push({ |
|
|
|
partNo, |
|
|
|
requiredQty, |
|
|
|
scannedQty, |
|
|
|
difference: scannedQty - requiredQty, |
|
|
|
}) |
|
|
|
|
|
|
|
const extra = [] |
|
|
|
Object.keys(labelMap).forEach((partNo) => { |
|
|
|
if (!orderMap[partNo]) { |
|
|
|
extra.push({ |
|
|
|
partNo, |
|
|
|
scannedQty: labelMap[partNo].qty, |
|
|
|
}) |
|
|
|
} |
|
|
|
} else { |
|
|
|
missing.push({ |
|
|
|
partNo, |
|
|
|
requiredQty, |
|
|
|
}) |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
this.comparisonData = { |
|
|
|
existing, |
|
|
|
missing, |
|
|
|
extra, |
|
|
|
} |
|
|
|
const extra = [] |
|
|
|
Object.keys(labelMap).forEach((partNo) => { |
|
|
|
if (!orderMap[partNo]) { |
|
|
|
extra.push({ |
|
|
|
partNo, |
|
|
|
scannedQty: labelMap[partNo].qty, |
|
|
|
}) |
|
|
|
} |
|
|
|
) |
|
|
|
}) |
|
|
|
|
|
|
|
this.comparisonData = { |
|
|
|
existing, |
|
|
|
missing, |
|
|
|
extra, |
|
|
|
} |
|
|
|
}, |
|
|
|
closeSummaryDialog() { |
|
|
|
if (this.loading) { |
|
|
|
@ -864,11 +936,70 @@ export default { |
|
|
|
closeShipmentLineDialog() { |
|
|
|
this.showShipmentLineDialogVisible = false |
|
|
|
}, |
|
|
|
async loadShipmentLineList() { |
|
|
|
applyShipmentLineMaterials(materials = []) { |
|
|
|
const list = Array.isArray(materials) ? materials : [] |
|
|
|
this.shipmentLineRaw = list |
|
|
|
this.$set(this, 'shipmentLine', list) |
|
|
|
const partNoSet = {} |
|
|
|
this.shipmentLineList = list.map((item) => { |
|
|
|
const inventoryQty = |
|
|
|
Number(this.pickFieldValue(item, ['INVENTORY_QTY'], 0)) || 0 |
|
|
|
const qtyAssigned = |
|
|
|
Number(this.pickFieldValue(item, ['QTY_ASSIGNED'], 0)) || 0 |
|
|
|
const partNo = this.pickFieldValue(item, ['INVENTORY_PART_NO'], '') |
|
|
|
const normalizedPartNo = |
|
|
|
typeof partNo === 'string' ? partNo.trim() : partNo |
|
|
|
if (normalizedPartNo) { |
|
|
|
partNoSet[normalizedPartNo] = true |
|
|
|
} |
|
|
|
return { |
|
|
|
lineNo: this.pickFieldValue(item, ['SHIPMENT_LINE_NO'], ''), |
|
|
|
partNo: normalizedPartNo || '', |
|
|
|
inventoryQty, |
|
|
|
qtyAssigned, |
|
|
|
remainQty: this.safeAdd(inventoryQty, -qtyAssigned), |
|
|
|
uom: this.pickFieldValue(item, ['INVENTORY_UOM'], ''), |
|
|
|
} |
|
|
|
}) |
|
|
|
this.shipmentPartNoSet = partNoSet |
|
|
|
this.shipmentLineInitialized = true |
|
|
|
return list |
|
|
|
}, |
|
|
|
// 从父页跳转时写入的 sessionStorage 初始化;失败则回退接口拉取一次 |
|
|
|
async initShipmentLineCache() { |
|
|
|
const cacheKey = `customerIssueShipmentLine_${this.orderNo}` |
|
|
|
let cached = null |
|
|
|
try { |
|
|
|
const raw = sessionStorage.getItem(cacheKey) |
|
|
|
if (raw) { |
|
|
|
cached = JSON.parse(raw) |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
cached = null |
|
|
|
} finally { |
|
|
|
try { |
|
|
|
sessionStorage.removeItem(cacheKey) |
|
|
|
} catch (e) { |
|
|
|
// ignore |
|
|
|
} |
|
|
|
} |
|
|
|
if (Array.isArray(cached)) { |
|
|
|
this.applyShipmentLineMaterials(cached) |
|
|
|
if (!cached.length) { |
|
|
|
this.$message.warning('该发货单暂无物料行,扫描将被禁止') |
|
|
|
} |
|
|
|
return |
|
|
|
} |
|
|
|
await this.ensureShipmentLineLoaded() |
|
|
|
}, |
|
|
|
async ensureShipmentLineLoaded() { |
|
|
|
if (this.shipmentLineRaw && this.shipmentLineRaw.length > 0) { |
|
|
|
return this.shipmentLineRaw |
|
|
|
} |
|
|
|
if (!this.site || !this.orderNo) { |
|
|
|
this.$message.error('缺少必要参数,无法获取物料清单') |
|
|
|
this.shipmentLineList = [] |
|
|
|
return |
|
|
|
this.applyShipmentLineMaterials([]) |
|
|
|
return [] |
|
|
|
} |
|
|
|
this.shipmentLineLoading = true |
|
|
|
try { |
|
|
|
@ -878,31 +1009,26 @@ export default { |
|
|
|
}) |
|
|
|
if (!data || data.code !== 0) { |
|
|
|
this.$message.error((data && data.msg) || '获取物料清单失败') |
|
|
|
this.shipmentLineList = [] |
|
|
|
return |
|
|
|
this.applyShipmentLineMaterials([]) |
|
|
|
return [] |
|
|
|
} |
|
|
|
const materials = data.data || [] |
|
|
|
this.shipmentLineList = materials.map((item) => { |
|
|
|
const inventoryQty = |
|
|
|
Number(this.pickFieldValue(item, ['INVENTORY_QTY'], 0)) || 0 |
|
|
|
const qtyAssigned = |
|
|
|
Number(this.pickFieldValue(item, ['QTY_ASSIGNED'], 0)) || 0 |
|
|
|
return { |
|
|
|
lineNo: this.pickFieldValue(item, ['SHIPMENT_LINE_NO'], ''), |
|
|
|
partNo: this.pickFieldValue(item, ['INVENTORY_PART_NO'], ''), |
|
|
|
inventoryQty, |
|
|
|
qtyAssigned, |
|
|
|
remainQty: this.safeAdd(inventoryQty, -qtyAssigned), |
|
|
|
uom: this.pickFieldValue(item, ['INVENTORY_UOM'], ''), |
|
|
|
} |
|
|
|
}) |
|
|
|
return this.applyShipmentLineMaterials(data.data || []) |
|
|
|
} catch (error) { |
|
|
|
this.$message.error('获取物料清单失败') |
|
|
|
this.shipmentLineList = [] |
|
|
|
this.applyShipmentLineMaterials([]) |
|
|
|
return [] |
|
|
|
} finally { |
|
|
|
this.shipmentLineLoading = false |
|
|
|
} |
|
|
|
}, |
|
|
|
async loadShipmentLineList() { |
|
|
|
// 弹窗优先展示已缓存数据,不再每次打开都调 IFS |
|
|
|
if (this.shipmentLineRaw && this.shipmentLineRaw.length > 0) { |
|
|
|
this.applyShipmentLineMaterials(this.shipmentLineRaw) |
|
|
|
return |
|
|
|
} |
|
|
|
await this.ensureShipmentLineLoaded() |
|
|
|
}, |
|
|
|
proceedIssueConfirm() { |
|
|
|
if (!this.pendingIssueParams) { |
|
|
|
this.pendingIssueParams = this.buildIssueParams() |
|
|
|
@ -1029,7 +1155,8 @@ export default { |
|
|
|
getShipmentHuDetail(params) |
|
|
|
.then(({ data }) => { |
|
|
|
if (data.code === 0 && data) { |
|
|
|
this.labelList = data.details || [] |
|
|
|
// 只保留属于当前 shipmentline 的扫描记录 |
|
|
|
this.labelList = this.filterLabelsByShipmentLine(data.details || []) |
|
|
|
} else { |
|
|
|
this.$message.error(data.msg || '获取订单详情失败') |
|
|
|
} |
|
|
|
@ -1181,7 +1308,7 @@ export default { |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
mounted() { |
|
|
|
async mounted() { |
|
|
|
// 获取路由参数 |
|
|
|
console.log('路由参数:', this.$route.query) |
|
|
|
|
|
|
|
@ -1203,7 +1330,8 @@ export default { |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
// 加载订单详情 |
|
|
|
// 必须先拿到 ShipmentLine,再加载历史扫描,避免错料先展示出来 |
|
|
|
await this.initShipmentLineCache() |
|
|
|
this.loadOrderDetails() |
|
|
|
}, |
|
|
|
} |
|
|
|
|