huminmin
6 天以前 62294084c9c35d35594e9f2d6c41342ae84e9526
修改工资
已修改6个文件
581 ■■■■ 文件已修改
src/views/basicData/product/index.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/components/auditDia.vue 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/components/formDia.vue 343 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionCosting/index.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/Edit.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionProcess/index.vue 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue
@@ -112,6 +112,19 @@
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="标准数量:"
                          prop="standardQuantity">
              <el-input v-model="form.standardQuantity"
                        type="number"
                        :step="0.001"
                        placeholder="请输入产品标准数量"
                        clearable
                        @keydown.enter.prevent />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
@@ -331,11 +344,29 @@
  const data = reactive({
    form: {
      productName: "",
      standardQuantity: null,
    },
    rules: {
      productName: [
        { required: true, message: "请输入", trigger: "blur" },
        { max: 20, message: "产品名称不能超过20个字符", trigger: "blur" },
      ],
      standardQuantity: [
        {
          required: false,
          trigger: "blur",
          validator: (rule, value, callback) => {
            if (value == null || value === "") {
              callback();
              return;
            }
            if (isNaN(value) || value < 0) {
              callback(new Error("标准数量必须是非负数字"));
            } else {
              callback();
            }
          },
        },
      ],
    },
    modelForm: {
@@ -412,6 +443,8 @@
    productDia.value = true;
    form.value.productName =
      type === "edit" && data ? data.productName : "";
    form.value.standardQuantity =
      type === "edit" && data ? data.standardQuantity : null;
  };
  // 打开规格型号弹框
  const openModelDia = (type, data) => {
src/views/personnelManagement/monthlyStatistics/components/auditDia.vue
@@ -13,6 +13,7 @@
      <el-descriptions :column="3" border>
        <el-descriptions-item label="工资主题">{{ auditData?.salaryTitle || '-' }}</el-descriptions-item>
        <el-descriptions-item label="工资月份">{{ auditData?.salaryMonth || '-' }}</el-descriptions-item>
        <el-descriptions-item label="工作日天数">{{ auditData?.monthWorkDays ?? '-' }}</el-descriptions-item>
        <el-descriptions-item label="工资总额">¥ {{ formatMoney(auditData?.totalSalary) }}</el-descriptions-item>
        <el-descriptions-item label="支付银行">{{ auditData?.payBank || '-' }}</el-descriptions-item>
        <el-descriptions-item label="审核人">{{ auditData?.auditUserName || '-' }}</el-descriptions-item>
@@ -32,18 +33,43 @@
        <div v-else>
          <el-table :data="employeeList" border max-height="300" style="width: 100%">
            <el-table-column prop="staffName" label="员工姓名" width="100" />
            <el-table-column prop="entryDate" label="入司日期" width="110" />
            <el-table-column prop="deptName" label="部门" width="120" />
            <el-table-column prop="basicSalary" label="基本工资" width="100" align="right">
              <template #default="{ row }">¥ {{ formatMoney(row.basicSalary) }}</template>
            </el-table-column>
            <el-table-column prop="pieceSalary" label="计件工资" width="100" align="right">
              <template #default="{ row }">¥ {{ formatMoney(row.pieceSalary) }}</template>
            <el-table-column prop="attendanceBonus" label="全勤奖" width="100" align="right">
              <template #default="{ row }">¥ {{ formatMoney(row.attendanceBonus) }}</template>
            </el-table-column>
            <el-table-column prop="hourlySalary" label="计时工资" width="100" align="right">
              <template #default="{ row }">¥ {{ formatMoney(row.hourlySalary) }}</template>
            <el-table-column prop="dailySalary" label="日计工资" width="100" align="right">
              <template #default="{ row }">¥ {{ formatMoney(row.dailySalary) }}</template>
            </el-table-column>
            <el-table-column prop="workDays" label="出勤天数" width="90" align="right">
              <template #default="{ row }">{{ formatNumber(row.workDays, 0) }}</template>
            </el-table-column>
            <el-table-column prop="conversionRatio" label="效益" width="90" align="right">
              <template #default="{ row }">{{ formatRate(row.conversionRatio) }}</template>
            </el-table-column>
            <el-table-column prop="monthTimeTotal" label="月时间合计" width="110" align="right">
              <template #default="{ row }">{{ formatNumber(row.monthTimeTotal, 2) }}</template>
            </el-table-column>
            <el-table-column prop="otherIncome" label="其他收入" width="100" align="right">
              <template #default="{ row }">¥ {{ formatMoney(row.otherIncome) }}</template>
            </el-table-column>
            <el-table-column prop="personalLeave" label="事假" width="80" align="right">
              <template #default="{ row }">{{ formatNumber(row.personalLeave, 0) }}</template>
            </el-table-column>
            <el-table-column prop="sickLeave" label="病假" width="80" align="right">
              <template #default="{ row }">{{ formatNumber(row.sickLeave, 0) }}</template>
            </el-table-column>
            <el-table-column prop="bereavementLeave" label="丧假" width="80" align="right">
              <template #default="{ row }">{{ formatNumber(row.bereavementLeave, 0) }}</template>
            </el-table-column>
            <el-table-column prop="marriageLeave" label="婚假" width="80" align="right">
              <template #default="{ row }">{{ formatNumber(row.marriageLeave, 0) }}</template>
            </el-table-column>
            <el-table-column prop="allowance" label="津贴" width="100" align="right">
              <template #default="{ row }">¥ {{ formatMoney(row.allowance) }}</template>
            </el-table-column>
            <el-table-column prop="socialPersonal" label="社保个人" width="100" align="right">
              <template #default="{ row }">¥ {{ formatMoney(row.socialPersonal) }}</template>
@@ -51,8 +77,20 @@
            <el-table-column prop="fundPersonal" label="公积金个人" width="120" align="right">
              <template #default="{ row }">¥ {{ formatMoney(row.fundPersonal) }}</template>
            </el-table-column>
            <el-table-column prop="otherDeduct" label="其他支出" width="100" align="right">
              <template #default="{ row }">¥ {{ formatMoney(row.otherDeduct) }}</template>
            </el-table-column>
            <el-table-column prop="socialSupplementAmount" label="社保补缴" width="110" align="right">
              <template #default="{ row }">¥ {{ formatMoney(row.socialSupplementAmount) }}</template>
            </el-table-column>
            <el-table-column prop="salaryTax" label="工资个税" width="100" align="right">
              <template #default="{ row }">¥ {{ formatMoney(row.salaryTax) }}</template>
            </el-table-column>
            <el-table-column prop="grossSalary" label="应发工资" width="100" align="right">
              <template #default="{ row }">¥ {{ formatMoney(row.grossSalary) }}</template>
            </el-table-column>
            <el-table-column prop="deductSalary" label="应扣工资" width="100" align="right">
              <template #default="{ row }">¥ {{ formatMoney(row.deductSalary) }}</template>
            </el-table-column>
            <el-table-column prop="netSalary" label="实发工资" width="100" align="right" fixed="right">
              <template #default="{ row }">¥ {{ formatMoney(row.netSalary) }}</template>
@@ -123,6 +161,16 @@
  return num.toFixed(2);
};
const formatNumber = (value, digits = 2) => {
  const num = Number(value) || 0;
  return num.toFixed(digits);
};
const formatRate = (value) => {
  const num = Number(value) || 0;
  return num.toFixed(4);
};
// 计算工资总额
const totalSalary = computed(() => {
  return employeeList.value.reduce((sum, e) => {
@@ -140,15 +188,29 @@
  if (row?.staffSalaryDetailList && Array.isArray(row.staffSalaryDetailList)) {
    employeeList.value = row.staffSalaryDetailList.map((e) => ({
      staffName: e.staffName ?? "",
      entryDate: e.entryDate ?? "",
      deptName: e.deptName ?? "",
      basicSalary: Number(e.basicSalary) || 0,
      attendanceBonus: Number(e.attendanceBonus) || 0,
      dailySalary: Number(e.dailySalary) || (Number(e.workDays) > 0 ? (Number(e.basicSalary) / Number(e.workDays)) : 0),
      workDays: Number(e.workDays) || 0,
      pieceSalary: Number(e.pieceSalary) || 0,
      hourlySalary: Number(e.hourlySalary) || 0,
      otherIncome: Number(e.otherIncome) || 0,
      personalLeave: Number(e.personalLeave) || 0,
      sickLeave: Number(e.sickLeave) || 0,
      bereavementLeave: Number(e.bereavementLeave) || 0,
      marriageLeave: Number(e.marriageLeave) || 0,
      allowance: Number(e.allowance) || 0,
      socialPersonal: Number(e.socialPersonal) || 0,
      fundPersonal: Number(e.fundPersonal) || 0,
      otherDeduct: Number(e.otherDeduct) || 0,
      socialSupplementAmount: Number(e.socialSupplementAmount) || 0,
      salaryTax: Number(e.salaryTax) || 0,
      grossSalary: Number(e.grossSalary) || 0,
      deductSalary: Number(e.deductSalary) || 0,
      netSalary: Number(e.netSalary) || 0,
      monthTimeTotal: Number(e.monthTimeTotal) || 0,
      conversionRatio: Number(e.conversionRatio) || 0,
    }));
  } else {
    console.log('没有找到员工明细数据');
@@ -220,4 +282,4 @@
:deep(.el-descriptions__label) {
  width: 100px;
}
</style>
</style>
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;
  };
  // 根据审核人ID获取审核人名称
  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),
src/views/productionManagement/productionCosting/index.vue
@@ -216,6 +216,11 @@
      minWidth: 100,
    },
    {
      label: "标准数量",
      prop: "standardQuantity",
      minWidth: 100,
    },
    {
      label: "工单编号",
      prop: "workOrderNo",
      minWidth: 140,
@@ -233,11 +238,6 @@
    {
      label: "生产数量",
      prop: "quantity",
      minWidth: 100,
    },
    {
      label: "工时定额",
      prop: "workHours",
      minWidth: 100,
    },
    {
src/views/productionManagement/productionProcess/Edit.vue
@@ -31,14 +31,16 @@
            :rules="[
                {
                required: true,
                message: '请选择工序类型',
                message: '工序仅支持计件',
              }
            ]"
        >
          <el-select v-model="formState.type" placeholder="请选择工序类型">
            <el-option label="计时" :value="0" />
          <el-select v-model="formState.type" placeholder="工序仅支持计件" disabled>
            <el-option label="计件" :value="1" />
          </el-select>
          <div style="margin-top: 6px; color: #909399; font-size: 12px;">
            工序仅支持计件
          </div>
        </el-form-item>
        <el-form-item label="工资定额" prop="salaryQuota">
          <el-input v-model="formState.salaryQuota" type="number" :step="0.001" />
@@ -88,7 +90,7 @@
const formState = ref({
  id: props.record.id,
  name: props.record.name,
  type: props.record.type,
  type: 1,
  no: props.record.no,
  remark: props.record.remark,
  salaryQuota: props.record.salaryQuota,
@@ -113,7 +115,7 @@
      id: newRecord.id,
      name: newRecord.name || '',
      no: newRecord.no || '',
      type: newRecord.type,
      type: 1,
      remark: newRecord.remark || '',
      salaryQuota: newRecord.salaryQuota || '',
      isQuality: props.record.isQuality,
@@ -130,7 +132,7 @@
      id: props.record.id,
      name: props.record.name || '',
      no: props.record.no || '',
      type: props.record.type,
      type: 1,
      remark: props.record.remark || '',
      salaryQuota: props.record.salaryQuota || '',
      isQuality: props.record.isQuality,
src/views/productionManagement/productionProcess/index.vue
@@ -46,7 +46,7 @@
              <div class="process-desc">{{ process.remark || '暂无描述' }}</div>
              <div class="process-device">关联设备: {{ (deviceOptions.find(item => item.id === Number(process.deviceLedgerId))?.deviceName) || '未关联' }}</div>
              <div class="process-device">标准数量: {{ process.standardQuantity || 0 }}</div>
              <div class="process-device">折算系数: {{ process.conversionFactor ?? 1 }}</div>
              <div class="process-device"></div>
            </div>
            <div class="card-footer">
              <div class="status-tag">
@@ -66,7 +66,7 @@
                  计件
                </el-tag>
              </div>
              <span class="param-count">工资定额: ¥{{ process.salaryQuota || 0 }}</span>
              <span class="param-count"></span>
            </div>
          </div>
        </div>
@@ -120,12 +120,6 @@
          <el-input v-model="processForm.name"
                    placeholder="请输入工序名称" />
        </el-form-item>
        <el-form-item label="工资定额"
                      prop="salaryQuota">
          <el-input v-model="processForm.salaryQuota"
                    type="number"
                    :step="0.001" />
        </el-form-item>
        <el-form-item label="标准数量"
                      prop="standardQuantity">
          <el-input v-model="processForm.standardQuantity"
@@ -133,12 +127,26 @@
                    :step="0.001"
                    placeholder="请输入工序标准数量" />
        </el-form-item>
        <el-form-item label="折算系数"
                      prop="conversionFactor">
          <el-input v-model="processForm.conversionFactor"
        <el-form-item label="小提琴系数"
                      prop="violinFactor">
          <el-input v-model="processForm.violinFactor"
                    type="number"
                    :step="0.01"
                    placeholder="默认 1" />
                    :step="0.001"
                    placeholder="请输入小提琴系数" />
        </el-form-item>
        <el-form-item label="大提琴系数"
                      prop="violaFactor">
          <el-input v-model="processForm.violaFactor"
                    type="number"
                    :step="0.001"
                    placeholder="请输入大提琴系数" />
        </el-form-item>
        <el-form-item label="贝斯系数"
                      prop="bassFactor">
          <el-input v-model="processForm.bassFactor"
                    type="number"
                    :step="0.001"
                    placeholder="请输入贝斯系数" />
        </el-form-item>
        <el-form-item label="是否质检"
                      prop="isQuality">
@@ -359,9 +367,10 @@
    id: null,
    no: "",
    name: "",
    salaryQuota: null,
    standardQuantity: null,
    conversionFactor: 1,
    violinFactor: null,
    violaFactor: null,
    bassFactor: null,
    isQuality: false,
    isProduction: false,
    remark: "",
@@ -371,20 +380,6 @@
  const processRules = {
    no: [{ required: true, message: "请输入工序编码", trigger: "blur" }],
    name: [{ required: true, message: "请输入工序名称", trigger: "blur" }],
    salaryQuota: [
      {
        required: false,
        message: "请输入工资定额",
        trigger: "blur",
        validator: (rule, value, callback) => {
          if (isNaN(value) || value < 0) {
            callback(new Error("工资定额必须是非负数字"));
          } else {
            callback();
          }
        },
      },
    ],
    standardQuantity: [
      {
        required: false,
@@ -403,18 +398,54 @@
        },
      },
    ],
    conversionFactor: [
    violinFactor: [
      {
        required: false,
        message: "请输入折算系数",
        message: "请输入小提琴系数",
        trigger: "blur",
        validator: (rule, value, callback) => {
          if (value == null || value === "") {
            callback();
            return;
          }
          if (isNaN(value) || value <= 0) {
            callback(new Error("折算系数必须是大于 0 的数字"));
          if (isNaN(value) || value < 0) {
            callback(new Error("小提琴系数必须是非负数字"));
          } else {
            callback();
          }
        },
      },
    ],
    violaFactor: [
      {
        required: false,
        message: "请输入大提琴系数",
        trigger: "blur",
        validator: (rule, value, callback) => {
          if (value == null || value === "") {
            callback();
            return;
          }
          if (isNaN(value) || value < 0) {
            callback(new Error("大提琴系数必须是非负数字"));
          } else {
            callback();
          }
        },
      },
    ],
    bassFactor: [
      {
        required: false,
        message: "请输入贝斯系数",
        trigger: "blur",
        validator: (rule, value, callback) => {
          if (value == null || value === "") {
            callback();
            return;
          }
          if (isNaN(value) || value < 0) {
            callback(new Error("贝斯系数必须是非负数字"));
          } else {
            callback();
          }
@@ -621,9 +652,10 @@
    processForm.id = null;
    processForm.no = "";
    processForm.name = "";
    processForm.salaryQuota = null;
    processForm.standardQuantity = null;
    processForm.conversionFactor = 1;
    processForm.violinFactor = null;
    processForm.violaFactor = null;
    processForm.bassFactor = null;
    processForm.isQuality = false;
    processForm.isProduction = false;
    processForm.remark = "";
@@ -637,9 +669,10 @@
    processForm.id = process.id;
    processForm.no = process.no;
    processForm.name = process.name;
    processForm.salaryQuota = process.salaryQuota;
    processForm.standardQuantity = process.standardQuantity;
    processForm.conversionFactor = process.conversionFactor ?? 1;
    processForm.violinFactor = process.violinFactor;
    processForm.violaFactor = process.violaFactor;
    processForm.bassFactor = process.bassFactor;
    processForm.isQuality = !!process.isQuality;
    processForm.isProduction = !!process.isProduction;
    processForm.remark = process.remark || "";