feat(hrm): 请假导入、加班代申请、批量排班与考勤应出勤重算
- 请假申请支持 Excel 导入:按手机号匹配员工档案,逐行独立校验并返回精确失败原因,仅生成草稿不自动提交
- 加班申请支持代申请:新增 employeeId 与 hrm:overtime:manage 权限,持有人可查看/操作全部单据,流程以申请人身份发起和撤销
- 排班新增批量排班:部门(含下级)+ 岗位 + 员工状态,节假日配置优先于周末规则,跳过已审批请假并被计入统计
- 排班唯一键 uk_employee_date 不含 deleted,保存/批量排班前先查询含已删除行并复活旧行,避免唯一键冲突导致整批回滚
- 考勤汇总查询/生成时按最新节假日配置重算应出勤天数并回写
- 补齐 hrm_holiday_type、hrm_is_legal_holiday 字典及 hrm_attendance_holiday 时间段字段,修复节假日配置类型为空
- 新增菜单权限 SQL 与 config_export_all_20261108.sql
Co-Authored-By: Claude Code <noreply@anthropic.com>
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # HRM 请å导å
¥ / å ç代ç³è¯· / æ¹éæç / èå¤éç® ââ èè°è¯´æï¼2026-11-08ï¼ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - 人äºå®¡æ¹ â 请åç³è¯·ï¼`hrm/leave/index`ï¼ |
| | | - 人äºå®¡æ¹ â å çç³è¯·ï¼`hrm/overtime/index`ï¼ |
| | | - èå¤ç®¡ç â æç管çï¼`hrm/schedule/index`ï¼ |
| | | - èå¤ç®¡ç â èå¤ç»è®¡æ±æ» |
| | | |
| | | ## ä¸ã请å Excel 导å
¥ |
| | | |
| | | ### API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | æé | 说æ | |
| | | |------|------|------|------| |
| | | | GET | `/hrm/leave/import-template` | `hrm:leave:import` | ä¸è½½å¯¼å
¥æ¨¡æ¿ï¼å«ç¤ºä¾è¡ï¼ | |
| | | | POST | `/hrm/leave/import` | `hrm:leave:import` | multipart ä¸ä¼ ï¼å段å `file` | |
| | | |
| | | **模æ¿åï¼é¡ºåºåºå®ï¼ï¼** |
| | | |
| | | | å | å¿
å¡« | 说æ | |
| | | |----|------|------| |
| | | | ææºå· | æ¯ | æææºå·å¹é
å工档æ¡ï¼å¡«å工档æ¡éç»è®°ç 11 使æºå· | |
| | | | åå·¥å§å | å¦ | å¡«ååä¼ä¸ææºå·å¹é
å°çåå·¥å§åæ ¸å¯¹ï¼ä¸ä¸è´è¯¥è¡å¤±è´¥ | |
| | | | 请åç±»å | æ¯ | äºå/ç
å/å¹´å/å©å/产å/丧åï¼ææ°å 1-6 | |
| | | | å¼å§æ¶é´ | æ¯ | `yyyy-MM-dd HH:mm` æ `yyyy-MM-dd HH:mm:ss`ï¼ä¹å¯åªå¡«æ¥æ | |
| | | | ç»ææ¶é´ | æ¯ | å¿
é¡»æäºå¼å§æ¶é´ | |
| | | | è¯·åæ¶é¿(天) | æ¯ | å¤§äº 0ï¼æ¯æ 0.5 | |
| | | | 请ååå | æ¯ | â¤500 å | |
| | | | 夿³¨ | å¦ | â¤500 å | |
| | | |
| | | **ååºï¼** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "totalCount": 10, |
| | | "successCount": 8, |
| | | "failureCount": 2, |
| | | "failures": [ |
| | | { "rowNum": 3, "phone": "13800138000", "employeeName": "å¼ ä¸", |
| | | "reason": "ææºå·[13800138000]æªå¹é
å°å工档æ¡ï¼è¯·å
å¨ãå工档æ¡ãä¸ç»´æ¤è¯¥ææºå·å鿰坼å
¥" } |
| | | ] |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### ä¸å¡è§å |
| | | |
| | | - **éè¡ç¬ç«å¤ç**ï¼æä¸è¡å¤±è´¥ä¸å½±åå
¶å®è¡ï¼å¤±è´¥åå 精确å°ã维度 + å®é
å¼ + ä¸ä¸æ¥å¨ä½ãã |
| | | - **åªçæè稿**ï¼å¯¼å
¥æåçæ°æ®ç¶æä¸ºè稿ï¼0ï¼ï¼ä¸ä¼èªå¨å起审æ¹ï¼éå¨åè¡¨éæ¡ç¹ãæäº¤ãã |
| | | - å工档æ¡å¿
é¡»åæ¶æ»¡è¶³ï¼ææºå·åå¨ãå·²ç»å®ç³»ç»è´¦å·ã已设置é¨é¨ã |
| | | - å䏿件å
ä¸åè¿éæ ¡éªï¼å¦éé²éï¼æãææºå· + å¼å§æ¶é´ãäººå·¥æ ¸å¯¹ã |
| | | - è¡å· `rowNum` å«è¡¨å¤´ï¼è¡¨å¤´ä¸ºç¬¬ 1 è¡ï¼ã |
| | | |
| | | ## äºãå ç代ç³è¯· |
| | | |
| | | ### åæ´ |
| | | |
| | | | é¡¹ç® | 说æ | |
| | | |------|------| |
| | | | æ°å¢æé | `hrm:overtime:manage`ï¼èå 1075664ï¼å·²æäºè§è² 166 ç产管çåã168 ç»çï¼ | |
| | | | `POST /hrm/overtime/create` | `SaveReqVO` æ°å¢ `employeeId`ï¼ç³è¯·äººåå·¥IDï¼ï¼ä¸ä¼ =æ¬äºº | |
| | | | é¡µé¢ | è¡¨åæ°å¢ã代ç³è¯·åå·¥ãéæ©å¨ï¼ä»
ææ `hrm:overtime:manage` æ¶æ¾ç¤º | |
| | | |
| | | ### æéææ |
| | | |
| | | | æ¯å¦æææé | å表 | 详æ
| ä¿®æ¹/å é¤/æäº¤/æ¤é | |
| | | |--------------|------|------|---------------------| |
| | | | å¦ | ä»
æ¬äººåå | æ¬äººææµç¨åä¸äºº | ä»
æ¬äººè稿/驳åå | |
| | | | æ¯ | å
¨é¨äººçåå | å
¨é¨ | å
¨é¨äººçåå | |
| | | |
| | | ### æ ¡éªä¸é误æç¤º |
| | | |
| | | - æ æéå´æå®ä»äººï¼`æ æä¸ºå
¶ä»å工代ç³è¯·å çï¼å½åè´¦å·æªææ hrm:overtime:manage æéï¼è¯·ç§»é¤ç³è¯·äººï¼æ¹ä¸ºæ¬äººï¼æè系管çåæäºè¯¥æé` |
| | | - æå®åå·¥ä¸åå¨ï¼`æéç³è¯·äººçå工档æ¡ä¸åå¨ï¼è¯·å·æ°åå·¥å表åéæ°éæ©` |
| | | - æ æéæä½ä»äººååï¼`æ ææä½è¯¥å çç³è¯·ï¼å½åè´¦å·æ¢ä¸æ¯ç³è¯·äººï¼ä¹æªææ hrm:overtime:manage æé` |
| | | |
| | | ### æµç¨å起人 |
| | | |
| | | 代ç³è¯·æäº¤åï¼BPM æµç¨å®ä¾ä»¥**ç³è¯·äººå工对åºçç³»ç»è´¦å·**åèµ·ï¼ä¸æ¯ä»£ç³è¯·äººï¼ï¼æ¤éåæ ·ä»¥ç³è¯·äººèº«ä»½æ§è¡ï¼ä¿è¯å®¡æ¹æµä¸çç³è¯·äººãå¾
åå½å±æ£ç¡®ã |
| | | |
| | | ## ä¸ãæ¹éæç |
| | | |
| | | ### API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | æé | 说æ | |
| | | |------|------|------|------| |
| | | | POST | `/hrm/employee-schedule/batch-assign` | `hrm:employee-schedule:save` | æé¨é¨ï¼å«ä¸çº§ï¼+ å²ä½ + åå·¥ç¶ææ¹éæç | |
| | | | GET | `/hrm/employee-schedule/leave-list` | ç»å½å³å¯ | æçé¡µè¯·åæ è®°ï¼å·²å®¡æ¹ï¼æå¤©å±å¼ï¼ | |
| | | |
| | | **æ¹éæç请æ±ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | deptId | Long | æ¯ | é¨é¨IDï¼å端èªå¨å
嫿æä¸çº§é¨é¨ | |
| | | | postId | Long | å¦ | å²ä½IDï¼æåå·¥æ¡£æ¡ `postIds` 夿ï¼ä¸ä¼ =ä¸é | |
| | | | employeeStatus | Integer | å¦ | 1 å¨è / 2 è¯ç¨ / 3 离èï¼ä¸ä¼ =ä¸é | |
| | | | startDate | LocalDate | æ¯ | å¼å§æ¥æï¼å«ï¼ | |
| | | | endDate | LocalDate | æ¯ | ç»ææ¥æï¼å«ï¼ï¼å次æå¤ 62 天 | |
| | | | shiftId | Long | æ¯ | çæ¬¡ID | |
| | | | remark | String | å¦ | åå
¥æçç夿³¨ | |
| | | |
| | | **ååºï¼** |
| | | |
| | | ```json |
| | | { |
| | | "matchedEmployeeCount": 12, |
| | | "scheduledCount": 240, |
| | | "skippedLeaveCount": 3, |
| | | "clearedRestDayCount": 96 |
| | | } |
| | | ``` |
| | | |
| | | ### æçè§åï¼æä¼å
çº§ï¼ |
| | | |
| | | 1. **è忥é
ç½®ä¼å
**ï¼`hrm_attendance_holiday` ä¸é
ç½®è¦ççæ¥æï¼ä»¥é
ç½®çç±»å为åï¼å·¥ä½æ¥/伿¯æ¥ï¼ï¼æ²¡æé
ç½®æ¶ææå¨å
æ¥å¤æã |
| | | 2. **工使¥**ï¼åå
¥æéçæ¬¡ï¼å·²ææçåè¦çã |
| | | 3. **伿¯æ¥/è忥**ï¼æ¸
空该åå·¥å½å¤©ççæ¬¡ã |
| | | 4. **已审æ¹è¯·åä¼å
**ï¼åå·¥å½å¤©åå¨å®¡æ¹éè¿ï¼status=20ï¼ç请ååæ¶ï¼**è·³è¿è¯¥åå
æ ¼**ï¼ä¸æ¹å¨åææçï¼ä¹ä¸è®¡å
¥åå
¥/æ¸
空æ°éã |
| | | 5. 忬¡æ¥æåºé´è¶
è¿ 62 天伿ç»ï¼åæ¹æ§è¡ã |
| | | |
| | | ### å¯ä¸é®ä¸é»è¾å é¤ |
| | | |
| | | `hrm_employee_schedule` çå¯ä¸é® `uk_employee_date(employee_id, date)` **ä¸å
å« `deleted`**ãé»è¾å é¤åçæ§è¡ä»å ç¨å¯ä¸é®ãå æ¤ä¿å/æ¹éæçæ¶ï¼ |
| | | |
| | | 1. å
ç¨å
å«å·²å é¤è¡çåå§ SQL æ¥è¯¢è¯¥åå·¥è¯¥æ¥æçè®°å½ï¼ |
| | | 2. 妿å½ä¸å·²å é¤è¡ï¼æ§è¡ `UPDATE ... SET deleted = 0`ã夿´»ãå¹¶è¦ççæ¬¡ï¼**ä¸å INSERT**ï¼ |
| | | 3. åªæå®å
¨ä¸åå¨è®°å½æ¶æ INSERTã |
| | | |
| | | è¿æ ·é¿å
ãå è¿ä¸æ¬¡åæåä¸å¤©ãæå¯ä¸é®å¯¼è´æ´æ¹äºå¡åæ»ã |
| | | |
| | | ## åãæç页 |
| | | |
| | | | åè½ | 说æ | |
| | | |------|------| |
| | | | è¯·åæ è®° | 已审æ¹è¯·åçåå
æ ¼æ¾ç¤ºç´«è²ã请åãæ ç¾ï¼å¹¶å¨å¾ä¾ä¸å¢å ã请åï¼å·²å®¡æ¹ï¼ãç´«è²è¯´æï¼æ¬åæ¾ç¤ºè¯·åç±»å | |
| | | | æ¹æ ¼åå³ä¿å | éå®ç次åç«å³è°ç¨ `/hrm/employee-schedule/save` ä¿å该åå
æ ¼ï¼å¤±è´¥æ¶æ¢å¤åå¼å¹¶æç¤º | |
| | | | 忢æ¡ä»¶ç¦æ¢ä¿å | 忢æä»½/é¨é¨åè¥æªéæ°ç¹ãæ¥è¯¢æçãï¼ä¿åæé®ç¦ç¨ãåå
æ ¼ä¸å¯ç¼è¾ï¼å¹¶æ¾ç¤ºæ©è²æç¤ºæ¡ | |
| | | | æ¹éæçå
¥å£ | å·¥å
·æ ãæ¹éæçãå¼¹çªï¼å²ä½ãåå·¥ç¶æãæ¥æåºé´ãçæ¬¡ï¼æ§è¡åèªå¨å·æ°å½å页 | |
| | | |
| | | ## äºãè夿±æ»éç® |
| | | |
| | | - **æ¥è¯¢æ¶éç®**ï¼`/hrm/attendance/summary/page`ã`/get`ãé¨é¨æåº¦å表å¨è¿ååï¼æ**å½åææ°è忥é
ç½®**éç® `workDays`ï¼ä¸åºä¸å¼ä¸åæ¶ååã |
| | | - **çææ¶éç®**ï¼`/hrm/attendance/summary/generate` 忬就æè忥é
置计ç®ï¼ä¿æä¸åã |
| | | - å䏿¬¡è¯·æ±å
åä¸å¹´æåªè®¡ç®ä¸æ¬¡ï¼é¿å
N 次é忥åã |
| | | - å¹´æçæ ¼å¼å¿
须为 `yyyy-MM`ï¼èæ°æ®ä¼è·³è¿å¹¶è®°å½ WARNï¼ä¸å½±åå
¶å®è¡è¿åã |
| | | |
| | | ## å
ãæ°æ®ä¸é
ç½®åæ´ |
| | | |
| | | - `docs/sql/config_export_all_20261108.sql`ï¼å¨ 20261106 çæ¬åºç¡ä¸è¿½å 2026-11-08 æé段ï¼é¶æ®µäºï¼ã |
| | | - `sql/mysql/hrm_20261108_menu_permissions.sql`ï¼æ¬æ¬¡æ°å¢çä¸¤æ¡æéãè§è²ææï¼å¯åç¬æ§è¡ã |
| | | - æ°å¢èåï¼ |
| | | - 1075663 `请åç³è¯·-导å
¥` / `hrm:leave:import`ï¼ç¶èå 6003ï¼ï¼æäºè§è² 163-168 |
| | | - 1075664 `å ç代ç³è¯·ç®¡ç` / `hrm:overtime:manage`ï¼ç¶èå 1065211ï¼ï¼æäºè§è² 166ã168 |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - `hrm:overtime:manage`ã`hrm:leave:import` å±äº**æé®æé**ï¼éè¦éæ°ç»å½ï¼æå·æ°æéç¼åï¼åå端æé®æä¼æ¾ç¤ºã |
| | | - 请å导å
¥åªå建è稿ï¼éä»¶æ æ³éè¿ Excel 带å
¥ï¼å¦å®¡æ¹éè¦éä»¶ï¼è¯·å¯¼å
¥åå¨è¯¦æ
ä¸è¡¥å
ã |
| | | - æ¹éæç伿¸
ç©ºä¼æ¯æ¥å·²æç次ï¼å±äºè¦ç弿ä½ï¼æ§è¡åè¯·ç¡®è®¤æ¥æåºé´ã |
| | | - æç页çãèªå¨æçã仿¯å端æ¬å°çæï¼æåä¼/åä¼è§åï¼ï¼ä¸ä¼è·³è¿è¯·åï¼éè¦æè忥 + 请åè·³è¿æ¶è¯·ä½¿ç¨ãæ¹éæçãã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================================ |
| | | -- 2026-11-08 HRM èåæ¥ç±»ååå
¸ |
| | | -- ç°è±¡ï¼äººäº â èå¤ç®¡ç â è忥é
ç½®ï¼æ°å¢/ç¼è¾å¼¹æ¡ãç±»åãä¸ºç©ºï¼æ å¯éé¡¹ï¼ |
| | | -- åå ï¼å端ä¸å端é½ä½¿ç¨åå
¸ hrm_holiday_typeï¼ä½åºé没æè¯¥åå
¸ç±»åä¸åå
¸æ°æ® |
| | | -- åå¼ä¸å端æä¸¾ HrmAttendanceHolidayTypeEnum 对é½ï¼ |
| | | -- 1 = 伿¯æ¥ï¼æ¾åï¼ä¸è®¡åºåºå¤ï¼ |
| | | -- 2 = 工使¥ï¼è°ä¼ä¸çï¼è®¡åºåºå¤ï¼ |
| | | -- 说æï¼é¡¹ç®ä¸ä½¿ç¨å¤ç§æ·ï¼SQL ä¸å
å« tenant_idï¼å¹çï¼å¯é夿§è¡ã |
| | | -- ============================================================ |
| | | |
| | | INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, |
| | | `creator`, `create_time`, `updater`, `update_time`, `deleted`) |
| | | VALUES (1065418, 'èå¤èåæ¥ç±»å', 'hrm_holiday_type', 0, '伿¯æ¥=æ¾åï¼å·¥ä½æ¥=è°ä¼ä¸ç', |
| | | '1', NOW(), '1', NOW(), 0) |
| | | ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `status` = 0, |
| | | `remark` = VALUES(`remark`), `deleted` = 0, `update_time` = NOW(); |
| | | |
| | | INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, |
| | | `color_type`, `css_class`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) |
| | | VALUES (1065526, 1, '伿¯æ¥', '1', 'hrm_holiday_type', 0, '', '', '1', NOW(), '1', NOW(), 0), |
| | | (1065527, 2, '工使¥', '2', 'hrm_holiday_type', 0, '', '', '1', NOW(), '1', NOW(), 0) |
| | | ON DUPLICATE KEY UPDATE `sort` = VALUES(`sort`), `label` = VALUES(`label`), `value` = VALUES(`value`), |
| | | `status` = 0, `deleted` = 0, `update_time` = NOW(); |
| | | |
| | | -- ------------------------------------------------------------ |
| | | -- æ¯å¦æ³å®è忥 hrm_is_legal_holidayï¼å表/导åºç¨ @DictFormat ç¿»è¯ï¼ |
| | | -- ------------------------------------------------------------ |
| | | INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, |
| | | `creator`, `create_time`, `updater`, `update_time`, `deleted`) |
| | | VALUES (1065419, 'æ¯å¦æ³å®è忥', 'hrm_is_legal_holiday', 0, 'èå¤è忥ï¼0=å¦ï¼1=æ¯', |
| | | '1', NOW(), '1', NOW(), 0) |
| | | ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `status` = 0, |
| | | `remark` = VALUES(`remark`), `deleted` = 0, `update_time` = NOW(); |
| | | |
| | | INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, |
| | | `color_type`, `css_class`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) |
| | | VALUES (1065528, 1, 'å¦', '0', 'hrm_is_legal_holiday', 0, '', '', '1', NOW(), '1', NOW(), 0), |
| | | (1065529, 2, 'æ¯', '1', 'hrm_is_legal_holiday', 0, '', '', '1', NOW(), '1', NOW(), 0) |
| | | ON DUPLICATE KEY UPDATE `sort` = VALUES(`sort`), `label` = VALUES(`label`), `value` = VALUES(`value`), |
| | | `status` = 0, `deleted` = 0, `update_time` = NOW(); |
| | | |
| | | -- ------------------------------------------------------------ |
| | | -- éï¼hrm_attendance_holiday çæ¶é´æ®µç»æï¼start_date / end_dateï¼ |
| | | -- è¥ç®æ åºå°æªæ§è¡è¿ï¼è¯·ä¸å¹¶æ§è¡ sql/mysql/hrm_attendance_holiday_date_range_20261017.sql |
| | | -- ------------------------------------------------------------ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================================ |
| | | -- 2026-11-08 HRM æéè¡¥å
|
| | | -- 1. hrm:leave:import 请åç³è¯· Excel 导å
¥ / 模æ¿ä¸è½½ |
| | | -- 2. hrm:overtime:manage å ç代ç³è¯·ï¼æ¥çå¹¶æä½å
¨é¨äººçå çå |
| | | -- 说æï¼é¡¹ç®ä¸ä½¿ç¨å¤ç§æ·ï¼SQL ä¸å
å« tenant_idã |
| | | -- ============================================================ |
| | | |
| | | -- 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) |
| | | VALUES (1075663, '请åç³è¯·-导å
¥', 'hrm:leave:import', 3, 91, 6003, '', '#', '', NULL, |
| | | 0, 1, 0, 1, '1', NOW(), '1', NOW(), 0) |
| | | ON DUPLICATE KEY UPDATE name = VALUES(name), permission = VALUES(permission), deleted = 0, update_time = NOW(); |
| | | |
| | | 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 (1075664, 'å ç代ç³è¯·ç®¡ç', 'hrm:overtime:manage', 3, 6, 1065211, '', '#', '', NULL, |
| | | 0, 1, 0, 1, '1', NOW(), '1', NOW(), 0) |
| | | ON DUPLICATE KEY UPDATE name = VALUES(name), permission = VALUES(permission), deleted = 0, update_time = NOW(); |
| | | |
| | | -- 2. è§è²ææï¼è¯·å导å
¥æäº 163-168ï¼å ç代ç³è¯·ç®¡çæäº 166 ç产管çåã168 ç»ç |
| | | INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted) |
| | | SELECT r.role_id, 1075663, '1', NOW(), '1', NOW(), 0 |
| | | FROM (SELECT 163 AS role_id UNION ALL SELECT 164 UNION ALL SELECT 165 |
| | | UNION ALL SELECT 166 UNION ALL SELECT 167 UNION ALL SELECT 168) r |
| | | WHERE NOT EXISTS (SELECT 1 FROM system_role_menu rm |
| | | WHERE rm.role_id = r.role_id AND rm.menu_id = 1075663 AND rm.deleted = 0); |
| | | |
| | | INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted) |
| | | SELECT r.role_id, 1075664, '1', NOW(), '1', NOW(), 0 |
| | | FROM (SELECT 166 AS role_id UNION ALL SELECT 168) r |
| | | WHERE NOT EXISTS (SELECT 1 FROM system_role_menu rm |
| | | WHERE rm.role_id = r.role_id AND rm.menu_id = 1075664 AND rm.deleted = 0); |
| | |
| | | ErrorCode LEAVE_APPLICATION_NO_EXISTS = new ErrorCode(1_030_001_004, "çæè¯·åç³è¯·åå·éå¤ï¼è¯·éè¯"); |
| | | ErrorCode LEAVE_APPLICATION_DURATION_EXCEED = new ErrorCode(1_030_001_005, "è¯·åæ¶é¿è¶
åºå¹´åä½é¢"); |
| | | ErrorCode LEAVE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_001_006, "请åå®¡æ¹æµç¨ä¸åå¨"); |
| | | ErrorCode LEAVE_APPLICATION_EMPLOYEE_NOT_EXISTS = new ErrorCode(1_030_001_007, "å½åè´¦å·æªå
³èå工档æ¡"); |
| | | ErrorCode LEAVE_APPLICATION_APPLICANT_NOT_EXISTS = new ErrorCode(1_030_001_008, "æéç³è¯·äººè´¦å·ä¸åå¨"); |
| | | ErrorCode LEAVE_APPLICATION_SELECTED_EMPLOYEE_NOT_EXISTS = new ErrorCode(1_030_001_009, "æéç³è¯·äººæªå
³èå工档æ¡"); |
| | | ErrorCode LEAVE_APPLICATION_UPDATE_FAIL_NOT_EDITABLE = new ErrorCode(1_030_001_010, "ä»
è稿æå®¡æ¹é©³åç¶æç请åç³è¯·å¯ä»¥ä¿®æ¹"); |
| | | ErrorCode LEAVE_APPLICATION_APPLICANT_IMMUTABLE = new ErrorCode(1_030_001_011, "请åç³è¯·äººå建åä¸è½ä¿®æ¹"); |
| | | ErrorCode LEAVE_APPLICATION_OPERATOR_NOT_ALLOWED = new ErrorCode(1_030_001_012, "ä»
ç³è¯·äººæå代ç³è¯·å建人å¯ä»¥æä½è¯¥è¯·åç³è¯·"); |
| | | ErrorCode LEAVE_APPLICATION_DEPT_MISMATCH = new ErrorCode(1_030_001_013, "请åç³è¯·é¨é¨ä¸æéç³è¯·äººå工档æ¡é¨é¨ä¸ä¸è´ï¼è¯·å·æ°åéè¯"); |
| | | ErrorCode LEAVE_APPLICATION_EMPLOYEE_DEPT_NOT_FOUND = new ErrorCode(1_030_001_014, "æéç³è¯·äººåå·¥æ¡£æ¡æªè®¾ç½®é¨é¨"); |
| | | ErrorCode LEAVE_APPLICATION_IMPORT_FILE_EMPTY = new ErrorCode(1_030_001_015, "导å
¥æä»¶æ²¡æå¯è¯»åçæ°æ®è¡ï¼è¯·ç¡®è®¤è¡¨å¤´ä¾æ¬¡ä¸ºï¼ææºå·ãåå·¥å§åã请åç±»åãå¼å§æ¶é´ãç»ææ¶é´ãè¯·åæ¶é¿(天)ã请ååå ã夿³¨"); |
| | | |
| | | // ========== è°å²ç³è¯· 1-030-002-000 ========== |
| | | ErrorCode TRANSFER_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_002_000, "è°å²ç³è¯·ä¸åå¨"); |
| | |
| | | ErrorCode TRANSFER_APPLICATION_APPLICANT_NOT_EXISTS = new ErrorCode(1_030_002_008, "è°å²ç³è¯·å¤±è´¥ï¼æéåå·¥æªå
³èææçç³»ç»è´¦å·"); |
| | | ErrorCode TRANSFER_APPLICATION_APPLICANT_DEPT_EMPTY = new ErrorCode(1_030_002_009, "è°å²ç³è¯·å¤±è´¥ï¼æéåå·¥æªè®¾ç½®é¨é¨"); |
| | | ErrorCode TRANSFER_APPLICATION_APPLICANT_POST_EMPTY = new ErrorCode(1_030_002_010, "è°å²ç³è¯·å¤±è´¥ï¼æéåå·¥æªè®¾ç½®å²ä½"); |
| | | ErrorCode TRANSFER_APPLICATION_EMPLOYEE_DEPT_NOT_FOUND = new ErrorCode(1_030_002_011, "æéåå·¥æªå
³èææåå·¥æ¡£æ¡ææ¡£æ¡æªè®¾ç½®é¨é¨"); |
| | | ErrorCode TRANSFER_APPLICATION_CURRENT_DEPT_MISMATCH = new ErrorCode(1_030_002_012, "åé¨é¨ä¸æéåå·¥å½åé¨é¨ä¸ä¸è´ï¼è¯·å·æ°å工信æ¯åéè¯"); |
| | | ErrorCode TRANSFER_APPLICATION_UPDATE_FAIL_NOT_EDITABLE = new ErrorCode(1_030_002_013, "ä»
è稿æå®¡æ¹é©³åç¶æçè°å²ç³è¯·å¯ä»¥ä¿®æ¹"); |
| | | ErrorCode TRANSFER_APPLICATION_APPLICANT_IMMUTABLE = new ErrorCode(1_030_002_014, "è°å²ç³è¯·äººå建åä¸è½ä¿®æ¹"); |
| | | ErrorCode TRANSFER_APPLICATION_OPERATOR_NOT_ALLOWED = new ErrorCode(1_030_002_015, "ä»
ç³è¯·äººæå代ç³è¯·å建人å¯ä»¥æä½è¯¥è°å²ç³è¯·"); |
| | | |
| | | // ========== 离èç³è¯· 1-030-003-000 ========== |
| | | ErrorCode RESIGNATION_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_003_000, "离èç³è¯·ä¸åå¨"); |
| | |
| | | ErrorCode RESIGNATION_HANDOVER_CHANGE_AFTER_START = new ErrorCode(1_030_003_016, "å·¥ä½äº¤æ¥å·²å¼å§ï¼ä¸è½æ´æ¢äº¤æ¥åå·¥"); |
| | | ErrorCode RESIGNATION_APPLICANT_NOT_EXISTS = new ErrorCode(1_030_003_017, "ç³è¯·äººä¸åå¨ï¼è¯·éæ°éæ©ç³è¯·äºº"); |
| | | ErrorCode RESIGNATION_HANDOVER_STATUS_INVALID = new ErrorCode(1_030_003_018, "交æ¥ç¶æå¼ä¸åæ³ï¼ä»
æ¯æ 0æªå¼å§ã10è¿è¡ä¸ã20已宿"); |
| | | ErrorCode RESIGNATION_APPLICATION_APPLICANT_IMMUTABLE = new ErrorCode(1_030_003_019, "离èç³è¯·äººå建åä¸è½ä¿®æ¹"); |
| | | ErrorCode RESIGNATION_APPLICATION_OPERATOR_NOT_ALLOWED = new ErrorCode(1_030_003_020, "ä»
ç³è¯·äººæå代ç³è¯·å建人å¯ä»¥æä½è¯¥ç¦»èç³è¯·"); |
| | | |
| | | // ========== å çç³è¯· 1-030-008-000 ========== |
| | | ErrorCode OVERTIME_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_008_000, "å çç³è¯·ä¸åå¨"); |
| | |
| | | ErrorCode OVERTIME_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_008_004, "å çå®¡æ¹æµç¨ä¸åå¨ï¼è¯·å
å¨åååå
¬ä¸é
置并å叿µç¨"); |
| | | ErrorCode OVERTIME_APPLICATION_EMPLOYEE_NOT_EXISTS = new ErrorCode(1_030_008_005, "å½åè´¦å·æªå
³èå工档æ¡"); |
| | | ErrorCode OVERTIME_APPLICATION_TIME_INVALID = new ErrorCode(1_030_008_006, "å çæ¶æ®µææ£é¤æ¶é¿ä¸åæ³"); |
| | | ErrorCode OVERTIME_APPLICATION_SELECTED_EMPLOYEE_NOT_EXISTS = new ErrorCode(1_030_008_007, "æéç³è¯·äººçå工档æ¡ä¸åå¨ï¼è¯·å·æ°åå·¥å表åéæ°éæ©"); |
| | | ErrorCode OVERTIME_APPLICATION_PROXY_NOT_ALLOWED = new ErrorCode(1_030_008_008, "æ æä¸ºå
¶ä»å工代ç³è¯·å çï¼å½åè´¦å·æªææ hrm:overtime:manage æéï¼è¯·ç§»é¤ç³è¯·äººï¼æ¹ä¸ºæ¬äººï¼æè系管çåæäºè¯¥æé"); |
| | | ErrorCode OVERTIME_APPLICATION_OPERATOR_NOT_ALLOWED = new ErrorCode(1_030_008_009, "æ ææä½è¯¥å çç³è¯·ï¼å½åè´¦å·æ¢ä¸æ¯ç³è¯·äººï¼ä¹æªææ hrm:overtime:manage æé"); |
| | | |
| | | // ========== ç¨æ·æ°æ®äº¤æ¥ 1-030-007-000 ========== |
| | | ErrorCode USER_HANDOVER_NOT_EXISTS = new ErrorCode(1_030_007_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, "æçå¼ç¨çåå·¥ä¸åå¨"); |
| | | ErrorCode SCHEDULE_BATCH_DATE_RANGE_INVALID = new ErrorCode(1_030_010_003, "æ¹éæçæ¥æåºé´ä¸åæ³ï¼ç»ææ¥æ[%s]æ©äºå¼å§æ¥æ[%s]ï¼è¯·è°æ´åéè¯"); |
| | | ErrorCode SCHEDULE_BATCH_RANGE_TOO_LONG = new ErrorCode(1_030_010_004, "æ¹éæçæ¥æåºé´ä¸º[%s ~ %s]ï¼å
± %s 天ï¼è¶
è¿å次æå¤ 62 天çéå¶ï¼è¯·å段æç"); |
| | | ErrorCode SCHEDULE_BATCH_NO_EMPLOYEE = new ErrorCode(1_030_010_005, "é¨é¨[%s]ï¼å«ä¸çº§é¨é¨ï¼ä¸æ²¡æç¬¦åæ¡ä»¶çåå·¥ï¼å²ä½ï¼%sï¼åå·¥ç¶æï¼%sï¼ï¼æªçæä»»ä½æçï¼è¯·è°æ´ç鿡件åéè¯"); |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; |
| | | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationImportExcelVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationImportRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationPageReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationSaveReqVO; |
| | |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.List; |
| | |
| | | return success(leaveApplicationService.getApproveProcessDefinitionList()); |
| | | } |
| | | |
| | | @GetMapping("/import-template") |
| | | @Operation(summary = "ä¸è½½è¯·åç³è¯·å¯¼å
¥æ¨¡æ¿") |
| | | @PreAuthorize("@ss.hasPermission('hrm:leave:import')") |
| | | public void importTemplate(HttpServletResponse response) throws IOException { |
| | | HrmLeaveApplicationImportExcelVO example = new HrmLeaveApplicationImportExcelVO(); |
| | | example.setPhone("13800138000"); |
| | | example.setEmployeeName("å¼ ä¸"); |
| | | example.setLeaveTypeName("äºå"); |
| | | example.setStartTime("2026-11-10 09:00"); |
| | | example.setEndTime("2026-11-10 18:00"); |
| | | example.setDuration("1"); |
| | | example.setReason("个人äºå¡"); |
| | | example.setRemark("示ä¾è¡ï¼å¯¼å
¥å请å é¤"); |
| | | ExcelUtils.write(response, "请åç³è¯·å¯¼å
¥æ¨¡æ¿.xls", "请åç³è¯·", HrmLeaveApplicationImportExcelVO.class, |
| | | List.of(example)); |
| | | } |
| | | |
| | | @PostMapping("/import") |
| | | @Operation(summary = "导å
¥è¯·åç³è¯·ï¼ä»
çæè稿ï¼ä¸èªå¨æäº¤å®¡æ¹ï¼") |
| | | @PreAuthorize("@ss.hasPermission('hrm:leave:import')") |
| | | public CommonResult<HrmLeaveApplicationImportRespVO> importExcel(@RequestParam("file") MultipartFile file) |
| | | throws IOException { |
| | | List<HrmLeaveApplicationImportExcelVO> list = ExcelUtils.read(file, HrmLeaveApplicationImportExcelVO.class); |
| | | return success(leaveApplicationService.importLeaveApplications(list)); |
| | | } |
| | | |
| | | @GetMapping("/export-excel") |
| | | @Operation(summary = "导åºè¯·åç³è¯· Excel") |
| | | @PreAuthorize("@ss.hasPermission('hrm:leave:export')") |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.controller.admin.approval.vo; |
| | | |
| | | import cn.idev.excel.annotation.ExcelProperty; |
| | | import cn.iocoder.yudao.framework.excel.core.handler.ExcelRequired; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * 请åç³è¯· Excel 导å
¥ VO |
| | | * |
| | | * 说æï¼ææå段åæå符串读åï¼ç± Service éè¡è§£æå¹¶ç»åºç²¾ç¡®å¤±è´¥åå ï¼ |
| | | * é¿å
å个åå
æ ¼æ ¼å¼éè¯¯å¯¼è´æ´ä¸ªæä»¶è¯»åå¤±è´¥ãæ æ³å®ä½å°å
·ä½è¡ã |
| | | */ |
| | | @Data |
| | | public class HrmLeaveApplicationImportExcelVO { |
| | | |
| | | @ExcelProperty("ææºå·") |
| | | @ExcelRequired |
| | | private String phone; |
| | | |
| | | @ExcelProperty("åå·¥å§å") |
| | | private String employeeName; |
| | | |
| | | @ExcelProperty("请åç±»å") |
| | | @ExcelRequired |
| | | private String leaveTypeName; |
| | | |
| | | @ExcelProperty("å¼å§æ¶é´") |
| | | @ExcelRequired |
| | | private String startTime; |
| | | |
| | | @ExcelProperty("ç»ææ¶é´") |
| | | @ExcelRequired |
| | | private String endTime; |
| | | |
| | | @ExcelProperty("è¯·åæ¶é¿(天)") |
| | | @ExcelRequired |
| | | private String duration; |
| | | |
| | | @ExcelProperty("请ååå ") |
| | | @ExcelRequired |
| | | private String reason; |
| | | |
| | | @ExcelProperty("夿³¨") |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.controller.admin.approval.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | /** |
| | | * 请åç³è¯·å¯¼å
¥å¤±è´¥æç» |
| | | */ |
| | | @Schema(description = "管çåå° - 请åç³è¯·å¯¼å
¥å¤±è´¥æç»") |
| | | @Data |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class HrmLeaveApplicationImportFailureVO { |
| | | |
| | | @Schema(description = "Excel è¡å·ï¼å«è¡¨å¤´ï¼è¡¨å¤´ä¸ºç¬¬ 1 è¡ï¼", example = "2") |
| | | private Integer rowNum; |
| | | |
| | | @Schema(description = "ææºå·", example = "13800138000") |
| | | private String phone; |
| | | |
| | | @Schema(description = "åå·¥å§å", example = "å¼ ä¸") |
| | | private String employeeName; |
| | | |
| | | @Schema(description = "失败åå ", example = "ææºå· 13800138000 æªå¹é
å°å工档æ¡ï¼è¯·å
å¨å工档æ¡ä¸ç»´æ¤è¯¥ææºå·") |
| | | private String reason; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.controller.admin.approval.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 请åç³è¯·å¯¼å
¥ç»æ |
| | | */ |
| | | @Schema(description = "管çåå° - 请åç³è¯·å¯¼å
¥ç»æ") |
| | | @Data |
| | | @Builder |
| | | public class HrmLeaveApplicationImportRespVO { |
| | | |
| | | @Schema(description = "读åå°çæææ°æ®è¡æ°ï¼ä¸å«å
¨ç©ºè¡ï¼", example = "10") |
| | | private Integer totalCount; |
| | | |
| | | @Schema(description = "导å
¥æåçè¡æ°", example = "8") |
| | | private Integer successCount; |
| | | |
| | | @Schema(description = "导å
¥å¤±è´¥çè¡æ°", example = "2") |
| | | private Integer failureCount; |
| | | |
| | | @Schema(description = "失败æç»ï¼éè¡è¯´æå¤±è´¥åå ") |
| | | private List<HrmLeaveApplicationImportFailureVO> failures; |
| | | |
| | | } |
| | |
| | | @Data |
| | | public class HrmOvertimeApplicationSaveReqVO { |
| | | private Long id; |
| | | |
| | | @Schema(description = "ç³è¯·äººåå·¥IDï¼ä¸ºç©ºè¡¨ç¤ºæ¬äººã为å
¶ä»å工代ç³è¯·éææ hrm:overtime:manage æé", example = "1001") |
| | | private Long employeeId; |
| | | @NotNull(message = "å çæ¥æä¸è½ä¸ºç©º") |
| | | private LocalDate overtimeDate; |
| | | @NotNull(message = "å¼å§æ¶é´ä¸è½ä¸ºç©º") |
| | |
| | | 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.HrmEmployeeScheduleBatchAssignReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleBatchAssignRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleLeaveRespVO; |
| | | 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; |
| | |
| | | return success(employeeScheduleService.getScheduleList(reqVO)); |
| | | } |
| | | |
| | | @GetMapping("/leave-list") |
| | | @Operation(summary = "è·å¾æçé¡µè¯·åæ è®°å表ï¼å·²å®¡æ¹éè¿ï¼æå¤©å±å¼ï¼") |
| | | public CommonResult<List<HrmEmployeeScheduleLeaveRespVO>> getScheduleLeaveList( |
| | | @Valid HrmEmployeeScheduleListReqVO reqVO) { |
| | | return success(employeeScheduleService.getScheduleLeaveList(reqVO)); |
| | | } |
| | | |
| | | @PostMapping("/batch-assign") |
| | | @Operation(summary = "æ¹éæçï¼æé¨é¨å«ä¸çº§ + å²ä½ + åå·¥ç¶æï¼è·³è¿å·²å®¡æ¹è¯·åï¼") |
| | | @PreAuthorize("@ss.hasPermission('hrm:employee-schedule:save')") |
| | | public CommonResult<HrmEmployeeScheduleBatchAssignRespVO> batchAssign( |
| | | @Valid @RequestBody HrmEmployeeScheduleBatchAssignReqVO reqVO) { |
| | | return success(employeeScheduleService.batchAssign(reqVO)); |
| | | } |
| | | |
| | | @PostMapping("/save") |
| | | @Operation(summary = "æ¹éä¿ååå·¥æçï¼æ´æè¦çå¼ï¼") |
| | | @PreAuthorize("@ss.hasPermission('hrm:employee-schedule:save')") |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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 HrmEmployeeScheduleBatchAssignReqVO { |
| | | |
| | | @Schema(description = "é¨é¨IDï¼é»è®¤å
å«ä¸çº§é¨é¨ï¼", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") |
| | | @NotNull(message = "é¨é¨ä¸è½ä¸ºç©º") |
| | | private Long deptId; |
| | | |
| | | @Schema(description = "å²ä½IDï¼ä¸ºç©ºè¡¨ç¤ºä¸éå²ä½", example = "10") |
| | | private Long postId; |
| | | |
| | | @Schema(description = "åå·¥ç¶æï¼1-å¨è 2-è¯ç¨ 3-离èï¼ä¸ºç©ºè¡¨ç¤ºä¸éç¶æ", example = "1") |
| | | private Integer employeeStatus; |
| | | |
| | | @Schema(description = "å¼å§æ¥æï¼å«ï¼", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-11-01") |
| | | @NotNull(message = "å¼å§æ¥æä¸è½ä¸ºç©º") |
| | | private LocalDate startDate; |
| | | |
| | | @Schema(description = "ç»ææ¥æï¼å«ï¼", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-11-30") |
| | | @NotNull(message = "ç»ææ¥æä¸è½ä¸ºç©º") |
| | | private LocalDate endDate; |
| | | |
| | | @Schema(description = "çæ¬¡ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "çæ¬¡ä¸è½ä¸ºç©º") |
| | | private Long shiftId; |
| | | |
| | | @Schema(description = "夿³¨", example = "11 ææ¹éæç") |
| | | private String remark; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - æ¹éæçç»æ Response VO") |
| | | @Data |
| | | @Builder |
| | | public class HrmEmployeeScheduleBatchAssignRespVO { |
| | | |
| | | @Schema(description = "æ¬æ¬¡å½ä¸çåå·¥æ°", example = "12") |
| | | private Integer matchedEmployeeCount; |
| | | |
| | | @Schema(description = "åå
¥çæçæ°ï¼å·¥ä½æ¥ï¼", example = "240") |
| | | private Integer scheduledCount; |
| | | |
| | | @Schema(description = "å 已审æ¹è¯·åèè·³è¿ãæªæ¹å¨çåå·¥æ¥ææ°", example = "3") |
| | | private Integer skippedLeaveCount; |
| | | |
| | | @Schema(description = "伿¯æ¥/èåæ¥è¢«æ¸
空çæçæ°", example = "96") |
| | | private Integer clearedRestDayCount; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | import java.time.LocalDate; |
| | | |
| | | @Schema(description = "管çåå° - æçé¡µè¯·åæ è®° Response VO") |
| | | @Data |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class HrmEmployeeScheduleLeaveRespVO { |
| | | |
| | | @Schema(description = "åå·¥ID", example = "1001") |
| | | private Long employeeId; |
| | | |
| | | @Schema(description = "åå·¥å§å", example = "å¼ ä¸") |
| | | private String employeeName; |
| | | |
| | | @Schema(description = "请åè¦ççæ¥æ", example = "2026-11-10") |
| | | private LocalDate date; |
| | | |
| | | @Schema(description = "请åç±»å", example = "1") |
| | | private Integer leaveType; |
| | | |
| | | @Schema(description = "请åç±»ååç§°", example = "äºå") |
| | | private String leaveTypeName; |
| | | |
| | | } |
| | |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | |
| | | /** |
| | |
| | | .le(HrmLeaveApplicationDO::getEndTime, endTime)); |
| | | } |
| | | |
| | | /** |
| | | * æ¥è¯¢å¤ä¸ªå工卿宿¶é´èå´å
已审æ¹éè¿ç请åè®°å½ï¼ä¸æ¶é´èå´æäº¤éå³å½ä¸ï¼ |
| | | * |
| | | * @param userIds ç³»ç»ç¨æ·IDéå |
| | | * @param startTime åºé´å¼å§æ¶é´ |
| | | * @param endTime åºé´ç»ææ¶é´ |
| | | * @return 请åè®°å½å表 |
| | | */ |
| | | default List<HrmLeaveApplicationDO> selectApprovedListByUserIdsAndTimeRange(Collection<Long> userIds, |
| | | LocalDateTime startTime, |
| | | LocalDateTime endTime) { |
| | | return selectList(new LambdaQueryWrapperX<HrmLeaveApplicationDO>() |
| | | .in(HrmLeaveApplicationDO::getUserId, userIds) |
| | | .eq(HrmLeaveApplicationDO::getStatus, HrmAuditStatusEnum.APPROVE.getStatus()) |
| | | .le(HrmLeaveApplicationDO::getStartTime, endTime) |
| | | .ge(HrmLeaveApplicationDO::getEndTime, startTime) |
| | | .orderByAsc(HrmLeaveApplicationDO::getStartTime)); |
| | | } |
| | | |
| | | } |
| | |
| | | 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 org.apache.ibatis.annotations.Param; |
| | | import org.apache.ibatis.annotations.Select; |
| | | import org.apache.ibatis.annotations.Update; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.util.Collection; |
| | |
| | | .eq(HrmEmployeeScheduleDO::getDate, date)); |
| | | } |
| | | |
| | | /** |
| | | * æ¥è¯¢åå·¥æå¤©çæçï¼å
å«å·²è¢«é»è¾å é¤çè¡ã |
| | | * |
| | | * 表ä¸å¯ä¸é® uk_employee_date(employee_id, date) ä¸å« deleted åï¼ |
| | | * é»è¾å é¤åçæ§è¡ä»ç¶å ç¨å¯ä¸é®ï¼éæ°æçåå¿
é¡»å
æ¥å°å®å¹¶â夿´»âï¼å¦åæå
¥ä¼æé®å¯¼è´æ´æ¹åæ»ã |
| | | */ |
| | | @Select("SELECT * FROM hrm_employee_schedule WHERE employee_id = #{employeeId} AND date = #{date} LIMIT 1") |
| | | HrmEmployeeScheduleDO selectByEmployeeIdAndDateIncludeDeleted(@Param("employeeId") Long employeeId, |
| | | @Param("date") LocalDate date); |
| | | |
| | | /** |
| | | * 夿´»å·²é»è¾å é¤çæçè¡å¹¶è¦ççæ¬¡ä¿¡æ¯ã |
| | | */ |
| | | @Update("UPDATE hrm_employee_schedule SET deleted = 0, shift_id = #{shiftId}, remark = #{remark}, " |
| | | + "updater = #{updater}, update_time = NOW() WHERE id = #{id}") |
| | | int reviveById(@Param("id") Long id, @Param("shiftId") Long shiftId, |
| | | @Param("remark") String remark, @Param("updater") String updater); |
| | | |
| | | default List<HrmEmployeeScheduleDO> selectListByPeriod(LocalDate startDate, LocalDate endDate) { |
| | | return selectList(new LambdaQueryWrapperX<HrmEmployeeScheduleDO>() |
| | | .between(HrmEmployeeScheduleDO::getDate, startDate, endDate) |
| | |
| | | return selectList(HrmEmployeeDO::getDeptId, deptId); |
| | | } |
| | | |
| | | /** |
| | | * æé¨é¨éåï¼å«ä¸çº§é¨é¨ï¼æ¥è¯¢åå·¥ |
| | | */ |
| | | default List<HrmEmployeeDO> selectListByDeptIds(Collection<Long> deptIds) { |
| | | return selectList(new LambdaQueryWrapperX<HrmEmployeeDO>() |
| | | .in(HrmEmployeeDO::getDeptId, deptIds) |
| | | .orderByAsc(HrmEmployeeDO::getName)); |
| | | } |
| | | |
| | | default List<HrmEmployeeDO> selectByEmployeeStatus(Integer employeeStatus) { |
| | | return selectList(HrmEmployeeDO::getEmployeeStatus, employeeStatus); |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.hrm.service.approval; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationImportExcelVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationImportRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationPageReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationSaveReqVO; |
| | |
| | | Long createLeaveApplication(@Valid HrmLeaveApplicationSaveReqVO createReqVO); |
| | | |
| | | /** |
| | | * 导å
¥è¯·åç³è¯·ï¼æææºå·å¹é
å工档æ¡ï¼éè¡ç¬ç«å¤çï¼ä»
çæè稿ãä¸èªå¨æäº¤ï¼ |
| | | * |
| | | * @param list Excel æ°æ®è¡ |
| | | * @return 导å
¥ç»æï¼æåè¡æ°ä¸éè¡å¤±è´¥åå ï¼ |
| | | */ |
| | | HrmLeaveApplicationImportRespVO importLeaveApplications(List<HrmLeaveApplicationImportExcelVO> list); |
| | | |
| | | /** |
| | | * æ´æ°è¯·åç³è¯· |
| | | * |
| | | * @param updateReqVO æ´æ°ä¿¡æ¯ |
| | |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| | | import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; |
| | | import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; |
| | | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| | | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; |
| | | import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService; |
| | | import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationImportExcelVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationImportFailureVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationImportRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationPageReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmLeaveApplicationSaveReqVO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmLeaveApplicationMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper; |
| | | import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO; |
| | | import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum; |
| | | import cn.iocoder.yudao.module.hrm.enums.HrmLeaveTypeEnum; |
| | |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDateTime; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.format.DateTimeParseException; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.Set; |
| | | import java.util.regex.Pattern; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | |
| | | @Slf4j |
| | | public class HrmLeaveApplicationServiceImpl implements HrmLeaveApplicationService { |
| | | |
| | | private static final DateTimeFormatter LEAVE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); |
| | | |
| | | @Resource |
| | | private HrmLeaveApplicationMapper leaveApplicationMapper; |
| | | @Resource |
| | | private HrmEmployeeMapper employeeMapper; |
| | | @Resource |
| | | private HrmNoRedisDAO noRedisDAO; |
| | | @Resource |
| | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createLeaveApplication(HrmLeaveApplicationSaveReqVO createReqVO) { |
| | | // 1. çæç³è¯·åå· |
| | | String no = generateUniqueNo(); |
| | | // 2. 设置ç³è¯·äººä¿¡æ¯ |
| | | // 1. 确认ç³è¯·äººå·²å
³èåå·¥æ¡£æ¡ |
| | | Long userId = createReqVO.getUserId(); |
| | | if (userId == null) { |
| | | userId = getCurrentUserId(); |
| | | } |
| | | AdminUserRespDTO user = adminUserApi.getUser(userId); |
| | | if (user == null) { |
| | | userId = getCurrentUserId(); |
| | | throw exception(LEAVE_APPLICATION_APPLICANT_NOT_EXISTS); |
| | | } |
| | | HrmEmployeeDO employee = employeeMapper.selectByUserId(userId); |
| | | if (employee == null) { |
| | | throw exception(Objects.equals(userId, getCurrentUserId()) |
| | | ? LEAVE_APPLICATION_EMPLOYEE_NOT_EXISTS : LEAVE_APPLICATION_SELECTED_EMPLOYEE_NOT_EXISTS); |
| | | } |
| | | if (employee.getDeptId() == null) { |
| | | throw exception(LEAVE_APPLICATION_EMPLOYEE_DEPT_NOT_FOUND); |
| | | } |
| | | if (createReqVO.getDeptId() != null && !Objects.equals(createReqVO.getDeptId(), employee.getDeptId())) { |
| | | throw exception(LEAVE_APPLICATION_DEPT_MISMATCH); |
| | | } |
| | | // 2. çæç³è¯·åå· |
| | | String no = generateUniqueNo(); |
| | | // 3. æå
¥ |
| | | HrmLeaveApplicationDO application = BeanUtils.toBean(createReqVO, HrmLeaveApplicationDO.class); |
| | | application.setNo(no); |
| | | application.setUserId(userId); |
| | | application.setDeptId(createReqVO.getDeptId() != null ? createReqVO.getDeptId() : user.getDeptId()); |
| | | application.setDeptId(employee.getDeptId()); |
| | | application.setStatus(HrmAuditStatusEnum.DRAFT.getStatus()); |
| | | leaveApplicationMapper.insert(application); |
| | | // ç»å®éä»¶ |
| | |
| | | application.getId(), createReqVO.getBlobIds()); |
| | | return application.getId(); |
| | | } |
| | | |
| | | @Override |
| | | public HrmLeaveApplicationImportRespVO importLeaveApplications(List<HrmLeaveApplicationImportExcelVO> list) { |
| | | if (CollUtil.isEmpty(list)) { |
| | | throw exception(LEAVE_APPLICATION_IMPORT_FILE_EMPTY); |
| | | } |
| | | int totalCount = 0; |
| | | int successCount = 0; |
| | | List<HrmLeaveApplicationImportFailureVO> failures = new ArrayList<>(); |
| | | for (int index = 0; index < list.size(); index++) { |
| | | HrmLeaveApplicationImportExcelVO row = list.get(index); |
| | | // Excel 第 1 è¡æ¯è¡¨å¤´ï¼ç¬¬ index æ¡æ°æ®å¯¹åºç¬¬ index + 2 è¡ |
| | | int rowNum = index + 2; |
| | | if (isBlankRow(row)) { |
| | | continue; |
| | | } |
| | | totalCount++; |
| | | try { |
| | | leaveApplicationMapper.insert(buildImportedDraft(row)); |
| | | successCount++; |
| | | } catch (LeaveImportRowException e) { |
| | | failures.add(new HrmLeaveApplicationImportFailureVO(rowNum, trimToNull(row.getPhone()), |
| | | trimToNull(row.getEmployeeName()), e.getMessage())); |
| | | } catch (Exception e) { |
| | | log.error("[importLeaveApplications][第 {} è¡å¯¼å
¥å¤±è´¥]", rowNum, e); |
| | | failures.add(new HrmLeaveApplicationImportFailureVO(rowNum, trimToNull(row.getPhone()), |
| | | trimToNull(row.getEmployeeName()), |
| | | "åå
¥æ°æ®åºå¤±è´¥ï¼" + StrUtil.blankToDefault(e.getMessage(), e.getClass().getSimpleName()) |
| | | + "ï¼è¯·æ£æ¥è¯·ååå /夿³¨é¿åº¦å鿰坼å
¥è¯¥è¡ï¼ä»å¤±è´¥è¯·è系管çå")); |
| | | } |
| | | } |
| | | if (totalCount == 0) { |
| | | throw exception(LEAVE_APPLICATION_IMPORT_FILE_EMPTY); |
| | | } |
| | | return HrmLeaveApplicationImportRespVO.builder() |
| | | .totalCount(totalCount) |
| | | .successCount(successCount) |
| | | .failureCount(failures.size()) |
| | | .failures(failures) |
| | | .build(); |
| | | } |
| | | |
| | | private boolean isBlankRow(HrmLeaveApplicationImportExcelVO row) { |
| | | return row == null || (StrUtil.isBlank(row.getPhone()) && StrUtil.isBlank(row.getEmployeeName()) |
| | | && StrUtil.isBlank(row.getLeaveTypeName()) && StrUtil.isBlank(row.getStartTime()) |
| | | && StrUtil.isBlank(row.getEndTime()) && StrUtil.isBlank(row.getDuration()) |
| | | && StrUtil.isBlank(row.getReason())); |
| | | } |
| | | |
| | | /** |
| | | * å°ä¸è¡ Excel æ°æ®è½¬æ¢ä¸ºè¯·åç³è¯·è稿ï¼ä»»ä¸æ ¡éªä¸éè¿æ¶æåºå¸¦ç²¾ç¡®åå ç {@link LeaveImportRowException} |
| | | */ |
| | | private HrmLeaveApplicationDO buildImportedDraft(HrmLeaveApplicationImportExcelVO row) { |
| | | // 1. ææºå·å¹é
åå·¥æ¡£æ¡ |
| | | String phone = normalizeCell(row.getPhone()); |
| | | if (StrUtil.isBlank(phone)) { |
| | | throw new LeaveImportRowException("ææºå·ä¸ºç©ºï¼è¯·å¡«åå工档æ¡ä¸ç»è®°ç 11 使æºå·"); |
| | | } |
| | | if (!PHONE_PATTERN.matcher(phone).matches()) { |
| | | throw new LeaveImportRowException("ææºå·[" + phone + "]䏿¯ææç 11 使æºå·ï¼è¯·æ£æ¥æ¯å¦å¤å¡«äºç©ºæ ¼ãç¬¦å·æå°æ°ä½"); |
| | | } |
| | | HrmEmployeeDO employee = employeeMapper.selectByPhone(phone); |
| | | if (employee == null) { |
| | | throw new LeaveImportRowException("ææºå·[" + phone + "]æªå¹é
å°å工档æ¡ï¼è¯·å
å¨ãå工档æ¡ãä¸ç»´æ¤è¯¥ææºå·å鿰坼å
¥"); |
| | | } |
| | | String inputName = trimToNull(row.getEmployeeName()); |
| | | if (inputName != null && !Objects.equals(inputName, StrUtil.trim(employee.getName()))) { |
| | | throw new LeaveImportRowException("ææºå·[" + phone + "]对åºå工为[" + employee.getName() |
| | | + "]ï¼ä¸å¡«åçåå·¥å§å[" + inputName + "]ä¸ä¸è´ï¼è¯·æ ¸å¯¹ææºå·å鿰坼å
¥"); |
| | | } |
| | | if (employee.getUserId() == null) { |
| | | throw new LeaveImportRowException("å工档æ¡[" + employee.getName() + "]ï¼ææºå· " + phone |
| | | + "ï¼æªå
³èç³»ç»ç»å½è´¦å·ï¼è¯·å
å¨å工档æ¡ä¸ç»å®è´¦å·å鿰坼å
¥"); |
| | | } |
| | | if (employee.getDeptId() == null) { |
| | | throw new LeaveImportRowException("å工档æ¡[" + employee.getName() |
| | | + "]æªè®¾ç½®é¨é¨ï¼è¯·å
å¨å工档æ¡ä¸è¡¥å
¨é¨é¨å鿰坼å
¥"); |
| | | } |
| | | // 2. 请åç±»å |
| | | Integer leaveType = parseLeaveType(row.getLeaveTypeName()); |
| | | // 3. èµ·æ¢æ¶é´ä¸æ¶é¿ |
| | | LocalDateTime startTime = parseDateTime("å¼å§æ¶é´", row.getStartTime()); |
| | | LocalDateTime endTime = parseDateTime("ç»ææ¶é´", row.getEndTime()); |
| | | if (!endTime.isAfter(startTime)) { |
| | | throw new LeaveImportRowException("ç»ææ¶é´[" + formatDateTime(endTime) + "]å¿
é¡»æäºå¼å§æ¶é´[" |
| | | + formatDateTime(startTime) + "]ï¼è¯·è°æ´å鿰坼å
¥"); |
| | | } |
| | | BigDecimal duration = parseDuration(row.getDuration(), startTime, endTime); |
| | | // 4. 请ååå |
| | | String reason = trimToNull(row.getReason()); |
| | | if (reason == null) { |
| | | throw new LeaveImportRowException("请ååå 为空ï¼è¯·å¡«å请åäºç±ï¼å°å±ç¤ºå¨å®¡æ¹åä¸ï¼"); |
| | | } |
| | | if (reason.length() > 500) { |
| | | throw new LeaveImportRowException("请ååå é¿åº¦ä¸º " + reason.length() |
| | | + " 个å符ï¼è¶
è¿ 500 åä¸éï¼è¯·ç²¾ç®å鿰坼å
¥"); |
| | | } |
| | | String remark = trimToNull(row.getRemark()); |
| | | if (remark != null && remark.length() > 500) { |
| | | throw new LeaveImportRowException("夿³¨é¿åº¦ä¸º " + remark.length() |
| | | + " 个å符ï¼è¶
è¿ 500 åä¸éï¼è¯·ç²¾ç®å鿰坼å
¥"); |
| | | } |
| | | // 5. ç»è£
è稿ï¼ä¸æäº¤å®¡æ¹ï¼ |
| | | HrmLeaveApplicationDO application = new HrmLeaveApplicationDO(); |
| | | application.setNo(generateUniqueNo()); |
| | | application.setUserId(employee.getUserId()); |
| | | application.setDeptId(employee.getDeptId()); |
| | | application.setLeaveType(leaveType); |
| | | application.setStartTime(startTime); |
| | | application.setEndTime(endTime); |
| | | application.setDuration(duration); |
| | | application.setReason(reason); |
| | | application.setRemark(remark); |
| | | application.setStatus(HrmAuditStatusEnum.DRAFT.getStatus()); |
| | | return application; |
| | | } |
| | | |
| | | private Integer parseLeaveType(String raw) { |
| | | String value = normalizeCell(raw); |
| | | if (StrUtil.isBlank(value)) { |
| | | throw new LeaveImportRowException("请åç±»å为空ï¼è¯·å¡«åï¼äºåãç
åãå¹´åãå©åã产åã丧åï¼ææ°å 1-6ï¼"); |
| | | } |
| | | for (HrmLeaveTypeEnum type : HrmLeaveTypeEnum.values()) { |
| | | if (StrUtil.equals(value, type.getName()) || StrUtil.equals(value, String.valueOf(type.getType()))) { |
| | | return type.getType(); |
| | | } |
| | | } |
| | | throw new LeaveImportRowException("请åç±»å[" + value |
| | | + "]ä¸å¨å¯éèå´å
ï¼è¯·å¡«åï¼äºåãç
åãå¹´åãå©åã产åã丧åï¼ææ°å 1-6ï¼"); |
| | | } |
| | | |
| | | private LocalDateTime parseDateTime(String field, String raw) { |
| | | String value = normalizeCell(raw); |
| | | if (StrUtil.isBlank(value)) { |
| | | throw new LeaveImportRowException(field + "为空ï¼è¯·å¡«åæ ¼å¼å¦ 2026-11-10 09:00 çæ¶é´"); |
| | | } |
| | | // å
¼å®¹ Excel ææ¥æåææ°ååºåå·ï¼å¦ 46000.375ï¼ |
| | | if (EXCEL_SERIAL_PATTERN.matcher(value).matches()) { |
| | | double serial = Double.parseDouble(value); |
| | | if (serial > 20000 && serial < 80000) { |
| | | return LocalDateTime.ofInstant( |
| | | org.apache.poi.ss.usermodel.DateUtil.getJavaDate(serial).toInstant(), ZoneId.systemDefault()); |
| | | } |
| | | } |
| | | for (DateTimeFormatter formatter : DATE_TIME_FORMATTERS) { |
| | | try { |
| | | return LocalDateTime.parse(value, formatter); |
| | | } catch (DateTimeParseException ignored) { |
| | | // å°è¯ä¸ä¸ç§æ ¼å¼ |
| | | } |
| | | } |
| | | // åªå¡«æ¥ææ¶æå½å¤© 00:00 å¤ç |
| | | for (DateTimeFormatter formatter : DATE_ONLY_FORMATTERS) { |
| | | try { |
| | | return java.time.LocalDate.parse(value, formatter).atStartOfDay(); |
| | | } catch (DateTimeParseException ignored) { |
| | | // å°è¯ä¸ä¸ç§æ ¼å¼ |
| | | } |
| | | } |
| | | throw new LeaveImportRowException(field + "[" + value |
| | | + "]æ ¼å¼ä¸æ£ç¡®ï¼è¯·ä½¿ç¨ yyyy-MM-dd HH:mm æ yyyy-MM-dd HH:mm:ssï¼ä¾å¦ 2026-11-10 09:00ï¼"); |
| | | } |
| | | |
| | | private BigDecimal parseDuration(String raw, LocalDateTime startTime, LocalDateTime endTime) { |
| | | String value = normalizeCell(raw); |
| | | if (StrUtil.isBlank(value)) { |
| | | throw new LeaveImportRowException("è¯·åæ¶é¿ä¸ºç©ºï¼è¯·å¡«åå¤§äº 0 ç天æ°ï¼æ¯æ 0.5 天ï¼"); |
| | | } |
| | | value = StrUtil.removeSuffix(value, "天").trim(); |
| | | BigDecimal duration; |
| | | try { |
| | | duration = new BigDecimal(value); |
| | | } catch (NumberFormatException e) { |
| | | throw new LeaveImportRowException("è¯·åæ¶é¿[" + value + "]䏿¯æææ°åï¼è¯·å¡«åå¤§äº 0 ç天æ°ï¼ä¾å¦ 0.5ã1ã2.5ï¼"); |
| | | } |
| | | if (duration.compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new LeaveImportRowException("è¯·åæ¶é¿[" + duration.stripTrailingZeros().toPlainString() |
| | | + "]å¿
é¡»å¤§äº 0 天ï¼è¯·æå®é
请å天æ°å¡«å"); |
| | | } |
| | | // ä»
åæç¤ºï¼ä¸é»æï¼æ¶é¿ä¸èµ·æ¢æ¶é´ææ¾ä¸ç¬¦æ¶ç»åºå¯æ ¸å¯¹ä¿¡æ¯ |
| | | long minutes = java.time.Duration.between(startTime, endTime).toMinutes(); |
| | | BigDecimal expectedMax = BigDecimal.valueOf(minutes) |
| | | .divide(BigDecimal.valueOf(60 * 24), 2, RoundingMode.HALF_UP); |
| | | if (duration.compareTo(expectedMax) > 0) { |
| | | throw new LeaveImportRowException("è¯·åæ¶é¿[" + duration.stripTrailingZeros().toPlainString() |
| | | + " 天]è¶
è¿èµ·æ¢æ¶é´è·¨åº¦[" + expectedMax.stripTrailingZeros().toPlainString() |
| | | + " 天]ï¼è¯·æ ¸å¯¹å¼å§/ç»ææ¶é´ä¸æ¶é¿å鿰坼å
¥"); |
| | | } |
| | | return duration; |
| | | } |
| | | |
| | | private static String normalizeCell(String raw) { |
| | | if (StrUtil.isBlank(raw)) { |
| | | return null; |
| | | } |
| | | String value = raw.trim(); |
| | | // Excel æ°å¼åå
æ ¼å¯è½è¢«è¯»æç§å¦è®¡æ°æ³æå¸¦ .0 çå°æ°ï¼ç»ä¸è¿å为æ®éæ°å |
| | | if (SCIENTIFIC_PATTERN.matcher(value).matches() || TRAILING_ZERO_PATTERN.matcher(value).matches()) { |
| | | try { |
| | | value = new BigDecimal(value).stripTrailingZeros().toPlainString(); |
| | | } catch (NumberFormatException ignored) { |
| | | // ä¿æåå¼ |
| | | } |
| | | } |
| | | return value; |
| | | } |
| | | |
| | | private static String trimToNull(String value) { |
| | | return StrUtil.isBlank(value) ? null : value.trim(); |
| | | } |
| | | |
| | | private static String formatDateTime(LocalDateTime value) { |
| | | return value.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); |
| | | } |
| | | |
| | | /** åè¡å¯¼å
¥æ ¡éªå¼å¸¸ï¼message ç´æ¥ä½ä¸ºè¯¥è¡ç失败åå è¿åç»ç¨æ· */ |
| | | private static class LeaveImportRowException extends RuntimeException { |
| | | LeaveImportRowException(String message) { |
| | | super(message); |
| | | } |
| | | } |
| | | |
| | | private static final Pattern PHONE_PATTERN = Pattern.compile("^1\\d{10}$"); |
| | | private static final Pattern EXCEL_SERIAL_PATTERN = Pattern.compile("^\\d{5}(\\.\\d+)?$"); |
| | | private static final Pattern SCIENTIFIC_PATTERN = Pattern.compile("^-?\\d+(\\.\\d+)?[eE][+-]?\\d+$"); |
| | | private static final Pattern TRAILING_ZERO_PATTERN = Pattern.compile("^-?\\d+\\.0+$"); |
| | | private static final DateTimeFormatter[] DATE_TIME_FORMATTERS = new DateTimeFormatter[]{ |
| | | DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"), |
| | | DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"), |
| | | DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"), |
| | | DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm") |
| | | }; |
| | | private static final DateTimeFormatter[] DATE_ONLY_FORMATTERS = new DateTimeFormatter[]{ |
| | | DateTimeFormatter.ofPattern("yyyy-MM-dd"), |
| | | DateTimeFormatter.ofPattern("yyyy/MM/dd") |
| | | }; |
| | | |
| | | private Long getCurrentUserId() { |
| | | return SecurityFrameworkUtils.getLoginUserId(); |
| | |
| | | public void updateLeaveApplication(HrmLeaveApplicationSaveReqVO updateReqVO) { |
| | | // æ ¡éªåå¨ |
| | | HrmLeaveApplicationDO application = validateLeaveApplicationExists(updateReqVO.getId()); |
| | | // æ ¡éªç¶æä¸ºè稿æå®¡æ¹ä¸ |
| | | validateOperator(application, getCurrentUserId()); |
| | | // 驳ååå
许修æ¹å¹¶éæ°æäº¤å®¡æ¹ã |
| | | if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus()) |
| | | && !Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) { |
| | | throw exception(LEAVE_APPLICATION_UPDATE_FAIL_NOT_DRAFT); |
| | | && !Objects.equals(application.getStatus(), HrmAuditStatusEnum.REJECT.getStatus())) { |
| | | throw exception(LEAVE_APPLICATION_UPDATE_FAIL_NOT_EDITABLE); |
| | | } |
| | | if (updateReqVO.getUserId() != null && !Objects.equals(updateReqVO.getUserId(), application.getUserId())) { |
| | | throw exception(LEAVE_APPLICATION_APPLICANT_IMMUTABLE); |
| | | } |
| | | validateApplicantDept(application.getUserId(), |
| | | updateReqVO.getDeptId() != null ? updateReqVO.getDeptId() : application.getDeptId()); |
| | | // æ´æ° |
| | | HrmLeaveApplicationDO updateObj = BeanUtils.toBean(updateReqVO, HrmLeaveApplicationDO.class); |
| | | updateObj.setUserId(null); |
| | | leaveApplicationMapper.updateById(updateObj); |
| | | storageAttachmentApi.updateAttachments("file", "hrm_leave_application", updateReqVO.getId(), updateReqVO.getBlobIds()); |
| | | } |
| | |
| | | public void deleteLeaveApplication(Long id) { |
| | | // æ ¡éªåå¨ |
| | | HrmLeaveApplicationDO application = validateLeaveApplicationExists(id); |
| | | validateOperator(application, getCurrentUserId()); |
| | | // æ ¡éªç¶æä¸ºè稿 |
| | | if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) { |
| | | throw exception(LEAVE_APPLICATION_UPDATE_FAIL_NOT_DRAFT); |
| | |
| | | public void submitLeaveApplication(Long id, String processDefinitionKey, Long userId) { |
| | | // 1. æ ¡éªè¯·åç³è¯·æ¯å¦åå¨ |
| | | HrmLeaveApplicationDO application = validateLeaveApplicationExists(id); |
| | | if (ObjUtil.notEqual(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) { |
| | | validateOperator(application, userId); |
| | | if (ObjUtil.notEqual(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus()) |
| | | && ObjUtil.notEqual(application.getStatus(), HrmAuditStatusEnum.REJECT.getStatus())) { |
| | | throw exception(LEAVE_APPLICATION_SUBMIT_FAIL_NOT_DRAFT); |
| | | } |
| | | validateApplicantDept(application.getUserId(), application.getDeptId()); |
| | | |
| | | // 2. æ ¡éªåç±»åæµç¨å®ä¹æ¯å¦åå¨ |
| | | // å
¨å±å
审æ¹ï¼ä¸å¡æ ¡éªéè¿åç´æ¥æ§è¡å®¡æ¹éè¿é»è¾ï¼æ éæµç¨æ¨¡åã |
| | |
| | | validateApproveCategoryAndProcessDefinition(); |
| | | |
| | | // 3. æ ¡éªä¼ å
¥çæµç¨å®ä¹ Key æ¯å¦å±äºè¯¥åç±»ä¸å¤äºæ¿æ´»ç¶æ |
| | | if (processDefinitionKey == null || processDefinitionKey.isBlank()) { |
| | | throw exception(LEAVE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); |
| | | } |
| | | ProcessDefinition processDefinition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey); |
| | | if (processDefinition == null) { |
| | | if (processDefinition == null || bpmProcessDefinitionService |
| | | .getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE).stream() |
| | | .noneMatch(info -> Objects.equals(info.getProcessDefinitionId(), processDefinition.getId()))) { |
| | | throw exception(LEAVE_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); |
| | | } |
| | | |
| | | // 4. åå»ºå®¡æ¹æµç¨å®ä¾ |
| | | String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO() |
| | | .setProcessDefinitionKey(processDefinitionKey).setBusinessKey(String.valueOf(id))); |
| | | Map<String, Object> variables = new HashMap<>(); |
| | | variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_BUSINESS_NO, application.getNo()); |
| | | AdminUserRespDTO applicant = adminUserApi.getUser(application.getUserId()); |
| | | variables.put("Fl55mt725q5yabc", applicant != null ? applicant.getNickname() : String.valueOf(application.getUserId())); |
| | | variables.put("F5eqmt725u3naec", LEAVE_TIME_FORMAT.format(application.getStartTime()) |
| | | + " è³ " + LEAVE_TIME_FORMAT.format(application.getEndTime())); |
| | | variables.put("F8wtmt726c3zahc", application.getDuration().toPlainString()); |
| | | String processInstanceId = bpmProcessInstanceApi.createProcessInstance(application.getUserId(), new BpmProcessInstanceCreateReqDTO() |
| | | .setProcessDefinitionKey(processDefinitionKey).setBusinessKey(String.valueOf(id)) |
| | | .setVariables(variables)); |
| | | |
| | | // 5. æ´æ°è¯·åç³è¯·å·¥ä½æµç¼å· |
| | | leaveApplicationMapper.updateById(HrmLeaveApplicationDO.builder() |
| | | .id(id) |
| | | .processInstanceId(processInstanceId) |
| | | .status(HrmAuditStatusEnum.PROCESS.getStatus()) |
| | | .build()); |
| | | leaveApplicationMapper.update(null, new LambdaUpdateWrapper<HrmLeaveApplicationDO>() |
| | | .eq(HrmLeaveApplicationDO::getId, id) |
| | | .set(HrmLeaveApplicationDO::getProcessInstanceId, processInstanceId) |
| | | .set(HrmLeaveApplicationDO::getStatus, HrmAuditStatusEnum.PROCESS.getStatus()) |
| | | .set(HrmLeaveApplicationDO::getApproveTime, null)); |
| | | } |
| | | |
| | | private void validateOperator(HrmLeaveApplicationDO application, Long operatorId) { |
| | | if (!Objects.equals(application.getUserId(), operatorId) |
| | | && !Objects.equals(application.getCreator(), String.valueOf(operatorId))) { |
| | | throw exception(LEAVE_APPLICATION_OPERATOR_NOT_ALLOWED); |
| | | } |
| | | } |
| | | |
| | | private void validateApplicantDept(Long userId, Long deptId) { |
| | | HrmEmployeeDO employee = employeeMapper.selectByUserId(userId); |
| | | if (employee == null || employee.getDeptId() == null) { |
| | | throw exception(LEAVE_APPLICATION_EMPLOYEE_DEPT_NOT_FOUND); |
| | | } |
| | | if (!Objects.equals(employee.getDeptId(), deptId)) { |
| | | throw exception(LEAVE_APPLICATION_DEPT_MISMATCH); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | throw exception(LEAVE_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS); |
| | | } |
| | | // æ´æ°ç¶æ |
| | | leaveApplicationMapper.updateById(HrmLeaveApplicationDO.builder() |
| | | .id(id) |
| | | .status(status) |
| | | .approveTime(java.time.LocalDateTime.now()) |
| | | .build()); |
| | | leaveApplicationMapper.update(null, new LambdaUpdateWrapper<HrmLeaveApplicationDO>() |
| | | .eq(HrmLeaveApplicationDO::getId, id) |
| | | .set(HrmLeaveApplicationDO::getStatus, status) |
| | | .set(HrmLeaveApplicationDO::getApproveTime, |
| | | Objects.equals(status, HrmAuditStatusEnum.APPROVE.getStatus()) |
| | | ? java.time.LocalDateTime.now() : null)); |
| | | } |
| | | |
| | | @Override |
| | |
| | | return leaveApplicationMapper.selectByProcessInstanceId(processInstanceId); |
| | | } |
| | | |
| | | } |
| | | } |
| | |
| | | import cn.hutool.core.util.NumberUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.hutool.json.JSONUtil; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService; |
| | | import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; |
| | | import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; |
| | | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; |
| | |
| | | @Resource private BpmTaskService bpmTaskService; |
| | | @Resource private BpmCategoryService bpmCategoryService; |
| | | @Resource private BpmProcessDefinitionService bpmProcessDefinitionService; |
| | | @Resource private SecurityFrameworkService securityFrameworkService; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long create(HrmOvertimeApplicationSaveReqVO reqVO, Long loginUserId) { |
| | | HrmEmployeeDO employee = requireEmployee(loginUserId); |
| | | HrmEmployeeDO employee = resolveApplicant(reqVO.getEmployeeId(), loginUserId); |
| | | HrmOvertimeApplicationDO application = BeanUtils.toBean(reqVO, HrmOvertimeApplicationDO.class); |
| | | applyApplicantAndCalculatedTime(application, employee); |
| | | application.setNo(noRedisDAO.generateOvertimeApplicationNo()); |
| | |
| | | public void update(HrmOvertimeApplicationSaveReqVO reqVO, Long loginUserId) { |
| | | HrmOvertimeApplicationDO existing = requireOwnedDraft(reqVO.getId(), loginUserId); |
| | | HrmOvertimeApplicationDO update = BeanUtils.toBean(reqVO, HrmOvertimeApplicationDO.class); |
| | | applyApplicantAndCalculatedTime(update, requireEmployee(loginUserId)); |
| | | // æªéæ°éæ©ç³è¯·äººæ¶ï¼æ²¿ç¨åç³è¯·äººï¼é¿å
代ç³è¯·å被æ¹åç»å½äºº |
| | | Long requestedEmployeeId = reqVO.getEmployeeId() != null ? reqVO.getEmployeeId() : existing.getEmployeeId(); |
| | | applyApplicantAndCalculatedTime(update, resolveApplicant(requestedEmployeeId, loginUserId)); |
| | | update.setId(existing.getId()); |
| | | mapper.updateById(update); |
| | | } |
| | |
| | | HrmOvertimeApplicationDO application = require(id); |
| | | boolean deletable = Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus()) |
| | | || Objects.equals(application.getStatus(), HrmAuditStatusEnum.CANCEL.getStatus()); |
| | | if (!Objects.equals(application.getUserId(), loginUserId) || !deletable) { |
| | | if (!Objects.equals(application.getUserId(), loginUserId) && !canManageOvertime()) { |
| | | throw exception(OVERTIME_APPLICATION_OPERATOR_NOT_ALLOWED); |
| | | } |
| | | if (!deletable) { |
| | | throw exception(OVERTIME_APPLICATION_UPDATE_FAIL_NOT_DRAFT); |
| | | } |
| | | mapper.deleteById(id); |
| | |
| | | if (definition == null || !BPM_PROCESS_DEFINITION_KEY.equals(definition.getKey())) { |
| | | throw exception(OVERTIME_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS); |
| | | } |
| | | String processInstanceId = bpmProcessInstanceApi.createProcessInstance(loginUserId, |
| | | // 以ç³è¯·äººå工对åºçç³»ç»è´¦å·åèµ·æµç¨ï¼ä¿è¯ä»£ç³è¯·åç审æ¹äººè½çå°æ£ç¡®çç³è¯·äºº |
| | | String processInstanceId = bpmProcessInstanceApi.createProcessInstance(application.getUserId(), |
| | | new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(processDefinitionKey) |
| | | .setBusinessKey(String.valueOf(id))); |
| | | mapper.updateById(HrmOvertimeApplicationDO.builder().id(id).processInstanceId(processInstanceId) |
| | | .status(HrmAuditStatusEnum.PROCESS.getStatus()).build()); |
| | | .setBusinessKey(String.valueOf(id)) |
| | | .setVariables(Map.of("businessNo", application.getNo()))); |
| | | mapper.update(null, new LambdaUpdateWrapper<HrmOvertimeApplicationDO>() |
| | | .eq(HrmOvertimeApplicationDO::getId, id) |
| | | .set(HrmOvertimeApplicationDO::getProcessInstanceId, processInstanceId) |
| | | .set(HrmOvertimeApplicationDO::getStatus, HrmAuditStatusEnum.PROCESS.getStatus()) |
| | | .set(HrmOvertimeApplicationDO::getApproveTime, null)); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void cancel(Long id, Long loginUserId) { |
| | | HrmOvertimeApplicationDO application = require(id); |
| | | if (!Objects.equals(application.getUserId(), loginUserId) |
| | | || !Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) { |
| | | if (!Objects.equals(application.getUserId(), loginUserId) && !canManageOvertime()) { |
| | | throw exception(OVERTIME_APPLICATION_OPERATOR_NOT_ALLOWED); |
| | | } |
| | | if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) { |
| | | throw exception(OVERTIME_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS); |
| | | } |
| | | bpmProcessInstanceService.cancelProcessInstanceByStartUser(loginUserId, |
| | | // æµç¨ç±ç³è¯·äººåå·¥åèµ·ï¼æ¤éä¹å¿
须以该å工账å·ä½ä¸ºå起人 |
| | | bpmProcessInstanceService.cancelProcessInstanceByStartUser(application.getUserId(), |
| | | new BpmProcessInstanceCancelReqVO().setId(application.getProcessInstanceId()).setReason("ç³è¯·äººæ¤éå çç³è¯·")); |
| | | } |
| | | |
| | |
| | | if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) { |
| | | throw exception(OVERTIME_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS); |
| | | } |
| | | mapper.updateById(HrmOvertimeApplicationDO.builder().id(id).status(status) |
| | | .approveTime(LocalDateTime.now()).build()); |
| | | mapper.update(null, new LambdaUpdateWrapper<HrmOvertimeApplicationDO>() |
| | | .eq(HrmOvertimeApplicationDO::getId, id) |
| | | .set(HrmOvertimeApplicationDO::getStatus, status) |
| | | .set(HrmOvertimeApplicationDO::getApproveTime, |
| | | Objects.equals(status, HrmAuditStatusEnum.APPROVE.getStatus()) |
| | | ? LocalDateTime.now() : null)); |
| | | } |
| | | |
| | | private void validateProcessDefinitions() { |
| | |
| | | |
| | | private HrmOvertimeApplicationDO requireOwnedDraft(Long id, Long userId) { |
| | | HrmOvertimeApplicationDO value = require(id); |
| | | if (!Objects.equals(value.getUserId(), userId) |
| | | || !Objects.equals(value.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) { |
| | | // ææ hrm:overtime:manage æé坿ä½å
¨é¨äººçååï¼å¦ååªè½æä½æ¬äººåå |
| | | if (!Objects.equals(value.getUserId(), userId) && !canManageOvertime()) { |
| | | throw exception(OVERTIME_APPLICATION_OPERATOR_NOT_ALLOWED); |
| | | } |
| | | if (!Objects.equals(value.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus()) |
| | | && !Objects.equals(value.getStatus(), HrmAuditStatusEnum.REJECT.getStatus())) { |
| | | throw exception(OVERTIME_APPLICATION_UPDATE_FAIL_NOT_DRAFT); |
| | | } |
| | | return value; |
| | | } |
| | | |
| | | /** |
| | | * è§£ææ¬æ¬¡ç³è¯·å¯¹åºçå工档æ¡ã |
| | | * |
| | | * @param requestedEmployeeId åç«¯éæ©çç³è¯·äººåå·¥IDï¼ä¸ºç©ºè¡¨ç¤ºæ¬äºº |
| | | * @param loginUserId å½åç»å½ç¨æ·ID |
| | | * @return ç³è¯·äººå¯¹åºçåå·¥æ¡£æ¡ |
| | | */ |
| | | private HrmEmployeeDO resolveApplicant(Long requestedEmployeeId, Long loginUserId) { |
| | | HrmEmployeeDO self = employeeMapper.selectByUserId(loginUserId); |
| | | if (requestedEmployeeId == null || (self != null && Objects.equals(self.getId(), requestedEmployeeId))) { |
| | | if (self == null) { |
| | | throw exception(OVERTIME_APPLICATION_EMPLOYEE_NOT_EXISTS); |
| | | } |
| | | return self; |
| | | } |
| | | // 代ç³è¯·ï¼å¿
é¡»æç®¡çæé |
| | | if (!canManageOvertime()) { |
| | | throw exception(OVERTIME_APPLICATION_PROXY_NOT_ALLOWED); |
| | | } |
| | | HrmEmployeeDO target = employeeMapper.selectById(requestedEmployeeId); |
| | | if (target == null) { |
| | | throw exception(OVERTIME_APPLICATION_SELECTED_EMPLOYEE_NOT_EXISTS); |
| | | } |
| | | return target; |
| | | } |
| | | |
| | | private boolean canManageOvertime() { |
| | | return securityFrameworkService.hasPermission("hrm:overtime:manage"); |
| | | } |
| | | |
| | | private HrmEmployeeDO requireEmployee(Long userId) { |
| | | HrmEmployeeDO employee = employeeMapper.selectByUserId(userId); |
| | | if (employee == null) throw exception(OVERTIME_APPLICATION_EMPLOYEE_NOT_EXISTS); |
| | |
| | | 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.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceSummaryPageReqVO; |
| | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.YearMonth; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * èå¤ç»è®¡æ±æ» Service å®ç°ç±» |
| | |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public PageResult<HrmAttendanceSummaryDO> getAttendanceSummaryPage(HrmAttendanceSummaryPageReqVO pageReqVO) { |
| | | return attendanceSummaryMapper.selectPage(pageReqVO, new LambdaQueryWrapperX<HrmAttendanceSummaryDO>() |
| | | .eqIfPresent(HrmAttendanceSummaryDO::getUserId, pageReqVO.getUserId()) |
| | | .eqIfPresent(HrmAttendanceSummaryDO::getDeptId, pageReqVO.getDeptId()) |
| | | .eqIfPresent(HrmAttendanceSummaryDO::getYearMonth, pageReqVO.getYearMonth()) |
| | | .orderByDesc(HrmAttendanceSummaryDO::getYearMonth) |
| | | .orderByAsc(HrmAttendanceSummaryDO::getDeptId)); |
| | | PageResult<HrmAttendanceSummaryDO> pageResult = attendanceSummaryMapper.selectPage(pageReqVO, |
| | | new LambdaQueryWrapperX<HrmAttendanceSummaryDO>() |
| | | .eqIfPresent(HrmAttendanceSummaryDO::getUserId, pageReqVO.getUserId()) |
| | | .eqIfPresent(HrmAttendanceSummaryDO::getDeptId, pageReqVO.getDeptId()) |
| | | .eqIfPresent(HrmAttendanceSummaryDO::getYearMonth, pageReqVO.getYearMonth()) |
| | | .orderByDesc(HrmAttendanceSummaryDO::getYearMonth) |
| | | .orderByAsc(HrmAttendanceSummaryDO::getDeptId)); |
| | | refreshWorkDays(pageResult.getList()); |
| | | return pageResult; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public HrmAttendanceSummaryDO getMonthlySummary(Long userId, String yearMonth) { |
| | | return attendanceSummaryMapper.selectByUserIdAndYearMonth(userId, yearMonth); |
| | | HrmAttendanceSummaryDO summary = attendanceSummaryMapper.selectByUserIdAndYearMonth(userId, yearMonth); |
| | | if (summary != null) { |
| | | refreshWorkDays(List.of(summary)); |
| | | } |
| | | return summary; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public List<HrmAttendanceSummaryDO> getDeptMonthlySummaryList(Long deptId, String yearMonth) { |
| | | return attendanceSummaryMapper.selectListByDeptIdAndYearMonth(deptId, yearMonth); |
| | | List<HrmAttendanceSummaryDO> list = attendanceSummaryMapper.selectListByDeptIdAndYearMonth(deptId, yearMonth); |
| | | refreshWorkDays(list); |
| | | return list; |
| | | } |
| | | |
| | | /** |
| | | * æ¥è¯¢/å¯¼åºæ¶æâææ°è忥é
ç½®âéç®åºåºå¤å¤©æ°ï¼å¹¶æååçè®°å½ååã |
| | | * |
| | | * è忥é
ç½®å¯è½å¨çææ±æ»ä¹åè¢«è°æ´ï¼å¦æ°å¢è°ä¼/æ³å®åæ¥ï¼ï¼è¥åªè¿åæ§å¼ï¼ |
| | | * 页é¢ä¸çåºåºå¤å¤©æ°ä¼ä¸è忥é
ç½®é¿æä¸ä¸è´ã |
| | | */ |
| | | private void refreshWorkDays(List<HrmAttendanceSummaryDO> list) { |
| | | if (CollUtil.isEmpty(list)) { |
| | | return; |
| | | } |
| | | // å䏿¬¡è¯·æ±å
åä¸å¹´æåªè®¡ç®ä¸æ¬¡ |
| | | Map<String, Integer> workDaysCache = new HashMap<>(); |
| | | for (HrmAttendanceSummaryDO summary : list) { |
| | | if (StrUtil.isBlank(summary.getYearMonth())) { |
| | | continue; |
| | | } |
| | | Integer expected; |
| | | try { |
| | | expected = workDaysCache.computeIfAbsent(summary.getYearMonth(), yearMonth -> { |
| | | YearMonth parsed = YearMonth.parse(yearMonth); |
| | | return calculateWorkDays(parsed.atDay(1), parsed.atEndOfMonth()); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.warn("[refreshWorkDays][è夿±æ»({}) å¹´æ({}) æ æ³è§£æï¼è·³è¿éç®]", |
| | | summary.getId(), summary.getYearMonth(), e); |
| | | continue; |
| | | } |
| | | if (!Objects.equals(summary.getWorkDays(), expected)) { |
| | | log.info("[refreshWorkDays][è夿±æ»({}) å¹´æ({}) åºåºå¤å¤©æ° {} -> {}ï¼æææ°è忥é
ç½®åå]", |
| | | summary.getId(), summary.getYearMonth(), summary.getWorkDays(), expected); |
| | | summary.setWorkDays(expected); |
| | | try { |
| | | attendanceSummaryMapper.updateById(HrmAttendanceSummaryDO.builder() |
| | | .id(summary.getId()).workDays(expected).build()); |
| | | } catch (Exception e) { |
| | | // åå失败ä¸å½±åæ¬æ¬¡æ¥è¯¢ç»æï¼ä¿çå
åä¸éç®åçå¼ |
| | | log.warn("[refreshWorkDays][è夿±æ»({}) åºåºå¤å¤©æ°åå失败]", summary.getId(), e); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.hrm.service.attendance; |
| | | |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleBatchAssignReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleBatchAssignRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleLeaveRespVO; |
| | | 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; |
| | |
| | | */ |
| | | void saveSchedule(List<HrmEmployeeScheduleSaveReqVO> saveReqVOList); |
| | | |
| | | /** |
| | | * æ¹éæçï¼æé¨é¨ï¼å«ä¸çº§ï¼+ å²ä½ + åå·¥ç¶æçéåå·¥ï¼ææ¥æåºé´ç»ä¸æçã |
| | | * |
| | | * è§åï¼ |
| | | * 1. è忥é
ç½®ä¼å
äºå¨å
æ¥è§åï¼ç± {@code isWorkDay} å³å®å·¥ä½æ¥/伿¯æ¥ï¼ï¼ |
| | | * 2. 工使¥åå
¥æå®ç次ï¼ä¼æ¯æ¥/è忥æ¸
空åæçæ¬¡ï¼ |
| | | * 3. åå·¥å½å¤©åå¨å·²å®¡æ¹éè¿çè¯·åæ¶è·³è¿ï¼ä¸æ¹å¨è¯¥åå
æ ¼ï¼ |
| | | * 4. é»è¾å é¤åéççå¯ä¸é®å²çªéè¿â夿´»æ§è¡âè§é¿ã |
| | | */ |
| | | HrmEmployeeScheduleBatchAssignRespVO batchAssign(HrmEmployeeScheduleBatchAssignReqVO reqVO); |
| | | |
| | | /** |
| | | * æ¥è¯¢æç页éè¦æ è®°çè¯·åæ¥æï¼å·²å®¡æ¹éè¿ï¼æå¤©å±å¼ï¼ |
| | | */ |
| | | List<HrmEmployeeScheduleLeaveRespVO> getScheduleLeaveList(HrmEmployeeScheduleListReqVO reqVO); |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.hrm.service.attendance; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.hutool.json.JSONUtil; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleBatchAssignReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleBatchAssignRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmEmployeeScheduleLeaveRespVO; |
| | | 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.approval.HrmLeaveApplicationDO; |
| | | 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.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.employee.HrmEmployeeMapper; |
| | | import cn.iocoder.yudao.module.hrm.enums.HrmEmployeeStatusEnum; |
| | | import cn.iocoder.yudao.module.hrm.enums.HrmLeaveTypeEnum; |
| | | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| | | import cn.iocoder.yudao.module.system.api.dept.PostApi; |
| | | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| | | import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | 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.time.temporal.ChronoUnit; |
| | | import java.util.ArrayList; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.Set; |
| | | import java.util.function.Function; |
| | | import java.util.stream.Collectors; |
| | | |
| | |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class HrmEmployeeScheduleServiceImpl implements HrmEmployeeScheduleService { |
| | | |
| | | /** æ¹éæç忬¡æå¤è¦çç天æ°ï¼é¿å
䏿¬¡çæè¿å¤è¡æå®è¯·æ± */ |
| | | private static final int BATCH_MAX_DAYS = 62; |
| | | |
| | | @Resource |
| | | private HrmEmployeeScheduleMapper employeeScheduleMapper; |
| | |
| | | private HrmEmployeeMapper employeeMapper; |
| | | @Resource |
| | | private HrmShiftMapper shiftMapper; |
| | | @Resource |
| | | private HrmLeaveApplicationMapper leaveApplicationMapper; |
| | | @Resource |
| | | private HrmAttendanceHolidayService attendanceHolidayService; |
| | | @Resource |
| | | private DeptApi deptApi; |
| | | @Resource |
| | | private PostApi postApi; |
| | | |
| | | @Override |
| | | public List<HrmEmployeeScheduleRespVO> getScheduleList(HrmEmployeeScheduleListReqVO reqVO) { |
| | |
| | | throw exception(SCHEDULE_SHIFT_NOT_EXISTS); |
| | | } |
| | | } |
| | | // 3. éæ¡ upsertï¼æçæ¬¡åä¿åï¼æ çæ¬¡åå é¤ |
| | | // 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()); |
| | | } |
| | | clearShift(reqVO.getEmployeeId(), reqVO.getDate()); |
| | | } else { |
| | | if (existing == null) { |
| | | employeeScheduleMapper.insert(BeanUtils.toBean(reqVO, HrmEmployeeScheduleDO.class)); |
| | | } else { |
| | | existing.setShiftId(reqVO.getShiftId()); |
| | | existing.setRemark(reqVO.getRemark()); |
| | | employeeScheduleMapper.updateById(existing); |
| | | upsertShift(reqVO.getEmployeeId(), reqVO.getDate(), reqVO.getShiftId(), reqVO.getRemark()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public HrmEmployeeScheduleBatchAssignRespVO batchAssign(HrmEmployeeScheduleBatchAssignReqVO reqVO) { |
| | | // 1. æ ¡éªæ¥æåºé´ |
| | | LocalDate startDate = reqVO.getStartDate(); |
| | | LocalDate endDate = reqVO.getEndDate(); |
| | | if (endDate.isBefore(startDate)) { |
| | | throw exception(SCHEDULE_BATCH_DATE_RANGE_INVALID, endDate, startDate); |
| | | } |
| | | long days = ChronoUnit.DAYS.between(startDate, endDate) + 1; |
| | | if (days > BATCH_MAX_DAYS) { |
| | | throw exception(SCHEDULE_BATCH_RANGE_TOO_LONG, startDate, endDate, days); |
| | | } |
| | | // 2. æ ¡éªç次åå¨ |
| | | HrmShiftDO shift = shiftMapper.selectById(reqVO.getShiftId()); |
| | | if (shift == null) { |
| | | throw exception(SCHEDULE_SHIFT_NOT_EXISTS); |
| | | } |
| | | // 3. çéåå·¥ï¼é¨é¨ï¼å«ä¸çº§ï¼+ å²ä½ + åå·¥ç¶æ |
| | | Set<Long> deptIds = new HashSet<>(); |
| | | deptIds.add(reqVO.getDeptId()); |
| | | List<DeptRespDTO> childDepts = deptApi.getChildDeptList(reqVO.getDeptId()); |
| | | if (CollUtil.isNotEmpty(childDepts)) { |
| | | childDepts.forEach(dept -> deptIds.add(dept.getId())); |
| | | } |
| | | List<HrmEmployeeDO> employees = employeeMapper.selectListByDeptIds(deptIds).stream() |
| | | .filter(employee -> reqVO.getEmployeeStatus() == null |
| | | || Objects.equals(reqVO.getEmployeeStatus(), employee.getEmployeeStatus())) |
| | | .filter(employee -> matchesPost(employee, reqVO.getPostId())) |
| | | .collect(Collectors.toList()); |
| | | if (CollUtil.isEmpty(employees)) { |
| | | throw exception(SCHEDULE_BATCH_NO_EMPLOYEE, |
| | | resolveDeptName(reqVO.getDeptId()), |
| | | resolvePostName(reqVO.getPostId()), |
| | | resolveStatusName(reqVO.getEmployeeStatus())); |
| | | } |
| | | // 4. é¢å 载已审æ¹è¯·åï¼æâåå·¥ + æ¥æâæ è®°éè¦è·³è¿çåå
æ ¼ |
| | | Set<String> leaveKeys = buildApprovedLeaveKeys(employees, startDate, endDate); |
| | | // 5. ææ¥æé天æçï¼å·¥ä½æ¥åçæ¬¡ã伿¯æ¥æ¸
空ã已审æ¹è¯·åè·³è¿ |
| | | int scheduledCount = 0; |
| | | int skippedLeaveCount = 0; |
| | | int clearedRestDayCount = 0; |
| | | for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) { |
| | | boolean workDay = attendanceHolidayService.isWorkDay(date); |
| | | for (HrmEmployeeDO employee : employees) { |
| | | if (leaveKeys.contains(buildKey(employee.getId(), date))) { |
| | | skippedLeaveCount++; |
| | | continue; |
| | | } |
| | | if (workDay) { |
| | | upsertShift(employee.getId(), date, reqVO.getShiftId(), reqVO.getRemark()); |
| | | scheduledCount++; |
| | | } else if (clearShift(employee.getId(), date)) { |
| | | clearedRestDayCount++; |
| | | } |
| | | } |
| | | } |
| | | log.info("[batchAssign][é¨é¨({}) å²ä½({}) ç¶æ({}) {}~{} çæ¬¡({}) å½ä¸åå·¥ {} 人ï¼åå
¥ {} æ¡ï¼è·³è¿è¯·å {} æ¡ï¼æ¸
ç©ºä¼æ¯æ¥ {} æ¡]", |
| | | reqVO.getDeptId(), reqVO.getPostId(), reqVO.getEmployeeStatus(), startDate, endDate, |
| | | reqVO.getShiftId(), employees.size(), scheduledCount, skippedLeaveCount, clearedRestDayCount); |
| | | return HrmEmployeeScheduleBatchAssignRespVO.builder() |
| | | .matchedEmployeeCount(employees.size()) |
| | | .scheduledCount(scheduledCount) |
| | | .skippedLeaveCount(skippedLeaveCount) |
| | | .clearedRestDayCount(clearedRestDayCount) |
| | | .build(); |
| | | } |
| | | |
| | | @Override |
| | | public List<HrmEmployeeScheduleLeaveRespVO> getScheduleLeaveList(HrmEmployeeScheduleListReqVO reqVO) { |
| | | YearMonth yearMonth = YearMonth.parse(reqVO.getYearMonth()); |
| | | LocalDate startDate = yearMonth.atDay(1); |
| | | LocalDate endDate = yearMonth.atEndOfMonth(); |
| | | // 1. ç¡®å®éè¦æ è®°çåå·¥ |
| | | List<HrmEmployeeDO> employees; |
| | | if (reqVO.getEmployeeId() != null) { |
| | | HrmEmployeeDO employee = employeeMapper.selectById(reqVO.getEmployeeId()); |
| | | employees = employee == null ? Collections.emptyList() : List.of(employee); |
| | | } else if (reqVO.getDeptId() != null) { |
| | | employees = employeeMapper.selectByDeptId(reqVO.getDeptId()); |
| | | } else { |
| | | employees = employeeMapper.selectList(); |
| | | } |
| | | if (CollUtil.isEmpty(employees)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | // 2. æ¥è¯¢å·²å®¡æ¹éè¿ç请åï¼æç³»ç»ç¨æ·IDï¼ |
| | | Map<Long, List<HrmEmployeeDO>> employeeByUserId = employees.stream() |
| | | .filter(employee -> employee.getUserId() != null) |
| | | .collect(Collectors.groupingBy(HrmEmployeeDO::getUserId)); |
| | | if (employeeByUserId.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | List<HrmLeaveApplicationDO> leaves = leaveApplicationMapper.selectApprovedListByUserIdsAndTimeRange( |
| | | employeeByUserId.keySet(), startDate.atStartOfDay(), endDate.atTime(23, 59, 59)); |
| | | if (CollUtil.isEmpty(leaves)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | // 3. æå¤©å±å¼ä¸ºæç页æ è®° |
| | | List<HrmEmployeeScheduleLeaveRespVO> result = new ArrayList<>(); |
| | | for (HrmLeaveApplicationDO leave : leaves) { |
| | | List<HrmEmployeeDO> matchedEmployees = employeeByUserId.get(leave.getUserId()); |
| | | if (CollUtil.isEmpty(matchedEmployees)) { |
| | | continue; |
| | | } |
| | | LocalDate from = leave.getStartTime().toLocalDate().isBefore(startDate) |
| | | ? startDate : leave.getStartTime().toLocalDate(); |
| | | LocalDate to = leave.getEndTime().toLocalDate().isAfter(endDate) |
| | | ? endDate : leave.getEndTime().toLocalDate(); |
| | | String leaveTypeName = HrmLeaveTypeEnum.getNameByStatus(leave.getLeaveType()); |
| | | for (LocalDate date = from; !date.isAfter(to); date = date.plusDays(1)) { |
| | | for (HrmEmployeeDO employee : matchedEmployees) { |
| | | result.add(new HrmEmployeeScheduleLeaveRespVO(employee.getId(), employee.getName(), date, |
| | | leave.getLeaveType(), leaveTypeName)); |
| | | } |
| | | } |
| | | } |
| | | result.sort((a, b) -> { |
| | | int compare = a.getDate().compareTo(b.getDate()); |
| | | return compare != 0 ? compare : a.getEmployeeId().compareTo(b.getEmployeeId()); |
| | | }); |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * åå
¥/è¦ççæ¬¡ï¼è¥è¯¥åå·¥è¯¥æ¥æåå¨é»è¾å é¤çæ§è¡ï¼åå
夿´»ï¼è§é¿å¯ä¸é®å²çªã |
| | | */ |
| | | private void upsertShift(Long employeeId, LocalDate date, Long shiftId, String remark) { |
| | | HrmEmployeeScheduleDO existing = employeeScheduleMapper.selectByEmployeeIdAndDateIncludeDeleted(employeeId, date); |
| | | if (existing == null) { |
| | | HrmEmployeeScheduleDO insert = new HrmEmployeeScheduleDO(); |
| | | insert.setEmployeeId(employeeId); |
| | | insert.setDate(date); |
| | | insert.setShiftId(shiftId); |
| | | insert.setRemark(remark); |
| | | employeeScheduleMapper.insert(insert); |
| | | return; |
| | | } |
| | | if (Boolean.TRUE.equals(existing.getDeleted())) { |
| | | employeeScheduleMapper.reviveById(existing.getId(), shiftId, remark, currentUpdater()); |
| | | return; |
| | | } |
| | | existing.setShiftId(shiftId); |
| | | existing.setRemark(remark); |
| | | employeeScheduleMapper.updateById(existing); |
| | | } |
| | | |
| | | /** |
| | | * æ¸
ç©ºçæ¬¡ï¼é»è¾å é¤ï¼ï¼è¿åæ¬æ¬¡æ¯å¦ççæ¸
æäºä¸æ¡éç©ºçæ¬¡ã |
| | | */ |
| | | private boolean clearShift(Long employeeId, LocalDate date) { |
| | | HrmEmployeeScheduleDO existing = employeeScheduleMapper.selectByEmployeeIdAndDateIncludeDeleted(employeeId, date); |
| | | if (existing == null || Boolean.TRUE.equals(existing.getDeleted()) || existing.getShiftId() == null) { |
| | | return false; |
| | | } |
| | | employeeScheduleMapper.deleteById(existing.getId()); |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * æé âåå·¥ + æ¥æâç已审æ¹è¯·åéå |
| | | */ |
| | | private Set<String> buildApprovedLeaveKeys(List<HrmEmployeeDO> employees, LocalDate startDate, LocalDate endDate) { |
| | | Map<Long, List<HrmEmployeeDO>> employeeByUserId = employees.stream() |
| | | .filter(employee -> employee.getUserId() != null) |
| | | .collect(Collectors.groupingBy(HrmEmployeeDO::getUserId)); |
| | | if (employeeByUserId.isEmpty()) { |
| | | return Collections.emptySet(); |
| | | } |
| | | List<HrmLeaveApplicationDO> leaves = leaveApplicationMapper.selectApprovedListByUserIdsAndTimeRange( |
| | | employeeByUserId.keySet(), startDate.atStartOfDay(), endDate.atTime(23, 59, 59)); |
| | | if (CollUtil.isEmpty(leaves)) { |
| | | return Collections.emptySet(); |
| | | } |
| | | Set<String> keys = new HashSet<>(); |
| | | for (HrmLeaveApplicationDO leave : leaves) { |
| | | List<HrmEmployeeDO> matchedEmployees = employeeByUserId.get(leave.getUserId()); |
| | | if (CollUtil.isEmpty(matchedEmployees)) { |
| | | continue; |
| | | } |
| | | LocalDate from = leave.getStartTime().toLocalDate().isBefore(startDate) |
| | | ? startDate : leave.getStartTime().toLocalDate(); |
| | | LocalDate to = leave.getEndTime().toLocalDate().isAfter(endDate) |
| | | ? endDate : leave.getEndTime().toLocalDate(); |
| | | for (LocalDate date = from; !date.isAfter(to); date = date.plusDays(1)) { |
| | | for (HrmEmployeeDO employee : matchedEmployees) { |
| | | keys.add(buildKey(employee.getId(), date)); |
| | | } |
| | | } |
| | | } |
| | | return keys; |
| | | } |
| | | |
| | | private static String buildKey(Long employeeId, LocalDate date) { |
| | | return employeeId + "_" + date; |
| | | } |
| | | |
| | | private boolean matchesPost(HrmEmployeeDO employee, Long postId) { |
| | | if (postId == null) { |
| | | return true; |
| | | } |
| | | if (StrUtil.isBlank(employee.getPostIds())) { |
| | | return false; |
| | | } |
| | | try { |
| | | return JSONUtil.toList(employee.getPostIds(), Long.class).contains(postId); |
| | | } catch (Exception e) { |
| | | log.warn("[matchesPost][åå·¥({}) ç postIds({}) 䏿¯åæ³ JSONï¼è·³è¿å²ä½å¹é
]", employee.getId(), employee.getPostIds()); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private String resolveDeptName(Long deptId) { |
| | | DeptRespDTO dept = deptId == null ? null : deptApi.getDept(deptId); |
| | | return dept == null ? "ID=" + deptId : dept.getName(); |
| | | } |
| | | |
| | | private String resolvePostName(Long postId) { |
| | | if (postId == null) { |
| | | return "ä¸é"; |
| | | } |
| | | List<PostRespDTO> posts = postApi.getPostList(Collections.singleton(postId)); |
| | | return CollUtil.isEmpty(posts) ? "ID=" + postId : posts.get(0).getName(); |
| | | } |
| | | |
| | | private String resolveStatusName(Integer employeeStatus) { |
| | | if (employeeStatus == null) { |
| | | return "ä¸é"; |
| | | } |
| | | for (HrmEmployeeStatusEnum status : HrmEmployeeStatusEnum.values()) { |
| | | if (Objects.equals(status.getStatus(), employeeStatus)) { |
| | | return status.getName(); |
| | | } |
| | | } |
| | | return "ID=" + employeeStatus; |
| | | } |
| | | |
| | | private static String currentUpdater() { |
| | | Long userId = SecurityFrameworkUtils.getLoginUserId(); |
| | | return userId == null ? "" : String.valueOf(userId); |
| | | } |
| | | |
| | | } |