huminmin
4 天以前 70a0b21595941890a9d26cfde01ff17e3427214a
修改工资计算
已修改17个文件
661 ■■■■■ 文件已修改
doc/sql/天津玮苓乐器.sql 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/tools/FinancialAgentTools.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionAccountMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/StaffSalaryMainController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/CalculateSalaryDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/StaffSalaryCalculateDto.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/excel/StaffSalaryExportData.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffOnJob.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffSalaryDetail.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/StaffSalaryMainService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java 203 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java 318 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/pojo/TechnologyOperation.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionAccountMapper.xml 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/StaffOnJobMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyOperationMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/sql/Ìì½òçâÜßÀÖÆ÷.sql
@@ -13,3 +13,17 @@
ALTER TABLE `quality_unqualified`
    ADD COLUMN `photo_urls` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '不合格图片' AFTER `deal_result`;
ALTER TABLE staff_on_job
    ADD COLUMN attendance_bonus DECIMAL(10,2) NOT NULL DEFAULT 100.00 COMMENT '全勤奖金' AFTER basic_salary;
ALTER TABLE staff_salary_detail
    ADD COLUMN attendance_bonus DECIMAL(10,2) NOT NULL DEFAULT 100.00 COMMENT '全勤奖金' AFTER basic_salary,
ADD COLUMN month_work_days INT NULL DEFAULT NULL COMMENT '月工作日天数' AFTER hourly_salary,
ADD COLUMN standard_hours DECIMAL(10,2) NULL DEFAULT NULL COMMENT '月标准工时' AFTER month_work_days,
ADD COLUMN month_time_total DECIMAL(10,2) NULL DEFAULT NULL COMMENT '月时间合计' AFTER standard_hours,
ADD COLUMN conversion_ratio DECIMAL(10,2) NULL DEFAULT NULL COMMENT '折合倍数' AFTER month_time_total,
ADD COLUMN converted_work_hours DECIMAL(10,2) NULL DEFAULT NULL COMMENT '工序折算工时' AFTER conversion_ratio;
ALTER TABLE technology_operation
    ADD COLUMN standard_quantity DECIMAL(18,2) NULL DEFAULT NULL COMMENT '标准数量' AFTER salary_quota,
ADD COLUMN conversion_factor DECIMAL(10,2) NOT NULL DEFAULT 1.00 COMMENT '工序折算系数' AFTER standard_quantity;
src/main/java/com/ruoyi/ai/tools/FinancialAgentTools.java
@@ -865,11 +865,8 @@
        }
        List<ProductionAccount> accountList = defaultList(productionAccountMapper.selectList(accountWrapper));
        Map<String, BigDecimal> salaryQuotaByOperation = defaultList(technologyOperationMapper.selectList(new LambdaQueryWrapper<TechnologyOperation>()
                        .select(TechnologyOperation::getName, TechnologyOperation::getSalaryQuota)))
                .stream()
                .filter(item -> StringUtils.hasText(item.getName()))
                .collect(Collectors.toMap(TechnologyOperation::getName, item -> defaultDecimal(item.getSalaryQuota()), (a, b) -> a));
        // å·¥èµ„定额已取消在薪资相关计算中的依赖,暂不再按工序工资定额估算人工成本。
        Map<String, BigDecimal> salaryQuotaByOperation = new HashMap<>();
        Map<Long, BigDecimal> laborCostByLedger = new HashMap<>();
        Map<String, BigDecimal> processCostMap = new HashMap<>();
@@ -1994,15 +1991,15 @@
    }
    private BigDecimal estimateLaborCost(ProductionAccount account, Map<String, BigDecimal> salaryQuotaByOperation) {
        BigDecimal salaryQuota = salaryQuotaByOperation.getOrDefault(safe(account.getTechnologyOperationName()), BigDecimal.ZERO);
        // BigDecimal salaryQuota = salaryQuotaByOperation.getOrDefault(safe(account.getTechnologyOperationName()), BigDecimal.ZERO);
        BigDecimal finishedNum = defaultDecimal(account.getFinishedNum());
        BigDecimal workHours = defaultDecimal(account.getWorkHours());
        if (salaryQuota.compareTo(BigDecimal.ZERO) > 0 && finishedNum.compareTo(BigDecimal.ZERO) > 0) {
            return finishedNum.multiply(salaryQuota);
        }
        if (salaryQuota.compareTo(BigDecimal.ZERO) > 0 && workHours.compareTo(BigDecimal.ZERO) > 0) {
            return workHours.multiply(salaryQuota);
        }
//        if (salaryQuota.compareTo(BigDecimal.ZERO) > 0 && finishedNum.compareTo(BigDecimal.ZERO) > 0) {
//            return finishedNum.multiply(salaryQuota);
//        }
//        if (salaryQuota.compareTo(BigDecimal.ZERO) > 0 && workHours.compareTo(BigDecimal.ZERO) > 0) {
//            return workHours.multiply(salaryQuota);
//        }
        if (workHours.compareTo(BigDecimal.ZERO) > 0) {
            return workHours;
        }
src/main/java/com/ruoyi/production/mapper/ProductionAccountMapper.java
@@ -27,6 +27,8 @@
    UserAccountDto selectUserAccount(@Param("userId") Long userId, @Param("date") String date);
    List<Map<String, Object>> selectMonthSalaryCalcItems(@Param("userId") Long userId, @Param("date") String date);
    List<Map<String, Object>> selectDailyWagesStats(@Param("startDate") String startDate, @Param("endDate") String endDate);
}
src/main/java/com/ruoyi/staff/controller/StaffSalaryMainController.java
@@ -81,6 +81,12 @@
        staffSalaryMainService.exportSalaryDetail(response, salaryMonth);
    }
    @PostMapping("/exportWorkHours")
    @Operation(summary = "导出工时表")
    public void exportWorkHours(HttpServletResponse response, String salaryMonth) {
        staffSalaryMainService.exportWorkHours(response, salaryMonth);
    }
    @GetMapping("/availableDepts")
    @Operation(summary = "获取可选部门(排除已发放工资表的部门)")
    public AjaxResult getAvailableDepts(String salaryMonth) {
src/main/java/com/ruoyi/staff/dto/CalculateSalaryDto.java
@@ -1,5 +1,6 @@
package com.ruoyi.staff.dto;
import java.math.BigDecimal;
import lombok.Data;
import java.util.List;
@@ -15,4 +16,6 @@
    private String date;
    private BigDecimal deductAmount;
}
src/main/java/com/ruoyi/staff/dto/StaffSalaryCalculateDto.java
@@ -13,4 +13,19 @@
     * å·¥èµ„月份,格式 yyyy-MM
     */
    private String salaryMonth;
    /**
     * å·¥åºæ ‡å‡†æ•°é‡
     */
    private java.math.BigDecimal standardQuantity;
    /**
     * å·¥åºæŠ˜ç®—系数
     */
    private java.math.BigDecimal conversionFactor;
    /**
     * å½“月实际产量
     */
    private java.math.BigDecimal actualOutput;
}
src/main/java/com/ruoyi/staff/excel/StaffSalaryExportData.java
@@ -26,12 +26,30 @@
    @Excel(name = "基本工资")
    private BigDecimal basicSalary;
    @Excel(name = "全勤奖金")
    private BigDecimal attendanceBonus;
    @Excel(name = "计件工资")
    private BigDecimal pieceSalary;
    @Excel(name = "计时工资")
    private BigDecimal hourlySalary;
    @Excel(name = "月工作日天数")
    private Integer monthWorkDays;
    @Excel(name = "月标准工时")
    private BigDecimal standardHours;
    @Excel(name = "月时间合计")
    private BigDecimal monthTimeTotal;
    @Excel(name = "折合倍数")
    private BigDecimal conversionRatio;
    @Excel(name = "工序折算工时")
    private BigDecimal convertedWorkHours;
    @Excel(name = "其他收入")
    private BigDecimal otherIncome;
src/main/java/com/ruoyi/staff/pojo/StaffOnJob.java
@@ -234,6 +234,12 @@
    @Schema(description = "基本工资")
    private BigDecimal basicSalary;
    /**
     * å…¨å‹¤å¥–金
     */
    @Excel(name = "全勤奖金", sort = 24)
    private BigDecimal attendanceBonus;
    @Schema(description = "员工教育经历集合")
    @TableField(exist = false)
    private List<StaffEducation> staffEducationList;
src/main/java/com/ruoyi/staff/pojo/StaffSalaryDetail.java
@@ -50,12 +50,30 @@
    @Schema(description = "基本工资")
    private BigDecimal basicSalary;
    @Schema(description = "全勤奖金")
    private BigDecimal attendanceBonus;
    @Schema(description = "计件工资")
    private BigDecimal pieceSalary;
    @Schema(description = "计时工资")
    private BigDecimal hourlySalary;
    @Schema(description = "月工作日天数")
    private Integer monthWorkDays;
    @Schema(description = "月标准工时")
    private BigDecimal standardHours;
    @Schema(description = "月时间合计")
    private BigDecimal monthTimeTotal;
    @Schema(description = "折合倍数")
    private BigDecimal conversionRatio;
    @Schema(description = "工序折算工时")
    private BigDecimal convertedWorkHours;
    @Schema(description = "其他收入")
    private BigDecimal otherIncome;
src/main/java/com/ruoyi/staff/service/StaffSalaryMainService.java
@@ -34,5 +34,7 @@
    void exportSalaryDetail(HttpServletResponse response, String salaryMonth);
    void exportWorkHours(HttpServletResponse response, String salaryMonth);
    AjaxResult getAvailableDepts(String salaryMonth);
}
src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java
@@ -7,6 +7,7 @@
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.production.bean.dto.UserAccountDto;
import com.ruoyi.production.bean.dto.UserProductionAccountingDto;
import com.ruoyi.production.mapper.ProductionProductMainMapper;
import com.ruoyi.production.service.SalesLedgerProductionAccountingService;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.domain.SysUser;
@@ -26,11 +27,14 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.DayOfWeek;
import java.time.YearMonth;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.time.LocalDate;
/**
 * <p>
@@ -50,6 +54,7 @@
    private final SysDeptMapper sysDeptMapper;
    private final StaffOnJobMapper staffOnJobMapper;
    private final SalesLedgerProductionAccountingService salesLedgerProductionAccountingService;
    private final ProductionProductMainMapper productionProductMainMapper;
    @Override
@@ -162,27 +167,34 @@
        if(staffOnJobDto == null){
            return;
        }
        BigDecimal basicSalary = hasValue(map, "basicSalary")
                ? normalizeMoney(getBigDecimal(map, "basicSalary"))
                : normalizeMoney(staffOnJobDto.getBasicSalary());
        BigDecimal basicSalary = resolveBasicSalary(map, staffOnJobDto);
        BigDecimal attendanceBonus = resolveAttendanceBonus(staffOnJobDto);
        BigDecimal otherIncome = normalizeMoney(getBigDecimal(map, "otherIncome"));
        BigDecimal otherDeduct = normalizeMoney(getBigDecimal(map, "otherDeduct"));
        BigDecimal socialSupplementAmount = hasValue(map, "socialSupplementAmount")
                ? normalizeMoney(getBigDecimal(map, "socialSupplementAmount"))
                : calculateLatestDeptSocialSupplementAmount(staffOnJobDto);
        BigDecimal pieceSalary = normalizeMoney(getBigDecimal(map, "pieceSalary"));
        BigDecimal hourlySalary = normalizeMoney(getBigDecimal(map, "hourlySalary"));
        BigDecimal socialSupplementAmount = resolveSocialSupplementAmount(map, staffOnJobDto);
        BigDecimal socialPersonal = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        BigDecimal fundPersonal = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        int monthWorkDays = calculateWorkDays(date);
        BigDecimal standardHours = calculateStandardHours(monthWorkDays);
        BigDecimal actualOutput = resolveActualOutput(map);
        BigDecimal standardQuantity = resolveStandardQuantity(map);
        BigDecimal conversionFactor = resolveConversionFactor(map);
        Long sysUserId = getUidByStaffId(staffId);
        UserProductionAccountingDto userProductionAccountingDto = new UserProductionAccountingDto();
        userProductionAccountingDto.setUserId(getUidByStaffId(staffId));
        userProductionAccountingDto.setUserId(sysUserId);
        userProductionAccountingDto.setDate(date);
        UserAccountDto byUserId = salesLedgerProductionAccountingService.getByUserId(userProductionAccountingDto);
        if(byUserId != null){
            pieceSalary = normalizeMoney(byUserId.getAccountBalance());
            hourlySalary = normalizeMoney(byUserId.getAccount());
        }
        BigDecimal repairWorkHour = resolveRepairWorkHour(sysUserId, date);
        BigDecimal convertedWorkHours = resolveConvertedWorkHours(
                map,
                actualOutput,
                standardQuantity,
                conversionFactor,
                repairWorkHour,
                byUserId
        );
        BigDecimal conversionRatio = calculateConversionRatio(standardHours, convertedWorkHours);
        // æŸ¥è¯¢è¯¥äººå‘˜å¯¹åº”的社保方案
        List<SchemeApplicableStaff> schemeList = schemeApplicableStaffMapper.selectSchemeByStaffId(staffId);
@@ -202,7 +214,8 @@
            }
        }
        BigDecimal grossSalary = basicSalary.add(pieceSalary).add(hourlySalary).add(otherIncome);
        // å·¥èµ„ = å…¨å‹¤å¥–金 + åŸºæœ¬å·¥èµ„ * æŠ˜åˆå€æ•°
        BigDecimal grossSalary = attendanceBonus.add(basicSalary.multiply(conversionRatio)).add(otherIncome);
        BigDecimal salaryTax = TaxCalculator.calculateMonthlyTax(
                grossSalary,
                socialPersonal.add(socialSupplementAmount),
@@ -222,8 +235,17 @@
            map.put("nation", staffOnJobDto.getNation());
        }
        map.put("basicSalary", basicSalary);
        map.put("pieceSalary", pieceSalary);
        map.put("hourlySalary", hourlySalary);
        map.put("attendanceBonus", attendanceBonus);
        map.put("monthWorkDays", monthWorkDays);
        map.put("standardHours", standardHours);
        map.put("actualOutput", actualOutput);
        map.put("standardQuantity", standardQuantity);
        map.put("conversionFactor", conversionFactor);
        map.put("convertedWorkHours", convertedWorkHours);
        map.put("monthTimeTotal", convertedWorkHours);
        map.put("conversionRatio", conversionRatio);
        map.put("pieceSalary", convertedWorkHours);
        map.put("hourlySalary", BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
        map.put("otherIncome", otherIncome);
        map.put("socialPersonal", socialPersonal);
        map.put("fundPersonal", fundPersonal);
@@ -233,6 +255,140 @@
        map.put("salaryTax", salaryTax);
        map.put("deductSalary", deductSalary);
        map.put("netSalary", netSalary);
    }
    /**
     * è§£æžå‘˜å·¥åŸºç¡€å·¥èµ„。
     * ä¼˜å…ˆä½¿ç”¨å·¥èµ„表里本次传入的值,未传时回落到员工档案中的基础工资。
     */
    private BigDecimal resolveBasicSalary(Map<String, Object> map, StaffOnJob staffOnJobDto) {
        // å‘˜å·¥åŸºç¡€å·¥èµ„优先取工资表传入值,没有则回落到员工档案中的基础工资。
        return hasValue(map, "basicSalary")
                ? normalizeMoney(getBigDecimal(map, "basicSalary"))
                : normalizeMoney(staffOnJobDto.getBasicSalary());
    }
    /**
     * èŽ·å–å‘˜å·¥æœ€æ–°å…¨å‹¤å¥–é‡‘ã€‚
     * å…¨å‹¤å¥–金统一以员工档案配置为准。
     */
    private BigDecimal resolveAttendanceBonus(StaffOnJob staffOnJobDto) {
        // å…¨å‹¤å¥–金直接取员工档案里的最新配置。
        return staffOnJobDto.getAttendanceBonus() == null
                ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)
                : normalizeMoney(staffOnJobDto.getAttendanceBonus());
    }
    /**
     * è§£æžç¤¾ä¿è¡¥ç¼´é‡‘额。
     * å¦‚果工资表传入了补缴金额则直接使用,否则按员工所属部门最新方案计算。
     */
    private BigDecimal resolveSocialSupplementAmount(Map<String, Object> map, StaffOnJob staffOnJobDto) {
        return hasValue(map, "socialSupplementAmount")
                ? normalizeMoney(getBigDecimal(map, "socialSupplementAmount"))
                : calculateLatestDeptSocialSupplementAmount(staffOnJobDto);
    }
    /**
     * è®¡ç®—月标准工时。
     * è§„则:工作日天数 * 8 å°æ—¶ã€‚
     */
    private BigDecimal calculateStandardHours(int monthWorkDays) {
        // æœˆæ ‡å‡†å·¥æ—¶ = å·¥ä½œæ—¥å¤©æ•° * 8 å°æ—¶ã€‚
        return BigDecimal.valueOf(monthWorkDays)
                .multiply(new BigDecimal("8"))
                .setScale(2, RoundingMode.HALF_UP);
    }
    /**
     * è§£æžå½“月实际产量。
     */
    private BigDecimal resolveActualOutput(Map<String, Object> map) {
        return hasValue(map, "actualOutput")
                ? normalizeMoney(getBigDecimal(map, "actualOutput"))
                : BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
    }
    /**
     * è§£æžå·¥åºæ ‡å‡†æ•°é‡ã€‚
     */
    private BigDecimal resolveStandardQuantity(Map<String, Object> map) {
        return hasValue(map, "standardQuantity")
                ? normalizeMoney(getBigDecimal(map, "standardQuantity"))
                : BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
    }
    /**
     * è§£æžå·¥åºæŠ˜ç®—系数,未配置时默认 1。
     */
    private BigDecimal resolveConversionFactor(Map<String, Object> map) {
        return hasValue(map, "conversionFactor")
                ? normalizeMoney(getBigDecimal(map, "conversionFactor"))
                : BigDecimal.ONE.setScale(2, RoundingMode.HALF_UP);
    }
    /**
     * æ±‡æ€»è½»å¾®è¿”工的补修理工时。
     * æŒ‰ç³»ç»Ÿç”¨æˆ· ID å’Œå·¥èµ„月份统计,并作为月时间合计的一部分。
     */
    private BigDecimal resolveRepairWorkHour(Long sysUserId, String date) {
        if (sysUserId == null || sysUserId <= 0 || StringUtils.isEmpty(date)) {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        LocalDate monthStart = YearMonth.parse(date).atDay(1);
        LocalDate monthEnd = YearMonth.parse(date).atEndOfMonth();
        return productionProductMainMapper.selectList(new LambdaQueryWrapper<com.ruoyi.production.pojo.ProductionProductMain>()
                        .eq(com.ruoyi.production.pojo.ProductionProductMain::getUserId, sysUserId)
                        .eq(com.ruoyi.production.pojo.ProductionProductMain::getReportType, 1)
                        .ge(com.ruoyi.production.pojo.ProductionProductMain::getCreateTime, monthStart.atStartOfDay())
                        .le(com.ruoyi.production.pojo.ProductionProductMain::getCreateTime, monthEnd.atTime(23, 59, 59)))
                .stream()
                .map(item -> normalizeMoney(item.getRepairWorkHour()))
                .reduce(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP), BigDecimal::add)
                .setScale(2, RoundingMode.HALF_UP);
    }
    /**
     * è®¡ç®—月时间合计。
     * è®¡ä»¶åœºæ™¯ä½¿ç”¨å®žé™…产量、标准数量和折算系数换算;
     * æ²¡æœ‰è®¡ä»¶æ•°æ®æ—¶ï¼Œå›žè½åˆ°ç”Ÿäº§æ ¸ç®—汇总值;
     * æœ€åŽå†å åŠ è¿”å·¥è¡¥ä¿®ç†å·¥æ—¶ã€‚
     */
    private BigDecimal resolveConvertedWorkHours(Map<String, Object> map,
                                                 BigDecimal actualOutput,
                                                 BigDecimal standardQuantity,
                                                 BigDecimal conversionFactor,
                                                 BigDecimal repairWorkHour,
                                                 UserAccountDto byUserId) {
        if (hasValue(map, "monthTimeTotal")) {
            BigDecimal monthTimeTotal = normalizeMoney(getBigDecimal(map, "monthTimeTotal"));
            if (monthTimeTotal.compareTo(BigDecimal.ZERO) > 0) {
                return monthTimeTotal;
            }
        }
        BigDecimal convertedWorkHours = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        // è®¡ä»¶æŠ˜ç®—工时 = å½“月实际产量 / å·¥åºæ ‡å‡†æ•°é‡ * 8 * å·¥åºæŠ˜ç®—系数
        if (actualOutput.compareTo(BigDecimal.ZERO) > 0 && standardQuantity.compareTo(BigDecimal.ZERO) > 0) {
            convertedWorkHours = actualOutput
                    .divide(standardQuantity, 8, RoundingMode.HALF_UP)
                    .multiply(new BigDecimal("8"))
                    .multiply(conversionFactor)
                    .setScale(2, RoundingMode.HALF_UP);
        } else if (byUserId != null) {
            convertedWorkHours = normalizeMoney(byUserId.getAccountBalance().add(byUserId.getAccount()));
        }
        return convertedWorkHours.add(repairWorkHour).setScale(2, RoundingMode.HALF_UP);
    }
    /**
     * è®¡ç®—折合倍数。
     * è§„则:月时间合计 / æœˆæ ‡å‡†å·¥æ—¶ã€‚
     */
    private BigDecimal calculateConversionRatio(BigDecimal standardHours, BigDecimal convertedWorkHours) {
        return standardHours.compareTo(BigDecimal.ZERO) == 0
                ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)
                : convertedWorkHours.divide(standardHours, 8, RoundingMode.HALF_UP)
                .setScale(2, RoundingMode.HALF_UP);
    }
    /**
@@ -340,4 +496,19 @@
    private BigDecimal normalizeMoney(BigDecimal value) {
        return value == null ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP) : value.setScale(2, RoundingMode.HALF_UP);
    }
    private int calculateWorkDays(String date) {
        if (StringUtils.isEmpty(date)) {
            return 0;
        }
        YearMonth yearMonth = YearMonth.parse(date);
        int workDays = 0;
        for (int day = 1; day <= yearMonth.lengthOfMonth(); day++) {
            DayOfWeek dayOfWeek = yearMonth.atDay(day).getDayOfWeek();
            if (dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY) {
                workDays++;
            }
        }
        return workDays;
    }
}
src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java
@@ -9,6 +9,10 @@
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.mapper.SysUserDeptMapper;
import com.ruoyi.production.mapper.ProductionAccountMapper;
import com.ruoyi.production.pojo.ProductionAccount;
import com.ruoyi.technology.mapper.TechnologyOperationMapper;
import com.ruoyi.technology.pojo.TechnologyOperation;
import com.ruoyi.staff.excel.StaffSalaryExportData;
import jakarta.servlet.http.HttpServletResponse;
import com.ruoyi.staff.dto.CalculateSalaryDto;
@@ -25,6 +29,7 @@
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -32,6 +37,15 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.stream.Collectors;
import java.time.LocalDate;
import java.time.YearMonth;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.IOException;
/**
 * <p>
@@ -52,6 +66,8 @@
    private final SysUserDeptMapper sysUserDeptMapper;
    private final StaffLeaveMapper staffLeaveMapper;
    private final SysDeptMapper sysDeptMapper;
    private final ProductionAccountMapper productionAccountMapper;
    private final TechnologyOperationMapper technologyOperationMapper;
    @Override
    public AjaxResult listPage(Page page, StaffSalaryMain staffSalaryMain) {
@@ -169,22 +185,31 @@
        if(CollectionUtils.isEmpty(calculateSalaryDto.getIds())){
            return AjaxResult.error("参数错误");
        }
        java.math.BigDecimal deductAmount = calculateSalaryDto.getDeductAmount() == null
                ? java.math.BigDecimal.ZERO
                : calculateSalaryDto.getDeductAmount();
        List<Map<String, Object>> longs = setSchemeApplicableStaffUserInfo(calculateSalaryDto.getIds()); // é€šè¿‡éƒ¨é—¨ids获取用户信息
        if(CollectionUtils.isEmpty(longs)){
            return AjaxResult.error("无员工");
        }
        List<Map<String, Object>> mapList = new ArrayList<>();
        for (Map<String, Object> id : longs) {
            Long staffOnJobId = id.get("id") == null ? null : ((Number) id.get("id")).longValue();
            // åˆ¤æ–­å‘˜å·¥æ˜¯å¦ä¸ºå½“月离职
            if((Integer) id.get("staffState") == 0){
                StaffLeave id1 = staffLeaveMapper.selectOne(new LambdaQueryWrapper<StaffLeave>()
                        .eq(StaffLeave::getStaffOnJobId, id.get("id"))
                        .eq(StaffLeave::getStaffOnJobId, staffOnJobId)
                        .like(StaffLeave::getLeaveDate, calculateSalaryDto.getDate()));
                if(id1 == null){
                    continue;
                }
            }
            schemeApplicableStaffService.calculateByEmployeeId((Integer) id.get("id"),id,calculateSalaryDto.getDate());
            id.put("otherDeduct", deductAmount);
            Long sysUserId = schemeApplicableStaffService.getUidByStaffId(staffOnJobId);
            BigDecimal monthTimeTotal = resolveMonthTimeTotal(sysUserId, calculateSalaryDto.getDate());
            id.put("monthTimeTotal", monthTimeTotal);
            id.put("convertedWorkHours", monthTimeTotal);
            schemeApplicableStaffService.calculateByEmployeeId(staffOnJobId == null ? null : staffOnJobId.intValue(), id, calculateSalaryDto.getDate());
            mapList.add(id);
        }
        return AjaxResult.success(mapList);
@@ -207,8 +232,17 @@
        if (staffSalaryCalculateDto.getBasicSalary() != null) {
            map.put("basicSalary", staffSalaryCalculateDto.getBasicSalary());
        }
        map.put("attendanceBonus", staffSalaryCalculateDto.getAttendanceBonus());
        map.put("pieceSalary", staffSalaryCalculateDto.getPieceSalary());
        map.put("hourlySalary", staffSalaryCalculateDto.getHourlySalary());
        map.put("monthWorkDays", staffSalaryCalculateDto.getMonthWorkDays());
        map.put("standardHours", staffSalaryCalculateDto.getStandardHours());
        map.put("monthTimeTotal", staffSalaryCalculateDto.getMonthTimeTotal());
        map.put("conversionRatio", staffSalaryCalculateDto.getConversionRatio());
        map.put("convertedWorkHours", staffSalaryCalculateDto.getConvertedWorkHours());
        map.put("actualOutput", staffSalaryCalculateDto.getActualOutput());
        map.put("standardQuantity", staffSalaryCalculateDto.getStandardQuantity());
        map.put("conversionFactor", staffSalaryCalculateDto.getConversionFactor());
        map.put("otherIncome", staffSalaryCalculateDto.getOtherIncome());
        map.put("socialPersonal", staffSalaryCalculateDto.getSocialPersonal());
        map.put("fundPersonal", staffSalaryCalculateDto.getFundPersonal());
@@ -248,8 +282,14 @@
                exportData.setStaffName(detail.getStaffName());
                exportData.setDeptName(detail.getDeptName());
                exportData.setBasicSalary(detail.getBasicSalary());
                exportData.setAttendanceBonus(detail.getAttendanceBonus());
                exportData.setPieceSalary(detail.getPieceSalary());
                exportData.setHourlySalary(detail.getHourlySalary());
                exportData.setMonthWorkDays(detail.getMonthWorkDays());
                exportData.setStandardHours(detail.getStandardHours());
                exportData.setMonthTimeTotal(detail.getMonthTimeTotal());
                exportData.setConversionRatio(detail.getConversionRatio());
                exportData.setConvertedWorkHours(detail.getConvertedWorkHours());
                exportData.setOtherIncome(detail.getOtherIncome());
                exportData.setSocialPersonal(detail.getSocialPersonal());
                exportData.setFundPersonal(detail.getFundPersonal());
@@ -266,6 +306,156 @@
        ExcelUtil<StaffSalaryExportData> util = new ExcelUtil<>(StaffSalaryExportData.class);
        util.exportExcel(response, exportDataList, "员工薪资明细");
    }
    /**
     * æ±‡æ€»å‘˜å·¥å½“月月时间合计。
     * è®¡æ—¶å·¥åºç›´æŽ¥å–工时,计件工序按产量折算后再叠加补修理工时。
     */
    private java.math.BigDecimal resolveMonthTimeTotal(Long sysUserId, String date) {
        if (sysUserId == null || sysUserId <= 0 || StringUtils.isEmpty(date)) {
            return java.math.BigDecimal.ZERO.setScale(2, java.math.RoundingMode.HALF_UP);
        }
        java.math.BigDecimal total = java.math.BigDecimal.ZERO.setScale(2, java.math.RoundingMode.HALF_UP);
        java.util.List<java.util.Map<String, Object>> items = productionAccountMapper.selectMonthSalaryCalcItems(sysUserId, date);
        for (java.util.Map<String, Object> item : items) {
            Integer operationType = item.get("operationType") == null ? null : ((Number) item.get("operationType")).intValue();
            java.math.BigDecimal finishedNum = toDecimal(item.get("finishedNum"));
            java.math.BigDecimal workHour = toDecimal(item.get("workHour"));
            java.math.BigDecimal repairWorkHour = toDecimal(item.get("repairWorkHour"));
            java.math.BigDecimal standardQuantity = toDecimal(item.get("standardQuantity"));
            java.math.BigDecimal conversionFactor = toDecimal(item.get("conversionFactor"));
            java.math.BigDecimal rowTotal;
            if (Integer.valueOf(0).equals(operationType)) {
                rowTotal = workHour.add(repairWorkHour);
            } else {
                if (standardQuantity.compareTo(java.math.BigDecimal.ZERO) <= 0) {
                    rowTotal = java.math.BigDecimal.ZERO.setScale(2, java.math.RoundingMode.HALF_UP);
                } else {
                    rowTotal = finishedNum
                            .divide(standardQuantity, 8, java.math.RoundingMode.HALF_UP)
                            .multiply(new java.math.BigDecimal("8"))
                            .multiply(conversionFactor)
                            .add(repairWorkHour)
                            .setScale(2, java.math.RoundingMode.HALF_UP);
                }
            }
            total = total.add(rowTotal).setScale(2, java.math.RoundingMode.HALF_UP);
        }
        return total;
    }
    private java.math.BigDecimal toDecimal(Object value) {
        if (value == null) {
            return java.math.BigDecimal.ZERO.setScale(2, java.math.RoundingMode.HALF_UP);
        }
        if (value instanceof java.math.BigDecimal) {
            return ((java.math.BigDecimal) value).setScale(2, java.math.RoundingMode.HALF_UP);
        }
        if (value instanceof Number) {
            return new java.math.BigDecimal(String.valueOf(value)).setScale(2, java.math.RoundingMode.HALF_UP);
        }
        String text = String.valueOf(value).trim();
        if (text.isEmpty()) {
            return java.math.BigDecimal.ZERO.setScale(2, java.math.RoundingMode.HALF_UP);
        }
        return new java.math.BigDecimal(text).setScale(2, java.math.RoundingMode.HALF_UP);
    }
    @Override
    public void exportWorkHours(HttpServletResponse response, String salaryMonth) {
        List<ProductionAccount> accountList = queryProductionAccounts(salaryMonth);
        List<String> operationNames = accountList.stream()
                .map(ProductionAccount::getTechnologyOperationName)
                .filter(StringUtils::isNotEmpty)
                .distinct()
                .sorted()
                .collect(Collectors.toList());
        Workbook workbook = new XSSFWorkbook();
        CellStyle titleStyle = createStyle(workbook, true, HorizontalAlignment.CENTER, 14, false);
        CellStyle headerStyle = createStyle(workbook, false, HorizontalAlignment.CENTER, 11, false);
        CellStyle labelStyle = createStyle(workbook, false, HorizontalAlignment.CENTER, 10, false);
        CellStyle numStyle = createStyle(workbook, false, HorizontalAlignment.CENTER, 10, false);
        DataFormat dataFormat = workbook.createDataFormat();
        numStyle.setDataFormat(dataFormat.getFormat("0.00"));
        if (CollectionUtils.isEmpty(operationNames)) {
            operationNames = new ArrayList<>();
        }
        int sheetIndex = 1;
        for (String operationName : operationNames) {
            TechnologyOperation operation = technologyOperationMapper.selectOne(
                    new LambdaQueryWrapper<TechnologyOperation>().eq(TechnologyOperation::getName, operationName).last("limit 1"));
            String sheetName = operationName;
            if (sheetName.length() > 31) {
                sheetName = sheetName.substring(0, 31);
            }
            Sheet sheet = workbook.createSheet(sheetName);
            int rowIdx = 0;
            Row titleRow = sheet.createRow(rowIdx++);
            titleRow.setHeightInPoints(52.5f);
            titleRow.createCell(0).setCellValue(operationName + "工时统计");
            titleRow.getCell(0).setCellStyle(titleStyle);
            sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 3));
            Row workDayRow = sheet.createRow(rowIdx++);
            workDayRow.createCell(0).setCellValue("工作日");
            workDayRow.getCell(0).setCellStyle(headerStyle);
            Integer monthWorkDays = calculateWorkDays(salaryMonth);
            createNumberCell(workDayRow, 2, monthWorkDays == null ? null : new java.math.BigDecimal(monthWorkDays), headerStyle);
            workDayRow.createCell(3).setCellValue("天");
            workDayRow.getCell(3).setCellStyle(headerStyle);
            createNumberCell(workDayRow, 4, monthWorkDays == null ? null : new java.math.BigDecimal(monthWorkDays * 8), headerStyle);
            workDayRow.createCell(5).setCellValue("小时");
            workDayRow.getCell(5).setCellStyle(headerStyle);
            Row headRow = sheet.createRow(rowIdx++);
            headRow.createCell(0).setCellValue("人员");
            headRow.getCell(0).setCellStyle(headerStyle);
            headRow.createCell(1).setCellValue("月产量");
            headRow.getCell(1).setCellStyle(headerStyle);
            headRow.createCell(2).setCellValue("折算时长");
            headRow.getCell(2).setCellStyle(headerStyle);
            headRow.createCell(3).setCellValue("工资");
            headRow.getCell(3).setCellStyle(headerStyle);
            Map<String, java.math.BigDecimal> standardMap = buildStandardQuantityMap(List.of(operationName));
            Map<String, java.math.BigDecimal> opQtyMap = buildOperationQuantityMap(accountList, operationName);
            Set<String> employeeNames = accountList.stream()
                    .map(ProductionAccount::getSchedulingUserName)
                    .filter(StringUtils::isNotEmpty)
                    .collect(Collectors.toCollection(LinkedHashSet::new));
            for (String staffName : employeeNames) {
                Row row = sheet.createRow(rowIdx++);
                row.createCell(0).setCellValue(staffName);
                row.getCell(0).setCellStyle(labelStyle);
                java.math.BigDecimal qty = opQtyMap.getOrDefault(staffName, java.math.BigDecimal.ZERO);
                createNumberCell(row, 1, qty, numStyle);
                java.math.BigDecimal standardQty = standardMap.getOrDefault(operationName, java.math.BigDecimal.ONE);
                createNumberCell(row, 2, standardQty.compareTo(java.math.BigDecimal.ZERO) == 0 ? java.math.BigDecimal.ZERO : qty.divide(standardQty, 2, java.math.RoundingMode.HALF_UP).multiply(new java.math.BigDecimal("8")), numStyle);
                java.math.BigDecimal wage = operation == null || operation.getSalaryQuota() == null
                        ? java.math.BigDecimal.ZERO
                        : (Integer.valueOf(1).equals(operation.getType()) ? operation.getSalaryQuota().multiply(qty) : operation.getSalaryQuota());
                createNumberCell(row, 3, wage, numStyle);
            }
            for (int i = 0; i <= 3; i++) {
                sheet.autoSizeColumn(i);
            }
            sheetIndex++;
        }
        try {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("UTF-8");
            String fileName = java.net.URLEncoder.encode("工时表.xlsx", java.nio.charset.StandardCharsets.UTF_8);
            response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName);
            workbook.write(response.getOutputStream());
            workbook.close();
        } catch (IOException e) {
            throw new RuntimeException("导出工时表失败", e);
        }
    }
    public List<Map<String, Object>> setSchemeApplicableStaffUserInfo(List<Long> ids) {
@@ -324,4 +514,128 @@
        return false;
    }
    private String buildWorkHoursTitle(StaffSalaryMain main) {
        String month = StringUtils.isNotEmpty(main.getSalaryMonth()) ? main.getSalaryMonth() : "未知月份";
        return month + "工时统计";
    }
    private Integer calculateWorkDays(String salaryMonth) {
        if (StringUtils.isEmpty(salaryMonth)) {
            return 0;
        }
        java.time.YearMonth yearMonth = java.time.YearMonth.parse(salaryMonth);
        int workDays = 0;
        for (int day = 1; day <= yearMonth.lengthOfMonth(); day++) {
            java.time.DayOfWeek dayOfWeek = yearMonth.atDay(day).getDayOfWeek();
            if (dayOfWeek != java.time.DayOfWeek.SATURDAY && dayOfWeek != java.time.DayOfWeek.SUNDAY) {
                workDays++;
            }
        }
        return workDays;
    }
    private CellStyle createStyle(Workbook workbook, boolean bold, HorizontalAlignment alignment, int fontSize, boolean fill) {
        Font font = workbook.createFont();
        font.setFontName("Microsoft YaHei");
        font.setFontHeightInPoints((short) fontSize);
        font.setBold(bold);
        CellStyle style = workbook.createCellStyle();
        style.setFont(font);
        style.setAlignment(alignment);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        if (fill) {
            style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        }
        return style;
    }
    private void createTextCell(Row row, int colIndex, String value, CellStyle style) {
        Cell cell = row.createCell(colIndex);
        cell.setCellValue(value == null ? "" : value);
        cell.setCellStyle(style);
    }
    private void createNumberCell(Row row, int colIndex, java.math.BigDecimal value, CellStyle style) {
        Cell cell = row.createCell(colIndex);
        if (value != null) {
            cell.setCellValue(value.doubleValue());
        }
        cell.setCellStyle(style);
    }
    private void createFormulaCell(Row row, int colIndex, String formula, CellStyle style) {
        Cell cell = row.createCell(colIndex);
        cell.setCellFormula(formula.startsWith("=") ? formula.substring(1) : formula);
        cell.setCellStyle(style);
    }
    private String toCellRef(int colIndex, int rowNum1Based) {
        return cellRef(colIndex) + rowNum1Based;
    }
    private String cellRef(int colIndex) {
        StringBuilder sb = new StringBuilder();
        int col = colIndex;
        do {
            int rem = col % 26;
            sb.insert(0, (char) ('A' + rem));
            col = col / 26 - 1;
        } while (col >= 0);
        return sb.toString();
    }
    private List<ProductionAccount> queryProductionAccounts(String salaryMonth) {
        if (StringUtils.isEmpty(salaryMonth)) {
            return new ArrayList<>();
        }
        YearMonth ym = YearMonth.parse(salaryMonth);
        LocalDate start = ym.atDay(1);
        LocalDate end = ym.atEndOfMonth();
        return productionAccountMapper.selectList(new LambdaQueryWrapper<ProductionAccount>()
                .ge(ProductionAccount::getSchedulingDate, start.atStartOfDay())
                .le(ProductionAccount::getSchedulingDate, end.atTime(23, 59, 59)));
    }
    private Map<String, java.math.BigDecimal> buildOperationQuantityMap(List<ProductionAccount> accountList, String operationName) {
        Map<String, java.math.BigDecimal> map = new LinkedHashMap<>();
        if (CollectionUtils.isEmpty(accountList) || StringUtils.isEmpty(operationName)) {
            return map;
        }
        accountList.stream()
                .filter(item -> item != null && operationName.equals(item.getTechnologyOperationName()))
                .filter(item -> StringUtils.isNotEmpty(item.getSchedulingUserName()))
                .forEach(item -> map.merge(item.getSchedulingUserName(),
                        item.getFinishedNum() == null ? java.math.BigDecimal.ZERO : item.getFinishedNum(),
                        java.math.BigDecimal::add));
        return map;
    }
    private Map<String, java.math.BigDecimal> buildStandardQuantityMap(List<String> operationNames) {
        Map<String, java.math.BigDecimal> map = new LinkedHashMap<>();
        if (CollectionUtils.isEmpty(operationNames)) {
            return map;
        }
        List<TechnologyOperation> operations = technologyOperationMapper.selectList(
                new LambdaQueryWrapper<TechnologyOperation>()
                        .in(TechnologyOperation::getName, operationNames)
        );
        Map<String, java.math.BigDecimal> opMap = operations.stream()
                .filter(item -> item != null && StringUtils.isNotEmpty(item.getName()))
                .collect(Collectors.toMap(
                        TechnologyOperation::getName,
                        item -> item.getStandardQuantity() == null ? java.math.BigDecimal.ZERO : item.getStandardQuantity(),
                        (left, right) -> left,
                        LinkedHashMap::new
                ));
        for (String operationName : operationNames) {
            map.put(operationName, opMap.getOrDefault(operationName, java.math.BigDecimal.ZERO));
        }
        return map;
    }
}
src/main/java/com/ruoyi/technology/pojo/TechnologyOperation.java
@@ -46,6 +46,12 @@
    @Schema(description = "工资定额")
    private BigDecimal salaryQuota;
    @Schema(description = "工序标准数量")
    private BigDecimal standardQuantity;
    @Schema(description = "工序折算系数")
    private BigDecimal conversionFactor;
    @Schema(description = "是否质检")
    private Boolean isQuality;
src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationServiceImpl.java
@@ -70,6 +70,8 @@
                        .set(TechnologyOperation::getNo, technologyOperation.getNo())
                        .set(TechnologyOperation::getRemark, technologyOperation.getRemark())
                        .set(TechnologyOperation::getSalaryQuota, technologyOperation.getSalaryQuota())
                        .set(TechnologyOperation::getStandardQuantity, technologyOperation.getStandardQuantity())
                        .set(TechnologyOperation::getConversionFactor, technologyOperation.getConversionFactor())
                        .set(TechnologyOperation::getIsQuality, technologyOperation.getIsQuality())
                        .set(TechnologyOperation::getIsProduction, technologyOperation.getIsProduction())
                        .set(TechnologyOperation::getType, technologyOperation.getType())
src/main/resources/mapper/production/ProductionAccountMapper.xml
@@ -172,6 +172,30 @@
          )
    </select>
    <select id="selectMonthSalaryCalcItems" resultType="java.util.Map">
        select
            poro.type as operationType,
            ifnull(pa.finished_num, 0) as finishedNum,
            ifnull(ppm.work_hour, 0) as workHour,
            ifnull(ppm.repair_work_hour, 0) as repairWorkHour,
            ifnull(too.standard_quantity, 0) as standardQuantity,
            ifnull(too.conversion_factor, 1) as conversionFactor
        from production_account pa
                 left join production_product_main ppm on ppm.id = pa.production_product_main_id
                 left join production_operation_task pot on ppm.production_operation_task_id = pot.id
                 left join production_order_routing_operation poro on pot.production_order_routing_operation_id = poro.id
                 left join technology_operation too on poro.technology_operation_id = too.id
        where pa.scheduling_user_id = #{userId}
          and date_format(pa.scheduling_date, '%Y-%m') = #{date}
          and (
              pa.production_product_main_id is null
              or (
                  ppm.audit_status = 1
                  and ppm.report_type in (0, 1)
              )
          )
    </select>
    <select id="selectDailyWagesStats" resultType="java.util.Map">
        select date_format(scheduling_date, '%m-%d') as dateStr,
               cast(ifnull(sum(finished_num), 0) as decimal(18,2)) as numberOfCompleted,
src/main/resources/mapper/staff/StaffOnJobMapper.xml
@@ -39,6 +39,7 @@
        staff_on_job.pro_term,
        staff_on_job.positive_date,
        staff_on_job.basic_salary,
        staff_on_job.attendance_bonus,
        staff_on_job.dept_id,
        sp.post_name AS post_name,
        sd.dept_name AS dept_name,
src/main/resources/mapper/technology/TechnologyOperationMapper.xml
@@ -11,6 +11,8 @@
        <result column="no" property="no" />
        <result column="remark" property="remark" />
        <result column="salary_quota" property="salaryQuota" />
        <result column="standard_quantity" property="standardQuantity" />
        <result column="conversion_factor" property="conversionFactor" />
        <result column="is_quality" property="isQuality" />
        <result column="type" property="type" />
        <result column="device_ledger_id" property="deviceLedgerId" />