|
|
@@ -1,60 +1,138 @@
|
|
|
<template>
|
|
|
<div v-loading="loading">
|
|
|
- <el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
|
|
|
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
|
|
<el-row :gutter="12">
|
|
|
- <el-col :span="12"><el-form-item label="采购单号"><el-input v-model="form.purOrd" disabled /></el-form-item></el-col>
|
|
|
- <el-col :span="12"><el-form-item label="行号"><el-input v-model="form.line" disabled /></el-form-item></el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="采购订单号"><el-input v-model="form.purOrd" disabled /></el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="项次"><el-input v-model="form.line" disabled /></el-form-item>
|
|
|
+ </el-col>
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="物料编号" prop="itemNum">
|
|
|
<div class="pick-wrap">
|
|
|
- <el-input v-model="form.itemNum" :disabled="isView" />
|
|
|
- <el-button :disabled="isView" @click="openPick('main')">选择</el-button>
|
|
|
+ <el-input v-model="form.itemNum" disabled />
|
|
|
+ <el-button v-if="!isView" @click="openPick('main')">选择</el-button>
|
|
|
</div>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
- <el-col :span="12"><el-form-item label="批号"><el-input v-model="form.lotSerial" disabled /></el-form-item></el-col>
|
|
|
- <el-col :span="12"><el-form-item label="版本"><el-input v-model="form.rev" disabled /></el-form-item></el-col>
|
|
|
- <el-col :span="12"><el-form-item label="图纸"><el-input v-model="form.drawing" disabled /></el-form-item></el-col>
|
|
|
- <el-col :span="12"><el-form-item label="单位"><el-input v-model="form.um" disabled /></el-form-item></el-col>
|
|
|
- <el-col :span="12"><el-form-item label="库位"><el-input v-model="form.location" disabled /></el-form-item></el-col>
|
|
|
- <el-col :span="12"><el-form-item label="订单数量" prop="qtyOrded"><el-input-number v-model="form.qtyOrded" :disabled="isView" :min="0" :precision="5" :controls="false" style="width: 100%" /></el-form-item></el-col>
|
|
|
- <el-col :span="12"><el-form-item label="收货数量"><el-input v-model="form.qtyReceived" disabled /></el-form-item></el-col>
|
|
|
- <el-col :span="12"><el-form-item label="下达数量"><el-input v-model="form.qtyReleased" disabled /></el-form-item></el-col>
|
|
|
- <el-col :span="12"><el-form-item label="交货日期"><el-date-picker v-model="form.dueDate" type="date" value-format="YYYY-MM-DD" :disabled="isView" style="width: 100%" /></el-form-item></el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="生产批次"><el-input v-model="form.lotSerial" disabled /></el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="版本"><el-input v-model="form.rev" disabled /></el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="图纸"><el-input v-model="form.drawing" disabled /></el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="单位"><el-input v-model="form.um" disabled /></el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="库位"><el-input v-model="form.location" disabled /></el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="订单数量" prop="qtyOrded">
|
|
|
+ <el-input-number
|
|
|
+ v-model="form.qtyOrded"
|
|
|
+ :disabled="isView"
|
|
|
+ :min="0"
|
|
|
+ :precision="5"
|
|
|
+ :controls="false"
|
|
|
+ style="width: 100%"
|
|
|
+ @change="syncAllBatchQty"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="收货数量"><el-input v-model="form.qtyReceived" disabled /></el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="暂收数量"><el-input v-model="form.qtyReleased" disabled /></el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="交货日期">
|
|
|
+ <el-date-picker v-model="form.dueDate" type="date" value-format="YYYY-MM-DD" :disabled="isView" style="width: 100%" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
</el-row>
|
|
|
</el-form>
|
|
|
|
|
|
<div class="sub-header">
|
|
|
- <span>批次明细</span>
|
|
|
+ <span>组件明细</span>
|
|
|
<div v-if="!isView">
|
|
|
<el-button type="primary" link @click="addBatch">添加</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<el-table :data="form.batches" border>
|
|
|
- <el-table-column prop="batch" label="批次" width="80">
|
|
|
- <template #default="{ row }"><el-input-number v-model="row.batch" :min="1" :controls="false" :disabled="isView" /></template>
|
|
|
+ <el-table-column prop="batch" label="组件行号" width="150">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <div class="pick-wrap">
|
|
|
+ <el-input v-model="row.batch" disabled />
|
|
|
+ <el-button v-if="!isView && editingBatchIndex === $index" @click="openPick('batch', $index)">选择</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="itemNum" label="物料编号" width="140">
|
|
|
+ <template #default="{ row }"><el-input v-model="row.itemNum" disabled /></template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="itemNum" label="物料编号" width="140"><template #default="{ row }"><el-input v-model="row.itemNum" disabled /></template></el-table-column>
|
|
|
- <el-table-column prop="suppItem" label="供应商物料" width="180">
|
|
|
+ <el-table-column prop="suppItem" label="组件物料编号" width="180">
|
|
|
<template #default="{ row, $index }">
|
|
|
<div class="pick-wrap">
|
|
|
- <el-input v-model="row.suppItem" :disabled="isView" />
|
|
|
- <el-button :disabled="isView" @click="openPick('batch', $index)">选</el-button>
|
|
|
+ <el-input v-model="row.suppItem" :disabled="isView || editingBatchIndex !== $index" />
|
|
|
+ <el-button v-if="!isView && editingBatchIndex === $index" @click="openPick('suppItem', $index)">选择</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="um" label="单位" width="90"><template #default="{ row }"><el-input v-model="row.um" :disabled="isView" /></template></el-table-column>
|
|
|
- <el-table-column prop="location" label="库位" width="120">
|
|
|
- <template #default="{ row }"><el-select v-model="row.location" filterable :disabled="isView"><el-option v-for="o in locationOptions" :key="o.value" :label="o.label" :value="o.value" /></el-select></template>
|
|
|
+ <el-table-column prop="um" label="单位" width="90">
|
|
|
+ <template #default="{ row, $index }"><el-input v-model="row.um" :disabled="isView || editingBatchIndex !== $index" /></template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="location" label="收货库位" width="160">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-select v-model="row.location" filterable :disabled="isView || editingBatchIndex !== $index" style="width: 100%">
|
|
|
+ <el-option v-for="o in locationOptions" :key="o.value" :label="o.label" :value="o.value" />
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="qtyOrded" label="订单数量" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input-number v-model="row.qtyOrded" :precision="5" :min="0" :controls="false" disabled style="width: 100%" />
|
|
|
+ </template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="qtyOrded" label="订单数量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyOrded" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
|
|
|
- <el-table-column prop="qtyBO" label="欠交量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyBO" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
|
|
|
- <el-table-column prop="qtyReleased" label="下达量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyReleased" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
|
|
|
- <el-table-column prop="qtyReceived" label="收货量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyReceived" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
|
|
|
- <el-table-column prop="qtyReturned" label="退货量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyReturned" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
|
|
|
- <el-table-column prop="lotSerial" label="批号" width="120"><template #default="{ row }"><el-input v-model="row.lotSerial" :disabled="isView" /></template></el-table-column>
|
|
|
- <el-table-column v-if="!isView" label="操作" fixed="right" width="90">
|
|
|
+ <el-table-column prop="qtyBO" label="BOM单位用量" width="130">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-input-number
|
|
|
+ v-model="row.qtyBO"
|
|
|
+ :precision="5"
|
|
|
+ :min="0"
|
|
|
+ :controls="false"
|
|
|
+ :disabled="isView || editingBatchIndex !== $index"
|
|
|
+ style="width: 100%"
|
|
|
+ @change="() => syncBatchRowQty($index)"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="qtyReleased" label="组件需求数量" width="130">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input-number v-model="row.qtyReleased" :precision="5" :min="0" :controls="false" disabled style="width: 100%" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="qtyReceived" label="收货数量" width="120">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-input-number v-model="row.qtyReceived" :precision="5" :min="0" :controls="false" :disabled="isView || editingBatchIndex !== $index" style="width: 100%" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="qtyReturned" label="退回数量" width="120">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-input-number v-model="row.qtyReturned" :precision="5" :min="0" :controls="false" :disabled="isView || editingBatchIndex !== $index" style="width: 100%" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="lotSerial" label="生产批号" width="120">
|
|
|
+ <template #default="{ row, $index }"><el-input v-model="row.lotSerial" :disabled="isView || editingBatchIndex !== $index" /></template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column v-if="!isView" label="操作" fixed="right" width="120">
|
|
|
<template #default="{ $index }">
|
|
|
+ <el-button type="primary" link @click="editBatch($index)">编辑</el-button>
|
|
|
<el-button type="danger" link @click="removeBatch($index)">删除</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
@@ -66,15 +144,23 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <el-dialog v-model="pickVisible" title="选择物料" width="75%">
|
|
|
+ <el-dialog v-model="pickVisible" title="选择物料" width="75%" append-to-body destroy-on-close>
|
|
|
<select-item-master @picked="onPicked" />
|
|
|
</el-dialog>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { onMounted, reactive, ref } from 'vue';
|
|
|
+import { computed, onMounted, reactive, ref, watch } from 'vue';
|
|
|
import { type FormInstance, type FormRules } from 'element-plus';
|
|
|
-import { fetchOutsourceLocations, fetchOutsourceOrderDetail, fetchOutsourceOrderDetailInit, saveOutsourceOrderDetail, type ItemRow, type OptionRow, type OutsourceOrderBatchRow } from '../api/outsourceOrder';
|
|
|
+import {
|
|
|
+ fetchOutsourceLocations,
|
|
|
+ fetchOutsourceOrderDetail,
|
|
|
+ fetchOutsourceOrderDetailInit,
|
|
|
+ saveOutsourceOrderDetail,
|
|
|
+ type ItemRow,
|
|
|
+ type OptionRow,
|
|
|
+ type OutsourceOrderBatchRow,
|
|
|
+} from '../api/outsourceOrder';
|
|
|
import SelectItemMaster from './selectItemMaster.vue';
|
|
|
|
|
|
const props = defineProps<{ mode: 'add' | 'edit' | 'view'; id: number | null; purOrd: string }>();
|
|
|
@@ -84,7 +170,8 @@ const formRef = ref<FormInstance>();
|
|
|
const loading = ref(false);
|
|
|
const saving = ref(false);
|
|
|
const locationOptions = ref<OptionRow[]>([]);
|
|
|
-const isView = ref(props.mode === 'view');
|
|
|
+const isView = computed(() => props.mode === 'view');
|
|
|
+const editingBatchIndex = ref(-1);
|
|
|
|
|
|
const form = reactive({
|
|
|
id: null as number | null,
|
|
|
@@ -104,38 +191,88 @@ const form = reactive({
|
|
|
potype: 'PW',
|
|
|
batches: [] as OutsourceOrderBatchRow[],
|
|
|
});
|
|
|
+
|
|
|
const rules: FormRules = {
|
|
|
itemNum: [{ required: true, message: '请选择物料', trigger: 'blur' }],
|
|
|
qtyOrded: [{ required: true, message: '请输入订单数量', trigger: 'blur' }],
|
|
|
};
|
|
|
|
|
|
const pickVisible = ref(false);
|
|
|
-const pickMode = ref<'main' | 'batch'>('main');
|
|
|
+const pickMode = ref<'main' | 'batch' | 'suppItem'>('main');
|
|
|
const pickRowIndex = ref(-1);
|
|
|
+const qtySyncReady = ref(false);
|
|
|
+
|
|
|
+function roundQty(v: number) {
|
|
|
+ return Math.round(v * 100000) / 100000;
|
|
|
+}
|
|
|
+
|
|
|
+function calcBatchReleased(qtyBO?: number | null, qtyOrded?: number | null) {
|
|
|
+ return roundQty(Number(qtyBO || 0) * Number(qtyOrded || 0));
|
|
|
+}
|
|
|
+
|
|
|
+function syncBatchRowQty(index: number) {
|
|
|
+ const row = form.batches[index];
|
|
|
+ if (!row) return;
|
|
|
+ row.qtyOrded = Number(form.qtyOrded || 0);
|
|
|
+ row.qtyReleased = calcBatchReleased(row.qtyBO, row.qtyOrded);
|
|
|
+}
|
|
|
+
|
|
|
+function syncAllBatchQty() {
|
|
|
+ const mainQty = Number(form.qtyOrded || 0);
|
|
|
+ for (let i = 0; i < form.batches.length; i++) {
|
|
|
+ const row = form.batches[i];
|
|
|
+ row.qtyOrded = mainQty;
|
|
|
+ row.qtyReleased = calcBatchReleased(row.qtyBO, mainQty);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function nextBatchNo() {
|
|
|
+ const nums = form.batches.map((x) => Number(x.batch || 0)).filter((n) => n > 0);
|
|
|
+ return nums.length ? Math.max(...nums) + 1 : 1;
|
|
|
+}
|
|
|
|
|
|
function addBatch() {
|
|
|
+ const index = form.batches.length;
|
|
|
form.batches.push({
|
|
|
- batch: form.batches.length + 1,
|
|
|
+ batch: nextBatchNo(),
|
|
|
itemNum: form.itemNum,
|
|
|
suppItem: '',
|
|
|
um: form.um,
|
|
|
location: form.location,
|
|
|
- qtyOrded: 0,
|
|
|
+ qtyOrded: Number(form.qtyOrded || 0),
|
|
|
qtyBO: 0,
|
|
|
qtyReleased: 0,
|
|
|
qtyReceived: 0,
|
|
|
qtyReturned: 0,
|
|
|
lotSerial: '',
|
|
|
});
|
|
|
+ syncBatchRowQty(index);
|
|
|
+ editingBatchIndex.value = index;
|
|
|
+}
|
|
|
+
|
|
|
+function editBatch(index: number) {
|
|
|
+ editingBatchIndex.value = index;
|
|
|
}
|
|
|
+
|
|
|
function removeBatch(index: number) {
|
|
|
form.batches.splice(index, 1);
|
|
|
+ if (editingBatchIndex.value === index) editingBatchIndex.value = -1;
|
|
|
+ else if (editingBatchIndex.value > index) editingBatchIndex.value -= 1;
|
|
|
}
|
|
|
-function openPick(mode: 'main' | 'batch', index = -1) {
|
|
|
+
|
|
|
+function openPick(mode: 'main' | 'batch' | 'suppItem', index = -1) {
|
|
|
pickMode.value = mode;
|
|
|
pickRowIndex.value = index;
|
|
|
pickVisible.value = true;
|
|
|
}
|
|
|
+
|
|
|
+function applyItemToBatchRow(row: OutsourceOrderBatchRow, item: ItemRow, fillSuppItem = true) {
|
|
|
+ row.itemNum = item.itemNum || '';
|
|
|
+ if (fillSuppItem) row.suppItem = item.itemNum || '';
|
|
|
+ row.um = item.um || '';
|
|
|
+ row.location = item.location || '';
|
|
|
+}
|
|
|
+
|
|
|
function onPicked(row: ItemRow) {
|
|
|
if (pickMode.value === 'main') {
|
|
|
form.itemNum = row.itemNum || '';
|
|
|
@@ -145,10 +282,14 @@ function onPicked(row: ItemRow) {
|
|
|
form.drawing = row.drawing || '';
|
|
|
} else if (pickRowIndex.value >= 0) {
|
|
|
const tar = form.batches[pickRowIndex.value];
|
|
|
- tar.itemNum = row.itemNum || '';
|
|
|
- tar.suppItem = row.itemNum || '';
|
|
|
- tar.um = row.um || '';
|
|
|
- tar.location = row.location || '';
|
|
|
+ if (pickMode.value === 'batch') {
|
|
|
+ applyItemToBatchRow(tar, row, true);
|
|
|
+ } else {
|
|
|
+ tar.suppItem = row.itemNum || '';
|
|
|
+ if (!tar.itemNum) tar.itemNum = row.itemNum || '';
|
|
|
+ if (!tar.um) tar.um = row.um || '';
|
|
|
+ if (!tar.location) tar.location = row.location || '';
|
|
|
+ }
|
|
|
}
|
|
|
pickVisible.value = false;
|
|
|
}
|
|
|
@@ -160,12 +301,25 @@ async function loadOptions() {
|
|
|
|
|
|
async function loadData() {
|
|
|
loading.value = true;
|
|
|
+ editingBatchIndex.value = -1;
|
|
|
+ qtySyncReady.value = false;
|
|
|
try {
|
|
|
if (props.mode === 'add') {
|
|
|
const init = await fetchOutsourceOrderDetailInit(props.purOrd);
|
|
|
+ form.id = null;
|
|
|
form.purOrd = init.purOrd || props.purOrd;
|
|
|
form.purOrdRecID = init.purOrdRecID;
|
|
|
form.line = init.line;
|
|
|
+ form.itemNum = '';
|
|
|
+ form.lotSerial = '';
|
|
|
+ form.rev = '';
|
|
|
+ form.drawing = '';
|
|
|
+ form.um = '';
|
|
|
+ form.location = '';
|
|
|
+ form.qtyOrded = 0;
|
|
|
+ form.qtyReceived = 0;
|
|
|
+ form.qtyReleased = 0;
|
|
|
+ form.dueDate = '';
|
|
|
form.batches = [];
|
|
|
return;
|
|
|
}
|
|
|
@@ -189,6 +343,7 @@ async function loadData() {
|
|
|
form.batches = (data.batches || []).map((x) => ({ ...x }));
|
|
|
} finally {
|
|
|
loading.value = false;
|
|
|
+ qtySyncReady.value = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -213,6 +368,21 @@ async function onSave() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+watch(
|
|
|
+ () => form.qtyOrded,
|
|
|
+ () => {
|
|
|
+ if (!qtySyncReady.value || isView.value) return;
|
|
|
+ syncAllBatchQty();
|
|
|
+ },
|
|
|
+);
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => [props.mode, props.id, props.purOrd] as const,
|
|
|
+ () => {
|
|
|
+ loadData();
|
|
|
+ },
|
|
|
+);
|
|
|
+
|
|
|
onMounted(async () => {
|
|
|
await loadOptions();
|
|
|
await loadData();
|
|
|
@@ -230,6 +400,7 @@ onMounted(async () => {
|
|
|
.pick-wrap {
|
|
|
display: flex;
|
|
|
gap: 6px;
|
|
|
+ align-items: center;
|
|
|
}
|
|
|
.footer {
|
|
|
margin-top: 12px;
|