| | |
| | | @close="handleClose"> |
| | | <div class="material-toolbar"> |
| | | <el-button type="primary" |
| | | plain |
| | | @click="handleAddPurchase">新增采购</el-button> |
| | | <el-button type="primary" |
| | | @click="handleAddMaterialRow">新增</el-button> |
| | | </div> |
| | | <el-table v-loading="materialTableLoading" |
| | | :data="materialTableData" |
| | | border |
| | | @selection-change="handleMaterialSelectionChange" |
| | | row-key="tempId"> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | width="55" /> |
| | | <el-table-column label="工序名称" |
| | | min-width="100"> |
| | | <template #default="{ row }"> |
| | |
| | | @change="val => handleRequiredQtyChange(row, val)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="库存余量" |
| | | min-width="90"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.stockQty === null">-</span> |
| | | <span v-else>{{ stripTrailingZeros(row.stockQty) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="已领数量" |
| | | min-width="90"> |
| | | <template #default="{ row }"> |
| | | {{ stripTrailingZeros(row.pickedQty) ?? 0 }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="剩余可领" |
| | | <el-table-column label="剩余需领" |
| | | min-width="90"> |
| | | <template #default="{ row }"> |
| | | <span :class="{ 'remaining-empty': Number(row.remainingQty) <= 0 }"> |
| | |
| | | <ProductSelectDialog v-model="materialProductDialogVisible" |
| | | @confirm="handleMaterialProductConfirm" |
| | | single /> |
| | | <PurchaseLedgerAddDialog ref="purchaseLedgerAddDialogRef" |
| | | v-model="purchaseLedgerDialogVisible" /> |
| | | <!-- request-url="/stockInventory/rawMaterials" --> |
| | | </div> |
| | | </template> |
| | |
| | | import { computed, ref, watch } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; |
| | | import PurchaseLedgerAddDialog from "./PurchaseLedgerAddDialog.vue"; |
| | | import { findProductProcessRouteItemList } from "@/api/productionManagement/productProcessRoute.js"; |
| | | import { getStockInventoryByModelId } from "@/api/inventoryManagement/stockInventory.js"; |
| | | import { |
| | | listMaterialPickingDetail, |
| | | listMaterialPickingBom, |
| | |
| | | const materialTableLoading = ref(false); |
| | | const materialSaving = ref(false); |
| | | const materialTableData = ref([]); |
| | | const selectedMaterialRows = ref([]); |
| | | const purchaseLedgerDialogVisible = ref(false); |
| | | const purchaseLedgerAddDialogRef = ref(null); |
| | | |
| | | const isSaveDisabled = computed(() => { |
| | | if (materialTableLoading.value) return true; |
| | |
| | | returnedQty, |
| | | remainingQty, |
| | | pickQty: 0, |
| | | stockQty: null, |
| | | batchNo: [], |
| | | batchNoList, |
| | | }; |
| | |
| | | }) |
| | | ); |
| | | }); |
| | | // 先查出库存余量再入表,保证首次打开即展示(rows 尚未转为响应式代理, |
| | | // 入表后再改原始对象不会触发视图更新) |
| | | await loadStockQtyForRows(rows); |
| | | // 打开领料台账默认:自动选中第一个可用批号,领用数量=需求数量 |
| | | rows.forEach(row => { |
| | | const batchNoList = Array.isArray(row.batchNoList) ? row.batchNoList : []; |
| | | if (batchNoList.length > 0) { |
| | | row.batchNo = [batchNoList[0]]; |
| | | row.pickQty = toNumber(row.demandedQuantity); |
| | | } |
| | | }); |
| | | materialTableData.value = rows; |
| | | } finally { |
| | | materialTableLoading.value = false; |
| | |
| | | |
| | | const handleClose = () => { |
| | | materialTableData.value = []; |
| | | selectedMaterialRows.value = []; |
| | | currentMaterialSelectRowIndex.value = -1; |
| | | }; |
| | | |
| | | // 按型号汇总所有批次的原料可用库存余量 |
| | | const loadStockQtyForRows = async rows => { |
| | | const modelIds = [ |
| | | ...new Set(rows.map(row => row.materialModelId).filter(Boolean)), |
| | | ]; |
| | | const stockMap = new Map(); |
| | | await Promise.all( |
| | | modelIds.map(async modelId => { |
| | | try { |
| | | const res = await getStockInventoryByModelId(modelId); |
| | | const list = Array.isArray(res?.data) |
| | | ? res.data |
| | | : res?.data?.records || res?.data?.rows || []; |
| | | const total = list.reduce( |
| | | (sum, item) => sum + toNumber(item?.qualitity ?? item?.quantity), |
| | | 0 |
| | | ); |
| | | stockMap.set(modelId, roundQuantity(total)); |
| | | } catch (e) { |
| | | stockMap.set(modelId, 0); |
| | | } |
| | | }) |
| | | ); |
| | | rows.forEach(row => { |
| | | if (!row.materialModelId) { |
| | | row.stockQty = null; |
| | | return; |
| | | } |
| | | row.stockQty = stockMap.has(row.materialModelId) |
| | | ? stockMap.get(row.materialModelId) |
| | | : 0; |
| | | }); |
| | | }; |
| | | |
| | | const handleAddMaterialRow = () => { |
| | | materialTableData.value.push(createMaterialRow()); |
| | | }; |
| | | |
| | | const handleMaterialSelectionChange = selection => { |
| | | selectedMaterialRows.value = selection; |
| | | }; |
| | | |
| | | const handleAddPurchase = () => { |
| | | if (selectedMaterialRows.value.length === 0) { |
| | | ElMessage.warning("请选择需要新增采购的原料行"); |
| | | return; |
| | | } |
| | | const rowsWithModel = selectedMaterialRows.value.filter( |
| | | row => row.materialModelId |
| | | ); |
| | | if (rowsWithModel.length === 0) { |
| | | ElMessage.warning("所选行缺少原料型号,请先选择原料后再新增采购"); |
| | | return; |
| | | } |
| | | const productRows = rowsWithModel.map(row => ({ |
| | | productModelId: row.materialModelId, |
| | | productCategory: row.materialName, |
| | | specificationModel: row.materialModel, |
| | | unit: row.unit, |
| | | quantity: row.demandedQuantity, |
| | | isChecked: false, |
| | | })); |
| | | purchaseLedgerAddDialogRef.value?.open(productRows); |
| | | }; |
| | | |
| | | const handleDeleteMaterialRow = index => { |
| | |
| | | row.batchNo = []; |
| | | currentMaterialSelectRowIndex.value = -1; |
| | | materialProductDialogVisible.value = false; |
| | | loadStockQtyForRows([row]); |
| | | }; |
| | | |
| | | const validateMaterialRows = () => { |