huminmin
2 天以前 3e17b898ed5c4a62fbad777f39c3f89953345ff2
修改工资计算
已修改17个文件
485 ■■■■ 文件已修改
src/main/java/com/ruoyi/basic/dto/ProductTreeDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/Product.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/ProductModel.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/StaffSalaryCalculateDto.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffSalaryDetail.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffSalaryMain.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java 102 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java 231 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/pojo/TechnologyOperation.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/ProductModelMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionAccountMapper.xml 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductMainMapper.xml 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/StaffSalaryDetailMapper.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/StaffSalaryMainMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyOperationMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/ProductTreeDto.java
@@ -2,6 +2,7 @@
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
@@ -9,6 +10,7 @@
    private Long id;
    private Long parentId;
    private String productName;
    private BigDecimal standardQuantity;
    private String label; // 用于树形结构的显示名称
    private List<ProductTreeDto> children;
}
src/main/java/com/ruoyi/basic/pojo/Product.java
@@ -26,6 +26,12 @@
     */
    private String productName;
    /**
     * 产品标准数量
     */
    @Schema(description = "产品标准数量")
    private java.math.BigDecimal standardQuantity;
    @Schema(description = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
src/main/java/com/ruoyi/basic/pojo/ProductModel.java
@@ -31,6 +31,10 @@
    @Excel(name = "产品名称")
    private String productName;
    @TableField(exist = false)
    @Excel(name = "产品标准数量")
    private BigDecimal productStandardQuantity;
    /**
     * 规格型号
     */
src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java
@@ -81,6 +81,9 @@
    @Schema(description = "工序")
    private String process;
    @Schema(description = "工序标准数量")
    private java.math.BigDecimal standardQuantity;
    @Schema(description = "工资定额")
    private BigDecimal workHours;
src/main/java/com/ruoyi/staff/dto/StaffSalaryCalculateDto.java
@@ -20,11 +20,6 @@
    private java.math.BigDecimal standardQuantity;
    /**
     * 工序折算系数
     */
    private java.math.BigDecimal conversionFactor;
    /**
     * 当月实际产量
     */
    private java.math.BigDecimal actualOutput;
src/main/java/com/ruoyi/staff/pojo/StaffSalaryDetail.java
@@ -47,11 +47,33 @@
    @Schema(description = "部门名称")
    private String deptName;
    @Schema(description = "入司日期")
    @TableField(exist = false)
    private String entryDate;
    @Schema(description = "基本工资")
    private BigDecimal basicSalary;
    @Schema(description = "全勤奖金")
    private BigDecimal attendanceBonus;
    @Schema(description = "出勤天数")
    private Integer workDays;
    @Schema(description = "事假")
    private BigDecimal personalLeave;
    @Schema(description = "病假")
    private BigDecimal sickLeave;
    @Schema(description = "丧假")
    private BigDecimal bereavementLeave;
    @Schema(description = "婚假")
    private BigDecimal marriageLeave;
    @Schema(description = "津贴")
    private BigDecimal allowance;
    @Schema(description = "计件工资")
    private BigDecimal pieceSalary;
@@ -59,20 +81,11 @@
    @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/pojo/StaffSalaryMain.java
@@ -53,6 +53,9 @@
    @Schema(description = "工资月份,格式:yyyy-MM")
    private String salaryMonth;
    @Schema(description = "工作日")
    private Integer monthWorkDays;
    @Schema(description = "备注")
    private String remark;
src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java
@@ -158,13 +158,13 @@
     * 通过员工id计算社保方案
     * @param id
     */
    public void calculateByEmployeeId(Integer id,Map<String, Object> map,String date) {
    public void calculateByEmployeeId(Integer id, Map<String, Object> map, String date) {
        if (id == null || map == null) {
            return;
        }
        Long staffId = id.longValue();
        StaffOnJob staffOnJobDto = staffOnJobMapper.selectById(staffId);
        if(staffOnJobDto == null){
        if (staffOnJobDto == null) {
            return;
        }
        BigDecimal basicSalary = resolveBasicSalary(map, staffOnJobDto);
@@ -174,11 +174,12 @@
        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);
        int monthWorkDays = resolveMonthWorkDays(map, date);
        BigDecimal workDays = resolveWorkDays(map, monthWorkDays);
        BigDecimal standardHours = resolveStandardHours(map, monthWorkDays);
        BigDecimal actualOutput = resolveActualOutput(map);
        BigDecimal standardQuantity = resolveStandardQuantity(map);
        BigDecimal conversionFactor = resolveConversionFactor(map);
        BigDecimal dailySalary = calculateDailySalary(basicSalary, workDays);
        Long sysUserId = getUidByStaffId(staffId);
        UserProductionAccountingDto userProductionAccountingDto = new UserProductionAccountingDto();
@@ -190,7 +191,6 @@
                map,
                actualOutput,
                standardQuantity,
                conversionFactor,
                repairWorkHour,
                byUserId
        );
@@ -201,21 +201,24 @@
        if (!CollectionUtils.isEmpty(schemeList)) {
            for (SchemeApplicableStaff scheme : schemeList) {
                List<SchemeInsuranceDetail> detailList = schemeApplicableStaffMapper.selectDetailBySchemeId(scheme.getId());
                if(CollectionUtils.isEmpty(detailList)){
                if (CollectionUtils.isEmpty(detailList)) {
                    continue;
                }
                for (SchemeInsuranceDetail detail : detailList) {
                    if("公积金".equals(detail.getInsuranceType())){
                    if ("公积金".equals(detail.getInsuranceType())) {
                        fundPersonal = calculateByEmployeeIdType(detail.getInsuranceType(), fundPersonal, basicSalary, staffOnJobDto, detail);
                    }else{
                    } else {
                        socialPersonal = calculateByEmployeeIdType(detail.getInsuranceType(), socialPersonal, basicSalary, staffOnJobDto, detail);
                    }
                }
            }
        }
        // 工资 = 全勤奖金 + 基本工资 * 折合倍数
        BigDecimal grossSalary = attendanceBonus.add(basicSalary.multiply(conversionRatio)).add(otherIncome);
        // 工资 = 全勤奖金 + 基本工资 * 折合倍数 + 其他收入 + 津贴
        BigDecimal grossSalary = attendanceBonus
                .add(basicSalary.multiply(conversionRatio))
                .add(otherIncome)
                .add(normalizeMoney(getBigDecimal(map, "allowance")));
        BigDecimal salaryTax = TaxCalculator.calculateMonthlyTax(
                grossSalary,
                socialPersonal.add(socialSupplementAmount),
@@ -236,14 +239,16 @@
        }
        map.put("basicSalary", basicSalary);
        map.put("attendanceBonus", attendanceBonus);
        map.put("workDays", workDays);
        map.put("monthWorkDays", monthWorkDays);
        map.put("dailySalary", dailySalary);
        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("benefit", conversionRatio);
        map.put("pieceSalary", convertedWorkHours);
        map.put("hourlySalary", BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
        map.put("otherIncome", otherIncome);
@@ -301,6 +306,20 @@
    }
    /**
     * 解析月标准工时。
     * 优先使用工资表传入的工作日计算,其次使用当月默认工作日。
     */
    private BigDecimal resolveStandardHours(Map<String, Object> map, int monthWorkDays) {
        if (hasValue(map, "standardHours")) {
            BigDecimal standardHours = normalizeMoney(getBigDecimal(map, "standardHours"));
            if (standardHours.compareTo(BigDecimal.ZERO) > 0) {
                return standardHours;
            }
        }
        return calculateStandardHours(monthWorkDays);
    }
    /**
     * 解析当月实际产量。
     */
    private BigDecimal resolveActualOutput(Map<String, Object> map) {
@@ -316,15 +335,6 @@
        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);
    }
    /**
@@ -350,14 +360,13 @@
    /**
     * 计算月时间合计。
     * 计件场景使用实际产量、标准数量和折算系数换算;
     * 计件场景使用实际产量和标准数量换算;
     * 没有计件数据时,回落到生产核算汇总值;
     * 最后再叠加返工补修理工时。
     */
    private BigDecimal resolveConvertedWorkHours(Map<String, Object> map,
                                                 BigDecimal actualOutput,
                                                 BigDecimal standardQuantity,
                                                 BigDecimal conversionFactor,
                                                 BigDecimal repairWorkHour,
                                                 UserAccountDto byUserId) {
        if (hasValue(map, "monthTimeTotal")) {
@@ -367,12 +376,11 @@
            }
        }
        BigDecimal convertedWorkHours = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        // 计件折算工时 = 当月实际产量 / 工序标准数量 * 8 * 工序折算系数
        // 计件折算工时 = 当月实际产量 / 工序标准数量 * 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()));
@@ -386,9 +394,49 @@
     */
    private BigDecimal calculateConversionRatio(BigDecimal standardHours, BigDecimal convertedWorkHours) {
        return standardHours.compareTo(BigDecimal.ZERO) == 0
                ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)
                ? BigDecimal.ZERO.setScale(4, RoundingMode.HALF_UP)
                : convertedWorkHours.divide(standardHours, 8, RoundingMode.HALF_UP)
                .setScale(2, RoundingMode.HALF_UP);
                .setScale(4, RoundingMode.HALF_UP);
    }
    /**
     * 计算日计工资。
     * 规则:基本工资 / 工作日天数。
     */
    private BigDecimal calculateDailySalary(BigDecimal basicSalary, BigDecimal workDays) {
        if (basicSalary == null || workDays == null || workDays.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO.setScale(3, RoundingMode.HALF_UP);
        }
        return basicSalary.divide(workDays, 3, RoundingMode.HALF_UP);
    }
    /**
     * 解析工资表工作日。
     * 优先使用页面传入的工作日,未传时才按自然月工作日计算。
     */
    private int resolveMonthWorkDays(Map<String, Object> map, String date) {
        if (hasValue(map, "monthWorkDays")) {
            BigDecimal monthWorkDays = normalizeMoney(getBigDecimal(map, "monthWorkDays"));
            if (monthWorkDays.compareTo(BigDecimal.ZERO) > 0) {
                return monthWorkDays.intValue();
            }
        }
        return calculateWorkDays(date);
    }
    /**
     * 解析工作日。
     * 如果工资表里已经填写了工作日,则优先使用人工填写值;
     * 否则默认使用当月工作日天数。
     */
    private BigDecimal resolveWorkDays(Map<String, Object> map, int monthWorkDays) {
        if (hasValue(map, "workDays")) {
            BigDecimal workDays = normalizeMoney(getBigDecimal(map, "workDays"));
            if (workDays.compareTo(BigDecimal.ZERO) > 0) {
                return workDays;
            }
        }
        return BigDecimal.valueOf(monthWorkDays).setScale(2, RoundingMode.HALF_UP);
    }
    /**
src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java
@@ -10,6 +10,9 @@
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.mapper.SysUserDeptMapper;
import com.ruoyi.production.mapper.ProductionAccountMapper;
import com.ruoyi.production.mapper.ProductionProductMainMapper;
import com.ruoyi.production.bean.dto.ProductionAccountDto;
import com.ruoyi.production.bean.dto.ProductionProductMainDto;
import com.ruoyi.production.pojo.ProductionAccount;
import com.ruoyi.technology.mapper.TechnologyOperationMapper;
import com.ruoyi.technology.pojo.TechnologyOperation;
@@ -18,9 +21,11 @@
import com.ruoyi.staff.dto.CalculateSalaryDto;
import com.ruoyi.staff.dto.StaffSalaryCalculateDto;
import com.ruoyi.staff.mapper.StaffLeaveMapper;
import com.ruoyi.staff.mapper.StaffContractMapper;
import com.ruoyi.staff.mapper.StaffSalaryDetailMapper;
import com.ruoyi.staff.mapper.StaffSalaryMainMapper;
import com.ruoyi.staff.pojo.StaffLeave;
import com.ruoyi.staff.pojo.StaffContract;
import com.ruoyi.staff.pojo.StaffSalaryDetail;
import com.ruoyi.staff.pojo.StaffSalaryMain;
import com.ruoyi.staff.service.StaffSalaryDetailService;
@@ -46,6 +51,7 @@
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.IOException;
import java.text.SimpleDateFormat;
/**
 * <p>
@@ -65,8 +71,10 @@
    private final SchemeApplicableStaffServiceImpl schemeApplicableStaffService;
    private final SysUserDeptMapper sysUserDeptMapper;
    private final StaffLeaveMapper staffLeaveMapper;
    private final StaffContractMapper staffContractMapper;
    private final SysDeptMapper sysDeptMapper;
    private final ProductionAccountMapper productionAccountMapper;
    private final ProductionProductMainMapper productionProductMainMapper;
    private final TechnologyOperationMapper technologyOperationMapper;
    @Override
@@ -87,6 +95,18 @@
        Page<StaffSalaryMain> page1 = staffSalaryMainMapper.selectPage(page, staffSalaryMainLambdaQueryWrapper);
        page1.getRecords().forEach(main -> {
            List<StaffSalaryDetail> staffSalaryDetailList = staffSalaryDetailMapper.selectList(new LambdaQueryWrapper<StaffSalaryDetail>().eq(StaffSalaryDetail::getMainId, main.getId()));
            staffSalaryDetailList.forEach(detail -> {
                if (detail.getEntryDate() == null && detail.getStaffOnJobId() != null) {
                    StaffContract staffContract = staffContractMapper.selectOne(new LambdaQueryWrapper<StaffContract>()
                            .eq(StaffContract::getStaffOnJobId, detail.getStaffOnJobId())
                            .orderByAsc(StaffContract::getContractStartTime)
                            .orderByAsc(StaffContract::getId)
                            .last("limit 1"));
                    if (staffContract != null && staffContract.getContractStartTime() != null) {
                        detail.setEntryDate(formatEntryDate(staffContract.getContractStartTime()));
                    }
                }
            });
            main.setStaffSalaryDetailList(staffSalaryDetailList);
        });
        return AjaxResult.success(page1);
@@ -182,33 +202,44 @@
    @Override
    public AjaxResult calculateSalary(CalculateSalaryDto calculateSalaryDto) {
        if(CollectionUtils.isEmpty(calculateSalaryDto.getIds())){
        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)){
        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){
            if ((Integer) id.get("staffState") == 0) {
                StaffLeave id1 = staffLeaveMapper.selectOne(new LambdaQueryWrapper<StaffLeave>()
                        .eq(StaffLeave::getStaffOnJobId, staffOnJobId)
                        .like(StaffLeave::getLeaveDate, calculateSalaryDto.getDate()));
                if(id1 == null){
                if (id1 == null) {
                    continue;
                }
            }
            id.put("otherDeduct", deductAmount);
            if (!id.containsKey("allowance")) {
                id.put("allowance", java.math.BigDecimal.ZERO.setScale(2, java.math.RoundingMode.HALF_UP));
            }
            if (id.get("entryDate") == null) {
                StaffContract staffContract = staffContractMapper.selectOne(new LambdaQueryWrapper<StaffContract>()
                        .eq(StaffContract::getStaffOnJobId, staffOnJobId)
                        .orderByDesc(StaffContract::getId)
                        .last("limit 1"));
                if (staffContract != null && staffContract.getContractStartTime() != null) {
                    id.put("entryDate", formatEntryDate(staffContract.getContractStartTime()));
                }
            }
            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);
        }
@@ -235,15 +266,12 @@
        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("allowance", staffSalaryCalculateDto.getAllowance());
        map.put("socialPersonal", staffSalaryCalculateDto.getSocialPersonal());
        map.put("fundPersonal", staffSalaryCalculateDto.getFundPersonal());
        map.put("otherDeduct", staffSalaryCalculateDto.getOtherDeduct());
@@ -285,11 +313,8 @@
                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());
@@ -310,7 +335,10 @@
    /**
     * 汇总员工当月月时间合计。
     * 计时工序直接取工时,计件工序按产量折算后再叠加补修理工时。
     * 规则:
     * 1. 小提琴 / 大提琴 / 贝斯:月产折合小提琴数量 / 工序标准数量 × 8 + 修补理工时
     * 2. 其他产品:当月实际产量 / 对应产品标准数量 × 8 + 修补理工时
     * 最终结果就是工资表里的月时间合计。
     */
    private java.math.BigDecimal resolveMonthTimeTotal(Long sysUserId, String date) {
        if (sysUserId == null || sysUserId <= 0 || StringUtils.isEmpty(date)) {
@@ -324,25 +352,108 @@
            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);
                }
            }
            java.math.BigDecimal productStandardQuantity = toDecimal(item.get("productStandardQuantity"));
            java.math.BigDecimal violinFactor = toDecimal(item.get("violinFactor"));
            java.math.BigDecimal violaFactor = toDecimal(item.get("violaFactor"));
            java.math.BigDecimal bassFactor = toDecimal(item.get("bassFactor"));
            String productName = item.get("productName") == null ? null : String.valueOf(item.get("productName"));
            java.math.BigDecimal rowTotal = calculateOperationTime(
                    operationType,
                    productName,
                    finishedNum,
                    workHour,
                    repairWorkHour,
                    standardQuantity,
                    productStandardQuantity,
                    violinFactor,
                    violaFactor,
                    bassFactor
            );
            total = total.add(rowTotal).setScale(2, java.math.RoundingMode.HALF_UP);
        }
        return total;
    }
    /**
     * 计算单条工序的折算工时。
     * 这里是月时间合计的核心分支:
     * - 小提琴 / 大提琴 / 贝斯:月产折合小提琴数量 / 工序标准数量 × 8 + 修补理工时;
     * - 其他产品:当月实际产量 / 对应产品标准数量 × 8 + 修补理工时。
     */
    private java.math.BigDecimal calculateOperationTime(Integer operationType,
                                                        String productName,
                                                        java.math.BigDecimal finishedNum,
                                                        java.math.BigDecimal workHour,
                                                        java.math.BigDecimal repairWorkHour,
                                                        java.math.BigDecimal standardQuantity,
                                                        java.math.BigDecimal productStandardQuantity,
                                                        java.math.BigDecimal violinFactor,
                                                        java.math.BigDecimal violaFactor,
                                                        java.math.BigDecimal bassFactor) {
        if (isInstrumentProduct(productName)) {
            java.math.BigDecimal foldedQuantity = calculateFoldedQuantity(
                    finishedNum,
                    resolveInstrumentFactor(productName, violinFactor, violaFactor, bassFactor)
            );
            if (standardQuantity.compareTo(java.math.BigDecimal.ZERO) <= 0) {
                return java.math.BigDecimal.ZERO.setScale(2, java.math.RoundingMode.HALF_UP);
            }
            return foldedQuantity
                    .divide(standardQuantity, 8, java.math.RoundingMode.HALF_UP)
                    .multiply(new java.math.BigDecimal("8"))
                    .add(repairWorkHour)
                    .setScale(2, java.math.RoundingMode.HALF_UP);
        }
        if (productStandardQuantity.compareTo(java.math.BigDecimal.ZERO) <= 0) {
            return java.math.BigDecimal.ZERO.setScale(2, java.math.RoundingMode.HALF_UP);
        }
        return finishedNum
                .divide(productStandardQuantity, 8, java.math.RoundingMode.HALF_UP)
                .multiply(new java.math.BigDecimal("8"))
                .add(repairWorkHour)
                .setScale(2, java.math.RoundingMode.HALF_UP);
    }
    /**
     * 按产品名称和配置系数折算产量。
     * 只对小提琴、大提琴、贝斯三类产品生效,
     * 返回的是可用于后续工序标准数量换算的统一折合产量。
     */
    private java.math.BigDecimal calculateFoldedQuantity(java.math.BigDecimal quantity,
                                                         java.math.BigDecimal factor) {
        if (quantity == null || factor == null) {
            return java.math.BigDecimal.ZERO.setScale(2, java.math.RoundingMode.HALF_UP);
        }
        return quantity.multiply(factor).setScale(2, java.math.RoundingMode.HALF_UP);
    }
    private boolean isInstrumentProduct(String productName) {
        return "小提琴".equals(productName)
                || "大提琴".equals(productName)
                || "贝斯".equals(productName);
    }
    private java.math.BigDecimal resolveInstrumentFactor(String productName,
                                                         java.math.BigDecimal violinFactor,
                                                         java.math.BigDecimal violaFactor,
                                                         java.math.BigDecimal bassFactor) {
        if ("小提琴".equals(productName)) {
            return violinFactor;
        }
        if ("大提琴".equals(productName)) {
            return violaFactor;
        }
        if ("贝斯".equals(productName)) {
            return bassFactor;
        }
        return java.math.BigDecimal.ZERO.setScale(2, java.math.RoundingMode.HALF_UP);
    }
    private String formatEntryDate(java.util.Date entryDate) {
        if (entryDate == null) {
            return null;
        }
        return new SimpleDateFormat("yyyy-MM-dd").format(entryDate);
    }
    private java.math.BigDecimal toDecimal(Object value) {
@@ -364,9 +475,9 @@
    @Override
    public void exportWorkHours(HttpServletResponse response, String salaryMonth) {
        List<ProductionAccount> accountList = queryProductionAccounts(salaryMonth);
        List<ProductionProductMainDto> accountList = queryProductionDetails(salaryMonth);
        List<String> operationNames = accountList.stream()
                .map(ProductionAccount::getTechnologyOperationName)
                .map(ProductionProductMainDto::getProcess)
                .filter(StringUtils::isNotEmpty)
                .distinct()
                .sorted()
@@ -421,9 +532,10 @@
            headRow.getCell(3).setCellStyle(headerStyle);
            Map<String, java.math.BigDecimal> standardMap = buildStandardQuantityMap(List.of(operationName));
            Map<String, java.math.BigDecimal> opQtyMap = buildOperationQuantityMap(accountList, operationName);
            Map<String, java.math.BigDecimal> opQtyMap = buildOperationQuantityMap(accountList, operationName, operation);
            Set<String> employeeNames = accountList.stream()
                    .map(ProductionAccount::getSchedulingUserName)
                    .filter(item -> operationName.equals(item.getProcess()))
                    .map(ProductionProductMainDto::getSchedulingUserName)
                    .filter(StringUtils::isNotEmpty)
                    .collect(Collectors.toCollection(LinkedHashSet::new));
            for (String staffName : employeeNames) {
@@ -434,9 +546,7 @@
                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());
                java.math.BigDecimal wage = calculateSheetWage(operation, operationName, qty, standardQty);
                createNumberCell(row, 3, wage, numStyle);
            }
@@ -589,32 +699,67 @@
        return sb.toString();
    }
    private List<ProductionAccount> queryProductionAccounts(String salaryMonth) {
    private List<ProductionProductMainDto> queryProductionDetails(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)));
        ProductionAccountDto dto = new ProductionAccountDto();
        dto.setEntryDateStart(start);
        dto.setEntryDateEnd(end);
        dto.setAuditStatus(1);
        dto.setReportType(0);
        Page<ProductionAccountDto> page = new Page<>(1, 100000);
        return productionProductMainMapper.listProductionDetails(dto, page).getRecords();
    }
    private Map<String, java.math.BigDecimal> buildOperationQuantityMap(List<ProductionAccount> accountList, String operationName) {
    private Map<String, java.math.BigDecimal> buildOperationQuantityMap(List<ProductionProductMainDto> accountList, String operationName, TechnologyOperation operation) {
        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 -> item != null && operationName.equals(item.getProcess()))
                .filter(item -> StringUtils.isNotEmpty(item.getSchedulingUserName()))
                .forEach(item -> map.merge(item.getSchedulingUserName(),
                        item.getFinishedNum() == null ? java.math.BigDecimal.ZERO : item.getFinishedNum(),
                        calculateFoldedQuantity(item, operation),
                        java.math.BigDecimal::add));
        return map;
    }
    private java.math.BigDecimal calculateSheetWage(TechnologyOperation operation,
                                                    String operationName,
                                                    java.math.BigDecimal qty,
                                                    java.math.BigDecimal standardQty) {
        if (operation == null || qty == null) {
            return java.math.BigDecimal.ZERO;
        }
        if (standardQty == null || standardQty.compareTo(java.math.BigDecimal.ZERO) <= 0) {
            return java.math.BigDecimal.ZERO;
        }
        return qty.divide(standardQty, 2, java.math.RoundingMode.HALF_UP).multiply(new java.math.BigDecimal("8"));
    }
    private java.math.BigDecimal calculateFoldedQuantity(ProductionProductMainDto item, TechnologyOperation operation) {
        if (item == null || operation == null) {
            return java.math.BigDecimal.ZERO;
        }
        java.math.BigDecimal quantity = item.getQuantity() == null ? java.math.BigDecimal.ZERO : item.getQuantity();
        String productName = item.getProductName();
        if ("小提琴".equals(productName)) {
            return operation.getViolinFactor() == null ? java.math.BigDecimal.ZERO : quantity.multiply(operation.getViolinFactor());
        }
        if ("大提琴".equals(productName)) {
            return operation.getViolaFactor() == null ? java.math.BigDecimal.ZERO : quantity.multiply(operation.getViolaFactor());
        }
        if ("贝斯".equals(productName)) {
            return operation.getBassFactor() == null ? java.math.BigDecimal.ZERO : quantity.multiply(operation.getBassFactor());
        }
        return java.math.BigDecimal.ZERO;
    }
    private Map<String, java.math.BigDecimal> buildStandardQuantityMap(List<String> operationNames) {
        Map<String, java.math.BigDecimal> map = new LinkedHashMap<>();
        if (CollectionUtils.isEmpty(operationNames)) {
src/main/java/com/ruoyi/technology/pojo/TechnologyOperation.java
@@ -49,8 +49,14 @@
    @Schema(description = "工序标准数量")
    private BigDecimal standardQuantity;
    @Schema(description = "工序折算系数")
    private BigDecimal conversionFactor;
    @Schema(description = "月产小提琴系数")
    private BigDecimal violinFactor;
    @Schema(description = "月产大提琴系数")
    private BigDecimal violaFactor;
    @Schema(description = "月产贝斯系数")
    private BigDecimal bassFactor;
    @Schema(description = "是否质检")
    private Boolean isQuality;
src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationServiceImpl.java
@@ -47,6 +47,9 @@
     */
    @Override
    public R add(TechnologyOperationDto technologyOperationDto) {
        if (technologyOperationDto == null || technologyOperationDto.getType() == null || technologyOperationDto.getType() != 1) {
            return R.fail("工序仅支持计件");
        }
        TechnologyOperation technologyOperation = new TechnologyOperation();
        BeanUtils.copyProperties(technologyOperationDto, technologyOperation);
        boolean saved = technologyOperationMapper.insert(technologyOperation) > 0;
@@ -62,6 +65,9 @@
        if (technologyOperation.getId() == null) {
            return R.fail("工序ID不能为空");
        }
        if (technologyOperation.getType() == null || technologyOperation.getType() != 1) {
            return R.fail("工序仅支持计件");
        }
        boolean updated = technologyOperationMapper.update(
                null,
                Wrappers.<TechnologyOperation>lambdaUpdate()
@@ -71,7 +77,9 @@
                        .set(TechnologyOperation::getRemark, technologyOperation.getRemark())
                        .set(TechnologyOperation::getSalaryQuota, technologyOperation.getSalaryQuota())
                        .set(TechnologyOperation::getStandardQuantity, technologyOperation.getStandardQuantity())
                        .set(TechnologyOperation::getConversionFactor, technologyOperation.getConversionFactor())
                        .set(TechnologyOperation::getViolinFactor, technologyOperation.getViolinFactor())
                        .set(TechnologyOperation::getViolaFactor, technologyOperation.getViolaFactor())
                        .set(TechnologyOperation::getBassFactor, technologyOperation.getBassFactor())
                        .set(TechnologyOperation::getIsQuality, technologyOperation.getIsQuality())
                        .set(TechnologyOperation::getIsProduction, technologyOperation.getIsProduction())
                        .set(TechnologyOperation::getType, technologyOperation.getType())
src/main/resources/mapper/basic/ProductModelMapper.xml
@@ -23,7 +23,7 @@
    </resultMap>
    <select id="listPageProductModel" resultMap="ProductModelVoResultMap">
        select pm.*,p.product_name
        select pm.*,p.product_name, ifnull(p.standard_quantity, 0) as product_standard_quantity
        from product_model pm
        left join product p on pm.product_id = p.id
        <where>
src/main/resources/mapper/production/ProductionAccountMapper.xml
@@ -43,6 +43,7 @@
        group_concat(distinct p_parent.product_name order by p_parent.product_name separator ',') as productCategory,
        group_concat(distinct pm.model order by pm.model separator ',') as specificationModel,
        group_concat(distinct pm.unit order by pm.unit separator ',') as unit,
        ifnull(p.standard_quantity, 0) as productStandardQuantity,
        pa.scheduling_user_id as schedulingUserId,
        pa.scheduling_user_name as schedulingUserName,
        group_concat(distinct pot.work_order_no order by pot.work_order_no separator ',') as workOrderNo,
@@ -50,11 +51,9 @@
            case
                when pa.adjust_amount is not null then ifnull(pa.adjust_amount, 0)
                when poro.type = 0 then ifnull(pa.work_hours, 0) * (ifnull(ppm.work_hour, 0) + ifnull(ppm.repair_work_hour, 0))
                else ifnull(pa.work_hours, 0) * ifnull(pa.finished_num, 0) *
                     case
                         when substring_index(pm.model, '*', -1) regexp '^[0-9]+(\\.[0-9]+)?$'
                         then cast(substring_index(pm.model, '*', -1) as decimal(18,4))
                         else 1
                else case
                         when ifnull(p.standard_quantity, 0) = 0 then 0
                         else ifnull(pa.finished_num, 0) / ifnull(p.standard_quantity, 0) * 8
                     end
            end
        ) as decimal(18,4)) as wages,
@@ -89,6 +88,7 @@
        left join production_operation_task pot on ppm.production_operation_task_id = pot.id
        left join production_order po on pot.production_order_id = po.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
        left join product_model pm on pm.id = ifnull(poro.product_model_id, po.product_model_id)
        left join product p on pm.product_id = p.id
        left join product p_parent on p_parent.id = p.parent_id
@@ -137,17 +137,15 @@
    <select id="selectUserAccount" resultType="com.ruoyi.production.bean.dto.UserAccountDto">
        select cast(ifnull(sum(
                   case
                       when pa.adjust_amount is not null then 0
                       when poro.type = 0 then 0
                       else ifnull(pa.work_hours, 0) * ifnull(pa.finished_num, 0) *
                            case
                                when substring_index(pm.model, '*', -1) regexp '^[0-9]+(\\.[0-9]+)?$'
                                then cast(substring_index(pm.model, '*', -1) as decimal(18,4))
                                else 1
                            end
                   end
               ), 0) as decimal(18,2)) as accountBalance,
            case
                when pa.adjust_amount is not null then 0
                when poro.type = 0 then 0
                else case
                         when ifnull(p.standard_quantity, 0) = 0 then 0
                         else ifnull(pa.finished_num, 0) / ifnull(p.standard_quantity, 0) * 8
                     end
            end
       ), 0) as decimal(18,2)) as accountBalance,
               cast(ifnull(sum(
                   case
                       when pa.adjust_amount is not null then ifnull(pa.adjust_amount, 0)
@@ -161,6 +159,7 @@
                 left join production_order po on pot.production_order_id = po.id
                 left join production_order_routing_operation poro on pot.production_order_routing_operation_id = poro.id
                 left join product_model pm on pm.id = ifnull(poro.product_model_id, po.product_model_id)
                 left join product p on pm.product_id = p.id
        where pa.scheduling_user_id = #{userId}
          and date_format(pa.scheduling_date, '%Y-%m') = #{date}
          and (
@@ -179,12 +178,20 @@
            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
            ifnull(p.standard_quantity, 0) as productStandardQuantity,
            ifnull(too.violin_factor, 0) as violinFactor,
            ifnull(too.viola_factor, 0) as violaFactor,
            ifnull(too.bass_factor, 0) as bassFactor,
            p.product_name as productName
        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
                 left join production_order po on pot.production_order_id = po.id
                 left join product_model pm on pm.id = ifnull(poro.product_model_id, po.product_model_id)
                 left join product p on pm.product_id = p.id
                 left join product p_parent on p_parent.id = p.parent_id
        where pa.scheduling_user_id = #{userId}
          and date_format(pa.scheduling_date, '%Y-%m') = #{date}
          and (
src/main/resources/mapper/production/ProductionProductMainMapper.xml
@@ -45,6 +45,7 @@
               ifnull(ppo.quantity, 0) as quantity,
               ifnull(ppo.scrap_qty, 0) as scrapQty,
               p.product_name as productName,
               ifnull(p.standard_quantity, 0) as productStandardQuantity,
               pm.model as productModelName,
               pm.unit,
               po_sales.salesContractNo,
@@ -131,9 +132,11 @@
                   end as auditStatusName,
               pot.work_order_no as workOrderNo,
               p.product_name as productName,
               ifnull(p.standard_quantity, 0) as productStandardQuantity,
               pm.model as productModelName,
               pm.unit,
               pa.technology_operation_name as process,
               ifnull(too.standard_quantity, 0) as standardQuantity,
               ifnull(ppo.quantity, 0) as quantity,
               ifnull(ppo.scrap_qty, 0) as scrapQty,
               date(pa.scheduling_date) as schedulingDate,
@@ -147,11 +150,9 @@
               cast(
                   case
                       when poro.type = 0 then ifnull(pa.work_hours, 0) * (ifnull(ppm.work_hour, 0) + ifnull(ppm.repair_work_hour, 0))
                       else ifnull(pa.work_hours, 0) * ifnull(pa.finished_num, 0) *
                            case
                                when substring_index(pm.model, '*', -1) regexp '^[0-9]+(\\.[0-9]+)?$'
                                then cast(substring_index(pm.model, '*', -1) as decimal(18,4))
                                else 1
                       else case
                                when ifnull(too.standard_quantity, 0) = 0 then 0
                                else ifnull(pa.finished_num, 0) / ifnull(too.standard_quantity, 0) * 8
                            end
                   end
                   as decimal(18,4)
@@ -160,11 +161,9 @@
                   case
                       when pa.adjust_amount is not null then ifnull(pa.adjust_amount, 0)
                       when poro.type = 0 then ifnull(pa.work_hours, 0) * (ifnull(ppm.work_hour, 0) + ifnull(ppm.repair_work_hour, 0))
                       else ifnull(pa.work_hours, 0) * ifnull(pa.finished_num, 0) *
                            case
                                when substring_index(pm.model, '*', -1) regexp '^[0-9]+(\\.[0-9]+)?$'
                                then cast(substring_index(pm.model, '*', -1) as decimal(18,4))
                                else 1
                       else case
                                when ifnull(too.standard_quantity, 0) = 0 then 0
                                else ifnull(pa.finished_num, 0) / ifnull(too.standard_quantity, 0) * 8
                            end
                   end
                   as decimal(18,4)
@@ -174,6 +173,7 @@
                 left join production_operation_task pot on ppm.production_operation_task_id = pot.id
                 left join production_order po on pot.production_order_id = po.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
                 left join product_model pm on pm.id = ifnull(poro.product_model_id, po.product_model_id)
                 left join product p on pm.product_id = p.id
                 left join product p_parent on p_parent.id = p.parent_id
src/main/resources/mapper/staff/StaffSalaryDetailMapper.xml
@@ -11,12 +11,23 @@
        <result column="post_name" property="postName" />
        <result column="dept_name" property="deptName" />
        <result column="basic_salary" property="basicSalary" />
        <result column="attendance_bonus" property="attendanceBonus" />
        <result column="daily_salary" property="dailySalary" />
        <result column="work_days" property="workDays" />
        <result column="personal_leave" property="personalLeave" />
        <result column="sick_leave" property="sickLeave" />
        <result column="bereavement_leave" property="bereavementLeave" />
        <result column="marriage_leave" property="marriageLeave" />
        <result column="allowance" property="allowance" />
        <result column="piece_salary" property="pieceSalary" />
        <result column="hourly_salary" property="hourlySalary" />
        <result column="month_time_total" property="monthTimeTotal" />
        <result column="conversion_ratio" property="conversionRatio" />
        <result column="other_income" property="otherIncome" />
        <result column="social_personal" property="socialPersonal" />
        <result column="fund_personal" property="fundPersonal" />
        <result column="other_deduct" property="otherDeduct" />
        <result column="social_supplement_amount" property="socialSupplementAmount" />
        <result column="salary_tax" property="salaryTax" />
        <result column="gross_salary" property="grossSalary" />
        <result column="deduct_salary" property="deductSalary" />
src/main/resources/mapper/staff/StaffSalaryMainMapper.xml
@@ -8,6 +8,7 @@
        <result column="salary_title" property="salaryTitle" />
        <result column="dept_ids" property="deptIds" />
        <result column="salary_month" property="salaryMonth" />
        <result column="month_work_days" property="monthWorkDays" />
        <result column="remark" property="remark" />
        <result column="status" property="status" />
        <result column="create_time" property="createTime" />
src/main/resources/mapper/technology/TechnologyOperationMapper.xml
@@ -12,6 +12,9 @@
        <result column="remark" property="remark" />
        <result column="salary_quota" property="salaryQuota" />
        <result column="standard_quantity" property="standardQuantity" />
        <result column="violin_factor" property="violinFactor" />
        <result column="viola_factor" property="violaFactor" />
        <result column="bass_factor" property="bassFactor" />
        <result column="conversion_factor" property="conversionFactor" />
        <result column="is_quality" property="isQuality" />
        <result column="type" property="type" />