From 858df78951d5c29e8d4217cffcf81d2cc4fabcd4 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 03 八月 2026 14:58:59 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_pro2.0' into dev_pro2.0

---
 src/views/srm/tender/detail.vue |  133 ++++++++++++++++++++++++++++++++------------
 1 files changed, 96 insertions(+), 37 deletions(-)

diff --git a/src/views/srm/tender/detail.vue b/src/views/srm/tender/detail.vue
index 4b34190..7de94be 100644
--- a/src/views/srm/tender/detail.vue
+++ b/src/views/srm/tender/detail.vue
@@ -6,7 +6,7 @@
 
 import { Page } from '@vben/common-ui';
 
-import { message } from 'ant-design-vue';
+import { message, Modal } from 'ant-design-vue';
 import { IconifyIcon } from '@vben/icons';
 
 import {
@@ -15,8 +15,8 @@
   getBidListByProject, createBid, withdrawBid,
   getQuoteList, createQuote, updateQuote,
   createBidOpen, getBidOpen,
-  getEvaluationList, createBidEvaluation, calculateRanking,
-  createAward, getAward, approveAward, generatePurchaseOrder,
+  getEvaluationList, createBidEvaluation, updateBidEvaluation, calculateRanking,
+  createAward, deleteAward, getAward, getAwardByProject, approveAward, generatePurchaseOrder,
 } from '#/api/srm/tender';
 import { getSupplierSimpleList } from '#/api/srm/supplier';
 import { getItemSimpleList } from '#/api/mdm/item';
@@ -63,6 +63,25 @@
 const materialLoading = ref(false);
 const materialFormVisible = ref(false);
 const materialForm = ref<SrmTenderApi.TenderMaterialVO>({ tenderProjectId: projectId });
+
+// 宸叉坊鍔犵殑鐗╂枡浜у搧ID闆嗗悎锛岄伩鍏嶉噸澶嶆坊鍔�
+const addedProductIds = computed(() => {
+  const ids = new Set(
+    materialList.value
+      .filter((m) => m.productId !== undefined)
+      .map((m) => m.productId!),
+  );
+  // 缂栬緫鏃跺厑璁镐繚鐣欏綋鍓嶇墿鏂欑殑 productId
+  if (materialForm.value.id && materialForm.value.productId) {
+    ids.delete(materialForm.value.productId);
+  }
+  return ids;
+});
+
+// 鐗╂枡涓嬫媺鍙�夊垪琛紙鎺掗櫎宸叉坊鍔犵殑锛�
+const availableMdmItems = computed(() =>
+  mdmItemList.value.filter((item) => !addedProductIds.value.has(item.id!)),
+);
 
 async function loadProject() {
   project.value = await getTenderProject(projectId);
@@ -150,6 +169,27 @@
   try { quoteList.value = await getQuoteList(bidId); } finally { quoteLoading.value = false; }
 }
 async function handleSaveQuote() {
+  // 鏍¢獙锛氭姤浠烽噾棰濅笌鎶曟爣閲戦涓�鑷存��
+  const bid = bidList.value.find((b) => b.id === quoteForm.value.bidId);
+  if (bid?.bidTotalAmount != null) {
+    const otherQuotesTotal = quoteList.value
+      .filter((q) => q.id !== quoteForm.value.id)
+      .reduce((sum, q) => sum + (q.quotePrice ?? 0), 0);
+    const newTotal = otherQuotesTotal + (quoteForm.value.quotePrice ?? 0);
+    if (newTotal !== bid.bidTotalAmount) {
+      await new Promise<void>((resolve, reject) => {
+        Modal.confirm({
+          title: '閲戦涓嶄竴鑷存彁绀�',
+          content: `鎶ヤ环鎬婚噾棰濓紙${newTotal.toLocaleString()}锛変笌鎶曟爣閲戦锛�${bid.bidTotalAmount!.toLocaleString()}锛変笉涓�鑷达紝鏄惁缁х画鎻愪氦锛焋,
+          okText: '缁х画鎻愪氦',
+          cancelText: '鍙栨秷',
+          onOk: () => { resolve(); },
+          onCancel: () => { reject(new Error('cancel')); },
+        });
+      });
+    }
+  }
+
   if (quoteForm.value.id) {
     await updateQuote(quoteForm.value);
   } else {
@@ -193,7 +233,11 @@
   try { evaluationList.value = await getEvaluationList(projectId); } finally { evalLoading.value = false; }
 }
 async function handleCreateEvaluation() {
-  await createBidEvaluation(evalForm.value);
+  if (evalForm.value.id) {
+    await updateBidEvaluation(evalForm.value);
+  } else {
+    await createBidEvaluation(evalForm.value);
+  }
   evalFormVisible.value = false;
   loadEvaluations();
   loadProject();
@@ -210,7 +254,7 @@
 const awardForm = ref<SrmTenderApi.BidAwardVO>({ tenderProjectId: projectId, supplierId: 0, bidId: 0, awardNo: '' });
 
 async function loadAward() {
-  try { award.value = await getAward(projectId); } catch { award.value = undefined; }
+  try { award.value = await getAwardByProject(projectId); } catch { award.value = undefined; }
 }
 async function handleCreateAward() {
   await createAward(awardForm.value);
@@ -225,6 +269,13 @@
   loadAward();
   loadProject();
 }
+async function handleDeleteAward() {
+  if (!award.value?.id) return;
+  await deleteAward(award.value.id);
+  message.success('瀹氭爣宸插垹闄�');
+  award.value = undefined;
+}
+
 async function handleGeneratePurchaseOrder() {
   if (!award.value?.id) return;
   await generatePurchaseOrder(award.value.id);
@@ -244,6 +295,7 @@
 const statusLabel = computed(() => getLabel(TENDER_STATUS_CELLTAG.labels, project.value?.tenderStatus));
 const statusColor = computed(() => TENDER_STATUS_COLORS[project.value?.tenderStatus!] || 'default');
 const typeLabel = computed(() => getLabel(TENDER_TYPE_CELLTAG.labels, project.value?.tenderType));
+const isAwarded = computed(() => project.value?.tenderStatus === TENDER_STATUS.AWARDED);
 
 const stepItems = [
   { title: '鑽夌', status: TENDER_STATUS.DRAFT },
@@ -410,7 +462,7 @@
                 <a-space>
                   <span class="text-xs text-gray-400">鍏� <b class="text-gray-600">{{ materialList.length }}</b> 椤癸紝棰勪及鎬婚噾棰� <b class="text-blue-500">{{ materialTotalAmount.toLocaleString() }}</b> 鍏�</span>
                 </a-space>
-                <a-button type="primary" size="small" @click="handleAddMaterial">
+                <a-button v-if="!isAwarded" type="primary" size="small" @click="handleAddMaterial">
                   <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
                   娣诲姞鐗╂枡
                 </a-button>
@@ -421,7 +473,7 @@
                     {{ record.estimatedPrice ? record.estimatedPrice.toLocaleString() : '-' }}
                   </template>
                   <template v-if="column.key === 'actions'">
-                    <a-space>
+                    <a-space v-if="!isAwarded">
                       <a-button size="small" type="link" @click="handleEditMaterial(record)"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
                       <a-popconfirm title="纭鍒犻櫎璇ョ墿鏂欙紵" @confirm="handleDeleteMaterial(record.id)">
                         <a-button size="small" type="link" danger><IconifyIcon icon="ant-design:delete-outlined" />鍒犻櫎</a-button>
@@ -440,7 +492,7 @@
               </template>
               <div class="flex items-center justify-between mb-3">
                 <span class="text-xs text-gray-400">鍏� <b class="text-gray-600">{{ bidCount }}</b> 涓姇鏍�</span>
-                <a-button type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: 0, bidNo: '' }; bidFormVisible = true">
+                <a-button v-if="!isAwarded" type="primary" size="small" @click="bidForm = { tenderProjectId: projectId, supplierId: 0, bidNo: '' }; bidFormVisible = true">
                   <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
                   鏂板鎶曟爣
                 </a-button>
@@ -458,7 +510,7 @@
                   <template v-if="column.key === 'actions'">
                     <a-space>
                       <a-button size="small" type="link" @click="loadQuotes(record.id); activeTab = 'quote'"><IconifyIcon icon="ant-design:dollar-outlined" />鎶ヤ环</a-button>
-                      <a-popconfirm v-if="record.bidStatus === BID_STATUS.REGISTERED" title="纭鎾ゆ爣锛�" @confirm="handleWithdrawBid(record.id)">
+                      <a-popconfirm v-if="!isAwarded && record.bidStatus === BID_STATUS.REGISTERED" title="纭鎾ゆ爣锛�" @confirm="handleWithdrawBid(record.id)">
                         <a-button size="small" type="link" danger><IconifyIcon icon="ant-design:close-circle-outlined" />鎾ゆ爣</a-button>
                       </a-popconfirm>
                     </a-space>
@@ -481,7 +533,7 @@
                   <a-button size="small" type="link" class="ml-auto" @click="activeTab = 'bid'">鍒囨崲鎶曟爣</a-button>
                 </div>
                 <div class="text-right mb-3">
-                  <a-button v-if="selectedBid.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote">
+                  <a-button v-if="!isAwarded && selectedBid.bidStatus !== BID_STATUS.WITHDRAWN" type="primary" size="small" @click="handleAddQuote">
                     <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
                     鏂板鎶ヤ环
                   </a-button>
@@ -501,7 +553,7 @@
                       {{ record.warrantyPeriod != null ? `${record.warrantyPeriod} 涓湀` : '-' }}
                     </template>
                     <template v-if="column.key === 'actions'">
-                      <a-button v-if="selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" size="small" type="link" @click="handleEditQuote(record)"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
+                      <a-button v-if="!isAwarded && selectedBid?.bidStatus !== BID_STATUS.WITHDRAWN" size="small" type="link" @click="handleEditQuote(record)"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
                     </template>
                   </template>
                 </a-table>
@@ -529,14 +581,12 @@
               </template>
               <template v-else>
                 <div class="text-center py-8">
-                  <a-result status="info" title="灏氭湭寮�鏍�" sub-title="鎵ц寮�鏍囧悗椤圭洰杩涘叆鎶曟爣闃舵锛屼緵搴斿晢鍙寮忔姇鏍�">
-                    <template #extra>
-                      <a-button type="primary" @click="handleCreateBidOpen">
-                        <template #icon><IconifyIcon icon="ant-design:thunderbolt-outlined" /></template>
-                        鎵ц寮�鏍�
-                      </a-button>
-                    </template>
-                  </a-result>
+                  <a-result status="info" title="灏氭湭寮�鏍�" sub-title="鎵ц寮�鏍囧悗椤圭洰杩涘叆鎶曟爣闃舵锛屼緵搴斿晢鍙寮忔姇鏍�" />
+                  <br />
+                  <a-button v-if="!isAwarded" type="primary" @click="handleCreateBidOpen">
+                    <template #icon><IconifyIcon icon="ant-design:thunderbolt-outlined" /></template>
+                    鎵ц寮�鏍�
+                  </a-button>
                 </div>
               </template>
             </a-tab-pane>
@@ -548,7 +598,7 @@
               </template>
               <div class="flex items-center justify-between mb-3">
                 <span class="text-xs text-gray-400">鍏� <b class="text-gray-600">{{ evalCount }}</b> 鏉★紝宸叉帓鍚� <b class="text-green-500">{{ rankedCount }}</b> 鏉�</span>
-                <a-space>
+                <a-space v-if="!isAwarded">
                   <a-button size="small" @click="handleCalculateRanking">
                     <template #icon><IconifyIcon icon="ant-design:bar-chart-outlined" /></template>
                     璁$畻鎺掑悕
@@ -580,7 +630,7 @@
                     <span v-else class="text-gray-300">-</span>
                   </template>
                   <template v-if="column.key === 'actions'">
-                    <a-button size="small" type="link" @click="evalForm = { ...record }; evalFormVisible = true"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
+                    <a-button v-if="!isAwarded" size="small" type="link" @click="evalForm = { ...record }; evalFormVisible = true"><IconifyIcon icon="ant-design:edit-outlined" />缂栬緫</a-button>
                   </template>
                 </template>
               </a-table>
@@ -615,28 +665,39 @@
                   </a-col>
                   <a-col :span="8" class="flex items-center justify-center">
                     <template v-if="award.awardStatus === AWARD_STATUS.ORDER_GENERATED">
-                      <a-result status="success" title="璁㈠崟宸茬敓鎴�" class="p-0">
-                        <template #sub-title>
-                          <span class="text-sm">閲囪喘璁㈠崟 ID: {{ award.purchaseOrderId }}</span>
-                        </template>
-                      </a-result>
+                      <div class="text-center">
+                        <a-result status="success" title="璁㈠崟宸茬敓鎴�" class="p-0">
+                          <template #sub-title>
+                            <span class="text-sm">閲囪喘璁㈠崟 ID: {{ award.purchaseOrderId }}</span>
+                          </template>
+                        </a-result>
+                        <a-popconfirm v-if="!isAwarded" title="纭畾鍒犻櫎璇ュ畾鏍囪褰曪紵" ok-text="纭畾" cancel-text="鍙栨秷" @confirm="handleDeleteAward">
+                          <a-button type="link" danger size="small" class="mt-2">鍒犻櫎瀹氭爣</a-button>
+                        </a-popconfirm>
+                      </div>
                     </template>
                     <template v-else-if="award.awardStatus === AWARD_STATUS.AWARDED">
                       <div class="text-center">
                         <a-result status="success" title="瀹氭爣瀹屾垚" class="p-0 mb-3" />
-                        <a-button type="primary" block @click="handleGeneratePurchaseOrder">
+                        <a-button v-if="!isAwarded" type="primary" block @click="handleGeneratePurchaseOrder">
                           <template #icon><IconifyIcon icon="ant-design:shopping-cart-outlined" /></template>
                           鐢熸垚閲囪喘璁㈠崟
                         </a-button>
+                        <a-popconfirm v-if="!isAwarded" title="纭畾鍒犻櫎璇ュ畾鏍囪褰曪紵" ok-text="纭畾" cancel-text="鍙栨秷" @confirm="handleDeleteAward">
+                          <a-button type="link" danger size="small" class="mt-2">鍒犻櫎瀹氭爣</a-button>
+                        </a-popconfirm>
                       </div>
                     </template>
                     <template v-else>
                       <div class="text-center">
                         <div class="text-gray-500 text-sm mb-3">瀹氭爣淇℃伅宸插垱寤猴紝璇峰鎵�</div>
-                        <a-button type="primary" block @click="handleApproveAward" style="background: #52c41a; border-color: #52c41a;">
+                        <a-button v-if="!isAwarded" type="primary" block @click="handleApproveAward" style="background: #52c41a; border-color: #52c41a;">
                           <template #icon><IconifyIcon icon="ant-design:check-outlined" /></template>
                           瀹℃壒閫氳繃
                         </a-button>
+                        <a-popconfirm v-if="!isAwarded" title="纭畾鍒犻櫎璇ュ畾鏍囪褰曪紵" ok-text="纭畾" cancel-text="鍙栨秷" @confirm="handleDeleteAward">
+                          <a-button type="link" danger size="small" class="mt-2">鍒犻櫎瀹氭爣</a-button>
+                        </a-popconfirm>
                       </div>
                     </template>
                   </a-col>
@@ -644,14 +705,12 @@
               </template>
               <template v-else>
                 <div class="text-center py-8">
-                  <a-result status="info" title="灏氭湭瀹氭爣" sub-title="鍩轰簬璇勬爣鎺掑悕閫夋嫨涓爣渚涘簲鍟嗗苟鍒涘缓瀹氭爣">
-                    <template #extra>
-                      <a-button type="primary" @click="awardForm = { tenderProjectId: projectId, supplierId: 0, bidId: 0, awardNo: '' }; awardFormVisible = true">
-                        <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
-                        鍒涘缓瀹氭爣
-                      </a-button>
-                    </template>
-                  </a-result>
+                  <a-result status="info" title="灏氭湭瀹氭爣" sub-title="鍩轰簬璇勬爣鎺掑悕閫夋嫨涓爣渚涘簲鍟嗗苟鍒涘缓瀹氭爣" />
+                  <br />
+                  <a-button v-if="!isAwarded" type="primary" @click="awardForm = { tenderProjectId: projectId, supplierId: 0, bidId: 0, awardNo: '' }; awardFormVisible = true">
+                    <template #icon><IconifyIcon icon="ant-design:plus-outlined" /></template>
+                    鍒涘缓瀹氭爣
+                  </a-button>
                 </div>
               </template>
             </a-tab-pane>
@@ -666,7 +725,7 @@
         <a-form layout="vertical" class="mt-4">
           <a-form-item label="MDM鐗╂枡" required>
             <a-select v-model:value="materialForm.productId" show-search placeholder="鎼滅储骞堕�夋嫨MDM鐗╂枡" :filter-option="filterOption" option-label-prop="label" @change="onMaterialSelect">
-              <a-select-option v-for="item in mdmItemList" :key="item.id" :value="item.id" :label="`${item.code} ${item.name}`">
+              <a-select-option v-for="item in availableMdmItems" :key="item.id" :value="item.id" :label="`${item.code} ${item.name}`">
                 <div class="flex flex-col">
                   <span class="font-medium">{{ item.code }}</span>
                   <span class="text-xs text-gray-400">{{ item.name }}{{ item.specification ? ` 路 ${item.specification}` : '' }}</span>

--
Gitblit v1.9.3