From 9697bac35fe2c74811afaf66f9cf28b758f3a386 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期五, 28 八月 2026 15:21:22 +0800
Subject: [PATCH] feat(hrm): 新增排班功能,薪酬核算按排班工时
---
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmShiftSaveReqVO.java | 45 ++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmShiftServiceImpl.java | 100 +++++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java | 10
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java | 10
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmEmployeeScheduleController.java | 46 ++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmEmployeeScheduleRespVO.java | 39 ++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeePageReqVO.java | 3
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmShiftPageReqVO.java | 21 +
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmEmployeeScheduleDO.java | 56 +++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmEmployeeScheduleListReqVO.java | 21 +
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryCalculationDO.java | 5
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmShiftService.java | 47 ++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmShiftDO.java | 73 ++++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmEmployeeScheduleServiceImpl.java | 143 ++++++++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmShiftController.java | 100 +++++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmEmployeeScheduleSaveReqVO.java | 27 +
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmShiftMapper.java | 33 +
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmShiftRespVO.java | 56 +++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationRespVO.java | 4
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java | 37 ++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmEmployeeScheduleService.java | 26 +
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmEmployeeScheduleMapper.java | 51 +++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java | 50 ++
23 files changed, 999 insertions(+), 4 deletions(-)
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
index cd8b6d8..aeedd30 100644
--- a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -119,4 +119,14 @@
ErrorCode SALARY_STRUCTURE_NAME_EXISTS = new ErrorCode(1_030_005_025, "钖叕缁撴瀯鍚嶇О宸插瓨鍦�");
ErrorCode SALARY_STRUCTURE_IN_USE = new ErrorCode(1_030_005_026, "钖叕缁撴瀯姝e湪琚憳宸ヤ娇鐢紝鏃犳硶鍒犻櫎");
+ // ========== 鐝绠$悊 1-030-009-000 ==========
+ ErrorCode SHIFT_NOT_EXISTS = new ErrorCode(1_030_009_000, "鐝涓嶅瓨鍦�");
+ ErrorCode SHIFT_NAME_EXISTS = new ErrorCode(1_030_009_001, "鐝鍚嶇О宸插瓨鍦�");
+ ErrorCode SHIFT_IN_USE = new ErrorCode(1_030_009_002, "鐝宸茶鎺掔彮浣跨敤锛屾棤娉曞垹闄�");
+
+ // ========== 鎺掔彮绠$悊 1-030-010-000 ==========
+ ErrorCode SCHEDULE_ITEM_LIST_EMPTY = new ErrorCode(1_030_010_000, "鎺掔彮鏁版嵁涓嶈兘涓虹┖");
+ ErrorCode SCHEDULE_SHIFT_NOT_EXISTS = new ErrorCode(1_030_010_001, "鎺掔彮寮曠敤鐨勭彮娆′笉瀛樺湪");
+ ErrorCode SCHEDULE_EMPLOYEE_NOT_EXISTS = new ErrorCode(1_030_010_002, "鎺掔彮寮曠敤鐨勫憳宸ヤ笉瀛樺湪");
+
}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmEmployeeScheduleController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmEmployeeScheduleController.java
new file mode 100644
index 0000000..d9c362a
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmEmployeeScheduleController.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleListReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleSaveReqVO;
+import cn.iocoder.yudao.module.hrm.service.attendance.HrmEmployeeScheduleService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鍛樺伐鎺掔彮")
+@RestController
+@RequestMapping("/hrm/employee-schedule")
+@Validated
+public class HrmEmployeeScheduleController {
+
+ @Resource
+ private HrmEmployeeScheduleService employeeScheduleService;
+
+ @GetMapping("/list")
+ @Operation(summary = "鑾峰緱鏌愭湀鍛樺伐鎺掔彮鍒楄〃")
+ @PreAuthorize("@ss.hasPermission('hrm:employee-schedule:query')")
+ public CommonResult<List<HrmEmployeeScheduleRespVO>> getEmployeeScheduleList(
+ @Valid HrmEmployeeScheduleListReqVO reqVO) {
+ return success(employeeScheduleService.getScheduleList(reqVO));
+ }
+
+ @PostMapping("/save")
+ @Operation(summary = "鎵归噺淇濆瓨鍛樺伐鎺掔彮锛堟暣鏈堣鐩栧紡锛�")
+ @PreAuthorize("@ss.hasPermission('hrm:employee-schedule:save')")
+ public CommonResult<Boolean> saveEmployeeSchedule(
+ @Valid @RequestBody List<@Valid HrmEmployeeScheduleSaveReqVO> saveReqVOList) {
+ employeeScheduleService.saveSchedule(saveReqVOList);
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmShiftController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmShiftController.java
new file mode 100644
index 0000000..9d6e055
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmShiftController.java
@@ -0,0 +1,100 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmShiftPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmShiftRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmShiftSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmShiftDO;
+import cn.iocoder.yudao.module.hrm.service.attendance.HrmShiftService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鐝瀹氫箟")
+@RestController
+@RequestMapping("/hrm/shift")
+@Validated
+public class HrmShiftController {
+
+ @Resource
+ private HrmShiftService shiftService;
+
+ @PostMapping("/create")
+ @Operation(summary = "鏂板鐝")
+ @PreAuthorize("@ss.hasPermission('hrm:shift:create')")
+ public CommonResult<Long> createShift(@Valid @RequestBody HrmShiftSaveReqVO createReqVO) {
+ return success(shiftService.createShift(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊鐝")
+ @PreAuthorize("@ss.hasPermission('hrm:shift:update')")
+ public CommonResult<Boolean> updateShift(@Valid @RequestBody HrmShiftSaveReqVO updateReqVO) {
+ shiftService.updateShift(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎鐝")
+ @Parameter(name = "id", description = "鐝ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:shift:delete')")
+ public CommonResult<Boolean> deleteShift(@RequestParam("id") Long id) {
+ shiftService.deleteShift(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱鐝")
+ @Parameter(name = "id", description = "鐝ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:shift:query')")
+ public CommonResult<HrmShiftRespVO> getShift(@RequestParam("id") Long id) {
+ HrmShiftDO shift = shiftService.getShift(id);
+ return success(BeanUtils.toBean(shift, HrmShiftRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱鐝鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('hrm:shift:query')")
+ public CommonResult<PageResult<HrmShiftRespVO>> getShiftPage(@Valid HrmShiftPageReqVO pageReqVO) {
+ PageResult<HrmShiftDO> pageResult = shiftService.getShiftPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, HrmShiftRespVO.class));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "鑾峰緱鐝鍒楄〃锛堝惎鐢ㄧ姸鎬侊級")
+ @PreAuthorize("@ss.hasPermission('hrm:shift:query')")
+ public CommonResult<List<HrmShiftRespVO>> getShiftList() {
+ List<HrmShiftDO> list = shiftService.getShiftList();
+ return success(BeanUtils.toBean(list, HrmShiftRespVO.class));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "瀵煎嚭鐝 Excel")
+ @PreAuthorize("@ss.hasPermission('hrm:shift:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportShiftExcel(@Valid HrmShiftPageReqVO pageReqVO,
+ HttpServletResponse response) throws IOException {
+ pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ PageResult<HrmShiftDO> pageResult = shiftService.getShiftPage(pageReqVO);
+ List<HrmShiftRespVO> data = BeanUtils.toBean(pageResult.getList(), HrmShiftRespVO.class);
+ ExcelUtils.write(response, "鐝.xls", "鏁版嵁", HrmShiftRespVO.class, data);
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmEmployeeScheduleListReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmEmployeeScheduleListReqVO.java
new file mode 100644
index 0000000..8653a66
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmEmployeeScheduleListReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐鎺掔彮鏌ヨ Request VO")
+@Data
+public class HrmEmployeeScheduleListReqVO {
+
+ @Schema(description = "鏈堜唤锛屾牸寮忥細yyyy-MM", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-08")
+ @NotBlank(message = "鏈堜唤涓嶈兘涓虹┖")
+ private String yearMonth;
+
+ @Schema(description = "閮ㄩ棬ID", example = "1")
+ private Long deptId;
+
+ @Schema(description = "鍛樺伐ID", example = "1")
+ private Long employeeId;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmEmployeeScheduleRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmEmployeeScheduleRespVO.java
new file mode 100644
index 0000000..c7a5458
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmEmployeeScheduleRespVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐鎺掔彮 Response VO")
+@Data
+public class HrmEmployeeScheduleRespVO {
+
+ @Schema(description = "鎺掔彮ID")
+ private Long id;
+
+ @Schema(description = "鍛樺伐ID")
+ private Long employeeId;
+
+ @Schema(description = "鍛樺伐濮撳悕")
+ private String employeeName;
+
+ @Schema(description = "鍛樺伐宸ュ彿")
+ private String employeeNo;
+
+ @Schema(description = "鎺掔彮鏃ユ湡")
+ private LocalDate date;
+
+ @Schema(description = "鐝ID锛屼负绌鸿〃绀轰紤鎭�")
+ private Long shiftId;
+
+ @Schema(description = "鐝鍚嶇О")
+ private String shiftName;
+
+ @Schema(description = "鐝棰滆壊")
+ private String shiftColor;
+
+ @Schema(description = "澶囨敞")
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmEmployeeScheduleSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmEmployeeScheduleSaveReqVO.java
new file mode 100644
index 0000000..feca57c
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmEmployeeScheduleSaveReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍛樺伐鎺掔彮淇濆瓨椤� Request VO")
+@Data
+public class HrmEmployeeScheduleSaveReqVO {
+
+ @Schema(description = "鍛樺伐ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "鍛樺伐涓嶈兘涓虹┖")
+ private Long employeeId;
+
+ @Schema(description = "鎺掔彮鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-08-01")
+ @NotNull(message = "鎺掔彮鏃ユ湡涓嶈兘涓虹┖")
+ private LocalDate date;
+
+ @Schema(description = "鐝ID锛屼负绌鸿〃绀轰紤鎭�", example = "1")
+ private Long shiftId;
+
+ @Schema(description = "澶囨敞", example = "璋冧紤")
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmShiftPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmShiftPageReqVO.java
new file mode 100644
index 0000000..4506ffb
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmShiftPageReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "绠$悊鍚庡彴 - 鐝鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class HrmShiftPageReqVO extends PageParam {
+
+ @Schema(description = "鐝鍚嶇О", example = "鐧界彮")
+ private String name;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
+ private Integer status;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmShiftRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmShiftRespVO.java
new file mode 100644
index 0000000..6117507
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmShiftRespVO.java
@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鐝 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class HrmShiftRespVO {
+
+ @Schema(description = "鐝ID")
+ @ExcelProperty("鐝ID")
+ private Long id;
+
+ @Schema(description = "鐝鍚嶇О")
+ @ExcelProperty("鐝鍚嶇О")
+ private String name;
+
+ @Schema(description = "涓婄彮鏃堕棿")
+ @ExcelProperty("涓婄彮鏃堕棿")
+ private LocalTime startTime;
+
+ @Schema(description = "涓嬬彮鏃堕棿")
+ @ExcelProperty("涓嬬彮鏃堕棿")
+ private LocalTime endTime;
+
+ @Schema(description = "鏍囧噯宸ユ椂锛堝皬鏃讹級")
+ @ExcelProperty("鏍囧噯宸ユ椂")
+ private BigDecimal workHours;
+
+ @Schema(description = "鏄惁璺ㄥぉ")
+ @ExcelProperty("鏄惁璺ㄥぉ")
+ private Boolean crossDay;
+
+ @Schema(description = "鏍囩棰滆壊锛堝崄鍏繘鍒讹級")
+ @ExcelProperty("鏍囩棰滆壊")
+ private String color;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤")
+ private Integer status;
+
+ @Schema(description = "澶囨敞")
+ @ExcelProperty("澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ @ExcelProperty("鍒涘缓鏃堕棿")
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmShiftSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmShiftSaveReqVO.java
new file mode 100644
index 0000000..646e6c9
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmShiftSaveReqVO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鐝鏂板/淇敼 Request VO")
+@Data
+public class HrmShiftSaveReqVO {
+
+ @Schema(description = "鐝ID锛堜慨鏀规椂蹇呭~锛�", example = "1")
+ private Long id;
+
+ @Schema(description = "鐝鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鐧界彮")
+ @NotBlank(message = "鐝鍚嶇О涓嶈兘涓虹┖")
+ private String name;
+
+ @Schema(description = "涓婄彮鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED, example = "08:00:00")
+ @NotNull(message = "涓婄彮鏃堕棿涓嶈兘涓虹┖")
+ private LocalTime startTime;
+
+ @Schema(description = "涓嬬彮鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED, example = "17:00:00")
+ @NotNull(message = "涓嬬彮鏃堕棿涓嶈兘涓虹┖")
+ private LocalTime endTime;
+
+ @Schema(description = "鏍囧噯宸ユ椂锛堝皬鏃讹級", example = "8")
+ private BigDecimal workHours;
+
+ @Schema(description = "鏄惁璺ㄥぉ", example = "false")
+ private Boolean crossDay;
+
+ @Schema(description = "鏍囩棰滆壊锛堝崄鍏繘鍒讹級", example = "#1677ff")
+ private String color;
+
+ @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
+ private Integer status;
+
+ @Schema(description = "澶囨敞", example = "鏍囧噯鐧界彮")
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeePageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeePageReqVO.java
index 391bea8..7693ebf 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeePageReqVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeePageReqVO.java
@@ -24,4 +24,7 @@
@Schema(description = "鍛樺伐鐘舵��", example = "1")
private Integer employeeStatus;
+ @Schema(description = "鍏抽敭璇嶏紙妯$硦鍖归厤鍛樺伐缂栧彿/濮撳悕/鎵嬫満鍙凤級", example = "寮犱笁")
+ private String keyword;
+
}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationRespVO.java
index 945b210..16836b7 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationRespVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryCalculationRespVO.java
@@ -97,6 +97,10 @@
@ExcelProperty("鍔犵彮鏃堕暱")
private BigDecimal overtimeHours;
+ @Schema(description = "鎺掔彮鎬诲伐鏃�")
+ @ExcelProperty("鎺掔彮鎬诲伐鏃�")
+ private BigDecimal totalWorkHours;
+
@Schema(description = "鐘舵��")
@ExcelProperty(value = "鐘舵��", converter = DictConvert.class)
@DictFormat("hrm_salary_calculation_status")
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmEmployeeScheduleDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmEmployeeScheduleDO.java
new file mode 100644
index 0000000..01e84a1
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmEmployeeScheduleDO.java
@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.attendance;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDate;
+
+/**
+ * 鍛樺伐鎺掔彮鏄庣粏 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("hrm_employee_schedule")
+@KeySequence("hrm_employee_schedule_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmEmployeeScheduleDO extends BaseDO {
+
+ /**
+ * 鎺掔彮ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鍛樺伐ID
+ *
+ * 鍏宠仈 HrmEmployeeDO#getId()
+ */
+ private Long employeeId;
+
+ /**
+ * 鎺掔彮鏃ユ湡
+ */
+ private LocalDate date;
+
+ /**
+ * 鐝ID
+ *
+ * 鍏宠仈 HrmShiftDO#getId()锛屼负绌鸿〃绀轰紤鎭�
+ */
+ private Long shiftId;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmShiftDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmShiftDO.java
new file mode 100644
index 0000000..be1ad37
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmShiftDO.java
@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.attendance;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalTime;
+
+/**
+ * 鐝瀹氫箟 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("hrm_shift")
+@KeySequence("hrm_shift_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmShiftDO extends BaseDO {
+
+ /**
+ * 鐝ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鐝鍚嶇О
+ */
+ private String name;
+
+ /**
+ * 涓婄彮鏃堕棿
+ */
+ private LocalTime startTime;
+
+ /**
+ * 涓嬬彮鏃堕棿
+ */
+ private LocalTime endTime;
+
+ /**
+ * 鏍囧噯宸ユ椂锛堝皬鏃讹級
+ */
+ private BigDecimal workHours;
+
+ /**
+ * 鏄惁璺ㄥぉ
+ */
+ private Boolean crossDay;
+
+ /**
+ * 鏍囩棰滆壊锛堝崄鍏繘鍒讹級
+ */
+ private String color;
+
+ /**
+ * 鐘舵�侊細0-鍚敤锛�1-绂佺敤
+ */
+ private Integer status;
+
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryCalculationDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryCalculationDO.java
index 187e15a..2f19cc4 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryCalculationDO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryCalculationDO.java
@@ -123,6 +123,11 @@
private BigDecimal overtimeHours;
/**
+ * 鎺掔彮鎬诲伐鏃讹紙灏忔椂锛�
+ */
+ private BigDecimal totalWorkHours;
+
+ /**
* 鐘舵�侊細0-寰呯‘璁わ紝10-宸茬‘璁わ紝20-宸插彂鏀�
*/
private Integer status;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmEmployeeScheduleMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmEmployeeScheduleMapper.java
new file mode 100644
index 0000000..53b9b00
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmEmployeeScheduleMapper.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.attendance;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmEmployeeScheduleDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.time.LocalDate;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 鍛樺伐鎺掔彮鏄庣粏 Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface HrmEmployeeScheduleMapper extends BaseMapperX<HrmEmployeeScheduleDO> {
+
+ default HrmEmployeeScheduleDO selectByEmployeeIdAndDate(Long employeeId, LocalDate date) {
+ return selectOne(new LambdaQueryWrapperX<HrmEmployeeScheduleDO>()
+ .eq(HrmEmployeeScheduleDO::getEmployeeId, employeeId)
+ .eq(HrmEmployeeScheduleDO::getDate, date));
+ }
+
+ default List<HrmEmployeeScheduleDO> selectListByPeriod(LocalDate startDate, LocalDate endDate) {
+ return selectList(new LambdaQueryWrapperX<HrmEmployeeScheduleDO>()
+ .between(HrmEmployeeScheduleDO::getDate, startDate, endDate)
+ .orderByAsc(HrmEmployeeScheduleDO::getDate));
+ }
+
+ default List<HrmEmployeeScheduleDO> selectListByEmployeeIdsAndPeriod(Collection<Long> employeeIds, LocalDate startDate, LocalDate endDate) {
+ return selectList(new LambdaQueryWrapperX<HrmEmployeeScheduleDO>()
+ .in(HrmEmployeeScheduleDO::getEmployeeId, employeeIds)
+ .between(HrmEmployeeScheduleDO::getDate, startDate, endDate)
+ .orderByAsc(HrmEmployeeScheduleDO::getDate));
+ }
+
+ default List<HrmEmployeeScheduleDO> selectListByEmployeeIdAndPeriod(Long employeeId, LocalDate startDate, LocalDate endDate) {
+ return selectList(new LambdaQueryWrapperX<HrmEmployeeScheduleDO>()
+ .eq(HrmEmployeeScheduleDO::getEmployeeId, employeeId)
+ .between(HrmEmployeeScheduleDO::getDate, startDate, endDate)
+ .orderByAsc(HrmEmployeeScheduleDO::getDate));
+ }
+
+ default Long selectCountByShiftId(Long shiftId) {
+ return selectCount(new LambdaQueryWrapperX<HrmEmployeeScheduleDO>()
+ .eq(HrmEmployeeScheduleDO::getShiftId, shiftId));
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmShiftMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmShiftMapper.java
new file mode 100644
index 0000000..030d58f
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/attendance/HrmShiftMapper.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.attendance;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmShiftPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmShiftDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 鐝瀹氫箟 Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface HrmShiftMapper extends BaseMapperX<HrmShiftDO> {
+
+ default PageResult<HrmShiftDO> selectPage(HrmShiftPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<HrmShiftDO>()
+ .likeIfPresent(HrmShiftDO::getName, reqVO.getName())
+ .eqIfPresent(HrmShiftDO::getStatus, reqVO.getStatus())
+ .orderByAsc(HrmShiftDO::getStartTime));
+ }
+
+ default List<HrmShiftDO> selectListByStatus(Integer status) {
+ return selectList(new LambdaQueryWrapperX<HrmShiftDO>()
+ .eqIfPresent(HrmShiftDO::getStatus, status)
+ .orderByAsc(HrmShiftDO::getStartTime));
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java
index 19ab24b..0a48fa0 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.hrm.dal.mysql.employee;
+import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -7,6 +8,7 @@
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
import org.apache.ibatis.annotations.Mapper;
+import java.util.Collection;
import java.util.List;
/**
@@ -46,12 +48,41 @@
}
default PageResult<HrmEmployeeDO> selectPage(HrmEmployeePageReqVO reqVO) {
- return selectPage(reqVO, new LambdaQueryWrapperX<HrmEmployeeDO>()
+ LambdaQueryWrapperX<HrmEmployeeDO> wrapper = new LambdaQueryWrapperX<HrmEmployeeDO>()
.likeIfPresent(HrmEmployeeDO::getEmployeeNo, reqVO.getEmployeeNo())
.likeIfPresent(HrmEmployeeDO::getName, reqVO.getName())
.eqIfPresent(HrmEmployeeDO::getDeptId, reqVO.getDeptId())
- .eqIfPresent(HrmEmployeeDO::getEmployeeStatus, reqVO.getEmployeeStatus())
- .orderByDesc(HrmEmployeeDO::getId));
+ .eqIfPresent(HrmEmployeeDO::getEmployeeStatus, reqVO.getEmployeeStatus());
+ appendKeyword(wrapper, reqVO.getKeyword());
+ wrapper.orderByDesc(HrmEmployeeDO::getId);
+ return selectPage(reqVO, wrapper);
+ }
+
+ /**
+ * 鎸夐儴闂ㄩ泦鍚堬紙鍚瓙閮ㄩ棬锛夊垎椤垫煡璇㈠憳宸�
+ */
+ default PageResult<HrmEmployeeDO> selectPageByDeptIds(HrmEmployeePageReqVO reqVO, Collection<Long> deptIds) {
+ LambdaQueryWrapperX<HrmEmployeeDO> wrapper = new LambdaQueryWrapperX<HrmEmployeeDO>()
+ .likeIfPresent(HrmEmployeeDO::getEmployeeNo, reqVO.getEmployeeNo())
+ .likeIfPresent(HrmEmployeeDO::getName, reqVO.getName())
+ .inIfPresent(HrmEmployeeDO::getDeptId, deptIds)
+ .eqIfPresent(HrmEmployeeDO::getEmployeeStatus, reqVO.getEmployeeStatus());
+ appendKeyword(wrapper, reqVO.getKeyword());
+ wrapper.orderByDesc(HrmEmployeeDO::getId);
+ return selectPage(reqVO, wrapper);
+ }
+
+ /**
+ * 鍏抽敭璇嶆ā绯婂尮閰嶅憳宸ョ紪鍙� / 濮撳悕 / 鎵嬫満鍙�
+ */
+ private void appendKeyword(LambdaQueryWrapperX<HrmEmployeeDO> wrapper, String keyword) {
+ if (StrUtil.isBlank(keyword)) {
+ return;
+ }
+ String kw = keyword.trim();
+ wrapper.and(w -> w.like(HrmEmployeeDO::getEmployeeNo, kw)
+ .or().like(HrmEmployeeDO::getName, kw)
+ .or().like(HrmEmployeeDO::getPhone, kw));
}
default List<HrmEmployeeDO> selectListByStatus(Integer employeeStatus) {
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmEmployeeScheduleService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmEmployeeScheduleService.java
new file mode 100644
index 0000000..c948a29
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmEmployeeScheduleService.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.hrm.service.attendance;
+
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleListReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleSaveReqVO;
+
+import java.util.List;
+
+/**
+ * 鍛樺伐鎺掔彮 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface HrmEmployeeScheduleService {
+
+ /**
+ * 鏌ヨ鏌愭湀鎺掔彮鍒楄〃
+ */
+ List<HrmEmployeeScheduleRespVO> getScheduleList(HrmEmployeeScheduleListReqVO reqVO);
+
+ /**
+ * 鎵归噺淇濆瓨鎺掔彮锛堟暣鏈堣鐩栧紡锛氭湁鐝 upsert锛屾棤鐝鍒犻櫎锛�
+ */
+ void saveSchedule(List<HrmEmployeeScheduleSaveReqVO> saveReqVOList);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmEmployeeScheduleServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmEmployeeScheduleServiceImpl.java
new file mode 100644
index 0000000..36eb169
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmEmployeeScheduleServiceImpl.java
@@ -0,0 +1,143 @@
+package cn.iocoder.yudao.module.hrm.service.attendance;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleListReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleSaveReqVO;
+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.mysql.attendance.HrmEmployeeScheduleMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmShiftMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
+
+/**
+ * 鍛樺伐鎺掔彮 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class HrmEmployeeScheduleServiceImpl implements HrmEmployeeScheduleService {
+
+ @Resource
+ private HrmEmployeeScheduleMapper employeeScheduleMapper;
+ @Resource
+ private HrmEmployeeMapper employeeMapper;
+ @Resource
+ private HrmShiftMapper shiftMapper;
+
+ @Override
+ public List<HrmEmployeeScheduleRespVO> getScheduleList(HrmEmployeeScheduleListReqVO reqVO) {
+ YearMonth yearMonth = YearMonth.parse(reqVO.getYearMonth());
+ LocalDate startDate = yearMonth.atDay(1);
+ LocalDate endDate = yearMonth.atEndOfMonth();
+
+ // 1. 鎸夋潯浠舵煡璇㈡帓鐝褰�
+ List<HrmEmployeeScheduleDO> list;
+ if (reqVO.getEmployeeId() != null) {
+ list = employeeScheduleMapper.selectListByEmployeeIdAndPeriod(reqVO.getEmployeeId(), startDate, endDate);
+ } else if (reqVO.getDeptId() != null) {
+ List<HrmEmployeeDO> employees = employeeMapper.selectByDeptId(reqVO.getDeptId());
+ Set<Long> employeeIds = employees.stream().map(HrmEmployeeDO::getId).collect(Collectors.toSet());
+ list = CollUtil.isEmpty(employeeIds) ? Collections.emptyList()
+ : employeeScheduleMapper.selectListByEmployeeIdsAndPeriod(employeeIds, startDate, endDate);
+ } else {
+ list = employeeScheduleMapper.selectListByPeriod(startDate, endDate);
+ }
+ if (CollUtil.isEmpty(list)) {
+ return Collections.emptyList();
+ }
+
+ // 2. 鎷兼帴鍛樺伐淇℃伅涓庣彮娆′俊鎭�
+ Set<Long> employeeIds = list.stream().map(HrmEmployeeScheduleDO::getEmployeeId)
+ .filter(Objects::nonNull).collect(Collectors.toSet());
+ Set<Long> shiftIds = list.stream().map(HrmEmployeeScheduleDO::getShiftId)
+ .filter(Objects::nonNull).collect(Collectors.toSet());
+ Map<Long, HrmEmployeeDO> employeeMap = CollUtil.isEmpty(employeeIds) ? Collections.emptyMap()
+ : employeeMapper.selectBatchIds(employeeIds).stream()
+ .collect(Collectors.toMap(HrmEmployeeDO::getId, Function.identity(), (a, b) -> a));
+ Map<Long, HrmShiftDO> shiftMap = CollUtil.isEmpty(shiftIds) ? Collections.emptyMap()
+ : shiftMapper.selectBatchIds(shiftIds).stream()
+ .collect(Collectors.toMap(HrmShiftDO::getId, Function.identity(), (a, b) -> a));
+
+ return list.stream().map(schedule -> {
+ HrmEmployeeScheduleRespVO respVO = new HrmEmployeeScheduleRespVO();
+ respVO.setId(schedule.getId());
+ respVO.setEmployeeId(schedule.getEmployeeId());
+ HrmEmployeeDO employee = schedule.getEmployeeId() == null ? null : employeeMap.get(schedule.getEmployeeId());
+ if (employee != null) {
+ respVO.setEmployeeName(employee.getName());
+ respVO.setEmployeeNo(employee.getEmployeeNo());
+ }
+ respVO.setDate(schedule.getDate());
+ respVO.setShiftId(schedule.getShiftId());
+ HrmShiftDO shift = schedule.getShiftId() == null ? null : shiftMap.get(schedule.getShiftId());
+ if (shift != null) {
+ respVO.setShiftName(shift.getName());
+ respVO.setShiftColor(shift.getColor());
+ }
+ respVO.setRemark(schedule.getRemark());
+ return respVO;
+ }).collect(Collectors.toList());
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void saveSchedule(List<HrmEmployeeScheduleSaveReqVO> saveReqVOList) {
+ if (CollUtil.isEmpty(saveReqVOList)) {
+ throw exception(SCHEDULE_ITEM_LIST_EMPTY);
+ }
+ // 1. 鏍¢獙鍛樺伐瀛樺湪
+ Set<Long> employeeIds = saveReqVOList.stream().map(HrmEmployeeScheduleSaveReqVO::getEmployeeId)
+ .collect(Collectors.toSet());
+ if (CollUtil.isNotEmpty(employeeIds)) {
+ List<HrmEmployeeDO> employees = employeeMapper.selectBatchIds(employeeIds);
+ if (employees.size() != employeeIds.size()) {
+ throw exception(SCHEDULE_EMPLOYEE_NOT_EXISTS);
+ }
+ }
+ // 2. 鏍¢獙鐝瀛樺湪
+ Set<Long> shiftIds = saveReqVOList.stream().map(HrmEmployeeScheduleSaveReqVO::getShiftId)
+ .filter(Objects::nonNull).collect(Collectors.toSet());
+ if (CollUtil.isNotEmpty(shiftIds)) {
+ List<HrmShiftDO> shifts = shiftMapper.selectBatchIds(shiftIds);
+ if (shifts.size() != shiftIds.size()) {
+ throw exception(SCHEDULE_SHIFT_NOT_EXISTS);
+ }
+ }
+ // 3. 閫愭潯 upsert锛氭湁鐝鍒欎繚瀛橈紝鏃犵彮娆″垯鍒犻櫎
+ for (HrmEmployeeScheduleSaveReqVO reqVO : saveReqVOList) {
+ HrmEmployeeScheduleDO existing = employeeScheduleMapper.selectByEmployeeIdAndDate(
+ reqVO.getEmployeeId(), reqVO.getDate());
+ if (reqVO.getShiftId() == null) {
+ if (existing != null) {
+ employeeScheduleMapper.deleteById(existing.getId());
+ }
+ } else {
+ if (existing == null) {
+ employeeScheduleMapper.insert(BeanUtils.toBean(reqVO, HrmEmployeeScheduleDO.class));
+ } else {
+ existing.setShiftId(reqVO.getShiftId());
+ existing.setRemark(reqVO.getRemark());
+ employeeScheduleMapper.updateById(existing);
+ }
+ }
+ }
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmShiftService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmShiftService.java
new file mode 100644
index 0000000..b7c0da4
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmShiftService.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.hrm.service.attendance;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmShiftPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmShiftSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmShiftDO;
+
+import java.util.List;
+
+/**
+ * 鐝瀹氫箟 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface HrmShiftService {
+
+ /**
+ * 鏂板鐝
+ */
+ Long createShift(HrmShiftSaveReqVO createReqVO);
+
+ /**
+ * 鏇存柊鐝
+ */
+ void updateShift(HrmShiftSaveReqVO updateReqVO);
+
+ /**
+ * 鍒犻櫎鐝
+ */
+ void deleteShift(Long id);
+
+ /**
+ * 鑾峰緱鐝
+ */
+ HrmShiftDO getShift(Long id);
+
+ /**
+ * 鑾峰緱鐝鍒嗛〉
+ */
+ PageResult<HrmShiftDO> getShiftPage(HrmShiftPageReqVO pageReqVO);
+
+ /**
+ * 鑾峰緱鐝鍒楄〃锛堝惎鐢ㄧ姸鎬侊級
+ */
+ List<HrmShiftDO> getShiftList();
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmShiftServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmShiftServiceImpl.java
new file mode 100644
index 0000000..ea66cc7
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmShiftServiceImpl.java
@@ -0,0 +1,100 @@
+package cn.iocoder.yudao.module.hrm.service.attendance;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmShiftPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmShiftSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmShiftDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmEmployeeScheduleMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmShiftMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
+
+/**
+ * 鐝瀹氫箟 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class HrmShiftServiceImpl implements HrmShiftService {
+
+ @Resource
+ private HrmShiftMapper shiftMapper;
+ @Resource
+ private HrmEmployeeScheduleMapper employeeScheduleMapper;
+
+ @Override
+ public Long createShift(HrmShiftSaveReqVO createReqVO) {
+ validateShiftNameUnique(null, createReqVO.getName());
+ HrmShiftDO shift = BeanUtils.toBean(createReqVO, HrmShiftDO.class);
+ if (shift.getStatus() == null) {
+ shift.setStatus(0);
+ }
+ if (shift.getCrossDay() == null) {
+ shift.setCrossDay(false);
+ }
+ shiftMapper.insert(shift);
+ return shift.getId();
+ }
+
+ @Override
+ public void updateShift(HrmShiftSaveReqVO updateReqVO) {
+ validateShiftExists(updateReqVO.getId());
+ validateShiftNameUnique(updateReqVO.getId(), updateReqVO.getName());
+ HrmShiftDO updateObj = BeanUtils.toBean(updateReqVO, HrmShiftDO.class);
+ if (updateObj.getCrossDay() == null) {
+ updateObj.setCrossDay(false);
+ }
+ shiftMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteShift(Long id) {
+ validateShiftExists(id);
+ // 鏍¢獙鐝鏄惁宸茶鎺掔彮浣跨敤
+ Long usedCount = employeeScheduleMapper.selectCountByShiftId(id);
+ if (usedCount != null && usedCount > 0) {
+ throw exception(SHIFT_IN_USE);
+ }
+ shiftMapper.deleteById(id);
+ }
+
+ private void validateShiftExists(Long id) {
+ if (shiftMapper.selectById(id) == null) {
+ throw exception(SHIFT_NOT_EXISTS);
+ }
+ }
+
+ private void validateShiftNameUnique(Long id, String name) {
+ HrmShiftDO shift = shiftMapper.selectOne(HrmShiftDO::getName, name);
+ if (shift == null) {
+ return;
+ }
+ if (id == null || !shift.getId().equals(id)) {
+ throw exception(SHIFT_NAME_EXISTS);
+ }
+ }
+
+ @Override
+ public HrmShiftDO getShift(Long id) {
+ return shiftMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult<HrmShiftDO> getShiftPage(HrmShiftPageReqVO pageReqVO) {
+ return shiftMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List<HrmShiftDO> getShiftList() {
+ return shiftMapper.selectListByStatus(0);
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
index c326a89..eefe7a7 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
@@ -325,6 +325,16 @@
@Override
public PageResult<HrmEmployeeDO> getEmployeePage(HrmEmployeePageReqVO pageReqVO) {
+ if (pageReqVO.getDeptId() != null) {
+ // 閫変腑閮ㄩ棬鏃讹紝杩炲甫鏌ヨ鍏舵墍鏈夊瓙閮ㄩ棬鐨勫憳宸ワ紙鍚嚜韬級
+ List<DeptRespDTO> childDepts = deptApi.getChildDeptList(pageReqVO.getDeptId());
+ Set<Long> deptIds = new LinkedHashSet<>();
+ deptIds.add(pageReqVO.getDeptId());
+ if (CollUtil.isNotEmpty(childDepts)) {
+ childDepts.forEach(dept -> deptIds.add(dept.getId()));
+ }
+ return employeeMapper.selectPageByDeptIds(pageReqVO, deptIds);
+ }
return employeeMapper.selectPage(pageReqVO);
}
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..6cf8b19 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;
@@ -39,8 +43,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;
@@ -96,6 +103,10 @@
private HrmSalaryPaymentMapper salaryPaymentMapper;
@Resource
private HrmSalaryPaymentDetailMapper salaryPaymentDetailMapper;
+ @Resource
+ private HrmEmployeeScheduleMapper employeeScheduleMapper;
+ @Resource
+ private HrmShiftMapper shiftMapper;
@Override
public PageResult<HrmSalaryCalculationDO> getSalaryCalculationPage(HrmSalaryCalculationPageReqVO pageReqVO) {
@@ -225,6 +236,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 +311,9 @@
.otherDeduction(otherDeduction)
.leaveDeduction(leaveDeduction)
.actualSalary(actualSalary)
- .workDays(22) // 榛樿宸ヤ綔鏃�
+ .workDays(workDays) // 鎸夋帓鐝疮璁″簲鍑哄嫟澶╂暟锛屾棤鎺掔彮鏃跺洖閫� 22
.overtimeHours(overtimeSalary.hours())
+ .totalWorkHours(totalWorkHours)
.status(0) // 寰呯‘璁�
.build();
}
@@ -401,6 +418,37 @@
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