| | |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryCalculationPageReqVO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceSummaryDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.performanceappraise.HrmPerformanceAppraiseResultDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSalaryDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSocialSecurityDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmLeaveTypeConfigDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryCalculationDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryPaymentDetailDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryPaymentDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryStructureDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmLeaveApplicationMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceSummaryMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.performanceappraise.HrmPerformanceAppraiseMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.performanceappraise.HrmPerformanceAppraiseResultMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSalaryMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSocialSecurityMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmLeaveTypeConfigMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryCalculationMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryPaymentDetailMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryPaymentMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryStructureMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSocialSecuritySchemeMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO; |
| | | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| | |
| | | /** |
| | | * 酬核算 Service 实现类 |
| | | * |
| | | * @author 芋道源码 |
| | | * @author 超级管理员 |
| | | */ |
| | | @Service |
| | | @Validated |
| | |
| | | private static final BigDecimal DEFAULT_MEDICAL_PERSONAL_RATIO = new BigDecimal("0.02"); |
| | | private static final BigDecimal DEFAULT_UNEMPLOYMENT_PERSONAL_RATIO = new BigDecimal("0.005"); |
| | | private static final BigDecimal DEFAULT_HOUSING_FUND_PERSONAL_RATIO = new BigDecimal("0.12"); |
| | | |
| | | /** |
| | | * 加班工资倍率默认值(薪酬结构未配置时使用) |
| | | */ |
| | | private static final BigDecimal DEFAULT_OVERTIME_PAY_RATIO = new BigDecimal("1.5"); |
| | | |
| | | /** |
| | | * 默认应出勤天数(无考勤汇总时的兜底值) |
| | | */ |
| | | private static final int DEFAULT_WORK_DAYS = 22; |
| | | |
| | | /** |
| | | * 绩效工资档级系数:1-优秀 2-良好 3-合格 4-待改进 |
| | | */ |
| | | private static final BigDecimal PERFORMANCE_GRADE_FACTOR_EXCELLENT = new BigDecimal("1.2"); |
| | | private static final BigDecimal PERFORMANCE_GRADE_FACTOR_GOOD = new BigDecimal("1.0"); |
| | | private static final BigDecimal PERFORMANCE_GRADE_FACTOR_PASS = new BigDecimal("0.8"); |
| | | private static final BigDecimal PERFORMANCE_GRADE_FACTOR_IMPROVE = new BigDecimal("0.6"); |
| | | |
| | | @Resource |
| | | private HrmSalaryCalculationMapper salaryCalculationMapper; |
| | |
| | | private HrmSalaryPaymentMapper salaryPaymentMapper; |
| | | @Resource |
| | | private HrmSalaryPaymentDetailMapper salaryPaymentDetailMapper; |
| | | @Resource |
| | | private HrmAttendanceSummaryMapper attendanceSummaryMapper; |
| | | @Resource |
| | | private HrmPerformanceAppraiseMapper performanceAppraiseMapper; |
| | | @Resource |
| | | private HrmPerformanceAppraiseResultMapper performanceAppraiseResultMapper; |
| | | @Resource |
| | | private HrmSalaryStructureMapper salaryStructureMapper; |
| | | |
| | | @Override |
| | | public PageResult<HrmSalaryCalculationDO> getSalaryCalculationPage(HrmSalaryCalculationPageReqVO pageReqVO) { |
| | |
| | | BigDecimal mealAllowance = employeeSalary.getMealAllowance() != null ? employeeSalary.getMealAllowance() : BigDecimal.ZERO; |
| | | BigDecimal transportAllowance = employeeSalary.getTransportAllowance() != null ? employeeSalary.getTransportAllowance() : BigDecimal.ZERO; |
| | | BigDecimal otherAllowance = employeeSalary.getOtherAllowance() != null ? employeeSalary.getOtherAllowance() : BigDecimal.ZERO; |
| | | // ===== 考勤链路:应出勤天数 / 加班时长 / 缺勤扣款 ===== |
| | | HrmAttendanceSummaryDO attendanceSummary = attendanceSummaryMapper.selectByUserIdAndYearMonth(employeeId, period); |
| | | int workDays = attendanceSummary != null && attendanceSummary.getWorkDays() != null |
| | | ? attendanceSummary.getWorkDays() : DEFAULT_WORK_DAYS; |
| | | BigDecimal overtimeHours = attendanceSummary != null && attendanceSummary.getOvertimeHours() != null |
| | | ? attendanceSummary.getOvertimeHours() : BigDecimal.ZERO; |
| | | // ===== 加班工资 = 时薪 × 加班时长 × 倍率(时薪 = 基本工资 ÷ (应出勤天 × 8)) ===== |
| | | BigDecimal overtimePay = BigDecimal.ZERO; |
| | | if (overtimeHours.compareTo(BigDecimal.ZERO) > 0 && workDays > 0) { |
| | | BigDecimal hourlyRate = baseSalary.divide(BigDecimal.valueOf(workDays * 8L), 4, RoundingMode.HALF_UP); |
| | | overtimePay = hourlyRate.multiply(overtimeHours).multiply(getOvertimePayRatio(employeeSalary, employee)) |
| | | .setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | // ===== 绩效工资:按当月绩效结果档级系数折算(无结果按全额) ===== |
| | | performanceSalary = calculatePerformanceSalary(performanceSalary, employeeId, period); |
| | | |
| | | // 应发工资总额 |
| | | BigDecimal totalIncome = baseSalary.add(performanceSalary).add(mealAllowance) |
| | |
| | | // 公积金扣除 = 基数 × 公积金个人比例 |
| | | BigDecimal housingFundDeduction = housingFundBase.multiply(housingFundPersonalRatio).setScale(2, RoundingMode.HALF_UP); |
| | | |
| | | // 计算请假扣款 |
| | | BigDecimal leaveDeduction = calculateLeaveDeduction(employee, period, baseSalary); |
| | | // 计算缺勤扣款(考勤口径:缺勤天数 = 应出勤 − 实际出勤;无考勤汇总时回退到请假申请口径) |
| | | BigDecimal leaveDeduction = calculateDeduction(attendanceSummary, baseSalary, workDays, employee, period); |
| | | |
| | | // 计算个税扣除 |
| | | // 应纳税所得额 = 应发工资 - 社保扣除 - 公积金扣除 - 起征点(5000) |
| | |
| | | .otherDeduction(otherDeduction) |
| | | .leaveDeduction(leaveDeduction) |
| | | .actualSalary(actualSalary) |
| | | .workDays(22) // 默认工作日 |
| | | .overtimeHours(BigDecimal.ZERO) |
| | | .workDays(workDays) // 动态应出勤天数(来自考勤汇总) |
| | | .overtimeHours(overtimeHours) // 加班时长(来自考勤汇总) |
| | | .status(0) // 待确认 |
| | | .build(); |
| | | } |
| | | |
| | | /** |
| | | * 计算缺勤扣款:优先按考勤汇总(缺勤天数 = 应出勤 − 实际出勤);无汇总时回退到请假申请口径 |
| | | */ |
| | | private BigDecimal calculateDeduction(HrmAttendanceSummaryDO attendanceSummary, BigDecimal baseSalary, |
| | | int workDays, HrmEmployeeDO employee, String period) { |
| | | if (attendanceSummary != null && workDays > 0 && attendanceSummary.getActualDays() != null) { |
| | | int absentDays = Math.max(0, workDays - attendanceSummary.getActualDays()); |
| | | if (absentDays == 0) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | BigDecimal daySalary = baseSalary.divide(BigDecimal.valueOf(workDays), 4, RoundingMode.HALF_UP); |
| | | return daySalary.multiply(BigDecimal.valueOf(absentDays)).setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | return calculateLeaveDeduction(employee, period, baseSalary); |
| | | } |
| | | |
| | | /** |
| | | * 计算绩效工资:按当月已完成且匹配核算周期的月度绩效考核结果的档级系数折算 |
| | | */ |
| | | private BigDecimal calculatePerformanceSalary(BigDecimal performanceSalary, Long employeeId, String period) { |
| | | HrmPerformanceAppraiseDO appraise = performanceAppraiseMapper.selectMonthlyCompletedByPeriod(period); |
| | | if (appraise == null) { |
| | | return performanceSalary; |
| | | } |
| | | HrmPerformanceAppraiseResultDO result = performanceAppraiseResultMapper |
| | | .selectByAppraiseIdAndEmployeeId(appraise.getId(), employeeId); |
| | | if (result == null || result.getGrade() == null) { |
| | | return performanceSalary; |
| | | } |
| | | BigDecimal factor = switch (result.getGrade()) { |
| | | case 1 -> PERFORMANCE_GRADE_FACTOR_EXCELLENT; |
| | | case 2 -> PERFORMANCE_GRADE_FACTOR_GOOD; |
| | | case 3 -> PERFORMANCE_GRADE_FACTOR_PASS; |
| | | case 4 -> PERFORMANCE_GRADE_FACTOR_IMPROVE; |
| | | default -> BigDecimal.ONE; |
| | | }; |
| | | return performanceSalary.multiply(factor).setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | /** |
| | | * 获取加班工资倍率(薪酬结构未配置时默认 1.5) |
| | | */ |
| | | private BigDecimal getOvertimePayRatio(HrmEmployeeSalaryDO employeeSalary, HrmEmployeeDO employee) { |
| | | Long structureId = employeeSalary.getSalaryStructureId() != null |
| | | ? employeeSalary.getSalaryStructureId() : employee.getSalaryStructureId(); |
| | | if (structureId == null) { |
| | | return DEFAULT_OVERTIME_PAY_RATIO; |
| | | } |
| | | HrmSalaryStructureDO structure = salaryStructureMapper.selectById(structureId); |
| | | return structure != null && structure.getOvertimePayRatio() != null |
| | | ? structure.getOvertimePayRatio() : DEFAULT_OVERTIME_PAY_RATIO; |
| | | } |
| | | |
| | | /** |
| | | * 计算请假扣款 |
| | | * |
| | | * @param employee 员工信息 |