xiaoyi
2026-10-29 416558785276159b8730b2826e71995fc16b59a7
refactor(safety): 人员与车间字段全面改为主数据关联

问题:模块初版把「负责人/巡检人/上报人/整改人/复查人/申请人/审批人/监护人/
保管人/讲师/组织人/车间」全部做成纯文本,人名靠手打、车间靠手输,
与人力资源员工档案、MES 车间主数据完全脱节,无法核对、无法联动、无法追责。

改动(统一模式:xxx_id 关联 + xxx_name 保留为快照):
- 新增 SafetyRefResolver:模块内所有人员/车间引用统一走这里解析
· requireEmployee(id, errorCode) / findEmployee / employeeMap(ids)
· requireWorkshop(id) / findWorkshop / workshopMap(ids)
· 批量方法一次性查好,避免逐条 N+1
- 17 个字段新增 id 列(SQL: sql/mysql/safety_master_data_link_20261029.sql,幂等)
· safety_org.leader_id
· safety_inspection_point.workshop_id / owner_id
· safety_inspection.inspector_id
· safety_hazard.reporter_id / assignee_id / rechecker_id
· safety_work_permit.workshop_id / applicant_id / approver_id / guardian_id
· safety_work_permit_monitor.guardian_id
· safety_material.manager_id
· safety_training.trainer_id
· safety_drill.organizer_id
· safety_accident.workshop_id / reporter_id
- 保存时从主数据带出名称写快照;查询时用主数据最新值覆盖快照,
员工改名/调岗、车间改名后台账自动跟着变;主数据被删则回退快照保证可读
- 「整改人与复查人不能同一人」「申请人不能审批自己」改为按员工编号比较,
避免同名不同人误判;新增错误码 SAFETY_PERMIT_SELF_APPROVE 给出明确提示
- 台账导出全部走批量回填
- 证件分页改用 fillDisplayFieldsBatch,修掉逐条查员工的 N+1
- 修复单条 get 接口只返回快照不回填主数据的问题(巡检点位、安全组织)

验证:mvn compile/package 通过;8 类业务端到端跑通(含改名联动)
已添加2个文件
已修改49个文件
838 ■■■■■ 文件已修改
sql/mysql/safety_master_data_link_20261029.sql 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyCertificateController.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyHazardController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyInspectionController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyMaterialController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyOrgController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyTrainingController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyWorkPermitController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardAssignReqVO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardRecheckReqVO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardRespVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardSaveReqVO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionPointRespVO.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionPointSaveReqVO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionRespVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionSaveReqVO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialRespVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialSaveReqVO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/org/SafetyOrgRespVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/org/SafetyOrgSaveReqVO.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitApproveReqVO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitMonitorRespVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitMonitorSaveReqVO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitRespVO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitSaveReqVO.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyAccidentRespVO.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyAccidentSaveReqVO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyDrillRespVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyDrillSaveReqVO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyTrainingRespVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyTrainingSaveReqVO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyAccidentDO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyDrillDO.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyHazardDO.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyInspectionDO.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyInspectionPointDO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyMaterialDO.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyOrgDO.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyTrainingDO.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyWorkPermitDO.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyWorkPermitMonitorDO.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/SafetyErrorCodeConstants.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyCertificateService.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyHazardService.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyInspectionService.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyLedgerExportService.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyMaterialService.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyOrgService.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyRefResolver.java 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyTrainingService.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyWorkPermitService.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/safety_master_data_link_20261029.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,102 @@
-- ============================================================
-- å®‰å…¨ç”Ÿäº§ - äººå‘˜ä¸Žè½¦é—´å­—段全面改为主数据关联
-- ç”Ÿæˆæ—¶é—´ï¼š2026-10-29
--
-- èƒŒæ™¯ï¼šæ¨¡å—初版把「负责人 / å·¡æ£€äºº / ä¸ŠæŠ¥äºº / æ•´æ”¹äºº / å¤æŸ¥äºº / ç”³è¯·äºº /
--      å®¡æ‰¹äºº / ç›‘护人 / ä¿ç®¡äºº / è®²å¸ˆ / ç»„织人 / è½¦é—´ã€å…¨éƒ¨åšæˆäº†çº¯æ–‡æœ¬ï¼Œ
--      ä¸ŽäººåŠ›èµ„æºå‘˜å·¥æ¡£æ¡ˆã€MES è½¦é—´ä¸»æ•°æ®å®Œå…¨è„±èŠ‚ï¼Œäººåé æ‰‹æ‰“ã€è½¦é—´é æ‰‹è¾“ã€‚
--
-- åšæ³•(统一模式,与 safety_certificate ä¸€è‡´ï¼‰ï¼š
--   1. æ–°å¢ž xxx_id å…³è”主数据:employee å…³è” hrm_employee.id,workshop å…³è” mes_md_workshop.id
--   2. åŽŸæ–‡æœ¬åˆ—ä¿ç•™ï¼Œé™çº§ä¸ºã€Œå¿«ç…§ã€ï¼šä¿å­˜æ—¶ä»Žä¸»æ•°æ®å¸¦å‡ºï¼ŒæŸ¥è¯¢æ—¶ç”¨ä¸»æ•°æ®æœ€æ–°å€¼è¦†ç›–ï¼›
--      ä¸»æ•°æ®è¢«åˆ æˆ–历史脏数据时回退到快照,保证老记录仍可读(即「特别的可以手打」)
--   3. id åˆ—一律 NULL å…è®¸ï¼Œé¿å…è¿ç§»å¡ä½åŽ†å²æ•°æ®
--
-- å¹‚等:全部用 information_schema åˆ¤æ–­åŽå† ALTER,可重复执行
-- ============================================================
-- ---------- é€šç”¨ï¼šæ–°å¢žåˆ—的过程式写法(MySQL æ—  ADD COLUMN IF NOT EXISTS) ----------
DROP PROCEDURE IF EXISTS sp_safety_add_col;
DELIMITER $$
CREATE PROCEDURE sp_safety_add_col(IN p_table VARCHAR(64), IN p_col VARCHAR(64),
                                   IN p_def VARCHAR(255), IN p_comment VARCHAR(255), IN p_after VARCHAR(64))
BEGIN
  IF NOT EXISTS (SELECT 1 FROM information_schema.columns
                  WHERE table_schema = DATABASE() AND table_name = p_table AND column_name = p_col) THEN
    SET @s = CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN `', p_col, '` ', p_def,
                    ' COMMENT ''', p_comment, ''' AFTER `', p_after, '`');
    PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;
  END IF;
  IF NOT EXISTS (SELECT 1 FROM information_schema.statistics
                  WHERE table_schema = DATABASE() AND table_name = p_table AND index_name = CONCAT('idx_', p_col)) THEN
    SET @s2 = CONCAT('CREATE INDEX `idx_', p_col, '` ON `', p_table, '` (`', p_col, '`)');
    PREPARE st2 FROM @s2; EXECUTE st2; DEALLOCATE PREPARE st2;
  END IF;
END$$
DELIMITER ;
-- ---------- 1. å®‰å…¨ç»„织架构:负责人 ----------
CALL sp_safety_add_col('safety_org', 'leader_id', 'bigint NULL', '负责人(员工编号,关联 hrm_employee.id)', 'name');
-- ---------- 2. å·¡æ£€ç‚¹ä½ï¼šæ‰€å±žè½¦é—´ + è´£ä»»äºº ----------
CALL sp_safety_add_col('safety_inspection_point', 'workshop_id', 'bigint NULL', '所属车间(关联 mes_md_workshop.id)', 'point_type');
CALL sp_safety_add_col('safety_inspection_point', 'owner_id', 'bigint NULL', '责任人(员工编号,关联 hrm_employee.id)', 'inspect_cycle');
-- ---------- 3. å·¡æ£€è®°å½•:巡检人 ----------
CALL sp_safety_add_col('safety_inspection', 'inspector_id', 'bigint NULL', '巡检人(员工编号,关联 hrm_employee.id)', 'inspector');
-- ---------- 4. é𐿂£ï¼šä¸ŠæŠ¥äºº / æ•´æ”¹è´£ä»»äºº / å¤æŸ¥äºº ----------
CALL sp_safety_add_col('safety_hazard', 'reporter_id', 'bigint NULL', '上报人(员工编号,关联 hrm_employee.id)', 'reporter');
CALL sp_safety_add_col('safety_hazard', 'assignee_id', 'bigint NULL', '整改责任人(员工编号,关联 hrm_employee.id)', 'assignee');
CALL sp_safety_add_col('safety_hazard', 'rechecker_id', 'bigint NULL', '复查人(员工编号,关联 hrm_employee.id)', 'rechecker');
-- ---------- 5. é«˜å±ä½œä¸šç¥¨ï¼šä½œä¸šè½¦é—´ / ç”³è¯·äºº / å®¡æ‰¹äºº / ç›‘护人 ----------
CALL sp_safety_add_col('safety_work_permit', 'workshop_id', 'bigint NULL', '作业车间(关联 mes_md_workshop.id)', 'permit_type');
CALL sp_safety_add_col('safety_work_permit', 'applicant_id', 'bigint NULL', '申请人(员工编号,关联 hrm_employee.id)', 'applicant');
CALL sp_safety_add_col('safety_work_permit', 'approver_id', 'bigint NULL', '审批人(员工编号,关联 hrm_employee.id)', 'approver');
CALL sp_safety_add_col('safety_work_permit', 'guardian_id', 'bigint NULL', '监护人(员工编号,关联 hrm_employee.id)', 'guardian');
-- ---------- 6. ä½œä¸šç›‘护记录:监护人 ----------
CALL sp_safety_add_col('safety_work_permit_monitor', 'guardian_id', 'bigint NULL', '监护人(员工编号,关联 hrm_employee.id)', 'guardian');
-- ---------- 7. åº”急物资:保管人 ----------
CALL sp_safety_add_col('safety_material', 'manager_id', 'bigint NULL', '保管人(员工编号,关联 hrm_employee.id)', 'manager');
-- ---------- 8. å®‰å…¨åŸ¹è®­ï¼šè®²å¸ˆ/组织人 ----------
CALL sp_safety_add_col('safety_training', 'trainer_id', 'bigint NULL', '讲师/组织人(员工编号,关联 hrm_employee.id)', 'trainer');
-- ---------- 9. æ¶ˆé˜²æ¼”练:组织人 ----------
CALL sp_safety_add_col('safety_drill', 'organizer_id', 'bigint NULL', '组织人(员工编号,关联 hrm_employee.id)', 'organizer');
-- ---------- 10. äº‹æ•…登记:所属车间 + ç™»è®°äºº ----------
CALL sp_safety_add_col('safety_accident', 'workshop_id', 'bigint NULL', '所属车间(关联 mes_md_workshop.id)', 'accident_type');
CALL sp_safety_add_col('safety_accident', 'reporter_id', 'bigint NULL', '登记人(员工编号,关联 hrm_employee.id)', 'reporter');
-- ---------- 11. åŽŸæ–‡æœ¬åˆ—æ³¨é‡Šæ”¹ä¸ºã€Œå¿«ç…§ã€è¯­ä¹‰ ----------
ALTER TABLE `safety_org` MODIFY COLUMN `leader_name` varchar(50) DEFAULT NULL COMMENT '负责人姓名(快照,由员工档案带出)';
ALTER TABLE `safety_inspection_point` MODIFY COLUMN `workshop_name` varchar(100) DEFAULT NULL COMMENT '所属车间(快照,由车间主数据带出)',
  MODIFY COLUMN `owner_name` varchar(50) DEFAULT NULL COMMENT '责任人姓名(快照,由员工档案带出)';
ALTER TABLE `safety_inspection` MODIFY COLUMN `inspector` varchar(50) DEFAULT NULL COMMENT '巡检人姓名(快照,由员工档案带出)';
ALTER TABLE `safety_hazard` MODIFY COLUMN `reporter` varchar(50) DEFAULT NULL COMMENT '上报人姓名(快照,由员工档案带出)',
  MODIFY COLUMN `assignee` varchar(50) DEFAULT NULL COMMENT '整改责任人姓名(快照,由员工档案带出)',
  MODIFY COLUMN `rechecker` varchar(50) DEFAULT NULL COMMENT '复查人姓名(快照,由员工档案带出)';
ALTER TABLE `safety_work_permit` MODIFY COLUMN `workshop_name` varchar(100) DEFAULT NULL COMMENT '作业车间(快照,由车间主数据带出)',
  MODIFY COLUMN `applicant` varchar(50) DEFAULT NULL COMMENT '申请人姓名(快照,由员工档案带出)',
  MODIFY COLUMN `approver` varchar(50) DEFAULT NULL COMMENT '审批人姓名(快照,由员工档案带出)',
  MODIFY COLUMN `guardian` varchar(50) DEFAULT NULL COMMENT '监护人姓名(快照,由员工档案带出)';
ALTER TABLE `safety_work_permit_monitor` MODIFY COLUMN `guardian` varchar(50) DEFAULT NULL COMMENT '监护人姓名(快照,由员工档案带出)';
ALTER TABLE `safety_material` MODIFY COLUMN `manager` varchar(50) DEFAULT NULL COMMENT '保管人姓名(快照,由员工档案带出)';
ALTER TABLE `safety_training` MODIFY COLUMN `trainer` varchar(50) DEFAULT NULL COMMENT '讲师/组织人姓名(快照,由员工档案带出)';
ALTER TABLE `safety_drill` MODIFY COLUMN `organizer` varchar(50) DEFAULT NULL COMMENT '组织人姓名(快照,由员工档案带出)';
ALTER TABLE `safety_accident` MODIFY COLUMN `workshop_name` varchar(100) DEFAULT NULL COMMENT '所属车间(快照,由车间主数据带出)',
  MODIFY COLUMN `reporter` varchar(50) DEFAULT NULL COMMENT '登记人姓名(快照,由员工档案带出)';
-- ---------- 12. æ¸…理临时过程 ----------
DROP PROCEDURE IF EXISTS sp_safety_add_col;
-- ---------- 13. æ ¡éªŒ ----------
SELECT TABLE_NAME, COLUMN_NAME, COLUMN_COMMENT
  FROM information_schema.columns
 WHERE table_schema = DATABASE() AND TABLE_NAME LIKE 'safety%'
   AND (COLUMN_NAME LIKE '%\_id' OR COLUMN_NAME IN ('inspector','reporter','assignee','rechecker','applicant','approver','guardian','manager','trainer','organizer','workshop_name','leader_name'))
 ORDER BY TABLE_NAME, ORDINAL_POSITION;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyCertificateController.java
@@ -74,12 +74,10 @@
    @PreAuthorize("@ss.hasPermission('safety:cert:query')")
    public CommonResult<PageResult<SafetyCertificateRespVO>> page(@Valid SafetyCertificatePageReqVO reqVO) {
        var page = certificateService.getCertificatePage(reqVO);
        // æ‰¹é‡è¡¥å…¨æŒè¯äººæœ€æ–°æ¡£æ¡ˆï¼ˆå§“名/工号/部门),避免逐条查询
        // æ‰¹é‡è¡¥å…¨æŒè¯äººæœ€æ–°æ¡£æ¡ˆï¼ˆå§“名/工号/部门),一次性查好避免 N+1
        certificateService.fillEmployeeInfo(page.getList());
        var result = BeanUtils.toBean(page, SafetyCertificateRespVO.class);
        for (int i = 0; i < page.getList().size(); i++) {
            certificateService.fillDisplayFields(page.getList().get(i), result.getList().get(i));
        }
        certificateService.fillDisplayFieldsBatch(page.getList(), result.getList());
        return success(result);
    }
@@ -92,13 +90,9 @@
        List<cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyCertificateDO> certs =
                certificateService.getExpiringCertificates(days);
        certificateService.fillEmployeeInfo(certs);
        List<SafetyCertificateRespVO> list = certs.stream()
                .map(cert -> {
                    var resp = BeanUtils.toBean(cert, SafetyCertificateRespVO.class);
                    certificateService.fillDisplayFields(cert, resp);
                    return resp;
                }).toList();
        return success(list);
        var resps = BeanUtils.toBean(certs, SafetyCertificateRespVO.class);
        certificateService.fillDisplayFieldsBatch(certs, resps);
        return success(resps);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyHazardController.java
@@ -60,6 +60,7 @@
    @PreAuthorize("@ss.hasPermission('safety:hazard:query')")
    public CommonResult<SafetyHazardRespVO> get(@RequestParam("id") Long id) {
        var hazard = hazardService.getHazard(id);
        hazardService.fillPersonNames(java.util.List.of(hazard));
        var resp = BeanUtils.toBean(hazard, SafetyHazardRespVO.class);
        SafetyHazardService.fillHazardDisplay(hazard, resp);
        return success(resp);
@@ -70,6 +71,7 @@
    @PreAuthorize("@ss.hasPermission('safety:hazard:query')")
    public CommonResult<PageResult<SafetyHazardRespVO>> page(@Valid SafetyHazardPageReqVO reqVO) {
        var page = hazardService.getHazardPage(reqVO);
        hazardService.fillPersonNames(page.getList());
        var result = BeanUtils.toBean(page, SafetyHazardRespVO.class);
        for (int i = 0; i < page.getList().size(); i++) {
            SafetyHazardService.fillHazardDisplay(page.getList().get(i), result.getList().get(i));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyInspectionController.java
@@ -64,6 +64,8 @@
    @PreAuthorize("@ss.hasPermission('safety:inspection-point:query')")
    public CommonResult<SafetyInspectionPointRespVO> getPoint(@RequestParam("id") Long id) {
        var point = inspectionService.getPoint(id);
        // å•条查询也要回填主数据最新值,不能只返回快照
        inspectionService.fillPointRefs(java.util.List.of(point));
        var resp = BeanUtils.toBean(point, SafetyInspectionPointRespVO.class);
        SafetyInspectionService.fillPointDisplay(point, resp);
        resp.setLastInspectTime(inspectionService.getLastInspectTime(id));
@@ -76,6 +78,7 @@
    public CommonResult<PageResult<SafetyInspectionPointRespVO>> getPointPage(
            @Valid SafetyInspectionPointPageReqVO reqVO) {
        var page = inspectionService.getPointPage(reqVO);
        inspectionService.fillPointRefs(page.getList());
        var result = BeanUtils.toBean(page, SafetyInspectionPointRespVO.class);
        for (int i = 0; i < page.getList().size(); i++) {
            SafetyInspectionService.fillPointDisplay(page.getList().get(i), result.getList().get(i));
@@ -87,7 +90,9 @@
    @Operation(summary = "获得启用中的巡检点位精简列表(移动端巡检选择)")
    @PreAuthorize("@ss.hasPermission('safety:inspection-point:query')")
    public CommonResult<List<SafetyInspectionPointRespVO>> getPointSimpleList() {
        List<SafetyInspectionPointRespVO> list = inspectionService.getEnabledPointList().stream()
        var points = inspectionService.getEnabledPointList();
        inspectionService.fillPointRefs(points);
        List<SafetyInspectionPointRespVO> list = points.stream()
                .map(point -> {
                    var resp = BeanUtils.toBean(point, SafetyInspectionPointRespVO.class);
                    SafetyInspectionService.fillPointDisplay(point, resp);
@@ -128,6 +133,7 @@
    @PreAuthorize("@ss.hasPermission('safety:inspection:query')")
    public CommonResult<SafetyInspectionRespVO> getRecord(@RequestParam("id") Long id) {
        var record = inspectionService.getInspection(id);
        inspectionService.fillInspectorNames(java.util.List.of(record));
        var resp = BeanUtils.toBean(record, SafetyInspectionRespVO.class);
        resp.setPointTypeName(cn.iocoder.yudao.module.mes.enums.safety.SafetyInspectionPointTypeEnum
                .getNameByType(record.getPointType()));
@@ -141,6 +147,7 @@
    public CommonResult<PageResult<SafetyInspectionRespVO>> getRecordPage(
            @Valid SafetyInspectionPageReqVO reqVO) {
        var page = inspectionService.getInspectionPage(reqVO);
        inspectionService.fillInspectorNames(page.getList());
        var result = BeanUtils.toBean(page, SafetyInspectionRespVO.class);
        for (int i = 0; i < page.getList().size(); i++) {
            var record = page.getList().get(i);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyMaterialController.java
@@ -62,6 +62,7 @@
    @PreAuthorize("@ss.hasPermission('safety:material:query')")
    public CommonResult<SafetyMaterialRespVO> get(@RequestParam("id") Long id) {
        var material = materialService.getMaterial(id);
        materialService.fillManagers(java.util.List.of(material));
        var resp = BeanUtils.toBean(material, SafetyMaterialRespVO.class);
        SafetyMaterialService.fillMaterialDisplay(material, resp);
        return success(resp);
@@ -72,6 +73,7 @@
    @PreAuthorize("@ss.hasPermission('safety:material:query')")
    public CommonResult<PageResult<SafetyMaterialRespVO>> page(@Valid SafetyMaterialPageReqVO reqVO) {
        var page = materialService.getMaterialPage(reqVO);
        materialService.fillManagers(page.getList());
        var result = BeanUtils.toBean(page, SafetyMaterialRespVO.class);
        for (int i = 0; i < page.getList().size(); i++) {
            SafetyMaterialService.fillMaterialDisplay(page.getList().get(i), result.getList().get(i));
@@ -83,7 +85,9 @@
    @Operation(summary = "获得低于安全库存的物资列表(预警)")
    @PreAuthorize("@ss.hasPermission('safety:material:query')")
    public CommonResult<List<SafetyMaterialRespVO>> belowSafeStock() {
        List<SafetyMaterialRespVO> list = materialService.getBelowSafeStockList().stream()
        var materials = materialService.getBelowSafeStockList();
        materialService.fillManagers(materials);
        List<SafetyMaterialRespVO> list = materials.stream()
                .map(material -> {
                    var resp = BeanUtils.toBean(material, SafetyMaterialRespVO.class);
                    SafetyMaterialService.fillMaterialDisplay(material, resp);
@@ -98,7 +102,9 @@
    @PreAuthorize("@ss.hasPermission('safety:material:query')")
    public CommonResult<List<SafetyMaterialRespVO>> expiringList(
            @RequestParam(value = "days", required = false) Integer days) {
        List<SafetyMaterialRespVO> list = materialService.getExpiringList(days).stream()
        var materials = materialService.getExpiringList(days);
        materialService.fillManagers(materials);
        List<SafetyMaterialRespVO> list = materials.stream()
                .map(material -> {
                    var resp = BeanUtils.toBean(material, SafetyMaterialRespVO.class);
                    SafetyMaterialService.fillMaterialDisplay(material, resp);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyOrgController.java
@@ -63,6 +63,14 @@
        var org = orgService.getOrg(id);
        var resp = new SafetyOrgRespVO();
        org2resp(org, resp);
        // è´Ÿè´£äººç”¨å‘˜å·¥æ¡£æ¡ˆæœ€æ–°å§“名覆盖快照
        if (org.getLeaderId() != null) {
            var employee = orgService.getLeader(org.getLeaderId());
            if (employee != null) {
                resp.setLeaderName(employee.getName());
                resp.setLeaderPhone(employee.getPhone());
            }
        }
        return success(resp);
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyTrainingController.java
@@ -62,6 +62,7 @@
    @PreAuthorize("@ss.hasPermission('safety:training:query')")
    public CommonResult<SafetyTrainingRespVO> get(@RequestParam("id") Long id) {
        var training = trainingService.getTraining(id);
        trainingService.fillTrainers(java.util.List.of(training));
        var resp = BeanUtils.toBean(training, SafetyTrainingRespVO.class);
        SafetyTrainingService.fillTrainingDisplay(training, resp);
        return success(resp);
@@ -72,6 +73,7 @@
    @PreAuthorize("@ss.hasPermission('safety:training:query')")
    public CommonResult<PageResult<SafetyTrainingRespVO>> page(@Valid SafetyTrainingPageReqVO reqVO) {
        var page = trainingService.getTrainingPage(reqVO);
        trainingService.fillTrainers(page.getList());
        var result = BeanUtils.toBean(page, SafetyTrainingRespVO.class);
        for (int i = 0; i < page.getList().size(); i++) {
            SafetyTrainingService.fillTrainingDisplay(page.getList().get(i), result.getList().get(i));
@@ -111,6 +113,7 @@
    @PreAuthorize("@ss.hasPermission('safety:drill:query')")
    public CommonResult<SafetyDrillRespVO> getDrill(@RequestParam("id") Long id) {
        var drill = trainingService.getDrill(id);
        trainingService.fillOrganizers(java.util.List.of(drill));
        var resp = BeanUtils.toBean(drill, SafetyDrillRespVO.class);
        SafetyTrainingService.fillDrillDisplay(drill, resp);
        return success(resp);
@@ -121,6 +124,7 @@
    @PreAuthorize("@ss.hasPermission('safety:drill:query')")
    public CommonResult<PageResult<SafetyDrillRespVO>> drillPage(@Valid SafetyDrillPageReqVO reqVO) {
        var page = trainingService.getDrillPage(reqVO);
        trainingService.fillOrganizers(page.getList());
        var result = BeanUtils.toBean(page, SafetyDrillRespVO.class);
        for (int i = 0; i < page.getList().size(); i++) {
            SafetyTrainingService.fillDrillDisplay(page.getList().get(i), result.getList().get(i));
@@ -160,6 +164,7 @@
    @PreAuthorize("@ss.hasPermission('safety:accident:query')")
    public CommonResult<SafetyAccidentRespVO> getAccident(@RequestParam("id") Long id) {
        var accident = trainingService.getAccident(id);
        trainingService.fillAccidentRefs(java.util.List.of(accident));
        var resp = BeanUtils.toBean(accident, SafetyAccidentRespVO.class);
        SafetyTrainingService.fillAccidentDisplay(accident, resp);
        return success(resp);
@@ -171,6 +176,7 @@
    public CommonResult<PageResult<SafetyAccidentRespVO>> accidentPage(
            @Valid SafetyAccidentPageReqVO reqVO) {
        var page = trainingService.getAccidentPage(reqVO);
        trainingService.fillAccidentRefs(page.getList());
        var result = BeanUtils.toBean(page, SafetyAccidentRespVO.class);
        for (int i = 0; i < page.getList().size(); i++) {
            SafetyTrainingService.fillAccidentDisplay(page.getList().get(i), result.getList().get(i));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyWorkPermitController.java
@@ -62,6 +62,7 @@
    @PreAuthorize("@ss.hasPermission('safety:permit:query')")
    public CommonResult<SafetyWorkPermitRespVO> get(@RequestParam("id") Long id) {
        var permit = permitService.getPermit(id);
        permitService.fillPermitRefs(java.util.List.of(permit));
        var resp = BeanUtils.toBean(permit, SafetyWorkPermitRespVO.class);
        SafetyWorkPermitService.fillPermitDisplay(permit, resp);
        return success(resp);
@@ -72,6 +73,7 @@
    @PreAuthorize("@ss.hasPermission('safety:permit:query')")
    public CommonResult<PageResult<SafetyWorkPermitRespVO>> page(@Valid SafetyWorkPermitPageReqVO reqVO) {
        var page = permitService.getPermitPage(reqVO);
        permitService.fillPermitRefs(page.getList());
        var result = BeanUtils.toBean(page, SafetyWorkPermitRespVO.class);
        for (int i = 0; i < page.getList().size(); i++) {
            SafetyWorkPermitService.fillPermitDisplay(page.getList().get(i), result.getList().get(i));
@@ -121,8 +123,9 @@
    @Parameter(name = "permitId", description = "作业票编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:permit:query')")
    public CommonResult<List<SafetyWorkPermitMonitorRespVO>> monitorList(@RequestParam("permitId") Long permitId) {
        return success(BeanUtils.toBean(permitService.getMonitorList(permitId),
                SafetyWorkPermitMonitorRespVO.class));
        var monitors = permitService.getMonitorList(permitId);
        permitService.fillMonitorGuardians(monitors);
        return success(BeanUtils.toBean(monitors, SafetyWorkPermitMonitorRespVO.class));
    }
    @PostMapping("/monitor/create")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardAssignReqVO.java
@@ -22,10 +22,9 @@
    @NotNull(message = "隐患编号不能为空")
    private Long id;
    @Schema(description = "整改责任人", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
    @NotBlank(message = "整改责任人不能为空")
    @Size(max = 50)
    private String assignee;
    @Schema(description = "整改责任人(员工编号,关联 hrm_employee)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "请选择整改责任人")
    private Long assigneeId;
    @Schema(description = "整改期限")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardRecheckReqVO.java
@@ -20,10 +20,9 @@
    @NotNull(message = "隐患编号不能为空")
    private Long id;
    @Schema(description = "复查人", requiredMode = Schema.RequiredMode.REQUIRED, example = "钱七")
    @NotBlank(message = "复查人不能为空")
    @Size(max = 50)
    private String rechecker;
    @Schema(description = "复查人(员工编号,关联 hrm_employee)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "请选择复查人")
    private Long recheckerId;
    @Schema(description = "复查结论:PASSED-通过,REJECTED-不通过", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank(message = "复查结论不能为空")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardRespVO.java
@@ -30,8 +30,12 @@
    private String location;
    private String description;
    private String photoUrls;
    @Schema(description = "上报人编号")
    private Long reporterId;
    private String reporter;
    private LocalDateTime reportTime;
    @Schema(description = "整改责任人编号")
    private Long assigneeId;
    private String assignee;
    private LocalDateTime assignTime;
    private LocalDate rectifyDeadline;
@@ -40,6 +44,8 @@
    private String rectifyDesc;
    private String rectifyPhotoUrls;
    private LocalDateTime rectifyTime;
    @Schema(description = "复查人编号")
    private Long recheckerId;
    private String rechecker;
    private LocalDateTime recheckTime;
    private String recheckResult;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardSaveReqVO.java
@@ -54,10 +54,9 @@
    @Size(max = 2000)
    private String photoUrls;
    @Schema(description = "上报人", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
    @NotBlank(message = "上报人不能为空")
    @Size(max = 50)
    private String reporter;
    @Schema(description = "上报人(员工编号,关联 hrm_employee)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "请选择上报人")
    private Long reporterId;
    @Schema(description = "上报时间")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionPointRespVO.java
@@ -20,12 +20,16 @@
    private String pointType;
    @Schema(description = "点位类型名称")
    private String pointTypeName;
    @Schema(description = "所属车间编号")
    private Long workshopId;
    private String workshopName;
    private String location;
    private String inspectItems;
    private String inspectCycle;
    @Schema(description = "巡检周期名称")
    private String inspectCycleName;
    @Schema(description = "责任人编号")
    private Long ownerId;
    private String ownerName;
    private Integer status;
    @Schema(description = "最近一次巡检时间")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionPointSaveReqVO.java
@@ -30,9 +30,8 @@
    @Pattern(regexp = "HIGH_VOLTAGE_ROOM|UNIT_AREA|DANGER_CHEMICAL|FIRE_AREA|WORKSHOP|OTHER", message = "点位类型不合法")
    private String pointType;
    @Schema(description = "所属车间/区域")
    @Size(max = 100)
    private String workshopName;
    @Schema(description = "所属车间(关联 mes_md_workshop)")
    private Long workshopId;
    @Schema(description = "具体位置")
    @Size(max = 200)
@@ -46,9 +45,8 @@
    @Pattern(regexp = "SHIFT|DAILY|WEEKLY|MONTHLY", message = "巡检周期不合法")
    private String inspectCycle;
    @Schema(description = "责任人")
    @Size(max = 50)
    private String ownerName;
    @Schema(description = "责任人(员工编号,关联 hrm_employee)")
    private Long ownerId;
    @Schema(description = "状态:0-启用,1-停用", example = "0")
    @NotNull(message = "状态不能为空")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionRespVO.java
@@ -21,6 +21,8 @@
    @Schema(description = "点位类型名称")
    private String pointTypeName;
    private LocalDateTime inspectTime;
    @Schema(description = "巡检人编号")
    private Long inspectorId;
    private String inspector;
    private String result;
    @Schema(description = "巡检结果名称")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionSaveReqVO.java
@@ -27,10 +27,9 @@
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime inspectTime;
    @Schema(description = "巡检人", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
    @NotBlank(message = "巡检人不能为空")
    @Size(max = 50)
    private String inspector;
    @Schema(description = "巡检人(员工编号,关联 hrm_employee)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "请选择巡检人")
    private Long inspectorId;
    @Schema(description = "巡检结果", example = "NORMAL")
    @Pattern(regexp = "NORMAL|ABNORMAL", message = "巡检结果只能为正常或异常")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialRespVO.java
@@ -33,6 +33,8 @@
    @Schema(description = "距有效期天数,负数表示已过期")
    private Long expireRemainingDays;
    private Integer validityMonths;
    @Schema(description = "保管人编号")
    private Long managerId;
    private String manager;
    private Integer status;
    private String remark;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialSaveReqVO.java
@@ -62,9 +62,8 @@
    @Min(value = 0, message = "有效期月数不能为负数")
    private Integer validityMonths;
    @Schema(description = "保管人")
    @Size(max = 50)
    private String manager;
    @Schema(description = "保管人(员工编号,关联 hrm_employee)")
    private Long managerId;
    @Schema(description = "状态:0-启用,1-停用", example = "0")
    @NotNull(message = "状态不能为空")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/org/SafetyOrgRespVO.java
@@ -18,6 +18,9 @@
    private Long id;
    private Long parentId;
    private String name;
    @Schema(description = "负责人(员工编号)")
    private Long leaderId;
    @Schema(description = "负责人姓名(快照)")
    private String leaderName;
    private String leaderPhone;
    private String memberNames;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/org/SafetyOrgSaveReqVO.java
@@ -25,13 +25,8 @@
    @Size(max = 100, message = "组织名称长度不能超过 100 å­—")
    private String name;
    @Schema(description = "负责人姓名")
    @Size(max = 50)
    private String leaderName;
    @Schema(description = "负责人电话")
    @Size(max = 30)
    private String leaderPhone;
    @Schema(description = "负责人(员工编号,关联 hrm_employee)")
    private Long leaderId;
    @Schema(description = "成员姓名,逗号分隔")
    @Size(max = 500)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitApproveReqVO.java
@@ -20,10 +20,9 @@
    @NotNull(message = "作业票编号不能为空")
    private Long id;
    @Schema(description = "审批人", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank(message = "审批人不能为空")
    @Size(max = 50)
    private String approver;
    @Schema(description = "审批人(员工编号,关联 hrm_employee)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "请选择审批人")
    private Long approverId;
    @Schema(description = "审批结论:APPROVED-通过,REJECTED-驳回", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank(message = "审批结论不能为空")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitMonitorRespVO.java
@@ -17,6 +17,8 @@
    private Long id;
    private Long permitId;
    private LocalDateTime monitorTime;
    @Schema(description = "监护人编号")
    private Long guardianId;
    private String guardian;
    private String monitorContent;
    private String abnormal;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitMonitorSaveReqVO.java
@@ -28,10 +28,9 @@
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime monitorTime;
    @Schema(description = "监护人", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank(message = "监护人不能为空")
    @Size(max = 50)
    private String guardian;
    @Schema(description = "监护人(员工编号,关联 hrm_employee)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "请选择监护人")
    private Long guardianId;
    @Schema(description = "监护内容与情况")
    @Size(max = 1000)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitRespVO.java
@@ -20,9 +20,13 @@
    @Schema(description = "作业类型名称")
    private String permitTypeName;
    private String title;
    @Schema(description = "作业车间编号")
    private Long workshopId;
    private String workshopName;
    private String location;
    private String workContent;
    @Schema(description = "申请人编号")
    private Long applicantId;
    private String applicant;
    private LocalDateTime applyTime;
    private LocalDateTime planStartTime;
@@ -33,9 +37,13 @@
    @Schema(description = "风险等级名称")
    private String riskLevelName;
    private String safetyMeasures;
    @Schema(description = "审批人编号")
    private Long approverId;
    private String approver;
    private LocalDateTime approveTime;
    private String approveComment;
    @Schema(description = "监护人编号")
    private Long guardianId;
    private String guardian;
    private String status;
    @Schema(description = "状态名称")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitSaveReqVO.java
@@ -29,9 +29,8 @@
    @Size(max = 200)
    private String title;
    @Schema(description = "作业区域/车间")
    @Size(max = 100)
    private String workshopName;
    @Schema(description = "作业车间(关联 mes_md_workshop)")
    private Long workshopId;
    @Schema(description = "作业地点")
    @Size(max = 200)
@@ -41,10 +40,9 @@
    @Size(max = 2000)
    private String workContent;
    @Schema(description = "申请人", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank(message = "申请人不能为空")
    @Size(max = 50)
    private String applicant;
    @Schema(description = "申请人(员工编号,关联 hrm_employee)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "请选择申请人")
    private Long applicantId;
    @Schema(description = "申请时间")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@@ -68,9 +66,8 @@
    @Size(max = 2000)
    private String safetyMeasures;
    @Schema(description = "监护人")
    @Size(max = 50)
    private String guardian;
    @Schema(description = "监护人(员工编号,关联 hrm_employee)")
    private Long guardianId;
    @Schema(description = "作业票附件 URL,逗号分隔")
    @Size(max = 2000)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyAccidentRespVO.java
@@ -26,6 +26,8 @@
    private String levelName;
    private LocalDateTime occurTime;
    private String location;
    @Schema(description = "所属车间编号")
    private Long workshopId;
    private String workshopName;
    private String casualtyDesc;
    private BigDecimal economicLoss;
@@ -33,6 +35,8 @@
    private String description;
    private String causeAnalysis;
    private String measures;
    @Schema(description = "登记人编号")
    private Long reporterId;
    private String reporter;
    private LocalDateTime reportTime;
    private String status;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyAccidentSaveReqVO.java
@@ -42,9 +42,8 @@
    @Size(max = 200)
    private String location;
    @Schema(description = "所属车间/区域")
    @Size(max = 100)
    private String workshopName;
    @Schema(description = "所属车间(关联 mes_md_workshop)")
    private Long workshopId;
    @Schema(description = "伤亡情况")
    @Size(max = 500)
@@ -70,9 +69,8 @@
    @Size(max = 2000)
    private String measures;
    @Schema(description = "登记人")
    @Size(max = 50)
    private String reporter;
    @Schema(description = "登记人(员工编号,关联 hrm_employee)")
    private Long reporterId;
    @Schema(description = "登记时间")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyDrillRespVO.java
@@ -22,6 +22,8 @@
    private String drillTypeName;
    private LocalDate drillDate;
    private String location;
    @Schema(description = "组织人编号")
    private Long organizerId;
    private String organizer;
    private String participants;
    private Integer participantCount;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyDrillSaveReqVO.java
@@ -36,9 +36,8 @@
    @Size(max = 200)
    private String location;
    @Schema(description = "组织人")
    @Size(max = 50)
    private String organizer;
    @Schema(description = "组织人(员工编号,关联 hrm_employee)")
    private Long organizerId;
    @Schema(description = "参加人员姓名,逗号分隔")
    @Size(max = 2000)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyTrainingRespVO.java
@@ -23,6 +23,8 @@
    private String trainingTypeName;
    private LocalDate trainDate;
    private BigDecimal durationHours;
    @Schema(description = "讲师/组织人编号")
    private Long trainerId;
    private String trainer;
    private String location;
    private String participants;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyTrainingSaveReqVO.java
@@ -37,9 +37,8 @@
    @DecimalMin(value = "0", message = "培训学时不能为负数")
    private BigDecimal durationHours;
    @Schema(description = "讲师/组织人")
    @Size(max = 50)
    private String trainer;
    @Schema(description = "讲师/组织人(员工编号,关联 hrm_employee)")
    private Long trainerId;
    @Schema(description = "培训地点")
    @Size(max = 200)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyAccidentDO.java
@@ -33,7 +33,9 @@
    private LocalDateTime occurTime;
    /** å‘生地点 */
    private String location;
    /** æ‰€å±žè½¦é—´/区域 */
    /** æ‰€å±žè½¦é—´ï¼ˆå…³è” mes_md_workshop.id) */
    private Long workshopId;
    /** æ‰€å±žè½¦é—´åç§°ï¼ˆå¿«ç…§ï¼Œç”±è½¦é—´ä¸»æ•°æ®å¸¦å‡ºï¼‰ */
    private String workshopName;
    /** ä¼¤äº¡æƒ…况 */
    private String casualtyDesc;
@@ -47,7 +49,9 @@
    private String causeAnalysis;
    /** å¤„理措施与整改要求 */
    private String measures;
    /** ç™»è®°äºº */
    /** ç™»è®°äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long reporterId;
    /** ç™»è®°äººå§“名(快照,由员工档案带出) */
    private String reporter;
    /** ç™»è®°æ—¶é—´ */
    private LocalDateTime reportTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyDrillDO.java
@@ -28,7 +28,9 @@
    private LocalDate drillDate;
    /** æ¼”练地点 */
    private String location;
    /** ç»„织人 */
    /** ç»„织人(员工编号,关联 hrm_employee.id) */
    private Long organizerId;
    /** ç»„织人姓名(快照,由员工档案带出) */
    private String organizer;
    /** å‚加人员姓名,逗号分隔 */
    private String participants;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyHazardDO.java
@@ -43,11 +43,15 @@
    private String description;
    /** çŽ°åœºç…§ç‰‡ URL,逗号分隔(移动端拍照上传) */
    private String photoUrls;
    /** ä¸ŠæŠ¥äºº */
    /** ä¸ŠæŠ¥äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long reporterId;
    /** ä¸ŠæŠ¥äººå§“名(快照,由员工档案带出) */
    private String reporter;
    /** ä¸ŠæŠ¥æ—¶é—´ */
    private LocalDateTime reportTime;
    /** æ•´æ”¹è´£ä»»äººï¼ˆåˆ†æ´¾ï¼‰ */
    /** æ•´æ”¹è´£ä»»äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long assigneeId;
    /** æ•´æ”¹è´£ä»»äººå§“名(快照,由员工档案带出) */
    private String assignee;
    /** åˆ†æ´¾æ—¶é—´ */
    private LocalDateTime assignTime;
@@ -59,7 +63,9 @@
    private String rectifyPhotoUrls;
    /** æ•´æ”¹å®Œæˆæ—¶é—´ */
    private LocalDateTime rectifyTime;
    /** å¤æŸ¥äºº */
    /** å¤æŸ¥äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long recheckerId;
    /** å¤æŸ¥äººå§“名(快照,由员工档案带出) */
    private String rechecker;
    /** å¤æŸ¥æ—¶é—´ */
    private LocalDateTime recheckTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyInspectionDO.java
@@ -28,7 +28,9 @@
    private String pointType;
    /** å·¡æ£€æ—¶é—´ */
    private LocalDateTime inspectTime;
    /** å·¡æ£€äºº */
    /** å·¡æ£€äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long inspectorId;
    /** å·¡æ£€äººå§“名(快照,由员工档案带出) */
    private String inspector;
    /** å·¡æ£€ç»“果:NORMAL-正常,ABNORMAL-异常 */
    private String result;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyInspectionPointDO.java
@@ -26,7 +26,9 @@
    private String name;
    /** ç‚¹ä½ç±»åž‹ï¼Œè§ SafetyInspectionPointTypeEnum */
    private String pointType;
    /** æ‰€å±žè½¦é—´/区域 */
    /** æ‰€å±žè½¦é—´ï¼ˆå…³è” mes_md_workshop.id) */
    private Long workshopId;
    /** æ‰€å±žè½¦é—´åç§°ï¼ˆå¿«ç…§ï¼Œç”±è½¦é—´ä¸»æ•°æ®å¸¦å‡ºï¼‰ */
    private String workshopName;
    /** å…·ä½“位置 */
    private String location;
@@ -34,7 +36,9 @@
    private String inspectItems;
    /** å·¡æ£€å‘¨æœŸï¼Œè§ SafetyInspectCycleEnum */
    private String inspectCycle;
    /** è´£ä»»äºº */
    /** è´£ä»»äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long ownerId;
    /** è´£ä»»äººå§“名(快照,由员工档案带出) */
    private String ownerName;
    /** çŠ¶æ€ï¼š0-启用,1-停用 */
    private Integer status;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyMaterialDO.java
@@ -43,7 +43,9 @@
    private LocalDate expireDate;
    /** æœ‰æ•ˆæœŸæœˆæ•° */
    private Integer validityMonths;
    /** ä¿ç®¡äºº */
    /** ä¿ç®¡äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long managerId;
    /** ä¿ç®¡äººå§“名(快照,由员工档案带出) */
    private String manager;
    /** çŠ¶æ€ï¼š0-启用,1-停用 */
    private Integer status;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyOrgDO.java
@@ -22,7 +22,9 @@
    private Long parentId;
    /** ç»„织名称,如安全生产委员会、安全管理部 */
    private String name;
    /** è´Ÿè´£äººå§“名 */
    /** è´Ÿè´£äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long leaderId;
    /** è´Ÿè´£äººå§“名(快照,由员工档案带出) */
    private String leaderName;
    /** è´Ÿè´£äººç”µè¯ */
    private String leaderPhone;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyTrainingDO.java
@@ -29,7 +29,9 @@
    private LocalDate trainDate;
    /** åŸ¹è®­å­¦æ—¶ */
    private BigDecimal durationHours;
    /** è®²å¸ˆ/组织人 */
    /** è®²å¸ˆ/组织人(员工编号,关联 hrm_employee.id) */
    private Long trainerId;
    /** è®²å¸ˆ/组织人姓名(快照,由员工档案带出) */
    private String trainer;
    /** åŸ¹è®­åœ°ç‚¹ */
    private String location;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyWorkPermitDO.java
@@ -28,13 +28,17 @@
    private String permitType;
    /** ä½œä¸šåç§° */
    private String title;
    /** ä½œä¸šåŒºåŸŸ/车间 */
    /** ä½œä¸šè½¦é—´ï¼ˆå…³è” mes_md_workshop.id) */
    private Long workshopId;
    /** ä½œä¸šè½¦é—´åç§°ï¼ˆå¿«ç…§ï¼Œç”±è½¦é—´ä¸»æ•°æ®å¸¦å‡ºï¼‰ */
    private String workshopName;
    /** ä½œä¸šåœ°ç‚¹ */
    private String location;
    /** ä½œä¸šå†…容与范围 */
    private String workContent;
    /** ç”³è¯·äºº */
    /** ç”³è¯·äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long applicantId;
    /** ç”³è¯·äººå§“名(快照,由员工档案带出) */
    private String applicant;
    /** ç”³è¯·æ—¶é—´ */
    private LocalDateTime applyTime;
@@ -50,13 +54,17 @@
    private String riskLevel;
    /** å®‰å…¨æŽªæ–½ */
    private String safetyMeasures;
    /** å®¡æ‰¹äºº */
    /** å®¡æ‰¹äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long approverId;
    /** å®¡æ‰¹äººå§“名(快照,由员工档案带出) */
    private String approver;
    /** å®¡æ‰¹æ—¶é—´ */
    private LocalDateTime approveTime;
    /** å®¡æ‰¹æ„è§ */
    private String approveComment;
    /** ç›‘护人 */
    /** ç›‘护人(员工编号,关联 hrm_employee.id) */
    private Long guardianId;
    /** ç›‘护人姓名(快照,由员工档案带出) */
    private String guardian;
    /** çŠ¶æ€ï¼Œè§ SafetyPermitStatusEnum */
    private String status;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyWorkPermitMonitorDO.java
@@ -24,7 +24,9 @@
    private Long permitId;
    /** ç›‘护时间 */
    private LocalDateTime monitorTime;
    /** ç›‘护人 */
    /** ç›‘护人(员工编号,关联 hrm_employee.id) */
    private Long guardianId;
    /** ç›‘护人姓名(快照,由员工档案带出) */
    private String guardian;
    /** ç›‘护内容与情况 */
    private String monitorContent;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/SafetyErrorCodeConstants.java
@@ -42,6 +42,7 @@
    ErrorCode SAFETY_PERMIT_TIME_INVALID = new ErrorCode(1_040_965_002, "计划结束时间必须晚于开始时间");
    ErrorCode SAFETY_PERMIT_NOT_APPROVED = new ErrorCode(1_040_965_003, "作业票未审批通过,不能开始作业");
    ErrorCode SAFETY_PERMIT_GUARDIAN_REQUIRED = new ErrorCode(1_040_965_004, "高危作业必须指定监护人");
    ErrorCode SAFETY_PERMIT_SELF_APPROVE = new ErrorCode(1_040_965_005, "申请人不能审批自己的作业票");
    ErrorCode SAFETY_PERMIT_MONITOR_NOT_EXISTS = new ErrorCode(1_040_965_010, "监护记录不存在");
    // ========== ç‰©èµ„台账 1_040_966_xxx ==========
@@ -55,6 +56,10 @@
    ErrorCode SAFETY_DRILL_NOT_EXISTS = new ErrorCode(1_040_967_010, "演练记录不存在");
    ErrorCode SAFETY_ACCIDENT_NOT_EXISTS = new ErrorCode(1_040_967_020, "事故记录不存在");
    // ========== ä¸»æ•°æ®å¼•用 1_040_969_xxx ==========
    ErrorCode SAFETY_WORKSHOP_NOT_EXISTS = new ErrorCode(1_040_969_000, "车间不存在,请从车间主数据中选择");
    ErrorCode SAFETY_EMPLOYEE_REQUIRED = new ErrorCode(1_040_969_001, "请选择员工");
    // ========== å°è´¦å¯¼å‡º 1_040_968_xxx ==========
    ErrorCode SAFETY_LEDGER_EXPORT_ITEM_UNKNOWN = new ErrorCode(1_040_968_000, "未知的台账导出项");
    ErrorCode SAFETY_LEDGER_DATE_RANGE_INVALID = new ErrorCode(1_040_968_001, "开始日期不能晚于结束日期");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyCertificateService.java
@@ -50,6 +50,8 @@
    private SafetyCertificateMapper certificateMapper;
    @Resource
    private HrmEmployeeApi hrmEmployeeApi;
    @Resource
    private SafetyRefResolver refResolver;
    public Long createCertificate(SafetyCertificateSaveReqVO reqVO) {
        reqVO.setId(null);
@@ -113,9 +115,25 @@
    }
    /**
     * æ‰¹é‡è¡¥å…¨å±•示字段,持证人档案一次性查好,避免逐条查询
     */
    public void fillDisplayFieldsBatch(List<SafetyCertificateDO> certs, List<SafetyCertificateRespVO> resps) {
        var employeeMap = refResolver.employeeMap(certs.stream().map(SafetyCertificateDO::getEmployeeId).toList());
        for (int i = 0; i < certs.size(); i++) {
            fillDisplayFields(certs.get(i), resps.get(i), employeeMap.get(certs.get(i).getEmployeeId()));
        }
    }
    /**
     * å•个证件的展示字段补全(类型名称、状态名称、剩余天数、持证人档案)
     */
    public void fillDisplayFields(SafetyCertificateDO cert, SafetyCertificateRespVO respVO) {
        fillDisplayFields(cert, respVO, cert.getEmployeeId() == null ? null
                : hrmEmployeeApi.getEmployee(cert.getEmployeeId()));
    }
    private void fillDisplayFields(SafetyCertificateDO cert, SafetyCertificateRespVO respVO,
                                   HrmEmployeeSimpleDTO employee) {
        respVO.setCertTypeName(SafetyCertificateTypeEnum.getNameByType(cert.getCertType()));
        String status = resolveStatus(cert.getExpireDate(), cert.getWarnDays(), cert.getStatus());
        respVO.setStatus(status);
@@ -123,8 +141,6 @@
        if (cert.getExpireDate() != null) {
            respVO.setRemainingDays(ChronoUnit.DAYS.between(LocalDate.now(), cert.getExpireDate()));
        }
        HrmEmployeeSimpleDTO employee = cert.getEmployeeId() == null ? null
                : hrmEmployeeApi.getEmployee(cert.getEmployeeId());
        if (employee != null) {
            respVO.setEmployeeName(employee.getName());
            respVO.setEmployeeNo(employee.getEmployeeNo());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyHazardService.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.mes.service.safety;
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.common.util.object.BeanUtils;
@@ -17,6 +18,7 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.SafetyErrorCodeConstants.*;
@@ -37,6 +39,8 @@
    @Resource
    private SafetyHazardMapper hazardMapper;
    @Resource
    private SafetyRefResolver refResolver;
    /**
     * é𐿂£ä¸ŠæŠ¥ï¼ˆå«ç§»åŠ¨ç«¯æ‹ç…§ä¸Šä¼ çš„ photoUrls)
@@ -47,7 +51,8 @@
        SafetyHazardDO hazard = BeanUtils.toBean(reqVO, SafetyHazardDO.class);
        hazard.setCode(hazardMapper.selectNextCode("HZ" + LocalDate.now().format(CODE_DATE)));
        hazard.setTitle(StrUtil.trim(reqVO.getTitle()));
        hazard.setReporter(StrUtil.trim(reqVO.getReporter()));
        hazard.setReporterId(reqVO.getReporterId());
        hazard.setReporter(refResolver.requireEmployee(reqVO.getReporterId(), SAFETY_EMPLOYEE_REQUIRED).getName());
        hazard.setLevel(StrUtil.blankToDefault(reqVO.getLevel(), SafetyHazardLevelEnum.GENERAL.getLevel()));
        hazard.setSource(StrUtil.blankToDefault(reqVO.getSource(), SafetyHazardSourceEnum.SELF_CHECK.getSource()));
        hazard.setReportTime(reqVO.getReportTime() == null ? LocalDateTime.now() : reqVO.getReportTime());
@@ -83,12 +88,14 @@
                && !SafetyHazardStatusEnum.REJECTED.getStatus().equals(hazard.getStatus())) {
            throw exception(SAFETY_HAZARD_STATUS_ERROR);
        }
        if (StrUtil.isBlank(reqVO.getAssignee())) {
        if (reqVO.getAssigneeId() == null) {
            throw exception(SAFETY_HAZARD_ASSIGNEE_REQUIRED);
        }
        var assignee = refResolver.requireEmployee(reqVO.getAssigneeId(), SAFETY_HAZARD_ASSIGNEE_REQUIRED);
        SafetyHazardDO update = new SafetyHazardDO();
        update.setId(hazard.getId());
        update.setAssignee(StrUtil.trim(reqVO.getAssignee()));
        update.setAssigneeId(assignee.getId());
        update.setAssignee(assignee.getName());
        update.setAssignTime(LocalDateTime.now());
        update.setRectifyDeadline(reqVO.getRectifyDeadline());
        update.setStatus(SafetyHazardStatusEnum.RECTIFYING.getStatus());
@@ -128,14 +135,16 @@
        if (!SafetyHazardStatusEnum.RECHECKING.getStatus().equals(hazard.getStatus())) {
            throw exception(SAFETY_HAZARD_STATUS_ERROR);
        }
        String rechecker = StrUtil.trim(reqVO.getRechecker());
        if (StrUtil.isNotBlank(hazard.getAssignee()) && hazard.getAssignee().equals(rechecker)) {
        var rechecker = refResolver.requireEmployee(reqVO.getRecheckerId(), SAFETY_EMPLOYEE_REQUIRED);
        // ç”¨å‘˜å·¥ç¼–号比较,比姓名比较可靠(同名不同人 / æ”¹åéƒ½ä¸ä¼šè¯¯åˆ¤ï¼‰
        if (hazard.getAssigneeId() != null && hazard.getAssigneeId().equals(rechecker.getId())) {
            throw exception(SAFETY_HAZARD_SAME_OPERATOR);
        }
        boolean passed = "PASSED".equals(reqVO.getRecheckResult());
        SafetyHazardDO update = new SafetyHazardDO();
        update.setId(hazard.getId());
        update.setRechecker(rechecker);
        update.setRecheckerId(rechecker.getId());
        update.setRechecker(rechecker.getName());
        update.setRecheckTime(LocalDateTime.now());
        update.setRecheckResult(reqVO.getRecheckResult());
        update.setRecheckRemark(reqVO.getRecheckRemark());
@@ -163,6 +172,26 @@
    }
    /**
     * æ‰¹é‡å›žå¡«ä¸ŠæŠ¥äºº / æ•´æ”¹è´£ä»»äºº / å¤æŸ¥äººçš„æœ€æ–°å§“名
     */
    public void fillPersonNames(List<SafetyHazardDO> hazards) {
        if (CollUtil.isEmpty(hazards)) {
            return;
        }
        var ids = new java.util.ArrayList<Long>(hazards.size() * 3);
        hazards.forEach(h -> { ids.add(h.getReporterId()); ids.add(h.getAssigneeId()); ids.add(h.getRecheckerId()); });
        var employeeMap = refResolver.employeeMap(ids);
        hazards.forEach(hazard -> {
            var reporter = employeeMap.get(hazard.getReporterId());
            if (reporter != null) hazard.setReporter(reporter.getName());
            var assignee = employeeMap.get(hazard.getAssigneeId());
            if (assignee != null) hazard.setAssignee(assignee.getName());
            var rechecker = employeeMap.get(hazard.getRecheckerId());
            if (rechecker != null) hazard.setRechecker(rechecker.getName());
        });
    }
    /**
     * å±•示字段补全(等级/来源/状态名称、超期标记)
     */
    public static void fillHazardDisplay(SafetyHazardDO hazard, SafetyHazardRespVO respVO) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyInspectionService.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.mes.service.safety;
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.common.util.object.BeanUtils;
@@ -35,6 +36,8 @@
    private SafetyInspectionPointMapper pointMapper;
    @Resource
    private SafetyInspectionMapper inspectionMapper;
    @Resource
    private SafetyRefResolver refResolver;
    // ==================== å·¡æ£€ç‚¹ä½ ====================
@@ -48,6 +51,7 @@
        point.setCode(code);
        point.setName(StrUtil.trim(reqVO.getName()));
        point.setInspectCycle(StrUtil.blankToDefault(reqVO.getInspectCycle(), SafetyInspectCycleEnum.DAILY.getCycle()));
        applyPointRefs(point, reqVO.getWorkshopId(), reqVO.getOwnerId());
        pointMapper.insert(point);
        return point.getId();
    }
@@ -62,6 +66,7 @@
        SafetyInspectionPointDO point = BeanUtils.toBean(reqVO, SafetyInspectionPointDO.class);
        point.setCode(code);
        point.setName(StrUtil.trim(reqVO.getName()));
        applyPointRefs(point, reqVO.getWorkshopId(), reqVO.getOwnerId());
        pointMapper.updateById(point);
    }
@@ -83,6 +88,45 @@
     */
    public List<SafetyInspectionPointDO> getEnabledPointList() {
        return pointMapper.selectListByStatus(0);
    }
    /**
     * ç‚¹ä½çš„主数据引用:车间 + è´£ä»»äººï¼Œä»Žä¸»æ•°æ®å¸¦å‡ºåç§°è½å¿«ç…§
     */
    private void applyPointRefs(SafetyInspectionPointDO point, Long workshopId, Long ownerId) {
        point.setWorkshopId(workshopId);
        if (workshopId == null) {
            point.setWorkshopName("");
        } else {
            point.setWorkshopName(refResolver.requireWorkshop(workshopId).getName());
        }
        point.setOwnerId(ownerId);
        if (ownerId == null) {
            point.setOwnerName("");
        } else {
            point.setOwnerName(refResolver.requireEmployee(ownerId, SAFETY_EMPLOYEE_REQUIRED).getName());
        }
    }
    /**
     * æ‰¹é‡å›žå¡«ç‚¹ä½çš„车间名与责任人姓名(用主数据最新值覆盖快照)
     */
    public void fillPointRefs(List<SafetyInspectionPointDO> points) {
        if (CollUtil.isEmpty(points)) {
            return;
        }
        var workshopMap = refResolver.workshopMap(points.stream().map(SafetyInspectionPointDO::getWorkshopId).toList());
        var employeeMap = refResolver.employeeMap(points.stream().map(SafetyInspectionPointDO::getOwnerId).toList());
        points.forEach(point -> {
            var workshop = workshopMap.get(point.getWorkshopId());
            if (workshop != null) {
                point.setWorkshopName(workshop.getName());
            }
            var employee = employeeMap.get(point.getOwnerId());
            if (employee != null) {
                point.setOwnerName(employee.getName());
            }
        });
    }
    public static void fillPointDisplay(SafetyInspectionPointDO point, SafetyInspectionPointRespVO respVO) {
@@ -129,7 +173,8 @@
        inspection.setPointId(point.getId());
        inspection.setPointName(point.getName());
        inspection.setPointType(point.getPointType());
        inspection.setInspector(StrUtil.trim(reqVO.getInspector()));
        inspection.setInspectorId(reqVO.getInspectorId());
        inspection.setInspector(refResolver.requireEmployee(reqVO.getInspectorId(), SAFETY_EMPLOYEE_REQUIRED).getName());
        inspection.setResult(StrUtil.blankToDefault(reqVO.getResult(), "NORMAL"));
        inspection.setInspectTime(reqVO.getInspectTime() == null ? LocalDateTime.now() : reqVO.getInspectTime());
        inspection.setHazardCount(reqVO.getHazardCount() == null ? 0 : reqVO.getHazardCount());
@@ -137,6 +182,22 @@
        return inspection;
    }
    /**
     * æ‰¹é‡å›žå¡«å·¡æ£€äººæœ€æ–°å§“名
     */
    public void fillInspectorNames(List<SafetyInspectionDO> records) {
        if (CollUtil.isEmpty(records)) {
            return;
        }
        var employeeMap = refResolver.employeeMap(records.stream().map(SafetyInspectionDO::getInspectorId).toList());
        records.forEach(record -> {
            var employee = employeeMap.get(record.getInspectorId());
            if (employee != null) {
                record.setInspector(employee.getName());
            }
        });
    }
    /** æœ€è¿‘一次巡检时间(用于点位列表展示) */
    public LocalDateTime getLastInspectTime(Long pointId) {
        SafetyInspectionDO latest = inspectionMapper.selectLatestByPointId(pointId);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyLedgerExportService.java
@@ -129,7 +129,9 @@
        if (start != null && end != null) {
            reqVO.setInspectTime(new java.time.LocalDateTime[]{start.atStartOfDay(), end.atTime(23, 59, 59)});
        }
        List<List<Object>> rows = inspectionService.getInspectionPage(reqVO).getList().stream()
        var inspections = inspectionService.getInspectionPage(reqVO).getList();
        inspectionService.fillInspectorNames(inspections);
        List<List<Object>> rows = inspections.stream()
                .map(i -> Arrays.<Object>asList(
                        dateTime(i.getInspectTime()), str(i.getPointName()),
                        SafetyInspectionPointTypeEnum.getNameByType(i.getPointType()),
@@ -149,7 +151,9 @@
        if (start != null && end != null) {
            reqVO.setReportTime(new java.time.LocalDateTime[]{start.atStartOfDay(), end.atTime(23, 59, 59)});
        }
        List<List<Object>> rows = hazardService.getHazardPage(reqVO).getList().stream()
        var hazards = hazardService.getHazardPage(reqVO).getList();
        hazardService.fillPersonNames(hazards);
        List<List<Object>> rows = hazards.stream()
                .map(h -> Arrays.<Object>asList(
                        str(h.getCode()), str(h.getTitle()),
                        SafetyHazardLevelEnum.getNameByLevel(h.getLevel()),
@@ -174,7 +178,9 @@
        if (start != null && end != null) {
            reqVO.setPlanStartTime(new java.time.LocalDateTime[]{start.atStartOfDay(), end.atTime(23, 59, 59)});
        }
        List<List<Object>> rows = permitService.getPermitPage(reqVO).getList().stream()
        var permits = permitService.getPermitPage(reqVO).getList();
        permitService.fillPermitRefs(permits);
        List<List<Object>> rows = permits.stream()
                .map(p -> Arrays.<Object>asList(
                        str(p.getCode()), SafetyPermitTypeEnum.getNameByType(p.getPermitType()),
                        str(p.getTitle()), str(p.getWorkshopName()), str(p.getLocation()),
@@ -194,7 +200,9 @@
        reqVO.setPageSize(5_000);
        List<List<Object>> rows = new ArrayList<>();
        for (SafetyWorkPermitDO permit : permitService.getPermitPage(reqVO).getList()) {
            for (SafetyWorkPermitMonitorDO monitor : permitService.getMonitorList(permit.getId())) {
            var monitors = permitService.getMonitorList(permit.getId());
            permitService.fillMonitorGuardians(monitors);
            for (SafetyWorkPermitMonitorDO monitor : monitors) {
                rows.add(Arrays.asList(str(permit.getCode()), dateTime(monitor.getMonitorTime()),
                        str(monitor.getGuardian()), str(monitor.getMonitorContent()), str(monitor.getAbnormal())));
            }
@@ -208,7 +216,9 @@
        var reqVO = new cn.iocoder.yudao.module.mes.controller.admin.safety.vo.material.SafetyMaterialPageReqVO();
        reqVO.setPageNo(1);
        reqVO.setPageSize(50_000);
        List<List<Object>> rows = materialService.getMaterialPage(reqVO).getList().stream()
        var materials = materialService.getMaterialPage(reqVO).getList();
        materialService.fillManagers(materials);
        List<List<Object>> rows = materials.stream()
                .map(m -> Arrays.<Object>asList(
                        str(m.getCode()), str(m.getName()),
                        SafetyMaterialCategoryEnum.getNameByCategory(m.getCategory()),
@@ -242,7 +252,9 @@
    private Sheet trainingSheet(LocalDate start, LocalDate end) {
        List<String> headers = List.of("培训日期", "培训主题", "培训类型", "学时", "讲师/组织人",
                "培训地点", "参加人数", "培训内容", "考核情况");
        List<List<Object>> rows = trainingService.listTrainingBetween(start, end).stream()
        var trainings = trainingService.listTrainingBetween(start, end);
        trainingService.fillTrainers(trainings);
        List<List<Object>> rows = trainings.stream()
                .map(t -> Arrays.<Object>asList(
                        date(t.getTrainDate()), str(t.getTitle()),
                        SafetyTrainingTypeEnum.getNameByType(t.getTrainingType()),
@@ -256,7 +268,9 @@
    private Sheet drillSheet(LocalDate start, LocalDate end) {
        List<String> headers = List.of("演练日期", "演练名称", "演练类型", "地点", "组织人",
                "参加人数", "时长(分钟)", "演练过程", "总结与改进");
        List<List<Object>> rows = trainingService.listDrillBetween(start, end).stream()
        var drills = trainingService.listDrillBetween(start, end);
        trainingService.fillOrganizers(drills);
        List<List<Object>> rows = drills.stream()
                .map(d -> Arrays.<Object>asList(
                        date(d.getDrillDate()), str(d.getTitle()),
                        SafetyDrillTypeEnum.getNameByType(d.getDrillType()), str(d.getLocation()),
@@ -270,7 +284,9 @@
    private Sheet accidentSheet(LocalDate start, LocalDate end) {
        List<String> headers = List.of("事故编号", "事故名称", "事故类型", "等级", "发生时间", "发生地点",
                "伤亡情况", "经济损失(元)", "事故经过", "原因分析", "处理措施", "状态");
        List<List<Object>> rows = trainingService.listAccidentBetween(start, end).stream()
        var accidents = trainingService.listAccidentBetween(start, end);
        trainingService.fillAccidentRefs(accidents);
        List<List<Object>> rows = accidents.stream()
                .map(a -> Arrays.<Object>asList(
                        str(a.getCode()), str(a.getTitle()),
                        SafetyAccidentTypeEnum.getNameByType(a.getAccidentType()),
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyMaterialService.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.mes.service.safety;
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.common.util.object.BeanUtils;
@@ -36,6 +37,8 @@
    @Resource
    private SafetyMaterialMapper materialMapper;
    @Resource
    private SafetyRefResolver refResolver;
    @Resource
    private SafetyMaterialStockMapper stockMapper;
    // ==================== ç‰©èµ„台账 ====================
@@ -50,6 +53,7 @@
        material.setCode(code);
        material.setName(StrUtil.trim(reqVO.getName()));
        material.setUnit(StrUtil.blankToDefault(reqVO.getUnit(), "个"));
        applyManager(material, reqVO.getManagerId());
        material.setStockQty(reqVO.getStockQty() == null ? BigDecimal.ZERO : reqVO.getStockQty());
        material.setSafeStock(reqVO.getSafeStock() == null ? BigDecimal.ZERO : reqVO.getSafeStock());
        materialMapper.insert(material);
@@ -66,6 +70,7 @@
        SafetyMaterialDO material = BeanUtils.toBean(reqVO, SafetyMaterialDO.class);
        material.setCode(code);
        material.setName(StrUtil.trim(reqVO.getName()));
        applyManager(material, reqVO.getManagerId());
        // åº“存数量只能通过出入库单变动,禁止直接编辑
        material.setStockQty(null);
        materialMapper.updateById(material);
@@ -84,6 +89,29 @@
        return materialMapper.selectPage(reqVO);
    }
    /**
     * ä¿ç®¡äººï¼šä»Žå‘˜å·¥æ¡£æ¡ˆå¸¦å‡ºå§“名落快照
     */
    private void applyManager(SafetyMaterialDO material, Long managerId) {
        material.setManagerId(managerId);
        material.setManager(managerId == null ? ""
                : refResolver.requireEmployee(managerId, SAFETY_EMPLOYEE_REQUIRED).getName());
    }
    /**
     * æ‰¹é‡å›žå¡«ä¿ç®¡äººæœ€æ–°å§“名
     */
    public void fillManagers(List<SafetyMaterialDO> materials) {
        if (CollUtil.isEmpty(materials)) {
            return;
        }
        var employeeMap = refResolver.employeeMap(materials.stream().map(SafetyMaterialDO::getManagerId).toList());
        materials.forEach(material -> {
            var employee = employeeMap.get(material.getManagerId());
            if (employee != null) material.setManager(employee.getName());
        });
    }
    public static void fillMaterialDisplay(SafetyMaterialDO material, SafetyMaterialRespVO respVO) {
        respVO.setCategoryName(SafetyMaterialCategoryEnum.getNameByCategory(material.getCategory()));
        BigDecimal stock = material.getStockQty() == null ? BigDecimal.ZERO : material.getStockQty();
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyOrgService.java
@@ -29,12 +29,15 @@
    @Resource
    private SafetyOrgMapper safetyOrgMapper;
    @Resource
    private SafetyRefResolver refResolver;
    public Long createOrg(SafetyOrgSaveReqVO reqVO) {
        reqVO.setId(null);
        validateParent(reqVO.getParentId(), null);
        SafetyOrgDO org = BeanUtils.toBean(reqVO, SafetyOrgDO.class);
        org.setParentId(reqVO.getParentId() == null ? 0L : reqVO.getParentId());
        applyLeader(org, reqVO.getLeaderId());
        safetyOrgMapper.insert(org);
        return org.getId();
    }
@@ -44,7 +47,22 @@
        validateParent(reqVO.getParentId(), reqVO.getId());
        SafetyOrgDO org = BeanUtils.toBean(reqVO, SafetyOrgDO.class);
        org.setParentId(reqVO.getParentId() == null ? 0L : reqVO.getParentId());
        applyLeader(org, reqVO.getLeaderId());
        safetyOrgMapper.updateById(org);
    }
    /**
     * è´Ÿè´£äººï¼šä»Žå‘˜å·¥æ¡£æ¡ˆå¸¦å‡ºå§“名与电话并落快照
     */
    private void applyLeader(SafetyOrgDO org, Long leaderId) {
        org.setLeaderId(leaderId);
        if (leaderId == null) {
            org.setLeaderName("");
            return;
        }
        var employee = refResolver.requireEmployee(leaderId, SAFETY_EMPLOYEE_REQUIRED);
        org.setLeaderName(employee.getName());
        org.setLeaderPhone(employee.getPhone());
    }
    public void deleteOrg(Long id) {
@@ -60,11 +78,27 @@
    }
    /**
     * å–负责人最新档案(供单条查询回填用)
     */
    public cn.iocoder.yudao.module.hrm.api.employee.dto.HrmEmployeeSimpleDTO getLeader(Long leaderId) {
        return refResolver.findEmployee(leaderId);
    }
    /**
     * æŸ¥è¯¢ç»„织树
     */
    public List<SafetyOrgRespVO> getOrgTree() {
        List<SafetyOrgDO> all = safetyOrgMapper.selectListOrdered();
        List<SafetyOrgRespVO> nodes = BeanUtils.toBean(all, SafetyOrgRespVO.class);
        // è´Ÿè´£äººç”¨å‘˜å·¥æ¡£æ¡ˆæœ€æ–°å§“名覆盖快照,员工改名/调岗后树也同步
        var employeeMap = refResolver.employeeMap(
                all.stream().map(SafetyOrgDO::getLeaderId).toList());
        nodes.forEach(node -> {
            var employee = employeeMap.get(node.getLeaderId());
            if (employee != null) {
                node.setLeaderName(employee.getName());
            }
        });
        Map<Long, List<SafetyOrgRespVO>> grouped = nodes.stream()
                .collect(Collectors.groupingBy(SafetyOrgRespVO::getParentId, Collectors.toList()));
        nodes.forEach(node -> node.setChildren(grouped.getOrDefault(node.getId(), new ArrayList<>())));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyRefResolver.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,117 @@
package cn.iocoder.yudao.module.mes.service.safety;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.hrm.api.employee.HrmEmployeeApi;
import cn.iocoder.yudao.module.hrm.api.employee.dto.HrmEmployeeSimpleDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkshopDO;
import cn.iocoder.yudao.module.mes.dal.mysql.md.workstation.MesMdWorkshopMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.SafetyErrorCodeConstants.*;
/**
 * å®‰å…¨ç”Ÿäº§ - ä¸»æ•°æ®å¼•用解析器
 *
 * <p>模块内所有「人员」与「车间」字段统一走这里解析,避免每个 Service é‡å¤å†™ä¸€éã€‚</p>
 *
 * <p>统一约定:业务表存 {@code xxxId} å…³è”主数据,同时保留 {@code xxxName} ä½œä¸ºå¿«ç…§ã€‚
 * ä¿å­˜æ—¶ä»Žä¸»æ•°æ®æŠŠåç§°å¸¦å‡ºå†™å¿«ç…§ï¼›æŸ¥è¯¢æ—¶ç”¨ä¸»æ•°æ®æœ€æ–°å€¼è¦†ç›–快照,
 * è¿™æ ·å‘˜å·¥è°ƒå²—、车间改名后台账会自动跟着变,而主数据被删时老记录仍能靠快照展示。</p>
 *
 * @author è¶…级管理员
 */
@Component
public class SafetyRefResolver {
    @Resource
    private HrmEmployeeApi hrmEmployeeApi;
    @Resource
    private MesMdWorkshopMapper workshopMapper;
    // ==================== å‘˜å·¥ ====================
    /**
     * æŒ‰å‘˜å·¥ç¼–号取员工,必须存在
     *
     * @throws cn.iocoder.yudao.framework.common.exception.ServiceException å‘˜å·¥ä¸å­˜åœ¨æ—¶æŠ›å‡º
     */
    public HrmEmployeeSimpleDTO requireEmployee(Long employeeId, cn.iocoder.yudao.framework.common.exception.ErrorCode errorCode) {
        if (employeeId == null) {
            throw exception(errorCode);
        }
        HrmEmployeeSimpleDTO employee = hrmEmployeeApi.getEmployee(employeeId);
        if (employee == null) {
            throw exception(errorCode);
        }
        return employee;
    }
    /**
     * å¯ç©ºçš„员工解析:id ä¸ºç©ºè¿”回 null,不校验存在性(用于非必填字段)
     */
    public HrmEmployeeSimpleDTO findEmployee(Long employeeId) {
        return employeeId == null ? null : hrmEmployeeApi.getEmployee(employeeId);
    }
    /**
     * æ‰¹é‡å–员工,返回 id -> å‘˜å·¥ çš„ Map
     */
    public Map<Long, HrmEmployeeSimpleDTO> employeeMap(Collection<Long> employeeIds) {
        if (CollUtil.isEmpty(employeeIds)) {
            return Map.of();
        }
        List<Long> ids = employeeIds.stream().filter(Objects::nonNull).distinct().toList();
        if (ids.isEmpty()) {
            return Map.of();
        }
        return hrmEmployeeApi.getEmployeeList(ids).stream()
                .collect(Collectors.toMap(HrmEmployeeSimpleDTO::getId, e -> e, (a, b) -> a));
    }
    // ==================== è½¦é—´ ====================
    /**
     * æŒ‰è½¦é—´ç¼–号取车间,必须存在
     */
    public MesMdWorkshopDO requireWorkshop(Long workshopId) {
        if (workshopId == null) {
            throw exception(SAFETY_WORKSHOP_NOT_EXISTS);
        }
        MesMdWorkshopDO workshop = workshopMapper.selectById(workshopId);
        if (workshop == null) {
            throw exception(SAFETY_WORKSHOP_NOT_EXISTS);
        }
        return workshop;
    }
    /**
     * å¯ç©ºçš„车间解析
     */
    public MesMdWorkshopDO findWorkshop(Long workshopId) {
        return workshopId == null ? null : workshopMapper.selectById(workshopId);
    }
    /**
     * æ‰¹é‡å–车间,返回 id -> è½¦é—´ çš„ Map
     */
    public Map<Long, MesMdWorkshopDO> workshopMap(Collection<Long> workshopIds) {
        if (CollUtil.isEmpty(workshopIds)) {
            return Map.of();
        }
        List<Long> ids = workshopIds.stream().filter(Objects::nonNull).distinct().toList();
        if (ids.isEmpty()) {
            return Map.of();
        }
        return workshopMapper.selectBatchIds(ids).stream()
                .collect(Collectors.toMap(MesMdWorkshopDO::getId, w -> w, (a, b) -> a));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyTrainingService.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.mes.service.safety;
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.common.util.object.BeanUtils;
@@ -39,6 +40,8 @@
    @Resource
    private SafetyTrainingMapper trainingMapper;
    @Resource
    private SafetyRefResolver refResolver;
    @Resource
    private SafetyDrillMapper drillMapper;
    @Resource
    private SafetyAccidentMapper accidentMapper;
@@ -50,6 +53,9 @@
        SafetyTrainingDO training = BeanUtils.toBean(reqVO, SafetyTrainingDO.class);
        training.setTitle(StrUtil.trim(reqVO.getTitle()));
        training.setTrainingType(StrUtil.blankToDefault(reqVO.getTrainingType(), SafetyTrainingTypeEnum.SAFETY.getType()));
        training.setTrainerId(reqVO.getTrainerId());
        training.setTrainer(reqVO.getTrainerId() == null ? ""
                : refResolver.requireEmployee(reqVO.getTrainerId(), SAFETY_EMPLOYEE_REQUIRED).getName());
        training.setParticipantCount(resolveCount(reqVO.getParticipantCount(), reqVO.getParticipants()));
        training.setRemark(reqVO.getRemark() == null ? "" : reqVO.getRemark().trim());
        trainingMapper.insert(training);
@@ -77,6 +83,51 @@
        return trainingMapper.selectPage(reqVO);
    }
    /**
     * äº‹æ•…的主数据引用:所属车间 + ç™»è®°äºº
     */
    private void applyAccidentRefs(SafetyAccidentDO accident, SafetyAccidentSaveReqVO reqVO) {
        accident.setWorkshopId(reqVO.getWorkshopId());
        accident.setWorkshopName(reqVO.getWorkshopId() == null ? ""
                : refResolver.requireWorkshop(reqVO.getWorkshopId()).getName());
        accident.setReporterId(reqVO.getReporterId());
        accident.setReporter(reqVO.getReporterId() == null ? ""
                : refResolver.requireEmployee(reqVO.getReporterId(), SAFETY_EMPLOYEE_REQUIRED).getName());
    }
    /** æ‰¹é‡å›žå¡«åŸ¹è®­è®²å¸ˆæœ€æ–°å§“名 */
    public void fillTrainers(List<SafetyTrainingDO> trainings) {
        if (CollUtil.isEmpty(trainings)) return;
        var employeeMap = refResolver.employeeMap(trainings.stream().map(SafetyTrainingDO::getTrainerId).toList());
        trainings.forEach(t -> {
            var e = employeeMap.get(t.getTrainerId());
            if (e != null) t.setTrainer(e.getName());
        });
    }
    /** æ‰¹é‡å›žå¡«æ¼”练组织人最新姓名 */
    public void fillOrganizers(List<SafetyDrillDO> drills) {
        if (CollUtil.isEmpty(drills)) return;
        var employeeMap = refResolver.employeeMap(drills.stream().map(SafetyDrillDO::getOrganizerId).toList());
        drills.forEach(d -> {
            var e = employeeMap.get(d.getOrganizerId());
            if (e != null) d.setOrganizer(e.getName());
        });
    }
    /** æ‰¹é‡å›žå¡«äº‹æ•…的车间名与登记人姓名 */
    public void fillAccidentRefs(List<SafetyAccidentDO> accidents) {
        if (CollUtil.isEmpty(accidents)) return;
        var workshopMap = refResolver.workshopMap(accidents.stream().map(SafetyAccidentDO::getWorkshopId).toList());
        var employeeMap = refResolver.employeeMap(accidents.stream().map(SafetyAccidentDO::getReporterId).toList());
        accidents.forEach(a -> {
            var w = workshopMap.get(a.getWorkshopId());
            if (w != null) a.setWorkshopName(w.getName());
            var e = employeeMap.get(a.getReporterId());
            if (e != null) a.setReporter(e.getName());
        });
    }
    public static void fillTrainingDisplay(SafetyTrainingDO training, SafetyTrainingRespVO respVO) {
        respVO.setTrainingTypeName(SafetyTrainingTypeEnum.getNameByType(training.getTrainingType()));
    }
@@ -88,6 +139,9 @@
        SafetyDrillDO drill = BeanUtils.toBean(reqVO, SafetyDrillDO.class);
        drill.setTitle(StrUtil.trim(reqVO.getTitle()));
        drill.setDrillType(StrUtil.blankToDefault(reqVO.getDrillType(), SafetyDrillTypeEnum.FIRE.getType()));
        drill.setOrganizerId(reqVO.getOrganizerId());
        drill.setOrganizer(reqVO.getOrganizerId() == null ? ""
                : refResolver.requireEmployee(reqVO.getOrganizerId(), SAFETY_EMPLOYEE_REQUIRED).getName());
        drill.setParticipantCount(resolveCount(reqVO.getParticipantCount(), reqVO.getParticipants()));
        drill.setRemark(reqVO.getRemark() == null ? "" : reqVO.getRemark().trim());
        drillMapper.insert(drill);
@@ -128,6 +182,7 @@
        accident.setTitle(StrUtil.trim(reqVO.getTitle()));
        accident.setLevel(StrUtil.blankToDefault(reqVO.getLevel(), SafetyAccidentLevelEnum.GENERAL.getLevel()));
        accident.setStatus(StrUtil.blankToDefault(reqVO.getStatus(), SafetyAccidentStatusEnum.REPORTED.getStatus()));
        applyAccidentRefs(accident, reqVO);
        accident.setReportTime(reqVO.getReportTime() == null ? LocalDateTime.now() : reqVO.getReportTime());
        accident.setRemark(reqVO.getRemark() == null ? "" : reqVO.getRemark().trim());
        accidentMapper.insert(accident);
@@ -138,6 +193,7 @@
        validateAccidentExists(reqVO.getId());
        SafetyAccidentDO accident = BeanUtils.toBean(reqVO, SafetyAccidentDO.class);
        accident.setCode(null);
        applyAccidentRefs(accident, reqVO);
        accidentMapper.updateById(accident);
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyWorkPermitService.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.mes.service.safety;
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.common.util.object.BeanUtils;
@@ -41,6 +42,8 @@
    @Resource
    private SafetyWorkPermitMapper permitMapper;
    @Resource
    private SafetyRefResolver refResolver;
    @Resource
    private SafetyWorkPermitMonitorMapper monitorMapper;
    /**
@@ -53,7 +56,7 @@
        SafetyWorkPermitDO permit = BeanUtils.toBean(reqVO, SafetyWorkPermitDO.class);
        permit.setCode(permitMapper.selectNextCode("WP" + LocalDate.now().format(CODE_DATE)));
        permit.setTitle(StrUtil.trim(reqVO.getTitle()));
        permit.setApplicant(StrUtil.trim(reqVO.getApplicant()));
        applyPermitRefs(permit, reqVO);
        permit.setApplyTime(reqVO.getApplyTime() == null ? LocalDateTime.now() : reqVO.getApplyTime());
        permit.setRiskLevel(StrUtil.blankToDefault(reqVO.getRiskLevel(), SafetyRiskLevelEnum.NORMAL.getLevel()));
        permit.setStatus(SafetyPermitStatusEnum.PENDING.getStatus());
@@ -72,7 +75,23 @@
        SafetyWorkPermitDO update = BeanUtils.toBean(reqVO, SafetyWorkPermitDO.class);
        update.setCode(null);
        update.setStatus(null);
        applyPermitRefs(update, reqVO);
        permitMapper.updateById(update);
    }
    /**
     * ä½œä¸šç¥¨çš„主数据引用:作业车间 + ç”³è¯·äºº + ç›‘护人,从主数据带出名称落快照
     */
    private void applyPermitRefs(SafetyWorkPermitDO permit, SafetyWorkPermitSaveReqVO reqVO) {
        permit.setWorkshopId(reqVO.getWorkshopId());
        permit.setWorkshopName(reqVO.getWorkshopId() == null ? ""
                : refResolver.requireWorkshop(reqVO.getWorkshopId()).getName());
        var applicant = refResolver.requireEmployee(reqVO.getApplicantId(), SAFETY_EMPLOYEE_REQUIRED);
        permit.setApplicantId(applicant.getId());
        permit.setApplicant(applicant.getName());
        permit.setGuardianId(reqVO.getGuardianId());
        permit.setGuardian(reqVO.getGuardianId() == null ? ""
                : refResolver.requireEmployee(reqVO.getGuardianId(), SAFETY_EMPLOYEE_REQUIRED).getName());
    }
    /**
@@ -85,19 +104,20 @@
    public void approvePermit(SafetyWorkPermitApproveReqVO reqVO) {
        SafetyWorkPermitDO permit = validatePermitExists(reqVO.getId());
        ensureStatus(permit, SafetyPermitStatusEnum.PENDING);
        String approver = StrUtil.trim(reqVO.getApprover());
        // ç”³è¯·äººä¸èƒ½å®¡æ‰¹è‡ªå·±çš„作业票
        if (approver.equals(permit.getApplicant())) {
            throw exception(SAFETY_PERMIT_STATUS_ERROR);
        var approver = refResolver.requireEmployee(reqVO.getApproverId(), SAFETY_EMPLOYEE_REQUIRED);
        // ç”³è¯·äººä¸èƒ½å®¡æ‰¹è‡ªå·±çš„作业票(按员工编号比较,避免同名误判)
        if (permit.getApplicantId() != null && permit.getApplicantId().equals(approver.getId())) {
            throw exception(SAFETY_PERMIT_SELF_APPROVE);
        }
        boolean approved = "APPROVED".equals(reqVO.getDecision());
        if (approved && SafetyPermitTypeEnum.requiresGuardian(permit.getPermitType())
                && StrUtil.isBlank(permit.getGuardian())) {
                && permit.getGuardianId() == null) {
            throw exception(SAFETY_PERMIT_GUARDIAN_REQUIRED);
        }
        SafetyWorkPermitDO update = new SafetyWorkPermitDO();
        update.setId(permit.getId());
        update.setApprover(approver);
        update.setApproverId(approver.getId());
        update.setApprover(approver.getName());
        update.setApproveTime(LocalDateTime.now());
        update.setApproveComment(reqVO.getApproveComment());
        update.setStatus(approved ? SafetyPermitStatusEnum.APPROVED.getStatus()
@@ -173,7 +193,9 @@
        }
        SafetyWorkPermitMonitorDO monitor = BeanUtils.toBean(reqVO, SafetyWorkPermitMonitorDO.class);
        monitor.setId(null);
        monitor.setGuardian(StrUtil.trim(reqVO.getGuardian()));
        var guardian = refResolver.requireEmployee(reqVO.getGuardianId(), SAFETY_EMPLOYEE_REQUIRED);
        monitor.setGuardianId(guardian.getId());
        monitor.setGuardian(guardian.getName());
        monitor.setMonitorTime(reqVO.getMonitorTime() == null ? LocalDateTime.now() : reqVO.getMonitorTime());
        monitorMapper.insert(monitor);
        return monitor.getId();
@@ -192,6 +214,44 @@
    // ==================== å±•示字段 ====================
    /**
     * æ‰¹é‡å›žå¡«ä½œä¸šç¥¨çš„车间名与三个人员姓名(申请人/审批人/监护人)
     */
    public void fillPermitRefs(List<SafetyWorkPermitDO> permits) {
        if (CollUtil.isEmpty(permits)) {
            return;
        }
        var workshopMap = refResolver.workshopMap(permits.stream().map(SafetyWorkPermitDO::getWorkshopId).toList());
        var ids = new java.util.ArrayList<Long>(permits.size() * 3);
        permits.forEach(p -> { ids.add(p.getApplicantId()); ids.add(p.getApproverId()); ids.add(p.getGuardianId()); });
        var employeeMap = refResolver.employeeMap(ids);
        permits.forEach(permit -> {
            var workshop = workshopMap.get(permit.getWorkshopId());
            if (workshop != null) permit.setWorkshopName(workshop.getName());
            var applicant = employeeMap.get(permit.getApplicantId());
            if (applicant != null) permit.setApplicant(applicant.getName());
            var approver = employeeMap.get(permit.getApproverId());
            if (approver != null) permit.setApprover(approver.getName());
            var guardian = employeeMap.get(permit.getGuardianId());
            if (guardian != null) permit.setGuardian(guardian.getName());
        });
    }
    /**
     * æ‰¹é‡å›žå¡«ç›‘护记录的监护人最新姓名
     */
    public void fillMonitorGuardians(List<SafetyWorkPermitMonitorDO> monitors) {
        if (CollUtil.isEmpty(monitors)) {
            return;
        }
        var employeeMap = refResolver.employeeMap(
                monitors.stream().map(SafetyWorkPermitMonitorDO::getGuardianId).toList());
        monitors.forEach(monitor -> {
            var employee = employeeMap.get(monitor.getGuardianId());
            if (employee != null) monitor.setGuardian(employee.getName());
        });
    }
    public static void fillPermitDisplay(SafetyWorkPermitDO permit, SafetyWorkPermitRespVO respVO) {
        respVO.setPermitTypeName(SafetyPermitTypeEnum.getNameByType(permit.getPermitType()));
        respVO.setRiskLevelName(SafetyRiskLevelEnum.getNameByLevel(permit.getRiskLevel()));