From 7c430ce110eca877841f3dcb372f3a5f7bdc4918 Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期五, 12 六月 2026 15:29:07 +0800
Subject: [PATCH] 修改社保工资计算

---
 src/api/personnelManagement/staffSalaryMain.js                         |    9 ++
 src/views/personnelManagement/monthlyStatistics/components/formDia.vue |  189 ++++++++++++++++++++++++++++++++++-------------
 2 files changed, 145 insertions(+), 53 deletions(-)

diff --git a/src/api/personnelManagement/staffSalaryMain.js b/src/api/personnelManagement/staffSalaryMain.js
index a42f96b..08de85c 100644
--- a/src/api/personnelManagement/staffSalaryMain.js
+++ b/src/api/personnelManagement/staffSalaryMain.js
@@ -17,6 +17,14 @@
   });
 }
 
+export function staffSalaryMainCalculateByEmployeeId(data) {
+  return request({
+    url: "/staffSalaryMain/calculateByEmployeeId",
+    method: "post",
+    data,
+  });
+}
+
 export function staffSalaryMainAdd(data) {
   return request({
     url: "/staffSalaryMain/add",
@@ -40,4 +48,3 @@
     data: ids,
   });
 }
-
diff --git a/src/views/personnelManagement/monthlyStatistics/components/formDia.vue b/src/views/personnelManagement/monthlyStatistics/components/formDia.vue
index 8bb63e9..808c817 100644
--- a/src/views/personnelManagement/monthlyStatistics/components/formDia.vue
+++ b/src/views/personnelManagement/monthlyStatistics/components/formDia.vue
@@ -137,7 +137,7 @@
                         type="number"
                         placeholder="0"
                         size="small"
-                        @input="row.basicSalary = parseNum(row.basicSalary)" />
+                        @input="handleSalaryInput(row)" />
             </template>
           </el-table-column>
           <el-table-column label="璁′欢宸ヨ祫"
@@ -169,7 +169,7 @@
                         type="number"
                         placeholder="0"
                         size="small"
-                        @input="row.otherIncome = parseNum(row.otherIncome)" />
+                        @input="handleSalaryInput(row)" />
             </template>
           </el-table-column>
           <el-table-column label="绀句繚涓汉"
@@ -203,6 +203,16 @@
                         size="small"
                         disabled
                         @input="row.otherDeduct = parseNum(row.otherDeduct)" />
+            </template>
+          </el-table-column>
+          <el-table-column label="绀句繚琛ョ即"
+                           minWidth="120">
+            <template #default="{ row }">
+              <el-input v-model.number="row.socialSupplementAmount"
+                        type="number"
+                        placeholder="0"
+                        size="small"
+                        @input="handleSalaryInput(row)" />
             </template>
           </el-table-column>
           <el-table-column label="宸ヨ祫涓◣"
@@ -344,6 +354,7 @@
     staffSalaryMainAdd,
     staffSalaryMainUpdate,
     staffSalaryMainCalculateSalary,
+    staffSalaryMainCalculateByEmployeeId,
   } from "@/api/personnelManagement/staffSalaryMain.js";
   import { userListNoPageByTenantId } from "@/api/system/user.js";
 
@@ -372,6 +383,8 @@
   const selectedEmployees = ref([]);
   const bankOptions = ref([]);
   const userList = ref([]);
+  const recalcTimers = new Map();
+  const recalcVersions = new Map();
   const taxTableData = ref([
     { level: 1, range: "涓嶈秴杩�36000鍏�", rate: 3, quickDeduction: 0 },
     { level: 2, range: "瓒呰繃36000-144000鍏�", rate: 10, quickDeduction: 2520 },
@@ -387,6 +400,115 @@
     const n = Number(v);
     return isNaN(n) ? 0 : n;
   }
+
+  function parseNullableNum(v) {
+    if (v === "" || v == null) return null;
+    const n = Number(v);
+    return isNaN(n) ? null : n;
+  }
+
+  const createEmployeeRow = () => ({
+    staffOnJobId: undefined,
+    id: undefined,
+    staffName: "",
+    postName: "",
+    deptName: "",
+    basicSalary: 0,
+    pieceSalary: 0,
+    hourlySalary: 0,
+    otherIncome: 0,
+    socialPersonal: 0,
+    fundPersonal: 0,
+    otherDeduct: 0,
+    socialSupplementAmount: null,
+    salaryTax: 0,
+    grossSalary: 0,
+    deductSalary: 0,
+    netSalary: 0,
+    remark: "",
+  });
+
+  const normalizeEmployeeRow = (source, fallback = {}) => ({
+    ...createEmployeeRow(),
+    ...fallback,
+    ...source,
+    staffOnJobId: source?.staffOnJobId ?? source?.staffId ?? source?.id ?? fallback.staffOnJobId ?? fallback.id,
+    id: source?.staffOnJobId ?? source?.staffId ?? source?.id ?? fallback.staffOnJobId ?? fallback.id,
+    staffName: source?.staffName ?? fallback.staffName ?? "",
+    postName: source?.postName ?? fallback.postName ?? "",
+    deptName: source?.deptName ?? fallback.deptName ?? "",
+    basicSalary: parseNum(source?.basicSalary ?? fallback.basicSalary),
+    pieceSalary: parseNum(source?.pieceSalary ?? fallback.pieceSalary),
+    hourlySalary: parseNum(source?.hourlySalary ?? fallback.hourlySalary),
+    otherIncome: parseNum(source?.otherIncome ?? fallback.otherIncome),
+    socialPersonal: parseNum(source?.socialPersonal ?? fallback.socialPersonal),
+    fundPersonal: parseNum(source?.fundPersonal ?? fallback.fundPersonal),
+    otherDeduct: parseNum(source?.otherDeduct ?? fallback.otherDeduct),
+    socialSupplementAmount:
+      source?.socialSupplementAmount ?? fallback.socialSupplementAmount ?? null,
+    salaryTax: parseNum(source?.salaryTax ?? fallback.salaryTax),
+    grossSalary: parseNum(source?.grossSalary ?? fallback.grossSalary),
+    deductSalary: parseNum(source?.deductSalary ?? fallback.deductSalary),
+    netSalary: parseNum(source?.netSalary ?? fallback.netSalary),
+    remark: source?.remark ?? fallback.remark ?? "",
+  });
+
+  const applyBackendRow = (targetRow, sourceRow) => {
+    Object.assign(targetRow, normalizeEmployeeRow(sourceRow, targetRow));
+  };
+
+  const clearRowRecalcState = row => {
+    const key = String(row?.staffOnJobId ?? row?.id ?? "");
+    if (!key) return;
+    clearTimeout(recalcTimers.get(key));
+    recalcTimers.delete(key);
+    recalcVersions.delete(key);
+  };
+
+  const resetRecalcState = () => {
+    recalcTimers.forEach(timer => clearTimeout(timer));
+    recalcTimers.clear();
+    recalcVersions.clear();
+  };
+
+  const requestEmployeeRecalculate = row => {
+    const key = String(row?.staffOnJobId ?? row?.id ?? "");
+    if (!key || !form.value.salaryMonth) return;
+    const version = (recalcVersions.get(key) || 0) + 1;
+    recalcVersions.set(key, version);
+    clearTimeout(recalcTimers.get(key));
+    const payload = {
+      staffOnJobId: row.staffOnJobId ?? row.id,
+      salaryMonth: form.value.salaryMonth,
+      staffName: row.staffName ?? "",
+      postName: row.postName ?? "",
+      deptName: row.deptName ?? "",
+      basicSalary: parseNum(row.basicSalary),
+      pieceSalary: parseNum(row.pieceSalary),
+      hourlySalary: parseNum(row.hourlySalary),
+      otherIncome: parseNum(row.otherIncome),
+      socialPersonal: parseNum(row.socialPersonal),
+      fundPersonal: parseNum(row.fundPersonal),
+      otherDeduct: parseNum(row.otherDeduct),
+      socialSupplementAmount: parseNullableNum(row.socialSupplementAmount),
+      remark: row.remark ?? "",
+    };
+    recalcTimers.set(
+      key,
+      setTimeout(() => {
+        staffSalaryMainCalculateByEmployeeId(payload).then(res => {
+          if (recalcVersions.get(key) !== version) return;
+          applyBackendRow(row, res?.data || {});
+        });
+      }, 250)
+    );
+  };
+
+  const handleSalaryInput = row => {
+    row.basicSalary = parseNum(row.basicSalary);
+    row.otherIncome = parseNum(row.otherIncome);
+    requestEmployeeRecalculate(row);
+  };
 
   // 鍩虹璧勬枡琛ㄥ崟
   const data = reactive({
@@ -498,6 +620,7 @@
 
   const openDialog = (type, row) => {
     nextTick(() => {
+      resetRecalcState();
       loadDeptOptions();
       loadBankOptions();
       loadUserList();
@@ -529,25 +652,7 @@
 
         // 濡傛灉鏈夊憳宸ユ槑缁嗘暟鎹紝鐩存帴鍙嶆樉
         if (row.staffSalaryDetailList && row.staffSalaryDetailList.length > 0) {
-          employeeList.value = row.staffSalaryDetailList.map(e => ({
-            staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id,
-            id: e.staffOnJobId ?? e.staffId ?? e.id,
-            staffName: e.staffName ?? "",
-            postName: e.postName ?? "",
-            deptName: e.deptName ?? "",
-            basicSalary: parseNum(e.basicSalary),
-            pieceSalary: parseNum(e.pieceSalary),
-            hourlySalary: parseNum(e.hourlySalary),
-            otherIncome: parseNum(e.otherIncome),
-            socialPersonal: parseNum(e.socialPersonal),
-            fundPersonal: parseNum(e.fundPersonal),
-            otherDeduct: parseNum(e.otherDeduct),
-            salaryTax: parseNum(e.salaryTax),
-            grossSalary: parseNum(e.grossSalary),
-            deductSalary: parseNum(e.deductSalary),
-            netSalary: parseNum(e.netSalary),
-            remark: e.remark ?? "",
-          }));
+          employeeList.value = row.staffSalaryDetailList.map(e => normalizeEmployeeRow(e));
         }
       }
     });
@@ -576,30 +681,23 @@
       const id = node.staffId ?? node.id;
       if (existIds.has(id)) return;
       existIds.add(id);
-      employeeList.value.push({
+      const newRow = normalizeEmployeeRow({
         staffOnJobId: id,
         id,
         staffName: node.label,
         postName: node.postName ?? node.post ?? "",
         deptName: node.deptName ?? "",
-        basicSalary: 0,
-        pieceSalary: 0,
-        hourlySalary: 0,
-        otherIncome: 0,
-        socialPersonal: 0,
-        fundPersonal: 0,
-        otherDeduct: 0,
-        salaryTax: 0,
-        grossSalary: 0,
-        deductSalary: 0,
-        netSalary: 0,
         remark: "",
+        socialSupplementAmount: null,
       });
+      employeeList.value.push(newRow);
+      requestEmployeeRecalculate(newRow);
     });
     addPersonVisible.value = false;
   };
 
   const removeEmployee = row => {
+    clearRowRecalcState(row);
     employeeList.value = employeeList.value.filter(
       e => (e.staffOnJobId || e.id) !== (row.staffOnJobId || row.id)
     );
@@ -615,6 +713,7 @@
       return;
     }
     const ids = new Set(selectedEmployees.value.map(e => e.staffOnJobId || e.id));
+    selectedEmployees.value.forEach(item => clearRowRecalcState(item));
     employeeList.value = employeeList.value.filter(
       e => !ids.has(e.staffOnJobId || e.id)
     );
@@ -639,25 +738,8 @@
         proxy.$modal.msgWarning("鏈绠楀埌宸ヨ祫鏁版嵁");
         return;
       }
-      employeeList.value = list.map(e => ({
-        ...e,
-        staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id,
-        staffName: e.staffName,
-        postName: e.postName,
-        deptName: e.deptName,
-        basicSalary: parseNum(e.basicSalary),
-        pieceSalary: parseNum(e.pieceSalary),
-        hourlySalary: parseNum(e.hourlySalary),
-        otherIncome: parseNum(e.otherIncome),
-        socialPersonal: parseNum(e.socialPersonal),
-        fundPersonal: parseNum(e.fundPersonal),
-        otherDeduct: parseNum(e.otherDeduct),
-        salaryTax: parseNum(e.salaryTax),
-        grossSalary: parseNum(e.grossSalary),
-        deductSalary: parseNum(e.deductSalary),
-        netSalary: parseNum(e.netSalary),
-        remark: e.remark ?? "",
-      }));
+      resetRecalcState();
+      employeeList.value = list.map(e => normalizeEmployeeRow(e));
       proxy.$modal.msgSuccess("鐢熸垚鎴愬姛");
     });
   };
@@ -666,6 +748,7 @@
     proxy.$modal
       .confirm("纭畾娓呯┖褰撳墠鍛樺伐鍒楄〃鍚楋紵")
       .then(() => {
+        resetRecalcState();
         employeeList.value = [];
       })
       .catch(() => {});
@@ -712,6 +795,7 @@
         socialPersonal: parseNum(e.socialPersonal),
         fundPersonal: parseNum(e.fundPersonal),
         otherDeduct: parseNum(e.otherDeduct),
+        socialSupplementAmount: parseNum(e.socialSupplementAmount),
         salaryTax: parseNum(e.salaryTax),
         grossSalary: parseNum(e.grossSalary),
         deductSalary: parseNum(e.deductSalary),
@@ -733,6 +817,7 @@
   };
 
   const closeDia = () => {
+    resetRecalcState();
     dialogVisible.value = false;
     emit("close");
   };

--
Gitblit v1.9.3