feat: 售后工单按条绑定部门与原因类型;新增加班工资管理;员工附件留档
- 售后工单明细新增 itemNo/deptId/reasonType/reason,按条生成独立单号、部门与原因类型
- 新增加班工资管理(岗位时薪/技工1:1/封顶)与员工类别字段
- 员工附件留档(recordType=hrm_employee,上传/回显)
- 含 SQL: sql/mysql/hrm_overtime_wage_config_init.sql、aftersales_ticket_item_enhance.sql
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================= |
| | | -- å®åå·¥åæç»å¢å¼º SQL åå§åèæ¬ï¼å¯é夿§è¡ï¼ |
| | | -- éæ±ï¼å®åéè´§/å·¥åæ¯æ¡æç»ç¬ç«å®ååå·ï¼æ¯æ¡éé¨é¨ãåå ç±»åå¹¶å¡«å
·ä½åå |
| | | -- å
å«ï¼after_sale_ticket_item å åæ®µ + aftersale_reason_type åå
¸ |
| | | -- ============================================= |
| | | |
| | | -- ============================================= |
| | | -- 1. å·¥åæç»è¡¨æ°å¢å段ï¼å卿§å¤æï¼ä¿è¯å¯é夿§è¡ï¼ |
| | | -- ============================================= |
| | | SET @col_exists := (SELECT COUNT(*) FROM information_schema.COLUMNS |
| | | WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'after_sale_ticket_item' AND COLUMN_NAME = 'item_no'); |
| | | SET @ddl := IF(@col_exists = 0, |
| | | 'ALTER TABLE `after_sale_ticket_item` |
| | | ADD COLUMN `item_no` varchar(64) NULL COMMENT ''æç»å®ååå·ï¼å·¥åå·-åºå·ï¼'' AFTER `ticket_id`, |
| | | ADD COLUMN `dept_id` bigint NULL COMMENT ''é¨é¨IDï¼å
³è system_dept.id'' AFTER `need_return`, |
| | | ADD COLUMN `reason_type` tinyint NULL COMMENT ''åå ç±»åï¼åå
¸ aftersale_reason_type'' AFTER `dept_id`, |
| | | ADD COLUMN `reason` varchar(500) NULL COMMENT ''å
·ä½åå '' AFTER `reason_type`', |
| | | 'SELECT 1'); |
| | | PREPARE stmt FROM @ddl; |
| | | EXECUTE stmt; |
| | | DEALLOCATE PREPARE stmt; |
| | | |
| | | -- ============================================= |
| | | -- 2. æ°æ®åå
¸ï¼å®ååå ç±»å aftersale_reason_type |
| | | -- ï¼åå¼å¯ç±ä¸å¡å¨ãç³»ç»ç®¡ç - åå
¸ç®¡çãä¸èªè¡ç»´æ¤ï¼ |
| | | -- ============================================= |
| | | INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES |
| | | (1065307, 'å®ååå ç±»å', 'aftersale_reason_type', 0, 'å®åå·¥åæç»åå åç±»', '', NOW(), '', NOW(), b'0') |
| | | ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `type` = VALUES(`type`), `status` = VALUES(`status`), `deleted` = b'0'; |
| | | |
| | | INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES |
| | | (1065352, 1, 'å¤è§ç¼ºé·', '1', 'aftersale_reason_type', 0, 'primary', NULL, '', NOW(), '', NOW(), b'0'), |
| | | (1065353, 2, 'åè½æ
é', '2', 'aftersale_reason_type', 0, 'warning', NULL, '', NOW(), '', NOW(), b'0'), |
| | | (1065354, 3, '尺寸åå·®', '3', 'aftersale_reason_type', 0, 'info', NULL, '', NOW(), '', NOW(), b'0'), |
| | | (1065355, 4, 'å
è£
ç ´æ', '4', 'aftersale_reason_type', 0, 'warning', NULL, '', NOW(), '', NOW(), b'0'), |
| | | (1065356, 5, 'ç©æµæå', '5', 'aftersale_reason_type', 0, 'danger', NULL, '', NOW(), '', NOW(), b'0'), |
| | | (1065357, 6, '缺件å°å', '6', 'aftersale_reason_type', 0, 'danger', NULL, '', NOW(), '', NOW(), b'0'), |
| | | (1065358, 7, 'è§æ ¼åå·ä¸ç¬¦', '7', 'aftersale_reason_type', 0, 'info', NULL, '', NOW(), '', NOW(), b'0'), |
| | | (1065359, 8, '使ç¨ä¸å½', '8', 'aftersale_reason_type', 0, 'default', NULL, '', NOW(), '', NOW(), b'0'), |
| | | (1065360, 9, 'å
¶ä»', '9', 'aftersale_reason_type', 0, 'default', NULL, '', NOW(), '', NOW(), b'0') |
| | | ON DUPLICATE KEY UPDATE `sort` = VALUES(`sort`), `label` = VALUES(`label`), `value` = VALUES(`value`), |
| | | `dict_type` = VALUES(`dict_type`), `status` = VALUES(`status`), `color_type` = VALUES(`color_type`), `deleted` = b'0'; |
| | | |
| | | SELECT 'å®åå·¥åæç»å¢å¼ºåå§å宿' AS result; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================= |
| | | -- HRM å çå·¥èµç®¡ç SQL åå§åèæ¬ï¼å¯é夿§è¡ï¼ |
| | | -- å
å«ï¼å²ä½å çå·¥èµé
置表 + åå·¥ãå工类å«ãåæ®µ + æ°æ®åå
¸ + èå/æé |
| | | -- 对åºéæ±ï¼äººåèµæº > èªé
¬ç®¡ç > å çå·¥èµç®¡ç |
| | | -- - ä¸åå²ä½å çå·¥èµä¸åï¼æ®éåå·¥æå²ä½æ¶èªï¼æå·¥ 1:1ï¼ç®¡çä¸è®¡ï¼ |
| | | -- - æå·¥ç§è®¾ç½®æå¤§å çæ¶é¿ä¸é |
| | | -- ============================================= |
| | | |
| | | -- ============================================= |
| | | -- 1. å²ä½å çå·¥èµé
置表 |
| | | -- ============================================= |
| | | CREATE TABLE IF NOT EXISTS `hrm_overtime_wage_config` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'é
ç½®ID', |
| | | `post_id` bigint NOT NULL COMMENT 'å²ä½IDï¼å
³è system_post.id', |
| | | `hourly_rate` decimal(10,2) NOT NULL DEFAULT 20.00 COMMENT 'å çæ¶èªï¼å
/å°æ¶ï¼ï¼é»è®¤ 20', |
| | | `max_overtime_hours` decimal(6,1) DEFAULT NULL COMMENT 'æ¯ææå¤§å çæ¶é¿ä¸éï¼å°æ¶ï¼ï¼ä¸ºç©ºè¡¨ç¤ºä¸é', |
| | | `status` tinyint NOT NULL DEFAULT 0 COMMENT 'ç¶æï¼0-å¯ç¨ï¼1-ç¦ç¨', |
| | | `remark` varchar(500) DEFAULT NULL COMMENT '夿³¨', |
| | | `creator` varchar(64) DEFAULT '' COMMENT 'å建è
', |
| | | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | `updater` varchar(64) DEFAULT '' COMMENT 'æ´æ°è
', |
| | | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT 'æ¯å¦å é¤', |
| | | PRIMARY KEY (`id`), |
| | | UNIQUE KEY `uk_post_id` (`post_id`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRMå²ä½å çå·¥èµé
置表'; |
| | | |
| | | -- ============================================= |
| | | -- 2. å工表æ°å¢ãå工类å«ãåæ®µï¼1-æ®éåå·¥ï¼2-æå·¥ï¼3-管çï¼ |
| | | -- 说æï¼ADD COLUMN 䏿¯æ IF NOT EXISTSï¼è¿éç¨ information_schema åå卿§å¤æï¼ä¿è¯å¯é夿§è¡ |
| | | -- ============================================= |
| | | SET @col_exists := (SELECT COUNT(*) FROM information_schema.COLUMNS |
| | | WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'hrm_employee' AND COLUMN_NAME = 'employee_category'); |
| | | SET @ddl := IF(@col_exists = 0, |
| | | 'ALTER TABLE `hrm_employee` ADD COLUMN `employee_category` tinyint NOT NULL DEFAULT 1 COMMENT ''å工类å«ï¼1-æ®éåå·¥ï¼2-æå·¥ï¼3-管ç'' AFTER `employee_status`', |
| | | 'SELECT 1'); |
| | | PREPARE stmt FROM @ddl; |
| | | EXECUTE stmt; |
| | | DEALLOCATE PREPARE stmt; |
| | | |
| | | -- ============================================= |
| | | -- 3. æ°æ®åå
¸ï¼åå·¥ç±»å« hrm_employee_category |
| | | -- ============================================= |
| | | INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES |
| | | (1061300, 'HRM å工类å«', 'hrm_employee_category', 0, 'å çå·¥èµå·®å¼æ ¸ç®ç¨', '', NOW(), '', NOW(), b'0') |
| | | ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `type` = VALUES(`type`), `status` = VALUES(`status`); |
| | | |
| | | INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES |
| | | (1061301, 1, 'æ®éåå·¥', '1', 'hrm_employee_category', 0, 'primary', 'æå²ä½é
ç½®çå çæ¶èªæ ¸ç®', '', NOW(), '', NOW(), b'0'), |
| | | (1061302, 2, 'æå·¥', '2', 'hrm_employee_category', 0, 'warning', 'å çæ 1:1ï¼ä¸å¹³æ¶æ¶èªä¸è´', '', NOW(), '', NOW(), b'0'), |
| | | (1061303, 3, '管ç', '3', 'hrm_employee_category', 0, 'default', 'ä¸è®¡å çå·¥èµ', '', NOW(), '', NOW(), b'0') |
| | | ON DUPLICATE KEY UPDATE `label` = VALUES(`label`), `value` = VALUES(`value`), `dict_type` = VALUES(`dict_type`), `status` = VALUES(`status`), `color_type` = VALUES(`color_type`); |
| | | |
| | | -- ============================================= |
| | | -- 4. èå䏿éï¼æè½½å° 6020 èªé
¬ç®¡ç ä¸ï¼ |
| | | -- ============================================= |
| | | 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`) |
| | | VALUES |
| | | (6026, 'å çå·¥èµç®¡ç', '', 2, 4, 6020, 'overtime-wage', 'ep:clock', |
| | | 'hrm/salary/overtime-wage/index', 'HrmOvertimeWage', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'), |
| | | (60261, 'å çå·¥èµæ¥è¯¢', 'hrm:overtime-wage:query', 3, 1, 6026, '', '', '', NULL, |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'), |
| | | (60262, 'å çå·¥èµæ°å¢', 'hrm:overtime-wage:create', 3, 2, 6026, '', '', '', NULL, |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'), |
| | | (60263, 'å çå·¥èµä¿®æ¹', 'hrm:overtime-wage:update', 3, 3, 6026, '', '', '', NULL, |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'), |
| | | (60264, 'å çå·¥èµå é¤', 'hrm:overtime-wage:delete', 3, 4, 6026, '', '', '', NULL, |
| | | 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0') |
| | | ON DUPLICATE KEY UPDATE |
| | | `name` = VALUES(`name`), `permission` = VALUES(`permission`), `parent_id` = VALUES(`parent_id`), |
| | | `path` = VALUES(`path`), `icon` = VALUES(`icon`), `component` = VALUES(`component`), |
| | | `component_name` = VALUES(`component_name`), `status` = VALUES(`status`), `deleted` = b'0'; |
| | | |
| | | -- æäºè¶
级管çåï¼role_id = 1ï¼ |
| | | INSERT INTO `system_role_menu` |
| | | (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) |
| | | SELECT 1, menu_id, '1', NOW(), '1', NOW(), b'0' |
| | | FROM ( |
| | | SELECT 6026 AS menu_id UNION ALL SELECT 60261 UNION ALL SELECT 60262 |
| | | UNION ALL SELECT 60263 UNION ALL SELECT 60264 |
| | | ) menu_ids |
| | | WHERE NOT EXISTS ( |
| | | SELECT 1 FROM `system_role_menu` role_menu |
| | | WHERE role_menu.`role_id` = 1 AND role_menu.`menu_id` = menu_ids.menu_id AND role_menu.`deleted` = b'0' |
| | | ); |
| | | |
| | | SELECT 'HRM å çå·¥èµç®¡çåå§å宿' AS result; |
| | |
| | | import cn.iocoder.yudao.module.aftersales.service.ticket.AfterSaleTicketService; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; |
| | | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| | | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | |
| | | private CrmCustomerApi customerApi; |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | @Resource |
| | | private DeptApi deptApi; |
| | | @Resource |
| | | private cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi storageAttachmentApi; |
| | | |
| | |
| | | // æ¹éå è½½å
³èåç§° |
| | | List<AfterSaleTicketRespVO> enrichedList = buildTicketDetailList(List.of(ticket)); |
| | | AfterSaleTicketRespVO respVO = enrichedList.get(0); |
| | | respVO.setItems(BeanUtils.toBean(items, AfterSaleTicketRespVO.Item.class)); |
| | | List<AfterSaleTicketRespVO.Item> itemVOs = BeanUtils.toBean(items, AfterSaleTicketRespVO.Item.class); |
| | | // åæ¾æç»é¨é¨åç§° |
| | | Set<Long> deptIds = convertSet(itemVOs, AfterSaleTicketRespVO.Item::getDeptId); |
| | | Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds); |
| | | itemVOs.forEach(itemVO -> { |
| | | DeptRespDTO dept = deptMap.get(itemVO.getDeptId()); |
| | | if (dept != null) { |
| | | itemVO.setDeptName(dept.getName()); |
| | | } |
| | | }); |
| | | respVO.setItems(itemVOs); |
| | | respVO.setAttachmentList(storageAttachmentApi.listAttachments("aftersales_after_sale_ticket", id)); |
| | | return success(respVO); |
| | | } |
| | |
| | | @Schema(description = "æ¯å¦éè¦éè´§", example = "true") |
| | | private Boolean needReturn; |
| | | |
| | | @Schema(description = "æç»å®ååå·", example = "AS20240101000001-01") |
| | | private String itemNo; |
| | | |
| | | @Schema(description = "é¨é¨ID", example = "1") |
| | | private Long deptId; |
| | | |
| | | @Schema(description = "é¨é¨åç§°") |
| | | private String deptName; |
| | | |
| | | @Schema(description = "åå ç±»å", example = "1") |
| | | private Integer reasonType; |
| | | |
| | | @Schema(description = "å
·ä½åå ", example = "产åè¡¨é¢æåç") |
| | | private String reason; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | private String remark; |
| | | } |
| | |
| | | @Schema(description = "æ¯å¦éè¦éè´§", example = "true") |
| | | private Boolean needReturn; |
| | | |
| | | @Schema(description = "é¨é¨ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "é¨é¨ä¸è½ä¸ºç©º") |
| | | private Long deptId; |
| | | |
| | | @Schema(description = "åå ç±»å", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "åå ç±»åä¸è½ä¸ºç©º") |
| | | private Integer reasonType; |
| | | |
| | | @Schema(description = "å
·ä½åå ", example = "产åè¡¨é¢æåç") |
| | | private String reason; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | private String remark; |
| | | } |
| | |
| | | */ |
| | | private Long ticketId; |
| | | /** |
| | | * æç»å®ååå·ï¼å·¥åå·-åºå·ï¼ |
| | | */ |
| | | private String itemNo; |
| | | /** |
| | | * éå®è®¢åè¡ç¼å· |
| | | */ |
| | | private Long saleOrderItemId; |
| | |
| | | */ |
| | | private Boolean needReturn; |
| | | /** |
| | | * é¨é¨IDï¼å
³è system_dept.id |
| | | */ |
| | | private Long deptId; |
| | | /** |
| | | * åå ç±»å |
| | | * |
| | | * åå
¸ aftersale_reason_type |
| | | */ |
| | | private Integer reasonType; |
| | | /** |
| | | * å
·ä½åå |
| | | */ |
| | | private String reason; |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | private String remark; |
| | |
| | | .setOwnerUserId(userId); |
| | | ticketMapper.insert(ticket); |
| | | |
| | | // 3. æå
¥æç»è¡ |
| | | // 3. æå
¥æç»è¡ï¼æ¯æ¡çæç¬ç«å®ååå·ï¼å·¥åå·-åºå·ï¼ |
| | | if (CollUtil.isNotEmpty(createReqVO.getItems())) { |
| | | createReqVO.getItems().forEach(item -> { |
| | | AfterSaleTicketItemDO itemDO = BeanUtils.toBean(item, AfterSaleTicketItemDO.class); |
| | | for (int i = 0; i < createReqVO.getItems().size(); i++) { |
| | | AfterSaleTicketItemDO itemDO = BeanUtils.toBean(createReqVO.getItems().get(i), AfterSaleTicketItemDO.class); |
| | | itemDO.setTicketId(ticket.getId()); |
| | | itemDO.setItemNo(buildItemNo(no, i + 1)); |
| | | ticketItemMapper.insert(itemDO); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // 4. ç»å®éä»¶ |
| | |
| | | AfterSaleTicketDO updateObj = BeanUtils.toBean(updateReqVO, AfterSaleTicketDO.class); |
| | | ticketMapper.updateById(updateObj); |
| | | |
| | | // 3. æ´æ°æç»è¡ï¼å
å åå¢ï¼ |
| | | // 3. æ´æ°æç»è¡ï¼å
å åå¢ï¼æ¯æ¡çæç¬ç«å®ååå·ï¼å·¥åå·-åºå·ï¼ |
| | | ticketItemMapper.deleteByTicketId(ticket.getId()); |
| | | if (CollUtil.isNotEmpty(updateReqVO.getItems())) { |
| | | updateReqVO.getItems().forEach(item -> { |
| | | AfterSaleTicketItemDO itemDO = BeanUtils.toBean(item, AfterSaleTicketItemDO.class); |
| | | for (int i = 0; i < updateReqVO.getItems().size(); i++) { |
| | | AfterSaleTicketItemDO itemDO = BeanUtils.toBean(updateReqVO.getItems().get(i), AfterSaleTicketItemDO.class); |
| | | itemDO.setTicketId(ticket.getId()); |
| | | itemDO.setItemNo(buildItemNo(ticket.getNo(), i + 1)); |
| | | ticketItemMapper.insert(itemDO); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // 4. æ´æ°éä»¶ |
| | |
| | | |
| | | // ========== å
鍿¹æ³ ========== |
| | | |
| | | /** çææç»å®ååå·ï¼å·¥åå·-两ä½åºå· */ |
| | | private String buildItemNo(String ticketNo, int index) { |
| | | return ticketNo + "-" + String.format("%02d", index); |
| | | } |
| | | |
| | | private AfterSaleTicketDO validateTicketExists(Long id) { |
| | | AfterSaleTicketDO ticket = ticketMapper.selectById(id); |
| | | if (ticket == null) { |
| | |
| | | ErrorCode SALARY_PAYMENT_HAS_PAID_DETAIL = new ErrorCode(1_030_005_024, "å é¤å¤±è´¥ï¼åå ï¼åå¨å·²åæ¾æç»ï¼è¯·å
æ¤é"); |
| | | ErrorCode SALARY_STRUCTURE_NAME_EXISTS = new ErrorCode(1_030_005_025, "èªé
¬ç»æåç§°å·²åå¨"); |
| | | ErrorCode SALARY_STRUCTURE_IN_USE = new ErrorCode(1_030_005_026, "èªé
¬ç»ææ£å¨è¢«å工使ç¨ï¼æ æ³å é¤"); |
| | | ErrorCode OVERTIME_WAGE_CONFIG_NOT_EXISTS = new ErrorCode(1_030_005_027, "å²ä½å çå·¥èµé
ç½®ä¸åå¨"); |
| | | ErrorCode OVERTIME_WAGE_CONFIG_POST_EXISTS = new ErrorCode(1_030_005_028, "该å²ä½å·²é
ç½®å çå·¥èµï¼è¯·å¿é夿·»å "); |
| | | |
| | | // ========== çæ¬¡ç®¡ç 1-030-009-000 ========== |
| | | ErrorCode SHIFT_NOT_EXISTS = new ErrorCode(1_030_009_000, "çæ¬¡ä¸åå¨"); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.enums; |
| | | |
| | | import lombok.Getter; |
| | | import lombok.RequiredArgsConstructor; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * åå·¥ç±»å«æä¸¾ï¼ç¨äºå çå·¥èµçå·®å¼åæ ¸ç®ã |
| | | * |
| | | * <ul> |
| | | * <li>æ®éåå·¥ï¼å çå·¥èµæå²ä½é
ç½®çåºå®æ¶èªï¼é»è®¤ 20 å
/å°æ¶ï¼æ ¸ç®</li> |
| | | * <li>æå·¥ï¼å çèªèµæ 1:1 æ ¸ç®ï¼ä¸å¹³æ¶æ¶èªä¸è´ï¼åºæ¬å·¥èµ / 21.75 / 8ï¼</li> |
| | | * <li>管çï¼ä¸è®¡å çå·¥èµ</li> |
| | | * </ul> |
| | | */ |
| | | @Getter |
| | | @RequiredArgsConstructor |
| | | public enum HrmEmployeeCategoryEnum { |
| | | |
| | | NORMAL(1, "æ®éåå·¥"), |
| | | TECHNICIAN(2, "æå·¥"), |
| | | MANAGER(3, "管ç"); |
| | | |
| | | private final Integer category; |
| | | private final String name; |
| | | |
| | | public static HrmEmployeeCategoryEnum valueOfCategory(Integer category) { |
| | | if (category == null) { |
| | | return null; |
| | | } |
| | | return Arrays.stream(values()).filter(item -> item.category.equals(category)).findFirst().orElse(null); |
| | | } |
| | | |
| | | public static String getName(Integer category) { |
| | | HrmEmployeeCategoryEnum value = valueOfCategory(category); |
| | | return value == null ? "" : value.name; |
| | | } |
| | | |
| | | } |
| | |
| | | import cn.idev.excel.annotation.ExcelProperty; |
| | | import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; |
| | | import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; |
| | | import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | |
| | | @DictFormat("hrm_employee_status") |
| | | private Integer employeeStatus; |
| | | |
| | | @Schema(description = "å工类å«") |
| | | @ExcelProperty(value = "å工类å«", converter = DictConvert.class) |
| | | @DictFormat("hrm_employee_category") |
| | | private Integer employeeCategory; |
| | | |
| | | @Schema(description = "å¹´åä½é¢ï¼å¤©ï¼") |
| | | @ExcelProperty("å¹´åä½é¢") |
| | | private BigDecimal annualLeaveBalance; |
| | |
| | | @ExcelProperty("å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | @Schema(description = "éä»¶å表") |
| | | private List<StorageBlobRespDTO> attachmentList; |
| | | |
| | | } |
| | |
| | | @NotNull(message = "åå·¥ç¶æä¸è½ä¸ºç©º") |
| | | private Integer employeeStatus; |
| | | |
| | | @Schema(description = "å工类å«ï¼1-æ®éåå·¥ï¼2-æå·¥ï¼3-管ç", example = "1") |
| | | private Integer employeeCategory; |
| | | |
| | | @Schema(description = "å¹´åä½é¢ï¼å¤©ï¼", example = "5") |
| | | private BigDecimal annualLeaveBalance; |
| | | |
| | |
| | | @Valid |
| | | private List<HrmEmployeeEmergencyContactSaveReqVO> emergencyContacts; |
| | | |
| | | @Schema(description = "éä»¶ blobId å表", example = "[1, 2, 3]") |
| | | private List<Long> blobIds; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.controller.admin.salary; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmOvertimeWageConfigRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmOvertimeWageConfigSaveReqVO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmOvertimeWageConfigDO; |
| | | import cn.iocoder.yudao.module.hrm.service.salary.HrmOvertimeWageConfigService; |
| | | import cn.iocoder.yudao.module.system.api.dept.PostApi; |
| | | import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; |
| | | 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.validation.Valid; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.DeleteMapping; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.PutMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | |
| | | @Tag(name = "管çåå° - HRM å çå·¥èµç®¡ç") |
| | | @RestController |
| | | @RequestMapping("/hrm/salary/overtime-wage") |
| | | @Validated |
| | | public class HrmOvertimeWageConfigController { |
| | | |
| | | @Resource |
| | | private HrmOvertimeWageConfigService overtimeWageConfigService; |
| | | @Resource |
| | | private PostApi postApi; |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "å建å²ä½å çå·¥èµé
ç½®") |
| | | @PreAuthorize("@ss.hasPermission('hrm:overtime-wage:create')") |
| | | public CommonResult<Long> createOvertimeWageConfig(@Valid @RequestBody HrmOvertimeWageConfigSaveReqVO reqVO) { |
| | | return success(overtimeWageConfigService.createOvertimeWageConfig( |
| | | BeanUtils.toBean(reqVO, HrmOvertimeWageConfigDO.class))); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Operation(summary = "æ´æ°å²ä½å çå·¥èµé
ç½®") |
| | | @PreAuthorize("@ss.hasPermission('hrm:overtime-wage:update')") |
| | | public CommonResult<Boolean> updateOvertimeWageConfig(@Valid @RequestBody HrmOvertimeWageConfigSaveReqVO reqVO) { |
| | | overtimeWageConfigService.updateOvertimeWageConfig(BeanUtils.toBean(reqVO, HrmOvertimeWageConfigDO.class)); |
| | | return success(true); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "å é¤å²ä½å çå·¥èµé
ç½®") |
| | | @Parameter(name = "id", description = "é
ç½®ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('hrm:overtime-wage:delete')") |
| | | public CommonResult<Boolean> deleteOvertimeWageConfig(@RequestParam("id") Long id) { |
| | | overtimeWageConfigService.deleteOvertimeWageConfig(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·å¾å²ä½å çå·¥èµé
ç½®") |
| | | @Parameter(name = "id", description = "é
ç½®ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('hrm:overtime-wage:query')") |
| | | public CommonResult<HrmOvertimeWageConfigRespVO> getOvertimeWageConfig(@RequestParam("id") Long id) { |
| | | return success(convert(overtimeWageConfigService.getOvertimeWageConfig(id))); |
| | | } |
| | | |
| | | @GetMapping("/list") |
| | | @Operation(summary = "è·å¾å²ä½å çå·¥èµé
ç½®å表") |
| | | @PreAuthorize("@ss.hasPermission('hrm:overtime-wage:query')") |
| | | public CommonResult<List<HrmOvertimeWageConfigRespVO>> getOvertimeWageConfigList( |
| | | @RequestParam(value = "postId", required = false) Long postId, |
| | | @RequestParam(value = "status", required = false) Integer status) { |
| | | return success(convertList(overtimeWageConfigService.getOvertimeWageConfigList(postId, status))); |
| | | } |
| | | |
| | | private HrmOvertimeWageConfigRespVO convert(HrmOvertimeWageConfigDO config) { |
| | | if (config == null) { |
| | | return null; |
| | | } |
| | | return convertList(Collections.singletonList(config)).get(0); |
| | | } |
| | | |
| | | private List<HrmOvertimeWageConfigRespVO> convertList(List<HrmOvertimeWageConfigDO> list) { |
| | | List<HrmOvertimeWageConfigRespVO> voList = BeanUtils.toBean(list, HrmOvertimeWageConfigRespVO.class); |
| | | List<Long> postIds = voList.stream().map(HrmOvertimeWageConfigRespVO::getPostId) |
| | | .filter(Objects::nonNull).distinct().collect(Collectors.toList()); |
| | | if (CollUtil.isNotEmpty(postIds)) { |
| | | Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds); |
| | | voList.forEach(vo -> { |
| | | PostRespDTO post = postMap.get(vo.getPostId()); |
| | | if (post != null) { |
| | | vo.setPostName(post.getName()); |
| | | } |
| | | }); |
| | | } |
| | | return voList; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Schema(description = "管çåå° - å²ä½å çå·¥èµé
ç½® Response VO") |
| | | @Data |
| | | public class HrmOvertimeWageConfigRespVO { |
| | | |
| | | @Schema(description = "é
ç½®ç¼å·") |
| | | private Long id; |
| | | |
| | | @Schema(description = "å²ä½ID") |
| | | private Long postId; |
| | | |
| | | @Schema(description = "å²ä½åç§°") |
| | | private String postName; |
| | | |
| | | @Schema(description = "å çæ¶èªï¼å
/å°æ¶ï¼") |
| | | private BigDecimal hourlyRate; |
| | | |
| | | @Schema(description = "æ¯ææå¤§å çæ¶é¿ä¸éï¼å°æ¶ï¼ï¼ä¸ºç©ºè¡¨ç¤ºä¸é") |
| | | private BigDecimal maxOvertimeHours; |
| | | |
| | | @Schema(description = "ç¶æï¼0-å¯ç¨ï¼1-ç¦ç¨") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | private String remark; |
| | | |
| | | @Schema(description = "å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.DecimalMin; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import jakarta.validation.constraints.Size; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | |
| | | @Schema(description = "管çåå° - å²ä½å çå·¥èµé
ç½®æ°å¢/ä¿®æ¹ Request VO") |
| | | @Data |
| | | public class HrmOvertimeWageConfigSaveReqVO { |
| | | |
| | | @Schema(description = "é
ç½®ç¼å·", example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "å²ä½ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "å²ä½ä¸è½ä¸ºç©º") |
| | | private Long postId; |
| | | |
| | | @Schema(description = "å çæ¶èªï¼å
/å°æ¶ï¼ï¼é»è®¤ 20", requiredMode = Schema.RequiredMode.REQUIRED, example = "20.00") |
| | | @NotNull(message = "å çæ¶èªä¸è½ä¸ºç©º") |
| | | @DecimalMin(value = "0", message = "å çæ¶èªä¸è½å°äº 0") |
| | | private BigDecimal hourlyRate; |
| | | |
| | | @Schema(description = "æ¯ææå¤§å çæ¶é¿ä¸éï¼å°æ¶ï¼ï¼ä¸ºç©ºè¡¨ç¤ºä¸é", example = "36.0") |
| | | @DecimalMin(value = "0", message = "æå¤§å çæ¶é¿ä¸è½å°äº 0") |
| | | private BigDecimal maxOvertimeHours; |
| | | |
| | | @Schema(description = "ç¶æï¼0-å¯ç¨ï¼1-ç¦ç¨", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| | | @NotNull(message = "ç¶æä¸è½ä¸ºç©º") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | @Size(max = 500, message = "夿³¨ä¸è½è¶
è¿ 500 个å符") |
| | | private String remark; |
| | | |
| | | } |
| | |
| | | private Integer employeeStatus; |
| | | |
| | | /** |
| | | * åå·¥ç±»å« |
| | | * |
| | | * 1-æ®éåå·¥ï¼2-æå·¥ï¼3-管çï¼ç¨äºå çå·¥èµå·®å¼åæ ¸ç® |
| | | */ |
| | | private Integer employeeCategory; |
| | | |
| | | /** |
| | | * å¹´åä½é¢ï¼å¤©ï¼ |
| | | */ |
| | | private BigDecimal annualLeaveBalance; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.dal.dataobject.salary; |
| | | |
| | | 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; |
| | | |
| | | /** |
| | | * å²ä½å çå·¥èµé
ç½® DO |
| | | * |
| | | * <p>ç¨äºãå çå·¥èµç®¡çãï¼ä¸åå²ä½ï¼å·¥ç§ï¼é
ç½®ä¸åçå çæ¶èªä¸æå¤§å çæ¶é¿ä¸éã |
| | | * æ ¸ç®æ¶æ®éåå·¥ææ¤é
ç½®çåºå®æ¶èªè®¡å çå·¥èµã |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @TableName("hrm_overtime_wage_config") |
| | | @KeySequence("hrm_overtime_wage_config_seq") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @ToString(callSuper = true) |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class HrmOvertimeWageConfigDO extends BaseDO { |
| | | |
| | | /** |
| | | * é
ç½®ID |
| | | */ |
| | | @TableId |
| | | private Long id; |
| | | |
| | | /** |
| | | * å²ä½ID |
| | | * |
| | | * å
³è PostDO#getId() |
| | | */ |
| | | private Long postId; |
| | | |
| | | /** |
| | | * å çæ¶èªï¼å
/å°æ¶ï¼ï¼é»è®¤ 20 |
| | | */ |
| | | private BigDecimal hourlyRate; |
| | | |
| | | /** |
| | | * æ¯ææå¤§å çæ¶é¿ä¸éï¼å°æ¶ï¼ |
| | | * |
| | | * 为空表示ä¸éå¶ |
| | | */ |
| | | private BigDecimal maxOvertimeHours; |
| | | |
| | | /** |
| | | * ç¶æï¼0-å¯ç¨ï¼1-ç¦ç¨ |
| | | */ |
| | | private Integer status; |
| | | |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.dal.mysql.salary; |
| | | |
| | | 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.salary.HrmOvertimeWageConfigDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * å²ä½å çå·¥èµé
ç½® Mapper |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Mapper |
| | | public interface HrmOvertimeWageConfigMapper extends BaseMapperX<HrmOvertimeWageConfigDO> { |
| | | |
| | | default List<HrmOvertimeWageConfigDO> selectListByCondition(Long postId, Integer status) { |
| | | return selectList(new LambdaQueryWrapperX<HrmOvertimeWageConfigDO>() |
| | | .eqIfPresent(HrmOvertimeWageConfigDO::getPostId, postId) |
| | | .eqIfPresent(HrmOvertimeWageConfigDO::getStatus, status) |
| | | .orderByDesc(HrmOvertimeWageConfigDO::getId)); |
| | | } |
| | | |
| | | default HrmOvertimeWageConfigDO selectByPostId(Long postId) { |
| | | return selectOne(HrmOvertimeWageConfigDO::getPostId, postId); |
| | | } |
| | | |
| | | /** |
| | | * æ¥è¯¢å¤ä¸ªå²ä½ä¸ãå¯ç¨ãçå çå·¥èµé
ç½®ï¼ç¨äºèªé
¬æ ¸ç®æ¶æåå·¥å²ä½å¹é
ã |
| | | */ |
| | | default List<HrmOvertimeWageConfigDO> selectEnabledListByPostIds(Collection<Long> postIds) { |
| | | return selectList(new LambdaQueryWrapperX<HrmOvertimeWageConfigDO>() |
| | | .inIfPresent(HrmOvertimeWageConfigDO::getPostId, postIds) |
| | | .eq(HrmOvertimeWageConfigDO::getStatus, 0)); |
| | | } |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; |
| | | import cn.iocoder.yudao.module.system.api.permission.RoleApi; |
| | | import cn.iocoder.yudao.module.system.api.permission.dto.RoleRespDTO; |
| | | import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; |
| | | import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO; |
| | | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserUpdateReqDTO; |
| | |
| | | @Resource |
| | | private RoleApi roleApi; |
| | | @Resource |
| | | private StorageAttachmentApi storageAttachmentApi; |
| | | @Resource |
| | | @Lazy // èªæ³¨å
¥éå»¶è¿å è½½ï¼å¦å bean 被代çå
è£
åæ³¨å
¥åå§çæ¬å¯¼è´å¯å¨å¤±è´¥ |
| | | private HrmEmployeeService self; |
| | | |
| | |
| | | } |
| | | employeeMapper.insert(employee); |
| | | Long employeeId = employee.getId(); |
| | | // ç»å®éä»¶ |
| | | storageAttachmentApi.bindAttachments("file", "hrm_employee", employeeId, createReqVO.getBlobIds()); |
| | | // 7. åæ¥æ·»å æè²ç»å |
| | | if (CollUtil.isNotEmpty(createReqVO.getEducations())) { |
| | | createReqVO.getEducations().forEach(educationVO -> { |
| | |
| | | syncWorkHistoryList(employeeId, updateReqVO.getWorkHistories()); |
| | | // åæ¥ç´§æ¥è系人 |
| | | syncEmergencyContactList(employeeId, updateReqVO.getEmergencyContacts()); |
| | | // æ´æ°éä»¶ |
| | | storageAttachmentApi.updateAttachments("file", "hrm_employee", employeeId, updateReqVO.getBlobIds()); |
| | | } |
| | | |
| | | private void syncEducationList(Long employeeId, List<HrmEmployeeEducationSaveReqVO> newList) { |
| | |
| | | educationMapper.deleteByEmployeeId(id); |
| | | workHistoryMapper.deleteByEmployeeId(id); |
| | | emergencyContactMapper.deleteByEmployeeId(id); |
| | | // å é¤éä»¶ |
| | | storageAttachmentApi.deleteAttachmentsByRecord("hrm_employee", id); |
| | | // å é¤åå·¥ |
| | | employeeMapper.deleteById(id); |
| | | } |
| | |
| | | respVO.setRoleNames(roles.stream().map(RoleRespDTO::getName).collect(Collectors.joining(","))); |
| | | } |
| | | } |
| | | // å¡«å
éä»¶ |
| | | respVO.setAttachmentList(storageAttachmentApi.listAttachments("hrm_employee", id)); |
| | | return respVO; |
| | | } |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.service.salary; |
| | | |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmOvertimeWageConfigDO; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * å²ä½å çå·¥èµé
ç½® Service |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | public interface HrmOvertimeWageConfigService { |
| | | |
| | | Long createOvertimeWageConfig(HrmOvertimeWageConfigDO config); |
| | | |
| | | void updateOvertimeWageConfig(HrmOvertimeWageConfigDO config); |
| | | |
| | | void deleteOvertimeWageConfig(Long id); |
| | | |
| | | HrmOvertimeWageConfigDO getOvertimeWageConfig(Long id); |
| | | |
| | | List<HrmOvertimeWageConfigDO> getOvertimeWageConfigList(Long postId, Integer status); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.service.salary; |
| | | |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmOvertimeWageConfigDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmOvertimeWageConfigMapper; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.OVERTIME_WAGE_CONFIG_NOT_EXISTS; |
| | | import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.OVERTIME_WAGE_CONFIG_POST_EXISTS; |
| | | |
| | | /** |
| | | * å²ä½å çå·¥èµé
ç½® Service å®ç°ç±» |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | @Service |
| | | @Validated |
| | | public class HrmOvertimeWageConfigServiceImpl implements HrmOvertimeWageConfigService { |
| | | |
| | | /** |
| | | * é»è®¤å çæ¶èªï¼å
/å°æ¶ï¼ |
| | | */ |
| | | private static final BigDecimal DEFAULT_HOURLY_RATE = new BigDecimal("20"); |
| | | |
| | | @Resource |
| | | private HrmOvertimeWageConfigMapper overtimeWageConfigMapper; |
| | | |
| | | @Override |
| | | public Long createOvertimeWageConfig(HrmOvertimeWageConfigDO config) { |
| | | validatePostUnique(null, config.getPostId()); |
| | | // æªå¡«åæ¶èªæ¶ï¼ä½¿ç¨é»è®¤ 20 å
/å°æ¶ |
| | | if (config.getHourlyRate() == null) { |
| | | config.setHourlyRate(DEFAULT_HOURLY_RATE); |
| | | } |
| | | overtimeWageConfigMapper.insert(config); |
| | | return config.getId(); |
| | | } |
| | | |
| | | @Override |
| | | public void updateOvertimeWageConfig(HrmOvertimeWageConfigDO config) { |
| | | validateExists(config.getId()); |
| | | validatePostUnique(config.getId(), config.getPostId()); |
| | | overtimeWageConfigMapper.updateById(config); |
| | | } |
| | | |
| | | @Override |
| | | public void deleteOvertimeWageConfig(Long id) { |
| | | validateExists(id); |
| | | overtimeWageConfigMapper.deleteById(id); |
| | | } |
| | | |
| | | @Override |
| | | public HrmOvertimeWageConfigDO getOvertimeWageConfig(Long id) { |
| | | return overtimeWageConfigMapper.selectById(id); |
| | | } |
| | | |
| | | @Override |
| | | public List<HrmOvertimeWageConfigDO> getOvertimeWageConfigList(Long postId, Integer status) { |
| | | return overtimeWageConfigMapper.selectListByCondition(postId, status); |
| | | } |
| | | |
| | | private void validateExists(Long id) { |
| | | if (id == null || overtimeWageConfigMapper.selectById(id) == null) { |
| | | throw exception(OVERTIME_WAGE_CONFIG_NOT_EXISTS); |
| | | } |
| | | } |
| | | |
| | | private void validatePostUnique(Long id, Long postId) { |
| | | if (postId == null) { |
| | | return; |
| | | } |
| | | HrmOvertimeWageConfigDO config = overtimeWageConfigMapper.selectByPostId(postId); |
| | | if (config != null && !config.getId().equals(id)) { |
| | | throw exception(OVERTIME_WAGE_CONFIG_POST_EXISTS); |
| | | } |
| | | } |
| | | |
| | | } |
| | |
| | | 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; |
| | |
| | | private HrmOvertimeApplicationMapper overtimeApplicationMapper; |
| | | @Resource |
| | | private HrmSalaryStructureMapper salaryStructureMapper; |
| | | @Resource |
| | | private HrmOvertimeWageConfigMapper overtimeWageConfigMapper; |
| | | @Resource |
| | | private HrmLeaveTypeConfigMapper leaveTypeConfigMapper; |
| | | @Resource |
| | |
| | | } |
| | | |
| | | /** |
| | | * æ±æ»å½æå®¡æ¹éè¿çå çç³è¯·ãå°æ¶å·¥èµæåºæ¬å·¥èµ / 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) { |
| | |
| | | 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)); |
| | | } |
| | | |
| | | /** |
| | | * è§£æåå·¥å²ä½å¯¹åºçå çå·¥èµè§åï¼æ¶èªåå½ä¸é
ç½®ä¸çæå¤§å¼ï¼ |
| | | * ä¸éåâæå®½âï¼å卿ªè®¾ç½®ä¸éçé
ç½®åè§ä¸ºä¸éï¼å¦ååæå¤§å¼ï¼ã |
| | | */ |
| | | 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) {} |
| | |
| | | HRM_RESIGNATION_APPLICATION("hrm_resignation_application", "HRM 离èç³è¯·"), |
| | | HRM_ATTENDANCE_EXCEPTION("hrm_attendance_exception", "HRM èå¤å¼å¸¸"), |
| | | HRM_EMPLOYEE_CONTRACT("hrm_employee_contract", "HRM åå·¥åå"), |
| | | HRM_EMPLOYEE("hrm_employee", "HRM åå·¥"), |
| | | |
| | | // ===== å®å ===== |
| | | AFTER_SALE_TICKET("aftersales_after_sale_ticket", "å®åå·¥å"), |