From fdbc5af6474abb39120b771af87005aeb6186ae5 Mon Sep 17 00:00:00 2001
From: tuqin <tuqin@tuqin.cn>
Date: 星期一, 14 九月 2026 15:28:28 +0800
Subject: [PATCH] feat: 售后工单按条绑定部门与原因类型;新增加班工资管理;员工附件留档
---
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java | 185 ++++++++++++++++++++++++++++++++++++++++------
1 files changed, 160 insertions(+), 25 deletions(-)
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
index 8be05ab..5f0019e 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
@@ -6,6 +6,8 @@
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.approval.HrmOvertimeApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmEmployeeScheduleDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmShiftDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSalaryDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSocialSecurityDO;
@@ -16,6 +18,8 @@
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryStructureDO;
import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmLeaveApplicationMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmEmployeeScheduleMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmShiftMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmOvertimeApplicationMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSalaryMapper;
@@ -28,6 +32,11 @@
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryStructureMapper;
import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmOvertimeWageConfigDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmOvertimeWageConfigMapper;
+import cn.iocoder.yudao.module.hrm.enums.HrmEmployeeCategoryEnum;
import cn.iocoder.yudao.module.hrm.enums.HrmOvertimeTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@@ -39,8 +48,11 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -91,11 +103,17 @@
@Resource
private HrmSalaryStructureMapper salaryStructureMapper;
@Resource
+ private HrmOvertimeWageConfigMapper overtimeWageConfigMapper;
+ @Resource
private HrmLeaveTypeConfigMapper leaveTypeConfigMapper;
@Resource
private HrmSalaryPaymentMapper salaryPaymentMapper;
@Resource
private HrmSalaryPaymentDetailMapper salaryPaymentDetailMapper;
+ @Resource
+ private HrmEmployeeScheduleMapper employeeScheduleMapper;
+ @Resource
+ private HrmShiftMapper shiftMapper;
@Override
public PageResult<HrmSalaryCalculationDO> getSalaryCalculationPage(HrmSalaryCalculationPageReqVO pageReqVO) {
@@ -225,6 +243,11 @@
OvertimeSalary overtimeSalary = calculateOvertimeSalary(employee, employeeSalary, period, baseSalary);
BigDecimal overtimePay = overtimeSalary.pay();
+ // 鎸夋帓鐝疮璁″嚭鍕ゅぉ鏁颁笌鎺掔彮鎬诲伐鏃讹紙鏃犳帓鐝椂鍑哄嫟澶╂暟鍥為��榛樿 22锛�
+ ScheduleStat scheduleStat = calculateScheduleStat(employeeId, period);
+ Integer workDays = scheduleStat.scheduledDays() > 0 ? scheduleStat.scheduledDays() : 22;
+ BigDecimal totalWorkHours = scheduleStat.totalWorkHours();
+
// 搴斿彂宸ヨ祫鎬婚
BigDecimal totalIncome = baseSalary.add(performanceSalary).add(mealAllowance)
.add(transportAllowance).add(otherAllowance).add(overtimePay);
@@ -295,8 +318,9 @@
.otherDeduction(otherDeduction)
.leaveDeduction(leaveDeduction)
.actualSalary(actualSalary)
- .workDays(22) // 榛樿宸ヤ綔鏃�
+ .workDays(workDays) // 鎸夋帓鐝疮璁″簲鍑哄嫟澶╂暟锛屾棤鎺掔彮鏃跺洖閫� 22
.overtimeHours(overtimeSalary.hours())
+ .totalWorkHours(totalWorkHours)
.status(0) // 寰呯‘璁�
.build();
}
@@ -354,8 +378,16 @@
}
/**
- * 姹囨�诲綋鏈堝鎵归�氳繃鐨勫姞鐝敵璇枫�傚皬鏃跺伐璧勬寜鍩烘湰宸ヨ祫 / 21.75 / 8 璁$畻锛�
- * 宸ヤ綔鏃ラ粯璁� 1.5 鍊嶃�佷紤鎭棩榛樿 2 鍊嶃�佹硶瀹氳妭鍋囨棩榛樿 3 鍊嶏紝鍧囧彲鐢辫柂閰粨鏋勮鐩栥��
+ * 姹囨�诲綋鏈堝鎵归�氳繃鐨勫姞鐝敵璇凤紝璁$畻鍔犵彮宸ヨ祫涓庡姞鐝椂闀裤��
+ *
+ * <p>鎸夈�屽憳宸ョ被鍒�嶅樊寮傚寲鏍哥畻锛�
+ * <ul>
+ * <li>鎶�宸ワ細鍔犵彮钖祫鎸� 1:1 鏍哥畻锛屼笌骞虫椂鏃惰柂涓�鑷达紙鍩烘湰宸ヨ祫 / 21.75 / 8锛夛紱</li>
+ * <li>鏅�氬憳宸ワ細鎸夊矖浣嶉厤缃殑鍥哄畾鍔犵彮鏃惰柂锛堥粯璁� 20 鍏�/灏忔椂锛夋牳绠楋紱</li>
+ * <li>绠$悊锛氫笉璁″姞鐝伐璧勶紱</li>
+ * </ul>
+ * 鍛戒腑宀椾綅閰嶇疆鏃讹紝鎸夐厤缃殑鏈�澶у姞鐝椂闀夸笂闄愬鍔犵彮鏃堕暱灏侀《锛�
+ * 鏈懡涓换浣曞矖浣嶉厤缃笖闈炴妧宸�/绠$悊鏃讹紝鍥為��鏃ч�昏緫锛堣柂閰粨鏋勫�嶇巼 / 鍔犵彮绫诲瀷榛樿鍊嶇巼锛夈��
*/
private OvertimeSalary calculateOvertimeSalary(HrmEmployeeDO employee, HrmEmployeeSalaryDO employeeSalary,
String period, BigDecimal baseSalary) {
@@ -368,39 +400,142 @@
if (CollUtil.isEmpty(applications)) {
return new OvertimeSalary(BigDecimal.ZERO, BigDecimal.ZERO);
}
- Long structureId = employeeSalary.getSalaryStructureId() != null
- ? employeeSalary.getSalaryStructureId() : employee.getSalaryStructureId();
- HrmSalaryStructureDO structure = structureId == null ? null : salaryStructureMapper.selectById(structureId);
- BigDecimal workdayMultiplier = structure != null && structure.getOvertimePayRatio() != null
- ? structure.getOvertimePayRatio() : HrmOvertimeTypeEnum.WORKDAY.getSalaryMultiplier();
- BigDecimal restDayMultiplier = structure != null && structure.getRestDayOvertimePayRatio() != null
- ? structure.getRestDayOvertimePayRatio() : HrmOvertimeTypeEnum.REST_DAY.getSalaryMultiplier();
- BigDecimal legalHolidayMultiplier = structure != null && structure.getLegalHolidayOvertimePayRatio() != null
- ? structure.getLegalHolidayOvertimePayRatio() : HrmOvertimeTypeEnum.LEGAL_HOLIDAY.getSalaryMultiplier();
- BigDecimal hourlySalary = baseSalary.divide(new BigDecimal("21.75"), 8, RoundingMode.HALF_UP)
+ // 绠$悊绫诲埆涓嶈鍔犵彮宸ヨ祫
+ HrmEmployeeCategoryEnum category = HrmEmployeeCategoryEnum.valueOfCategory(employee.getEmployeeCategory());
+ if (category == HrmEmployeeCategoryEnum.MANAGER) {
+ return new OvertimeSalary(BigDecimal.ZERO, BigDecimal.ZERO);
+ }
+ // 鍛樺伐宀椾綅鍛戒腑鐨勫姞鐝伐璧勯厤缃紙鍙栨椂钖渶楂樸�佷笂闄愭渶瀹界殑涓�鏉★級
+ OvertimeWageRule wageRule = resolveOvertimeWageRule(employee);
+ // 骞虫椂鏃惰柂 = 鍩烘湰宸ヨ祫 / 21.75 / 8
+ BigDecimal baseHourlySalary = baseSalary.divide(new BigDecimal("21.75"), 8, RoundingMode.HALF_UP)
.divide(new BigDecimal("8"), 8, RoundingMode.HALF_UP);
+
BigDecimal totalHours = BigDecimal.ZERO;
BigDecimal totalPay = BigDecimal.ZERO;
- for (HrmOvertimeApplicationDO application : applications) {
- BigDecimal hours = application.getDuration() == null ? BigDecimal.ZERO : application.getDuration();
- HrmOvertimeTypeEnum type = HrmOvertimeTypeEnum.valueOfType(application.getOvertimeType());
- if (type == null || hours.signum() <= 0) {
- continue;
+ if (category == HrmEmployeeCategoryEnum.TECHNICIAN || wageRule != null) {
+ // 鎶�宸ワ細1:1锛堜笌骞虫椂鏃惰柂涓�鑷达級锛涙櫘閫氬憳宸ュ懡涓厤缃細鎸夐厤缃浐瀹氭椂钖紝鍧囦笉鍖哄垎鍔犵彮绫诲瀷鍊嶇巼
+ BigDecimal hourlySalary = category == HrmEmployeeCategoryEnum.TECHNICIAN
+ ? baseHourlySalary : wageRule.hourlyRate();
+ for (HrmOvertimeApplicationDO application : applications) {
+ BigDecimal hours = application.getDuration() == null ? BigDecimal.ZERO : application.getDuration();
+ HrmOvertimeTypeEnum type = HrmOvertimeTypeEnum.valueOfType(application.getOvertimeType());
+ if (type == null || hours.signum() <= 0) {
+ continue;
+ }
+ totalHours = totalHours.add(hours);
}
- BigDecimal multiplier = switch (type) {
- case WORKDAY -> workdayMultiplier;
- case REST_DAY -> restDayMultiplier;
- case LEGAL_HOLIDAY -> legalHolidayMultiplier;
- };
- totalHours = totalHours.add(hours);
- totalPay = totalPay.add(hourlySalary.multiply(hours).multiply(multiplier));
+ // 鎸夊矖浣嶉厤缃皝椤跺姞鐝椂闀�
+ BigDecimal paidHours = wageRule != null && wageRule.maxOvertimeHours() != null
+ && totalHours.compareTo(wageRule.maxOvertimeHours()) > 0
+ ? wageRule.maxOvertimeHours() : totalHours;
+ totalPay = hourlySalary.multiply(paidHours);
+ } else {
+ // 鍏煎鏃ч�昏緫锛氭棤宀椾綅閰嶇疆銆佷笖闈炴妧宸�/绠$悊
+ Long structureId = employeeSalary.getSalaryStructureId() != null
+ ? employeeSalary.getSalaryStructureId() : employee.getSalaryStructureId();
+ HrmSalaryStructureDO structure = structureId == null ? null : salaryStructureMapper.selectById(structureId);
+ BigDecimal workdayMultiplier = structure != null && structure.getOvertimePayRatio() != null
+ ? structure.getOvertimePayRatio() : HrmOvertimeTypeEnum.WORKDAY.getSalaryMultiplier();
+ BigDecimal restDayMultiplier = structure != null && structure.getRestDayOvertimePayRatio() != null
+ ? structure.getRestDayOvertimePayRatio() : HrmOvertimeTypeEnum.REST_DAY.getSalaryMultiplier();
+ BigDecimal legalHolidayMultiplier = structure != null && structure.getLegalHolidayOvertimePayRatio() != null
+ ? structure.getLegalHolidayOvertimePayRatio() : HrmOvertimeTypeEnum.LEGAL_HOLIDAY.getSalaryMultiplier();
+ for (HrmOvertimeApplicationDO application : applications) {
+ BigDecimal hours = application.getDuration() == null ? BigDecimal.ZERO : application.getDuration();
+ HrmOvertimeTypeEnum type = HrmOvertimeTypeEnum.valueOfType(application.getOvertimeType());
+ if (type == null || hours.signum() <= 0) {
+ continue;
+ }
+ BigDecimal multiplier = switch (type) {
+ case WORKDAY -> workdayMultiplier;
+ case REST_DAY -> restDayMultiplier;
+ case LEGAL_HOLIDAY -> legalHolidayMultiplier;
+ };
+ totalHours = totalHours.add(hours);
+ totalPay = totalPay.add(baseHourlySalary.multiply(hours).multiply(multiplier));
+ }
}
return new OvertimeSalary(totalHours.setScale(1, RoundingMode.HALF_UP),
totalPay.setScale(2, RoundingMode.HALF_UP));
}
+ /**
+ * 瑙f瀽鍛樺伐宀椾綅瀵瑰簲鐨勫姞鐝伐璧勮鍒欙細鏃惰柂鍙栧懡涓厤缃腑鐨勬渶澶у�硷紱
+ * 涓婇檺鍙栤�滄渶瀹解�濓紙瀛樺湪鏈缃笂闄愮殑閰嶇疆鍒欒涓轰笉闄愶紝鍚﹀垯鍙栨渶澶у�硷級銆�
+ */
+ private OvertimeWageRule resolveOvertimeWageRule(HrmEmployeeDO employee) {
+ if (StrUtil.isEmpty(employee.getPostIds())) {
+ return null;
+ }
+ List<Long> postIds;
+ try {
+ postIds = JSONUtil.toList(employee.getPostIds(), Long.class);
+ } catch (Exception ex) {
+ return null;
+ }
+ if (CollUtil.isEmpty(postIds)) {
+ return null;
+ }
+ List<HrmOvertimeWageConfigDO> configs = overtimeWageConfigMapper.selectEnabledListByPostIds(postIds);
+ if (CollUtil.isEmpty(configs)) {
+ return null;
+ }
+ BigDecimal hourlyRate = null;
+ BigDecimal maxHours = null;
+ boolean unlimited = false;
+ for (HrmOvertimeWageConfigDO config : configs) {
+ if (config.getHourlyRate() != null
+ && (hourlyRate == null || config.getHourlyRate().compareTo(hourlyRate) > 0)) {
+ hourlyRate = config.getHourlyRate();
+ }
+ if (config.getMaxOvertimeHours() == null) {
+ unlimited = true;
+ } else if (!unlimited && (maxHours == null || config.getMaxOvertimeHours().compareTo(maxHours) > 0)) {
+ maxHours = config.getMaxOvertimeHours();
+ }
+ }
+ if (hourlyRate == null) {
+ return null;
+ }
+ return new OvertimeWageRule(hourlyRate, unlimited ? null : maxHours);
+ }
+
+ private record OvertimeWageRule(BigDecimal hourlyRate, BigDecimal maxOvertimeHours) {}
+
private record OvertimeSalary(BigDecimal hours, BigDecimal pay) {}
+ private record ScheduleStat(int scheduledDays, BigDecimal totalWorkHours) {}
+
+ /**
+ * 鎸夋帓鐝〃绱鍛樺伐鐨勫嚭鍕ゅぉ鏁颁笌鎺掔彮鎬诲伐鏃躲��
+ * 浠呯粺璁℃湁鐝鐨勬帓鐝褰曪紝鐝鏍囧噯宸ユ椂涔嬪拰鍗充负鎺掔彮宸ユ椂銆�
+ */
+ private ScheduleStat calculateScheduleStat(Long employeeId, String period) {
+ YearMonth yearMonth = YearMonth.parse(period);
+ List<HrmEmployeeScheduleDO> schedules = employeeScheduleMapper.selectListByEmployeeIdAndPeriod(
+ employeeId, yearMonth.atDay(1), yearMonth.atEndOfMonth());
+ if (CollUtil.isEmpty(schedules)) {
+ return new ScheduleStat(0, BigDecimal.ZERO);
+ }
+ Set<Long> shiftIds = schedules.stream().map(HrmEmployeeScheduleDO::getShiftId)
+ .filter(Objects::nonNull).collect(Collectors.toSet());
+ Map<Long, HrmShiftDO> shiftMap = CollUtil.isEmpty(shiftIds) ? Collections.emptyMap()
+ : shiftMapper.selectBatchIds(shiftIds).stream()
+ .collect(Collectors.toMap(HrmShiftDO::getId, shift -> shift, (a, b) -> a));
+ int scheduledDays = 0;
+ BigDecimal totalWorkHours = BigDecimal.ZERO;
+ for (HrmEmployeeScheduleDO schedule : schedules) {
+ HrmShiftDO shift = schedule.getShiftId() == null ? null : shiftMap.get(schedule.getShiftId());
+ if (shift == null) {
+ continue;
+ }
+ scheduledDays++;
+ totalWorkHours = totalWorkHours.add(shift.getWorkHours() != null ? shift.getWorkHours() : BigDecimal.ZERO);
+ }
+ return new ScheduleStat(scheduledDays, totalWorkHours.setScale(2, RoundingMode.HALF_UP));
+ }
+
@Override
@Transactional(rollbackFor = Exception.class)
public void confirmSalaryCalculation(List<Long> ids) {
--
Gitblit v1.9.3