feat(hrm): 考勤规则支持 Excel 导入;修复定位打卡测试类编译错误
新增(同事 9aa810ea 未覆盖的部分):
- 考勤规则 Excel 导入:模板下载 + 导入(按名称判重,支持 updateSupport 覆盖,部门按名称匹配)
- 补充 LocalTimeConvert(框架缺失,导入模板依赖 LocalTime)
- 导入相关错误码:ATTENDANCE_RULE_IMPORT_LIST_IS_EMPTY / ATTENDANCE_RULE_EXISTS / ATTENDANCE_RULE_DEPT_NOT_EXISTS
- SQL:hrm_attendance_rule_import_menu.sql(import 权限点 + 全角色授权,幂等)
修复:
- HrmAttendanceClockLocationTest 中 getAttendanceRuleByDeptId 调用与签名不符(9aa810ea 为单参数)。
该分支的方法签名已含考勤日期参数,改为 getAttendanceRuleByDeptId(eq(2L), any(LocalDate.class)),
否则 mvn package 编译失败。
未包含(已在 9aa810ea 实现,本提交不重复、不覆盖):
- 考勤规则地点字段与 validateLocation 校验
- 打卡接口 JSON Body + legacyLocation 兼容、记录定位字段
验证:导入三态(创建/不覆盖/覆盖)、部门名称匹配、全局规则(dept_id NULL)、
普通角色权限点、HrmAttendanceRuleImportExcelVO 解析均通过。
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================= |
| | | -- èå¤è§åï¼å¯¼å
¥æé®æéç¹ |
| | | -- |
| | | -- èæ¯ï¼æ°å¢ /hrm/attendance/rule/import ä¸ /get-import-template 两个æ¥å£ï¼ |
| | | -- æéç hrm:attendance-rule:import å¨ system_menu ä¸ä¸åå¨ï¼ |
| | | -- ä¼å¯¼è´éè¶
级管çåè°ç¨æ¶ 403ã |
| | | -- |
| | | -- åæ³ï¼è¡¥ä¸ä¸ª type=3 çæé®æéç¹ï¼ä¸æ¾ç¤ºå¨ä¾§æ ï¼ï¼å¹¶ææç»ææç°æå¯ç¨è§è²ï¼ |
| | | -- 䏿¢æãèå¤è§åæ¥è¯¢/æ°å¢/ä¿®æ¹/å é¤/导åºãçææèå´ä¿æä¸è´ã |
| | | -- |
| | | -- 卿 idï¼MAX(id)+1ï¼ï¼å¯é夿§è¡ï¼å¹çï¼ã |
| | | -- ============================================= |
| | | SET NAMES utf8mb4; |
| | | |
| | | -- 1. å®ä½ç¶èåï¼èå¤è§åï¼ä¼å
æ pathï¼å
åºææ¢ææéç åæ¥ï¼ |
| | | SET @hrm := (SELECT id FROM `system_menu` WHERE `path` IN ('hrm','/hrm') AND `deleted`=b'0' LIMIT 1); |
| | | SET @att := (SELECT id FROM `system_menu` WHERE `path`='attendance' AND `parent_id`=@hrm AND `deleted`=b'0' LIMIT 1); |
| | | SET @att_rule := (SELECT `parent_id` FROM `system_menu` |
| | | WHERE `permission`='hrm:attendance-rule:query' AND `deleted`=b'0' LIMIT 1); |
| | | SET @att_rule := IFNULL(@att_rule, |
| | | (SELECT id FROM `system_menu` WHERE `path`='rule' AND `parent_id`=@att AND `deleted`=b'0' LIMIT 1)); |
| | | |
| | | -- 2. æå
¥æéç¹ |
| | | SET @perm_exists := (SELECT COUNT(*) FROM `system_menu` WHERE `permission`='hrm:attendance-rule:import'); |
| | | SET @import_menu_id := IFNULL((SELECT MAX(id) FROM `system_menu`), 0) + 1; |
| | | |
| | | INSERT INTO `system_menu` (`id`,`name`,`permission`,`type`,`sort`,`parent_id`,`path`,`icon`,`component`, |
| | | `component_name`,`status`,`visible`,`keep_alive`,`always_show`, |
| | | `creator`,`create_time`,`updater`,`update_time`,`deleted`) |
| | | SELECT @import_menu_id, 'èå¤è§å导å
¥', 'hrm:attendance-rule:import', 3, 6, @att_rule, '', '', '', NULL, |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | FROM DUAL |
| | | WHERE @perm_exists = 0 AND @att_rule IS NOT NULL; |
| | | |
| | | -- 3. ææç»ææç°æå¯ç¨è§è² |
| | | SET @menu_id := (SELECT id FROM `system_menu` WHERE `permission`='hrm:attendance-rule:import' AND `deleted`=b'0' LIMIT 1); |
| | | |
| | | INSERT INTO `system_role_menu` (`role_id`,`menu_id`,`creator`,`create_time`,`updater`,`update_time`,`deleted`) |
| | | SELECT r.`id`, @menu_id, '1', NOW(), '1', NOW(), b'0' |
| | | FROM `system_role` r |
| | | WHERE @menu_id IS NOT NULL |
| | | AND r.`deleted` = b'0' |
| | | AND r.`status` = 0 |
| | | AND NOT EXISTS (SELECT 1 FROM `system_role_menu` rm |
| | | WHERE rm.`role_id` = r.`id` AND rm.`menu_id` = @menu_id AND rm.`deleted` = b'0'); |
| | | |
| | | -- 4. æ ¸å¯¹ |
| | | SELECT id, name, permission, type, parent_id FROM `system_menu` |
| | | WHERE permission = 'hrm:attendance-rule:import'; |
| | | |
| | | SELECT r.id AS role_id, r.name AS role_name, rm.menu_id |
| | | FROM `system_role` r |
| | | LEFT JOIN `system_role_menu` rm ON rm.role_id = r.id AND rm.menu_id = @menu_id AND rm.deleted = b'0' |
| | | WHERE r.deleted = b'0' AND r.status = 0; |
| | | |
| | | SELECT 'hrm:attendance-rule:import æéç¹åå§å宿' AS result; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.framework.excel.core.convert; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.idev.excel.converters.Converter; |
| | | import cn.idev.excel.enums.CellDataTypeEnum; |
| | | import cn.idev.excel.metadata.GlobalConfiguration; |
| | | import cn.idev.excel.metadata.data.ReadCellData; |
| | | import cn.idev.excel.metadata.data.WriteCellData; |
| | | import cn.idev.excel.metadata.property.ExcelContentProperty; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | |
| | | /** |
| | | * LocalTime 转æ¢å¨ |
| | | * |
| | | * FastExcelï¼cn.idev.excelï¼å
ç½®ä¸æ¯æ LocalTimeï¼æªæå® converter æ¶è¯»åé½ä¼æåº |
| | | * Can not find Converter support class LocalTimeãè¿éè¡¥ä¸ãHH:mm:ss å符串ãä¸ |
| | | * ãExcel æ¶é´æ°å¼ï¼ä¸å¤©çå æ¯ï¼ã两ç§åæ³çè¯»åæ¯æã |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | public class LocalTimeConvert implements Converter<LocalTime> { |
| | | |
| | | private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); |
| | | |
| | | @Override |
| | | public Class<LocalTime> supportJavaTypeKey() { |
| | | return LocalTime.class; |
| | | } |
| | | |
| | | @Override |
| | | public CellDataTypeEnum supportExcelTypeKey() { |
| | | return CellDataTypeEnum.STRING; |
| | | } |
| | | |
| | | @Override |
| | | public LocalTime convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty, |
| | | GlobalConfiguration globalConfiguration) { |
| | | // æ
åµä¸ï¼Excel éå¡«çæ¯æ¶é´æ°å¼ï¼ä¸å¤©çå æ¯ï¼ |
| | | if (CellDataTypeEnum.NUMBER.equals(readCellData.getType())) { |
| | | BigDecimal numberValue = readCellData.getNumberValue(); |
| | | if (numberValue == null) { |
| | | return null; |
| | | } |
| | | long secondOfDay = Math.round(numberValue.doubleValue() * 24 * 60 * 60) % (24 * 60 * 60); |
| | | return LocalTime.ofSecondOfDay(secondOfDay); |
| | | } |
| | | // æ
åµäºï¼Excel éå¡«çæ¯ææ¬ï¼å¦ 8:30 / 08:30:00 |
| | | String text = readCellData.getStringValue(); |
| | | if (StrUtil.isBlank(text)) { |
| | | return null; |
| | | } |
| | | try { |
| | | String[] parts = text.trim().replace('ï¼', ':').split(":"); |
| | | int hour = Integer.parseInt(parts[0].trim()); |
| | | int minute = parts.length > 1 ? Integer.parseInt(parts[1].trim()) : 0; |
| | | int second = parts.length > 2 ? Integer.parseInt(parts[2].trim()) : 0; |
| | | return LocalTime.of(hour, minute, second); |
| | | } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public WriteCellData<String> convertToExcelData(LocalTime value, ExcelContentProperty contentProperty, |
| | | GlobalConfiguration globalConfiguration) { |
| | | return new WriteCellData<>(value == null ? "" : value.format(TIME_FORMATTER)); |
| | | } |
| | | |
| | | } |
| | |
| | | ErrorCode ATTENDANCE_CLOCK_ALREADY_CLOCKED = new ErrorCode(1_030_004_016, "å·²æè¿å¡ï¼è¯·å¿é夿å¡"); |
| | | ErrorCode ATTENDANCE_RULE_EFFECTIVE_DATE_INVALID = new ErrorCode(1_030_004_017, "çæå¼å§æ¥æä¸è½æäºçæç»ææ¥æ"); |
| | | ErrorCode ATTENDANCE_HOLIDAY_DATE_RANGE_INVALID = new ErrorCode(1_030_004_017, "ç»ææ¥æã{}ãä¸è½æ©äºå¼å§æ¥æã{}ãï¼æ æ³ä¿åãè¯·éæ°éæ©æ£ç¡®çæ¥æåºé´"); |
| | | // ========== èå¤è§åï¼å¯¼å
¥ ========== |
| | | ErrorCode ATTENDANCE_RULE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_030_004_023, "导å
¥èå¤è§åæ°æ®ä¸è½ä¸ºç©º"); |
| | | ErrorCode ATTENDANCE_RULE_EXISTS = new ErrorCode(1_030_004_024, "å·²åå¨èå¤è§åã{}ãï¼ä¸æªå¼å¯è¦çï¼"); |
| | | ErrorCode ATTENDANCE_RULE_DEPT_NOT_EXISTS = new ErrorCode(1_030_004_025, "é¨é¨ã{}ãä¸åå¨"); |
| | | ErrorCode ATTENDANCE_LOCATION_RULE_INVALID = new ErrorCode(1_030_004_050, "èå¤è§åå°ç¹é
ç½®ä¸å®æ´ï¼è¯·è系管çå"); |
| | | ErrorCode ATTENDANCE_LOCATION_REQUIRED = new ErrorCode(1_030_004_051, "请å¼å¯å®ä½å¹¶æä¾ç»çº¬åº¦ã精度ååæ ç³»åæå¡"); |
| | | ErrorCode ATTENDANCE_LOCATION_COORDINATE_INVALID = new ErrorCode(1_030_004_052, "æå¡ç»çº¬åº¦è¶
åºææèå´"); |
| | |
| | | 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.HrmAttendanceRuleImportExcelVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleImportReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleImportRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRulePageReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleSaveReqVO; |
| | |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.io.IOException; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalTime; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | |
| | | ExcelUtils.write(response, "èå¤è§å.xls", "æ°æ®", HrmAttendanceRuleRespVO.class, data); |
| | | } |
| | | |
| | | @GetMapping("/get-import-template") |
| | | @Operation(summary = "è·å¾å¯¼å
¥èå¤è§å模æ¿") |
| | | @PreAuthorize("@ss.hasPermission('hrm:attendance-rule:import')") |
| | | public void importTemplate(HttpServletResponse response) throws IOException { |
| | | // æå¨åå»ºå¯¼åº demo |
| | | List<HrmAttendanceRuleImportExcelVO> list = Collections.singletonList( |
| | | HrmAttendanceRuleImportExcelVO.builder().name("æ åèå¤è§å").deptName("ç åé¨") |
| | | .workStartTime(LocalTime.of(9, 0)).workEndTime(LocalTime.of(18, 0)) |
| | | .lateAllowMinutes(10).earlyAllowMinutes(10).overtimeThreshold(BigDecimal.ONE) |
| | | .status(0).remark("").build() |
| | | ); |
| | | // è¾åº |
| | | ExcelUtils.write(response, "èå¤è§å导å
¥æ¨¡æ¿.xls", "èå¤è§åå表", HrmAttendanceRuleImportExcelVO.class, list); |
| | | } |
| | | |
| | | @PostMapping("/import") |
| | | @Operation(summary = "导å
¥èå¤è§å") |
| | | @PreAuthorize("@ss.hasPermission('hrm:attendance-rule:import')") |
| | | public CommonResult<HrmAttendanceRuleImportRespVO> importExcel(@Valid HrmAttendanceRuleImportReqVO importReqVO) |
| | | throws Exception { |
| | | List<HrmAttendanceRuleImportExcelVO> list = ExcelUtils.read(importReqVO.getFile(), HrmAttendanceRuleImportExcelVO.class); |
| | | return success(attendanceRuleService.importAttendanceRuleList(list, importReqVO)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo; |
| | | |
| | | import cn.idev.excel.annotation.ExcelProperty; |
| | | import cn.iocoder.yudao.framework.excel.core.convert.LocalTimeConvert; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalTime; |
| | | |
| | | /** |
| | | * èå¤è§åé
ç½® Excel 导å
¥ VO |
| | | */ |
| | | @Data |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | public class HrmAttendanceRuleImportExcelVO { |
| | | |
| | | @ExcelProperty("è§ååç§°") |
| | | private String name; |
| | | |
| | | @ExcelProperty("é¨é¨åç§°") |
| | | private String deptName; |
| | | |
| | | @ExcelProperty(value = "ä¸çæ¶é´", converter = LocalTimeConvert.class) |
| | | private LocalTime workStartTime; |
| | | |
| | | @ExcelProperty(value = "ä¸çæ¶é´", converter = LocalTimeConvert.class) |
| | | private LocalTime workEndTime; |
| | | |
| | | @ExcelProperty("è¿å°å
许åéæ°") |
| | | private Integer lateAllowMinutes; |
| | | |
| | | @ExcelProperty("æ©éå
许åéæ°") |
| | | private Integer earlyAllowMinutes; |
| | | |
| | | @ExcelProperty("å çèµ·ç®æ¶é¿ï¼å°æ¶ï¼") |
| | | private BigDecimal overtimeThreshold; |
| | | |
| | | @ExcelProperty("ç¶æï¼0-å¯ç¨ï¼1-ç¦ç¨ï¼") |
| | | private Integer status; |
| | | |
| | | @ExcelProperty("夿³¨") |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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 org.springframework.web.multipart.MultipartFile; |
| | | |
| | | @Schema(description = "管çåå° - èå¤è§å导å
¥ Request VO") |
| | | @Data |
| | | public class HrmAttendanceRuleImportReqVO { |
| | | |
| | | @Schema(description = "Excel æä»¶", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | @NotNull(message = "Excel æä»¶ä¸è½ä¸ºç©º") |
| | | private MultipartFile file; |
| | | |
| | | @Schema(description = "æ¯å¦æ¯ææ´æ°ï¼é»è®¤ false", example = "true") |
| | | private Boolean updateSupport = false; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Schema(description = "管çåå° - èå¤è§å导å
¥ Response VO") |
| | | @Data |
| | | @Builder |
| | | public class HrmAttendanceRuleImportRespVO { |
| | | |
| | | @Schema(description = "å建æåçèå¤è§ååç§°æ°ç»", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private List<String> createNames; |
| | | |
| | | @Schema(description = "æ´æ°æåçèå¤è§ååç§°æ°ç»", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private List<String> updateNames; |
| | | |
| | | @Schema(description = "导å
¥å¤±è´¥çèå¤è§åéåï¼key 为è§ååç§°ï¼value 为失败åå ", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private Map<String, String> failureNames; |
| | | |
| | | } |
| | |
| | | */ |
| | | HrmAttendanceRuleDO getAttendanceRuleByDeptId(Long deptId, java.time.LocalDate date); |
| | | |
| | | /** |
| | | * 导å
¥èå¤è§åï¼ç§»æ¤èªæ°é«çµåï¼ |
| | | * |
| | | * æãè§ååç§°ãå¤éï¼å·²å卿¶é updateSupport=true æè¦çã |
| | | * é¨é¨æåç§°å¹é
ï¼é¨é¨ä¸ºç©ºè¡¨ç¤ºå
¨å±è§åã |
| | | * |
| | | * @param importList 导å
¥çè§åå表 |
| | | * @param importReqVO 导å
¥åæ° |
| | | * @return 导å
¥ç»æ |
| | | */ |
| | | cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleImportRespVO importAttendanceRuleList( |
| | | List<cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleImportExcelVO> importList, |
| | | cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleImportReqVO importReqVO); |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.hrm.service.attendance; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleImportExcelVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleImportReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleImportRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRulePageReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleSaveReqVO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRuleDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceRuleMapper; |
| | | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| | | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.util.ArrayList; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; |
| | | import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*; |
| | | |
| | | /** |
| | |
| | | |
| | | @Resource |
| | | private HrmAttendanceRuleMapper attendanceRuleMapper; |
| | | |
| | | @Resource |
| | | private DeptApi deptApi; |
| | | |
| | | @Override |
| | | public Long createAttendanceRule(HrmAttendanceRuleSaveReqVO createReqVO) { |
| | |
| | | return attendanceRuleMapper.selectList(wrapper); |
| | | } |
| | | |
| | | /** |
| | | * 导å
¥èå¤è§åï¼ç§»æ¤èªæ°é«çµåï¼ |
| | | * |
| | | * 说æï¼å¯¼å
¥æãè§ååç§°ãå¤éï¼å·²å卿¶é updateSupport=true æè¦çã |
| | | * é¨é¨æåç§°å¹é
ï¼deptName -> deptIdï¼ï¼é¨é¨ä¸ºç©ºè¡¨ç¤ºå
¨å±è§åã |
| | | */ |
| | | @Override |
| | | public HrmAttendanceRuleImportRespVO importAttendanceRuleList( |
| | | List<HrmAttendanceRuleImportExcelVO> importList, HrmAttendanceRuleImportReqVO importReqVO) { |
| | | // 1. æ ¡éªé空 |
| | | importList = filterList(importList, item -> StrUtil.isNotBlank(item.getName())); |
| | | if (CollUtil.isEmpty(importList)) { |
| | | throw exception(ATTENDANCE_RULE_IMPORT_LIST_IS_EMPTY); |
| | | } |
| | | // 2. é¢å è½½é¨é¨åç§° -> é¨é¨ID |
| | | Map<String, Long> deptMap = deptApi.getDeptList().stream() |
| | | .filter(dept -> StrUtil.isNotBlank(dept.getName())) |
| | | .collect(Collectors.toMap(DeptRespDTO::getName, DeptRespDTO::getId, (a, b) -> a)); |
| | | // 3. éæ¡å¤ç |
| | | HrmAttendanceRuleImportRespVO respVO = HrmAttendanceRuleImportRespVO.builder() |
| | | .createNames(new ArrayList<>()) |
| | | .updateNames(new ArrayList<>()) |
| | | .failureNames(new LinkedHashMap<>()) |
| | | .build(); |
| | | importList.forEach(importRule -> { |
| | | try { |
| | | // 3.1 è§£æé¨é¨ |
| | | Long deptId = null; |
| | | if (StrUtil.isNotBlank(importRule.getDeptName())) { |
| | | deptId = deptMap.get(importRule.getDeptName()); |
| | | if (deptId == null) { |
| | | throw exception(ATTENDANCE_RULE_DEPT_NOT_EXISTS, importRule.getDeptName()); |
| | | } |
| | | } |
| | | // 3.2 夿æ¯å¦åå¨ï¼æåç§°å¯ä¸ï¼ |
| | | HrmAttendanceRuleDO existRule = attendanceRuleMapper.selectOne(HrmAttendanceRuleDO::getName, importRule.getName()); |
| | | if (existRule == null) { |
| | | // 3.2.1 æ°å¢ |
| | | HrmAttendanceRuleDO rule = BeanUtils.toBean(importRule, HrmAttendanceRuleDO.class).setDeptId(deptId); |
| | | applyImportDefaults(rule); |
| | | attendanceRuleMapper.insert(rule); |
| | | respVO.getCreateNames().add(importRule.getName()); |
| | | return; |
| | | } |
| | | // 3.2.2 å·²åå¨ï¼å¤ææ¯å¦å
è®¸æ´æ° |
| | | if (!Boolean.TRUE.equals(importReqVO.getUpdateSupport())) { |
| | | respVO.getFailureNames().put(importRule.getName(), |
| | | StrUtil.format(ATTENDANCE_RULE_EXISTS.getMsg(), importRule.getName())); |
| | | return; |
| | | } |
| | | HrmAttendanceRuleDO updateRule = BeanUtils.toBean(importRule, HrmAttendanceRuleDO.class) |
| | | .setId(existRule.getId()).setDeptId(deptId); |
| | | applyImportDefaults(updateRule); |
| | | attendanceRuleMapper.updateById(updateRule); |
| | | respVO.getUpdateNames().add(importRule.getName()); |
| | | } catch (ServiceException ex) { |
| | | respVO.getFailureNames().put(importRule.getName(), ex.getMessage()); |
| | | } |
| | | }); |
| | | return respVO; |
| | | } |
| | | |
| | | /** |
| | | * 导å
¥æ¶çé»è®¤å¼è¡¥å
¨ï¼ä¸é¡µé¢æ°å¢ä¿æä¸è´çé»è®¤å£å¾ï¼ä¸è¦çå°ç¹å段ï¼é¿å
导å
¥æ¸
空已é
ç½®çå°ç¹ï¼ |
| | | */ |
| | | private void applyImportDefaults(HrmAttendanceRuleDO rule) { |
| | | if (rule.getLateAllowMinutes() == null) { |
| | | rule.setLateAllowMinutes(0); |
| | | } |
| | | if (rule.getEarlyAllowMinutes() == null) { |
| | | rule.setEarlyAllowMinutes(0); |
| | | } |
| | | if (rule.getOvertimeThreshold() == null) { |
| | | rule.setOvertimeThreshold(BigDecimal.ONE); |
| | | } |
| | | if (rule.getStatus() == null) { |
| | | rule.setStatus(0); |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.ArgumentMatchers.eq; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class HrmAttendanceClockLocationTest { |
| | |
| | | when(holidayService.isWorkDay(any(LocalDate.class))).thenReturn(true); |
| | | HrmEmployeeDO employee = HrmEmployeeDO.builder().id(1L).deptId(2L).build(); |
| | | when(employeeService.getEmployee(1L)).thenReturn(employee); |
| | | when(ruleService.getAttendanceRuleByDeptId(2L)).thenReturn(requiredRule()); |
| | | when(ruleService.getAttendanceRuleByDeptId(eq(2L), any(LocalDate.class))).thenReturn(requiredRule()); |
| | | assertThrows(ServiceException.class, |
| | | () -> service.clockOut(1L, clockTime, position("0.00091", "20"), null)); |
| | | verify(mapper, never()).insert(any(HrmAttendanceRecordDO.class)); |