From 07aab8bb335132d5dc56f2b000301457079e8539 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 29 九月 2026 10:49:01 +0800
Subject: [PATCH] feat(research): 完善经费拨付流程支持财务报销审批

---
 src/views/research/application/data.ts                |   16 +++++++
 src/api/research/fund/index.ts                        |   10 +++-
 src/views/research/application/modules/fund-form.vue  |   49 ++++++++++++------------
 src/views/research/application/modules/fund-panel.vue |   39 ++++++++++++++++---
 4 files changed, 78 insertions(+), 36 deletions(-)

diff --git a/src/api/research/fund/index.ts b/src/api/research/fund/index.ts
index 3e7ceab..e0c949d 100644
--- a/src/api/research/fund/index.ts
+++ b/src/api/research/fund/index.ts
@@ -11,7 +11,11 @@
     amount: number; // 鎷ㄤ粯閲戦锛堝厓锛�
     payDate?: string; // 鎷ㄤ粯鏃ユ湡
     payee?: string; // 鏀舵鍗曚綅/璐︽埛鍚�
-    status?: number; // 鎷ㄤ粯鐘舵�侊紝瀛楀吀 research_fund_status
+    status?: number; // 鎷ㄤ粯鐘舵�侊紝瀛楀吀 research_fund_status锛�0-寰呮嫧浠� 15-鎶ラ攢瀹℃壒涓� 20-宸叉嫧浠� 30-宸查┏鍥烇級
+    reimbursementId?: number; // 鍏宠仈璐㈠姟鎶ラ攢鍗� ID
+    reimbursementNo?: string; // 鍏宠仈璐㈠姟鎶ラ攢鍗曞彿
+    rejectReason?: string; // 璐㈠姟鎶ラ攢椹冲洖鍘熷洜锛圔PM 鍥炲啓锛�
+    rejectTime?: string; // 璐㈠姟鎶ラ攢椹冲洖鏃堕棿
     remark?: string; // 澶囨敞
     creator?: string; // 鍒涘缓浜� ID
     creatorName?: string; // 鍒涘缓浜哄鍚�
@@ -30,12 +34,12 @@
   );
 }
 
-/** 鏂板缁忚垂鎷ㄤ粯璁板綍 */
+/** 鐧昏缁忚垂鎷ㄤ粯璁板綍锛堢櫥璁板悗鑷姩鐢熸垚骞舵彁浜よ储鍔℃姤閿�瀹℃壒锛� */
 export function createFundRecord(data: ResearchFundRecordApi.FundRecord) {
   return requestClient.post('/research/fund-record/create', data);
 }
 
-/** 淇敼缁忚垂鎷ㄤ粯璁板綍锛堜粎寰呮嫧浠樺彲鏀癸級 */
+/** 淇敼骞堕噸鏂板彂璧锋姤閿�锛堜粎銆屽緟鎷ㄤ粯銆嶃�屽凡椹冲洖銆嶅彲鏀癸級 */
 export function updateFundRecord(data: ResearchFundRecordApi.FundRecord) {
   return requestClient.put('/research/fund-record/update', data);
 }
diff --git a/src/views/research/application/data.ts b/src/views/research/application/data.ts
index b427dd3..258cb62 100644
--- a/src/views/research/application/data.ts
+++ b/src/views/research/application/data.ts
@@ -28,7 +28,9 @@
 /** 缁忚垂鎷ㄤ粯鐘舵�侊紙瀵瑰簲瀛楀吀 research_fund_status锛� */
 export const FundStatus = {
   PENDING: 0, // 寰呮嫧浠�
+  APPROVING: 15, // 鎶ラ攢瀹℃壒涓�
   PAID: 20, // 宸叉嫧浠�
+  REJECTED: 30, // 宸查┏鍥�
 } as const;
 
 /** 璇鹃鐢虫姤涔﹁〃鍗曢厤缃� */
@@ -516,13 +518,25 @@
     {
       field: 'status',
       title: '鎷ㄤ粯鐘舵��',
-      minWidth: 100,
+      minWidth: 110,
       cellRender: {
         name: 'CellDict',
         props: { type: DICT_TYPE.RESEARCH_FUND_STATUS },
       },
     },
     {
+      field: 'reimbursementNo',
+      title: '璐㈠姟鎶ラ攢鍗曞彿',
+      minWidth: 160,
+      formatter: dashIfEmpty,
+    },
+    {
+      field: 'rejectReason',
+      title: '椹冲洖鍘熷洜',
+      minWidth: 200,
+      formatter: dashIfEmpty,
+    },
+    {
       field: 'hasAttachment',
       title: '鎷ㄤ粯鍑瘉',
       minWidth: 100,
diff --git a/src/views/research/application/modules/fund-form.vue b/src/views/research/application/modules/fund-form.vue
index 33b2be5..107af50 100644
--- a/src/views/research/application/modules/fund-form.vue
+++ b/src/views/research/application/modules/fund-form.vue
@@ -3,9 +3,6 @@
 
 import { computed, ref } from 'vue';
 
-import { DICT_TYPE } from '@vben/constants';
-import { getDictOptions } from '@vben/hooks';
-
 import { useVbenModal } from '@vben/common-ui';
 
 import { Alert, message } from 'ant-design-vue';
@@ -22,6 +19,8 @@
 const isEdit = ref(false);
 /** 鐢宠缁忚垂鍑忓幓宸茬櫥璁版嫧浠橀噾棰濆悗鐨勫彲鐢ㄤ綑棰� */
 const remainder = ref<number>();
+/** 缂栬緫銆屽凡椹冲洖銆嶈褰曟椂灞曠ず鐨勮储鍔¢┏鍥炲師鍥� */
+const rejectReason = ref('');
 
 const alertMessage = computed(() => {
   if (remainder.value === undefined) {
@@ -98,17 +97,6 @@
       },
     },
     {
-      fieldName: 'status',
-      label: '鎷ㄤ粯鐘舵��',
-      component: 'Select',
-      componentProps: {
-        options: getDictOptions(DICT_TYPE.RESEARCH_FUND_STATUS, 'number'),
-        placeholder: '璇烽�夋嫨鎷ㄤ粯鐘舵��',
-      },
-      rules: 'selectRequired',
-      help: '閫夋嫨銆屽凡鎷ㄤ粯銆嶆椂蹇呴』涓婁紶鎷ㄤ粯鍑瘉闄勪欢',
-    },
-    {
       fieldName: 'remark',
       label: '澶囨敞',
       component: 'Textarea',
@@ -128,6 +116,8 @@
         multiple: true,
         showDescription: true,
       },
+      rules: 'required',
+      help: '蹇呭~锛氬嚟璇佸皢闅忚嚜鍔ㄧ敓鎴愮殑璐㈠姟鎶ラ攢鍗曚竴璧烽�佷氦璐㈠姟瀹℃壒',
       formItemClass: 'col-span-2',
     },
   ],
@@ -142,12 +132,9 @@
     }
     const values =
       (await formApi.getValues()) as ResearchFundRecordApi.FundRecord;
-    // 銆屽凡鎷ㄤ粯銆嶅繀椤绘湁鍑瘉锛屽厛鍦ㄥ墠绔嫤涓�閬擄紝閬垮厤鐢ㄦ埛鐧界瓑涓�娆¤姹�
-    if (
-      values.status === FundStatus.PAID &&
-      (!values.blobIds || values.blobIds.length === 0)
-    ) {
-      message.warning('鎷ㄤ粯鐘舵�佷负銆屽凡鎷ㄤ粯銆嶆椂锛岃鍏堜笂浼犳嫧浠樺嚟璇侀檮浠�');
+    // 鍑瘉蹇呭~锛屽厛鍦ㄥ墠绔嫤涓�閬擄紝閬垮厤鐢ㄦ埛鐧界瓑涓�娆¤姹�
+    if (!values.blobIds || values.blobIds.length === 0) {
+      message.warning('璇峰厛涓婁紶鏈鎷ㄤ粯鍑瘉锛屽嚟璇佸皢闅忚储鍔℃姤閿�鍗曢�佷氦璐㈠姟瀹℃壒');
       return;
     }
     modalApi.lock();
@@ -159,15 +146,16 @@
       amount: values.amount,
       payDate: values.payDate,
       payee: values.payee,
-      status: values.status,
       remark: values.remark,
-      blobIds: values.blobIds ?? [],
+      blobIds: values.blobIds,
     } as ResearchFundRecordApi.FundRecord;
     try {
       await (data.id ? updateFundRecord(data) : createFundRecord(data));
       await modalApi.close();
       emit('success');
-      message.success('淇濆瓨鎴愬姛');
+      message.success(
+        data.id ? '宸查噸鏂板彂璧疯储鍔℃姤閿�瀹℃壒' : '鐧昏鎴愬姛锛屽凡鑷姩鎻愪氦璐㈠姟鎶ラ攢瀹℃壒',
+      );
     } finally {
       modalApi.unlock();
     }
@@ -176,6 +164,7 @@
     if (!isOpen) {
       isEdit.value = false;
       remainder.value = undefined;
+      rejectReason.value = '';
       return;
     }
     const data = modalApi.getData<{
@@ -185,6 +174,10 @@
     }>();
     isEdit.value = !!data.record;
     remainder.value = data.remainder;
+    rejectReason.value =
+      data.record?.status === FundStatus.REJECTED
+        ? data.record.rejectReason || '璐㈠姟鏈~鍐欓┏鍥炲師鍥�'
+        : '';
     await formApi.resetForm();
     if (data.record) {
       const blobIds = (data.record.attachmentList ?? []).map((item) => ({
@@ -206,7 +199,6 @@
       await formApi.setValues(
         {
           applicationId: data.applicationId,
-          status: FundStatus.PENDING,
         },
         false,
       );
@@ -217,11 +209,18 @@
 
 <template>
   <Modal
-    :title="isEdit ? '缂栬緫鎷ㄦ璁板綍' : '鐧昏鎷ㄦ璁板綍'"
+    :title="isEdit ? '淇敼骞堕噸鏂板彂璧锋姤閿�' : '鐧昏鎷ㄦ锛堢櫥璁板悗鑷姩鎻愪氦璐㈠姟鎶ラ攢瀹℃壒锛�'"
     class="w-1/2"
     :close-on-click-modal="false"
   >
     <Alert
+      v-if="rejectReason"
+      class="mx-3 mb-3"
+      type="error"
+      show-icon
+      :message="`璐㈠姟鎶ラ攢宸查┏鍥烇細${rejectReason}`"
+    />
+    <Alert
       v-if="alertMessage"
       class="mx-3 mb-3"
       type="info"
diff --git a/src/views/research/application/modules/fund-panel.vue b/src/views/research/application/modules/fund-panel.vue
index f3900fe..49fef57 100644
--- a/src/views/research/application/modules/fund-panel.vue
+++ b/src/views/research/application/modules/fund-panel.vue
@@ -39,6 +39,10 @@
   () => (Number(application.value?.budgetAmount) || 0) - registeredAmount.value,
 );
 
+/** 鍚勭姸鎬佽褰曟暟閲忥紝鐢ㄤ簬椤堕儴鎻愮ず锛�0 鏉℃椂涓嶆彁绀猴級 */
+const statusCount = (status: number) =>
+  records.value.filter((item) => item.status === status).length;
+
 const [FundFormModal, fundFormModalApi] = useVbenModal({
   connectedComponent: FundForm,
   destroyOnClose: true,
@@ -176,6 +180,25 @@
         </div>
       </div>
 
+      <Alert
+        v-if="statusCount(FundStatus.APPROVING) > 0"
+        type="info"
+        show-icon
+        :message="`鏈� ${statusCount(FundStatus.APPROVING)} 鏉℃嫧浠樿褰曟鍦ㄦ姤閿�瀹℃壒涓紝鏆備笉鍙慨鏀癸紱璐㈠姟瀹℃壒缁撴灉灏嗚嚜鍔ㄥ洖鍐欐湰璁板綍`"
+      />
+      <Alert
+        v-if="statusCount(FundStatus.PAID) > 0"
+        type="success"
+        show-icon
+        :message="`鏈� ${statusCount(FundStatus.PAID)} 鏉℃嫧浠樿褰曡储鍔″凡閫氳繃锛屾嫧娆惧畬鎴恅"
+      />
+      <Alert
+        v-if="statusCount(FundStatus.REJECTED) > 0"
+        type="error"
+        show-icon
+        :message="`鏈� ${statusCount(FundStatus.REJECTED)} 鏉℃嫧浠樿褰曡储鍔″凡椹冲洖锛岃淇敼閲戦鎴栧嚟璇佸悗鐐广�岄噸鏂板彂璧锋姤閿�銆峘"
+      />
+
       <div v-if="editable">
         <TableAction
           :actions="[
@@ -193,13 +216,7 @@
       <Grid>
         <template #hasAttachment="{ row }">
           <Tag :color="row.hasAttachment ? 'success' : 'warning'">
-            {{
-              row.hasAttachment
-                ? '宸蹭笂浼�'
-                : row.status === FundStatus.PAID
-                  ? '缂哄け'
-                  : '涓嶉渶瑕�'
-            }}
+            {{ row.hasAttachment ? '宸蹭笂浼�' : '缂哄け' }}
           </Tag>
         </template>
         <template #actions="{ row }">
@@ -215,6 +232,14 @@
                 onClick: handleEdit.bind(null, row),
               },
               {
+                label: '閲嶆柊鍙戣捣鎶ラ攢',
+                type: 'link',
+                icon: ACTION_ICON.REFRESH,
+                auth: ['research:fund:update'],
+                ifShow: () => row.status === FundStatus.REJECTED,
+                onClick: handleEdit.bind(null, row),
+              },
+              {
                 label: '鍒犻櫎',
                 type: 'link',
                 danger: true,

--
Gitblit v1.9.3