refactor: 重构产线工位与备料流程,移除冗余字段
1. 移除工单、工件、日排产中的冗余工位组合/接驳台字段 2. 新增工位接驳台编码字段与唯一约束,关联WMS接驳台主数据 3. 重构日排产为工位产量分配模式,替代原接驳台列表 4. 新增备料单工位级字段与分批出库支持 5. 移除定时同步可生产数量,改为WMS实时拉取接口 6. 过滤操作日志,排除登录/退出相关日志 7. 调整仪表盘工序展示逻辑为今日派工路线 8. 新增接驳台维护菜单与基础数据
This commit is contained in:
@@ -20,10 +20,10 @@
|
||||
<el-table-column prop="planDate" label="排产日期" width="120" />
|
||||
<el-table-column prop="planQty" label="计划数量" width="100" />
|
||||
<el-table-column prop="completedQty" label="已完成" width="90" />
|
||||
<el-table-column label="送达接驳台" min-width="150">
|
||||
<el-table-column label="送达接驳台(派生)" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-for="d in (row.dockCodes||[])" :key="d" size="small" class="dock-tag">{{ d }}</el-tag>
|
||||
<span v-if="!(row.dockCodes||[]).length" class="dock-empty">未指定</span>
|
||||
<el-tag v-for="d in (dockByDate[row.planDate] || [])" :key="d" size="small" class="dock-tag">{{ d }}</el-tag>
|
||||
<span v-if="!(dockByDate[row.planDate] || []).length" class="dock-empty">未派工</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="执行状态" width="110">
|
||||
@@ -45,7 +45,7 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog v-model="dialog.show" :title="dialog.id ? '编辑排产' : '新建排产'" width="560px">
|
||||
<el-dialog v-model="dialog.show" :title="dialog.id ? '编辑排产' : '新建排产'" width="600px">
|
||||
<el-form :model="dialog" label-width="92px">
|
||||
<el-form-item label="工单号">
|
||||
<el-select v-model="dialog.orderNo" filterable allow-create placeholder="请选择/输入工单号" style="width:100%" @change="onOrderChange">
|
||||
@@ -75,28 +75,29 @@
|
||||
</el-form-item>
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:12px"
|
||||
:title="`共 ${previewDates.length} 天:${previewDates.join('、') || '-'}`" />
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:12px"
|
||||
title="送达接驳台与工位产量分配由「工位派工」按排产日自动派生,此处无需手填;如需调整工位产量,启用后点击「编辑」设置。" />
|
||||
</template>
|
||||
<!-- 编辑:单日 -->
|
||||
<template v-else>
|
||||
<el-form-item label="排产日期">
|
||||
<el-date-picker v-model="dialog.planDate" type="date" value-format="YYYY-MM-DD" style="width:100%" />
|
||||
<el-date-picker v-model="dialog.planDate" type="date" value-format="YYYY-MM-DD" style="width:100%" @change="loadAlloc" />
|
||||
</el-form-item>
|
||||
<el-form-item label="计划数量">
|
||||
<el-input-number v-model="dialog.planQty" :min="1" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工位产量分配">
|
||||
<div v-if="dispatchStations.length" style="width:100%">
|
||||
<div v-for="st in dispatchStations" :key="st" style="display:flex;gap:8px;align-items:center;margin-bottom:6px">
|
||||
<span style="width:150px">工位{{ st }}<template v-if="stationNameMap[st]">({{ stationNameMap[st] }})</template></span>
|
||||
<el-input-number v-model="alloc[st]" :min="0" :max="dialog.planQty" controls-position="right" style="width:150px" />
|
||||
<span style="color:#909399;font-size:12px">件</span>
|
||||
</div>
|
||||
<div style="font-size:12px;color:#909399">已分配合计:{{ allocTotal }} / 计划 {{ dialog.planQty }}(建议相等;留空或 0 的工位不分担产量)</div>
|
||||
</div>
|
||||
<el-alert v-else type="info" :closable="false" title="该排产日暂无工位派工,无法分配工位产量;请先在「工位」页完成当日工位↔工序派工。" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-form-item label="送达接驳台" prop="dockCodes">
|
||||
<el-select v-model="dialog.dockCodes" multiple filterable clearable placeholder="该工单物料送达哪些接驳台(可多选)" style="width:100%">
|
||||
<el-option-group label="产线接驳台(DOCK n↔工位n)">
|
||||
<el-option v-for="n in 20" :key="n" :label="`DOCK${String(n).padStart(2,'0')} · 工位${n}`" :value="`DOCK${String(n).padStart(2,'0')}`" />
|
||||
</el-option-group>
|
||||
<el-option-group label="库房">
|
||||
<el-option label="DOCK21 · 库房收货" value="DOCK21" />
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
<div class="dock-tip">选需送达的接驳台;建议为当前工单上次使用的接驳台</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialog.show=false">取消</el-button>
|
||||
@@ -114,7 +115,6 @@
|
||||
<style scoped>
|
||||
.dock-tag { margin-right: 4px; }
|
||||
.dock-empty { color: #999; font-size: 12px; }
|
||||
.dock-tip { width: 100%; font-size: 12px; color: #888780; line-height: 1.5; }
|
||||
</style>
|
||||
|
||||
<script setup>
|
||||
@@ -130,7 +130,17 @@ 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, dockCodes: [], productType: '', productCode: '', productName: '', woQty: '' })
|
||||
const dialog = reactive({ show: false, id: 0, orderNo: '', planDate: '', planRange: [], mode: 'AVG', planQty: 1, productType: '', productCode: '', productName: '', woQty: '', stationPlanQty: {} })
|
||||
|
||||
// 工位派工派生:工位号→接驳台编码、工位号→名称
|
||||
const stationDockMap = ref({})
|
||||
const stationNameMap = ref({})
|
||||
// 排产日→送达接驳台列表(派生展示)
|
||||
const dockByDate = ref({})
|
||||
// 编辑时工位产量分配
|
||||
const dispatchStations = ref([])
|
||||
const alloc = reactive({})
|
||||
const allocTotal = computed(() => Object.values(alloc).reduce((a, b) => a + (Number(b) || 0), 0))
|
||||
|
||||
// 新建排产「日期范围 + 模式」预览:展开为去重升序日期列表(仅前端提示,后端以 planRange 为准)
|
||||
const previewDates = computed(() => {
|
||||
@@ -147,7 +157,6 @@ const previewDates = computed(() => {
|
||||
return out
|
||||
})
|
||||
|
||||
const dockOptions = [...Array.from({ length: 20 }, (_, i) => `DOCK${String(i + 1).padStart(2, '0')}`), 'DOCK21']
|
||||
const statusOptions = [
|
||||
{ v: 'PENDING', l: '未启用' }, { v: 'CONFIRMED', l: '已启用' },
|
||||
{ v: 'PROCESSING', l: '执行中' }, { v: 'DONE', l: '已完成' }, { v: 'CANCELLED', l: '已取消' }
|
||||
@@ -156,16 +165,53 @@ const statusText = (s) => (statusOptions.find((x) => x.v === s) || {}).l || s
|
||||
const planStatusTag = (s) =>
|
||||
s === 'DONE' ? 'success' : s === 'PROCESSING' ? 'warning' : s === 'CONFIRMED' ? 'primary' : 'info'
|
||||
|
||||
async function loadStations() {
|
||||
const sts = (await request.get('/stations')) || []
|
||||
const dm = {}
|
||||
const nm = {}
|
||||
sts.forEach((s) => { dm[s.stationNo] = s.dockCode || ''; nm[s.stationNo] = s.name })
|
||||
stationDockMap.value = dm
|
||||
stationNameMap.value = nm
|
||||
}
|
||||
|
||||
async function load() {
|
||||
list.value = (await request.get('/daily-plans', { params: query })) || []
|
||||
await refreshDocks()
|
||||
}
|
||||
// 推荐:该工单上次使用的接驳台
|
||||
async function loadSuggest() {
|
||||
if (!dialog.orderNo) return
|
||||
try {
|
||||
const d = await request.get('/daily-plans/suggest-docks', { params: { orderNo: dialog.orderNo } })
|
||||
dialog.dockCodes = d?.dockCodes || []
|
||||
} catch { dialog.dockCodes = [] }
|
||||
// 根据各排产日的工位派工路线,派生送达接驳台(展示用,来源于 station_process + station.dockCode)
|
||||
async function refreshDocks() {
|
||||
const dates = [...new Set(list.value.map((r) => r.planDate))]
|
||||
const out = {}
|
||||
for (const d of dates) {
|
||||
const r = await request.get('/station-process/route', { params: { effectDate: d, shift: '' } })
|
||||
const stations = (r && r.stations) || []
|
||||
const seen = {}
|
||||
const ds = []
|
||||
stations.forEach((n) => {
|
||||
const dc = stationDockMap.value[n]
|
||||
if (dc && !seen[dc]) { seen[dc] = true; ds.push(dc) }
|
||||
})
|
||||
out[d] = ds
|
||||
}
|
||||
dockByDate.value = out
|
||||
}
|
||||
// 加载某排产日的工位派工,初始化工位产量分配
|
||||
async function loadAlloc(date) {
|
||||
const r = await request.get('/station-process/route', { params: { effectDate: date, shift: '' } })
|
||||
const stations = (r && r.stations) || []
|
||||
dispatchStations.value = stations
|
||||
const init = {}
|
||||
stations.forEach((st) => { init[st] = 0 })
|
||||
const spq = dialog.stationPlanQty || {}
|
||||
const hasAny = stations.some((st) => spq[String(st)] != null)
|
||||
if (hasAny) {
|
||||
stations.forEach((st) => { if (spq[String(st)] != null) init[st] = spq[String(st)] })
|
||||
} else if (stations.length) {
|
||||
const base = Math.floor(dialog.planQty / stations.length)
|
||||
stations.forEach((st, i) => { init[st] = base + (i < dialog.planQty % stations.length ? 1 : 0) })
|
||||
}
|
||||
Object.keys(alloc).forEach((k) => { delete alloc[k] })
|
||||
Object.assign(alloc, init)
|
||||
}
|
||||
// 选工单号自动带出其产品信息(只读展示,避免手填错位)
|
||||
function loadWorkOrderInfo() {
|
||||
@@ -181,32 +227,35 @@ function loadWorkOrderInfo() {
|
||||
dialog.productType = pt ? (pt.name + (pt.code ? ' / ' + pt.code : '')) : ''
|
||||
}
|
||||
function onOrderChange() {
|
||||
loadSuggest()
|
||||
loadWorkOrderInfo()
|
||||
}
|
||||
function openAdd() {
|
||||
const today = new Date().toISOString().slice(0, 10)
|
||||
Object.assign(dialog, {
|
||||
show: true, id: 0, orderNo: '', planDate: '', planRange: [today, today], mode: 'AVG',
|
||||
planQty: 1, dockCodes: [], productType: '', productCode: '', productName: '', woQty: ''
|
||||
planQty: 1, productType: '', productCode: '', productName: '', woQty: '', stationPlanQty: {}
|
||||
})
|
||||
}
|
||||
function openEdit(row) {
|
||||
const spq = row.stationPlanQty || {}
|
||||
Object.assign(dialog, {
|
||||
show: true, id: row.id, orderNo: row.orderNo, planDate: row.planDate, planRange: [],
|
||||
mode: 'AVG', planQty: row.planQty, dockCodes: row.dockCodes || [],
|
||||
productType: '', productCode: '', productName: '', woQty: ''
|
||||
mode: 'AVG', planQty: row.planQty, productType: '', productCode: '', productName: '', woQty: '', stationPlanQty: spq
|
||||
})
|
||||
loadWorkOrderInfo()
|
||||
loadAlloc(row.planDate)
|
||||
}
|
||||
async function save() {
|
||||
if (!dialog.orderNo) return ElMessage.warning('请选择工单号')
|
||||
if (!dialog.dockCodes.length) return ElMessage.warning('请确认送达接驳台(AGV 配送目标)')
|
||||
if (dialog.id) {
|
||||
// 编辑单条日排产
|
||||
// 编辑单条日排产:工位产量分配按派工工位拆分
|
||||
const spq = {}
|
||||
dispatchStations.value.forEach((st) => {
|
||||
if (alloc[st]) spq[String(st)] = Number(alloc[st]) || 0
|
||||
})
|
||||
if (!dialog.planDate) return ElMessage.warning('请选择排产日期')
|
||||
await request.put(`/daily-plans/${dialog.id}`, {
|
||||
id: dialog.id, orderNo: dialog.orderNo, planDate: dialog.planDate, planQty: dialog.planQty, dockCodes: dialog.dockCodes
|
||||
id: dialog.id, orderNo: dialog.orderNo, planDate: dialog.planDate, planQty: dialog.planQty, stationPlanQty: spq
|
||||
})
|
||||
} else {
|
||||
// 新建批量:按日期范围 + 模式生成多天日排产
|
||||
@@ -214,8 +263,7 @@ async function save() {
|
||||
return ElMessage.warning('请选择排产日期范围')
|
||||
if (!dialog.planQty || dialog.planQty < 1) return ElMessage.warning('请填写数量')
|
||||
await request.post('/daily-plans', {
|
||||
orderNo: dialog.orderNo, planRange: dialog.planRange, mode: dialog.mode,
|
||||
planQty: dialog.planQty, dockCodes: dialog.dockCodes
|
||||
orderNo: dialog.orderNo, planRange: dialog.planRange, mode: dialog.mode, planQty: dialog.planQty
|
||||
})
|
||||
}
|
||||
ElMessage.success('保存成功')
|
||||
@@ -231,7 +279,6 @@ function openDetail(row) {
|
||||
{ key: 'planDate', label: '排产日期' },
|
||||
{ key: 'planQty', label: '计划数量' },
|
||||
{ key: 'completedQty', label: '已完成' },
|
||||
{ key: 'dockCodes', label: '送达接驳台', format: (v) => (v && v.length ? v.join('、') : '未指定') },
|
||||
{ key: 'status', label: '执行状态', type: 'enum', map: statusOptions.reduce((m, x) => { m[x.v] = x.l; return m }, {}) },
|
||||
{ key: 'operator', label: '操作人' }
|
||||
]
|
||||
@@ -249,8 +296,7 @@ async function toggleStatus(row) {
|
||||
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,
|
||||
dockCodes: row.dockCodes || [], status: target
|
||||
orderNo: row.orderNo, planDate: row.planDate, planQty: row.planQty, status: target
|
||||
})
|
||||
ElMessage.success(`已${label}`)
|
||||
load()
|
||||
@@ -258,6 +304,7 @@ async function toggleStatus(row) {
|
||||
onMounted(async () => {
|
||||
workOrders.value = (await request.get('/work-orders')) || []
|
||||
productTypes.value = (await request.get('/product-types')) || []
|
||||
await loadStations()
|
||||
load()
|
||||
})
|
||||
</script>
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user