From 1ae52b4e9e4fea731844fae1a9217d2128a9335c Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期五, 12 六月 2026 14:19:40 +0800
Subject: [PATCH] 工资计算逻辑放到后端

---
 src/api/personnelManagement/staffSalaryMain.js                         |    9 +
 src/views/personnelManagement/monthlyStatistics/components/formDia.vue |  260 ++++++++++++++++++++++++++++++----------------------
 2 files changed, 158 insertions(+), 111 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 26b5c73..b3437e7 100644
--- a/src/views/personnelManagement/monthlyStatistics/components/formDia.vue
+++ b/src/views/personnelManagement/monthlyStatistics/components/formDia.vue
@@ -141,7 +141,7 @@
                         type="number"
                         placeholder="0"
                         size="small"
-                        @input="calculateRow(row)" />
+                        @input="handleSalaryInput(row)" />
             </template>
           </el-table-column>
           <el-table-column label="鐧界彮澶╂暟"
@@ -151,7 +151,7 @@
                         type="number"
                         placeholder="0"
                         size="small"
-                        @input="calculateRow(row)" />
+                        @input="handleSalaryInput(row)" />
             </template>
           </el-table-column>
           <el-table-column label="澶滅彮澶╂暟"
@@ -161,7 +161,7 @@
                         type="number"
                         placeholder="0"
                         size="small"
-                        @input="calculateRow(row)" />
+                        @input="handleSalaryInput(row)" />
             </template>
           </el-table-column>
           <el-table-column label="椁愯ˉ"
@@ -191,7 +191,7 @@
                         type="number"
                         placeholder="0"
                         size="small"
-                        @input="calculateRow(row)" />
+                        @input="handleSalaryInput(row)" />
             </template>
           </el-table-column>
           <el-table-column label="绀句繚涓汉"
@@ -201,8 +201,7 @@
                         type="number"
                         placeholder="0"
                         size="small"
-                        disabled
-                        @input="calculateRow(row)" />
+                        disabled />
             </template>
           </el-table-column>
           <el-table-column label="鍏Н閲戜釜浜�"
@@ -222,7 +221,7 @@
                         type="number"
                         placeholder="0"
                         size="small"
-                        @input="calculateRow(row)" />
+                        @input="handleSalaryInput(row)" />
             </template>
           </el-table-column>
           <el-table-column label="绀句繚琛ョ即"
@@ -370,9 +369,9 @@
     staffSalaryMainAdd,
     staffSalaryMainUpdate,
     staffSalaryMainCalculateSalary,
+    staffSalaryMainCalculateByEmployeeId,
   } from "@/api/personnelManagement/staffSalaryMain.js";
   import { userListNoPageByTenantId } from "@/api/system/user.js";
-  import { listSubsidyConfiguration } from "@/api/personnelManagement/subsidyConfig.js";
 
   const emit = defineEmits(["update:modelValue", "close"]);
   const props = defineProps({
@@ -399,59 +398,149 @@
   const selectedEmployees = ref([]);
   const bankOptions = ref([]);
   const userList = ref([]);
-  const subsidyStandard = ref({
+  const recalcTimers = new Map();
+  const recalcVersions = new Map();
+
+  const getEmployeeKey = row => row?.staffOnJobId ?? row?.staffId ?? row?.id;
+
+  const createEmptySalaryRow = () => ({
+    staffOnJobId: undefined,
+    id: undefined,
+    staffName: "",
+    postName: "",
+    deptName: "",
+    nation: "",
+    basicSalary: 0,
+    dayDays: 0,
+    nightDays: 0,
     mealAmount: 0,
     nightAmount: 0,
+    pieceSalary: 0,
+    hourlySalary: 0,
+    otherIncome: 0,
+    socialPersonal: 0,
+    fundPersonal: 0,
+    otherDeduct: 0,
+    socialSupplementAmount: 0,
+    salaryTax: 0,
+    grossSalary: 0,
+    deductSalary: 0,
+    netSalary: 0,
+    remark: "",
   });
 
-  const loadSubsidyStandard = () => {
-    listSubsidyConfiguration().then(res => {
-      if (res.data && res.data.length > 0) {
-        subsidyStandard.value = {
-          mealAmount: res.data[0].mealAmount || 0,
-          nightAmount: res.data[0].nightAmount || 0,
-        };
-      }
+  const normalizeSalaryRow = source => ({
+    ...createEmptySalaryRow(),
+    ...source,
+    staffOnJobId: source?.staffOnJobId ?? source?.staffId ?? source?.id,
+    id: source?.staffOnJobId ?? source?.staffId ?? source?.id,
+    basicSalary: parseNum(source?.basicSalary),
+    dayDays: parseNum(source?.dayDays ?? source?.dayShiftDays),
+    nightDays: parseNum(source?.nightDays ?? source?.nightShiftDays),
+    mealAmount: parseNum(source?.mealAmount ?? source?.mealSubsidy),
+    nightAmount: parseNum(source?.nightAmount ?? source?.nightSubsidy),
+    pieceSalary: parseNum(source?.pieceSalary),
+    hourlySalary: parseNum(source?.hourlySalary),
+    otherIncome: parseNum(source?.otherIncome),
+    socialPersonal: parseNum(source?.socialPersonal),
+    fundPersonal: parseNum(source?.fundPersonal),
+    otherDeduct: parseNum(source?.otherDeduct),
+    socialSupplementAmount: parseNum(source?.socialSupplementAmount),
+    salaryTax: parseNum(source?.salaryTax),
+    grossSalary: parseNum(source?.grossSalary),
+    deductSalary: parseNum(source?.deductSalary),
+    netSalary: parseNum(source?.netSalary),
+    remark: source?.remark ?? "",
+  });
+
+  const applyBackendRow = (targetRow, sourceRow) => {
+    const normalized = normalizeSalaryRow(sourceRow);
+    Object.assign(targetRow, {
+      staffOnJobId: targetRow.staffOnJobId ?? normalized.staffOnJobId,
+      id: targetRow.id ?? normalized.id,
+      staffName: normalized.staffName || targetRow.staffName || "",
+      postName: normalized.postName || targetRow.postName || "",
+      deptName: normalized.deptName || targetRow.deptName || "",
+      nation: normalized.nation || targetRow.nation || "",
+      basicSalary: normalized.basicSalary,
+      dayDays: normalized.dayDays,
+      nightDays: normalized.nightDays,
+      mealAmount: normalized.mealAmount,
+      nightAmount: normalized.nightAmount,
+      pieceSalary: normalized.pieceSalary,
+      hourlySalary: normalized.hourlySalary,
+      otherIncome: normalized.otherIncome,
+      socialPersonal: normalized.socialPersonal,
+      fundPersonal: normalized.fundPersonal,
+      otherDeduct: normalized.otherDeduct,
+      socialSupplementAmount: normalized.socialSupplementAmount,
+      salaryTax: normalized.salaryTax,
+      grossSalary: normalized.grossSalary,
+      deductSalary: normalized.deductSalary,
+      netSalary: normalized.netSalary,
+      remark: normalized.remark || targetRow.remark || "",
     });
   };
 
-  const calculateRow = row => {
-    // 纭繚鎵�鏈夋暟鍊煎瓧娈典负鏁板瓧
+  const requestBackendRecalculate = row => {
+    const key = String(getEmployeeKey(row) ?? "");
+    if (!key || !form.value.deptIds?.length || !form.value.salaryMonth) {
+      return;
+    }
+    const version = (recalcVersions.get(key) || 0) + 1;
+    recalcVersions.set(key, version);
+    clearTimeout(recalcTimers.get(key));
+    const payload = {
+      staffOnJobId: getEmployeeKey(row),
+      salaryMonth: form.value.salaryMonth,
+      basicSalary: parseNum(row.basicSalary),
+      dayDays: parseNum(row.dayDays),
+      nightDays: parseNum(row.nightDays),
+      pieceSalary: parseNum(row.pieceSalary),
+      hourlySalary: parseNum(row.hourlySalary),
+      otherIncome: parseNum(row.otherIncome),
+      otherDeduct: parseNum(row.otherDeduct),
+      socialPersonal: parseNum(row.socialPersonal),
+      fundPersonal: parseNum(row.fundPersonal),
+      remark: row.remark ?? "",
+    };
+    recalcTimers.set(
+      key,
+      setTimeout(() => {
+        staffSalaryMainCalculateByEmployeeId(payload)
+          .then(res => {
+            if (recalcVersions.get(key) !== version) return;
+            applyBackendRow(row, res?.data || {});
+          })
+          .catch(() => {
+          });
+      }, 300)
+    );
+  };
+
+  const handleSalaryInput = row => {
     row.basicSalary = parseNum(row.basicSalary);
     row.dayDays = parseNum(row.dayDays);
     row.nightDays = parseNum(row.nightDays);
+    row.pieceSalary = parseNum(row.pieceSalary);
+    row.hourlySalary = parseNum(row.hourlySalary);
     row.otherIncome = parseNum(row.otherIncome);
-    row.socialPersonal = parseNum(row.socialPersonal);
-    row.fundPersonal = parseNum(row.fundPersonal);
     row.otherDeduct = parseNum(row.otherDeduct);
-    row.socialSupplementAmount = parseNum(row.socialSupplementAmount);
-    row.salaryTax = parseNum(row.salaryTax);
+    requestBackendRecalculate(row);
+  };
 
-    // 1. 璁$畻澶滅彮琛ヨ创锛氬鐝ぉ鏁� * 鏍囧噯
-    row.nightAmount = row.nightDays * subsidyStandard.value.nightAmount;
+  const resetRecalcState = () => {
+    recalcTimers.forEach(timer => clearTimeout(timer));
+    recalcTimers.clear();
+    recalcVersions.clear();
+  };
 
-    // 2. 璁$畻椁愯ˉ锛氫粎闄愬洖鏃忥紝(鐧界彮 + 澶滅彮) * 鏍囧噯
-    if (row.nation && (row.nation === "鍥炴棌" || row.nation.includes("鍥�"))) {
-      row.mealAmount =
-        (row.dayDays + row.nightDays) * subsidyStandard.value.mealAmount;
-    } else {
-      row.mealAmount = 0;
-    }
-
-    // 3. 璁$畻搴斿彂宸ヨ祫 = 鍩烘湰宸ヨ祫 + 椁愯ˉ + 澶滅彮琛ュ姪 + 鍏朵粬鏀跺叆
-    row.grossSalary =
-      row.basicSalary + row.mealAmount + row.nightAmount + row.otherIncome;
-
-    // 4. 璁$畻搴旀墸宸ヨ祫 = 绀句繚涓汉 + 鍏Н閲戜釜浜� + 鍏朵粬鏀嚭 + 绀句繚琛ョ即 + 宸ヨ祫涓◣
-    row.deductSalary =
-      row.socialPersonal +
-      row.fundPersonal +
-      row.otherDeduct +
-      row.socialSupplementAmount +
-      row.salaryTax;
-
-    // 5. 璁$畻瀹炲彂宸ヨ祫 = 搴斿彂宸ヨ祫 - 搴旀墸宸ヨ祫
-    row.netSalary = row.grossSalary - row.deductSalary;
+  const clearRowRecalcState = row => {
+    const key = String(getEmployeeKey(row) ?? "");
+    if (!key) return;
+    clearTimeout(recalcTimers.get(key));
+    recalcTimers.delete(key);
+    recalcVersions.delete(key);
   };
   const taxTableData = ref([
     { level: 1, range: "涓嶈秴杩�36000鍏�", rate: 3, quickDeduction: 0 },
@@ -579,10 +668,10 @@
 
   const openDialog = (type, row) => {
     nextTick(() => {
+      resetRecalcState();
       loadDeptOptions();
       loadBankOptions();
       loadUserList();
-      loadSubsidyStandard();
       employeeList.value = [];
       Object.assign(form.value, {
         id: undefined,
@@ -611,29 +700,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 ?? "",
-            nation: e.nation ?? "",
-            basicSalary: parseNum(e.basicSalary),
-            dayDays: parseNum(e.dayDays ?? e.dayShiftDays),
-            nightDays: parseNum(e.nightDays ?? e.nightShiftDays),
-            mealAmount: parseNum(e.mealAmount ?? e.mealSubsidy),
-            nightAmount: parseNum(e.nightAmount ?? e.nightSubsidy),
-            otherIncome: parseNum(e.otherIncome),
-            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),
-            netSalary: parseNum(e.netSalary),
-            remark: e.remark ?? "",
-          }));
+          employeeList.value = row.staffSalaryDetailList.map(e => normalizeSalaryRow(e));
         }
       }
     });
@@ -662,7 +729,8 @@
       const id = node.staffId ?? node.id;
       if (existIds.has(id)) return;
       existIds.add(id);
-      employeeList.value.push({
+      const newRow = {
+        ...createEmptySalaryRow(),
         staffOnJobId: id,
         id,
         staffName: node.label,
@@ -670,26 +738,15 @@
         deptName: node.deptName ?? "",
         nation: node.nation ?? "",
         basicSalary: parseNum(node.basicSalary),
-        dayDays: 0,
-        nightDays: 0,
-        mealAmount: 0,
-        nightAmount: 0,
-        otherIncome: 0,
-        socialPersonal: 0,
-        fundPersonal: 0,
-        otherDeduct: 0,
-        socialSupplementAmount: 0,
-        salaryTax: 0,
-        grossSalary: 0,
-        deductSalary: 0,
-        netSalary: 0,
-        remark: "",
-      });
+      };
+      employeeList.value.push(newRow);
+      requestBackendRecalculate(newRow);
     });
     addPersonVisible.value = false;
   };
 
   const removeEmployee = row => {
+    clearRowRecalcState(row);
     employeeList.value = employeeList.value.filter(
       e => (e.staffOnJobId || e.id) !== (row.staffOnJobId || row.id)
     );
@@ -705,6 +762,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)
     );
@@ -729,29 +787,7 @@
         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,
-        nation: e.nation ?? "",
-        basicSalary: parseNum(e.basicSalary),
-        dayDays: parseNum(e.dayDays ?? e.dayShiftDays),
-        nightDays: parseNum(e.nightDays ?? e.nightShiftDays),
-        mealAmount: parseNum(e.mealAmount ?? e.mealSubsidy),
-        nightAmount: parseNum(e.nightAmount ?? e.nightSubsidy),
-        otherIncome: parseNum(e.otherIncome),
-        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),
-        netSalary: parseNum(e.netSalary),
-        remark: e.remark ?? "",
-      }));
+      employeeList.value = list.map(e => normalizeSalaryRow(e));
       proxy.$modal.msgSuccess("鐢熸垚鎴愬姛");
     });
   };
@@ -760,6 +796,7 @@
     proxy.$modal
       .confirm("纭畾娓呯┖褰撳墠鍛樺伐鍒楄〃鍚楋紵")
       .then(() => {
+        resetRecalcState();
         employeeList.value = [];
       })
       .catch(() => {});
@@ -805,6 +842,8 @@
         nightDays: parseNum(e.nightDays),
         mealAmount: parseNum(e.mealAmount),
         nightAmount: parseNum(e.nightAmount),
+        pieceSalary: parseNum(e.pieceSalary),
+        hourlySalary: parseNum(e.hourlySalary),
         otherIncome: parseNum(e.otherIncome),
         socialPersonal: parseNum(e.socialPersonal),
         fundPersonal: parseNum(e.fundPersonal),
@@ -831,6 +870,7 @@
   };
 
   const closeDia = () => {
+    resetRecalcState();
     dialogVisible.value = false;
     emit("close");
   };

--
Gitblit v1.9.3