From 62294084c9c35d35594e9f2d6c41342ae84e9526 Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期三, 29 七月 2026 16:39:20 +0800
Subject: [PATCH] 修改工资

---
 src/views/personnelManagement/monthlyStatistics/components/formDia.vue |  343 +++++++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 283 insertions(+), 60 deletions(-)

diff --git a/src/views/personnelManagement/monthlyStatistics/components/formDia.vue b/src/views/personnelManagement/monthlyStatistics/components/formDia.vue
index 981fefe..709d316 100644
--- a/src/views/personnelManagement/monthlyStatistics/components/formDia.vue
+++ b/src/views/personnelManagement/monthlyStatistics/components/formDia.vue
@@ -48,6 +48,14 @@
               </el-form-item>
             </el-col>
             <el-col :span="6">
+              <el-form-item label="宸ヤ綔鏃�">
+                <el-input v-model.number="monthWorkDays"
+                          type="number"
+                          placeholder="鑷姩璁$畻"
+                          @input="handleMonthWorkDaysInput" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
               <el-form-item label="閫夋嫨閮ㄩ棬"
                             prop="deptIds">
                 <el-select v-model="form.deptIds"
@@ -59,12 +67,14 @@
                   <el-option v-for="item in deptOptions"
                              :key="item.deptId"
                              :label="item.deptName"
-                             :value="item.deptId"
-                             :disabled="isDeptDisabled(item.deptId)" />
+                            :value="item.deptId"
+                            :disabled="isDeptDisabled(item.deptId)" />
                 </el-select>
               </el-form-item>
             </el-col>
-            <el-col :span="6">
+          </el-row>
+          <el-row :gutter="24">
+            <el-col :span="12">
               <el-form-item label="澶囨敞"
                             prop="remark">
                 <el-input v-model="form.remark"
@@ -72,9 +82,7 @@
                           clearable />
               </el-form-item>
             </el-col>
-          </el-row>
-          <el-row :gutter="24">
-            <el-col :span="6">
+            <el-col :span="4">
               <el-form-item label="鏀粯閾惰"
                             prop="payBank">
                 <el-select v-model="form.payBank"
@@ -89,7 +97,7 @@
                 </el-select>
               </el-form-item>
             </el-col>
-            <el-col :span="6">
+            <el-col :span="8">
               <el-form-item label="瀹℃牳浜�"
                             prop="auditUserId">
                 <el-select v-model="form.auditUserId"
@@ -128,6 +136,9 @@
           <el-table-column label="鍛樺伐濮撳悕"
                            prop="staffName"
                            minWidth="100" />
+          <el-table-column label="鍏ュ徃鏃ユ湡"
+                           prop="entryDate"
+                           minWidth="110" />
           <el-table-column label="閮ㄩ棬"
                            prop="deptName"
                            minWidth="100" />
@@ -151,27 +162,28 @@
                         disabled />
             </template>
           </el-table-column>
-          <el-table-column label="鏈堟椂闂村悎璁�"
+          <el-table-column label="鏃ヨ宸ヨ祫"
                            minWidth="110">
             <template #default="{ row }">
-              <el-input v-model.number="row.monthTimeTotal"
+              <el-input v-model.number="row.dailySalary"
                         type="number"
                         placeholder="0"
                         size="small"
                         disabled />
             </template>
           </el-table-column>
-          <el-table-column label="鏈堟爣鍑嗗伐鏃�"
-                           minWidth="110">
+          <el-table-column label="鍑哄嫟澶╂暟"
+                           minWidth="130">
             <template #default="{ row }">
-              <el-input v-model.number="row.standardHours"
+              <el-input v-model.number="row.workDays"
                         type="number"
                         placeholder="0"
                         size="small"
-                        disabled />
+                        style="width: 100%"
+                        @input="handleWorkDaysInput(row)" />
             </template>
           </el-table-column>
-          <el-table-column label="鎶樺悎鍊嶆暟"
+          <el-table-column label="鏁堢泭"
                            minWidth="100">
             <template #default="{ row }">
               <el-input v-model.number="row.conversionRatio"
@@ -181,30 +193,70 @@
                         disabled />
             </template>
           </el-table-column>
-          <el-table-column label="璁′欢宸ヨ祫"
+          <el-table-column label="鏈堟椂闂村悎璁�"
                            minWidth="110">
             <template #default="{ row }">
-              <el-input v-model.number="row.pieceSalary"
+              <el-input v-model.number="row.monthTimeTotal"
                         type="number"
                         placeholder="0"
                         size="small"
-                        @input="row.pieceSalary = parseNum(row.pieceSalary)" />
-            </template>
-          </el-table-column>
-          <el-table-column label="璁℃椂宸ヨ祫"
-                           minWidth="110">
-            <template #default="{ row }">
-              <el-input v-model.number="row.hourlySalary"
-                        type="number"
-                        placeholder="0"
-                        size="small"
-                        @input="row.hourlySalary = parseNum(row.hourlySalary)" />
+                        @input="handlePieceSalaryInput(row)" />
             </template>
           </el-table-column>
           <el-table-column label="鍏朵粬鏀跺叆"
                            minWidth="110">
             <template #default="{ row }">
               <el-input v-model.number="row.otherIncome"
+                        type="number"
+                        placeholder="0"
+                        size="small"
+                        @input="handleSalaryInput(row)" />
+            </template>
+          </el-table-column>
+          <el-table-column label="浜嬪亣"
+                           minWidth="90">
+            <template #default="{ row }">
+              <el-input v-model.number="row.personalLeave"
+                        type="number"
+                        placeholder="0"
+                        size="small"
+                        @input="handleLeaveInput(row)" />
+            </template>
+          </el-table-column>
+          <el-table-column label="鐥呭亣"
+                           minWidth="90">
+            <template #default="{ row }">
+              <el-input v-model.number="row.sickLeave"
+                        type="number"
+                        placeholder="0"
+                        size="small"
+                        @input="handleLeaveInput(row)" />
+            </template>
+          </el-table-column>
+          <el-table-column label="涓у亣"
+                           minWidth="90">
+            <template #default="{ row }">
+              <el-input v-model.number="row.bereavementLeave"
+                        type="number"
+                        placeholder="0"
+                        size="small"
+                        @input="handleLeaveInput(row)" />
+            </template>
+          </el-table-column>
+          <el-table-column label="濠氬亣"
+                           minWidth="90">
+            <template #default="{ row }">
+              <el-input v-model.number="row.marriageLeave"
+                        type="number"
+                        placeholder="0"
+                        size="small"
+                        @input="handleLeaveInput(row)" />
+            </template>
+          </el-table-column>
+          <el-table-column label="娲ヨ创"
+                           minWidth="110">
+            <template #default="{ row }">
+              <el-input v-model.number="row.allowance"
                         type="number"
                         placeholder="0"
                         size="small"
@@ -456,16 +508,20 @@
     deptName: "",
     basicSalary: 0,
     attendanceBonus: 100,
-    pieceSalary: 0,
+    entryDate: "",
+    workDays: 0,
+    dailySalary: 0,
     hourlySalary: 0,
     actualOutput: 0,
     standardQuantity: 0,
-    conversionFactor: 1,
-    convertedWorkHours: 0,
     monthTimeTotal: 0,
-    standardHours: 0,
     conversionRatio: 0,
     otherIncome: 0,
+    personalLeave: 0,
+    sickLeave: 0,
+    bereavementLeave: 0,
+    marriageLeave: 0,
+    allowance: 0,
     socialPersonal: 0,
     fundPersonal: 0,
     otherDeduct: 0,
@@ -488,16 +544,20 @@
     deptName: source?.deptName ?? fallback.deptName ?? "",
     basicSalary: parseNum(source?.basicSalary ?? fallback.basicSalary),
     attendanceBonus: parseNum(source?.attendanceBonus ?? fallback.attendanceBonus ?? 100) || 100,
-    pieceSalary: parseNum(source?.pieceSalary ?? fallback.pieceSalary),
+    entryDate: source?.entryDate ?? source?.contractStartTime ?? fallback.entryDate ?? "",
+    workDays: parseNum(source?.workDays ?? fallback.workDays),
+    dailySalary: parseNum(source?.dailySalary ?? fallback.dailySalary),
     hourlySalary: parseNum(source?.hourlySalary ?? fallback.hourlySalary),
     actualOutput: parseNum(source?.actualOutput ?? fallback.actualOutput),
     standardQuantity: parseNum(source?.standardQuantity ?? fallback.standardQuantity),
-    conversionFactor: parseNum(source?.conversionFactor ?? fallback.conversionFactor) || 1,
-    convertedWorkHours: parseNum(source?.convertedWorkHours ?? fallback.convertedWorkHours),
     monthTimeTotal: parseNum(source?.monthTimeTotal ?? fallback.monthTimeTotal),
-    standardHours: parseNum(source?.standardHours ?? fallback.standardHours),
     conversionRatio: parseNum(source?.conversionRatio ?? fallback.conversionRatio),
     otherIncome: parseNum(source?.otherIncome ?? fallback.otherIncome),
+    personalLeave: parseNum(source?.personalLeave ?? fallback.personalLeave),
+    sickLeave: parseNum(source?.sickLeave ?? fallback.sickLeave),
+    bereavementLeave: parseNum(source?.bereavementLeave ?? fallback.bereavementLeave),
+    marriageLeave: parseNum(source?.marriageLeave ?? fallback.marriageLeave),
+    allowance: parseNum(source?.allowance ?? fallback.allowance),
     socialPersonal: parseNum(source?.socialPersonal ?? fallback.socialPersonal),
     fundPersonal: parseNum(source?.fundPersonal ?? fallback.fundPersonal),
     otherDeduct: parseNum(source?.otherDeduct ?? fallback.otherDeduct),
@@ -510,8 +570,35 @@
     remark: source?.remark ?? fallback.remark ?? "",
   });
 
-  const applyBackendRow = (targetRow, sourceRow) => {
+  const recalculateRowByWorkDays = row => {
+    const workDayCount = parseNum(monthWorkDays.value);
+    const monthTimeTotal = parseNum(row.monthTimeTotal);
+    const standardHours = workDayCount > 0 ? workDayCount * 8 : 0;
+    if (workDayCount > 0 && parseNum(row.workDays) > workDayCount) {
+      row.workDays = workDayCount;
+    }
+    if (standardHours > 0) {
+      row.conversionRatio = (monthTimeTotal / standardHours).toFixed(4);
+    } else {
+      row.conversionRatio = 0;
+    }
+  };
+
+  const applyBackendRow = (targetRow, sourceRow, options = {}) => {
+    const preservedConversionRatio = options.preserveConversionRatio
+      ? parseNum(targetRow.conversionRatio)
+      : null;
     Object.assign(targetRow, normalizeEmployeeRow(sourceRow, targetRow));
+    if (options.preserveConversionRatio) {
+      targetRow.conversionRatio = preservedConversionRatio;
+    }
+    recalculateRowByWorkDays(targetRow);
+  };
+
+  const recalcRowsByCurrentWorkDays = () => {
+    employeeList.value.forEach(row => {
+      recalculateRowByWorkDays(row);
+    });
   };
 
   const clearRowRecalcState = row => {
@@ -528,30 +615,36 @@
     recalcVersions.clear();
   };
 
-  const requestEmployeeRecalculate = row => {
+  const requestEmployeeRecalculate = (row, options = {}) => {
     const key = String(row?.staffOnJobId ?? row?.id ?? "");
     if (!key || !form.value.salaryMonth) return;
     const version = (recalcVersions.get(key) || 0) + 1;
     recalcVersions.set(key, version);
+    const manualWorkDays = parseNum(row.workDays);
     clearTimeout(recalcTimers.get(key));
       const payload = {
       staffOnJobId: row.staffOnJobId ?? row.id,
       salaryMonth: form.value.salaryMonth,
+      monthWorkDays: parseNum(monthWorkDays.value),
       staffName: row.staffName ?? "",
       postName: row.postName ?? "",
       deptName: row.deptName ?? "",
+      entryDate: row.entryDate ?? row.contractStartTime ?? "",
       basicSalary: parseNum(row.basicSalary),
       attendanceBonus: parseNum(row.attendanceBonus),
-      pieceSalary: parseNum(row.pieceSalary),
+      workDays: parseNum(row.workDays),
+      dailySalary: parseNum(row.dailySalary),
       hourlySalary: parseNum(row.hourlySalary),
       actualOutput: parseNum(row.actualOutput),
       standardQuantity: parseNum(row.standardQuantity),
-      conversionFactor: parseNum(row.conversionFactor) || 1,
-      convertedWorkHours: parseNum(row.convertedWorkHours),
       monthTimeTotal: parseNum(row.monthTimeTotal),
-      standardHours: parseNum(row.standardHours),
       conversionRatio: parseNum(row.conversionRatio),
       otherIncome: parseNum(row.otherIncome),
+      personalLeave: parseNum(row.personalLeave),
+      sickLeave: parseNum(row.sickLeave),
+      bereavementLeave: parseNum(row.bereavementLeave),
+      marriageLeave: parseNum(row.marriageLeave),
+      allowance: parseNum(row.allowance),
       otherDeduct: parseNum(row.otherDeduct),
       socialSupplementAmount: parseNullableNum(row.socialSupplementAmount),
       remark: row.remark ?? "",
@@ -561,7 +654,12 @@
       setTimeout(() => {
         staffSalaryMainCalculateByEmployeeId(payload).then(res => {
           if (recalcVersions.get(key) !== version) return;
-          applyBackendRow(row, res?.data || {});
+          applyBackendRow(row, res?.data || {}, options);
+          if (manualWorkDays > 0) {
+            row.workDays = manualWorkDays;
+            row.dailySalary = manualWorkDays > 0 ? parseNum(row.basicSalary) / manualWorkDays : 0;
+            recalculateRowByWorkDays(row);
+          }
         });
       }, 250)
     );
@@ -572,8 +670,81 @@
     row.otherIncome = parseNum(row.otherIncome);
     row.actualOutput = parseNum(row.actualOutput);
     row.standardQuantity = parseNum(row.standardQuantity);
-    row.conversionFactor = parseNum(row.conversionFactor) || 1;
+    row.workDays = parseNum(row.workDays);
+    row.dailySalary = row.workDays > 0
+      ? (parseNum(row.basicSalary) / row.workDays).toFixed(3)
+      : 0;
+    requestEmployeeRecalculate(row, { preserveConversionRatio: true });
+  };
+
+  const handlePieceSalaryInput = row => {
+    row.monthTimeTotal = parseNum(row.monthTimeTotal);
     requestEmployeeRecalculate(row);
+  };
+
+  const handleWorkDaysInput = row => {
+    row.workDays = parseNum(row.workDays);
+    const maxWorkDays = parseNum(monthWorkDays.value);
+    if (maxWorkDays > 0 && row.workDays > maxWorkDays) {
+      row.workDays = maxWorkDays;
+      proxy.$modal.msgWarning("鍑哄嫟澶╂暟涓嶈兘澶т簬宸ヤ綔鏃�");
+    }
+    enforceLeaveAndWorkDayLimit(row);
+    row.dailySalary = row.workDays > 0
+      ? (parseNum(row.basicSalary) / row.workDays).toFixed(3)
+      : 0;
+    requestEmployeeRecalculate(row);
+  };
+
+  const handleLeaveInput = row => {
+    row.personalLeave = parseNum(row.personalLeave);
+    row.sickLeave = parseNum(row.sickLeave);
+    row.bereavementLeave = parseNum(row.bereavementLeave);
+    row.marriageLeave = parseNum(row.marriageLeave);
+    enforceLeaveAndWorkDayLimit(row);
+    requestEmployeeRecalculate(row);
+  };
+
+  const enforceLeaveAndWorkDayLimit = row => {
+    const maxWorkDays = parseNum(monthWorkDays.value);
+    if (maxWorkDays <= 0) {
+      return true;
+    }
+    const leaveTotal = parseNum(row.personalLeave)
+      + parseNum(row.sickLeave)
+      + parseNum(row.bereavementLeave)
+      + parseNum(row.marriageLeave)
+      + parseNum(row.workDays);
+    if (leaveTotal > maxWorkDays) {
+      proxy.$modal.msgWarning("璇峰亣鍚堣鍔犲嚭鍕ゅぉ鏁颁笉鑳借秴杩囧伐浣滄棩");
+      const allowedWorkDays = Math.max(0, maxWorkDays
+        - parseNum(row.personalLeave)
+        - parseNum(row.sickLeave)
+        - parseNum(row.bereavementLeave)
+        - parseNum(row.marriageLeave));
+      row.workDays = allowedWorkDays;
+      return false;
+    }
+    return true;
+  };
+
+  const handleMonthWorkDaysInput = () => {
+    monthWorkDays.value = parseNum(monthWorkDays.value);
+    employeeList.value.forEach(row => {
+      const standardHours = monthWorkDays.value > 0 ? monthWorkDays.value * 8 : 0;
+      if (parseNum(row.workDays) > monthWorkDays.value) {
+        row.workDays = monthWorkDays.value;
+        row.dailySalary = monthWorkDays.value > 0
+          ? (parseNum(row.basicSalary) / monthWorkDays.value).toFixed(3)
+          : 0;
+      }
+      if (parseNum(row.monthTimeTotal) > 0 && standardHours > 0) {
+        row.conversionRatio = (parseNum(row.monthTimeTotal) / standardHours).toFixed(4);
+      } else {
+        row.conversionRatio = 0;
+      }
+      requestEmployeeRecalculate(row);
+    });
   };
 
   // 鍩虹璧勬枡琛ㄥ崟
@@ -606,6 +777,26 @@
   const totalSalary = computed(() => {
     return employeeList.value.reduce((sum, e) => sum + parseNum(e.netSalary), 0);
   });
+
+  const monthWorkDays = ref(0);
+
+  const calculateMonthWorkDays = salaryMonth => {
+    if (!salaryMonth) return 0;
+    const [yearStr, monthStr] = String(salaryMonth).split("-");
+    const year = Number(yearStr);
+    const month = Number(monthStr);
+    if (!year || !month) return 0;
+    const start = new Date(year, month - 1, 1);
+    const end = new Date(year, month, 0);
+    let workDays = 0;
+    for (let day = new Date(start); day <= end; day.setDate(day.getDate() + 1)) {
+      const weekday = day.getDay();
+      if (weekday !== 0 && weekday !== 6) {
+        workDays += 1;
+      }
+    }
+    return workDays;
+  };
 
   // 鏍规嵁瀹℃牳浜篒D鑾峰彇瀹℃牳浜哄悕绉�
   const auditUserName = computed(() => {
@@ -712,6 +903,7 @@
       loadUserList();
       loadAllDeptOptions();
       employeeList.value = [];
+      monthWorkDays.value = 0;
       Object.assign(form.value, {
         id: undefined,
         salaryTitle: "",
@@ -736,10 +928,19 @@
         form.value.remark = row.remark ?? "";
         form.value.payBank = row.payBank ?? "";
         form.value.auditUserId = row.auditUserId ?? undefined;
-        // 濡傛灉鏈夊憳宸ユ槑缁嗘暟鎹紝鐩存帴鍙嶆樉
-        if (row.staffSalaryDetailList && row.staffSalaryDetailList.length > 0) {
-          employeeList.value = row.staffSalaryDetailList.map(e => normalizeEmployeeRow(e));
+        monthWorkDays.value = parseNum(row.monthWorkDays) || calculateMonthWorkDays(form.value.salaryMonth);
+      // 濡傛灉鏈夊憳宸ユ槑缁嗘暟鎹紝鐩存帴鍙嶆樉
+      if (row.staffSalaryDetailList && row.staffSalaryDetailList.length > 0) {
+          employeeList.value = row.staffSalaryDetailList.map(e => {
+            const normalized = normalizeEmployeeRow(e);
+            recalculateRowByWorkDays(normalized);
+            return normalized;
+          });
         }
+        recalcRowsByCurrentWorkDays();
+      } else if (form.value.salaryMonth) {
+        monthWorkDays.value = calculateMonthWorkDays(form.value.salaryMonth);
+        recalcRowsByCurrentWorkDays();
       }
       // 缂栬緫鐘舵�佷笖鏈夋湀浠芥椂锛屽姞杞藉彲鐢ㄩ儴闂ㄥ垪琛�
       if (type === "edit" && form.value.salaryMonth) {
@@ -753,6 +954,10 @@
     // 鍒囨崲鏈堜唤鏃舵竻绌哄凡閫夋嫨鐨勯儴闂�
     if (newMonth !== oldMonth) {
       form.value.deptIds = [];
+    }
+    if (!monthWorkDays.value || newMonth !== oldMonth) {
+      monthWorkDays.value = calculateMonthWorkDays(newMonth);
+      recalcRowsByCurrentWorkDays();
     }
     loadAvailableDepts(newMonth);
   });
@@ -786,6 +991,7 @@
         staffName: node.label,
         postName: node.postName ?? node.post ?? "",
         deptName: node.deptName ?? "",
+        entryDate: node.contractStartTime ?? node.entryDate ?? "",
         remark: "",
         socialSupplementAmount: null,
         attendanceBonus: node.attendanceBonus,
@@ -873,16 +1079,30 @@
   };
 
   const saveData = status => {
-    const payload = {
-      id: form.value.id,
-      salaryTitle: form.value.salaryTitle,
-      deptIds: form.value.deptIds?.length ? form.value.deptIds.join(",") : "",
-      salaryMonth: form.value.salaryMonth,
-      remark: form.value.remark,
-      payBank: form.value.payBank,
-      auditUserId: form.value.auditUserId,
-      auditUserName: auditUserName.value,
-      totalSalary: totalSalary.value,
+      const invalidRow = employeeList.value.find(row => {
+        const maxWorkDays = parseNum(monthWorkDays.value);
+        const leaveTotal = parseNum(row.personalLeave)
+          + parseNum(row.sickLeave)
+          + parseNum(row.bereavementLeave)
+          + parseNum(row.marriageLeave)
+          + parseNum(row.workDays);
+        return maxWorkDays > 0 && leaveTotal > maxWorkDays;
+      });
+      if (invalidRow) {
+        proxy.$modal.msgWarning("璇峰亣鍚堣鍔犲嚭鍕ゅぉ鏁颁笉鑳借秴杩囧伐浣滄棩");
+        return;
+      }
+      const payload = {
+        id: form.value.id,
+        salaryTitle: form.value.salaryTitle,
+        deptIds: form.value.deptIds?.length ? form.value.deptIds.join(",") : "",
+        salaryMonth: form.value.salaryMonth,
+        monthWorkDays: parseNum(monthWorkDays.value),
+        remark: form.value.remark,
+        payBank: form.value.payBank,
+        auditUserId: form.value.auditUserId,
+        auditUserName: auditUserName.value,
+        totalSalary: totalSalary.value,
       staffSalaryDetailList: employeeList.value.map(e => ({
         staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id,
         staffName: e.staffName,
@@ -890,14 +1110,17 @@
         deptName: e.deptName ?? "",
         basicSalary: parseNum(e.basicSalary),
         attendanceBonus: parseNum(e.attendanceBonus),
-        pieceSalary: parseNum(e.pieceSalary),
+        dailySalary: parseNum(e.dailySalary),
+        workDays: parseNum(e.workDays),
+        personalLeave: parseNum(e.personalLeave),
+        sickLeave: parseNum(e.sickLeave),
+        bereavementLeave: parseNum(e.bereavementLeave),
+        marriageLeave: parseNum(e.marriageLeave),
+        allowance: parseNum(e.allowance),
         hourlySalary: parseNum(e.hourlySalary),
         actualOutput: parseNum(e.actualOutput),
         standardQuantity: parseNum(e.standardQuantity),
-        conversionFactor: parseNum(e.conversionFactor) || 1,
-        convertedWorkHours: parseNum(e.convertedWorkHours),
         monthTimeTotal: parseNum(e.monthTimeTotal),
-        standardHours: parseNum(e.standardHours),
         conversionRatio: parseNum(e.conversionRatio),
         otherIncome: parseNum(e.otherIncome),
         socialPersonal: parseNum(e.socialPersonal),

--
Gitblit v1.9.3