feat&refactor: 2026-09-22 半成品业务模型重构与功能完善
- 重构半成品流转逻辑:进度权威源改为 workpiece.currentStationNo,新增 completedText 快照字段,废除 doneProcessCodes - 清理物料品类中半成品类型,存量数据幂等清理,调整物料管理方式文案为批次/序列号 - 新增日排产状态专用更新接口,修复工单工艺校验逻辑 - 新增物料档案代理接口、深路径文件下载接口 - 优化前端界面文案与工位选择逻辑,补充追溯页半成品流转展示 - 调整WMS端物料品类校验与入库接口契约 - 新增/更新数据库表结构与实体类代码
This commit is contained in:
@@ -84,7 +84,7 @@ export const helpDailyPlan = {
|
||||
export const helpBom = {
|
||||
title: '产品物料清单',
|
||||
overview:
|
||||
'物料清单 = 生产 1 台该产品需要哪些物料、各用多少。\n按「产品编号 + 物料清单名称」维护:同一产品编号可并存多份物料清单(选用制)——不同厂家供货、含半成品的结构差异,都用不同的物料清单名称区分(缺省叫「默认」,不用 V1/V2 版本号)。\n工单建单时选定其中一份,之后该工单的备料算料、装机绑定校验都锁定这份物料清单,不同厂家的料单互不串。\n\n与 WMS 的关系:本页(MES)只维护「产品由哪几种料、各用几个」;每种物料叫什么、规格、是电气件(SN)还是结构件(批次)、库存有多少,统一在 WMS(库房)的「物料档案」里维护,两套系统互相解耦、通过图号对齐。\n\n编码对应规则:本页的「图号」必须与 WMS 物料档案里的「图号」完全一致;「管理方式」也要对应(电气件=SN 逐件,结构件=批次)。编码对不上,WMS 将无法为对应工单备料/出库。\n\n保护规则:某份物料清单一旦被工单选用过,就不允许再从中移除物料(防止历史工单追溯断链);新建/改名的物料清单名称在本页「物料清单名称」下拉直接输入即可创建。\n\n数据链路:先建「物料档案」→ 到本页选产品编号 + 命名物料清单,逐条添加物料 → 建工单时选定物料清单 → 仓库才能为对应工单领料。',
|
||||
'物料清单 = 生产 1 台该产品需要哪些物料、各用多少。\n按「产品编号 + 物料清单名称」维护:同一产品编号可并存多份物料清单(选用制)——不同厂家供货、含半成品的结构差异,都用不同的物料清单名称区分(缺省叫「默认」,不用 V1/V2 版本号)。\n工单建单时选定其中一份,之后该工单的备料算料、装机绑定校验都锁定这份物料清单,不同厂家的料单互不串。\n\n与 WMS 的关系:本页(MES)只维护「产品由哪几种料、各用几个」;每种物料叫什么、规格、按序列号还是按批次管理、库存有多少,统一在 WMS(库房)的「物料档案」里维护,两套系统互相解耦、通过图号对齐。\n\n编码对应规则:本页的「图号」必须与 WMS 物料档案里的「图号」完全一致;「管理方式」也要对应(电气件=SN 逐件,结构件=批次)。编码对不上,WMS 将无法为对应工单备料/出库。\n\n保护规则:某份物料清单一旦被工单选用过,就不允许再从中移除物料(防止历史工单追溯断链);新建/改名的物料清单名称在本页「物料清单名称」下拉直接输入即可创建。\n\n数据链路:先建「物料档案」→ 到本页选产品编号 + 命名物料清单,逐条添加物料 → 建工单时选定物料清单 → 仓库才能为对应工单领料。',
|
||||
fields: [
|
||||
{ name: '物料清单名称', source: '下拉选择/输入(可搜索,输入新名即创建)。缺省「默认」。', purpose: '同一产品编号下多份物料清单的区分名,工单建单时按此选定', fill: '如「默认」「A厂料单」「含半成品」;同一型号下名称唯一' },
|
||||
{ name: '产品编号', source: '下拉选择。来自「物料档案」里建的编码。', purpose: '要为哪个产品配置物料清单', fill: '在查询区或「添加物料」弹窗里选择产品编号' },
|
||||
@@ -92,7 +92,7 @@ export const helpBom = {
|
||||
{ name: '物料名称', source: '手工填写。', purpose: '物料中文名,便于识别', fill: '如 十字螺丝、主控芯片' },
|
||||
{ name: '规格', source: '手工填写。', purpose: '物料规格型号', fill: '如 M16×80 / SMT-48脚' },
|
||||
{ name: '单位', source: '手工填写。', purpose: '计量单位,用于算量', fill: '个 / 件 / 套 / kg' },
|
||||
{ name: '管理方式', source: '单选。按物料类型决定,与 WMS 一致。', purpose: '电气件=逐件按SN管理;结构件=按批次管理', fill: '电子件/电气件选"电气件 SN";螺丝等标准件选"结构件 批次"' },
|
||||
{ name: '管理方式', source: '单选。按物料类型决定,与 WMS 一致。', purpose: '电气件=逐件按SN管理;结构件=按批次管理', fill: '电气件/电子件选"序列号";螺丝等标准件选"批次"' },
|
||||
{ name: '单台用量', source: '手工填写。', purpose: '生产 1 台产品用的该物料数量', fill: '如每台 8 颗螺丝 → 填 8' },
|
||||
{ name: '损耗率%', source: '手工填写。', purpose: '考虑报废/损耗的加放比例', fill: '如 2% 填 2;0=不计算损耗' },
|
||||
{ name: '需求量', source: '自动计算 = 数量×单台用量×(1+损耗率),可覆盖。', purpose: '该物料总需求,出库不能超它', fill: '一般自动算,不必改' },
|
||||
@@ -313,7 +313,7 @@ export const helpEventLog = {
|
||||
export const helpProcessFlow = {
|
||||
title: '工艺(作业指导,含工艺步骤与物料清单)',
|
||||
overview:
|
||||
'页面分「工艺列表」「工艺配置」两个页签:列表查看全部工艺并做启用/停用/删除;配置页维护工艺基本信息、工艺步骤与物料清单(新增与编辑共用,不再弹窗套弹窗)。\n\n工艺 = 一份「菜谱」:只描述某个产品该怎么装配(工艺步骤名、采集方式、考核标准、所需物料),可被多个工位绑定,不隶属某个工位。\n\n概念统一说明:\n· 工位:产线上的物理工位(工位数以数据库为准),工位号即顺序号,相当于「灶台」。\n· 工艺:一份作业指导(PDF + 工艺步骤 + 物料清单);其启用状态决定是否参与生产。\n· 工艺步骤:工艺内包含多个步骤(如:装配完成、拧紧扭矩、测试)。\n· 物料清单:该工艺装配所需物料(编码/名称/规格/单位/管控方式/单台用量/损耗率),整表覆盖保存。\n\n绑定关系在「工位绑定」页维护:为某工位选择启用的工艺即绑定;一个工位只能绑定一个工艺。工单在「工单管理」勾选「工艺组合」确定执行路线(工位号升序)。\n\n数据链路:本页维护工艺+工艺步骤+物料清单+考核标准 → 工位绑定页绑定 → 工单勾选工艺组合 → 工位终端按工艺步骤采集 → 报工按考核标准自动判 合格/不合格 → 工件追溯/绩效报表可见。',
|
||||
'页面分「工艺列表」「工艺配置」两个页签:列表查看全部工艺并做启用/停用/删除;配置页维护工艺基本信息、工艺步骤与物料清单(新增与编辑共用,不再弹窗套弹窗)。\n\n工艺 = 一份「菜谱」:只描述某个产品该怎么装配(工艺步骤名、采集方式、考核标准、所需物料),可被多个工位绑定,不隶属某个工位。\n\n概念统一说明:\n· 工位:产线上的物理工位(工位数以数据库为准),工位号即顺序号,相当于「灶台」。\n· 工艺:一份作业指导(PDF + 工艺步骤 + 物料清单);其启用状态决定是否参与生产。\n· 工艺步骤:工艺内包含多个步骤(如:装配完成、拧紧扭矩、测试)。\n· 物料清单:该工艺装配所需物料(物料编号/名称/规格/单位/管控方式/单台用量/损耗率),整表覆盖保存。\n\n绑定关系在「工位绑定」页维护:为某工位选择启用的工艺即绑定;一个工位只能绑定一个工艺。工单在「工单管理」勾选「工艺组合」确定执行路线(工位号升序)。\n\n数据链路:本页维护工艺+工艺步骤+物料清单+考核标准 → 工位绑定页绑定 → 工单勾选工艺组合 → 工位终端按工艺步骤采集 → 报工按考核标准自动判 合格/不合格 → 工件追溯/绩效报表可见。',
|
||||
sections: [
|
||||
{
|
||||
title: '工艺基本信息',
|
||||
@@ -324,7 +324,7 @@ export const helpProcessFlow = {
|
||||
{ name: '作业指导PDF', source: '本页上传。文件由 MES 服务器保存。', purpose: '该工艺的作业指导图,工位终端打开预览', fill: '选择 PDF 上传,成功后显示文件名可预览' },
|
||||
{ name: '工艺步骤', source: '本页维护(步骤名/采集方式/是否拧紧/考核标准)。', purpose: '工位终端报工时按此动态渲染参数输入', fill: '一个工艺可含多个工艺步骤,按序号顺序执行' },
|
||||
{ name: '步骤附件/图纸', source: '每个工艺步骤单独上传(绑定式)。文件由 MES 服务器保存。', purpose: '把图纸精确绑定到具体步骤,工位终端按当前步骤自动展示对应图纸', fill: '在步骤行上传该步骤图纸/附件(可多个);随步骤自动分发到工位终端' },
|
||||
{ name: '物料清单', source: '本页维护:物料编码/名称/规格/单位/管控方式/单台用量/损耗率%,整表覆盖保存。', purpose: '该工艺装配所需物料;备料需求 = 各工位分产量 × 单台用量 × (1+损耗率%)', fill: '物料编码/名称/规格/单位可手填,须属于产品物料清单范围,工单保存时校验;管控方式:批次·结构件=1、序列号·电气件=2' }
|
||||
{ name: '物料清单', source: '本页维护:物料编号(下拉选择,取自物料档案)/名称/规格/单位/管控方式/单台用量/损耗率%,整表覆盖保存。', purpose: '该工艺装配所需物料;备料需求 = 各工位分产量 × 单台用量 × (1+损耗率%)', fill: '物料编号取自物料档案编码(结构件通常即图号),选中后名称/规格/单位/管控方式自动带出、不可改;管控方式:批次=1、序列号=2' }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<el-dialog v-model="dialog.show" :title="dialog.id ? '编辑账号' : '新增账号'" width="520px" append-to-body destroy-on-close>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="用户名" required>
|
||||
<el-input v-model="dialog.username" :disabled="lockedName(dialog.username)" placeholder="登录名(创建后不可改)" />
|
||||
<el-input v-model="dialog.username" :disabled="dialog.id > 0 && lockedName(dialog.username)" placeholder="登录名(创建后不可改)" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="dialog.id ? '重置密码' : '初始密码'" required>
|
||||
<el-input v-model="dialog.password" type="password" show-password
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<el-table-column prop="spec" label="规格" />
|
||||
<el-table-column prop="unit" label="单位" width="70" />
|
||||
<el-table-column prop="manageMode" label="管理方式" width="110">
|
||||
<template #default="{row}">{{ row.manageMode === '1' ? '结构件(批次)' : '电气件(SN)' }}</template>
|
||||
<template #default="{row}">{{ row.manageMode === '1' ? '批次' : '序列号' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="unitQty" label="单台用量" width="90" />
|
||||
<el-table-column prop="lossRate" label="损耗率%" width="90" />
|
||||
@@ -66,8 +66,8 @@
|
||||
<el-form-item label="单位"><el-input v-model="add.unit" /></el-form-item>
|
||||
<el-form-item label="管理方式">
|
||||
<el-radio-group v-model="add.manageMode">
|
||||
<el-radio label="2">电气件(SN)</el-radio>
|
||||
<el-radio label="1">结构件(批次)</el-radio>
|
||||
<el-radio label="2">序列号</el-radio>
|
||||
<el-radio label="1">批次</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="单台用量"><el-input-number v-model="add.unitQty" :min="0" /></el-form-item>
|
||||
|
||||
@@ -131,7 +131,7 @@ const query = reactive({ orderNo: '', planDate: '' })
|
||||
const list = ref([])
|
||||
const workOrders = ref([])
|
||||
const productTypes = ref([])
|
||||
const dialog = reactive({ show: false, id: 0, orderNo: '', planDate: '', planRange: [], mode: 'AVG', planQty: 1, productType: '', productCode: '', productName: '', woQty: '', stationPlanQty: {}, combination: [] })
|
||||
const dialog = reactive({ show: false, id: 0, orderNo: '', planDate: '', planRange: [], mode: 'AVG', planQty: 1, productType: '', productCode: '', productName: '', woQty: '', woStart: '', woDue: '', stationPlanQty: {}, combination: [] })
|
||||
|
||||
// 工位号→接驳台编码、工位号→名称(送达接驳台由工单工艺组合派生)
|
||||
const stationDockMap = ref({})
|
||||
@@ -210,6 +210,7 @@ function loadWorkOrderInfo() {
|
||||
if (!wo) {
|
||||
dialog.productType = ''; dialog.productCode = ''; dialog.productName = ''; dialog.woQty = ''
|
||||
dialog.combination = []
|
||||
dialog.woStart = ''; dialog.woDue = ''
|
||||
return
|
||||
}
|
||||
dialog.productCode = wo.productCode || ''
|
||||
@@ -218,9 +219,20 @@ function loadWorkOrderInfo() {
|
||||
dialog.combination = Array.isArray(wo.flowCombination) ? wo.flowCombination : []
|
||||
const pt = productTypes.value.find((p) => p.id === wo.productTypeId)
|
||||
dialog.productType = pt ? (pt.name + (pt.code ? ' / ' + pt.code : '')) : ''
|
||||
// 记录工单起止日期(planStart/dueDate),用于日期预填与超期提示
|
||||
dialog.woStart = wo.planStart ? String(wo.planStart).slice(0, 10) : ''
|
||||
dialog.woDue = wo.dueDate ? String(wo.dueDate).slice(0, 10) : ''
|
||||
// 新建排产:日期范围默认取所选工单的开始/结束日期(可修改)
|
||||
if (!dialog.id && (dialog.woStart || dialog.woDue)) {
|
||||
dialog.planRange = [dialog.woStart || dialog.woDue, dialog.woDue || dialog.woStart]
|
||||
}
|
||||
}
|
||||
function onOrderChange() {
|
||||
loadWorkOrderInfo()
|
||||
// 编辑弹窗切换工单:排产日期默认取该工单开始日期(无则取结束日期,可修改)
|
||||
if (dialog.id && (dialog.woStart || dialog.woDue)) {
|
||||
dialog.planDate = dialog.woStart || dialog.woDue
|
||||
}
|
||||
}
|
||||
function openAdd() {
|
||||
const today = new Date().toISOString().slice(0, 10)
|
||||
@@ -242,6 +254,15 @@ function openEdit(row) {
|
||||
: dialog.combination
|
||||
loadAlloc()
|
||||
}
|
||||
// 所选日期超出工单执行期间(planStart~dueDate)时提示,但不阻断提交
|
||||
function warnOutOfWindow(dates) {
|
||||
const s = dialog.woStart || ''
|
||||
const e = dialog.woDue || ''
|
||||
if ((!s && !e) || !dialog.orderNo) return
|
||||
if (dates.some((d) => (s && d < s) || (e && d > e))) {
|
||||
ElMessage.warning('所选日期超出工单执行期间')
|
||||
}
|
||||
}
|
||||
async function save() {
|
||||
if (!dialog.orderNo) return ElMessage.warning('请选择工单号')
|
||||
if (dialog.id) {
|
||||
@@ -251,6 +272,7 @@ async function save() {
|
||||
if (alloc[st]) spq[String(st)] = Number(alloc[st]) || 0
|
||||
})
|
||||
if (!dialog.planDate) return ElMessage.warning('请选择排产日期')
|
||||
warnOutOfWindow([dialog.planDate])
|
||||
await request.put(`/daily-plans/${dialog.id}`, {
|
||||
id: dialog.id, orderNo: dialog.orderNo, planDate: dialog.planDate, planQty: dialog.planQty, stationPlanQty: spq
|
||||
})
|
||||
@@ -259,6 +281,7 @@ async function save() {
|
||||
if (!dialog.planRange || dialog.planRange.length !== 2 || !dialog.planRange[0] || !dialog.planRange[1])
|
||||
return ElMessage.warning('请选择排产日期范围')
|
||||
if (!dialog.planQty || dialog.planQty < 1) return ElMessage.warning('请填写数量')
|
||||
warnOutOfWindow([dialog.planRange[0], dialog.planRange[1]])
|
||||
await request.post('/daily-plans', {
|
||||
orderNo: dialog.orderNo, planRange: dialog.planRange, mode: dialog.mode, planQty: dialog.planQty
|
||||
})
|
||||
@@ -292,9 +315,7 @@ async function toggleStatus(row) {
|
||||
const target = row.status === 'CONFIRMED' ? 'PENDING' : 'CONFIRMED'
|
||||
const label = target === 'CONFIRMED' ? '启用' : '停用'
|
||||
await ElMessageBox.confirm(`确认${label}该排产(${row.orderNo} ${row.planDate})?`, '提示', { type: 'warning' })
|
||||
await request.put(`/daily-plans/${row.id}`, {
|
||||
orderNo: row.orderNo, planDate: row.planDate, planQty: row.planQty, status: target
|
||||
})
|
||||
await request.post(`/daily-plans/${row.id}/status`, { status: target })
|
||||
ElMessage.success(`已${label}`)
|
||||
load()
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<el-table-column prop="materialName" label="物料名称" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="unit" label="单位" width="70" />
|
||||
<el-table-column prop="manageMode" label="管理方式" width="110">
|
||||
<template #default="{row}">{{ row.manageMode === '1' ? '结构件(批次)' : '电气件(SN)' }}</template>
|
||||
<template #default="{row}">{{ row.manageMode === '1' ? '批次' : '序列号' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="stationNo" label="工位" width="70" align="center" />
|
||||
<el-table-column prop="targetDock" label="接驳台" width="100" align="center" />
|
||||
|
||||
@@ -161,53 +161,62 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 物料清单子表(整表覆盖保存) -->
|
||||
<!-- 物料清单子表(整表覆盖保存;物料编号下拉选择,快照信息取自物料档案) -->
|
||||
<el-form-item label="物料清单">
|
||||
<div style="width:100%">
|
||||
<el-alert v-if="!flowDlg.id" type="info" :closable="false" style="margin-bottom:8px"
|
||||
title="新工艺请先保存,保存后即可维护物料清单。" />
|
||||
<el-table v-else :data="flowDlg.materials" border size="small">
|
||||
<el-table-column label="物料编码" min-width="130">
|
||||
<template #default="{ row }"><el-input v-model="row.materialCode" placeholder="手填" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="物料名称" min-width="120">
|
||||
<template #default="{ row }"><el-input v-model="row.materialName" placeholder="手填" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格" min-width="110">
|
||||
<template #default="{ row }"><el-input v-model="row.spec" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" width="80">
|
||||
<template #default="{ row }"><el-input v-model="row.unit" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="管控方式" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.manageMode" placeholder="请选择">
|
||||
<el-option value="1" label="批次·结构件" />
|
||||
<el-option value="2" label="序列号·电气件" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单台用量" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.unitQty" :min="0" size="small" controls-position="right" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="损耗率%" width="110">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.lossRate" :min="0" size="small" controls-position="right" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="70" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="danger" size="small" @click="removeMaterial(row)">删</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="display:flex;gap:10px;margin-top:10px;align-items:center">
|
||||
<el-button v-if="flowDlg.id" size="small" @click="addMaterial">新增物料行</el-button>
|
||||
<el-button v-if="flowDlg.id" size="small" type="primary" :loading="matSaving" @click="saveMaterials">保存物料清单</el-button>
|
||||
<span style="color:#909399;font-size:12px">物料编码/名称/规格/单位手填,须属于产品物料清单范围,工单保存时校验;备料需求 = 各工位分产量 × 单台用量 ×(1+损耗率%)。</span>
|
||||
</div>
|
||||
<template v-else>
|
||||
<el-alert v-if="materialsUnavailable" type="warning" :closable="false" style="margin-bottom:8px"
|
||||
title="物料档案服务不可用,清单暂只读:不可新增物料行,已有行仅可调整单台用量/损耗率。" />
|
||||
<el-table :data="flowDlg.materials" border size="small">
|
||||
<el-table-column label="物料编号" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.materialCode" filterable placeholder="请选择物料编号" style="width:100%"
|
||||
@change="(code) => onMaterialPicked(row, code)">
|
||||
<el-option v-for="m in materialOptions" :key="m.code" :label="m.code" :value="m.code" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="物料名称" min-width="120">
|
||||
<template #default="{ row }"><el-input v-model="row.materialName" disabled /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格" min-width="110">
|
||||
<template #default="{ row }"><el-input v-model="row.spec" disabled /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" width="80">
|
||||
<template #default="{ row }"><el-input v-model="row.unit" disabled /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="管控方式" width="110">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.manageMode" disabled>
|
||||
<el-option value="1" label="批次" />
|
||||
<el-option value="2" label="序列号" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单台用量" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.unitQty" :min="0" size="small" controls-position="right" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="损耗率%" width="110">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.lossRate" :min="0" size="small" controls-position="right" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="70" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="danger" size="small" @click="removeMaterial(row)">删</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="display:flex;gap:10px;margin-top:10px;align-items:center">
|
||||
<el-button v-if="!materialsUnavailable" size="small" @click="addMaterial">新增物料行</el-button>
|
||||
<el-button size="small" type="primary" :loading="matSaving" @click="saveMaterials">保存物料清单</el-button>
|
||||
<span style="color:#909399;font-size:12px">物料编号取自物料档案编码(结构件通常即图号),选中后名称/规格/单位/管控方式自动带出、不可改;备料需求 = 各工位分产量 × 单台用量 ×(1+损耗率%)。</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -369,6 +378,31 @@ function blankMaterial() {
|
||||
return { materialCode: '', materialName: '', spec: '', unit: '', manageMode: '2', unitQty: 1, lossRate: 0 }
|
||||
}
|
||||
|
||||
/* ---------- 物料档案(物料清单「物料编号」下拉数据源) ---------- */
|
||||
const materialOptions = ref([])
|
||||
const materialsUnavailable = ref(false)
|
||||
|
||||
async function loadMaterialArchive() {
|
||||
materialsUnavailable.value = false
|
||||
try {
|
||||
const list = (await request.get('/materials')) || []
|
||||
materialOptions.value = Array.isArray(list) ? list : []
|
||||
} catch {
|
||||
materialOptions.value = []
|
||||
}
|
||||
if (!materialOptions.value.length) materialsUnavailable.value = true
|
||||
}
|
||||
|
||||
// 选中物料编号后,快照字段(名称/规格/单位/管控方式)自动带出,均不可手改
|
||||
function onMaterialPicked(row, code) {
|
||||
const m = materialOptions.value.find((x) => x.code === code)
|
||||
if (!m) return
|
||||
row.materialName = m.name || ''
|
||||
row.spec = m.spec || ''
|
||||
row.unit = m.unit || ''
|
||||
if (m.manageMode != null && m.manageMode !== '') row.manageMode = String(m.manageMode)
|
||||
}
|
||||
|
||||
// 加载该工艺的物料清单(整表编辑,保存时整表覆盖)
|
||||
async function loadMaterials(flowId) {
|
||||
flowDlg.materials = []
|
||||
@@ -399,7 +433,7 @@ async function saveMaterials() {
|
||||
unitQty: Number(m.unitQty) || 0,
|
||||
lossRate: Number(m.lossRate) || 0
|
||||
}))
|
||||
if (items.some((m) => !m.materialCode)) return ElMessage.warning('存在未填写物料编码的行,请补全或删除')
|
||||
if (items.some((m) => !m.materialCode)) return ElMessage.warning('存在未选择物料编号的行,请补全或删除')
|
||||
matSaving.value = true
|
||||
try {
|
||||
await request.post(`/process-flows/${flowDlg.id}/materials`, { items })
|
||||
@@ -552,6 +586,7 @@ async function toggleStatus(row) {
|
||||
|
||||
onMounted(() => {
|
||||
loadFlows()
|
||||
loadMaterialArchive()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -82,6 +82,27 @@
|
||||
<p>批次号:{{ (data.associationTrace?.batchItems||[]).join(', ') || '无' }}</p>
|
||||
<p>电气件SN:{{ (data.associationTrace?.serialItems||[]).join(', ') || '无' }}</p>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="暂存退库/重上线">
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:10px"
|
||||
title="半成品流转记录:暂存退库=入 WMS 库、重上线=出库继续执行;「完成至」为流转时的进度快照(最后报工工位)。" />
|
||||
<el-table :data="data.semiFlows||[]" border size="small">
|
||||
<el-table-column label="动作" width="110" align="center">
|
||||
<template #default="{row}">
|
||||
<el-tag :type="row.action==='INBOUND' ? 'warning' : 'success'">{{ row.action==='INBOUND' ? '暂存退库' : '重上线' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="completedText" label="完成至工位" min-width="150">
|
||||
<template #default="{row}">{{ row.completedText || '尚未报工' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operator" label="操作人" width="110" />
|
||||
<el-table-column prop="targetDock" label="目标接驳台" width="110">
|
||||
<template #default="{row}">{{ row.targetDock || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="时间" min-width="165">
|
||||
<template #default="{row}">{{ fmt(row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="装机绑定">
|
||||
<el-alert :type="data.bindTrace?.complete ? 'success' : 'warning'" :closable="false" style="margin-bottom:10px"
|
||||
:title="data.bindTrace?.complete ? '装配物料已全部绑齐 ✓' : ('装配物料未绑齐:' + (data.bindTrace?.missing || ''))" show-icon />
|
||||
@@ -91,7 +112,7 @@
|
||||
<el-table-column prop="materialName" label="物料名称" min-width="110" />
|
||||
<el-table-column prop="spec" label="规格" min-width="90" />
|
||||
<el-table-column prop="bindType" label="类型" width="70">
|
||||
<template #default="{row}">{{ row.bindType === 'SN' ? '电气件SN' : '批次' }}</template>
|
||||
<template #default="{row}">{{ row.bindType === 'SN' ? '序列号' : '批次' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="bindValue" label="绑定值(SN/批次)" min-width="140" />
|
||||
<el-table-column prop="operator" label="绑定人" width="90" />
|
||||
@@ -109,7 +130,7 @@
|
||||
<el-table-column prop="spec" label="规格" min-width="90" />
|
||||
<el-table-column prop="qty" label="单台用量" width="100" />
|
||||
<el-table-column label="管理方式" width="100">
|
||||
<template #default="{row}">{{ row.manageMode === '2' ? '电气件SN' : '批次' }}</template>
|
||||
<template #default="{row}">{{ row.manageMode === '2' ? '序列号' : '批次' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
执行路线:{{ routeText }}
|
||||
<span class="combo-route-tip">(执行顺序=工位号升序,可跳过未选工位)</span>
|
||||
</div>
|
||||
<div class="combo-tip">默认每个已绑定工艺的工位全选;至少选一道工艺;全不选某工艺则该工艺不进入组合。</div>
|
||||
<div class="combo-tip">仅显示已绑定启用工位的工艺;默认每个工艺的启用工位全选,至少选一道工艺;全不选某工艺则该工艺不进入组合。</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -246,16 +246,13 @@ const flowGroups = computed(() => {
|
||||
flows.value.forEach((f) => {
|
||||
if (map.has(f.id) && f.name) map.get(f.id).flowName = f.name
|
||||
})
|
||||
const arr = Array.from(map.values())
|
||||
arr.sort((a, b) => {
|
||||
if (a.flowId === 0) return 1
|
||||
if (b.flowId === 0) return -1
|
||||
return (a.flowName || '').localeCompare(b.flowName || '', 'zh')
|
||||
})
|
||||
// 只显示「至少绑定了一个启用工位」的工艺;未绑定工位、或绑定的工位全部停用的工艺整组隐藏
|
||||
const arr = Array.from(map.values()).filter((g) => g.flowId > 0 && g.stations.some((s) => s.isActive))
|
||||
arr.sort((a, b) => (a.flowName || '').localeCompare(b.flowName || '', 'zh'))
|
||||
return arr.map((g) => ({
|
||||
key: g.flowId,
|
||||
flowId: g.flowId,
|
||||
title: g.flowId ? (g.flowName || `工艺${g.flowId}`) : '未绑定工艺',
|
||||
title: g.flowName || `工艺${g.flowId}`,
|
||||
stations: [...g.stations].sort((a, b) => (a.stationNo || 0) - (b.stationNo || 0))
|
||||
}))
|
||||
})
|
||||
@@ -272,7 +269,12 @@ const routeText = computed(() => flowCombination.value.map((c) => c.stationNo).j
|
||||
const hasCombination = (row) => Array.isArray(row.flowCombination) && row.flowCombination.length > 0
|
||||
|
||||
function defaultSelStations() {
|
||||
return stations.value.filter((s) => s.flowId > 0).map((s) => s.stationNo).sort((a, b) => a - b)
|
||||
// 默认全选「可见工艺组」内的启用工位(被隐藏工艺组下的工位不参与组合)
|
||||
const visible = new Set(flowGroups.value.map((g) => g.flowId))
|
||||
return stations.value
|
||||
.filter((s) => s.flowId > 0 && visible.has(s.flowId) && s.isActive)
|
||||
.map((s) => s.stationNo)
|
||||
.sort((a, b) => a - b)
|
||||
}
|
||||
|
||||
async function loadComboData() {
|
||||
|
||||
Reference in New Issue
Block a user