From d78815179b7d49509e6637d547ca8bbd525f7fcf Mon Sep 17 00:00:00 2001
From: yuan <123@>
Date: 星期一, 07 九月 2026 11:45:41 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_pro_河南鹤壁' into dev_pro_河南鹤壁
---
src/views/collaborativeApproval/approvalProcess/components/viewDetailDia.vue | 630 ++++++++++++
multiple/assets/favicon/JMTMfavicon.ico | 0
multiple/assets/logo/HMCLLogo.png | 0
src/views/basicData/product/ProductSelectDialog.vue | 31
multiple/config.json | 84 +
src/views/productionManagement/processRoute/processRouteItem/index.vue | 76 -
multiple/assets/logo/JMTMLogo.png | 0
src/views/collaborativeApproval/approvalProcess/index.vue | 21
src/views/qualityManagement/rawMaterialInspection/index.vue | 12
src/views/salesManagement/salesLedger/index.vue | 99 +
src/views/qualityManagement/finalInspection/index.vue | 12
src/views/monitor/logininfor/index.vue | 6
src/views/monitor/operlog/index.vue | 7
src/views/qualityManagement/processInspection/index.vue | 12
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue | 433 +++++--
src/views/procurementManagement/procurementPlan/index.vue | 2
src/views/productionManagement/productionTraceability/index.vue | 2
multiple/assets/logo/SDKSLogo.png | 0
src/views/equipmentManagement/ledger/index.vue | 20
multiple/assets/logo/CHMYLogo.png | 0
multiple/assets/logo/SJDQLogo.png | 0
multiple/assets/favicon/HMCLfavicon.ico | 0
src/views/productionManagement/productionProcess/ProcessSelectDialog.vue | 159 +++
src/views/inventoryManagement/stockManagement/New.vue | 12
multiple/assets/favicon/CHMYfavicon.ico | 0
multiple/assets/favicon/TXDQfavicon.ico | 0
src/views/productionManagement/productionOrder/components/PurchaseLedgerAddDialog.vue | 856 ++++++++++++++++
multiple/assets/favicon/SJDQfavicon.ico | 0
src/api/productionManagement/productionProcess.js | 8
src/views/equipmentManagement/repair/index.vue | 9
src/views/qualityManagement/nonconformingManagement/components/detailModal.vue | 98 +
src/views/procurementManagement/paymentLedger/index.vue | 14
src/views/productionManagement/productStructure/Detail/index.vue | 283 +++++
src/views/equipmentManagement/repair/Modal/RepairDetailModal.vue | 96 +
src/views/procurementManagement/procurementLedger/index.vue | 12
multiple/assets/favicon/SDKSfavicon.ico | 0
src/views/productionManagement/productionOrder/index.vue | 16
src/views/productionPlan/productionPlan/index.vue | 28
src/api/qualityManagement/nonconformingManagement.js | 9
src/components/filePreview/index.vue | 14
src/views/qualityManagement/nonconformingManagement/index.vue | 12
multiple/assets/logo/TXDQLogo.png | 0
src/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue | 2
43 files changed, 2,756 insertions(+), 319 deletions(-)
diff --git a/multiple/assets/favicon/CHMYfavicon.ico b/multiple/assets/favicon/CHMYfavicon.ico
new file mode 100644
index 0000000..47e0a72
--- /dev/null
+++ b/multiple/assets/favicon/CHMYfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/HMCLfavicon.ico b/multiple/assets/favicon/HMCLfavicon.ico
new file mode 100644
index 0000000..29633d6
--- /dev/null
+++ b/multiple/assets/favicon/HMCLfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/JMTMfavicon.ico b/multiple/assets/favicon/JMTMfavicon.ico
new file mode 100644
index 0000000..ef5b373
--- /dev/null
+++ b/multiple/assets/favicon/JMTMfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/SDKSfavicon.ico b/multiple/assets/favicon/SDKSfavicon.ico
new file mode 100644
index 0000000..3b2f908
--- /dev/null
+++ b/multiple/assets/favicon/SDKSfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/SJDQfavicon.ico b/multiple/assets/favicon/SJDQfavicon.ico
new file mode 100644
index 0000000..c87460c
--- /dev/null
+++ b/multiple/assets/favicon/SJDQfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/TXDQfavicon.ico b/multiple/assets/favicon/TXDQfavicon.ico
new file mode 100644
index 0000000..92c8b17
--- /dev/null
+++ b/multiple/assets/favicon/TXDQfavicon.ico
Binary files differ
diff --git a/multiple/assets/logo/CHMYLogo.png b/multiple/assets/logo/CHMYLogo.png
new file mode 100644
index 0000000..d3ade0e
--- /dev/null
+++ b/multiple/assets/logo/CHMYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/HMCLLogo.png b/multiple/assets/logo/HMCLLogo.png
new file mode 100644
index 0000000..3710fcf
--- /dev/null
+++ b/multiple/assets/logo/HMCLLogo.png
Binary files differ
diff --git a/multiple/assets/logo/JMTMLogo.png b/multiple/assets/logo/JMTMLogo.png
new file mode 100644
index 0000000..ad8e952
--- /dev/null
+++ b/multiple/assets/logo/JMTMLogo.png
Binary files differ
diff --git a/multiple/assets/logo/SDKSLogo.png b/multiple/assets/logo/SDKSLogo.png
new file mode 100644
index 0000000..46b7f3c
--- /dev/null
+++ b/multiple/assets/logo/SDKSLogo.png
Binary files differ
diff --git a/multiple/assets/logo/SJDQLogo.png b/multiple/assets/logo/SJDQLogo.png
new file mode 100644
index 0000000..3657807
--- /dev/null
+++ b/multiple/assets/logo/SJDQLogo.png
Binary files differ
diff --git a/multiple/assets/logo/TXDQLogo.png b/multiple/assets/logo/TXDQLogo.png
new file mode 100644
index 0000000..a1e4efb
--- /dev/null
+++ b/multiple/assets/logo/TXDQLogo.png
Binary files differ
diff --git a/multiple/config.json b/multiple/config.json
index 0ed35b4..777276a 100644
--- a/multiple/config.json
+++ b/multiple/config.json
@@ -153,8 +153,17 @@
"DDGW": {
"env": {
"VITE_APP_TITLE": "娌冲崡涓滃ぇ楂樻俯鑺傝兘鏉愭枡鏈夐檺鍏徃",
- "VITE_BASE_API": "http://103.189.141.42:9001",
- "VITE_JAVA_API": "http://103.189.141.42:9002"
+ "VITE_BASE_API": "http://36.133.40.190:81",
+ "VITE_JAVA_API": "http://36.133.40.190:82"
+ },
+ "logo": "logo/DDGWLogo.png",
+ "favicon": "favicon/DDGWfavicon.ico"
+ },
+ "DDGW-TEST": {
+ "env": {
+ "VITE_APP_TITLE": "娌冲崡涓滃ぇ楂樻俯鑺傝兘鏉愭枡鏈夐檺鍏徃",
+ "VITE_BASE_API": "http://36.133.40.190:83",
+ "VITE_JAVA_API": "http://36.133.40.190:84"
},
"logo": "logo/DDGWLogo.png",
"favicon": "favicon/DDGWfavicon.ico"
@@ -167,6 +176,60 @@
},
"logo": "logo/XHZGLogo.png",
"favicon": "favicon/XHZGfavicon.ico"
+ },
+ "SDKS": {
+ "env": {
+ "VITE_APP_TITLE": "楣ゅ甯傚洓杈剧熆灞辫澶囨湁闄愬叕鍙�",
+ "VITE_BASE_API": "http://36.213.180.217:9520",
+ "VITE_JAVA_API": "http://36.213.180.217:9521"
+ },
+ "logo": "logo/SDKSLogo.png",
+ "favicon": "favicon/SDKSfavicon.ico"
+ },
+ "JMTM": {
+ "env": {
+ "VITE_APP_TITLE": "娌冲崡鎹风編鐗归晛绉戞妧鏈夐檺鍏徃",
+ "VITE_BASE_API": "http://36.133.40.61:81",
+ "VITE_JAVA_API": "http://36.133.40.61:82"
+ },
+ "logo": "logo/JMTMLogo.png",
+ "favicon": "favicon/JMTMfavicon.ico"
+ },
+ "CHMY": {
+ "env": {
+ "VITE_APP_TITLE": "楣ゅ鏄屽畯闀佷笟鏈夐檺鍏徃",
+ "VITE_BASE_API": "http://36.133.39.224:81",
+ "VITE_JAVA_API": "http://36.133.39.224:82"
+ },
+ "logo": "logo/CHMYLogo.png",
+ "favicon": "favicon/CHMYfavicon.ico"
+ },
+ "SJDQ": {
+ "env": {
+ "VITE_APP_TITLE": "楣ゅ甯傚北鍩庡尯鐩涗箙鐢靛櫒鏈夐檺鍏徃",
+ "VITE_BASE_API": "http://36.139.201.235:81",
+ "VITE_JAVA_API": "http://36.139.201.235:82"
+ },
+ "logo": "logo/SJDQLogo.png",
+ "favicon": "favicon/SJDQfavicon.ico"
+ },
+ "TXDQ": {
+ "env": {
+ "VITE_APP_TITLE": "楣ゅ娉颁俊鐢垫皵鏈夐檺鍏徃",
+ "VITE_BASE_API": "http://36.133.46.171:81",
+ "VITE_JAVA_API": "http://36.133.46.171:82"
+ },
+ "logo": "logo/TXDQLogo.png",
+ "favicon": "favicon/TXDQfavicon.ico"
+ },
+ "HMCL": {
+ "env": {
+ "VITE_APP_TITLE": "楣ゅ娴╅晛鏉愭枡鏈夐檺鍏徃",
+ "VITE_BASE_API": "http://36.213.90.123:9522",
+ "VITE_JAVA_API": "http://36.213.90.123:9523"
+ },
+ "logo": "logo/HMCLLogo.png",
+ "favicon": "favicon/HMCLfavicon.ico"
},
"DTZG": {
"env": {
@@ -185,15 +248,16 @@
},
"logo": "logo/XLZNLogo.png",
"favicon": "favicon/XLZNfavicon.ico"
- },"SKJS": {
- "env": {
- "VITE_APP_TITLE": "寰愬窞浠�鍏嬮噾灞炲埗鍝佹湁闄愬叕鍙�",
- "VITE_BASE_API": "http://36.213.90.123:9514",
- "VITE_JAVA_API": "http://36.213.90.123:9515"
},
- "logo": "logo/SKJSLogo.png",
- "favicon": "favicon/SKJSFavicon.ico"
-},
+ "SKJS": {
+ "env": {
+ "VITE_APP_TITLE": "寰愬窞浠�鍏嬮噾灞炲埗鍝佹湁闄愬叕鍙�",
+ "VITE_BASE_API": "http://36.213.90.123:9514",
+ "VITE_JAVA_API": "http://36.213.90.123:9515"
+ },
+ "logo": "logo/SKJSLogo.png",
+ "favicon": "favicon/SKJSFavicon.ico"
+ },
"YJYY": {
"env": {
"VITE_APP_TITLE": "寰愬窞姘镐匠娑插帇璁惧鏈夐檺鍏徃",
diff --git a/src/api/productionManagement/productionProcess.js b/src/api/productionManagement/productionProcess.js
index 783f584..09cbff9 100644
--- a/src/api/productionManagement/productionProcess.js
+++ b/src/api/productionManagement/productionProcess.js
@@ -26,6 +26,14 @@
params: query,
});
}
+
+export function listAll() {
+ return request({
+ url: "/technologyOperation/listPage",
+ method: "get",
+ params: { current: -1, size: -1 },
+ });
+}
export function add(data) {
return request({
url: "/technologyOperation/add",
diff --git a/src/api/qualityManagement/nonconformingManagement.js b/src/api/qualityManagement/nonconformingManagement.js
index 50a1b74..2433091 100644
--- a/src/api/qualityManagement/nonconformingManagement.js
+++ b/src/api/qualityManagement/nonconformingManagement.js
@@ -48,3 +48,12 @@
data: query,
});
}
+
+// 鏌ヨ涓嶅悎鏍艰鎯�
+export function getQualityUnqualifiedDetail(query) {
+ return request({
+ url: "/quality/qualityUnqualified/detail/" + query,
+ method: "get",
+ data: query,
+ });
+}
diff --git a/src/components/filePreview/index.vue b/src/components/filePreview/index.vue
index d8720c3..cd29fbc 100644
--- a/src/components/filePreview/index.vue
+++ b/src/components/filePreview/index.vue
@@ -1,8 +1,8 @@
<template>
- <el-dialog v-model="dialogVisible" title="棰勮" width="100%" fullscreen align-center :before-close="handleClose" append-to-body>
+ <el-dialog v-model="dialogVisible" title="棰勮" :width="isImage ? '60%' : '100%'" :fullscreen="!isImage" align-center :before-close="handleClose" append-to-body>
<div>
<!-- 鍥剧墖棰勮 -->
- <div v-if="isImage">
+ <div v-if="isImage" class="image-preview-container">
<img :src="imgUrl" alt="Image Preview" />
</div>
@@ -188,8 +188,18 @@
</script>
<style scoped>
+.image-preview-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ max-height: calc(100vh - 200px);
+ width: 100%;
+}
+
img {
max-width: 100%;
+ max-height: calc(100vh - 200px);
+ object-fit: contain;
display: block;
margin: 0 auto;
}
diff --git a/src/views/basicData/product/ProductSelectDialog.vue b/src/views/basicData/product/ProductSelectDialog.vue
index 590f8ca..1586f58 100644
--- a/src/views/basicData/product/ProductSelectDialog.vue
+++ b/src/views/basicData/product/ProductSelectDialog.vue
@@ -1,12 +1,18 @@
<template>
<el-dialog v-model="visible" title="閫夋嫨浜у搧" width="900px" destroy-on-close :close-on-click-modal="false">
<el-form :inline="true" :model="query" class="mb-2">
+ <el-form-item label="浜у搧澶х被">
+ <el-select v-model="query.topProductParentId" placeholder="閫夋嫨浜у搧澶х被" clearable @change="onSearch" style="width: 150px">
+ <el-option v-for="item in categoryList" :key="item.id" :label="item.label || item.productName" :value="item.id" />
+ </el-select>
+ </el-form-item>
+
<el-form-item label="浜у搧鍚嶇О">
- <el-input v-model="query.productName" placeholder="杈撳叆浜у搧鍚嶇О" clearable @keyup.enter="onSearch" style="width: 200px" />
+ <el-input v-model="query.productName" placeholder="杈撳叆浜у搧鍚嶇О" clearable @keyup.enter="onSearch" style="width: 150px" />
</el-form-item>
<el-form-item label="浜у搧鍨嬪彿">
- <el-input v-model="query.model" placeholder="杈撳叆浜у搧鍨嬪彿" clearable @keyup.enter="onSearch" style="width: 200px" />
+ <el-input v-model="query.model" placeholder="杈撳叆浜у搧鍨嬪彿" clearable @keyup.enter="onSearch" style="width: 150px" />
</el-form-item>
<el-form-item>
@@ -45,6 +51,7 @@
import { computed, onMounted, reactive, ref, watch, nextTick } from "vue";
import { ElMessage } from "element-plus";
import { productModelList, productModelListByUrl } from '@/api/basicData/productModel'
+import { productTreeList } from '@/api/basicData/product'
export type ProductRow = {
id: number;
@@ -70,7 +77,19 @@
const query = reactive({
productName: "",
model: "",
+ topProductParentId: undefined as number | undefined,
});
+
+const categoryList = ref<any[]>([]);
+
+async function loadCategory() {
+ try {
+ const res = await productTreeList();
+ categoryList.value = res || [];
+ } catch (e) {
+ console.error("Failed to load product categories", e);
+ }
+}
const page = reactive({
pageNum: 1,
@@ -132,6 +151,7 @@
function onReset() {
query.productName = "";
query.model = "";
+ query.topProductParentId = props.topProductParentId;
page.pageNum = 1;
loadData();
}
@@ -162,7 +182,7 @@
model: query.model.trim(),
current: page.pageNum,
size: page.pageSize,
- topProductParentId: props.topProductParentId,
+ topProductParentId: query.topProductParentId ?? props.topProductParentId,
};
const res: any = props.requestUrl
? await productModelListByUrl(props.requestUrl, params)
@@ -179,10 +199,13 @@
watch(() => props.modelValue, (visible) => {
if (visible) {
multipleSelection.value = [];
+ query.topProductParentId = props.topProductParentId;
}
});
onMounted(() => {
- loadData()
+ query.topProductParentId = props.topProductParentId;
+ loadCategory();
+ loadData();
})
</script>
diff --git a/src/views/collaborativeApproval/approvalProcess/components/viewDetailDia.vue b/src/views/collaborativeApproval/approvalProcess/components/viewDetailDia.vue
new file mode 100644
index 0000000..196efde
--- /dev/null
+++ b/src/views/collaborativeApproval/approvalProcess/components/viewDetailDia.vue
@@ -0,0 +1,630 @@
+<template>
+ <div>
+ <el-dialog v-model="dialogFormVisible"
+ :title="operationType === 'approval' ? '瀹℃壒' : '璇︽儏'"
+ width="700px"
+ @close="closeDia">
+ <el-descriptions border :column="1" class="approval-detail">
+ <el-descriptions-item label="娴佺▼缂栧彿">{{ form.approveId }}</el-descriptions-item>
+ <el-descriptions-item label="鐢宠閮ㄩ棬">{{ form.approveDeptName }}</el-descriptions-item>
+ <el-descriptions-item :label="props.approveType == 5 ? '閲囪喘鍚堝悓鍙�' : '瀹℃壒浜嬬敱'" v-if="!isQuotationApproval && !isPurchaseApproval">
+ {{ form.approveReason }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎶ラ攢閲戦" v-if="Number(props.approveType) === 4">
+ {{ form.price }}
+ </el-descriptions-item>
+ </el-descriptions>
+ <!-- 鎶ヤ环瀹℃壒锛氬睍绀烘姤浠疯鎯咃紙澶嶇敤閿�鍞姤浠�"鏌ョ湅璇︽儏瀵硅瘽妗�"鍐呭缁撴瀯锛� -->
+ <div v-if="isQuotationApproval"
+ style="margin: 10px 0 18px;">
+ <el-divider content-position="left">鎶ヤ环璇︽儏</el-divider>
+ <el-skeleton :loading="quotationLoading"
+ animated>
+ <template #template>
+ <el-skeleton-item variant="h3"
+ style="width: 30%" />
+ <el-skeleton-item variant="text"
+ style="width: 100%" />
+ <el-skeleton-item variant="text"
+ style="width: 100%" />
+ </template>
+ <template #default>
+ <el-empty v-if="!currentQuotation || !currentQuotation.quotationNo"
+ description="鏈煡璇㈠埌瀵瑰簲鎶ヤ环璇︽儏" />
+ <template v-else>
+ <el-descriptions :column="2"
+ border>
+ <el-descriptions-item label="鎶ヤ环鍗曞彿">{{ currentQuotation.quotationNo }}</el-descriptions-item>
+ <el-descriptions-item label="瀹㈡埛鍚嶇О">{{ currentQuotation.customer }}</el-descriptions-item>
+ <el-descriptions-item label="涓氬姟鍛�">{{ currentQuotation.salesperson }}</el-descriptions-item>
+ <el-descriptions-item label="鎶ヤ环鏃ユ湡">{{ currentQuotation.quotationDate }}</el-descriptions-item>
+ <el-descriptions-item label="鏈夋晥鏈熻嚦">{{ currentQuotation.validDate }}</el-descriptions-item>
+ <el-descriptions-item label="浠樻鏂瑰紡">{{ currentQuotation.paymentMethod }}</el-descriptions-item>
+ <el-descriptions-item label="鎶ヤ环鎬婚"
+ :span="2">
+ <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">
+ {{ formatCurrency(currentQuotation.totalAmount) }}
+ </span>
+ </el-descriptions-item>
+ </el-descriptions>
+ <div style="margin-top: 20px;">
+ <h4>浜у搧鏄庣粏</h4>
+ <el-table :data="currentQuotation.products || []"
+ border
+ style="width: 100%">
+ <el-table-column prop="product"
+ label="浜у搧鍚嶇О" />
+ <el-table-column prop="specification"
+ label="瑙勬牸鍨嬪彿" />
+ <el-table-column prop="unit"
+ label="鍗曚綅" />
+ <el-table-column prop="unitPrice"
+ label="鍗曚环">
+ <template #default="scope">{{ formatCurrency(scope.row.unitPrice) }}</template>
+ </el-table-column>
+ </el-table>
+ </div>
+ <div v-if="currentQuotation.remark"
+ style="margin-top: 20px;">
+ <h4>澶囨敞</h4>
+ <p>{{ currentQuotation.remark }}</p>
+ </div>
+ </template>
+ </template>
+ </el-skeleton>
+ </div>
+ <!-- 閲囪喘瀹℃壒锛氬睍绀洪噰璐鎯� -->
+ <div v-if="isPurchaseApproval"
+ style="margin: 10px 0 18px;">
+ <el-divider content-position="left">閲囪喘璇︽儏</el-divider>
+ <el-skeleton :loading="purchaseLoading"
+ animated>
+ <template #template>
+ <el-skeleton-item variant="h3"
+ style="width: 30%" />
+ <el-skeleton-item variant="text"
+ style="width: 100%" />
+ <el-skeleton-item variant="text"
+ style="width: 100%" />
+ </template>
+ <template #default>
+ <el-empty v-if="!currentPurchase || !currentPurchase.purchaseContractNumber"
+ description="鏈煡璇㈠埌瀵瑰簲閲囪喘璇︽儏" />
+ <template v-else>
+ <el-descriptions :column="2"
+ border>
+ <el-descriptions-item label="閲囪喘鍚堝悓鍙�">{{ currentPurchase.purchaseContractNumber }}</el-descriptions-item>
+ <el-descriptions-item label="渚涘簲鍟嗗悕绉�">{{ currentPurchase.supplierName }}</el-descriptions-item>
+ <el-descriptions-item label="椤圭洰鍚嶇О">{{ currentPurchase.projectName }}</el-descriptions-item>
+ <el-descriptions-item label="閿�鍞悎鍚屽彿">{{ currentPurchase.salesContractNo }}</el-descriptions-item>
+ <el-descriptions-item label="绛捐鏃ユ湡">{{ currentPurchase.executionDate }}</el-descriptions-item>
+ <el-descriptions-item label="褰曞叆鏃ユ湡">{{ currentPurchase.entryDate }}</el-descriptions-item>
+ <el-descriptions-item label="浠樻鏂瑰紡">{{ currentPurchase.paymentMethod }}</el-descriptions-item>
+ <el-descriptions-item label="鍚堝悓閲戦"
+ :span="2">
+ <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">
+ {{ formatCurrency(currentPurchase.contractAmount) }}
+ </span>
+ </el-descriptions-item>
+ </el-descriptions>
+ <div style="margin-top: 20px;">
+ <h4>浜у搧鏄庣粏</h4>
+ <el-table :data="currentPurchase.productData || []"
+ border
+ style="width: 100%">
+ <el-table-column prop="productCategory"
+ label="浜у搧鍚嶇О" />
+ <el-table-column prop="specificationModel"
+ label="瑙勬牸鍨嬪彿" />
+ <el-table-column prop="unit"
+ label="鍗曚綅" />
+ <el-table-column prop="quantity"
+ label="鏁伴噺" />
+ <el-table-column prop="taxInclusiveUnitPrice"
+ label="鍚◣鍗曚环">
+ <template #default="scope">{{ formatCurrency(scope.row.taxInclusiveUnitPrice) }}</template>
+ </el-table-column>
+ <el-table-column prop="taxInclusiveTotalPrice"
+ label="鍚◣鎬讳环">
+ <template #default="scope">{{ formatCurrency(scope.row.taxInclusiveTotalPrice) }}</template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </template>
+ </template>
+ </el-skeleton>
+ </div>
+ <!-- 鍙戣揣瀹℃壒锛氬睍绀哄彂璐ц鎯� -->
+ <div v-if="isDeliveryApproval"
+ style="margin: 10px 0 18px;">
+ <el-divider content-position="left">鍙戣揣璇︽儏</el-divider>
+ <el-skeleton :loading="deliveryLoading"
+ animated>
+ <template #template>
+ <el-skeleton-item variant="h3"
+ style="width: 30%" />
+ <el-skeleton-item variant="text"
+ style="width: 100%" />
+ <el-skeleton-item variant="text"
+ style="width: 100%" />
+ </template>
+ <template #default>
+ <el-empty v-if="!currentDelivery || !currentDelivery.shippingInfo"
+ description="鏈煡璇㈠埌瀵瑰簲鍙戣揣璇︽儏" />
+ <template v-else>
+ <el-descriptions :column="2"
+ border>
+ <el-descriptions-item label="閿�鍞鍗�">{{ currentDelivery.shippingInfo.salesContractNo || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="鍙戣揣璁㈠崟鍙�">{{ currentDelivery.shippingInfo.shippingNo || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="瀹㈡埛鍚嶇О">{{ currentDelivery.shippingInfo.customerName || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="鍙戣揣绫诲瀷">{{ currentDelivery.shippingInfo.type || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="鍙戣揣鏃ユ湡">{{ currentDelivery.shippingInfo.shippingDate || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="瀹℃牳鐘舵��">{{ currentDelivery.shippingInfo.status || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="鍙戣揣杞︾墝鍙�">{{ currentDelivery.shippingInfo.shippingCarNumber || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="蹇�掑叕鍙�">{{ currentDelivery.shippingInfo.expressCompany || '--' }}</el-descriptions-item>
+ <el-descriptions-item label="蹇�掑崟鍙�"
+ :span="2">{{ currentDelivery.shippingInfo.expressNumber || '--' }}</el-descriptions-item>
+ </el-descriptions>
+ <div style="margin-top: 20px;">
+ <h4>浜у搧鏄庣粏</h4>
+ <el-table :data="deliveryProductList"
+ border
+ size="small"
+ style="width: 100%">
+ <el-table-column prop="batchNo"
+ label="鎵瑰彿"
+ show-overflow-tooltip />
+ <el-table-column prop="productName"
+ label="浜у搧鍚嶇О"
+ show-overflow-tooltip />
+ <el-table-column prop="specificationModel"
+ label="瑙勬牸鍨嬪彿"
+ show-overflow-tooltip />
+ <el-table-column prop="deliveryQuantity"
+ label="鍙戣揣鏁伴噺"
+ align="center" />
+ </el-table>
+ </div>
+ <div v-if="currentDelivery.shippingInfo.storageBlobVOs && currentDelivery.shippingInfo.storageBlobVOs.length"
+ style="margin-top: 20px;">
+ <h4>鍙戣揣鍥剧墖</h4>
+ <ImagePreview :file-list="currentDelivery.shippingInfo.storageBlobVOs" />
+ </div>
+ </template>
+ </template>
+ </el-skeleton>
+ </div>
+ <div class="activities-wrapper" style="margin-top: 20px;">
+ <el-steps :active="getActiveStep()"
+ finish-status="success"
+ process-status="process"
+ align-center
+ direction="vertical">
+ <el-step v-for="(activity, index) in activities"
+ :key="index"
+ finish-status="success"
+ :title="getNodeTitle(index, activities.length)"
+ :description="activity.approveNodeUser"
+ :icon="getNodeIcon(activity, index)">
+ <template #icon>
+ <el-icon v-if="activity.approveNodeStatus === 2"
+ color="red"
+ :size="22">
+ <WarningFilled />
+ </el-icon>
+ <el-icon v-else-if="activity.isShen"
+ color="#1890ff"
+ :size="22">
+ <Edit />
+ </el-icon>
+ <el-icon v-else-if="activity.approveNodeStatus === 1"
+ color="#67C23A"
+ :size="26">
+ <Check />
+ </el-icon>
+ <el-icon v-else
+ color="#C0C4CC"
+ :size="22">
+ <MoreFilled />
+ </el-icon>
+ </template>
+ <template #title>
+ <span style="color: #000000">{{ getNodeTitle(index, activities.length) }}</span>
+ </template>
+ <template #description>
+ <div class="node-user">
+ <div class="avatar-wrapper">
+ <img :src="userStore.avatar"
+ class="user-avatar"
+ alt="" />
+ </div>
+ <span style="color: #000000">{{ activity.approveNodeUser }}</span>
+ </div>
+ <div class="node-reason" v-if="activity.approveNodeReason">
+ <span>瀹℃壒鎰忚锛�</span>{{ activity.approveNodeReason }}
+ </div>
+ </template>
+ </el-step>
+ </el-steps>
+ </div>
+
+ <el-descriptions border :column="1" style="margin-top: 20px;" class="approval-detail">
+ <el-descriptions-item label="闄勪欢">
+ <ImageUpload v-if="attachments && attachments.length > 0" v-model:file-list="attachments" :disabled="true" />
+ <span v-else style="color: #999;">鏃犻檮浠�</span>
+ </el-descriptions-item>
+ </el-descriptions>
+
+ <el-form ref="formRef" :model="form" label-width="100px" style="margin-top: 20px;">
+ <el-form-item label="瀹℃壒鎰忚" prop="approveReason" v-if="operationType === 'approval'">
+ <el-input v-model="form.approveReason" type="textarea" placeholder="璇疯緭鍏ュ鎵规剰瑙�" />
+ </el-form-item>
+ </el-form>
+
+ <template #footer
+ v-if="operationType === 'approval'">
+ <div class="dialog-footer">
+ <el-button type="primary"
+ :loading="submitLoading"
+ @click="submitForm(2)">涓嶉�氳繃</el-button>
+ <el-button type="primary"
+ :loading="submitLoading"
+ @click="submitForm(1)">閫氳繃</el-button>
+ <el-button @click="closeDia">鍙栨秷</el-button>
+ </div>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script setup>
+ import {
+ computed,
+ getCurrentInstance,
+ nextTick,
+ reactive,
+ ref,
+ toRefs,
+ } from "vue";
+ import {
+ approveProcessDetails,
+ getDept,
+ updateApproveNode,
+ } from "@/api/collaborativeApproval/approvalProcess.js";
+ import useUserStore from "@/store/modules/user.js";
+ import {
+ WarningFilled,
+ Edit,
+ Check,
+ MoreFilled,
+ } from "@element-plus/icons-vue";
+ import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
+ import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js";
+ import { getDeliveryDetailByShippingNo } from "@/api/salesManagement/deliveryLedger.js";
+ import ImagePreview from "@/components/AttachmentPreview/image/index.vue";
+ import { formatCurrency } from "@/utils/numberFormat";
+ import ImageUpload from "@/components/AttachmentUpload/image/index.vue";
+ import { attachmentList } from "@/api/basicData/storageAttachment.js";
+
+ const emit = defineEmits(["close"]);
+ const { proxy } = getCurrentInstance();
+
+ const props = defineProps({
+ approveType: {
+ type: [Number, String],
+ default: 0,
+ },
+ });
+
+ const dialogFormVisible = ref(false);
+ const operationType = ref("");
+ const activities = ref([]);
+ const attachments = ref([]);
+ const formRef = ref(null);
+ const userStore = useUserStore();
+ const productOptions = ref([]);
+ const quotationLoading = ref(false);
+ const currentQuotation = ref({});
+ const purchaseLoading = ref(false);
+ const currentPurchase = ref({});
+ const deliveryLoading = ref(false);
+ const currentDelivery = ref({});
+ const deliveryProductList = ref([]);
+ const submitLoading = ref(false);
+ const isQuotationApproval = computed(() => Number(props.approveType) === 6);
+ const isPurchaseApproval = computed(() => Number(props.approveType) === 5);
+ const isDeliveryApproval = computed(() => Number(props.approveType) === 7);
+
+ const data = reactive({
+ form: {
+ approveId: "",
+ approveDeptId: "",
+ approveReason: "",
+ checkResult: "",
+ },
+ });
+ const { form } = toRefs(data);
+
+ // 鑺傜偣鏍囬
+ const getNodeTitle = (index, len) => {
+ if (index === len - 1) return "缁撴潫";
+ return "瀹℃壒";
+ };
+
+ // 鑾峰彇褰撳墠婵�娲绘楠�
+ const getActiveStep = () => {
+ // 濡傛灉鎵�鏈� isShen 閮戒负 false锛岃繑鍥炴渶鍚庝竴涓楠わ紙鍏ㄩ儴瀹屾垚锛�
+ const hasActive = activities.value.some(a => a.isShen === true);
+ if (!hasActive) return activities.value.length;
+ // 褰撳墠鑺傜偣绱㈠紩
+ return activities.value.findIndex(a => a.isShen == true);
+ };
+ // 姝ラicon
+ const getNodeIcon = (activity, index) => {
+ if (activity.approveNodeStatus === 2) return "el-icon-warning"; // 涓嶉�氳繃
+ if (activity.isShen) return "Edit";
+ return "";
+ };
+
+ // 鎵撳紑寮规
+ const openDialog = (type, row) => {
+ operationType.value = type;
+ dialogFormVisible.value = true;
+ currentQuotation.value = {};
+ currentPurchase.value = {};
+ form.value = { ...row };
+
+ // 鑾峰彇闄勪欢
+ attachments.value = [];
+ attachmentList({ recordId: row.id, recordType: 'approve_process' }).then(res => {
+ attachments.value = res.data || [];
+ });
+ // 绔嬪嵆娓呴櫎琛ㄥ崟楠岃瘉鐘舵�侊紙鍥犱负瀛楁鏄痙isabled鐨勶紝涓嶉渶瑕侀獙璇侊級
+ nextTick(() => {
+ if (formRef.value) {
+ formRef.value.clearValidate();
+ }
+ });
+ // 纭繚閫夐」鍔犺浇瀹屾垚鍚庡啀鍖归厤鍊肩被鍨�
+ getProductOptions().then(() => {
+ // 纭繚鍊肩被鍨嬪尮閰嶏紙濡傛灉閫夐」宸插姞杞斤級
+ if (productOptions.value.length > 0 && form.value.approveDeptId) {
+ const matchedOption = productOptions.value.find(
+ opt =>
+ opt.deptId == form.value.approveDeptId ||
+ String(opt.deptId) === String(form.value.approveDeptId)
+ );
+ if (matchedOption) {
+ form.value.approveDeptId = matchedOption.deptId;
+ }
+ }
+ // 鍐嶆娓呴櫎楠岃瘉锛岀‘淇濋�夐」鍔犺浇鍚庡�煎尮閰嶆纭�
+ nextTick(() => {
+ if (formRef.value) {
+ formRef.value.clearValidate();
+ }
+ });
+ });
+
+ // 鎶ヤ环瀹℃壒锛氱敤瀹℃壒浜嬬敱瀛楁鎵胯浇鐨�"鎶ヤ环鍗曞彿"鍘绘煡鎶ヤ环鍒楄〃
+ if (isQuotationApproval.value) {
+ const quotationNo = row?.approveReason;
+ if (quotationNo) {
+ quotationLoading.value = true;
+ getQuotationList({ quotationNo })
+ .then(res => {
+ const records = res?.data?.records || [];
+ currentQuotation.value = records[0] || {};
+ })
+ .finally(() => {
+ quotationLoading.value = false;
+ });
+ }
+ }
+
+ // 閲囪喘瀹℃壒锛氱敤瀹℃壒浜嬬敱瀛楁鎵胯浇鐨�"閲囪喘鍚堝悓鍙�"鍘绘煡閲囪喘璇︽儏
+ if (isPurchaseApproval.value) {
+ const purchaseContractNumber = row?.approveReason;
+ if (purchaseContractNumber) {
+ purchaseLoading.value = true;
+ getPurchaseByCode({ purchaseContractNumber })
+ .then(res => {
+ currentPurchase.value = res;
+ })
+ .catch(err => {
+ console.error("鏌ヨ閲囪喘璇︽儏澶辫触:", err);
+ proxy.$modal.msgError("鏌ヨ閲囪喘璇︽儏澶辫触");
+ })
+ .finally(() => {
+ purchaseLoading.value = false;
+ });
+ }
+ }
+ // 鍙戣揣瀹℃壒锛氱敤瀹℃壒浜嬬敱瀛楁鎵胯浇鐨�"鍙戣揣鍗曞彿"鍘绘煡鍙戣揣璇︽儏
+ if (isDeliveryApproval.value) {
+ const deliveryNo = row?.approveReason;
+ if (deliveryNo) {
+ deliveryLoading.value = true;
+ currentDelivery.value = {};
+ deliveryProductList.value = [];
+ getDeliveryDetailByShippingNo({ shippingNo: deliveryNo })
+ .then(res => {
+ const detailData = res?.data || res || {};
+ currentDelivery.value = detailData;
+ deliveryProductList.value =
+ detailData.shippingProductDetailDtoList || [];
+ })
+ .catch(err => {
+ console.error("鏌ヨ鍙戣揣璇︽儏澶辫触:", err);
+ proxy.$modal.msgError("鏌ヨ鍙戣揣璇︽儏澶辫触");
+ })
+ .finally(() => {
+ deliveryLoading.value = false;
+ });
+ }
+ }
+
+ approveProcessDetails(row.approveId).then(res => {
+ activities.value = res.data;
+ // 澧炲姞isApproval瀛楁
+ activities.value.forEach(item => {
+ if (item.url && item.url.includes("word")) {
+ item.urlTem = item.url.replaceAll("word", "img");
+ } else {
+ item.urlTem = item.url;
+ }
+ if (item.approveNodeStatus === 2) {
+ item.isApproval = "宸查┏鍥�";
+ } else if (item.approveNodeStatus === 1) {
+ item.isApproval = "宸插悓鎰�";
+ } else {
+ item.isApproval = "鏈鎵�";
+ }
+ });
+ });
+ };
+
+ const getDeliveryProductInfoList = () => {
+ const row = currentDelivery.value;
+ if (!row) return [];
+ const normalizeBatchNoList = value => {
+ if (Array.isArray(value)) return value;
+ if (typeof value === "string" && value.includes(",")) {
+ return value
+ .split(",")
+ .map(item => item.trim())
+ .filter(Boolean);
+ }
+ return value ? [value] : [];
+ };
+ const detailList = deliveryProductList.value.length
+ ? deliveryProductList.value
+ : [
+ row.batchNoDetailList,
+ row.batchNoList,
+ row.shippingBatchList,
+ row.shippingInfoDetailList,
+ row.detailList,
+ row.batchDetailList,
+ ].find(value => Array.isArray(value) && value.length);
+ const batchNoList = normalizeBatchNoList(row.batchNo);
+ const toTableRow = (item = {}) => ({
+ batchNo:
+ typeof item === "string" || typeof item === "number"
+ ? item
+ : item.batchNo ?? item.batchNumber ?? row.batchNo ?? "--",
+ productName: item.productName ?? row.productName ?? "--",
+ specificationModel:
+ item.specificationModel ?? item.model ?? row.specificationModel ?? "--",
+ deliveryQuantity:
+ item.deliveryQuantity ??
+ item.quantity ??
+ item.shippingQuantity ??
+ row.deliveryQuantity ??
+ row.quantity ??
+ "--",
+ });
+ if (detailList?.length) {
+ return detailList.map(toTableRow);
+ }
+ if (batchNoList.length) {
+ return batchNoList.map(batchNo => toTableRow({ batchNo }));
+ }
+ return [toTableRow()];
+ };
+ const getApprovalStatusText = status => {
+ const statusMap = {
+ 0: "寰呭鏍�",
+ 1: "瀹℃牳閫氳繃",
+ 2: "瀹℃牳鎷掔粷",
+ 3: "瀹℃牳涓�",
+ };
+ return statusMap[status] || "寰呭鏍�";
+ };
+
+ const getProductOptions = () => {
+ return getDept().then(res => {
+ productOptions.value = res.data;
+ });
+ };
+ // 鎻愪氦瀹℃壒
+ const submitForm = status => {
+ const filteredActivities = activities.value.filter(
+ activity => activity.isShen
+ );
+ if (!filteredActivities || filteredActivities.length === 0) {
+ proxy.$modal.msgError("鏈壘鍒板緟瀹℃壒鐨勮妭鐐�");
+ return;
+ }
+ const currentActivity = filteredActivities[0];
+ if (!currentActivity) {
+ proxy.$modal.msgError("鏈壘鍒板緟瀹℃壒鐨勮妭鐐�");
+ return;
+ }
+ currentActivity.approveNodeStatus = status;
+ // 鍒ゆ柇鏄惁涓烘渶鍚庝竴姝�
+ const isLast =
+ activities.value.findIndex(a => a.isShen) === activities.value.length - 1;
+ submitLoading.value = true;
+ updateApproveNode({ ...currentActivity, isLast })
+ .then(() => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ })
+ .finally(() => {
+ submitLoading.value = false;
+ });
+ };
+ // 鍏抽棴寮规
+ const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogFormVisible.value = false;
+ quotationLoading.value = false;
+ currentQuotation.value = {};
+ purchaseLoading.value = false;
+ currentPurchase.value = {};
+ submitLoading.value = false;
+ emit("close");
+ };
+ defineExpose({
+ openDialog,
+ });
+</script>
+
+<style scoped>
+ .node-user {
+ margin: 10px 0;
+ font-size: 16px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+ .node-status {
+ color: #1890ff;
+ margin-left: 8px;
+ font-size: 14px;
+ }
+ .node-reason {
+ font-size: 15px;
+ color: #333;
+ margin: 10px 0;
+ }
+ .user-avatar {
+ cursor: pointer;
+ width: 30px;
+ height: 30px;
+ border-radius: 50px;
+ }
+ .signImg {
+ cursor: pointer;
+ width: 200px;
+ height: 60px;
+ }
+ .approval-detail :deep(.el-upload) {
+ display: none !important;
+ }
+ .approval-detail :deep(.upload-tip) {
+ display: none !important;
+ }
+</style>
\ No newline at end of file
diff --git a/src/views/collaborativeApproval/approvalProcess/index.vue b/src/views/collaborativeApproval/approvalProcess/index.vue
index 158916c..b3c6daa 100644
--- a/src/views/collaborativeApproval/approvalProcess/index.vue
+++ b/src/views/collaborativeApproval/approvalProcess/index.vue
@@ -124,8 +124,9 @@
</el-card>
<!-- 寮圭獥缁勪欢 -->
- <info-form-dia ref="infoFormDia" @close="handleQuery" :approveType="currentApproveType"></info-form-dia>
- <approval-dia ref="approvalDia" @close="handleQuery" :approveType="currentApproveType"></approval-dia>
+ <info-form-dia ref="infoFormDia" @close="handleDialogClose" :approveType="currentApproveType"></info-form-dia>
+ <approval-dia ref="approvalDia" @close="handleDialogClose" :approveType="currentApproveType"></approval-dia>
+ <view-detail-dia ref="viewDetailDia" @close="handleDialogClose" :approveType="currentApproveType"></view-detail-dia>
<FileList v-if="fileDialogVisible"
v-model:visible="fileDialogVisible"
record-type="approve_process"
@@ -140,6 +141,7 @@
import { useRoute } from 'vue-router';
import InfoFormDia from "@/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue";
import ApprovalDia from "@/views/collaborativeApproval/approvalProcess/components/approvalDia.vue";
+import ViewDetailDia from "@/views/collaborativeApproval/approvalProcess/components/viewDetailDia.vue";
import {approveProcessDelete, approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess.js";
import useUserStore from "@/store/modules/user";
const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue"));
@@ -393,12 +395,17 @@
});
const infoFormDia = ref()
const approvalDia = ref()
+const viewDetailDia = ref()
const { proxy } = getCurrentInstance()
// 鏌ヨ鍒楄〃
/** 鎼滅储鎸夐挳鎿嶄綔 */
const handleQuery = () => {
page.current = 1;
+ getList();
+};
+
+const handleDialogClose = () => {
getList();
};
@@ -425,6 +432,10 @@
page.total = res.data.total;
// 鏇存柊褰撳墠绫诲瀷鏁伴噺
typeCounts.value[activeTab.value] = res.data.total;
+ if (res.data.records.length === 0 && page.current > 1) {
+ page.current = Math.max(1, Math.ceil(res.data.total / page.size));
+ getList();
+ }
}).catch(err => {
tableLoading.value = false;
})
@@ -470,7 +481,11 @@
// 鎵撳紑鏂板妫�楠屽脊妗�
const openApprovalDia = (type, row) => {
nextTick(() => {
- approvalDia.value?.openDialog(type, row)
+ if (type === 'view') {
+ viewDetailDia.value?.openDialog(type, row)
+ } else {
+ approvalDia.value?.openDialog(type, row)
+ }
})
};
diff --git a/src/views/equipmentManagement/ledger/index.vue b/src/views/equipmentManagement/ledger/index.vue
index af10532..6a70d46 100644
--- a/src/views/equipmentManagement/ledger/index.vue
+++ b/src/views/equipmentManagement/ledger/index.vue
@@ -115,10 +115,10 @@
<el-descriptions :column="2" border>
<el-descriptions-item label="璁惧鍚嶇О">{{ detailData.deviceName }}</el-descriptions-item>
<el-descriptions-item label="瑙勬牸鍨嬪彿">{{ detailData.deviceModel }}</el-descriptions-item>
- <el-descriptions-item label="璁惧鍝佺墝">{{ detailData.deviceBrand }}</el-descriptions-item>
+ <!-- <el-descriptions-item label="璁惧鍝佺墝">{{ detailData.deviceBrand }}</el-descriptions-item> -->
<el-descriptions-item label="璁惧绫诲瀷">{{ detailData.type }}</el-descriptions-item>
<el-descriptions-item label="渚涘簲鍟�">{{ detailData.supplierName }}</el-descriptions-item>
- <el-descriptions-item label="瀛樻斁浣嶇疆">{{ detailData.storageLocation }}</el-descriptions-item>
+ <!-- <el-descriptions-item label="瀛樻斁浣嶇疆">{{ detailData.storageLocation }}</el-descriptions-item> -->
<el-descriptions-item label="鍗曚綅">{{ detailData.unit }}</el-descriptions-item>
<el-descriptions-item label="鏁伴噺">{{ detailData.number }}</el-descriptions-item>
<el-descriptions-item label="鍚敤鎶樻棫">{{ detailData.isDepr === 1 ? '鏄�' : '鍚�' }}</el-descriptions-item>
@@ -219,10 +219,10 @@
label: "瑙勬牸鍨嬪彿",
prop: "deviceModel",
},
- {
- label: "璁惧鍝佺墝",
- prop: "deviceBrand",
- },
+ // {
+ // label: "璁惧鍝佺墝",
+ // prop: "deviceBrand",
+ // },
{
label: "璁惧绫诲瀷",
prop: "type",
@@ -231,10 +231,10 @@
label: "渚涘簲鍟�",
prop: "supplierName",
},
- {
- label: "瀛樻斁浣嶇疆",
- prop: "storageLocation",
- },
+ // {
+ // label: "瀛樻斁浣嶇疆",
+ // prop: "storageLocation",
+ // },
{
label: "鏁伴噺",
prop: "number",
diff --git a/src/views/equipmentManagement/repair/Modal/RepairDetailModal.vue b/src/views/equipmentManagement/repair/Modal/RepairDetailModal.vue
new file mode 100644
index 0000000..fab6dd7
--- /dev/null
+++ b/src/views/equipmentManagement/repair/Modal/RepairDetailModal.vue
@@ -0,0 +1,96 @@
+<template>
+ <el-dialog
+ v-model="visible"
+ title="璁惧鎶ヤ慨璇︽儏"
+ width="800px"
+ destroy-on-close
+ :close-on-click-modal="false"
+ @close="handleClose"
+ >
+ <el-descriptions border :column="2" class="repair-detail">
+ <el-descriptions-item label="璁惧鍚嶇О">{{ form.deviceName }}</el-descriptions-item>
+ <el-descriptions-item label="瑙勬牸鍨嬪彿">{{ form.deviceModel }}</el-descriptions-item>
+
+ <el-descriptions-item label="鎶ヤ慨鏃ユ湡">
+ {{ form.repairTime ? dayjs(form.repairTime).format("YYYY-MM-DD") : "" }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鎶ヤ慨浜�">{{ form.repairName }}</el-descriptions-item>
+
+ <el-descriptions-item label="鎶ヤ慨椤圭洰">{{ form.machineryCategory }}</el-descriptions-item>
+ <el-descriptions-item label="缁翠慨浜�">{{ form.maintenanceName }}</el-descriptions-item>
+
+ <el-descriptions-item label="鎶ヤ慨鐘舵��">
+ <el-tag v-if="form.status === 2" type="danger">澶辫触</el-tag>
+ <el-tag v-else-if="form.status === 1" type="success">宸查獙鏀�</el-tag>
+ <el-tag v-else-if="form.status === 3" type="info">寰呴獙鏀�</el-tag>
+ <el-tag v-else-if="form.status === 0" type="warning">寰呯淮淇�</el-tag>
+ </el-descriptions-item>
+ <el-descriptions-item label="鏁呴殰鐜拌薄">{{ form.remark }}</el-descriptions-item>
+
+ <!-- 楠屾敹淇℃伅 -->
+ <template v-if="form.status === 1 || form.status === 3">
+ <el-descriptions-item label="楠屾敹浜�">{{ form.acceptanceName }}</el-descriptions-item>
+ <el-descriptions-item label="楠屾敹鏃堕棿">{{ form.acceptanceTime }}</el-descriptions-item>
+ <el-descriptions-item label="楠屾敹澶囨敞" :span="2">{{ form.acceptanceRemark }}</el-descriptions-item>
+ </template>
+
+ <!-- 闄勪欢 -->
+ <el-descriptions-item label="闄勪欢" :span="2">
+ <ImageUpload
+ v-if="form.storageBlobDTOs && form.storageBlobDTOs.length > 0"
+ v-model:file-list="form.storageBlobDTOs"
+ :disabled="true"
+ />
+ <span v-else style="color: #999;">鏃犻檮浠�</span>
+ </el-descriptions-item>
+ </el-descriptions>
+
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="handleClose">鍏抽棴</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { ref, nextTick } from "vue";
+import dayjs from "dayjs";
+import { getRepairById } from "@/api/equipmentManagement/repair";
+import ImageUpload from "@/components/AttachmentUpload/image/index.vue";
+
+const visible = ref(false);
+const form = ref({});
+
+const openView = async (id) => {
+ visible.value = true;
+ await nextTick();
+ try {
+ const { data } = await getRepairById(id);
+ // 纭繚闄勪欢瀛楁鍚嶇О鍖归厤缁勪欢闇�瑕�
+ data.storageBlobDTOs = data.storageBlobVOs || [];
+ form.value = data || {};
+ } catch (error) {
+ console.error("Failed to load repair details:", error);
+ }
+};
+
+const handleClose = () => {
+ visible.value = false;
+ form.value = {};
+};
+
+defineExpose({
+ openView,
+});
+</script>
+
+<style scoped>
+.repair-detail :deep(.el-descriptions__label) {
+ width: 120px;
+ justify-content: flex-end;
+}
+.repair-detail :deep(.el-upload) {
+ display: none !important;
+}
+</style>
diff --git a/src/views/equipmentManagement/repair/index.vue b/src/views/equipmentManagement/repair/index.vue
index f1573cb..7cbff88 100644
--- a/src/views/equipmentManagement/repair/index.vue
+++ b/src/views/equipmentManagement/repair/index.vue
@@ -136,6 +136,7 @@
</div>
<RepairModal ref="repairModalRef"
@ok="getTableData" />
+ <RepairDetailModal ref="repairDetailModalRef" />
<MaintainModal ref="maintainModalRef"
@ok="getTableData" />
<AcceptanceModal ref="acceptanceModalRef"
@@ -158,6 +159,7 @@
import { usePaginationApi } from "@/hooks/usePaginationApi";
import { getRepairPage, delRepair } from "@/api/equipmentManagement/repair";
import RepairModal from "./Modal/RepairModal.vue";
+ import RepairDetailModal from "./Modal/RepairDetailModal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import dayjs from "dayjs";
import MaintainModal from "./Modal/MaintainModal.vue";
@@ -174,6 +176,7 @@
// 妯℃�佹瀹炰緥
const repairModalRef = ref();
+ const repairDetailModalRef = ref();
const maintainModalRef = ref();
const acceptanceModalRef = ref();
@@ -274,14 +277,14 @@
return multipleList.value.some(item => item.status === 1);
});
- // 鏂板鎶ヤ慨
+ // 鏂板璁惧鎶ヤ慨
const addRepair = () => {
repairModalRef.value.openAdd();
};
- // 璇︽儏鏌ョ湅
+ // 鏌ョ湅璇︽儏
const viewRepair = id => {
- repairModalRef.value.openView(id);
+ repairDetailModalRef.value.openView(id);
};
// 缂栬緫鎶ヤ慨
diff --git a/src/views/inventoryManagement/stockManagement/New.vue b/src/views/inventoryManagement/stockManagement/New.vue
index 3d4b398..5608cd0 100644
--- a/src/views/inventoryManagement/stockManagement/New.vue
+++ b/src/views/inventoryManagement/stockManagement/New.vue
@@ -52,8 +52,9 @@
<el-form-item label="搴撳瓨鏁伴噺"
prop="qualitity">
<el-input-number v-model="formState.qualitity"
- :step="1"
- :min="1"
+ :step="0.00000001"
+ :min="0"
+ :precision="8"
style="width: 100%" />
</el-form-item>
<el-form-item label="鎵瑰彿"
@@ -65,8 +66,9 @@
label="搴撳瓨棰勮鏁伴噺"
prop="warnNum">
<el-input-number v-model="formState.warnNum"
- :step="1"
+ :step="0.00000001"
:min="0"
+ :precision="8"
:max="formState.qualitity"
style="width: 100%" />
</el-form-item>
@@ -119,7 +121,7 @@
productName: "",
productModelName: "",
unit: "",
- type: undefined,
+ type: "qualified",
qualitity: 0,
batchNo: null,
warnNum: 0,
@@ -157,7 +159,7 @@
productName: "",
productModelName: "",
unit: "",
- type: undefined,
+ type: "qualified",
qualitity: 0,
batchNo: null,
warnNum: 0,
diff --git a/src/views/monitor/logininfor/index.vue b/src/views/monitor/logininfor/index.vue
index aa42b52..5e969f7 100644
--- a/src/views/monitor/logininfor/index.vue
+++ b/src/views/monitor/logininfor/index.vue
@@ -95,7 +95,11 @@
<el-table ref="logininforRef" v-loading="loading" :data="logininforList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange">
<el-table-column type="selection" width="55" align="center" />
- <el-table-column label="璁块棶缂栧彿" align="center" prop="infoId" />
+ <el-table-column label="璁块棶缂栧彿" align="center">
+ <template #default="scope">
+ <span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
+ </template>
+ </el-table-column>
<el-table-column label="鐢ㄦ埛鍚嶇О" align="center" prop="userName" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']" />
<el-table-column label="鍦板潃" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
<el-table-column label="鐧诲綍鍦扮偣" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
diff --git a/src/views/monitor/operlog/index.vue b/src/views/monitor/operlog/index.vue
index 53979da..70ae4cd 100644
--- a/src/views/monitor/operlog/index.vue
+++ b/src/views/monitor/operlog/index.vue
@@ -109,7 +109,11 @@
<el-table ref="operlogRef" v-loading="loading" :data="operlogList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange">
<el-table-column type="selection" width="50" align="center" />
- <el-table-column label="鏃ュ織缂栧彿" align="center" prop="operId" />
+ <el-table-column label="鏃ュ織缂栧彿" align="center">
+ <template #default="scope">
+ <span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
+ </template>
+ </el-table-column>
<el-table-column label="绯荤粺妯″潡" align="center" prop="title" :show-overflow-tooltip="true" />
<el-table-column label="鎿嶄綔绫诲瀷" align="center" prop="businessType">
<template #default="scope">
@@ -145,6 +149,7 @@
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
+ :page-sizes="[10, 20, 30, 50, 100, 200]"
@pagination="getList"
/>
diff --git a/src/views/procurementManagement/paymentLedger/index.vue b/src/views/procurementManagement/paymentLedger/index.vue
index b913032..19816a4 100644
--- a/src/views/procurementManagement/paymentLedger/index.vue
+++ b/src/views/procurementManagement/paymentLedger/index.vue
@@ -202,6 +202,14 @@
page.size = obj.limit;
getList();
};
+ // 灏嗘湭鍏ュ簱閲戦骞跺叆宸插叆搴撻噾棰濓紝鏈叆搴撻噾棰濈疆0
+ const mergeUnshippedAmount = row => {
+ if (row && row.unshippedAmount) {
+ row.shippedAmount = (Number(row.shippedAmount) || 0) + (Number(row.unshippedAmount) || 0);
+ row.unshippedAmount = 0;
+ }
+ return row;
+ };
const getList = () => {
tableLoading.value = true;
paymentLedgerList({
@@ -210,7 +218,7 @@
}).then(res => {
let result = res.data;
tableLoading.value = false;
- tableData.value = result.records || [];
+ tableData.value = (result.records || []).map(mergeUnshippedAmount);
total.value = result.total || 0;
if (tableData.value.length > 0) {
currentSupplierId.value = tableData.value[0].supplierId;
@@ -231,11 +239,11 @@
tableLoadingSon.value = false;
let result = res.data;
if (Array.isArray(result)) {
- tableDataSon.value = result;
+ tableDataSon.value = result.map(mergeUnshippedAmount);
sonPage.total = result.length;
handlePagination({ page: sonPage.current, limit: sonPage.size });
} else {
- originalTableDataSon.value = result.records || [];
+ originalTableDataSon.value = (result.records || []).map(mergeUnshippedAmount);
sonPage.total = result.total || 0;
}
})
diff --git a/src/views/procurementManagement/procurementLedger/index.vue b/src/views/procurementManagement/procurementLedger/index.vue
index 6167e6c..f59ed95 100644
--- a/src/views/procurementManagement/procurementLedger/index.vue
+++ b/src/views/procurementManagement/procurementLedger/index.vue
@@ -90,7 +90,7 @@
<el-table-column label="鍏ュ簱鐘舵��" prop="stockInStatus" width="100" show-overflow-tooltip>
<template #default="scope">
<el-tag :type="getStockInStatusType(scope.row.stockInStatus)" size="small">
- {{ scope.row.stockInStatus || '--' }}
+ {{ formatStockInStatus(scope.row.stockInStatus) || '--' }}
</el-tag>
</template>
</el-table-column>
@@ -137,7 +137,7 @@
<el-col :span="12">
<el-form-item label="渚涘簲鍟嗗悕绉帮細" prop="supplierId">
<el-select v-model="form.supplierId" placeholder="璇烽�夋嫨" filterable clearable>
- <el-option v-for="item in supplierList" :key="item.id" :label="item.supplierName" :value="item.id">{{ item.supplierType ? item.supplierName + '---' + item.supplierType : item.supplierName }}</el-option>
+<el-option v-for="item in supplierList" :key="item.id" :label="item.supplierName" :value="item.id">{{ item.supplierName }}</el-option>
</el-select>
</el-form-item>
</el-col>
@@ -480,11 +480,17 @@
return typeMap[status] || "";
};
+// 鍏ュ簱鐘舵�佹樉绀猴細鍏ュ簱涓篃灞曠ず涓哄畬鍏ㄥ叆搴�
+const formatStockInStatus = status => {
+ if (status === "鍏ュ簱涓�") return "瀹屽叏鍏ュ簱";
+ return status;
+};
+
// 鑾峰彇鍏ュ簱鐘舵�佹爣绛剧被鍨�
const getStockInStatusType = status => {
const typeMap = {
"寰呭叆搴�": "info", // 寰呭叆搴� - 鐏拌壊
- "鍏ュ簱涓�": "warning", // 鍏ュ簱涓� - 姗欒壊
+ "鍏ュ簱涓�": "success", // 鍏ュ簱涓� - 缁胯壊
"瀹屽叏鍏ュ簱": "success", // 瀹屽叏鍏ュ簱 - 缁胯壊
};
return typeMap[status] || "";
diff --git a/src/views/procurementManagement/procurementPlan/index.vue b/src/views/procurementManagement/procurementPlan/index.vue
index 42a1bcf..7280a6e 100644
--- a/src/views/procurementManagement/procurementPlan/index.vue
+++ b/src/views/procurementManagement/procurementPlan/index.vue
@@ -183,7 +183,7 @@
<div class="checkbox-group">
<el-checkbox v-model="formData.summaryMaterial">鐗╂枡</el-checkbox>
<el-checkbox v-model="formData.summaryAuxAttributes">杈呭姪灞炴��</el-checkbox>
- <el-checkbox v-model="formData.summaryDemandDate">闇�姹傛棩鏈�</el-checkbox>
+ <el-checkbox v-model="formData.summaryDemandDate">浜よ揣鏃ユ湡</el-checkbox>
</div>
</div>
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index 41e2109..befab45 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -343,7 +343,7 @@
:step="1"
controls-position="right"
style="width: 100%"
- @change="handleUnitQuantityChange"
+ @change="handleUnitQuantityChange(row)"
:disabled="!bomDataValue.isEdit || bomDataValue.dataList.some(item => (item).tempId === row.tempId)" />
</el-form-item>
</template>
@@ -1196,52 +1196,6 @@
});
};
- const toQuantityNumber = value => {
- const numberValue = Number(value);
- if (!Number.isFinite(numberValue)) {
- return 0;
- }
- return Number(numberValue.toFixed(4));
- };
-
- const syncDemandedQuantityTree = (items, parentDemandedQuantity = null) => {
- items.forEach(item => {
- if (parentDemandedQuantity !== null) {
- item.demandedQuantity = toQuantityNumber(
- parentDemandedQuantity * toQuantityNumber(item.unitQuantity)
- );
- }
-
- if (Array.isArray(item.children) && item.children.length > 0) {
- syncDemandedQuantityTree(
- item.children,
- toQuantityNumber(item.demandedQuantity)
- );
- }
- });
- };
-
- const recalculateDemandedQuantities = () => {
- if (pageType.value !== "order") {
- return;
- }
-
- const rootDemandedQuantity = routeInfo.value.quantity;
- if (
- rootDemandedQuantity === undefined ||
- rootDemandedQuantity === null ||
- rootDemandedQuantity === ""
- ) {
- syncDemandedQuantityTree(bomDataValue.value.dataList);
- return;
- }
-
- syncDemandedQuantityTree(
- bomDataValue.value.dataList,
- toQuantityNumber(rootDemandedQuantity)
- );
- };
-
const processChange = value => {
processOptions.value.forEach(item => {
if (item.id == value) {
@@ -1266,6 +1220,24 @@
}
}
return null;
+ };
+
+ const toQuantityNumber = value => {
+ const numberValue = Number(value);
+ if (!Number.isFinite(numberValue)) {
+ return 0;
+ }
+ return Number(numberValue.toFixed(4));
+ };
+
+ const handleUnitQuantityChange = row => {
+ if (pageType.value !== "order") {
+ return;
+ }
+
+ row.demandedQuantity = toQuantityNumber(
+ toQuantityNumber(row.unitQuantity) * toQuantityNumber(routeInfo.value.quantity)
+ );
};
const handleBomProcessChange = (row, value) => {
@@ -1301,7 +1273,6 @@
);
bomDataValue.value.dataList = data || [];
normalizeTreeData(bomDataValue.value.dataList);
- recalculateDemandedQuantities();
} catch (err) {
console.error("鑾峰彇BOM鏁版嵁澶辫触锛�", err);
}
@@ -1397,10 +1368,6 @@
});
};
- const handleUnitQuantityChange = () => {
- recalculateDemandedQuantities();
- };
-
const addchildItem = (item, tempId) => {
if (item.tempId === tempId) {
if (!item.children) {
@@ -1424,7 +1391,6 @@
unit: "",
tempId: new Date().getTime(),
});
- recalculateDemandedQuantities();
return true;
}
if (item.children && item.children.length > 0) {
@@ -1461,7 +1427,6 @@
children: [],
tempId: new Date().getTime(),
});
- recalculateDemandedQuantities();
return;
}
addchildItem(item, tempId);
@@ -1556,7 +1521,6 @@
console.log(bomDataValue.value.dataList, "bomDataValue.value.dataList");
normalizeTreeData(bomDataValue.value.dataList);
- recalculateDemandedQuantities();
const valid = validateAllBom();
if (valid) {
@@ -1859,4 +1823,4 @@
line-height: 1.5;
word-break: break-all;
}
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/productionManagement/productStructure/Detail/index.vue b/src/views/productionManagement/productStructure/Detail/index.vue
index ec18ddf..6899eb0 100644
--- a/src/views/productionManagement/productStructure/Detail/index.vue
+++ b/src/views/productionManagement/productStructure/Detail/index.vue
@@ -22,16 +22,34 @@
border
:preserve-expanded-content="false"
:default-expand-all="true"
- style="width: 100%">
+ style="width: 100%"
+ @expand-change="handleOuterExpandChange">
<el-table-column type="expand">
<template #default="props">
<el-form ref="form"
:model="dataValue">
- <el-table :data="dataValue.dataList"
+ <div v-if="dataValue.isEdit"
+ class="drag-tip">
+ <el-icon><Rank /></el-icon>
+ <span>鎷栧姩鎵嬫焺璋冩暣缁撴瀯:鍚屽眰绾х洿鎺ユ嫋鍔ㄦ帓搴�;鎷栧埌鏌愯姝d笅鏂规垚涓哄叾瀛愮骇;鎷栧埌鍒楄〃鏈�鍓嶆垚涓轰竴绾�</span>
+ </div>
+ <el-table ref="tableRef"
+ :data="dataValue.dataList"
row-key="tempId"
default-expand-all
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
+ @expand-change="handleTreeExpandChange"
style="width: 100%">
+ <el-table-column v-if="dataValue.isEdit"
+ width="44"
+ align="center">
+ <template #default="{ row }">
+ <div class="drag-handle"
+ title="鎷栧姩璋冩暣椤哄簭:鎷栧埌鏌愯姝d笅鏂规垚涓哄叾瀛愮骇,鎷栧埌鍒楄〃鏈�鍓嶆垚涓轰竴绾�">
+ <el-icon><Rank /></el-icon>
+ </div>
+ </template>
+ </el-table-column>
<el-table-column prop="productName"
label="浜у搧" />
<el-table-column prop="model"
@@ -59,18 +77,20 @@
<el-form-item v-if="dataValue.isEdit"
:rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: '璇烽�夋嫨娑堣�楀伐搴�', trigger: 'change' }]"
style="margin: 0">
- <el-select v-model="row.processId"
- placeholder="璇烽�夋嫨"
- filterable
- clearable
- style="width: 100%"
- @change="value => handleProcessChange(row, value)"
- :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)">
- <el-option v-for="item in dataValue.processOptions"
- :key="item.id"
- :label="item.name"
- :value="item.id" />
- </el-select>
+ <el-input v-model="row.processName"
+ readonly
+ placeholder="璇烽�夋嫨娑堣�楀伐搴�"
+ clearable
+ style="width: 100%"
+ :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
+ @click="openProcessDialog(row, $event)"
+ @clear="clearProcess(row)">
+ <template #append>
+ <el-button icon="Search"
+ :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"
+ @click="openProcessDialog(row)" />
+ </template>
+ </el-input>
</el-form-item>
</template>
</el-table-column>
@@ -150,6 +170,9 @@
v-model:model-value="dataValue.showProductDialog"
:single="true"
@confirm="handleProduct" />
+ <process-select-dialog v-if="dataValue.showProcessDialog"
+ v-model:model-value="dataValue.showProcessDialog"
+ @confirm="handleProcessConfirm" />
</div>
</template>
@@ -158,18 +181,23 @@
computed,
defineAsyncComponent,
defineComponent,
+ nextTick,
+ onBeforeUnmount,
onMounted,
reactive,
ref,
+ watch,
} from "vue";
import {
queryList,
addBomDetail,
} from "@/api/productionManagement/productStructure.js";
import { listProcessBom } from "@/api/productionManagement/productionOrder.js";
- import { list } from "@/api/productionManagement/productionProcess";
+ import { listAll } from "@/api/productionManagement/productionProcess";
import { ElMessage } from "element-plus";
import { useRoute, useRouter } from "vue-router";
+ import Sortable from "sortablejs";
+ import { Rank } from "@element-plus/icons-vue";
defineComponent({
name: "StructureEdit",
@@ -178,8 +206,16 @@
const ProductSelectDialog = defineAsyncComponent(
() => import("@/views/basicData/product/ProductSelectDialog.vue")
);
+ const ProcessSelectDialog = defineAsyncComponent(
+ () =>
+ import(
+ "@/views/productionManagement/productionProcess/ProcessSelectDialog.vue"
+ )
+ );
const emit = defineEmits(["update:router"]);
const form = ref();
+ const tableRef = ref();
+ let tableSortable = null;
const route = useRoute();
const router = useRouter();
@@ -210,8 +246,10 @@
productOptions: [],
processOptions: [],
showProductDialog: false,
+ showProcessDialog: false,
currentRowIndex: null,
currentRowName: null,
+ currentProcessRow: null,
loading: false,
isEdit: false,
});
@@ -303,6 +341,155 @@
syncDemandedQuantityTree(dataValue.dataList);
};
+ // ===== 鎷栨嫿鎺掑簭 =====
+ // 鎸夊綋鍓� DOM 琛岄『搴忓睍寮�鎴愭墎骞冲垪琛�(缂栬緫妯″紡涓嬪己鍒跺叏閮ㄥ睍寮�,淇濊瘉琛岄『搴忎笌鏁版嵁涓�鑷�)
+ const buildFlatTree = () => {
+ const flat = [];
+ const walk = (items, depth, parentNode) => {
+ items.forEach(node => {
+ flat.push({ node, depth, parentArray: items, parentNode });
+ if (Array.isArray(node.children) && node.children.length > 0) {
+ walk(node.children, depth + 1, node);
+ }
+ });
+ };
+ walk(dataValue.dataList, 0, null);
+ return flat;
+ };
+
+ const removeNodeByTempId = (items, tempId) => {
+ for (let i = 0; i < items.length; i++) {
+ if (String(items[i].tempId) === String(tempId)) {
+ items.splice(i, 1);
+ return true;
+ }
+ if (
+ Array.isArray(items[i].children) &&
+ removeNodeByTempId(items[i].children, tempId)
+ ) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ const updateParentRef = (node, parent) => {
+ node.parentTempId = parent?.tempId ?? "";
+ node.parentId = parent?.id ?? "";
+ };
+
+ const handleSortEnd = evt => {
+ const { oldIndex, newIndex } = evt;
+ if (
+ typeof oldIndex !== "number" ||
+ typeof newIndex !== "number" ||
+ oldIndex === newIndex
+ ) {
+ return;
+ }
+ const flat = buildFlatTree();
+ if (oldIndex < 0 || oldIndex >= flat.length) return;
+ const dragged = flat[oldIndex];
+
+ // 鎷栨斁鍚庝綅浜庤鎷栬涓婃柟涓�琛岀殑鏁版嵁(鎸夋渶缁� DOM 椤哄簭鎹㈢畻鍒板師鎵佸钩鍒楄〃)
+ let aEntry = null;
+ if (newIndex > oldIndex) {
+ aEntry = flat[newIndex];
+ } else if (newIndex > 0) {
+ aEntry = flat[newIndex - 1];
+ }
+
+ if (!aEntry) {
+ // 鎷栧埌鍒楄〃鏈�鍓� 鈫� 鎴愪负涓�绾�
+ removeNodeByTempId(dataValue.dataList, dragged.node.tempId);
+ dataValue.dataList.unshift(dragged.node);
+ updateParentRef(dragged.node, null);
+ } else if (aEntry.depth < dragged.depth) {
+ // 鎷栧埌鏌愯姝d笅鏂� 鈫� 鎴愪负璇ヨ鐨勫瓙绾�
+ removeNodeByTempId(dataValue.dataList, dragged.node.tempId);
+ if (!aEntry.node.children) {
+ aEntry.node.children = [];
+ }
+ aEntry.node.children.push(dragged.node);
+ updateParentRef(dragged.node, aEntry.node);
+ } else {
+ // 鍚岀骇鎻掑叆:鍚戜笂鎵剧涓�涓繁搴︿笉瓒呰繃琚嫋琛岀殑鑺傜偣
+ let i = flat.indexOf(aEntry);
+ while (i >= 0 && flat[i].depth > dragged.depth) {
+ i--;
+ }
+ const eEntry = flat[i];
+ // 鎷栧洖鑷繁鐨勫瓙鏍戝唴 鈫� 涓嶅鐞�(闃叉褰㈡垚寰幆)
+ if (!eEntry || eEntry.node.tempId === dragged.node.tempId) {
+ return;
+ }
+ removeNodeByTempId(dataValue.dataList, dragged.node.tempId);
+ const siblings = eEntry.parentArray;
+ const idx = siblings.indexOf(eEntry.node);
+ siblings.splice(idx + 1, 0, dragged.node);
+ updateParentRef(dragged.node, eEntry.parentNode);
+ }
+
+ recalculateDemandedQuantities();
+ };
+
+ const initSortable = () => {
+ if (!tableRef.value) return;
+ const tbody = tableRef.value.$el.querySelector(".el-table__body tbody");
+ if (!tbody) return;
+ tableSortable?.destroy();
+ tableSortable = new Sortable(tbody, {
+ animation: 150,
+ handle: ".drag-handle",
+ disabled: !dataValue.isEdit,
+ ghostClass: "sortable-ghost",
+ onEnd: handleSortEnd,
+ });
+ };
+
+ const expandAllTree = () => {
+ const table = tableRef.value;
+ if (!table) return;
+ const walk = items => {
+ items.forEach(item => {
+ if (Array.isArray(item.children) && item.children.length > 0) {
+ table.toggleRowExpansion(item, true);
+ walk(item.children);
+ }
+ });
+ };
+ walk(dataValue.dataList);
+ };
+
+ // 缂栬緫妯″紡涓嬩笉鍏佽鎶樺彔,淇濊瘉鎷栨嫿鏃� DOM 琛岄『搴忎笌 dataList 涓�鑷�
+ const handleTreeExpandChange = (row, expanded) => {
+ if (dataValue.isEdit && expanded === false) {
+ nextTick(() => {
+ tableRef.value?.toggleRowExpansion(row, true);
+ });
+ }
+ };
+
+ // 澶栧眰琛屾姌鍙犲悗鍐呭浼氶攢姣�,閲嶆柊灞曞紑鏃堕噸鏂板垵濮嬪寲鎷栨嫿
+ const handleOuterExpandChange = (row, expandedRows) => {
+ if (Array.isArray(expandedRows) && expandedRows.some(r => r === row)) {
+ nextTick(() => initSortable());
+ }
+ };
+
+ watch(
+ () => dataValue.isEdit,
+ val => {
+ tableSortable?.option("disabled", !val);
+ if (val) {
+ nextTick(() => {
+ initSortable();
+ expandAllTree();
+ });
+ }
+ }
+ );
+
const buildSubmitTree = (items: any[]) => {
return items.map((item: any) => {
const current = { ...item };
@@ -346,6 +533,35 @@
}
};
+ const openProcessDialog = (row: any, event?: Event) => {
+ // 鐐瑰嚮娓呴櫎鎸夐挳鏃朵笉寮圭獥
+ const target = event?.target as HTMLElement | undefined;
+ if (target && target.closest(".el-input__clear")) {
+ return;
+ }
+ dataValue.currentProcessRow = row;
+ dataValue.showProcessDialog = true;
+ };
+
+ const clearProcess = (row: any) => {
+ row.processId = "";
+ row.operationId = "";
+ row.processName = "";
+ row.operationName = "";
+ };
+
+ const handleProcessConfirm = (processRow: any) => {
+ const row = dataValue.currentProcessRow;
+ if (!row) {
+ return;
+ }
+ row.processId = processRow.id;
+ row.operationId = processRow.id;
+ row.processName = processRow.name;
+ row.operationName = processRow.name;
+ handleProcessChange(row, processRow.id);
+ };
+
const handleUnitQuantityChange = () => {
recalculateDemandedQuantities();
};
@@ -382,7 +598,7 @@
};
const fetchProcessOptions = async () => {
- const { data } = await list({});
+ const { data } = await listAll();
console.log(data, "dataValue.dataList");
dataValue.processOptions = normalizeListData(data);
};
@@ -693,5 +909,38 @@
// 鍏堝姞杞藉伐搴忛�夐」锛屽啀鍔犺浇鏁版嵁锛岀‘淇漞l-select鑳藉姝g‘鍥炴樉
await fetchProcessOptions();
await fetchData();
+ await nextTick();
+ initSortable();
});
-</script>
\ No newline at end of file
+
+ onBeforeUnmount(() => {
+ tableSortable?.destroy();
+ tableSortable = null;
+ });
+</script>
+
+<style scoped>
+ .drag-tip {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ margin-bottom: 8px;
+ font-size: 12px;
+ color: #909399;
+ }
+
+ .drag-handle {
+ cursor: move;
+ color: #909399;
+ text-align: center;
+ }
+
+ .drag-handle:hover {
+ color: var(--el-color-primary);
+ }
+
+ :deep(.sortable-ghost) {
+ opacity: 0.6;
+ background-color: #f5f7fa !important;
+ }
+</style>
\ No newline at end of file
diff --git a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
index 6c175f9..9067dde 100644
--- a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
+++ b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -2,18 +2,25 @@
<div>
<el-dialog v-model="dialogVisible"
title="棰嗘枡鍙拌处"
- width="1350px"
+ width="1450px"
@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="80">
+ min-width="100">
<template #default="{ row }">
<span v-if="row.bom === true">{{ row.operationName || "-" }}</span>
<el-select v-else
@@ -43,13 +50,13 @@
</template>
</el-table-column>
<el-table-column label="鍘熸枡鍨嬪彿"
- min-width="120">
+ min-width="110">
<template #default="{ row }">
{{ row.materialModel || "-" }}
</template>
</el-table-column>
<!-- 鎵瑰彿澶氶�� -->
- <el-table-column min-width="200"
+ <el-table-column min-width="180"
label="鎵瑰彿">
<template #default="{ row }">
<el-select v-model="row.batchNo"
@@ -66,7 +73,7 @@
</template>
</el-table-column>
<el-table-column label="闇�姹傛暟閲�"
- min-width="100">
+ min-width="95">
<template #default="{ row }">
<span v-if="row.bom === true">{{ stripTrailingZeros(row.demandedQuantity) ?? "-" }}</span>
<el-input-number v-else
@@ -78,6 +85,27 @@
:parser="value => parseFloat(value) || 0"
style="width: 100%;"
@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="鍓╀綑鍙"
+ min-width="90">
+ <template #default="{ row }">
+ <span :class="{ 'remaining-empty': Number(row.remainingQty) <= 0 }">
+ {{ stripTrailingZeros(row.remainingQty) ?? 0 }}
+ </span>
</template>
</el-table-column>
<el-table-column label="璁¢噺鍗曚綅"
@@ -102,7 +130,7 @@
width="90"
fixed="right">
<template #default="{ $index, row }">
- <el-button v-if="row.bom !== true"
+ <el-button v-if="row.bom !== true || Number(row.pickQty) > 0"
type="danger"
link
@click="handleDeleteMaterialRow($index)">鍒犻櫎</el-button>
@@ -122,6 +150,8 @@
<ProductSelectDialog v-model="materialProductDialogVisible"
@confirm="handleMaterialProductConfirm"
single />
+ <PurchaseLedgerAddDialog ref="purchaseLedgerAddDialogRef"
+ v-model="purchaseLedgerDialogVisible" />
<!-- request-url="/stockInventory/rawMaterials" -->
</div>
</template>
@@ -130,18 +160,14 @@
import { computed, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
- import {
- findProductProcessRouteItemList,
- listMain,
- } from "@/api/productionManagement/productProcessRoute.js";
+ import PurchaseLedgerAddDialog from "./PurchaseLedgerAddDialog.vue";
+ import { findProductProcessRouteItemList } from "@/api/productionManagement/productProcessRoute.js";
+ import { getStockInventoryByModelId } from "@/api/inventoryManagement/stockInventory.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 },
@@ -158,64 +184,67 @@
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;
if (materialTableData.value.length === 0) return true;
- return !materialTableData.value.some(row => {
- // 妫�鏌ユ槸鍚︽湁浠讳綍鐢ㄦ埛杈撳叆鍐呭
- const hasBatch = Array.isArray(row.batchNo) && row.batchNo.length > 0;
- const hasPickQty =
- row.pickQty !== null && row.pickQty !== undefined && row.pickQty !== 0;
-
- if (row.bom) {
- // 瀵逛簬鏉ヨ嚜BOM鐨勮锛岃緭鍏ユ鍙湁鈥滄壒鍙封�濆拰鈥滈鐢ㄦ暟閲忊��
- return hasBatch || hasPickQty;
- } else {
- // 瀵逛簬鏂板琛岋紝杈撳叆妗嗗寘鎷�滃伐搴忊�濄�佲�滃師鏂欌�濄�佲�滈渶姹傛暟閲忊�濄�佲�滄壒鍙封�濆拰鈥滈鐢ㄦ暟閲忊��
- const hasOperation = !!row.operationName;
- const hasMaterial = !!row.materialName;
- const hasDemanded =
- row.demandedQuantity !== null &&
- row.demandedQuantity !== undefined &&
- row.demandedQuantity !== 0;
- return (
- hasBatch || hasPickQty || hasOperation || hasMaterial || hasDemanded
- );
- }
- });
+ // 鍙湁瀛樺湪鏈棰嗙敤鏁伴噺澶т簬0鐨勮鎵嶅厑璁镐繚瀛�
+ return !materialTableData.value.some(item => Number(item.pickQty) > 0);
});
const processOptions = ref([]);
const currentMaterialSelectRowIndex = ref(-1);
let materialTempId = 0;
- const getDefaultBatchNo = batchNoList => {
- if (!Array.isArray(batchNoList) || batchNoList.length === 0) return [];
- return [batchNoList[0]];
+ const toNumber = val => {
+ const n = Number(val);
+ return Number.isFinite(n) ? n : 0;
+ };
+
+ // 淇濈暀6浣嶅皬鏁帮紝閬垮厤娴偣璇樊
+ const roundQuantity = val => Math.round(toNumber(val) * 1e6) / 1e6;
+
+ // 闇�姹傛暟閲忓垎缁勯敭锛氫笌鍚庣淇濇寔涓�鑷达紙宸ュ簭ID浼樺厛锛岀己澶辨椂鐢ㄥ伐搴忓悕绉帮級
+ const buildDemandKey = (technologyOperationId, operationName, productModelId) =>
+ `${technologyOperationId != null && technologyOperationId !== undefined ? "T" + technologyOperationId : "N" + (operationName || "")}#${productModelId ?? ""}`;
+
+ const stripTrailingZeros = val => {
+ if (val === null || val === undefined || val === "") return "0";
+ const str = String(val);
+ if (str.includes(".")) {
+ return parseFloat(str).toString();
+ }
+ return str;
};
const createMaterialRow = (row = {}) => {
const batchNoList = Array.isArray(row.batchNoList) ? row.batchNoList : [];
- const batchNo = row.batchNo
- ? typeof row.batchNo === "string"
- ? row.batchNo.split(",")
- : row.batchNo
- : getDefaultBatchNo(batchNoList);
+ const pickedEntry = row.pickedEntry || null;
+ const pickedQty = pickedEntry ? roundQuantity(pickedEntry.pickedQty) : 0;
+ const returnedQty = pickedEntry ? roundQuantity(pickedEntry.returnedQty) : 0;
+ const demandedQuantity = roundQuantity(row.demandedQuantity ?? 0);
+ const remainingQty = Math.max(roundQuantity(demandedQuantity - pickedQty + returnedQty), 0);
return {
- tempId: row.id || `temp_${++materialTempId}`,
- id: row.id,
- processId: row.processId || row.technologyOperationId,
- technologyOperationId: row.technologyOperationId || row.processId,
- operationName: row.operationName || "",
+ tempId: `temp_${++materialTempId}`,
+ id: null,
bom: row.bom === true,
- materialModelId: row.materialModelId || row.productModelId,
- materialName: row.materialName || row.productName || "",
- materialModel: row.materialModel || row.model || "",
- demandedQuantity: Number(row.requiredQty ?? row.demandedQuantity ?? 0),
+ technologyOperationId: row.technologyOperationId ?? null,
+ operationName: row.operationName || "",
+ materialModelId: row.materialModelId ?? null,
+ materialName: row.materialName || "",
+ materialModel: row.materialModel || "",
unit: row.unit || "",
- pickQty: Number(row.pickQty ?? row.pickQuantity ?? row.requiredQty ?? row.demandedQuantity ?? 0),
- batchNo,
+ demandedQuantity,
+ pickedQty,
+ returnedQty,
+ remainingQty,
+ pickQty: 0,
+ stockQty: null,
+ batchNo: [],
batchNoList,
};
};
@@ -237,37 +266,115 @@
if (!processMap.has(key)) {
processMap.set(key, {
id: processId,
+ technologyOperationId: processId,
name: operationName,
});
}
});
processOptions.value = Array.from(processMap.values());
};
- const isDetail = ref(true);
const loadMaterialData = async () => {
if (!props.orderRow?.id) return;
materialTableLoading.value = true;
materialTableData.value = [];
- await getProcessOptions();
try {
- const detailRes = await listMaterialPickingDetail(props.orderRow.id);
+ await getProcessOptions();
+ // 鍚屾椂鑾峰彇BOM闇�姹傛暟鎹笌宸查鏂欐槑缁嗭紝鐢ㄤ簬璁$畻鈥滃凡棰嗘暟閲�/鍓╀綑鍙鈥�
+ const [bomRes, detailRes] = await Promise.all([
+ listMaterialPickingBom(props.orderRow.id),
+ listMaterialPickingDetail(props.orderRow.id),
+ ]);
+ const bomList = Array.isArray(bomRes?.data)
+ ? bomRes.data
+ : bomRes?.data?.records || [];
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 {
- 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;
- }
+
+ // 鎸夆�滃伐搴�+鍘熸枡鈥濇眹鎬诲巻鍙查鏂欙細宸查鏁伴噺銆侀��鏂欐暟閲�
+ const pickedMap = new Map();
+ detailList.forEach(item => {
+ if (!item) return;
+ const key = buildDemandKey(
+ item.technologyOperationId,
+ item.operationName,
+ item.productModelId
+ );
+ const entry = pickedMap.get(key) || {
+ pickedQty: 0,
+ returnedQty: 0,
+ manualDemand: 0,
+ technologyOperationId: item.technologyOperationId,
+ operationName: item.operationName || "",
+ materialModelId: item.productModelId,
+ materialName: item.productName || "",
+ materialModel: item.model || "",
+ unit: item.unit || "",
+ batchNoList: Array.isArray(item.batchNoList)
+ ? item.batchNoList
+ : [],
+ };
+ entry.pickedQty = roundQuantity(entry.pickedQty + toNumber(item.pickQuantity));
+ entry.returnedQty = roundQuantity(entry.returnedQty + toNumber(item.returnQty));
+ // 鎵嬪伐鏂板琛岋紙闈濨OM锛夌殑闇�姹傛暟閲忎互棰嗘枡蹇収涓哄噯
+ if (item.bom !== true && item.demandedQuantity != null) {
+ entry.manualDemand = Math.max(
+ toNumber(entry.manualDemand),
+ toNumber(item.demandedQuantity)
+ );
+ }
+ pickedMap.set(key, entry);
+ });
+
+ const rows = [];
+ const bomKeySet = new Set();
+ // BOM琛岋細闇�姹傛暟閲忓彇BOM锛屽凡棰嗘暟閲忓彇鍘嗗彶棰嗘枡姹囨��
+ bomList.forEach(item => {
+ const materialModelId = item.productModelId ?? item.materialModelId;
+ const key = buildDemandKey(
+ item.technologyOperationId,
+ item.operationName,
+ materialModelId
+ );
+ bomKeySet.add(key);
+ rows.push(
+ createMaterialRow({
+ bom: true,
+ technologyOperationId: item.technologyOperationId,
+ operationName: item.operationName || "",
+ materialModelId,
+ materialName: item.productName || item.materialName || "",
+ materialModel: item.model || item.materialModel || "",
+ unit: item.unit || "",
+ demandedQuantity: item.demandedQuantity ?? item.requiredQty ?? 0,
+ pickedEntry: pickedMap.get(key),
+ batchNoList: item.batchNoList || [],
+ })
+ );
+ });
+ // 涓嶅湪BOM涓殑鍘嗗彶棰嗘枡锛堟墜宸ユ柊澧炶锛夛紝涔熷睍绀哄嚭鏉ユ敮鎸佺户缁鏂�
+ pickedMap.forEach((entry, key) => {
+ if (bomKeySet.has(key)) return;
+ rows.push(
+ createMaterialRow({
+ bom: false,
+ technologyOperationId: entry.technologyOperationId,
+ operationName: entry.operationName,
+ materialModelId: entry.materialModelId,
+ materialName: entry.materialName,
+ materialModel: entry.materialModel,
+ unit: entry.unit,
+ demandedQuantity: entry.manualDemand,
+ pickedEntry: entry,
+ batchNoList: entry.batchNoList,
+ })
+ );
+ });
+ // 鍏堟煡鍑哄簱瀛樹綑閲忓啀鍏ヨ〃锛屼繚璇侀娆℃墦寮�鍗冲睍绀猴紙rows 灏氭湭杞负鍝嶅簲寮忎唬鐞嗭紝
+ // 鍏ヨ〃鍚庡啀鏀瑰師濮嬪璞′笉浼氳Е鍙戣鍥炬洿鏂帮級
+ await loadStockQtyForRows(rows);
+ materialTableData.value = rows;
} finally {
materialTableLoading.value = false;
}
@@ -282,21 +389,74 @@
}
);
- const stripTrailingZeros = val => {
- const str = String(val);
- if (str.includes(".")) {
- return parseFloat(str).toString();
- }
- return str;
- };
-
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,
+ isChecked: false,
+ }));
+ purchaseLedgerAddDialogRef.value?.open(productRows);
};
const handleDeleteMaterialRow = index => {
@@ -311,11 +471,12 @@
};
const handleRequiredQtyChange = (row, val) => {
- const required = Number(val ?? 0);
+ const required = roundQuantity(val);
row.demandedQuantity = required;
- if (!row.pickQty || Number(row.pickQty) === 0) {
- row.pickQty = required;
- }
+ row.remainingQty = Math.max(
+ roundQuantity(required - toNumber(row.pickedQty) + toNumber(row.returnedQty)),
+ 0
+ );
};
const openMaterialProductSelect = row => {
@@ -326,8 +487,6 @@
};
const handleMaterialProductConfirm = products => {
- console.log(products, "products");
-
if (!products || products.length === 0) return;
const index = currentMaterialSelectRowIndex.value;
if (index < 0 || !materialTableData.value[index]) return;
@@ -340,42 +499,32 @@
row.materialModel = product.materialModel || product.model || "";
row.unit = product.unit || product.measureUnit || "";
row.batchNoList = Array.isArray(product.batchNoList) ? product.batchNoList : [];
- row.batchNo = getDefaultBatchNo(row.batchNoList);
+ row.batchNo = [];
currentMaterialSelectRowIndex.value = -1;
materialProductDialogVisible.value = false;
+ loadStockQtyForRows([row]);
};
const validateMaterialRows = () => {
if (materialTableData.value.length === 0) {
return { valid: false, message: "璇峰厛鏂板棰嗘枡鏁版嵁" };
}
- const invalidNewRow = materialTableData.value.find(
- item => item.bom !== true && (!item.operationName || !item.materialName)
+ const pickRows = materialTableData.value.filter(
+ item => toNumber(item.pickQty) > 0
);
- if (invalidNewRow) {
- return { valid: false, message: "鏂板琛岀殑宸ュ簭鍚嶇О鍜屽師鏂欏悕绉颁负蹇呭~椤�" };
+ if (pickRows.length === 0) {
+ return { valid: false, message: "璇疯嚦灏戝~鍐欎竴琛屽ぇ浜�0鐨勯鐢ㄦ暟閲�" };
}
- const invalidRow = materialTableData.value.find(
- item =>
- !item.operationName ||
- !item.materialName ||
- (Number(item.pickQty) > 0 &&
- (!item.batchNo || item.batchNo.length === 0)) ||
- (item.batchNo && item.batchNo.length > 0 && Number(item.pickQty) <= 0) ||
- item.demandedQuantity === null ||
- item.demandedQuantity === undefined ||
- item.pickQty === null ||
- item.pickQty === undefined
- );
- if (invalidRow) {
- if (
- invalidRow.batchNo &&
- invalidRow.batchNo.length > 0 &&
- Number(invalidRow.pickQty) <= 0
- ) {
- return { valid: false, message: "閫夋嫨浜嗘壒鍙锋椂锛岄鐢ㄦ暟閲忓繀椤诲ぇ浜庨浂" };
+ for (let i = 0; i < materialTableData.value.length; i++) {
+ const item = materialTableData.value[i];
+ const rowNo = i + 1;
+ if (toNumber(item.pickQty) <= 0) continue;
+ if (item.bom !== true && (!item.operationName || !item.materialName)) {
+ return { valid: false, message: `绗�${rowNo}琛岀殑宸ュ簭鍚嶇О鍜屽師鏂欏悕绉颁负蹇呭~椤筦 };
}
- return { valid: false, message: "璇峰畬鍠勫伐搴忋�佸師鏂欍�佹壒鍙峰拰鏁伴噺鍚庡啀淇濆瓨" };
+ if (!Array.isArray(item.batchNo) || item.batchNo.length === 0) {
+ return { valid: false, message: `绗�${rowNo}琛岃閫夋嫨鎵瑰彿` };
+ }
}
return { valid: true, message: "" };
};
@@ -389,47 +538,26 @@
}
materialSaving.value = true;
try {
- if (isDetail.value) {
- await updateMaterialPickingLedger({
- productionOrderId: props.orderRow.id,
- productionOrderPickDto: materialTableData.value.map(item => ({
- id: item.id,
- // processId: item.operationName,
- technologyOperationId: item.technologyOperationId,
- operationName: item.operationName,
- bom: item.bom === true,
- productModelId: item.materialModelId,
- // materialName: item.materialName,
- // materialModel: item.materialModel,
- demandedQuantity: item.demandedQuantity,
- unit: item.unit,
- pickQuantity: item.pickQty,
- batchNo: Array.isArray(item.batchNo)
- ? item.batchNo.join(",")
- : item.batchNo,
- })),
- });
- } else {
- await saveMaterialPickingLedger({
- productionOrderId: props.orderRow.id,
- productionOrderPickDto: materialTableData.value.map(item => ({
- id: item.id,
- // processId: item.operationName,
- technologyOperationId: item.technologyOperationId,
- operationName: item.operationName,
- bom: item.bom === true,
- productModelId: item.materialModelId,
- // materialName: item.materialName,
- // materialModel: item.materialModel,
- demandedQuantity: item.demandedQuantity,
- unit: item.unit,
- pickQuantity: item.pickQty,
- batchNo: Array.isArray(item.batchNo)
- ? item.batchNo.join(",")
- : item.batchNo,
- })),
- });
- }
+ // 姣忔淇濆瓨閮芥槸涓�娆$嫭绔嬬殑棰嗘枡锛氬彧鎻愪氦鏈棰嗙敤鏁伴噺澶т簬0鐨勮锛�
+ // 鍑哄簱鍙拌处鎸夋湰娆¢鏂欐暟閲忚璐︼紝鍘嗗彶棰嗘枡璁板綍淇濇寔涓嶅彉銆�
+ const pickRows = materialTableData.value.filter(
+ item => toNumber(item.pickQty) > 0
+ );
+ await saveMaterialPickingLedger({
+ productionOrderId: props.orderRow.id,
+ productionOrderPickDto: pickRows.map(item => ({
+ technologyOperationId: item.technologyOperationId,
+ operationName: item.operationName,
+ bom: item.bom === true,
+ productModelId: item.materialModelId,
+ demandedQuantity: item.demandedQuantity,
+ unit: item.unit,
+ pickQuantity: toNumber(item.pickQty),
+ batchNo: Array.isArray(item.batchNo)
+ ? item.batchNo.join(",")
+ : item.batchNo,
+ })),
+ });
ElMessage({ message: "棰嗘枡鎴愬姛", type: "success" });
emit("saved");
@@ -445,4 +573,9 @@
margin-bottom: 12px;
text-align: right;
}
+
+ .remaining-empty {
+ color: #f56c6c;
+ font-weight: 600;
+ }
</style>
diff --git a/src/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue b/src/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue
index d1aa267..ac1f563 100644
--- a/src/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue
+++ b/src/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue
@@ -31,7 +31,7 @@
</div>
<div class="info-item"
style="width: 25%;">
- <span class="label">闇�姹傛棩鏈燂細</span>
+ <span class="label">浜よ揣鏃ユ湡锛�</span>
<span class="value">{{ formatDate(orderRow?.planCompleteTime) }}</span>
</div>
</div>
diff --git a/src/views/productionManagement/productionOrder/components/PurchaseLedgerAddDialog.vue b/src/views/productionManagement/productionOrder/components/PurchaseLedgerAddDialog.vue
new file mode 100644
index 0000000..a0da9d4
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/components/PurchaseLedgerAddDialog.vue
@@ -0,0 +1,856 @@
+<template>
+ <div>
+ <FormDialog v-model="dialogVisible"
+ title="鏂板閲囪喘鍙拌处椤甸潰"
+ :width="'70%'"
+ operation-type="add"
+ @close="closeDia"
+ @confirm="submitForm"
+ @cancel="closeDia">
+ <el-form :model="form"
+ label-width="140px"
+ label-position="top"
+ :rules="rules"
+ ref="formRef">
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="閲囪喘鍚堝悓鍙凤細" prop="purchaseContractNumber">
+ <el-input v-model="form.purchaseContractNumber" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col v-if="showSalesContractBinding" :span="12">
+ <el-form-item label="閿�鍞悎鍚屽彿锛�" prop="salesLedgerId">
+ <el-select v-model="form.salesLedgerId"
+ placeholder="璇烽�夋嫨"
+ filterable
+ clearable
+ @change="salesLedgerChange">
+ <el-option v-for="item in salesContractList"
+ :key="item.id"
+ :label="item.salesContractNo"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="渚涘簲鍟嗗悕绉帮細" prop="supplierId">
+ <el-select v-model="form.supplierId" placeholder="璇烽�夋嫨" filterable clearable>
+ <el-option v-for="item in supplierList"
+ :key="item.id"
+ :label="item.supplierName"
+ :value="item.id">
+ {{ item.supplierName }}
+ </el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="椤圭洰鍚嶇О" prop="projectName">
+ <el-input v-model="form.projectName" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="浠樻鏂瑰紡">
+ <el-input v-model="form.paymentMethod" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绛捐鏃ユ湡锛�" prop="executionDate">
+ <el-date-picker v-model="form.executionDate"
+ style="width: 100%"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="褰曞叆浜猴細" prop="recorderId">
+ <el-select v-model="form.recorderId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ filterable
+ disabled>
+ <el-option v-for="item in userList"
+ :key="item.userId"
+ :label="item.nickName"
+ :value="item.userId" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="褰曞叆鏃ユ湡锛�" prop="entryDate">
+ <el-date-picker v-model="form.entryDate"
+ style="width: 100%"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-form-item label="浜у搧淇℃伅锛�">
+ <el-button type="primary" @click="openProductForm('add')">娣诲姞</el-button>
+ <el-button plain type="danger" @click="deleteProduct">鍒犻櫎</el-button>
+ </el-form-item>
+ <div class="select-button-group">
+ <el-select v-model="templateName"
+ filterable
+ allow-create
+ :reserve-keyword="true"
+ :default-first-option="false"
+ clearable
+ :input-value="filterInputValue"
+ @filter-change="onTemplateFilterChange"
+ @change="onTemplateChange"
+ @focus="getTemplateList"
+ placeholder="璇烽�夋嫨妯$増鎴栬�呰緭鍏ユ柊鐨勬ā鐗堝悕绉板悗閫夋嫨"
+ class="no-arrow-select template-select">
+ <el-option v-for="item in templateList"
+ :key="item.id || item.value"
+ :label="item.templateName"
+ :value="item.templateName">
+ <div class="template-option">
+ <span>{{ item.templateName }}</span>
+ <el-icon v-if="item.id"
+ class="delete-icon"
+ @click.stop="handleDeleteTemplate(item)">
+ <Delete />
+ </el-icon>
+ </div>
+ </el-option>
+ </el-select>
+ <el-button size="small"
+ class="template-save-button"
+ :disabled="!templateName || templateName.trim() === '' || (!currentTemplateId && isTemplateNameDuplicate)"
+ @click="handleButtonClick">
+ 淇濆瓨
+ </el-button>
+ </div>
+ </el-row>
+ <el-table :data="productData"
+ border
+ show-summary
+ :summary-method="summarizeProTable"
+ @selection-change="productSelected">
+ <el-table-column align="center" type="selection" width="55" />
+ <el-table-column align="center" label="搴忓彿" type="index" width="60" />
+ <el-table-column label="浜у搧澶х被" prop="productCategory" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="specificationModel" />
+ <el-table-column label="鍗曚綅" prop="unit" width="70" />
+ <el-table-column label="鏁伴噺" prop="quantity" width="70" />
+ <el-table-column label="绋庣巼(%)" prop="taxRate" width="80" />
+ <el-table-column label="鍚◣鍗曚环(鍏�)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" width="150" />
+ <el-table-column label="鍚◣鎬讳环(鍏�)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" width="150" />
+ <el-table-column label="涓嶅惈绋庢�讳环(鍏�)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" width="150" />
+ <el-table-column label="鏄惁璐ㄦ" prop="isChecked" width="150">
+ <template #default="{ row }">
+ <el-tag :type="row.isChecked ? 'success' : 'info'">
+ {{ row.isChecked ? '鏄�' : '鍚�' }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column fixed="right" label="鎿嶄綔" min-width="60" align="center">
+ <template #default="{ row, $index }">
+ <el-button link type="primary" @click="openProductForm('edit', row, $index)">缂栬緫</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="澶囨敞路锛�" prop="remarks">
+ <el-input v-model="form.remarks" placeholder="璇疯緭鍏�" clearable type="textarea" :rows="2" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="闄勪欢鏉愭枡锛�" prop="purchaseLedgerFiles">
+ <FileUpload v-model:file-list="fileList" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </FormDialog>
+
+ <FormDialog v-model="productFormVisible"
+ :title="productOperationType === 'add' ? '鏂板浜у搧' : '缂栬緫浜у搧'"
+ :width="'40%'"
+ :operation-type="productOperationType"
+ @close="closeProductDia"
+ @confirm="submitProduct"
+ @cancel="closeProductDia">
+ <el-form :model="productForm"
+ label-width="140px"
+ label-position="top"
+ :rules="productRules"
+ ref="productFormRef">
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="浜у搧澶х被锛�" prop="productCategory">
+ <el-input v-model="productForm.productCategory"
+ placeholder="鐐瑰嚮閫夋嫨"
+ readonly
+ style="cursor:pointer"
+ @click="showProductSelectDialog = true" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="瑙勬牸鍨嬪彿锛�" prop="productModelId">
+ <el-input v-model="productForm.specificationModel"
+ placeholder="鐐瑰嚮閫夋嫨"
+ readonly
+ style="cursor:pointer"
+ @click="showProductSelectDialog = true" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍗曚綅锛�" prop="unit">
+ <el-input v-model="productForm.unit" placeholder="璇疯緭鍏�" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="绋庣巼(%)锛�" prop="taxRate">
+ <el-select v-model="productForm.taxRate" placeholder="璇烽�夋嫨" clearable @change="mathNum">
+ <el-option v-for="dict in tax_rate"
+ :key="dict.value"
+ :label="dict.label"
+ :value="dict.value" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍚◣鍗曚环(鍏�)锛�" prop="taxInclusiveUnitPrice">
+ <el-input-number v-model="productForm.taxInclusiveUnitPrice"
+ :precision="6"
+ :step="0.000001"
+ :min="0"
+ clearable
+ style="width: 100%"
+ @change="mathNum" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏁伴噺锛�" prop="quantity">
+ <el-input-number v-model="productForm.quantity"
+ :step="0.1"
+ clearable
+ :precision="2"
+ :min="0"
+ style="width: 100%"
+ placeholder="璇疯緭鍏�"
+ @change="mathNum" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍚◣鎬讳环(鍏�)锛�" prop="taxInclusiveTotalPrice">
+ <el-input-number v-model="productForm.taxInclusiveTotalPrice"
+ :precision="6"
+ :step="0.000001"
+ :min="0"
+ clearable
+ style="width: 100%"
+ @change="reverseMathNum('taxInclusiveTotalPrice')" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="涓嶅惈绋庢�讳环(鍏�)锛�" prop="taxExclusiveTotalPrice">
+ <el-input-number v-model="productForm.taxExclusiveTotalPrice"
+ :precision="6"
+ :step="0.000001"
+ :min="0"
+ clearable
+ style="width: 100%"
+ @change="reverseMathNum('taxExclusiveTotalPrice')" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="鍙戠エ绫诲瀷锛�" prop="invoiceType">
+ <el-select v-model="productForm.invoiceType" placeholder="璇烽�夋嫨" clearable>
+ <el-option label="澧炴櫘绁�" value="澧炴櫘绁�" />
+ <el-option label="澧炰笓绁�" value="澧炰笓绁�" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏄惁璐ㄦ锛�" prop="isChecked">
+ <el-radio-group v-model="productForm.isChecked">
+ <el-radio label="鏄�" :value="true" />
+ <el-radio label="鍚�" :value="false" />
+ </el-radio-group>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ </FormDialog>
+
+ <ProductSelectDialog v-model="showProductSelectDialog"
+ @confirm="handleProductSelect"
+ single />
+ </div>
+</template>
+
+<script setup>
+ import { computed, getCurrentInstance, nextTick, reactive, ref, toRefs } from "vue";
+ import { Delete } from "@element-plus/icons-vue";
+ import { ElMessage, ElMessageBox } from "element-plus";
+ import FormDialog from "@/components/Dialog/FormDialog.vue";
+ import FileUpload from "@/components/AttachmentUpload/file/index.vue";
+ import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+ import useUserStore from "@/store/modules/user";
+ import { userListNoPage } from "@/api/system/user.js";
+ import { getProductInfoByContractNo } from "@/api/salesManagement/salesLedger.js";
+ import {
+ addOrEditPurchase,
+ addPurchaseTemplate,
+ createPurchaseNo,
+ delPurchaseTemplate,
+ getOptions,
+ getPurchaseTemplateList,
+ getSalesNo,
+ updatePurchaseTemplate,
+ } from "@/api/procurementManagement/procurementLedger.js";
+ import { tableAmountFormatter, formatDecimal, buildAmountSummaryFormat } from "@/utils/numberFormat";
+
+ const props = defineProps({
+ modelValue: { type: Boolean, default: false },
+ initialProducts: { type: Array, default: () => [] },
+ });
+ const emit = defineEmits(["update:modelValue", "saved"]);
+
+ const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: val => emit("update:modelValue", val),
+ });
+
+ const { proxy } = getCurrentInstance();
+ const { tax_rate } = proxy.useDict("tax_rate");
+ const userStore = useUserStore();
+ const showSalesContractBinding = false;
+
+ const productData = ref([]);
+ const productSelectedRows = ref([]);
+ const userList = ref([]);
+ const salesContractList = ref([]);
+ const supplierList = ref([]);
+ const fileList = ref([]);
+ const productFormVisible = ref(false);
+ const showProductSelectDialog = ref(false);
+ const productOperationType = ref("");
+ const productOperationIndex = ref(-1);
+ const templateName = ref("");
+ const filterInputValue = ref("");
+ const templateList = ref([]);
+ const isTemplateNameDuplicate = ref(false);
+ const currentTemplateId = ref(null);
+ let duplicateCheckTimer = null;
+
+ const data = reactive({
+ form: {
+ purchaseContractNumber: "",
+ salesLedgerId: "",
+ projectName: "",
+ recorderId: "",
+ entryDate: "",
+ productData: [],
+ supplierName: "",
+ supplierId: "",
+ paymentMethod: "",
+ executionDate: "",
+ isChecked: false,
+ },
+ rules: {
+ purchaseContractNumber: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ projectName: [{ required: true, message: "璇疯緭鍏ラ」鐩悕绉�", trigger: "blur" }],
+ supplierId: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ entryDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ executionDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ },
+ });
+ const { form, rules } = toRefs(data);
+
+ const productFormData = reactive({
+ productForm: {
+ productId: "",
+ productCategory: "",
+ productModelId: "",
+ specificationModel: "",
+ unit: "",
+ quantity: "",
+ taxInclusiveUnitPrice: "",
+ taxRate: "",
+ taxInclusiveTotalPrice: "",
+ taxExclusiveTotalPrice: "",
+ invoiceType: "",
+ warnNum: "",
+ isChecked: false,
+ },
+ productRules: {
+ productCategory: [{ required: true, message: "璇烽�夋嫨浜у搧澶х被", trigger: "change" }],
+ productModelId: [{ required: true, message: "璇烽�夋嫨瑙勬牸鍨嬪彿", trigger: "change" }],
+ unit: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ quantity: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ taxInclusiveUnitPrice: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ taxRate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ taxInclusiveTotalPrice: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ taxExclusiveTotalPrice: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ invoiceType: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ isChecked: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ },
+ });
+ const { productForm, productRules } = toRefs(productFormData);
+
+ const formattedNumber = tableAmountFormatter;
+
+ const getCurrentDate = () => {
+ const today = new Date();
+ const year = today.getFullYear();
+ const month = String(today.getMonth() + 1).padStart(2, "0");
+ const day = String(today.getDate()).padStart(2, "0");
+ return `${year}-${month}-${day}`;
+ };
+
+ const normalizeProductRow = row => ({
+ productId: row.productId || "",
+ productCategory: row.productCategory || row.materialName || row.productName || "",
+ productModelId: row.productModelId || row.materialModelId || row.modelId || "",
+ specificationModel: row.specificationModel || row.materialModel || row.model || "",
+ unit: row.unit || "",
+ quantity: row.quantity || "",
+ taxInclusiveUnitPrice: row.taxInclusiveUnitPrice || "",
+ taxRate: row.taxRate || "",
+ taxInclusiveTotalPrice: row.taxInclusiveTotalPrice || "",
+ taxExclusiveTotalPrice: row.taxExclusiveTotalPrice || "",
+ invoiceType: row.invoiceType || "",
+ warnNum: row.warnNum || "",
+ isChecked: row.isChecked ?? false,
+ });
+
+ const resetDialogState = () => {
+ form.value = {
+ purchaseContractNumber: "",
+ salesLedgerId: "",
+ projectName: "",
+ recorderId: "",
+ entryDate: "",
+ productData: [],
+ supplierName: "",
+ supplierId: "",
+ paymentMethod: "",
+ executionDate: "",
+ isChecked: false,
+ };
+ productData.value = [];
+ productSelectedRows.value = [];
+ fileList.value = [];
+ templateName.value = "";
+ filterInputValue.value = "";
+ isTemplateNameDuplicate.value = false;
+ currentTemplateId.value = null;
+ };
+
+ const open = async products => {
+ resetDialogState();
+ await Promise.all([
+ getTemplateList(),
+ userListNoPage().then(res => {
+ userList.value = res.data || [];
+ }),
+ getSalesNo().then(res => {
+ salesContractList.value = res || [];
+ }),
+ getOptions().then(res => {
+ supplierList.value = (res.data || []).filter(item => item.isWhite === 0);
+ }),
+ ]);
+
+ form.value.entryDate = getCurrentDate();
+ form.value.recorderId = userStore.id;
+ try {
+ const purchaseNoRes = await createPurchaseNo();
+ if (purchaseNoRes?.data) {
+ form.value.purchaseContractNumber = purchaseNoRes.data;
+ }
+ } catch (error) {
+ console.error("鐢熸垚閲囪喘鍚堝悓鍙峰け璐�:", error);
+ proxy.$modal.msgWarning("鐢熸垚閲囪喘鍚堝悓鍙峰け璐�");
+ }
+
+ const sourceProducts = Array.isArray(products) ? products : props.initialProducts;
+ productData.value = sourceProducts.map(normalizeProductRow);
+ dialogVisible.value = true;
+ await nextTick();
+ if (productData.value.length > 0) {
+ ElMessage.warning("宸叉寜鎵�閫夐鏂欒鐢熸垚浜у搧淇℃伅锛岃缂栬緫琛ュ叏鏁伴噺銆佺◣鐜囥�佸崟浠枫�佸彂绁ㄧ被鍨嬬瓑蹇呭~椤瑰悗鍐嶆彁浜ら噰璐彴璐�");
+ }
+ };
+
+ defineExpose({ open });
+
+ const checkTemplateNameDuplicate = name => {
+ if (!name || name.trim() === "") {
+ isTemplateNameDuplicate.value = false;
+ return false;
+ }
+ const isDuplicate = templateList.value.some(item => item.templateName === name.trim());
+ isTemplateNameDuplicate.value = isDuplicate;
+ return isDuplicate;
+ };
+
+ const onTemplateFilterChange = val => {
+ filterInputValue.value = val ?? "";
+ if (duplicateCheckTimer) {
+ clearTimeout(duplicateCheckTimer);
+ }
+ if (val && val.trim()) {
+ duplicateCheckTimer = setTimeout(() => {
+ if (checkTemplateNameDuplicate(val)) {
+ ElMessage.warning("妯℃澘鍚嶇О宸插瓨鍦紝璇锋洿鎹㈡ā鏉垮悕绉�");
+ }
+ }, 300);
+ } else {
+ isTemplateNameDuplicate.value = false;
+ }
+ };
+
+ const onTemplateChange = async val => {
+ if (typeof val === "string") {
+ filterInputValue.value = val;
+ checkTemplateNameDuplicate(val);
+ }
+ const matchedTemplate = templateList.value.find(item => item.templateName === val);
+ if (matchedTemplate?.id) {
+ currentTemplateId.value = matchedTemplate.id;
+ isTemplateNameDuplicate.value = false;
+ form.value.supplierId = matchedTemplate.supplierId || form.value.supplierId;
+ form.value.supplierName = matchedTemplate.supplierName || form.value.supplierName;
+ form.value.projectName = matchedTemplate.projectName || form.value.projectName;
+ form.value.paymentMethod = matchedTemplate.paymentMethod || form.value.paymentMethod;
+ productData.value = matchedTemplate.productList || matchedTemplate.productData || [];
+ } else {
+ currentTemplateId.value = null;
+ }
+ };
+
+ const getTemplateList = async () => {
+ const res = await getPurchaseTemplateList();
+ if (res && res.code === 200 && Array.isArray(res.data)) {
+ templateList.value = res.data;
+ }
+ };
+
+ const handleButtonClick = async () => {
+ if (!templateName.value || templateName.value.trim() === "") {
+ ElMessage.warning("璇疯緭鍏ユā鏉垮悕绉�");
+ return;
+ }
+ if (!currentTemplateId.value && checkTemplateNameDuplicate(templateName.value)) {
+ ElMessage.warning("妯℃澘鍚嶇О宸插瓨鍦紝璇锋洿鎹㈡ā鏉垮悕绉�");
+ return;
+ }
+ if (!form.value.supplierId) {
+ ElMessage.warning("璇峰厛閫夋嫨渚涘簲鍟�");
+ return;
+ }
+ if (!productData.value || productData.value.length === 0) {
+ ElMessage.warning("璇峰厛娣诲姞浜у搧淇℃伅");
+ return;
+ }
+
+ const params = {
+ productData: proxy.HaveJson(productData.value),
+ supplierId: form.value.supplierId,
+ paymentMethod: form.value.paymentMethod,
+ recorderId: form.value.recorderId,
+ projectName: form.value.projectName,
+ templateName: templateName.value.trim(),
+ };
+ const res = currentTemplateId.value
+ ? await updatePurchaseTemplate({ id: currentTemplateId.value, ...params })
+ : await addPurchaseTemplate(params);
+ if (res && res.code === 200) {
+ ElMessage.success(currentTemplateId.value ? "妯℃澘鏇存柊鎴愬姛" : "妯℃澘淇濆瓨鎴愬姛");
+ await getTemplateList();
+ templateName.value = "";
+ filterInputValue.value = "";
+ isTemplateNameDuplicate.value = false;
+ currentTemplateId.value = null;
+ } else {
+ ElMessage.error(res?.msg || "妯℃澘淇濆瓨澶辫触");
+ }
+ };
+
+ const productSelected = selectedRows => {
+ productSelectedRows.value = selectedRows;
+ };
+
+ const openProductForm = (type, row, index) => {
+ productOperationType.value = type;
+ productOperationIndex.value = index ?? -1;
+ proxy.resetForm("productFormRef");
+ productForm.value = type === "edit" ? { ...row } : { isChecked: false };
+ productFormVisible.value = true;
+ };
+
+ const handleProductSelect = products => {
+ if (!products || !products.length) return;
+ const p = products[0];
+ productForm.value.productId = p.productId || "";
+ productForm.value.productCategory = p.productName || p.topProductName || "";
+ productForm.value.productModelId = p.id;
+ productForm.value.specificationModel = p.model || "";
+ productForm.value.unit = p.unit || "";
+ showProductSelectDialog.value = false;
+ };
+
+ const closeProductDia = () => {
+ proxy.resetForm("productFormRef");
+ productFormVisible.value = false;
+ };
+
+ const submitProduct = () => {
+ proxy.$refs["productFormRef"].validate(valid => {
+ if (!valid) return;
+ if (productOperationType.value === "add") {
+ productData.value.push({ ...productForm.value });
+ } else if (productOperationIndex.value > -1) {
+ productData.value[productOperationIndex.value] = { ...productForm.value };
+ }
+ closeProductDia();
+ });
+ };
+
+ const deleteProduct = () => {
+ if (productSelectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
+ return;
+ }
+ productData.value = productData.value.filter(item => !productSelectedRows.value.includes(item));
+ productSelectedRows.value = [];
+ };
+
+ const summarizeProTable = param => proxy.summarizeTable(
+ param,
+ ["taxInclusiveUnitPrice", "taxInclusiveTotalPrice", "taxExclusiveTotalPrice"],
+ buildAmountSummaryFormat(["taxInclusiveUnitPrice", "taxInclusiveTotalPrice", "taxExclusiveTotalPrice"])
+ );
+
+ const mathNum = () => {
+ if (!productForm.value.taxRate) {
+ proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
+ return;
+ }
+ if (!productForm.value.taxInclusiveUnitPrice || !productForm.value.quantity) return;
+ productForm.value.taxInclusiveTotalPrice = proxy.calculateTaxIncludeTotalPrice(
+ productForm.value.taxInclusiveUnitPrice,
+ productForm.value.quantity
+ );
+ productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice(
+ productForm.value.taxInclusiveTotalPrice,
+ productForm.value.taxRate
+ );
+ };
+
+ const reverseMathNum = field => {
+ if (!productForm.value.taxRate) {
+ proxy.$modal.msgWarning("璇峰厛閫夋嫨绋庣巼");
+ return;
+ }
+ const taxRate = Number(productForm.value.taxRate);
+ if (!taxRate) return;
+ if (field === "taxInclusiveTotalPrice") {
+ if (productForm.value.quantity) {
+ productForm.value.taxInclusiveUnitPrice = formatDecimal(
+ Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)
+ );
+ } else if (productForm.value.taxInclusiveUnitPrice) {
+ productForm.value.quantity = (
+ Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)
+ ).toFixed(2);
+ }
+ productForm.value.taxExclusiveTotalPrice = formatDecimal(
+ Number(productForm.value.taxInclusiveTotalPrice) / (1 + taxRate / 100)
+ );
+ } else if (field === "taxExclusiveTotalPrice") {
+ productForm.value.taxInclusiveTotalPrice = formatDecimal(
+ Number(productForm.value.taxExclusiveTotalPrice) * (1 + taxRate / 100)
+ );
+ if (productForm.value.quantity) {
+ productForm.value.taxInclusiveUnitPrice = formatDecimal(
+ Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.quantity)
+ );
+ } else if (productForm.value.taxInclusiveUnitPrice) {
+ productForm.value.quantity = (
+ Number(productForm.value.taxInclusiveTotalPrice) / Number(productForm.value.taxInclusiveUnitPrice)
+ ).toFixed(2);
+ }
+ }
+ };
+
+ const salesLedgerChange = async () => {
+ await querygProductInfoByContractNo();
+ };
+
+ const querygProductInfoByContractNo = async () => {
+ const { code, data } = await getProductInfoByContractNo({
+ contractNo: form.value.salesLedgerId,
+ });
+ if (code === 200) {
+ productData.value = data;
+ }
+ };
+
+ const validateProductRows = () => {
+ if (productData.value.length === 0) {
+ return "璇锋坊鍔犱骇鍝佷俊鎭�";
+ }
+ const requiredFields = [
+ ["productCategory", "浜у搧澶х被"],
+ ["productModelId", "瑙勬牸鍨嬪彿"],
+ ["unit", "鍗曚綅"],
+ ["quantity", "鏁伴噺"],
+ ["taxRate", "绋庣巼"],
+ ["taxInclusiveUnitPrice", "鍚◣鍗曚环"],
+ ["taxInclusiveTotalPrice", "鍚◣鎬讳环"],
+ ["taxExclusiveTotalPrice", "涓嶅惈绋庢�讳环"],
+ ["invoiceType", "鍙戠エ绫诲瀷"],
+ ];
+ for (let i = 0; i < productData.value.length; i++) {
+ const row = productData.value[i];
+ const missing = requiredFields.find(([field]) => row[field] === "" || row[field] === null || row[field] === undefined);
+ if (missing) {
+ return `绗�${i + 1}琛屼骇鍝佷俊鎭己灏�${missing[1]}锛岃缂栬緫瀹屽啀鎻愪氦`;
+ }
+ }
+ return "";
+ };
+
+ const submitForm = () => {
+ proxy.$refs["formRef"].validate(valid => {
+ if (!valid) return;
+ const productMessage = validateProductRows();
+ if (productMessage) {
+ proxy.$modal.msgWarning(productMessage);
+ return;
+ }
+
+ const processedProductData = productData.value.map(product => {
+ const { id, ...rest } = product;
+ return rest;
+ });
+ const submitData = {
+ ...form.value,
+ productData: proxy.HaveJson(processedProductData),
+ storageBlobDTOS: fileList.value,
+ type: 2,
+ };
+ if (!submitData.salesLedgerId) {
+ submitData.salesContractNo = "";
+ }
+ delete submitData.id;
+
+ addOrEditPurchase(submitData).then(() => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+ emit("saved");
+ });
+ });
+ };
+
+ const closeDia = () => {
+ proxy.resetForm("formRef");
+ dialogVisible.value = false;
+ };
+
+ const handleDeleteTemplate = async item => {
+ if (!item.id) {
+ proxy.$modal.msgWarning("鏃犳硶鍒犻櫎璇ユā鏉�");
+ return;
+ }
+ try {
+ await ElMessageBox.confirm(`纭畾瑕佸垹闄ゆā鏉�"${item.templateName}"鍚楋紵`, "鍒犻櫎纭", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ });
+ const res = await delPurchaseTemplate([item.id]);
+ if (res && res.code === 200) {
+ ElMessage.success("鍒犻櫎鎴愬姛");
+ if (templateName.value === item.templateName) {
+ templateName.value = "";
+ filterInputValue.value = "";
+ }
+ await getTemplateList();
+ } else {
+ ElMessage.error(res?.msg || "鍒犻櫎澶辫触");
+ }
+ } catch (error) {
+ if (error !== "cancel") {
+ ElMessage.error("鍒犻櫎澶辫触锛岃绋嶅悗閲嶈瘯");
+ }
+ }
+ };
+</script>
+
+<style scoped lang="scss">
+ .el-row {
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .select-button-group {
+ display: flex;
+ align-items: center;
+ width: 500px;
+ margin: 20px 0;
+ }
+
+ .template-select {
+ width: 500px;
+ }
+
+ .template-save-button {
+ height: 32px;
+ margin-left: 8px;
+ }
+
+ .template-option {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .no-arrow-select {
+ --el-select-suffix-icon-color: transparent;
+ }
+
+ .delete-icon {
+ cursor: pointer;
+ color: #f56c6c;
+ font-size: 14px;
+ margin-left: 8px;
+ transition: all 0.3s;
+
+ &:hover {
+ color: #f56c6c !important;
+ transform: scale(1.2);
+ }
+ }
+</style>
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index 5c3d4cf..9ac0ffa 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -11,6 +11,14 @@
style="width: 160px;"
@change="handleQuery" />
</el-form-item>
+ <el-form-item label="閿�鍞崟鍙�:">
+ <el-input v-model="searchForm.salesContractNo"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ style="width: 160px;"
+ @change="handleQuery" />
+ </el-form-item>
<el-form-item label="浜у搧鍚嶇О:">
<el-input v-model="searchForm.productName"
placeholder="璇疯緭鍏�"
@@ -183,7 +191,7 @@
<div class="info-value">{{ item.unit || '-' }}</div>
</div>
<div class="info-item">
- <div class="info-label">闇�姹傛棩鏈�</div>
+ <div class="info-label">浜よ揣鏃ユ湡</div>
<div class="info-value">{{ item.requiredDate ? dayjs(item.requiredDate).format('YYYY-MM-DD') : '-' }}</div>
</div>
</div>
@@ -194,6 +202,7 @@
</el-dialog>
<MaterialLedgerDialog v-model="materialDialogVisible"
:order-row="currentMaterialOrder"
+ @saved="getList"
/>
<MaterialDetailDialog v-model="materialDetailDialogVisible"
:order-row="currentMaterialDetailOrder"
@@ -277,6 +286,11 @@
prop: "npsNo",
width: "150px",
},
+ {
+ label: "閿�鍞崟鍙�",
+ prop: "salesContractNo",
+ width: "150px",
+ },
// 1.寰呭紑濮嬨��2.杩涜涓��3.宸插畬鎴愩��4.宸插彇娑堛��5.宸茬粨鏉�
{
label: "鐘舵��",
diff --git a/src/views/productionManagement/productionProcess/ProcessSelectDialog.vue b/src/views/productionManagement/productionProcess/ProcessSelectDialog.vue
new file mode 100644
index 0000000..9ece5b8
--- /dev/null
+++ b/src/views/productionManagement/productionProcess/ProcessSelectDialog.vue
@@ -0,0 +1,159 @@
+<template>
+ <el-dialog v-model="visible" title="閫夋嫨宸ュ簭" width="800px" destroy-on-close :close-on-click-modal="false">
+ <el-form :inline="true" :model="query" class="mb-2">
+ <el-form-item label="宸ュ簭鍚嶇О">
+ <el-input v-model="query.name" placeholder="杈撳叆宸ュ簭鍚嶇О" clearable @keyup.enter="onSearch" style="width: 180px" />
+ </el-form-item>
+ <el-form-item label="宸ュ簭缂栫爜">
+ <el-input v-model="query.no" placeholder="杈撳叆宸ュ簭缂栫爜" clearable @keyup.enter="onSearch" style="width: 180px" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="onSearch">鎼滅储</el-button>
+ <el-button @click="onReset">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <el-table ref="tableRef" v-loading="loading" :data="tableData" height="380" highlight-current-row row-key="id"
+ @current-change="handleCurrentChange" @row-dblclick="onConfirm">
+ <el-table-column type="index" label="搴忓彿" width="60" />
+ <el-table-column prop="no" label="宸ュ簭缂栫爜" min-width="130" />
+ <el-table-column prop="name" label="宸ュ簭鍚嶇О" min-width="150" />
+ <el-table-column prop="isQuality" label="鏄惁璐ㄦ" min-width="90">
+ <template #default="{ row }">
+ <el-tag size="small" :type="row.isQuality ? 'warning' : 'info'">{{ row.isQuality ? '鏄�' : '鍚�' }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="isProduction" label="鏄惁鐢熶骇" min-width="90">
+ <template #default="{ row }">
+ <el-tag size="small" :type="row.isProduction ? 'success' : 'info'">{{ row.isProduction ? '鏄�' : '鍚�' }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="remark" label="澶囨敞" min-width="120" show-overflow-tooltip />
+ </el-table>
+
+ <div class="mt-3 flex justify-end">
+ <el-pagination background layout="total, sizes, prev, pager, next, jumper" :total="total"
+ v-model:page-size="page.pageSize" v-model:current-page="page.pageNum" :page-sizes="[10, 20, 50, 100]"
+ @size-change="onPageChange" @current-change="onPageChange" />
+ </div>
+
+ <template #footer>
+ <el-button type="primary" :disabled="!selectedRow" @click="onConfirm">纭畾</el-button>
+ <el-button @click="close()">鍙栨秷</el-button>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup lang="ts">
+ import { computed, reactive, ref, watch } from "vue";
+ import { ElMessage } from "element-plus";
+ import { listPage } from "@/api/productionManagement/productionProcess";
+
+ export type ProcessRow = {
+ id: number;
+ no?: string;
+ name: string;
+ isQuality?: boolean;
+ isProduction?: boolean;
+ remark?: string;
+ };
+
+ const props = defineProps<{
+ modelValue: boolean;
+ }>();
+
+ const emit = defineEmits(["update:modelValue", "confirm"]);
+
+ const visible = computed({
+ get: () => props.modelValue,
+ set: v => emit("update:modelValue", v),
+ });
+
+ const query = reactive({
+ name: "",
+ no: "",
+ });
+
+ const page = reactive({
+ pageNum: 1,
+ pageSize: 10,
+ });
+
+ const loading = ref(false);
+ const tableData = ref<ProcessRow[]>([]);
+ const total = ref(0);
+ const selectedRow = ref<ProcessRow | null>(null);
+ const tableRef = ref();
+
+ function close() {
+ visible.value = false;
+ }
+
+ function handleCurrentChange(row: ProcessRow) {
+ selectedRow.value = row;
+ }
+
+ function onSearch() {
+ page.pageNum = 1;
+ loadData();
+ }
+
+ function onReset() {
+ query.name = "";
+ query.no = "";
+ onSearch();
+ }
+
+ function onPageChange() {
+ loadData();
+ }
+
+ function onConfirm() {
+ if (!selectedRow.value) {
+ ElMessage.warning("璇烽�夋嫨涓�涓伐搴�");
+ return;
+ }
+ emit("confirm", selectedRow.value);
+ close();
+ }
+
+ async function loadData() {
+ loading.value = true;
+ try {
+ selectedRow.value = null;
+ if (tableRef.value) {
+ tableRef.value.setCurrentRow(null);
+ }
+ const { data } = await listPage({
+ current: page.pageNum,
+ size: page.pageSize,
+ name: query.name.trim(),
+ no: query.no.trim(),
+ });
+ const records = data?.records ?? (Array.isArray(data) ? data : []);
+ tableData.value = Array.isArray(records) ? records : [];
+ total.value = Number(data?.total ?? tableData.value.length);
+ } catch (e) {
+ console.error("鍔犺浇宸ュ簭鍒楄〃澶辫触", e);
+ tableData.value = [];
+ total.value = 0;
+ } finally {
+ loading.value = false;
+ }
+ }
+
+ // 鐩戝惉寮圭獥鎵撳紑锛岄噸缃煡璇㈡潯浠跺拰椤电爜锛坕mmediate锛氱粍浠跺垱寤烘椂 modelValue 宸蹭负 true锛屼篃绔嬪嵆鍔犺浇锛�
+ watch(
+ () => props.modelValue,
+ visible => {
+ if (visible) {
+ selectedRow.value = null;
+ query.name = "";
+ query.no = "";
+ page.pageNum = 1;
+ loadData();
+ }
+ },
+ { immediate: true }
+ );
+</script>
\ No newline at end of file
diff --git a/src/views/productionManagement/productionTraceability/index.vue b/src/views/productionManagement/productionTraceability/index.vue
index 19f7ed9..9312a44 100644
--- a/src/views/productionManagement/productionTraceability/index.vue
+++ b/src/views/productionManagement/productionTraceability/index.vue
@@ -157,7 +157,7 @@
<el-table-column label="鍒涘缓鏃堕棿"
align="center">
<template #default="{ row }">
- {{ parseTime(row.createTime) }}
+ {{ parseTime(row.createTime, '{y}-{m}-{d}') }}
</template>
</el-table-column>
<el-table-column label="宸ユ椂锛坔锛�"
diff --git a/src/views/productionPlan/productionPlan/index.vue b/src/views/productionPlan/productionPlan/index.vue
index fd56e89..44297e9 100644
--- a/src/views/productionPlan/productionPlan/index.vue
+++ b/src/views/productionPlan/productionPlan/index.vue
@@ -21,7 +21,7 @@
style="width: 160px;"
@keyup.enter="handleQuery" />
</el-form-item>
- <el-form-item label="闇�姹傛棩鏈熻寖鍥�:"
+ <el-form-item label="浜よ揣鏃ユ湡鑼冨洿:"
prop="dateRange">
<el-date-picker v-model="searchForm.dateRange"
type="daterange"
@@ -212,13 +212,13 @@
<el-input v-model="form.unit"
placeholder="璇疯緭鍏ュ崟浣�" />
</el-form-item>
- <el-form-item label="闇�姹傛棩鏈�"
+ <el-form-item label="鍙戣揣鏃ユ湡"
prop="requiredDate">
<el-date-picker v-model="form.requiredDate"
type="date"
value-format="YYYY-MM-DD"
style="width: 100%"
- placeholder="璇烽�夋嫨闇�姹傛棩鏈�" />
+ placeholder="璇烽�夋嫨鍙戣揣鏃ユ湡" />
</el-form-item>
<el-form-item label="鎵胯鏃ユ湡"
prop="promisedDeliveryDate">
@@ -371,7 +371,7 @@
// formatData: (cell, row) => (cell ? `${cell}${row.unit || "鏂�"}` : 0),
},
{
- label: "闇�姹傛棩鏈�",
+ label: "鍙戣揣鏃ユ湡",
prop: "requiredDate",
width: "160px",
className: "date-cell",
@@ -513,7 +513,7 @@
],
qtyRequired: [{ required: true, message: "璇疯緭鍏ユ暟閲�", trigger: "blur" }],
requiredDate: [
- { required: true, message: "璇烽�夋嫨闇�姹傛棩鏈�", trigger: "change" },
+ { required: true, message: "璇烽�夋嫨鍙戣揣鏃ユ湡", trigger: "change" },
],
});
@@ -530,8 +530,7 @@
});
};
const onBlur = value => {
- // 闄愬埗鍥涗綅灏忔暟
- mergeForm.totalAssignedQuantity = Number(value.toFixed(4));
+ mergeForm.totalAssignedQuantity = Number(Number(value).toFixed(10));
};
const fetchProductOptions = () => {
@@ -656,9 +655,9 @@
};
}
summary[category].totalAssignedQuantity += Number(
- (Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0)).toFixed(
- 4
- )
+ (
+ Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0)
+ ).toFixed(10)
);
});
@@ -739,13 +738,14 @@
// 璁$畻鎬诲埗閫犳暟閲� (榛樿qtyRequired鐨勫拰)
const totalAssignedQuantity = selectedRows.value.reduce((sum, row) => {
return sum + Number(row.qtyRequired || 0) - Number(row.quantityIssued || 0);
- }, 0);
- sumAssignedQuantity.value = totalAssignedQuantity;
- console.log(totalAssignedQuantity);
+ }, 0); // 閬垮厤娴偣鏁扮簿搴﹂棶棰橈紙濡� 37.800000000000004锛�
+ const fixedTotal = Number(totalAssignedQuantity.toFixed(10));
+ sumAssignedQuantity.value = fixedTotal;
+ console.log(fixedTotal);
// 璁剧疆琛ㄥ崟鏁版嵁
mergeForm.productName = productName;
mergeForm.model = firstRow.model || "";
- mergeForm.totalAssignedQuantity = totalAssignedQuantity;
+ mergeForm.totalAssignedQuantity = fixedTotal;
mergeForm.planCompleteTime = firstRow.requiredDate || "";
mergeForm.productId = firstRow.productId || "";
mergeForm.ids = selectedRows.value.map(row => row.id);
diff --git a/src/views/qualityManagement/finalInspection/index.vue b/src/views/qualityManagement/finalInspection/index.vue
index 181f8bb..198adb2 100644
--- a/src/views/qualityManagement/finalInspection/index.vue
+++ b/src/views/qualityManagement/finalInspection/index.vue
@@ -2,7 +2,16 @@
<div class="app-container">
<div class="search_form mb20">
<div>
- <span class="search_title">浜у搧鍚嶇О锛�</span>
+ <span class="search_title">閿�鍞崟鍙凤細</span>
+ <el-input
+ v-model="searchForm.salesContractNo"
+ style="width: 240px"
+ placeholder="璇疯緭鍏ラ攢鍞崟鍙锋悳绱�"
+ @change="handleQuery"
+ clearable
+ :prefix-icon="Search"
+ />
+ <span style="margin-left: 10px" class="search_title">浜у搧鍚嶇О锛�</span>
<el-input
v-model="searchForm.productName"
style="width: 240px"
@@ -80,6 +89,7 @@
const data = reactive({
searchForm: {
+ salesContractNo: "",
productName: "",
entryDate: undefined, // 褰曞叆鏃ユ湡
entryDateStart: undefined,
diff --git a/src/views/qualityManagement/nonconformingManagement/components/detailModal.vue b/src/views/qualityManagement/nonconformingManagement/components/detailModal.vue
new file mode 100644
index 0000000..4622796
--- /dev/null
+++ b/src/views/qualityManagement/nonconformingManagement/components/detailModal.vue
@@ -0,0 +1,98 @@
+<template>
+ <el-dialog
+ v-model="visible"
+ title="涓嶅悎鏍煎鐞嗚鎯�"
+ width="70%"
+ @close="handleClose"
+ >
+ <el-descriptions border :column="2" class="detail-descriptions">
+ <el-descriptions-item label="绫诲埆">
+ {{ getInspectTypeName(form.inspectType) }}
+ </el-descriptions-item>
+ <el-descriptions-item label="浜у搧鍚嶇О">
+ {{ form.productName }}
+ </el-descriptions-item>
+ <el-descriptions-item label="瑙勬牸鍨嬪彿">
+ {{ form.model }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鍗曚綅">
+ {{ form.unit }}
+ </el-descriptions-item>
+ <el-descriptions-item label="鏁伴噺">
+ {{ form.quantity }}
+ </el-descriptions-item>
+ <el-descriptions-item label="妫�娴嬫棩鏈�">
+ {{ form.checkTime }}
+ </el-descriptions-item>
+ <el-descriptions-item label="宸ュ簭">
+ {{ form.process }}
+ </el-descriptions-item>
+ <el-descriptions-item label="妫�楠屽憳">
+ {{ form.checkName }}
+ </el-descriptions-item>
+ <el-descriptions-item label="妫�娴嬪崟浣�">
+ {{ form.checkCompany }}
+ </el-descriptions-item>
+ <el-descriptions-item label="涓嶅悎鏍肩幇璞�" :span="2">
+ {{ form.defectivePhenomena }}
+ </el-descriptions-item>
+ <el-descriptions-item label="澶勭悊缁撴灉">
+ {{ form.dealResult }}
+ </el-descriptions-item>
+ <el-descriptions-item label="澶勭悊浜�">
+ {{ form.dealName }}
+ </el-descriptions-item>
+ <el-descriptions-item label="澶勭悊鏃ユ湡">
+ {{ form.dealTime }}
+ </el-descriptions-item>
+ </el-descriptions>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="handleClose">鍏抽棴</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { getQualityUnqualifiedDetail } from "@/api/qualityManagement/nonconformingManagement.js";
+
+const visible = ref(false);
+const form = ref({});
+
+const getInspectTypeName = (type) => {
+ if (type === 0) return "鍘熸潗鏂欐楠�";
+ if (type === 1) return "杩囩▼妫�楠�";
+ if (type === 2) return "鍑哄巶妫�楠�";
+ return "";
+};
+
+const openDialog = (row) => {
+ visible.value = true;
+ form.value = { ...row };
+ if (row.id) {
+ getQualityUnqualifiedDetail(row.id).then((res) => {
+ if (res.data) {
+ form.value = { ...res.data };
+ }
+ });
+ }
+};
+
+const handleClose = () => {
+ visible.value = false;
+ form.value = {};
+};
+
+defineExpose({
+ openDialog,
+});
+</script>
+
+<style scoped>
+.detail-descriptions :deep(.el-descriptions__label) {
+ width: 120px;
+ justify-content: flex-end;
+}
+</style>
diff --git a/src/views/qualityManagement/nonconformingManagement/index.vue b/src/views/qualityManagement/nonconformingManagement/index.vue
index fc9d5d2..7f9feb6 100644
--- a/src/views/qualityManagement/nonconformingManagement/index.vue
+++ b/src/views/qualityManagement/nonconformingManagement/index.vue
@@ -55,6 +55,7 @@
</div>
<FormDia ref="formDia" @close="handleQuery"></FormDia>
<InspectionFormDia ref="inspectionFormDia" @close="handleQuery"></InspectionFormDia>
+ <DetailModal ref="detailModal"></DetailModal>
</div>
</template>
@@ -65,6 +66,7 @@
import {ElMessageBox} from "element-plus";
import {qualityUnqualifiedDel, qualityUnqualifiedListPage} from "@/api/qualityManagement/nonconformingManagement.js";
import InspectionFormDia from "@/views/qualityManagement/nonconformingManagement/components/inspectionFormDia.vue";
+import DetailModal from "@/views/qualityManagement/nonconformingManagement/components/detailModal.vue";
import dayjs from "dayjs";
const data = reactive({
@@ -177,7 +179,7 @@
label: "鎿嶄綔",
align: "center",
fixed: "right",
- width: 100,
+ width: 150,
operation: [
{
name: "澶勭悊",
@@ -186,6 +188,13 @@
openInspectionForm("edit", row);
},
disabled: (row) => row.inspectState === 1,
+ },
+ {
+ name: "璇︽儏",
+ type: "text",
+ clickFun: (row) => {
+ detailModal.value?.openDialog(row);
+ },
},
],
},
@@ -200,6 +209,7 @@
});
const formDia = ref()
const inspectionFormDia = ref()
+const detailModal = ref()
const { proxy } = getCurrentInstance()
const changeDaterange = (value) => {
diff --git a/src/views/qualityManagement/processInspection/index.vue b/src/views/qualityManagement/processInspection/index.vue
index 1f2b26e..15ce00b 100644
--- a/src/views/qualityManagement/processInspection/index.vue
+++ b/src/views/qualityManagement/processInspection/index.vue
@@ -2,7 +2,16 @@
<div class="app-container">
<div class="search_form mb20">
<div>
- <span class="search_title">宸ュ簭锛�</span>
+ <span class="search_title">鐢熶骇宸ュ崟鍙凤細</span>
+ <el-input
+ v-model="searchForm.workOrderNo"
+ style="width: 240px"
+ placeholder="璇疯緭鍏ョ敓浜у伐鍗曞彿鎼滅储"
+ @change="handleQuery"
+ clearable
+ :prefix-icon="Search"
+ />
+ <span style="margin-left: 10px" class="search_title">宸ュ簭锛�</span>
<el-input
v-model="searchForm.process"
style="width: 240px"
@@ -79,6 +88,7 @@
const data = reactive({
searchForm: {
+ workOrderNo: "",
process: "",
entryDate: undefined, // 褰曞叆鏃ユ湡
entryDateStart: undefined,
diff --git a/src/views/qualityManagement/rawMaterialInspection/index.vue b/src/views/qualityManagement/rawMaterialInspection/index.vue
index 346ae9b..74d4766 100644
--- a/src/views/qualityManagement/rawMaterialInspection/index.vue
+++ b/src/views/qualityManagement/rawMaterialInspection/index.vue
@@ -2,7 +2,16 @@
<div class="app-container">
<div class="search_form mb20">
<div>
- <span class="search_title">渚涘簲鍟嗭細</span>
+ <span class="search_title">閲囪喘璁㈠崟鍙凤細</span>
+ <el-input
+ v-model="searchForm.purchaseContractNo"
+ style="width: 240px"
+ placeholder="璇疯緭鍏ラ噰璐鍗曞彿鎼滅储"
+ @change="handleQuery"
+ clearable
+ :prefix-icon="Search"
+ />
+ <span style="margin-left: 10px" class="search_title">渚涘簲鍟嗭細</span>
<el-input
v-model="searchForm.supplier"
style="width: 240px"
@@ -81,6 +90,7 @@
const data = reactive({
searchForm: {
+ purchaseContractNo: "",
supplier: "",
entryDate: undefined, // 褰曞叆鏃ユ湡
entryDateStart: undefined,
diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
index 9362a18..8dab3ca 100644
--- a/src/views/salesManagement/salesLedger/index.vue
+++ b/src/views/salesManagement/salesLedger/index.vue
@@ -24,6 +24,18 @@
prefix-icon="Search"
@change="handleQuery" />
</el-form-item>
+ <el-form-item label="鍙戣揣鐘舵�侊細">
+ <el-select v-model="searchForm.shippingStatus"
+ placeholder="璇烽�夋嫨"
+ clearable
+ style="width: 160px"
+ @change="handleQuery">
+ <el-option label="鏈彂璐�"
+ value="鏈彂璐�" />
+ <el-option label="宸插彂璐�"
+ value="宸插彂璐�" />
+ </el-select>
+ </el-form-item>
<el-form-item label="褰曞叆鏃ユ湡锛�">
<el-date-picker v-model="searchForm.entryDate"
value-format="YYYY-MM-DD"
@@ -910,7 +922,7 @@
<!-- 鍙戣揣寮规 -->
<el-dialog v-model="deliveryFormVisible"
title="鍙戣揣淇℃伅"
- width="40%"
+ width="960px"
@close="closeDeliveryDia">
<el-form :model="deliveryForm"
label-width="120px"
@@ -935,6 +947,12 @@
<el-col :span="24">
<el-form-item label="寰呭彂璐ф暟閲忥細">
<el-input :model-value="currentDeliveryRow?.noQuantity"
+ disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="24">
+ <el-form-item label="宸查�夋嫨鍙戣揣鏁伴噺锛�">
+ <el-input :model-value="selectedDeliveryQuantity"
disabled />
</el-form-item>
</el-col>
@@ -990,6 +1008,13 @@
<el-table-column label="鎵瑰彿"
prop="batchNo"
min-width="180" />
+ <el-table-column label="閿�鍞鍗曞彿"
+ min-width="180"
+ show-overflow-tooltip>
+ <template #default="scope">
+ {{ formatDeliveryBatchSalesOrderNo(scope.row) }}
+ </template>
+ </el-table-column>
<el-table-column label="浜у搧澶х被"
prop="productName"
min-width="100" />
@@ -1013,8 +1038,6 @@
<el-input-number v-model="scope.row.deliveryQuantity"
:min="0"
:max="getDeliveryBatchDeliveryMax(scope.row)"
- :precision="2"
- :step="0.01"
controls-position="right"
@change="handleDeliveryBatchQuantityChange(scope.row)"
style="width: 100%;" />
@@ -1071,6 +1094,7 @@
tableAmountFormatter,
formatDecimal,
buildAmountSummaryFormat,
+ roundAmount,
} from "@/utils/numberFormat";
import ImageUpload from "@/components/AttachmentUpload/image/index.vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
@@ -1111,6 +1135,8 @@
searchForm: {
customerName: "", // 瀹㈡埛鍚嶇О
salesContractNo: "", // 閿�鍞悎鍚岀紪鍙�
+ projectName: "", // 椤圭洰鍚嶇О
+ shippingStatus: "", // 鍙戣揣鐘舵�侊紙鏈彂璐�/宸插彂璐э級
entryDate: null, // 褰曞叆鏃ユ湡
entryDateStart: undefined,
entryDateEnd: undefined,
@@ -1214,6 +1240,8 @@
item?.stockQuantity;
return quantity ?? 0;
};
+ const formatDeliveryBatchSalesOrderNo = item =>
+ item?.salesContractNo || "--";
const currentStock = ref(null);
const fetchCurrentStock = async (productModelId) => {
if (!productModelId) {
@@ -1221,7 +1249,7 @@
return;
}
try {
- const res = await getStockInventoryByModelId(productModelId);
+ const res = await getStockInventoryByModelId(productModelId, currentId.value);
const rawList = Array.isArray(res?.data)
? res.data
: res?.data?.records || res?.data?.rows || [];
@@ -1229,7 +1257,7 @@
rawList.forEach(item => {
total += Number(getDeliveryBatchQuantity(item) || 0);
});
- currentStock.value = total;
+ currentStock.value = roundAmount(total);
} catch (e) {
console.error(e);
currentStock.value = 0;
@@ -1238,35 +1266,45 @@
const getCurrentDeliveryRowQuantity = () => {
return Number(currentDeliveryRow.value?.noQuantity || 0);
};
+ const selectedDeliveryQuantity = computed(() => {
+ return roundAmount(
+ (deliveryForm.value.batchNoList || []).reduce(
+ (sum, item) => sum + Number(item?.deliveryQuantity || 0),
+ 0
+ )
+ );
+ });
const getDeliveryBatchDeliveryMax = row => {
const productQuantity = getCurrentDeliveryRowQuantity();
const batchQuantity = Number(getDeliveryBatchQuantity(row) || 0);
- const otherBatchTotal = (deliveryForm.value.batchNoList || []).reduce(
- (sum, item) => {
- if (item?.id === row?.id) return sum;
- return sum + Number(item?.deliveryQuantity || 0);
- },
- 0
+ const otherBatchTotal = roundAmount(
+ (deliveryForm.value.batchNoList || []).reduce(
+ (sum, item) => {
+ if (item?.id === row?.id) return sum;
+ return sum + Number(item?.deliveryQuantity || 0);
+ },
+ 0
+ )
);
- const remainingProductQuantity = Math.max(
- 0,
- productQuantity - otherBatchTotal
+ const remainingProductQuantity = roundAmount(
+ Math.max(0, productQuantity - otherBatchTotal)
);
return Math.max(0, Math.min(batchQuantity, remainingProductQuantity));
};
const handleDeliveryBatchQuantityChange = row => {
const productQuantity = getCurrentDeliveryRowQuantity();
const batchQuantity = Number(getDeliveryBatchQuantity(row) || 0);
- const otherBatchTotal = (deliveryForm.value.batchNoList || []).reduce(
- (sum, item) => {
- if (item?.id === row?.id) return sum;
- return sum + Number(item?.deliveryQuantity || 0);
- },
- 0
+ const otherBatchTotal = roundAmount(
+ (deliveryForm.value.batchNoList || []).reduce(
+ (sum, item) => {
+ if (item?.id === row?.id) return sum;
+ return sum + Number(item?.deliveryQuantity || 0);
+ },
+ 0
+ )
);
- const remainingProductQuantity = Math.max(
- 0,
- productQuantity - otherBatchTotal
+ const remainingProductQuantity = roundAmount(
+ Math.max(0, productQuantity - otherBatchTotal)
);
const currentValue = Number(row?.deliveryQuantity || 0);
@@ -1283,9 +1321,9 @@
item => Number(item?.deliveryQuantity || 0) > 0
);
};
- const getDeliveryBatchNoList = async productModelId => {
+ const getDeliveryBatchNoList = async (productModelId, salesLedgerId) => {
if (!productModelId) return [];
- const res = await getStockInventoryByModelId(productModelId);
+ const res = await getStockInventoryByModelId(productModelId, salesLedgerId);
const rawList = Array.isArray(res?.data)
? res.data
: res?.data?.records || res?.data?.rows || [];
@@ -2476,7 +2514,8 @@
currentDeliveryRow.value = row;
const batchNoList = await getDeliveryBatchNoList(
- row.productModelId || row.modelId
+ row.productModelId || row.modelId,
+ row.salesLedgerId
);
deliveryForm.value = {
shippingCarNumber: "",
@@ -2499,9 +2538,11 @@
proxy.$modal.msgWarning("璇疯嚦灏戝~鍐欎竴涓壒鍙风殑鍙戣揣鏁伴噺");
return;
}
- const totalDeliveryQuantity = selectedBatchRows.reduce(
- (sum, item) => sum + Number(item.deliveryQuantity || 0),
- 0
+ const totalDeliveryQuantity = roundAmount(
+ selectedBatchRows.reduce(
+ (sum, item) => sum + Number(item.deliveryQuantity || 0),
+ 0
+ )
);
const currentRowNoQuantity = Number(
currentDeliveryRow.value?.noQuantity || 0
--
Gitblit v1.9.3