From bfa9cabc4fe74da05c3d1eb5148929551c4d87c2 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 28 五月 2026 09:13:01 +0800
Subject: [PATCH] 中兴实强new 1.生产订单领料时如果库存不足可以提交采购申请单 2.采购申请单通知点击跳转是根据合同号直接进行查询
---
src/views/procurementManagement/procurementLedger/index.vue | 26 ++-
src/layout/components/NotificationCenter/index.vue | 7
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue | 87 ++++++++--
src/api/procurementManagement/procurementLedger.js | 9 +
src/views/productionManagement/productionOrder/components/PurchaseRequestDialog.vue | 308 ++++++++++++++++++++++++++++++++++++++
5 files changed, 410 insertions(+), 27 deletions(-)
diff --git a/src/api/procurementManagement/procurementLedger.js b/src/api/procurementManagement/procurementLedger.js
index 5f9df05..18c58ca 100644
--- a/src/api/procurementManagement/procurementLedger.js
+++ b/src/api/procurementManagement/procurementLedger.js
@@ -80,6 +80,15 @@
});
}
+// 淇濆瓨閲囪喘鑽夌锛堝簱瀛樹笉瓒冲満鏅級
+export function saveShortagePurchaseDraft(data) {
+ return request({
+ url: "/purchase/ledger/saveShortagePurchaseDraft",
+ method: "post",
+ data: data,
+ });
+}
+
// 淇濆瓨閲囪喘妯℃澘
export function addPurchaseTemplate(data) {
return request({
diff --git a/src/layout/components/NotificationCenter/index.vue b/src/layout/components/NotificationCenter/index.vue
index 66f489a..2d3df05 100644
--- a/src/layout/components/NotificationCenter/index.vue
+++ b/src/layout/components/NotificationCenter/index.vue
@@ -190,6 +190,13 @@
});
}
+ // 濡傛灉鏄噰璐敵璇锋彁閱掞紝鏍规嵁purchaseContractNumber鏌ヨ
+ if (item.noticeTitle === "閲囪喘鐢宠鎻愰啋" && query.purchaseContractNumber) {
+ query = {
+ purchaseContractNumber: query.purchaseContractNumber,
+ };
+ }
+
// 璺宠浆鍒版寚瀹氶〉闈�
router.push({
path: path,
diff --git a/src/views/procurementManagement/procurementLedger/index.vue b/src/views/procurementManagement/procurementLedger/index.vue
index dad6a26..6a50276 100644
--- a/src/views/procurementManagement/procurementLedger/index.vue
+++ b/src/views/procurementManagement/procurementLedger/index.vue
@@ -699,6 +699,7 @@
getCurrentInstance,
nextTick,
} from "vue";
+ import { useRoute } from "vue-router";
import { Search, Delete } from "@element-plus/icons-vue";
import { ElMessageBox, ElMessage } from "element-plus";
import { userListNoPage } from "@/api/system/user.js";
@@ -728,6 +729,7 @@
);
const { proxy } = getCurrentInstance();
+ const route = useRoute();
const { tax_rate } = proxy.useDict("tax_rate");
const tableData = ref([]);
const productData = ref([]);
@@ -756,6 +758,7 @@
// 璁㈠崟瀹℃壒鐘舵�佹樉绀烘枃鏈�
const approvalStatusText = {
+ 0: "鑽夌",
1: "寰呭鏍�",
2: "瀹℃壒涓�",
3: "瀹℃壒閫氳繃",
@@ -765,6 +768,7 @@
// 鑾峰彇瀹℃壒鐘舵�佹爣绛剧被鍨�
const getApprovalStatusType = status => {
const typeMap = {
+ 0: "info", // 寰呭鏍� - 鐏拌壊
1: "info", // 寰呭鏍� - 鐏拌壊
2: "warning", // 瀹℃壒涓� - 姗欒壊
3: "success", // 瀹℃壒閫氳繃 - 缁胯壊
@@ -936,13 +940,10 @@
const { form, rules } = toRefs(data);
const { form: searchForm } = useFormData({
...data.searchForm,
- // 璁剧疆褰曞叆鏃ユ湡鑼冨洿涓哄綋澶�
- entryDate: [
- dayjs().startOf("day").format("YYYY-MM-DD"),
- dayjs().endOf("day").format("YYYY-MM-DD"),
- ],
- entryDateStart: dayjs().startOf("day").format("YYYY-MM-DD"),
- entryDateEnd: dayjs().endOf("day").format("YYYY-MM-DD"),
+ // 褰曞叆鏃ユ湡涓嶈缃粯璁ゅ��
+ entryDate: null,
+ entryDateStart: undefined,
+ entryDateEnd: undefined,
});
// 浜у搧琛ㄥ崟寮规鏁版嵁
@@ -1919,7 +1920,16 @@
};
onMounted(() => {
- getList();
+ // 妫�鏌RL鍙傛暟涓槸鍚︽湁purchaseContractNumber锛屾湁鍒欒缃埌鎼滅储鏉′欢
+ if (route.query.purchaseContractNumber) {
+ // 浣跨敤setTimeout纭繚searchForm宸茬粡鍒濆鍖�
+ setTimeout(() => {
+ searchForm.purchaseContractNumber = route.query.purchaseContractNumber;
+ getList();
+ }, 0);
+ } else {
+ getList();
+ }
getTemplateList();
});
</script>
diff --git a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
index 09e7421..85fbe35 100644
--- a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
+++ b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -2,7 +2,7 @@
<div>
<el-dialog v-model="dialogVisible"
title="棰嗘枡鍙拌处"
- width="1200px"
+ width="1400px"
@close="handleClose">
<div class="material-toolbar">
<el-button type="primary"
@@ -65,10 +65,18 @@
</el-select>
</template>
</el-table-column>
+ <el-table-column label="搴撳瓨鏁伴噺"
+ min-width="120">
+ <template #default="{ row }">
+ <span :class="{ 'text-danger': isStockInsufficient(row) }">
+ {{ row.stockQuantity ?? '-' }}
+ </span>
+ </template>
+ </el-table-column>
<el-table-column label="闇�姹傛暟閲�"
min-width="120">
<template #default="{ row }">
- <span v-if="row.bom === true">{{ row.demandedQuantity ?? "-" }}</span>
+ <span v-if="row.bom === true" :class="{ 'text-danger': isStockInsufficient(row) }">{{ row.demandedQuantity ?? "-" }}</span>
<el-input-number v-else
v-model="row.demandedQuantity"
:min="0"
@@ -76,6 +84,7 @@
:step="1"
controls-position="right"
style="width: 100%;"
+ :class="{ 'is-stock-insufficient': isStockInsufficient(row) }"
@change="val => handleRequiredQtyChange(row, val)" />
</template>
</el-table-column>
@@ -109,6 +118,9 @@
</el-table>
<template #footer>
<span class="dialog-footer">
+ <el-button v-if="hasInsufficientStock"
+ type="warning"
+ @click="openPurchaseRequestDialog">閲囪喘鐢宠</el-button>
<el-button type="primary"
:loading="materialSaving"
:disabled="isSaveDisabled"
@@ -120,6 +132,10 @@
<ProductSelectDialog v-model="materialProductDialogVisible"
@confirm="handleMaterialProductConfirm"
single />
+ <PurchaseRequestDialog v-model="purchaseRequestDialogVisible"
+ :insufficient-items="insufficientStockItems"
+ :order-row="props.orderRow"
+ @saved="handlePurchaseRequestSaved" />
<!-- request-url="/stockInventory/rawMaterials" -->
</div>
</template>
@@ -128,18 +144,18 @@
import { computed, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+ import PurchaseRequestDialog from "./PurchaseRequestDialog.vue";
import {
findProductProcessRouteItemList,
listMain,
} from "@/api/productionManagement/productProcessRoute.js";
import {
- listMaterialPickingDetail,
listMaterialPickingBom,
- listMaterialPickingLedger,
saveMaterialPickingLedger,
updateMaterialPickingLedger,
} from "@/api/productionManagement/productionOrder.js";
import { queryList2 } from "@/api/productionManagement/productStructure.js";
+
const props = defineProps({
modelValue: { type: Boolean, default: false },
@@ -156,6 +172,7 @@
const materialTableLoading = ref(false);
const materialSaving = ref(false);
const materialTableData = ref([]);
+ const purchaseRequestDialogVisible = ref(false);
const isSaveDisabled = computed(() => {
if (materialTableData.value.length === 0) return true;
@@ -183,6 +200,23 @@
});
});
+ // 鍒ゆ柇搴撳瓨鏄惁涓嶈冻
+ const isStockInsufficient = (row) => {
+ const stockQuantity = Number(row.stockQuantity ?? 0);
+ const demandedQty = Number(row.demandedQuantity ?? 0);
+ return demandedQty > 0 && stockQuantity > 0 && demandedQty > stockQuantity;
+ };
+
+ // 搴撳瓨涓嶈冻鐨勮
+ const insufficientStockItems = computed(() => {
+ return materialTableData.value.filter(row => isStockInsufficient(row));
+ });
+
+ // 鏄惁鏈夊簱瀛樹笉瓒�
+ const hasInsufficientStock = computed(() => {
+ return insufficientStockItems.value.length > 0;
+ });
+
const processOptions = ref([]);
const currentMaterialSelectRowIndex = ref(-1);
let materialTempId = 0;
@@ -206,6 +240,7 @@
: row.batchNo
: [],
batchNoList: row.batchNoList || [],
+ stockQuantity: row.stockQuantity ?? row.stockQty ?? null,
});
const getProcessOptions = async () => {
@@ -239,22 +274,14 @@
materialTableData.value = [];
await getProcessOptions();
try {
- const detailRes = await listMaterialPickingDetail(props.orderRow.id);
- const detailList = Array.isArray(detailRes?.data)
- ? detailRes.data
- : detailRes?.data?.records || [];
- if (detailList.length > 0) {
- isDetail.value = true;
- materialTableData.value = detailList.map(item => createMaterialRow(item));
- return;
- } else {
+ // 鐩存帴璋冪敤listMaterialPickingBom鎺ュ彛鑾峰彇搴撳瓨鏁伴噺
+ const bomRes = await listMaterialPickingBom(props.orderRow.id);
+ const bomList = Array.isArray(bomRes?.data)
+ ? bomRes.data
+ : bomRes?.data?.records || [];
+ if (bomList.length > 0) {
isDetail.value = false;
- const bomRes = await listMaterialPickingBom(props.orderRow.id);
- const bomList = Array.isArray(bomRes?.data)
- ? bomRes.data
- : bomRes?.data?.records || [];
materialTableData.value = bomList.map(item => createMaterialRow(item));
- return;
}
} finally {
materialTableLoading.value = false;
@@ -305,7 +332,7 @@
materialProductDialogVisible.value = true;
};
- const handleMaterialProductConfirm = products => {
+ const handleMaterialProductConfirm = async (products) => {
console.log(products, "products");
if (!products || products.length === 0) return;
@@ -417,6 +444,17 @@
materialSaving.value = false;
}
};
+
+ // 鎵撳紑閲囪喘鐢宠瀵硅瘽妗�
+ const openPurchaseRequestDialog = () => {
+ purchaseRequestDialogVisible.value = true;
+ };
+
+ // 閲囪喘鐢宠淇濆瓨鍥炶皟
+ const handlePurchaseRequestSaved = () => {
+ // 閲囪喘鐢宠淇濆瓨鎴愬姛鍚庡埛鏂版暟鎹�
+ loadMaterialData();
+ };
</script>
<style scoped lang="scss">
@@ -424,4 +462,15 @@
margin-bottom: 12px;
text-align: right;
}
+
+ .text-danger {
+ color: #f56c6c;
+ font-weight: bold;
+ }
+
+ :deep(.is-stock-insufficient) {
+ .el-input__wrapper {
+ box-shadow: 0 0 0 1px #f56c6c inset;
+ }
+ }
</style>
diff --git a/src/views/productionManagement/productionOrder/components/PurchaseRequestDialog.vue b/src/views/productionManagement/productionOrder/components/PurchaseRequestDialog.vue
new file mode 100644
index 0000000..0000c26
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/components/PurchaseRequestDialog.vue
@@ -0,0 +1,308 @@
+<template>
+ <div>
+ <el-dialog v-model="dialogVisible"
+ title="閲囪喘鐢宠锛堝簱瀛樹笉瓒筹級"
+ width="900px"
+ @close="handleClose">
+ <!-- 绠�鏄撻噰璐敵璇疯〃鍗� -->
+ <el-form :model="form" label-width="100px">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="閿�鍞鍗曞彿" required>
+ <el-input v-model="form.salesContractNo" placeholder="璇疯緭鍏ラ攢鍞鍗曞彿" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鎶勯�佷汉" required>
+ <el-select v-model="form.ccUserId" placeholder="璇烽�夋嫨鎶勯�佷汉" style="width: 100%" filterable>
+ <el-option v-for="user in userOptions" :key="user.userId" :label="user.nickName" :value="user.userId" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+
+ <!-- 浜у搧琛ㄦ牸 -->
+ <div class="purchase-request-table">
+ <div class="table-toolbar">
+ <span class="table-title">閲囪喘浜у搧鏄庣粏</span>
+ <el-button type="primary" @click="handleAddRow">鏂板浜у搧</el-button>
+ </div>
+ <el-table :data="tableData" border style="width: 100%;" max-height="400">
+ <el-table-column type="index" label="搴忓彿" width="60" align="center" />
+ <el-table-column label="浜у搧鍚嶇О" min-width="150">
+ <template #default="{ row }">
+ <el-button v-if="!row.productName" type="primary" link @click="openProductSelect(row)">
+ 閫夋嫨浜у搧
+ </el-button>
+ <span v-else>{{ row.productName }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍨嬪彿/瑙勬牸" min-width="150">
+ <template #default="{ row }">
+ {{ row.model || '-' }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鍗曚綅" width="80" align="center">
+ <template #default="{ row }">
+ {{ row.unit || '-' }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鏁伴噺" width="120">
+ <template #default="{ row }">
+ <el-input-number v-model="row.quantity" :min="1" :precision="0" :step="1" controls-position="right" style="width: 100%;" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="80" align="center" fixed="right">
+ <template #default="{ $index }">
+ <el-button type="danger" link @click="handleDeleteRow($index)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <template #footer>
+ <span class="dialog-footer">
+ <el-button type="primary" :loading="saving" @click="handleSave">淇濆瓨鑽夌</el-button>
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ </span>
+ </template>
+ </el-dialog>
+
+ <ProductSelectDialog v-model="productSelectVisible" @confirm="handleProductConfirm" single />
+ </div>
+</template>
+
+<script setup>
+import { computed, ref, watch, onMounted } from "vue";
+import { ElMessage } from "element-plus";
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+import { saveShortagePurchaseDraft } from "@/api/procurementManagement/procurementLedger.js";
+import { listUser } from "@/api/system/user.js";
+import useUserStore from "@/store/modules/user.js";
+
+const userStore = useUserStore();
+
+const props = defineProps({
+ modelValue: { type: Boolean, default: false },
+ insufficientItems: { type: Array, default: () => [] },
+ orderRow: { type: Object, default: null },
+});
+const emit = defineEmits(["update:modelValue", "saved"]);
+
+const dialogVisible = computed({
+ get: () => props.modelValue,
+ set: (val) => emit("update:modelValue", val),
+});
+
+const productSelectVisible = ref(false);
+const saving = ref(false);
+const currentSelectRowIndex = ref(-1);
+const userOptions = ref([]);
+
+// 琛ㄥ崟鏁版嵁
+const form = ref({
+ salesContractNo: "",
+ ccUserId: null,
+ ccUserName: "",
+});
+
+// 琛ㄦ牸鏁版嵁
+const tableData = ref([]);
+
+// 鑾峰彇鐢ㄦ埛鍒楄〃锛堟妱閫佷汉閫夋嫨锛�
+const getUserList = async () => {
+ try {
+ const res = await listUser({ pageSize: 1000 });
+ userOptions.value = res.rows || [];
+ } catch (error) {
+ console.error("鑾峰彇鐢ㄦ埛鍒楄〃澶辫触:", error);
+ }
+};
+
+onMounted(() => {
+ getUserList();
+});
+
+// 鐩戝惉瀵硅瘽妗嗘墦寮�锛屽垵濮嬪寲鏁版嵁
+watch(
+ () => dialogVisible.value,
+ (visible) => {
+ if (visible) {
+ initData();
+ }
+ }
+);
+
+// 鍒濆鍖栨暟鎹�
+const initData = () => {
+ // 浠庣敓浜ц鍗曚腑鑾峰彇閿�鍞鍗曞彿
+ form.value.salesContractNo = props.orderRow?.salesContractNo || "";
+ form.value.applicantId = userStore.id || "";
+ form.value.applicantName = userStore.name || "";
+ form.value.ccUserId = null;
+ form.value.ccUserName = "";
+
+ // 灏嗗簱瀛樹笉瓒崇殑浜у搧濉厖鍒拌〃鏍�
+ tableData.value = props.insufficientItems.map((item) => ({
+ tempId: generateTempId(),
+ productModelId: item.materialModelId,
+ productName: item.materialName,
+ model: item.materialModel,
+ unit: item.unit,
+ quantity: Math.max(1, Math.ceil((item.demandedQuantity || 0) - (item.stockQuantity || 0))),
+ }));
+};
+
+// 鐢熸垚涓存椂ID
+let tempIdCounter = 0;
+const generateTempId = () => {
+ return `temp_${++tempIdCounter}_${Date.now()}`;
+};
+
+// 鍏抽棴瀵硅瘽妗�
+const handleClose = () => {
+ form.value = {
+ salesContractNo: "",
+ applicantId: "",
+ applicantName: "",
+ ccUserId: null,
+ ccUserName: "",
+ };
+ tableData.value = [];
+ currentSelectRowIndex.value = -1;
+};
+
+// 鏂板琛�
+const handleAddRow = () => {
+ tableData.value.push({
+ tempId: generateTempId(),
+ productModelId: null,
+ productName: "",
+ model: "",
+ unit: "",
+ quantity: 1,
+ });
+};
+
+// 鍒犻櫎琛�
+const handleDeleteRow = (index) => {
+ tableData.value.splice(index, 1);
+};
+
+// 鎵撳紑浜у搧閫夋嫨
+const openProductSelect = (row) => {
+ currentSelectRowIndex.value = tableData.value.findIndex(
+ (item) => item.tempId === row.tempId
+ );
+ productSelectVisible.value = true;
+};
+
+// 浜у搧閫夋嫨纭
+const handleProductConfirm = (products) => {
+ if (!products || products.length === 0) return;
+ const index = currentSelectRowIndex.value;
+ if (index < 0 || !tableData.value[index]) return;
+ const product = products[0];
+ const row = tableData.value[index];
+ row.productModelId = product.materialModelId || product.modelId || product.id;
+ row.productName = product.materialName || product.productName || product.name || "";
+ row.model = product.materialModel || product.model || "";
+ row.unit = product.unit || product.measureUnit || "";
+ currentSelectRowIndex.value = -1;
+ productSelectVisible.value = false;
+};
+
+// 楠岃瘉琛ㄥ崟
+const validateForm = () => {
+ if (!form.value.salesContractNo) {
+ ElMessage.warning("璇疯緭鍏ラ攢鍞鍗曞彿");
+ return false;
+ }
+ if (!form.value.ccUserId) {
+ ElMessage.warning("璇烽�夋嫨鎶勯�佷汉");
+ return false;
+ }
+ if (tableData.value.length === 0) {
+ ElMessage.warning("璇疯嚦灏戞坊鍔犱竴涓骇鍝�");
+ return false;
+ }
+ for (let i = 0; i < tableData.value.length; i++) {
+ const row = tableData.value[i];
+ if (!row.productName) {
+ ElMessage.warning(`绗�${i + 1}琛岃閫夋嫨浜у搧`);
+ return false;
+ }
+ if (!row.quantity || row.quantity <= 0) {
+ ElMessage.warning(`绗�${i + 1}琛屾暟閲忓繀椤诲ぇ浜�0`);
+ return false;
+ }
+ }
+ return true;
+};
+
+// 淇濆瓨鑽夌
+const handleSave = async () => {
+ if (!validateForm()) return;
+
+ // 鑾峰彇鎶勯�佷汉濮撳悕
+ const selectedUser = userOptions.value.find(u => u.userId === form.value.ccUserId);
+ form.value.ccUserName = selectedUser?.userName || "";
+
+ saving.value = true;
+ try {
+ // 鏋勫缓閲囪喘鑽夌鏁版嵁锛堟牴鎹仈璋冩枃妗� PurchaseLedgerDto 鏍煎紡锛�
+ const draftData = {
+ salesContractNo: form.value.salesContractNo,
+ ccUserId: form.value.ccUserId,
+ ccUserName: form.value.ccUserName,
+ // 浜у搧鏄庣粏鏁版嵁
+ productData: tableData.value.map((item) => ({
+ productModelId: item.productModelId,
+ productName: item.productName,
+ model: item.model,
+ unit: item.unit,
+ quantity: item.quantity,
+ })),
+ };
+
+ const res = await saveShortagePurchaseDraft(draftData);
+ if (res.code === 200) {
+ ElMessage.success("閲囪喘鑽夌淇濆瓨鎴愬姛锛屽凡閫氱煡鎶勯�佷汉琛ュ叏淇℃伅");
+ emit("saved", res.data); // 杩斿洖鑽夌ID
+ dialogVisible.value = false;
+ } else {
+ ElMessage.error(res.msg || "淇濆瓨澶辫触");
+ }
+ } catch (error) {
+ console.error("淇濆瓨閲囪喘鑽夌澶辫触:", error);
+ ElMessage.error("淇濆瓨澶辫触锛岃閲嶈瘯");
+ } finally {
+ saving.value = false;
+ }
+};
+</script>
+
+<style scoped lang="scss">
+.purchase-request-table {
+ margin-top: 20px;
+
+ .table-toolbar {
+ margin-bottom: 10px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .table-title {
+ font-weight: bold;
+ font-size: 14px;
+ }
+ }
+}
+
+.dialog-footer {
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+}
+</style>
--
Gitblit v1.9.3