huminmin
7 天以前 1ae52b4e9e4fea731844fae1a9217d2128a9335c
工资计算逻辑放到后端
已修改2个文件
265 ■■■■■ 文件已修改
src/api/personnelManagement/staffSalaryMain.js 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/components/formDia.vue 256 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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,
  });
}
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 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 calculateRow = row => {
    // 确保所有数值字段为数字
  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");
  };