From ef1052e7bf9bf1765d61776bc49ce69a23c6cdee Mon Sep 17 00:00:00 2001
From: yuan <123@>
Date: 星期二, 20 十月 2026 00:25:29 +0800
Subject: [PATCH] feat: 添加加班时长和加班工资字段到员工工资明细
---
sql/20261019_staff_salary_detail_add_leave_attendance_deduct.sql | 8 +-
src/main/java/com/ruoyi/staff/pojo/StaffSalaryDetail.java | 6 ++
src/main/resources/mapper/staff/StaffSalaryDetailMapper.xml | 2
src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java | 3 +
src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java | 6 +
src/main/java/com/ruoyi/staff/dto/SalaryStat.java | 5 +
sql/product-inventory-management-new-pro.sql | 2
src/main/java/com/ruoyi/staff/excel/StaffSalaryExportData.java | 6 ++
src/main/java/com/ruoyi/staff/service/impl/SalaryStatServiceImpl.java | 117 +++++++++++++++++++++++++++++++++++++-
9 files changed, 145 insertions(+), 10 deletions(-)
diff --git a/sql/20261019_staff_salary_detail_add_leave_attendance_deduct.sql b/sql/20261019_staff_salary_detail_add_leave_attendance_deduct.sql
index 34bd4d8..70b8ca2 100644
--- a/sql/20261019_staff_salary_detail_add_leave_attendance_deduct.sql
+++ b/sql/20261019_staff_salary_detail_add_leave_attendance_deduct.sql
@@ -8,7 +8,7 @@
ADD COLUMN `absence_count` int NULL DEFAULT 0 COMMENT '缂哄嫟娆℃暟' AFTER `leave_count`,
ADD COLUMN `late_count` int NULL DEFAULT 0 COMMENT '杩熷埌娆℃暟' AFTER `absence_count`,
ADD COLUMN `early_count` int NULL DEFAULT 0 COMMENT '鏃╅��娆℃暟' AFTER `late_count`,
- ADD COLUMN `manual_deduct` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '搴旀墸宸ヨ祫锛堟墜鍔ㄥ~鍐欙級' AFTER `early_count`;
-
-ALTER TABLE staff_salary_detail
-MODIFY COLUMN remark text NULL COMMENT '澶囨敞';
+ ADD COLUMN `manual_deduct` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '搴旀墸宸ヨ祫锛堟墜鍔ㄥ~鍐欙級' AFTER `early_count`,
+ ADD COLUMN `overtime_hours` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '鍔犵彮鏃堕暱锛堝皬鏃讹級' AFTER `hourly_salary`,
+ ADD COLUMN `overtime_pay` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '鍔犵彮宸ヨ祫锛堟墜鍔ㄥ~鍐欙級' AFTER `overtime_hours`,
+ MODIFY COLUMN remark text NULL COMMENT '澶囨敞';
diff --git a/sql/product-inventory-management-new-pro.sql b/sql/product-inventory-management-new-pro.sql
index 5e24716..8dc9b70 100644
--- a/sql/product-inventory-management-new-pro.sql
+++ b/sql/product-inventory-management-new-pro.sql
@@ -4666,6 +4666,8 @@
`basic_salary` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '鍩烘湰宸ヨ祫',
`piece_salary` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '璁′欢宸ヨ祫',
`hourly_salary` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '璁℃椂宸ヨ祫',
+ `overtime_hours` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '鍔犵彮鏃堕暱锛堝皬鏃讹級',
+ `overtime_pay` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '鍔犵彮宸ヨ祫锛堟墜鍔ㄥ~鍐欙級',
`other_income` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '鍏朵粬鏀跺叆',
`social_personal` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '绀句繚涓汉',
`fund_personal` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '鍏Н閲戜釜浜�',
diff --git a/src/main/java/com/ruoyi/staff/dto/SalaryStat.java b/src/main/java/com/ruoyi/staff/dto/SalaryStat.java
index f107a17..9f5f728 100644
--- a/src/main/java/com/ruoyi/staff/dto/SalaryStat.java
+++ b/src/main/java/com/ruoyi/staff/dto/SalaryStat.java
@@ -3,6 +3,8 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
+import java.math.BigDecimal;
+
/**
* 鍛樺伐褰撴湀璇峰亣銆佽�冨嫟缁熻缁撴灉
*/
@@ -21,6 +23,9 @@
@Schema(description = "鏃╅��娆℃暟")
private Integer earlyCount;
+ @Schema(description = "鍔犵彮鏃堕暱锛堝皬鏃讹級")
+ private BigDecimal overtimeHours;
+
@Schema(description = "璇峰亣鍘熷洜锛屽娆¤鍋囩敤锛涙嫾鎺�")
private String leaveReason;
}
diff --git a/src/main/java/com/ruoyi/staff/excel/StaffSalaryExportData.java b/src/main/java/com/ruoyi/staff/excel/StaffSalaryExportData.java
index 67a1d1b..b2986a2 100644
--- a/src/main/java/com/ruoyi/staff/excel/StaffSalaryExportData.java
+++ b/src/main/java/com/ruoyi/staff/excel/StaffSalaryExportData.java
@@ -32,6 +32,12 @@
@Excel(name = "璁℃椂宸ヨ祫")
private BigDecimal hourlySalary;
+ @Excel(name = "鍔犵彮鏃堕暱")
+ private BigDecimal overtimeHours;
+
+ @Excel(name = "鍔犵彮宸ヨ祫")
+ private BigDecimal overtimePay;
+
@Excel(name = "鍏朵粬鏀跺叆")
private BigDecimal otherIncome;
diff --git a/src/main/java/com/ruoyi/staff/pojo/StaffSalaryDetail.java b/src/main/java/com/ruoyi/staff/pojo/StaffSalaryDetail.java
index b9cd992..96709bf 100644
--- a/src/main/java/com/ruoyi/staff/pojo/StaffSalaryDetail.java
+++ b/src/main/java/com/ruoyi/staff/pojo/StaffSalaryDetail.java
@@ -56,6 +56,12 @@
@Schema(description = "璁℃椂宸ヨ祫")
private BigDecimal hourlySalary;
+ @Schema(description = "鍔犵彮鏃堕暱锛堝皬鏃讹級")
+ private BigDecimal overtimeHours;
+
+ @Schema(description = "鍔犵彮宸ヨ祫锛堟墜鍔ㄥ~鍐欙級")
+ private BigDecimal overtimePay;
+
@Schema(description = "鍏朵粬鏀跺叆")
private BigDecimal otherIncome;
diff --git a/src/main/java/com/ruoyi/staff/service/impl/SalaryStatServiceImpl.java b/src/main/java/com/ruoyi/staff/service/impl/SalaryStatServiceImpl.java
index 85df88c..a854fd8 100644
--- a/src/main/java/com/ruoyi/staff/service/impl/SalaryStatServiceImpl.java
+++ b/src/main/java/com/ruoyi/staff/service/impl/SalaryStatServiceImpl.java
@@ -10,20 +10,33 @@
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.staff.dto.SalaryStat;
+import com.ruoyi.staff.mapper.PersonalAttendanceLocationConfigMapper;
import com.ruoyi.staff.mapper.PersonalAttendanceRecordsMapper;
+import com.ruoyi.staff.mapper.PersonalShiftMapper;
+import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
+import com.ruoyi.staff.pojo.PersonalShift;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.Duration;
import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* 鍛樺伐褰撴湀璇峰亣娆℃暟銆佺己鍕ゆ鏁般�佽繜鍒版鏁般�佹棭閫�娆℃暟缁熻
@@ -61,14 +74,20 @@
private static final String[] IGNORE_LABEL_KEYWORDS = {"鏃堕棿", "鏃ユ湡", "鐢宠浜�", "閮ㄩ棬", "闄勪欢", "鍥剧墖", "涓婁紶"};
/** 璇峰亣鍘熷洜瀛楁鍏抽敭瀛楋紝鍛戒腑鍒欎紭鍏堜綔涓鸿鍋囧師鍥� */
private static final String[] REASON_LABEL_KEYWORDS = {"鍘熷洜", "浜嬬敱", "鐞嗙敱", "璇存槑"};
+ /** 鍔犵彮璧风畻鍒嗛挓鏁帮細鏃╁埌銆佹櫄璧板潎闇�婊� 60 鍒嗛挓鎵嶈鍏ュ姞鐝椂闀� */
+ private static final long OVERTIME_MIN_MINUTES = 60L;
+ /** 鍒嗛挓鎹㈢畻灏忔椂鐨勯櫎鏁� */
+ private static final BigDecimal MINUTES_PER_HOUR = BigDecimal.valueOf(60);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final PersonalAttendanceRecordsMapper personalAttendanceRecordsMapper;
+ private final PersonalShiftMapper personalShiftMapper;
+ private final PersonalAttendanceLocationConfigMapper personalAttendanceLocationConfigMapper;
private final ApprovalInstanceMapper approvalInstanceMapper;
private final SysUserMapper sysUserMapper;
/**
- * 缁熻鍛樺伐鎸囧畾鏈堜唤鐨勮鍋囨鏁般�佺己鍕ゆ鏁般�佽繜鍒版鏁般�佹棭閫�娆℃暟鍜岃鍋囧師鍥�
+ * 缁熻鍛樺伐鎸囧畾鏈堜唤鐨勮鍋囨鏁般�佺己鍕ゆ鏁般�佽繜鍒版鏁般�佹棭閫�娆℃暟銆佸姞鐝椂闀垮拰璇峰亣鍘熷洜
*
* @param staffOnJobId staff_on_job 琛ㄤ富閿�
* @param staffName 鍛樺伐濮撳悕锛堢敤浜庡尮閰嶅鎵圭敵璇蜂汉锛�
@@ -81,12 +100,15 @@
stat.setAbsenceCount(0);
stat.setLateCount(0);
stat.setEarlyCount(0);
+ stat.setOvertimeHours(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
stat.setLeaveReason(null);
YearMonth month = parseMonth(salaryMonth);
if (staffOnJobId == null || month == null) {
return stat;
}
- fillAttendanceStat(stat, staffOnJobId, month);
+ List<PersonalAttendanceRecords> records = listAttendanceRecords(staffOnJobId, month);
+ fillAttendanceStat(stat, records);
+ fillOvertimeStat(stat, staffOnJobId, month, records);
List<ApprovalInstance> leaveList = listLeaveInstances(staffName, month);
stat.setLeaveCount(leaveList.size());
stat.setLeaveReason(joinLeaveReason(leaveList));
@@ -94,14 +116,20 @@
}
/**
- * 鑰冨嫟缁熻锛氱己鍕ゆ鏁般�佽繜鍒版鏁般�佹棭閫�娆℃暟锛堣繜鍒版棭閫�鍚屾椂璁″叆涓よ竟锛�
+ * 鏌ヨ鍛樺伐鎸囧畾鏈堜唤鐨勬墦鍗¤褰�
*/
- private void fillAttendanceStat(SalaryStat stat, Long staffOnJobId, YearMonth month) {
- List<PersonalAttendanceRecords> records = personalAttendanceRecordsMapper.selectList(
+ private List<PersonalAttendanceRecords> listAttendanceRecords(Long staffOnJobId, YearMonth month) {
+ return personalAttendanceRecordsMapper.selectList(
new LambdaQueryWrapper<PersonalAttendanceRecords>()
.eq(PersonalAttendanceRecords::getStaffOnJobId, staffOnJobId)
.ge(PersonalAttendanceRecords::getDate, month.atDay(1))
.le(PersonalAttendanceRecords::getDate, month.atEndOfMonth()));
+ }
+
+ /**
+ * 鑰冨嫟缁熻锛氱己鍕ゆ鏁般�佽繜鍒版鏁般�佹棭閫�娆℃暟锛堣繜鍒版棭閫�鍚屾椂璁″叆涓よ竟锛�
+ */
+ private void fillAttendanceStat(SalaryStat stat, List<PersonalAttendanceRecords> records) {
int absenceCount = 0;
int lateCount = 0;
int earlyCount = 0;
@@ -127,6 +155,85 @@
}
/**
+ * 鍔犵彮鏃堕暱缁熻锛�
+ * 鎸夊憳宸ュ綋鏈堟瘡澶╂帓鐝搴旂彮娆$殑涓婁笅鐝椂闂达紝涓庡綋澶╃殑鎵撳崱鏃堕棿姣旇緝锛�
+ * 鏃╁埌锛堟帓鐝笂鐝椂闂� - 瀹為檯鎵撳崱涓婄彮鏃堕棿锛夋弧 60 鍒嗛挓鎵嶈鍏ワ紝
+ * 鏅氳蛋锛堝疄闄呮墦鍗′笅鐝椂闂� - 鎺掔彮涓嬬彮鏃堕棿锛夋弧 60 鍒嗛挓鎵嶈鍏ワ紝
+ * 涓ゆ鍒嗛挓鏁扮疮鍔犲悗鎹㈢畻鎴愬皬鏃讹紝淇濈暀涓や綅灏忔暟銆�
+ */
+ private void fillOvertimeStat(SalaryStat stat, Long staffOnJobId, YearMonth month,
+ List<PersonalAttendanceRecords> records) {
+ stat.setOvertimeHours(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
+ if (records.isEmpty()) {
+ return;
+ }
+ List<PersonalShift> shifts = personalShiftMapper.selectList(
+ new LambdaQueryWrapper<PersonalShift>()
+ .eq(PersonalShift::getStaffOnJobId, staffOnJobId)
+ .isNotNull(PersonalShift::getPersonalAttendanceLocationConfigId)
+ .ge(PersonalShift::getWorkTime, month.atDay(1).atStartOfDay())
+ .lt(PersonalShift::getWorkTime, month.plusMonths(1).atDay(1).atStartOfDay()));
+ if (shifts.isEmpty()) {
+ return;
+ }
+ Set<Integer> configIds = shifts.stream()
+ .map(PersonalShift::getPersonalAttendanceLocationConfigId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ if (configIds.isEmpty()) {
+ return;
+ }
+ Map<Integer, PersonalAttendanceLocationConfig> configMap = personalAttendanceLocationConfigMapper
+ .selectBatchIds(configIds).stream()
+ .collect(Collectors.toMap(PersonalAttendanceLocationConfig::getId, config -> config,
+ (first, second) -> first));
+ Map<LocalDate, PersonalAttendanceRecords> recordMap = records.stream()
+ .filter(record -> record.getDate() != null)
+ .collect(Collectors.toMap(PersonalAttendanceRecords::getDate, record -> record,
+ (first, second) -> first));
+ Map<LocalDate, PersonalShift> shiftMap = new HashMap<>();
+ for (PersonalShift shift : shifts) {
+ if (shift.getWorkTime() != null) {
+ shiftMap.putIfAbsent(shift.getWorkTime().toLocalDate(), shift);
+ }
+ }
+ long overtimeMinutes = 0L;
+ for (Map.Entry<LocalDate, PersonalShift> entry : shiftMap.entrySet()) {
+ LocalDate workDate = entry.getKey();
+ PersonalAttendanceRecords record = recordMap.get(workDate);
+ PersonalAttendanceLocationConfig config =
+ configMap.get(entry.getValue().getPersonalAttendanceLocationConfigId());
+ if (record == null || config == null) {
+ continue;
+ }
+ LocalTime startAt = config.getStartAt();
+ LocalTime endAt = config.getEndAt();
+ if (startAt != null && record.getWorkStartAt() != null) {
+ long earlyMinutes = Duration.between(record.getWorkStartAt(),
+ LocalDateTime.of(workDate, startAt)).toMinutes();
+ if (earlyMinutes >= OVERTIME_MIN_MINUTES) {
+ overtimeMinutes += earlyMinutes;
+ }
+ }
+ if (endAt != null && record.getWorkEndAt() != null) {
+ LocalDateTime scheduledEnd = LocalDateTime.of(workDate, endAt);
+ // 璺ㄥぉ鐝锛堝 22:00-娆℃棩 06:00锛変笅鐝椂闂存寜娆℃棩璁$畻
+ if (startAt == null || !endAt.isAfter(startAt)) {
+ scheduledEnd = scheduledEnd.plusDays(1);
+ }
+ long lateMinutes = Duration.between(scheduledEnd, record.getWorkEndAt()).toMinutes();
+ if (lateMinutes >= OVERTIME_MIN_MINUTES) {
+ overtimeMinutes += lateMinutes;
+ }
+ }
+ }
+ if (overtimeMinutes > 0) {
+ stat.setOvertimeHours(BigDecimal.valueOf(overtimeMinutes)
+ .divide(MINUTES_PER_HOUR, 2, RoundingMode.HALF_UP));
+ }
+ }
+
+ /**
* 鏌ヨ鍛樺伐褰掑睘褰撴湀鐨勫凡閫氳繃璇峰亣鐢宠锛屾寜璇峰亣鏃堕棿鍗囧簭
* <p>
* 鍏堟寜鐢宠浜烘煡鍑烘墍鏈夊凡閫氳繃鐨勮鍋囷紙璇峰亣绠$悊銆佽鍋囧鎵逛袱绉嶄笟鍔$被鍨嬮兘鍏煎锛夛紝
diff --git a/src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java b/src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java
index fe84134..c7ad83a 100644
--- a/src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java
+++ b/src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java
@@ -174,6 +174,8 @@
: calculateLatestDeptSocialSupplementAmount(staffOnJobDto);
BigDecimal pieceSalary = normalizeMoney(getBigDecimal(map, "pieceSalary"));
BigDecimal hourlySalary = normalizeMoney(getBigDecimal(map, "hourlySalary"));
+ // 鍔犵彮宸ヨ祫锛氭墜宸ュ~鍐欙紝涓嶆寜鍊嶇巼鑷姩璁$畻
+ BigDecimal overtimePay = normalizeMoney(getBigDecimal(map, "overtimePay"));
BigDecimal socialPersonal = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
BigDecimal fundPersonal = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
@@ -204,7 +206,7 @@
}
}
- BigDecimal grossSalary = basicSalary.add(pieceSalary).add(hourlySalary).add(otherIncome);
+ BigDecimal grossSalary = basicSalary.add(pieceSalary).add(hourlySalary).add(overtimePay).add(otherIncome);
BigDecimal salaryTax = TaxCalculator.calculateMonthlyTax(
grossSalary,
socialPersonal.add(socialSupplementAmount),
@@ -231,6 +233,8 @@
map.put("basicSalary", basicSalary);
map.put("pieceSalary", pieceSalary);
map.put("hourlySalary", hourlySalary);
+ map.put("overtimeHours", salaryStat.getOvertimeHours());
+ map.put("overtimePay", overtimePay);
map.put("otherIncome", otherIncome);
map.put("socialPersonal", socialPersonal);
map.put("fundPersonal", fundPersonal);
diff --git a/src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java b/src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java
index f15b9b9..f3974d6 100644
--- a/src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java
+++ b/src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java
@@ -210,6 +210,7 @@
}
map.put("pieceSalary", staffSalaryCalculateDto.getPieceSalary());
map.put("hourlySalary", staffSalaryCalculateDto.getHourlySalary());
+ map.put("overtimePay", staffSalaryCalculateDto.getOvertimePay());
map.put("otherIncome", staffSalaryCalculateDto.getOtherIncome());
map.put("socialPersonal", staffSalaryCalculateDto.getSocialPersonal());
map.put("fundPersonal", staffSalaryCalculateDto.getFundPersonal());
@@ -256,6 +257,8 @@
exportData.setBasicSalary(detail.getBasicSalary());
exportData.setPieceSalary(detail.getPieceSalary());
exportData.setHourlySalary(detail.getHourlySalary());
+ exportData.setOvertimeHours(detail.getOvertimeHours() == null ? BigDecimal.ZERO : detail.getOvertimeHours());
+ exportData.setOvertimePay(detail.getOvertimePay() == null ? BigDecimal.ZERO : detail.getOvertimePay());
exportData.setOtherIncome(detail.getOtherIncome());
exportData.setSocialPersonal(detail.getSocialPersonal());
exportData.setFundPersonal(detail.getFundPersonal());
diff --git a/src/main/resources/mapper/staff/StaffSalaryDetailMapper.xml b/src/main/resources/mapper/staff/StaffSalaryDetailMapper.xml
index f029b09..f3647df 100644
--- a/src/main/resources/mapper/staff/StaffSalaryDetailMapper.xml
+++ b/src/main/resources/mapper/staff/StaffSalaryDetailMapper.xml
@@ -13,6 +13,8 @@
<result column="basic_salary" property="basicSalary" />
<result column="piece_salary" property="pieceSalary" />
<result column="hourly_salary" property="hourlySalary" />
+ <result column="overtime_hours" property="overtimeHours" />
+ <result column="overtime_pay" property="overtimePay" />
<result column="other_income" property="otherIncome" />
<result column="social_personal" property="socialPersonal" />
<result column="fund_personal" property="fundPersonal" />
--
Gitblit v1.9.3