| src/api/procurementManagement/procurementLedger.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/layout/components/NotificationCenter/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/procurementManagement/procurementLedger/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/productionOrder/components/PurchaseRequestDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/api/procurementManagement/procurementLedger.js
@@ -80,6 +80,15 @@ }); } // ä¿åéè´è稿ï¼åºåä¸è¶³åºæ¯ï¼ export function saveShortagePurchaseDraft(data) { return request({ url: "/purchase/ledger/saveShortagePurchaseDraft", method: "post", data: data, }); } // ä¿åéè´æ¨¡æ¿ export function addPurchaseTemplate(data) { return request({ src/layout/components/NotificationCenter/index.vue
@@ -190,6 +190,13 @@ }); } // 妿æ¯éè´ç³è¯·æéï¼æ ¹æ®purchaseContractNumberæ¥è¯¢ if (item.noticeTitle === "éè´ç³è¯·æé" && query.purchaseContractNumber) { query = { purchaseContractNumber: query.purchaseContractNumber, }; } // è·³è½¬å°æå®é¡µé¢ router.push({ path: path, src/views/procurementManagement/procurementLedger/index.vue
@@ -699,6 +699,7 @@ getCurrentInstance, nextTick, } from "vue"; import { useRoute } from "vue-router"; import { Search, Delete } from "@element-plus/icons-vue"; import { ElMessageBox, ElMessage } from "element-plus"; import { userListNoPage } from "@/api/system/user.js"; @@ -728,6 +729,7 @@ ); const { proxy } = getCurrentInstance(); const route = useRoute(); const { tax_rate } = proxy.useDict("tax_rate"); const tableData = ref([]); const productData = ref([]); @@ -756,6 +758,7 @@ // 订å审æ¹ç¶ææ¾ç¤ºææ¬ const approvalStatusText = { 0: "è稿", 1: "å¾ å®¡æ ¸", 2: "审æ¹ä¸", 3: "审æ¹éè¿", @@ -765,6 +768,7 @@ // è·å审æ¹ç¶ææ ç¾ç±»å const getApprovalStatusType = status => { const typeMap = { 0: "info", // å¾ å®¡æ ¸ - ç°è² 1: "info", // å¾ å®¡æ ¸ - ç°è² 2: "warning", // 审æ¹ä¸ - æ©è² 3: "success", // 审æ¹éè¿ - ç»¿è² @@ -936,13 +940,10 @@ const { form, rules } = toRefs(data); const { form: searchForm } = useFormData({ ...data.searchForm, // 设置å½å ¥æ¥æèå´ä¸ºå½å¤© entryDate: [ dayjs().startOf("day").format("YYYY-MM-DD"), dayjs().endOf("day").format("YYYY-MM-DD"), ], entryDateStart: dayjs().startOf("day").format("YYYY-MM-DD"), entryDateEnd: dayjs().endOf("day").format("YYYY-MM-DD"), // å½å ¥æ¥æä¸è®¾ç½®é»è®¤å¼ entryDate: null, entryDateStart: undefined, entryDateEnd: undefined, }); // 产å表åå¼¹æ¡æ°æ® @@ -1919,7 +1920,16 @@ }; onMounted(() => { getList(); // æ£æ¥URL忰䏿¯å¦æpurchaseContractNumberï¼æåè®¾ç½®å°æç´¢æ¡ä»¶ if (route.query.purchaseContractNumber) { // 使ç¨setTimeoutç¡®ä¿searchFormå·²ç»åå§å setTimeout(() => { searchForm.purchaseContractNumber = route.query.purchaseContractNumber; getList(); }, 0); } else { getList(); } getTemplateList(); }); </script> src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -2,7 +2,7 @@ <div> <el-dialog v-model="dialogVisible" title="颿å°è´¦" width="1200px" width="1400px" @close="handleClose"> <div class="material-toolbar"> <el-button type="primary" @@ -65,10 +65,18 @@ </el-select> </template> </el-table-column> <el-table-column label="åºåæ°é" min-width="120"> <template #default="{ row }"> <span :class="{ 'text-danger': isStockInsufficient(row) }"> {{ row.stockQuantity ?? '-' }} </span> </template> </el-table-column> <el-table-column label="éæ±æ°é" min-width="120"> <template #default="{ row }"> <span v-if="row.bom === true">{{ row.demandedQuantity ?? "-" }}</span> <span v-if="row.bom === true" :class="{ 'text-danger': isStockInsufficient(row) }">{{ row.demandedQuantity ?? "-" }}</span> <el-input-number v-else v-model="row.demandedQuantity" :min="0" @@ -76,6 +84,7 @@ :step="1" controls-position="right" style="width: 100%;" :class="{ 'is-stock-insufficient': isStockInsufficient(row) }" @change="val => handleRequiredQtyChange(row, val)" /> </template> </el-table-column> @@ -109,6 +118,9 @@ </el-table> <template #footer> <span class="dialog-footer"> <el-button v-if="hasInsufficientStock" type="warning" @click="openPurchaseRequestDialog">éè´ç³è¯·</el-button> <el-button type="primary" :loading="materialSaving" :disabled="isSaveDisabled" @@ -120,6 +132,10 @@ <ProductSelectDialog v-model="materialProductDialogVisible" @confirm="handleMaterialProductConfirm" single /> <PurchaseRequestDialog v-model="purchaseRequestDialogVisible" :insufficient-items="insufficientStockItems" :order-row="props.orderRow" @saved="handlePurchaseRequestSaved" /> <!-- request-url="/stockInventory/rawMaterials" --> </div> </template> @@ -128,18 +144,18 @@ import { computed, ref, watch } from "vue"; import { ElMessage } from "element-plus"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; import PurchaseRequestDialog from "./PurchaseRequestDialog.vue"; import { findProductProcessRouteItemList, listMain, } from "@/api/productionManagement/productProcessRoute.js"; import { listMaterialPickingDetail, listMaterialPickingBom, listMaterialPickingLedger, saveMaterialPickingLedger, updateMaterialPickingLedger, } from "@/api/productionManagement/productionOrder.js"; import { queryList2 } from "@/api/productionManagement/productStructure.js"; const props = defineProps({ modelValue: { type: Boolean, default: false }, @@ -156,6 +172,7 @@ const materialTableLoading = ref(false); const materialSaving = ref(false); const materialTableData = ref([]); const purchaseRequestDialogVisible = ref(false); const isSaveDisabled = computed(() => { if (materialTableData.value.length === 0) return true; @@ -183,6 +200,23 @@ }); }); // 夿åºåæ¯å¦ä¸è¶³ const isStockInsufficient = (row) => { const stockQuantity = Number(row.stockQuantity ?? 0); const demandedQty = Number(row.demandedQuantity ?? 0); return demandedQty > 0 && stockQuantity > 0 && demandedQty > stockQuantity; }; // åºåä¸è¶³çè¡ const insufficientStockItems = computed(() => { return materialTableData.value.filter(row => isStockInsufficient(row)); }); // æ¯å¦æåºåä¸è¶³ const hasInsufficientStock = computed(() => { return insufficientStockItems.value.length > 0; }); const processOptions = ref([]); const currentMaterialSelectRowIndex = ref(-1); let materialTempId = 0; @@ -206,6 +240,7 @@ : row.batchNo : [], batchNoList: row.batchNoList || [], stockQuantity: row.stockQuantity ?? row.stockQty ?? null, }); const getProcessOptions = async () => { @@ -239,22 +274,14 @@ materialTableData.value = []; await getProcessOptions(); try { const detailRes = await listMaterialPickingDetail(props.orderRow.id); const detailList = Array.isArray(detailRes?.data) ? detailRes.data : detailRes?.data?.records || []; if (detailList.length > 0) { isDetail.value = true; materialTableData.value = detailList.map(item => createMaterialRow(item)); return; } else { // ç´æ¥è°ç¨listMaterialPickingBomæ¥å£è·ååºåæ°é const bomRes = await listMaterialPickingBom(props.orderRow.id); const bomList = Array.isArray(bomRes?.data) ? bomRes.data : bomRes?.data?.records || []; if (bomList.length > 0) { isDetail.value = false; const bomRes = await listMaterialPickingBom(props.orderRow.id); const bomList = Array.isArray(bomRes?.data) ? bomRes.data : bomRes?.data?.records || []; materialTableData.value = bomList.map(item => createMaterialRow(item)); return; } } finally { materialTableLoading.value = false; @@ -305,7 +332,7 @@ materialProductDialogVisible.value = true; }; const handleMaterialProductConfirm = products => { const handleMaterialProductConfirm = async (products) => { console.log(products, "products"); if (!products || products.length === 0) return; @@ -417,6 +444,17 @@ materialSaving.value = false; } }; // æå¼éè´ç³è¯·å¯¹è¯æ¡ const openPurchaseRequestDialog = () => { purchaseRequestDialogVisible.value = true; }; // éè´ç³è¯·ä¿ååè° const handlePurchaseRequestSaved = () => { // éè´ç³è¯·ä¿åæååå·æ°æ°æ® loadMaterialData(); }; </script> <style scoped lang="scss"> @@ -424,4 +462,15 @@ margin-bottom: 12px; text-align: right; } .text-danger { color: #f56c6c; font-weight: bold; } :deep(.is-stock-insufficient) { .el-input__wrapper { box-shadow: 0 0 0 1px #f56c6c inset; } } </style> src/views/productionManagement/productionOrder/components/PurchaseRequestDialog.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,308 @@ <template> <div> <el-dialog v-model="dialogVisible" title="éè´ç³è¯·ï¼åºåä¸è¶³ï¼" width="900px" @close="handleClose"> <!-- ç®æéè´ç³è¯·è¡¨å --> <el-form :model="form" label-width="100px"> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="éå®è®¢åå·" required> <el-input v-model="form.salesContractNo" placeholder="请è¾å ¥éå®è®¢åå·" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æé人" required> <el-select v-model="form.ccUserId" placeholder="è¯·éæ©æé人" style="width: 100%" filterable> <el-option v-for="user in userOptions" :key="user.userId" :label="user.nickName" :value="user.userId" /> </el-select> </el-form-item> </el-col> </el-row> </el-form> <!-- 产åè¡¨æ ¼ --> <div class="purchase-request-table"> <div class="table-toolbar"> <span class="table-title">éè´äº§åæç»</span> <el-button type="primary" @click="handleAddRow">æ°å¢äº§å</el-button> </div> <el-table :data="tableData" border style="width: 100%;" max-height="400"> <el-table-column type="index" label="åºå·" width="60" align="center" /> <el-table-column label="产ååç§°" min-width="150"> <template #default="{ row }"> <el-button v-if="!row.productName" type="primary" link @click="openProductSelect(row)"> éæ©äº§å </el-button> <span v-else>{{ row.productName }}</span> </template> </el-table-column> <el-table-column label="åå·/è§æ ¼" min-width="150"> <template #default="{ row }"> {{ row.model || '-' }} </template> </el-table-column> <el-table-column label="åä½" width="80" align="center"> <template #default="{ row }"> {{ row.unit || '-' }} </template> </el-table-column> <el-table-column label="æ°é" width="120"> <template #default="{ row }"> <el-input-number v-model="row.quantity" :min="1" :precision="0" :step="1" controls-position="right" style="width: 100%;" /> </template> </el-table-column> <el-table-column label="æä½" width="80" align="center" fixed="right"> <template #default="{ $index }"> <el-button type="danger" link @click="handleDeleteRow($index)">å é¤</el-button> </template> </el-table-column> </el-table> </div> <template #footer> <span class="dialog-footer"> <el-button type="primary" :loading="saving" @click="handleSave">ä¿åè稿</el-button> <el-button @click="dialogVisible = false">åæ¶</el-button> </span> </template> </el-dialog> <ProductSelectDialog v-model="productSelectVisible" @confirm="handleProductConfirm" single /> </div> </template> <script setup> import { computed, ref, watch, onMounted } from "vue"; import { ElMessage } from "element-plus"; import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; import { saveShortagePurchaseDraft } from "@/api/procurementManagement/procurementLedger.js"; import { listUser } from "@/api/system/user.js"; import useUserStore from "@/store/modules/user.js"; const userStore = useUserStore(); const props = defineProps({ modelValue: { type: Boolean, default: false }, insufficientItems: { type: Array, default: () => [] }, orderRow: { type: Object, default: null }, }); const emit = defineEmits(["update:modelValue", "saved"]); const dialogVisible = computed({ get: () => props.modelValue, set: (val) => emit("update:modelValue", val), }); const productSelectVisible = ref(false); const saving = ref(false); const currentSelectRowIndex = ref(-1); const userOptions = ref([]); // è¡¨åæ°æ® const form = ref({ salesContractNo: "", ccUserId: null, ccUserName: "", }); // è¡¨æ ¼æ°æ® const tableData = ref([]); // è·åç¨æ·åè¡¨ï¼æéäººéæ©ï¼ const getUserList = async () => { try { const res = await listUser({ pageSize: 1000 }); userOptions.value = res.rows || []; } catch (error) { console.error("è·åç¨æ·å表失败:", error); } }; onMounted(() => { getUserList(); }); // çå¬å¯¹è¯æ¡æå¼ï¼åå§åæ°æ® watch( () => dialogVisible.value, (visible) => { if (visible) { initData(); } } ); // åå§åæ°æ® const initData = () => { // ä»ç产订åä¸è·åéå®è®¢åå· form.value.salesContractNo = props.orderRow?.salesContractNo || ""; form.value.applicantId = userStore.id || ""; form.value.applicantName = userStore.name || ""; form.value.ccUserId = null; form.value.ccUserName = ""; // å°åºåä¸è¶³ç产åå¡«å å°è¡¨æ ¼ tableData.value = props.insufficientItems.map((item) => ({ tempId: generateTempId(), productModelId: item.materialModelId, productName: item.materialName, model: item.materialModel, unit: item.unit, quantity: Math.max(1, Math.ceil((item.demandedQuantity || 0) - (item.stockQuantity || 0))), })); }; // çæä¸´æ¶ID let tempIdCounter = 0; const generateTempId = () => { return `temp_${++tempIdCounter}_${Date.now()}`; }; // å ³éå¯¹è¯æ¡ const handleClose = () => { form.value = { salesContractNo: "", applicantId: "", applicantName: "", ccUserId: null, ccUserName: "", }; tableData.value = []; currentSelectRowIndex.value = -1; }; // æ°å¢è¡ const handleAddRow = () => { tableData.value.push({ tempId: generateTempId(), productModelId: null, productName: "", model: "", unit: "", quantity: 1, }); }; // å é¤è¡ const handleDeleteRow = (index) => { tableData.value.splice(index, 1); }; // æå¼äº§åéæ© const openProductSelect = (row) => { currentSelectRowIndex.value = tableData.value.findIndex( (item) => item.tempId === row.tempId ); productSelectVisible.value = true; }; // 产åéæ©ç¡®è®¤ const handleProductConfirm = (products) => { if (!products || products.length === 0) return; const index = currentSelectRowIndex.value; if (index < 0 || !tableData.value[index]) return; const product = products[0]; const row = tableData.value[index]; row.productModelId = product.materialModelId || product.modelId || product.id; row.productName = product.materialName || product.productName || product.name || ""; row.model = product.materialModel || product.model || ""; row.unit = product.unit || product.measureUnit || ""; currentSelectRowIndex.value = -1; productSelectVisible.value = false; }; // éªè¯è¡¨å const validateForm = () => { if (!form.value.salesContractNo) { ElMessage.warning("请è¾å ¥éå®è®¢åå·"); return false; } if (!form.value.ccUserId) { ElMessage.warning("è¯·éæ©æé人"); return false; } if (tableData.value.length === 0) { ElMessage.warning("请è³å°æ·»å ä¸ä¸ªäº§å"); return false; } for (let i = 0; i < tableData.value.length; i++) { const row = tableData.value[i]; if (!row.productName) { ElMessage.warning(`第${i + 1}è¡è¯·éæ©äº§å`); return false; } if (!row.quantity || row.quantity <= 0) { ElMessage.warning(`第${i + 1}è¡æ°éå¿ é¡»å¤§äº0`); return false; } } return true; }; // ä¿åè稿 const handleSave = async () => { if (!validateForm()) return; // è·åæé人å§å const selectedUser = userOptions.value.find(u => u.userId === form.value.ccUserId); form.value.ccUserName = selectedUser?.userName || ""; saving.value = true; try { // æå»ºéè´èç¨¿æ°æ®ï¼æ ¹æ®èè°ææ¡£ PurchaseLedgerDto æ ¼å¼ï¼ const draftData = { salesContractNo: form.value.salesContractNo, ccUserId: form.value.ccUserId, ccUserName: form.value.ccUserName, // 产åæç»æ°æ® productData: tableData.value.map((item) => ({ productModelId: item.productModelId, productName: item.productName, model: item.model, unit: item.unit, quantity: item.quantity, })), }; const res = await saveShortagePurchaseDraft(draftData); if (res.code === 200) { ElMessage.success("éè´è稿ä¿åæåï¼å·²éç¥æéäººè¡¥å ¨ä¿¡æ¯"); emit("saved", res.data); // è¿åè稿ID dialogVisible.value = false; } else { ElMessage.error(res.msg || "ä¿å失败"); } } catch (error) { console.error("ä¿åéè´è稿失败:", error); ElMessage.error("ä¿å失败ï¼è¯·éè¯"); } finally { saving.value = false; } }; </script> <style scoped lang="scss"> .purchase-request-table { margin-top: 20px; .table-toolbar { margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; .table-title { font-weight: bold; font-size: 14px; } } } .dialog-footer { display: flex; justify-content: center; gap: 10px; } </style>