lead
7 天以前 e2339db8255e350596310ab27bebe5b4a014d5aa
feat(safety): 从长辉分支移植安全生产模块

来源:dev_新疆_长辉 分支(安全生产一期~七期),整体覆盖康意版本。

后端(101 个 Java 文件):
- controller/admin/safety:组织架构、特种作业证、安全巡检(点位/记录)、隐患闭环、
高危作业票(含监护)、应急物资(台账/出入库)、培训演练、事故登记、标准化台账、
附件上传,共 9 个 Controller
- dal/dataobject/safety + dal/mysql/safety:12 个 DO 与对应 Mapper
- service/safety:11 个 Service,含 SafetyRefResolver 统一解析员工/车间主数据
- enums/safety + SafetyErrorCodeConstants:错误码区间 1_040_961_xxx ~ 1_040_968_xxx

依赖:
- yudao-module-mes/pom.xml 增加 yudao-module-hrm-api(按员工档案解析持证人/负责人)

数据库(sql/mysql/):
- safety_module_tables_20261029.sql:12 张安全表
- safety_certificate_employee_20261029.sql:证件持证人关联员工档案
- safety_master_data_link_20261029.sql:人员/车间字段改为主数据关联
- safety_fix_code_index_20261029.sql:去掉 (code, deleted) 唯一键,改普通索引
- safety_module_menu_20261029.sql:76 条菜单与权限(1075600~1075678)

移植适配:
- 康意库原有一套无数据表支撑、无角色授权的旧菜单(1075502~1075512),已在库中清理
- 修复 SafetyRefResolver / SafetyCertificateService 空集合返回 Map.of() 导致
以 null 作为 key 查询时抛 NPE(SafetyOrgService 组织树、证件列表直接 500)

验证:
- 安全生产模块 70 个接口全量探测,500 数量为 0
- 组织架构新增/查询链路实测通过(测试数据已清理)
已添加106个文件
已修改1个文件
7631 ■■■■■ 文件已修改
sql/mysql/safety_certificate_employee_20261029.sql 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/safety_fix_code_index_20261029.sql 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/safety_master_data_link_20261029.sql 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/safety_module_menu_20261029.sql 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/safety_module_tables_20261029.sql 343 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyCertificateController.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyFileController.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyHazardController.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyInspectionController.java 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyLedgerController.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyMaterialController.java 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyOrgController.java 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyTrainingController.java 187 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyWorkPermitController.java 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/cert/SafetyCertificatePageReqVO.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/cert/SafetyCertificateRespVO.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/cert/SafetyCertificateSaveReqVO.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardAssignReqVO.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardPageReqVO.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardRecheckReqVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardRectifyReqVO.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardRespVO.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardSaveReqVO.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionPageReqVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionPointPageReqVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionPointRespVO.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionPointSaveReqVO.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionRespVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionSaveReqVO.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/ledger/SafetyLedgerExportReqVO.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/ledger/SafetyOverviewRespVO.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialPageReqVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialRespVO.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialSaveReqVO.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialStockPageReqVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialStockRespVO.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialStockSaveReqVO.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/org/SafetyOrgRespVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/org/SafetyOrgSaveReqVO.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitApproveReqVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitMonitorRespVO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitMonitorSaveReqVO.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitPageReqVO.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitRespVO.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitSaveReqVO.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyAccidentPageReqVO.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyAccidentRespVO.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyAccidentSaveReqVO.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyDrillPageReqVO.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyDrillRespVO.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyDrillSaveReqVO.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyTrainingPageReqVO.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyTrainingRespVO.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyTrainingSaveReqVO.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyAccidentDO.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyCertificateDO.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyDrillDO.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyHazardDO.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyInspectionDO.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyInspectionPointDO.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyMaterialDO.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyMaterialStockDO.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyOrgDO.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyTrainingDO.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyWorkPermitDO.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyWorkPermitMonitorDO.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyAccidentMapper.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyCertificateMapper.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyDrillMapper.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyHazardMapper.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyInspectionMapper.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyInspectionPointMapper.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyMaterialMapper.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyMaterialStockMapper.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyOrgMapper.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyTrainingMapper.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyWorkPermitMapper.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyWorkPermitMonitorMapper.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/SafetyErrorCodeConstants.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyAccidentLevelEnum.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyAccidentStatusEnum.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyAccidentTypeEnum.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyCertificateStatusEnum.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyCertificateTypeEnum.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyDrillTypeEnum.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyHazardLevelEnum.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyHazardSourceEnum.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyHazardStatusEnum.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyInspectCycleEnum.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyInspectionPointTypeEnum.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyMaterialCategoryEnum.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyPermitStatusEnum.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyPermitTypeEnum.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyRiskLevelEnum.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyTrainingTypeEnum.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyCertificateService.java 233 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyFileService.java 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyHazardService.java 220 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyInspectionService.java 223 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyLedgerExportService.java 334 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyMaterialService.java 192 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyOrgService.java 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyOverviewService.java 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyRefResolver.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyTrainingService.java 273 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyWorkPermitService.java 286 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/safety_certificate_employee_20261029.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
-- ============================================================
-- å®‰å…¨ç”Ÿäº§ - ç‰¹ç§ä½œä¸šè¯æŒè¯äººæ”¹ä¸ºå…³è”员工档案
-- ç”Ÿæˆæ—¶é—´ï¼š2026-10-29
-- èƒŒæ™¯ï¼šåŽŸè®¾è®¡æŠŠæŒè¯äººåšæˆçº¯æ–‡æœ¬å§“åï¼ˆemployee_name / employee_no / dept_name),
--       æ— æ³•与人力资源的员工档案、系统用户打通。
-- åšæ³•:新增 employee_id å…³è” hrm_employee.id,姓名/工号/部门改为员工档案带出;
--       æ—§æ–‡æœ¬å­—段保留为快照,便于员工档案变更后仍可追溯开证时的人事信息。
-- è¯´æ˜Žï¼šå¹‚等,可重复执行
-- ============================================================
-- ---------- 1. æ–°å¢ž employee_id å­—段 ----------
SET @exist_col = (SELECT COUNT(*) FROM information_schema.columns
                  WHERE table_schema = DATABASE() AND table_name = 'safety_certificate' AND column_name = 'employee_id');
SET @sql = IF(@exist_col = 0,
              'ALTER TABLE `safety_certificate` ADD COLUMN `employee_id` bigint NULL COMMENT ''持证人(员工编号,关联 hrm_employee.id)'' AFTER `id`',
              'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ---------- 2. ç´¢å¼• ----------
SET @exist_idx = (SELECT COUNT(*) FROM information_schema.statistics
                  WHERE table_schema = DATABASE() AND table_name = 'safety_certificate' AND index_name = 'idx_employee_id');
SET @sql2 = IF(@exist_idx = 0,
               'CREATE INDEX `idx_employee_id` ON `safety_certificate` (`employee_id`)',
               'SELECT 1');
PREPARE stmt2 FROM @sql2;
EXECUTE stmt2;
DEALLOCATE PREPARE stmt2;
-- ---------- 3. åŽŸä¸‰åˆ—æ”¹ä¸ºå¿«ç…§è¯­ä¹‰ï¼ˆä¿ç•™ï¼Œä¸å†ä½œä¸ºå”¯ä¸€æ¥æºï¼‰ ----------
ALTER TABLE `safety_certificate`
  MODIFY COLUMN `employee_name` varchar(50) NULL COMMENT '持证人姓名(快照,由员工档案带出)',
  MODIFY COLUMN `employee_no` varchar(50) NULL COMMENT '工号(快照,由员工档案带出)',
  MODIFY COLUMN `dept_name` varchar(100) NULL COMMENT '所属部门(快照,由员工档案带出)';
-- ---------- 4. æ ¡éªŒ ----------
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_COMMENT
  FROM information_schema.columns
 WHERE table_schema = DATABASE() AND table_name = 'safety_certificate'
   AND COLUMN_NAME IN ('employee_id', 'employee_name', 'employee_no', 'dept_name')
 ORDER BY ORDINAL_POSITION;
sql/mysql/safety_fix_code_index_20261029.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
-- ============================================================
-- å®‰å…¨ç”Ÿäº§ - ä¿®å¤ uk_code å”¯ä¸€ç´¢å¼•导致「第二次软删除同编码」报错
-- ç”Ÿæˆæ—¶é—´ï¼š2026-10-29
--
-- çŽ°è±¡ï¼šå·¡æ£€ç‚¹ä½ PD-001 å…ˆè¢«è½¯åˆ ä¸€æ¬¡ï¼Œä¹‹åŽæ–°å»ºåŒç¼–码 PD-001 å†åˆ é™¤æ—¶æŠ¥
--       Duplicate entry 'PD-001-\x01' for key 'safety_inspection_point.uk_code'
--       æŽ¥å£è¿”回「系统异常」(500)
--
-- åŽŸå› ï¼šuk_code å»ºåœ¨ (code, deleted) ä¸Šï¼Œdeleted æ˜¯ bit(1)。
--       è½¯åˆ é™¤åŽ deleted=b'1',若历史上已有一条同编码且已软删的记录,
--       ç¬¬äºŒæ¬¡è½¯åˆ é™¤å°±ä¼šæ’入重复的 (code, b'1') ç»„合,撞唯一键。
--
-- ä¿®å¤ï¼šåŽ»æŽ‰ (code, deleted) å”¯ä¸€çº¦æŸï¼Œæ”¹æˆæ™®é€šç´¢å¼• idx_code ç”¨äºŽæŸ¥è¯¢ï¼›
--       ç¼–码唯一性由 Service å±‚保证(新增/修改时 selectByCode æŸ¥æ´»è·ƒè®°å½•判重,
--       ä¸Ž yudao å…¶å®ƒä¸šåŠ¡è¡¨ä¸€è‡´ï¼Œä¾‹å¦‚ mes_dv_machinery å°±æ²¡æœ‰ code å”¯ä¸€é”®ï¼‰ã€‚
--
-- å¹‚等:先判断索引是否存在再操作,可重复执行
-- ============================================================
DROP PROCEDURE IF EXISTS sp_safety_fix_code_index;
DELIMITER $$
CREATE PROCEDURE sp_safety_fix_code_index(IN p_table VARCHAR(64))
BEGIN
  -- 1. åˆ æŽ‰æ—§çš„ (code, deleted) å”¯ä¸€é”®
  IF EXISTS (SELECT 1 FROM information_schema.statistics
              WHERE table_schema = DATABASE() AND table_name = p_table AND index_name = 'uk_code') THEN
    SET @s = CONCAT('ALTER TABLE `', p_table, '` DROP INDEX `uk_code`');
    PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st;
  END IF;
  -- 2. è¡¥ä¸€ä¸ªæ™®é€šç´¢å¼•,保证按编码查询的性能
  IF NOT EXISTS (SELECT 1 FROM information_schema.statistics
                  WHERE table_schema = DATABASE() AND table_name = p_table AND index_name = 'idx_code') THEN
    SET @s2 = CONCAT('CREATE INDEX `idx_code` ON `', p_table, '` (`code`)');
    PREPARE st2 FROM @s2; EXECUTE st2; DEALLOCATE PREPARE st2;
  END IF;
END$$
DELIMITER ;
CALL sp_safety_fix_code_index('safety_inspection_point');
CALL sp_safety_fix_code_index('safety_hazard');
CALL sp_safety_fix_code_index('safety_material');
CALL sp_safety_fix_code_index('safety_work_permit');
CALL sp_safety_fix_code_index('safety_accident');
DROP PROCEDURE IF EXISTS sp_safety_fix_code_index;
-- ---------- æ ¡éªŒï¼šç¡®è®¤å·²æ—  uk_code,且有 idx_code ----------
SELECT TABLE_NAME, INDEX_NAME, NON_UNIQUE,
       GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS cols
  FROM information_schema.statistics
 WHERE table_schema = DATABASE() AND TABLE_NAME LIKE 'safety%'
   AND INDEX_NAME IN ('uk_code', 'idx_code')
 GROUP BY TABLE_NAME, INDEX_NAME, NON_UNIQUE
 ORDER BY TABLE_NAME;
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;
sql/mysql/safety_module_menu_20261029.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,125 @@
-- ============================================================
-- å®‰å…¨ç”Ÿäº§æ¨¡å— - èœå• + è§’色授权
-- ç”Ÿæˆæ—¶é—´ï¼š2026-10-29
-- è¯´æ˜Žï¼šé¡¶çº§èœå•「安全生产」/safety,sort=67(排在能耗管理 65 ä¸Žå”®åŽç®¡ç† 70 ä¹‹é—´ï¼‰
--       æœ¬è„šæœ¬å¹‚ç­‰ï¼Œå¯é‡å¤æ‰§è¡Œ
-- ============================================================
-- ---------- 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 (1075600, '安全生产', '', 1, 67, 0, '/safety', 'ep:first-aid-kit', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0')
ON DUPLICATE KEY UPDATE `name`=VALUES(`name`), `sort`=VALUES(`sort`), `path`=VALUES(`path`), `icon`=VALUES(`icon`), `status`=0, `deleted`=b'0';
-- ---------- 2. äºŒçº§ç›®å½• ----------
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
(1075601, '安全组织架构', '', 2, 1, 1075600, 'org', 'ep:user-filled', 'safety/org/index', 'SafetyOrg', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075602, '特种作业证', '', 2, 2, 1075600, 'cert', 'ep:postcard', 'safety/cert/index', 'SafetyCert', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075603, '安全巡检', '', 1, 3, 1075600, 'inspection', 'ep:view', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075604, '隐患闭环', '', 1, 4, 1075600, 'hazard', 'ep:warning-filled', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075605, '高危作业票', '', 2, 5, 1075600, 'permit', 'ep:document-checked', 'safety/permit/index', 'SafetyPermit', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075606, '应急物资', '', 1, 6, 1075600, 'material', 'ep:box', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075607, '培训演练', '', 1, 7, 1075600, 'training', 'ep:reading', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075608, '事故登记', '', 2, 8, 1075600, 'accident', 'ep:first-aid-kit', 'safety/accident/index', 'SafetyAccident', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075609, '标准化台账', '', 2, 9, 1075600, 'ledger', 'ep:files', 'safety/ledger/index', 'SafetyLedger', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0')
ON DUPLICATE KEY UPDATE `name`=VALUES(`name`), `sort`=VALUES(`sort`), `path`=VALUES(`path`), `component`=VALUES(`component`), `component_name`=VALUES(`component_name`), `status`=0, `deleted`=b'0';
-- ---------- 3. ä¸‰çº§é¡µé¢ ----------
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
(1075610, '巡检点位', '', 2, 1, 1075603, 'point', 'ep:location', 'safety/inspection/point/index', 'SafetyInspectionPoint', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075611, '巡检记录', '', 2, 2, 1075603, 'record', 'ep:tickets', 'safety/inspection/record/index', 'SafetyInspectionRecord', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075612, '隐患台账', '', 2, 1, 1075604, 'list', 'ep:list', 'safety/hazard/index', 'SafetyHazard', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075613, '物资台账', '', 2, 1, 1075606, 'list', 'ep:goods', 'safety/material/index', 'SafetyMaterial', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075614, '出入库流水', '', 2, 2, 1075606, 'stock', 'ep:sort', 'safety/material/stock/index', 'SafetyMaterialStock', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075615, '安全培训', '', 2, 1, 1075607, 'training', 'ep:notebook', 'safety/training/index', 'SafetyTraining', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075616, '消防演练', '', 2, 2, 1075607, 'drill', 'ep:alarm-clock', 'safety/drill/index', 'SafetyDrill', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0')
ON DUPLICATE KEY UPDATE `name`=VALUES(`name`), `sort`=VALUES(`sort`), `path`=VALUES(`path`), `component`=VALUES(`component`), `component_name`=VALUES(`component_name`), `status`=0, `deleted`=b'0';
-- ---------- 4. æŒ‰é’®æƒé™ ----------
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
-- ç»„织架构
(1075620, '组织查询', 'safety:org:query', 3, 1, 1075601, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075621, '组织新增', 'safety:org:create', 3, 2, 1075601, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075622, '组织修改', 'safety:org:update', 3, 3, 1075601, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075623, '组织删除', 'safety:org:delete', 3, 4, 1075601, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- ç‰¹ç§ä½œä¸šè¯
(1075624, '证件查询', 'safety:cert:query', 3, 1, 1075602, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075625, '证件新增', 'safety:cert:create', 3, 2, 1075602, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075626, '证件修改', 'safety:cert:update', 3, 3, 1075602, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075627, '证件删除', 'safety:cert:delete', 3, 4, 1075602, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075628, '证件导出', 'safety:cert:export', 3, 5, 1075602, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- å·¡æ£€ç‚¹ä½
(1075629, '点位查询', 'safety:inspection-point:query', 3, 1, 1075610, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075630, '点位新增', 'safety:inspection-point:create', 3, 2, 1075610, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075631, '点位修改', 'safety:inspection-point:update', 3, 3, 1075610, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075632, '点位删除', 'safety:inspection-point:delete', 3, 4, 1075610, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- å·¡æ£€è®°å½•
(1075633, '巡检查询', 'safety:inspection:query', 3, 1, 1075611, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075634, '巡检新增', 'safety:inspection:create', 3, 2, 1075611, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075635, '巡检修改', 'safety:inspection:update', 3, 3, 1075611, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075636, '巡检删除', 'safety:inspection:delete', 3, 4, 1075611, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075637, '巡检导出', 'safety:inspection:export', 3, 5, 1075611, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- é𐿂£
(1075638, '隐患查询', 'safety:hazard:query', 3, 1, 1075612, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075639, '隐患上报', 'safety:hazard:create', 3, 2, 1075612, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075640, '隐患修改', 'safety:hazard:update', 3, 3, 1075612, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075641, '隐患分派', 'safety:hazard:assign', 3, 4, 1075612, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075642, '隐患整改', 'safety:hazard:rectify', 3, 5, 1075612, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075643, '隐患复查', 'safety:hazard:recheck', 3, 6, 1075612, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075644, '隐患删除', 'safety:hazard:delete', 3, 7, 1075612, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075645, '隐患导出', 'safety:hazard:export', 3, 8, 1075612, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- é«˜å±ä½œä¸šç¥¨
(1075646, '作业票查询', 'safety:permit:query', 3, 1, 1075605, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075647, '作业票申请', 'safety:permit:create', 3, 2, 1075605, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075648, '作业票修改', 'safety:permit:update', 3, 3, 1075605, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075649, '作业票审批', 'safety:permit:approve', 3, 4, 1075605, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075650, '作业票删除', 'safety:permit:delete', 3, 5, 1075605, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075651, '作业票导出', 'safety:permit:export', 3, 6, 1075605, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- ç‰©èµ„台账
(1075652, '物资查询', 'safety:material:query', 3, 1, 1075613, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075653, '物资新增', 'safety:material:create', 3, 2, 1075613, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075654, '物资修改', 'safety:material:update', 3, 3, 1075613, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075655, '物资删除', 'safety:material:delete', 3, 4, 1075613, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075656, '物资导出', 'safety:material:export', 3, 5, 1075613, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- å‡ºå…¥åº“流水
(1075657, '流水查询', 'safety:material-stock:query', 3, 1, 1075614, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075658, '入库登记', 'safety:material-stock:in', 3, 2, 1075614, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075659, '出库登记', 'safety:material-stock:out', 3, 3, 1075614, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075660, '流水导出', 'safety:material-stock:export', 3, 4, 1075614, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- åŸ¹è®­
(1075661, '培训查询', 'safety:training:query', 3, 1, 1075615, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075662, '培训新增', 'safety:training:create', 3, 2, 1075615, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075663, '培训修改', 'safety:training:update', 3, 3, 1075615, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075664, '培训删除', 'safety:training:delete', 3, 4, 1075615, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075665, '培训导出', 'safety:training:export', 3, 5, 1075615, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- æ¼”练
(1075666, '演练查询', 'safety:drill:query', 3, 1, 1075616, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075667, '演练新增', 'safety:drill:create', 3, 2, 1075616, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075668, '演练修改', 'safety:drill:update', 3, 3, 1075616, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075669, '演练删除', 'safety:drill:delete', 3, 4, 1075616, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075670, '演练导出', 'safety:drill:export', 3, 5, 1075616, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- äº‹æ•…
(1075671, '事故查询', 'safety:accident:query', 3, 1, 1075608, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075672, '事故登记', 'safety:accident:create', 3, 2, 1075608, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075673, '事故修改', 'safety:accident:update', 3, 3, 1075608, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075674, '事故删除', 'safety:accident:delete', 3, 4, 1075608, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075675, '事故导出', 'safety:accident:export', 3, 5, 1075608, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- æ ‡å‡†åŒ–台账
(1075676, '台账查询', 'safety:ledger:query', 3, 1, 1075609, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075677, '台账导出', 'safety:ledger:export', 3, 2, 1075609, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- é™„件上传(移动端拍照)
(1075678, '附件上传', 'safety:file:upload', 3, 1, 1075600, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0')
ON DUPLICATE KEY UPDATE `name`=VALUES(`name`), `permission`=VALUES(`permission`), `status`=0, `deleted`=b'0';
-- ---------- 5. è¶…管角色授权(role_id=1 ä¸ºè¶…级管理员,实际由 hasAnySuperAdmin å…œåº•;此处授权给普通角色 2) ----------
INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`)
SELECT 2, m.`id`, '1', NOW(), '1', NOW(), b'0', 1
FROM `system_menu` m
LEFT JOIN `system_role_menu` rm ON rm.`role_id` = 2 AND rm.`menu_id` = m.`id` AND rm.`deleted` = b'0' AND rm.`tenant_id` = 1
WHERE m.`id` BETWEEN 1075600 AND 1075678 AND m.`deleted` = b'0' AND rm.`id` IS NULL;
-- ---------- 6. æ ¡éªŒ ----------
SELECT COUNT(*) AS safety_menus FROM `system_menu` WHERE `id` BETWEEN 1075600 AND 1075678 AND `deleted` = b'0';
SELECT `id`, `name`, `parent_id`, `path`, `component` FROM `system_menu` WHERE `id` BETWEEN 1075600 AND 1075616 AND `deleted` = b'0' ORDER BY `id`;
sql/mysql/safety_module_tables_20261029.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,343 @@
-- ============================================================
-- å®‰å…¨ç”Ÿäº§æ¨¡å— - è¡¨ç»“æž„ + èœå•(电力行业安全生产标准化)
-- ç”Ÿæˆæ—¶é—´ï¼š2026-10-29
-- é€‚用:MySQL 8.4+
-- å†…容:
--   1. å®‰å…¨ç»„织架构       safety_org
--   2. ç‰¹ç§ä½œä¸šè¯å°è´¦     safety_certificate(电工证/高压操作证等,到期预警)
--   3. å®‰å…¨å·¡æ£€ç‚¹ä½       safety_inspection_point
--   4. å®‰å…¨å·¡æ£€è®°å½•       safety_inspection
--   5. é𐿂£ç™»è®°ä¸Žé—­çޝ     safety_hazard(上报-分派-整改-复查)
--   6. é«˜å±ä½œä¸šç¥¨         safety_work_permit(动火/高压带电/有限空间等)
--   7. ä½œä¸šç›‘护记录       safety_work_permit_monitor
--   8. åº”急物资台账       safety_material(绝缘/危化/消防)
--   9. ç‰©èµ„出入库流水     safety_material_stock
--  10. å®‰å…¨åŸ¹è®­è®°å½•       safety_training
--  11. æ¶ˆé˜²æ¼”练记录       safety_drill
--  12. äº‹æ•…登记           safety_accident
-- è¯´æ˜Žï¼šæœ¬è„šæœ¬å¹‚ç­‰ï¼ˆIF NOT EXISTS / ON DUPLICATE KEY UPDATE),可重复执行
-- ============================================================
-- ---------- 1. å®‰å…¨ç»„织架构 ----------
CREATE TABLE IF NOT EXISTS `safety_org` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `parent_id` bigint NOT NULL DEFAULT 0 COMMENT '父级编号(0 ä¸ºé¡¶çº§ï¼‰',
  `name` varchar(100) NOT NULL COMMENT '组织名称',
  `leader_name` varchar(50) DEFAULT NULL COMMENT '负责人姓名',
  `leader_phone` varchar(30) DEFAULT NULL COMMENT '负责人电话',
  `member_names` varchar(500) DEFAULT NULL COMMENT '成员姓名,逗号分隔',
  `duty` varchar(500) DEFAULT NULL COMMENT '安全职责',
  `sort` int NOT NULL DEFAULT 0 COMMENT '显示顺序',
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态:0-启用,1-停用',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='安全生产-组织架构';
-- ---------- 2. ç‰¹ç§ä½œä¸šè¯å°è´¦ï¼ˆåˆ°æœŸè‡ªåŠ¨é¢„è­¦ï¼‰ ----------
CREATE TABLE IF NOT EXISTS `safety_certificate` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `employee_name` varchar(50) NOT NULL COMMENT '持证人姓名',
  `employee_no` varchar(50) DEFAULT NULL COMMENT '工号',
  `dept_name` varchar(100) DEFAULT NULL COMMENT '所属部门',
  `cert_type` varchar(50) NOT NULL COMMENT '证件类型:LOW_VOLTAGE_ELECTRICIAN/HIGH_VOLTAGE_OPERATION/WELDING/HOIST/HEIGHT/LIMITED_SPACE/WORK_AT_HEIGHT/OTHER',
  `cert_no` varchar(64) NOT NULL COMMENT '证件编号',
  `issuer` varchar(100) DEFAULT NULL COMMENT '发证机关',
  `issue_date` date DEFAULT NULL COMMENT '发证日期',
  `expire_date` date NOT NULL COMMENT '有效期至',
  `review_date` date DEFAULT NULL COMMENT '下次复审日期',
  `warn_days` int NOT NULL DEFAULT 30 COMMENT '到期预警提前天数',
  `status` varchar(20) NOT NULL DEFAULT 'VALID' COMMENT '状态:VALID-有效,EXPIRING-即将到期,EXPIRED-已过期,REVOKED-已注销',
  `attachment` varchar(500) DEFAULT NULL COMMENT '证件扫描件 URL,逗号分隔',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_employee_name` (`employee_name`),
  KEY `idx_cert_type` (`cert_type`),
  KEY `idx_expire_date` (`expire_date`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='安全生产-特种作业证台账';
-- ---------- 3. å®‰å…¨å·¡æ£€ç‚¹ä½ ----------
CREATE TABLE IF NOT EXISTS `safety_inspection_point` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `code` varchar(64) NOT NULL COMMENT '点位编码',
  `name` varchar(100) NOT NULL COMMENT '点位名称',
  `point_type` varchar(30) NOT NULL COMMENT '点位类型:HIGH_VOLTAGE_ROOM-高压配电室,UNIT_AREA-机组区,DANGER_CHEMICAL-危化品库,FIRE_AREA-消防重点部位,OTHER-其他',
  `workshop_name` varchar(100) DEFAULT NULL COMMENT '所属车间/区域',
  `location` varchar(200) DEFAULT NULL COMMENT '具体位置',
  `inspect_items` varchar(2000) DEFAULT NULL COMMENT '巡检项目清单,按行分隔',
  `inspect_cycle` varchar(20) NOT NULL DEFAULT 'DAILY' COMMENT '巡检周期:SHIFT-每班,DAILY-每日,WEEKLY-每周,MONTHLY-每月',
  `owner_name` varchar(50) DEFAULT NULL COMMENT '责任人',
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态:0-启用,1-停用',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_code` (`code`, `deleted`),
  KEY `idx_point_type` (`point_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='安全生产-巡检点位';
-- ---------- 4. å®‰å…¨å·¡æ£€è®°å½• ----------
CREATE TABLE IF NOT EXISTS `safety_inspection` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `point_id` bigint DEFAULT NULL COMMENT '巡检点位编号',
  `point_name` varchar(100) DEFAULT NULL COMMENT '点位名称(快照)',
  `point_type` varchar(30) DEFAULT NULL COMMENT '点位类型(快照)',
  `inspect_time` datetime NOT NULL COMMENT '巡检时间',
  `inspector` varchar(50) NOT NULL COMMENT '巡检人',
  `result` varchar(20) NOT NULL DEFAULT 'NORMAL' COMMENT '巡检结果:NORMAL-正常,ABNORMAL-异常',
  `content` varchar(2000) DEFAULT NULL COMMENT '巡检内容与情况',
  `photo_urls` varchar(2000) DEFAULT NULL COMMENT '现场照片 URL,逗号分隔',
  `hazard_count` int NOT NULL DEFAULT 0 COMMENT '本次发现的隐患数',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_point_id` (`point_id`),
  KEY `idx_inspect_time` (`inspect_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='安全生产-巡检记录';
-- ---------- 5. é𐿂£ç™»è®°ä¸Žé—­çŽ¯ï¼ˆä¸ŠæŠ¥-分派-整改-复查) ----------
CREATE TABLE IF NOT EXISTS `safety_hazard` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `code` varchar(64) NOT NULL COMMENT '隐患编号',
  `title` varchar(200) NOT NULL COMMENT '隐患标题',
  `level` varchar(20) NOT NULL DEFAULT 'GENERAL' COMMENT '隐患等级:GENERAL-一般,MAJOR-重大',
  `source` varchar(30) NOT NULL DEFAULT 'INSPECTION' COMMENT '来源:INSPECTION-巡检发现,SELF_CHECK-自查上报,REPORT-员工上报,EXTERNAL-外部检查',
  `inspection_id` bigint DEFAULT NULL COMMENT '关联巡检记录编号',
  `point_id` bigint DEFAULT NULL COMMENT '关联巡检点位编号',
  `point_name` varchar(100) DEFAULT NULL COMMENT '点位名称',
  `location` varchar(200) DEFAULT NULL COMMENT '隐患位置',
  `description` varchar(2000) DEFAULT NULL COMMENT '隐患描述',
  `photo_urls` varchar(2000) DEFAULT NULL COMMENT '现场照片 URL,逗号分隔(移动端拍照上传)',
  `reporter` varchar(50) NOT NULL COMMENT '上报人',
  `report_time` datetime NOT NULL COMMENT '上报时间',
  `assignee` varchar(50) DEFAULT NULL COMMENT '整改责任人(分派)',
  `assign_time` datetime DEFAULT NULL COMMENT '分派时间',
  `rectify_deadline` date DEFAULT NULL COMMENT '整改期限',
  `rectify_desc` varchar(2000) DEFAULT NULL COMMENT '整改措施与结果',
  `rectify_photo_urls` varchar(2000) DEFAULT NULL COMMENT '整改后照片 URL,逗号分隔',
  `rectify_time` datetime DEFAULT NULL COMMENT '整改完成时间',
  `rechecker` varchar(50) DEFAULT NULL COMMENT '复查人',
  `recheck_time` datetime DEFAULT NULL COMMENT '复查时间',
  `recheck_result` varchar(20) DEFAULT NULL COMMENT '复查结论:PASSED-通过,REJECTED-不通过退回整改',
  `recheck_remark` varchar(500) DEFAULT NULL COMMENT '复查意见',
  `status` varchar(20) NOT NULL DEFAULT 'REPORTED' COMMENT '状态:REPORTED-已上报,ASSIGNED-已分派,RECTIFYING-整改中,RECHECKING-待复查,CLOSED-已闭环,REJECTED-复查不通过',
  `close_time` datetime DEFAULT NULL COMMENT '闭环时间',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_code` (`code`, `deleted`),
  KEY `idx_status` (`status`),
  KEY `idx_report_time` (`report_time`),
  KEY `idx_inspection_id` (`inspection_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='安全生产-隐患登记与闭环';
-- ---------- 6. é«˜å±ä½œä¸šç¥¨ ----------
CREATE TABLE IF NOT EXISTS `safety_work_permit` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `code` varchar(64) NOT NULL COMMENT '作业票编号',
  `permit_type` varchar(30) NOT NULL COMMENT '作业类型:HOT_WORK-动火作业,LIVE_HIGH_VOLTAGE-高压带电作业,LIMITED_SPACE-有限空间作业,WORK_AT_HEIGHT-高处作业,LIFTING-吊装作业,TEMP_ELECTRIC-临时用电作业,OTHER-其他',
  `title` varchar(200) NOT NULL COMMENT '作业名称',
  `workshop_name` varchar(100) DEFAULT NULL COMMENT '作业区域/车间',
  `location` varchar(200) DEFAULT NULL COMMENT '作业地点',
  `work_content` varchar(2000) DEFAULT NULL COMMENT '作业内容与范围',
  `applicant` varchar(50) NOT NULL COMMENT '申请人',
  `apply_time` datetime NOT NULL COMMENT '申请时间',
  `plan_start_time` datetime NOT NULL COMMENT '计划开始时间',
  `plan_end_time` datetime NOT NULL COMMENT '计划结束时间',
  `actual_start_time` datetime DEFAULT NULL COMMENT '实际开始时间',
  `actual_end_time` datetime DEFAULT NULL COMMENT '实际结束时间',
  `risk_level` varchar(20) NOT NULL DEFAULT 'NORMAL' COMMENT '风险等级:LOW-低,NORMAL-一般,HIGH-高,VERY_HIGH-极高',
  `safety_measures` varchar(2000) DEFAULT NULL COMMENT '安全措施',
  `approver` varchar(50) DEFAULT NULL COMMENT '审批人',
  `approve_time` datetime DEFAULT NULL COMMENT '审批时间',
  `approve_comment` varchar(500) DEFAULT NULL COMMENT '审批意见',
  `guardian` varchar(50) DEFAULT NULL COMMENT '监护人',
  `status` varchar(20) NOT NULL DEFAULT 'DRAFT' COMMENT '状态:DRAFT-草稿,PENDING-待审批,APPROVED-已批准,REJECTED-已驳回,IN_PROGRESS-作业中,CLOSED-已完结,CANCELLED-已作废',
  `attachment` varchar(2000) DEFAULT NULL COMMENT '作业票附件 URL,逗号分隔',
  `photo_urls` varchar(2000) DEFAULT NULL COMMENT '现场照片 URL,逗号分隔',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_code` (`code`, `deleted`),
  KEY `idx_permit_type` (`permit_type`),
  KEY `idx_status` (`status`),
  KEY `idx_plan_start_time` (`plan_start_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='安全生产-高危作业票';
-- ---------- 7. ä½œä¸šç›‘护记录 ----------
CREATE TABLE IF NOT EXISTS `safety_work_permit_monitor` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `permit_id` bigint NOT NULL COMMENT '作业票编号',
  `monitor_time` datetime NOT NULL COMMENT '监护时间',
  `guardian` varchar(50) NOT NULL COMMENT '监护人',
  `monitor_content` varchar(1000) DEFAULT NULL COMMENT '监护内容与情况',
  `abnormal` varchar(500) DEFAULT NULL COMMENT '异常情况与处置',
  `photo_urls` varchar(2000) DEFAULT NULL COMMENT '现场照片 URL,逗号分隔',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_permit_id` (`permit_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='安全生产-作业监护记录';
-- ---------- 8. åº”急/安全物资台账 ----------
CREATE TABLE IF NOT EXISTS `safety_material` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `code` varchar(64) NOT NULL COMMENT '物资编码',
  `name` varchar(100) NOT NULL COMMENT '物资名称',
  `category` varchar(30) NOT NULL COMMENT '类别:INSULATING-电力绝缘,HAZARD_CHEMICAL-危化品,FIRE-消防物资,PPE-劳保用品,OTHER-其他',
  `spec` varchar(100) DEFAULT NULL COMMENT '规格型号',
  `unit` varchar(20) NOT NULL DEFAULT '个' COMMENT '计量单位',
  `stock_qty` decimal(18,3) NOT NULL DEFAULT 0 COMMENT '当前库存数量',
  `safe_stock` decimal(18,3) NOT NULL DEFAULT 0 COMMENT '安全库存(低于则预警)',
  `storage_location` varchar(100) DEFAULT NULL COMMENT '存放位置',
  `expire_date` date DEFAULT NULL COMMENT '有效期至(绝缘/危化品必填)',
  `validity_months` int DEFAULT NULL COMMENT '有效期月数(如绝缘手套 6 ä¸ªæœˆï¼‰',
  `manager` varchar(50) DEFAULT NULL COMMENT '保管人',
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态:0-启用,1-停用',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_code` (`code`, `deleted`),
  KEY `idx_category` (`category`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='安全生产-应急物资台账';
-- ---------- 9. ç‰©èµ„出入库流水 ----------
CREATE TABLE IF NOT EXISTS `safety_material_stock` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `material_id` bigint NOT NULL COMMENT '物资编号',
  `material_name` varchar(100) DEFAULT NULL COMMENT '物资名称(快照)',
  `category` varchar(30) DEFAULT NULL COMMENT '物资类别(快照)',
  `biz_type` varchar(20) NOT NULL COMMENT '业务类型:IN-入库,OUT-出库',
  `quantity` decimal(18,3) NOT NULL COMMENT '数量',
  `stock_before` decimal(18,3) NOT NULL DEFAULT 0 COMMENT '操作前库存',
  `stock_after` decimal(18,3) NOT NULL DEFAULT 0 COMMENT '操作后库存',
  `biz_date` date NOT NULL COMMENT '业务日期',
  `operator` varchar(50) NOT NULL COMMENT '经办人',
  `receiver` varchar(50) DEFAULT NULL COMMENT '领用人(出库)',
  `purpose` varchar(200) DEFAULT NULL COMMENT '用途/去向',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_material_id` (`material_id`),
  KEY `idx_biz_date` (`biz_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='安全生产-物资出入库流水';
-- ---------- 10. å®‰å…¨åŸ¹è®­è®°å½• ----------
CREATE TABLE IF NOT EXISTS `safety_training` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `title` varchar(200) NOT NULL COMMENT '培训主题',
  `training_type` varchar(30) NOT NULL DEFAULT 'SAFETY' COMMENT '培训类型:SAFETY-安全培训,SKILL-技能培训,LAW-法规宣贯,EMERGENCY-应急演练培训,OTHER-其他',
  `train_date` date NOT NULL COMMENT '培训日期',
  `duration_hours` decimal(8,1) DEFAULT NULL COMMENT '培训学时',
  `trainer` varchar(50) DEFAULT NULL COMMENT '讲师/组织人',
  `location` varchar(200) DEFAULT NULL COMMENT '培训地点',
  `participants` varchar(2000) DEFAULT NULL COMMENT '参加人员姓名,逗号分隔',
  `participant_count` int NOT NULL DEFAULT 0 COMMENT '参加人数',
  `content` varchar(2000) DEFAULT NULL COMMENT '培训内容',
  `exam_result` varchar(2000) DEFAULT NULL COMMENT '考核情况',
  `attachment` varchar(2000) DEFAULT NULL COMMENT '签到表/课件/照片 URL,逗号分隔',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_train_date` (`train_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='安全生产-培训记录';
-- ---------- 11. æ¶ˆé˜²æ¼”练记录 ----------
CREATE TABLE IF NOT EXISTS `safety_drill` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `title` varchar(200) NOT NULL COMMENT '演练名称',
  `drill_type` varchar(30) NOT NULL DEFAULT 'FIRE' COMMENT '演练类型:FIRE-消防演练,EMERGENCY_EVAC-应急疏散,ELECTRIC_ACCIDENT-触电事故,CHEMICAL_LEAK-危化品泄漏,OTHER-其他',
  `drill_date` date NOT NULL COMMENT '演练日期',
  `location` varchar(200) DEFAULT NULL COMMENT '演练地点',
  `organizer` varchar(50) DEFAULT NULL COMMENT '组织人',
  `participants` varchar(2000) DEFAULT NULL COMMENT '参加人员姓名,逗号分隔',
  `participant_count` int NOT NULL DEFAULT 0 COMMENT '参加人数',
  `plan_desc` varchar(2000) DEFAULT NULL COMMENT '演练方案',
  `process_desc` varchar(2000) DEFAULT NULL COMMENT '演练过程记录',
  `summary` varchar(2000) DEFAULT NULL COMMENT '演练总结与改进',
  `duration_minutes` int DEFAULT NULL COMMENT '演练时长(分钟)',
  `attachment` varchar(2000) DEFAULT NULL COMMENT '演练照片/记录表 URL,逗号分隔',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_drill_date` (`drill_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='安全生产-消防演练记录';
-- ---------- 12. å®‰å…¨äº‹æ•…登记 ----------
CREATE TABLE IF NOT EXISTS `safety_accident` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `code` varchar(64) NOT NULL COMMENT '事故编号',
  `title` varchar(200) NOT NULL COMMENT '事故名称',
  `accident_type` varchar(30) NOT NULL COMMENT '事故类型:ELECTRIC_SHOCK-触电,FIRE-火灾,FALL-高处坠落,OBJECT_STRIKE-物体打击,MECHANICAL-机械伤害,CHEMICAL-中毒窒息,OTHER-其他',
  `level` varchar(20) NOT NULL DEFAULT 'GENERAL' COMMENT '事故等级:SLIGHT-轻伤,GENERAL-一般,SEVERE-重伤,MAJOR-重大',
  `occur_time` datetime NOT NULL COMMENT '发生时间',
  `location` varchar(200) DEFAULT NULL COMMENT '发生地点',
  `workshop_name` varchar(100) DEFAULT NULL COMMENT '所属车间/区域',
  `casualty_desc` varchar(500) DEFAULT NULL COMMENT '伤亡情况',
  `economic_loss` decimal(18,2) DEFAULT NULL COMMENT '直接经济损失(元)',
  `involved_persons` varchar(500) DEFAULT NULL COMMENT '涉事人员姓名,逗号分隔',
  `description` varchar(2000) DEFAULT NULL COMMENT '事故经过',
  `cause_analysis` varchar(2000) DEFAULT NULL COMMENT '原因分析',
  `measures` varchar(2000) DEFAULT NULL COMMENT '处理措施与整改要求',
  `reporter` varchar(50) DEFAULT NULL COMMENT '登记人',
  `report_time` datetime DEFAULT NULL COMMENT '登记时间',
  `status` varchar(20) NOT NULL DEFAULT 'REPORTED' COMMENT '状态:REPORTED-已登记,INVESTIGATING-调查中,HANDLED-已处理,CLOSED-已结案',
  `attachment` varchar(2000) DEFAULT NULL COMMENT '现场照片/调查报告 URL,逗号分隔',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_code` (`code`, `deleted`),
  KEY `idx_occur_time` (`occur_time`),
  KEY `idx_accident_type` (`accident_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='安全生产-事故登记';
SELECT '安全生产模块表结构创建完成' AS result;
yudao-module-mes/pom.xml
@@ -38,6 +38,13 @@
            <version>${revision}</version>
        </dependency>
        <!-- HRM äººèµ„ API:安全生产模块需要按员工档案解析持证人/负责人 -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-hrm-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- CRM å®¢æˆ·ç®¡ç† API -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyCertificateController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.cert.SafetyCertificatePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.cert.SafetyCertificateRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.cert.SafetyCertificateSaveReqVO;
import cn.iocoder.yudao.module.mes.service.safety.SafetyCertificateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰¹ç§ä½œä¸šè¯å°è´¦ï¼ˆå«åˆ°æœŸè‡ªåŠ¨é¢„è­¦ï¼‰
 *
 * @author è¶…级管理员
 */
@Tag(name = "管理后台 - å®‰å…¨ç”Ÿäº§-特种作业证")
@RestController
@RequestMapping("/safety/certificate")
@Validated
public class SafetyCertificateController {
    @Resource
    private SafetyCertificateService certificateService;
    @PostMapping("/create")
    @Operation(summary = "新增特种作业证")
    @PreAuthorize("@ss.hasPermission('safety:cert:create')")
    public CommonResult<Long> create(@Valid @RequestBody SafetyCertificateSaveReqVO reqVO) {
        return success(certificateService.createCertificate(reqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "修改特种作业证")
    @PreAuthorize("@ss.hasPermission('safety:cert:update')")
    public CommonResult<Boolean> update(@Valid @RequestBody SafetyCertificateSaveReqVO reqVO) {
        certificateService.updateCertificate(reqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除特种作业证")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:cert:delete')")
    public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
        certificateService.deleteCertificate(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得特种作业证")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:cert:query')")
    public CommonResult<SafetyCertificateRespVO> get(@RequestParam("id") Long id) {
        var cert = certificateService.getCertificate(id);
        var resp = BeanUtils.toBean(cert, SafetyCertificateRespVO.class);
        certificateService.fillDisplayFields(cert, resp);
        return success(resp);
    }
    @GetMapping("/page")
    @Operation(summary = "获得特种作业证分页")
    @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);
        certificateService.fillDisplayFieldsBatch(page.getList(), result.getList());
        return success(result);
    }
    @GetMapping("/expiring-list")
    @Operation(summary = "获得即将到期/已过期的特种作业证(到期自动预警)")
    @Parameter(name = "days", description = "提前预警天数,默认 30 å¤©")
    @PreAuthorize("@ss.hasPermission('safety:cert:query')")
    public CommonResult<List<SafetyCertificateRespVO>> expiringList(
            @RequestParam(value = "days", required = false) Integer days) {
        List<cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyCertificateDO> certs =
                certificateService.getExpiringCertificates(days);
        certificateService.fillEmployeeInfo(certs);
        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/SafetyFileController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.mes.service.safety.SafetyFileService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.http.MediaType;
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.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * å®‰å…¨ç”Ÿäº§ - ç§»åŠ¨ç«¯çŽ°åœºæ‹ç…§ä¸Šä¼ 
 *
 * <p>巡检、隐患上报、作业票、演练等环节的照片统一走此接口,
 * è¿”回可访问 URL,前端拼成逗号串写入业务表。</p>
 *
 * @author è¶…级管理员
 */
@Tag(name = "管理后台 - å®‰å…¨ç”Ÿäº§-附件上传")
@RestController
@RequestMapping("/safety/file")
@Validated
public class SafetyFileController {
    @Resource
    private SafetyFileService fileService;
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @Operation(summary = "上传单个附件(移动端拍照)")
    @Parameter(name = "file", description = "文件", required = true)
    @PreAuthorize("@ss.hasPermission('safety:file:upload')")
    public CommonResult<String> upload(@RequestPart("file") MultipartFile file,
                                       @RequestParam(value = "bizType", required = false) String bizType) {
        return success(fileService.upload(file, bizType));
    }
    @PostMapping(value = "/upload-batch", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @Operation(summary = "批量上传附件(移动端一次多张)")
    @PreAuthorize("@ss.hasPermission('safety:file:upload')")
    public CommonResult<List<String>> uploadBatch(@RequestPart("files") List<MultipartFile> files,
                                                  @RequestParam(value = "bizType", required = false) String bizType) {
        return success(fileService.uploadBatch(files, bizType));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyHazardController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.hazard.*;
import cn.iocoder.yudao.module.mes.service.safety.SafetyHazardService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * å®‰å…¨ç”Ÿäº§ - é𐿂£ä¸ŠæŠ¥ä¸Žé—­çŽ¯ï¼ˆä¸ŠæŠ¥-分派-整改-复查)
 *
 * @author è¶…级管理员
 */
@Tag(name = "管理后台 - å®‰å…¨ç”Ÿäº§-隐患闭环")
@RestController
@RequestMapping("/safety/hazard")
@Validated
public class SafetyHazardController {
    @Resource
    private SafetyHazardService hazardService;
    @PostMapping("/create")
    @Operation(summary = "隐患上报(支持移动端拍照上传的 photoUrls)")
    @PreAuthorize("@ss.hasPermission('safety:hazard:create')")
    public CommonResult<Long> create(@Valid @RequestBody SafetyHazardSaveReqVO reqVO) {
        return success(hazardService.createHazard(reqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "修改隐患基础信息")
    @PreAuthorize("@ss.hasPermission('safety:hazard:update')")
    public CommonResult<Boolean> update(@Valid @RequestBody SafetyHazardSaveReqVO reqVO) {
        hazardService.updateHazard(reqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除隐患")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:hazard:delete')")
    public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
        hazardService.deleteHazard(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得隐患")
    @Parameter(name = "id", description = "编号", required = true)
    @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);
    }
    @GetMapping("/page")
    @Operation(summary = "获得隐患分页")
    @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));
        }
        return success(result);
    }
    @PutMapping("/assign")
    @Operation(summary = "分派隐患整改责任人")
    @PreAuthorize("@ss.hasPermission('safety:hazard:assign')")
    public CommonResult<Boolean> assign(@Valid @RequestBody SafetyHazardAssignReqVO reqVO) {
        hazardService.assignHazard(reqVO);
        return success(true);
    }
    @PutMapping("/rectify")
    @Operation(summary = "提交隐患整改结果")
    @PreAuthorize("@ss.hasPermission('safety:hazard:rectify')")
    public CommonResult<Boolean> rectify(@Valid @RequestBody SafetyHazardRectifyReqVO reqVO) {
        hazardService.rectifyHazard(reqVO);
        return success(true);
    }
    @PutMapping("/recheck")
    @Operation(summary = "隐患复查(通过则闭环,不通过退回整改)")
    @PreAuthorize("@ss.hasPermission('safety:hazard:recheck')")
    public CommonResult<Boolean> recheck(@Valid @RequestBody SafetyHazardRecheckReqVO reqVO) {
        hazardService.recheckHazard(reqVO);
        return success(true);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyInspectionController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,162 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.inspection.*;
import cn.iocoder.yudao.module.mes.service.safety.SafetyInspectionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * å®‰å…¨ç”Ÿäº§ - å·¡æ£€ç‚¹ä½ä¸Žå·¡æ£€è®°å½•
 *
 * @author è¶…级管理员
 */
@Tag(name = "管理后台 - å®‰å…¨ç”Ÿäº§-安全巡检")
@RestController
@RequestMapping("/safety/inspection")
@Validated
public class SafetyInspectionController {
    @Resource
    private SafetyInspectionService inspectionService;
    // ==================== å·¡æ£€ç‚¹ä½ ====================
    @PostMapping("/point/create")
    @Operation(summary = "新增巡检点位")
    @PreAuthorize("@ss.hasPermission('safety:inspection-point:create')")
    public CommonResult<Long> createPoint(@Valid @RequestBody SafetyInspectionPointSaveReqVO reqVO) {
        return success(inspectionService.createPoint(reqVO));
    }
    @PutMapping("/point/update")
    @Operation(summary = "修改巡检点位")
    @PreAuthorize("@ss.hasPermission('safety:inspection-point:update')")
    public CommonResult<Boolean> updatePoint(@Valid @RequestBody SafetyInspectionPointSaveReqVO reqVO) {
        inspectionService.updatePoint(reqVO);
        return success(true);
    }
    @DeleteMapping("/point/delete")
    @Operation(summary = "删除巡检点位")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:inspection-point:delete')")
    public CommonResult<Boolean> deletePoint(@RequestParam("id") Long id) {
        inspectionService.deletePoint(id);
        return success(true);
    }
    @GetMapping("/point/get")
    @Operation(summary = "获得巡检点位")
    @Parameter(name = "id", description = "编号", required = true)
    @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));
        return success(resp);
    }
    @GetMapping("/point/page")
    @Operation(summary = "获得巡检点位分页")
    @PreAuthorize("@ss.hasPermission('safety:inspection-point:query')")
    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));
        }
        return success(result);
    }
    @GetMapping("/point/simple-list")
    @Operation(summary = "获得启用中的巡检点位精简列表(移动端巡检选择)")
    @PreAuthorize("@ss.hasPermission('safety:inspection-point:query')")
    public CommonResult<List<SafetyInspectionPointRespVO>> getPointSimpleList() {
        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);
                    return resp;
                }).toList();
        return success(list);
    }
    // ==================== å·¡æ£€è®°å½• ====================
    @PostMapping("/record/create")
    @Operation(summary = "新增巡检记录")
    @PreAuthorize("@ss.hasPermission('safety:inspection:create')")
    public CommonResult<Long> createRecord(@Valid @RequestBody SafetyInspectionSaveReqVO reqVO) {
        return success(inspectionService.createInspection(reqVO));
    }
    @PutMapping("/record/update")
    @Operation(summary = "修改巡检记录")
    @PreAuthorize("@ss.hasPermission('safety:inspection:update')")
    public CommonResult<Boolean> updateRecord(@Valid @RequestBody SafetyInspectionSaveReqVO reqVO) {
        inspectionService.updateInspection(reqVO);
        return success(true);
    }
    @DeleteMapping("/record/delete")
    @Operation(summary = "删除巡检记录")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:inspection:delete')")
    public CommonResult<Boolean> deleteRecord(@RequestParam("id") Long id) {
        inspectionService.deleteInspection(id);
        return success(true);
    }
    @GetMapping("/record/get")
    @Operation(summary = "获得巡检记录")
    @Parameter(name = "id", description = "编号", required = true)
    @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()));
        resp.setResultName("NORMAL".equals(record.getResult()) ? "正常" : "异常");
        return success(resp);
    }
    @GetMapping("/record/page")
    @Operation(summary = "获得巡检记录分页")
    @PreAuthorize("@ss.hasPermission('safety:inspection:query')")
    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);
            var resp = result.getList().get(i);
            resp.setPointTypeName(cn.iocoder.yudao.module.mes.enums.safety.SafetyInspectionPointTypeEnum
                    .getNameByType(record.getPointType()));
            resp.setResultName("NORMAL".equals(record.getResult()) ? "正常" : "异常");
        }
        return success(result);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyLedgerController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.ledger.SafetyLedgerExportReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.ledger.SafetyOverviewRespVO;
import cn.iocoder.yudao.module.mes.service.safety.SafetyLedgerExportService;
import cn.iocoder.yudao.module.mes.service.safety.SafetyOverviewService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * å®‰å…¨ç”Ÿäº§ - æ€»è§ˆç»Ÿè®¡ä¸Žæ ‡å‡†åŒ–台账导出
 *
 * @author è¶…级管理员
 */
@Tag(name = "管理后台 - å®‰å…¨ç”Ÿäº§-台账与统计")
@RestController
@RequestMapping("/safety/ledger")
@Validated
public class SafetyLedgerController {
    @Resource
    private SafetyOverviewService overviewService;
    @Resource
    private SafetyLedgerExportService ledgerExportService;
    @GetMapping("/overview")
    @Operation(summary = "获得安全生产总览统计")
    @PreAuthorize("@ss.hasPermission('safety:ledger:query')")
    public CommonResult<SafetyOverviewRespVO> overview() {
        return success(overviewService.getOverview());
    }
    @GetMapping("/export")
    @Operation(summary = "导出电力行业安全标准化台账(全套或多选其一)")
    @PreAuthorize("@ss.hasPermission('safety:ledger:export')")
    public void export(@Valid SafetyLedgerExportReqVO reqVO, HttpServletResponse response) throws IOException {
        ledgerExportService.export(reqVO, response);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyMaterialController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,138 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.material.*;
import cn.iocoder.yudao.module.mes.service.safety.SafetyMaterialService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * å®‰å…¨ç”Ÿäº§ - åº”急物资台账与出入库(绝缘用具 / å±åŒ–品 / æ¶ˆé˜²ç‰©èµ„)
 *
 * @author è¶…级管理员
 */
@Tag(name = "管理后台 - å®‰å…¨ç”Ÿäº§-应急物资")
@RestController
@RequestMapping("/safety/material")
@Validated
public class SafetyMaterialController {
    @Resource
    private SafetyMaterialService materialService;
    @PostMapping("/create")
    @Operation(summary = "新增物资")
    @PreAuthorize("@ss.hasPermission('safety:material:create')")
    public CommonResult<Long> create(@Valid @RequestBody SafetyMaterialSaveReqVO reqVO) {
        return success(materialService.createMaterial(reqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "修改物资(库存数量只能通过出入库变动)")
    @PreAuthorize("@ss.hasPermission('safety:material:update')")
    public CommonResult<Boolean> update(@Valid @RequestBody SafetyMaterialSaveReqVO reqVO) {
        materialService.updateMaterial(reqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除物资")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:material:delete')")
    public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
        materialService.deleteMaterial(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得物资")
    @Parameter(name = "id", description = "编号", required = true)
    @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);
    }
    @GetMapping("/page")
    @Operation(summary = "获得物资分页")
    @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));
        }
        return success(result);
    }
    @GetMapping("/below-safe-stock")
    @Operation(summary = "获得低于安全库存的物资列表(预警)")
    @PreAuthorize("@ss.hasPermission('safety:material:query')")
    public CommonResult<List<SafetyMaterialRespVO>> belowSafeStock() {
        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);
                    return resp;
                }).toList();
        return success(list);
    }
    @GetMapping("/expiring-list")
    @Operation(summary = "获得即将到期的物资列表(预警)")
    @Parameter(name = "days", description = "提前预警天数,默认 30 å¤©")
    @PreAuthorize("@ss.hasPermission('safety:material:query')")
    public CommonResult<List<SafetyMaterialRespVO>> expiringList(
            @RequestParam(value = "days", required = false) Integer days) {
        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);
                    return resp;
                }).toList();
        return success(list);
    }
    // ==================== å‡ºå…¥åº“ ====================
    @PostMapping("/stock/create")
    @Operation(summary = "出入库登记(自动更新库存)")
    @PreAuthorize("@ss.hasPermission('safety:material-stock:in') or @ss.hasPermission('safety:material-stock:out')")
    public CommonResult<Long> createStock(@Valid @RequestBody SafetyMaterialStockSaveReqVO reqVO) {
        return success(materialService.createStock(reqVO));
    }
    @GetMapping("/stock/page")
    @Operation(summary = "获得出入库流水分页")
    @PreAuthorize("@ss.hasPermission('safety:material-stock:query')")
    public CommonResult<PageResult<SafetyMaterialStockRespVO>> stockPage(
            @Valid SafetyMaterialStockPageReqVO reqVO) {
        var page = materialService.getStockPage(reqVO);
        var result = BeanUtils.toBean(page, SafetyMaterialStockRespVO.class);
        for (int i = 0; i < page.getList().size(); i++) {
            SafetyMaterialService.fillStockDisplay(page.getList().get(i), result.getList().get(i));
        }
        return success(result);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyOrgController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.org.SafetyOrgRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.org.SafetyOrgSaveReqVO;
import cn.iocoder.yudao.module.mes.service.safety.SafetyOrgService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * å®‰å…¨ç”Ÿäº§ - å®‰å…¨ç»„织架构
 *
 * @author è¶…级管理员
 */
@Tag(name = "管理后台 - å®‰å…¨ç”Ÿäº§-组织架构")
@RestController
@RequestMapping("/safety/org")
@Validated
public class SafetyOrgController {
    @Resource
    private SafetyOrgService orgService;
    @PostMapping("/create")
    @Operation(summary = "新增安全组织")
    @PreAuthorize("@ss.hasPermission('safety:org:create')")
    public CommonResult<Long> create(@Valid @RequestBody SafetyOrgSaveReqVO reqVO) {
        return success(orgService.createOrg(reqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "修改安全组织")
    @PreAuthorize("@ss.hasPermission('safety:org:update')")
    public CommonResult<Boolean> update(@Valid @RequestBody SafetyOrgSaveReqVO reqVO) {
        orgService.updateOrg(reqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除安全组织")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:org:delete')")
    public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
        orgService.deleteOrg(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得安全组织")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:org:query')")
    public CommonResult<SafetyOrgRespVO> get(@RequestParam("id") Long id) {
        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);
    }
    @GetMapping("/tree")
    @Operation(summary = "获得安全组织树")
    @PreAuthorize("@ss.hasPermission('safety:org:query')")
    public CommonResult<List<SafetyOrgRespVO>> tree() {
        return success(orgService.getOrgTree());
    }
    private static void org2resp(cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyOrgDO org,
                                 SafetyOrgRespVO resp) {
        resp.setId(org.getId());
        resp.setParentId(org.getParentId());
        resp.setName(org.getName());
        resp.setLeaderName(org.getLeaderName());
        resp.setLeaderPhone(org.getLeaderPhone());
        resp.setMemberNames(org.getMemberNames());
        resp.setDuty(org.getDuty());
        resp.setSort(org.getSort());
        resp.setStatus(org.getStatus());
        resp.setRemark(org.getRemark());
        resp.setCreateTime(org.getCreateTime());
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyTrainingController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,187 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.train.*;
import cn.iocoder.yudao.module.mes.service.safety.SafetyTrainingService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * å®‰å…¨ç”Ÿäº§ - åŸ¹è®­ã€æ¼”练、事故登记电子化归档
 *
 * @author è¶…级管理员
 */
@Tag(name = "管理后台 - å®‰å…¨ç”Ÿäº§-培训与事故")
@RestController
@RequestMapping("/safety/training")
@Validated
public class SafetyTrainingController {
    @Resource
    private SafetyTrainingService trainingService;
    // ==================== åŸ¹è®­ ====================
    @PostMapping("/create")
    @Operation(summary = "新增安全培训记录")
    @PreAuthorize("@ss.hasPermission('safety:training:create')")
    public CommonResult<Long> create(@Valid @RequestBody SafetyTrainingSaveReqVO reqVO) {
        return success(trainingService.createTraining(reqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "修改安全培训记录")
    @PreAuthorize("@ss.hasPermission('safety:training:update')")
    public CommonResult<Boolean> update(@Valid @RequestBody SafetyTrainingSaveReqVO reqVO) {
        trainingService.updateTraining(reqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除安全培训记录")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:training:delete')")
    public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
        trainingService.deleteTraining(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得安全培训记录")
    @Parameter(name = "id", description = "编号", required = true)
    @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);
    }
    @GetMapping("/page")
    @Operation(summary = "获得安全培训记录分页")
    @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));
        }
        return success(result);
    }
    // ==================== æ¼”练 ====================
    @PostMapping("/drill/create")
    @Operation(summary = "新增消防演练记录")
    @PreAuthorize("@ss.hasPermission('safety:drill:create')")
    public CommonResult<Long> createDrill(@Valid @RequestBody SafetyDrillSaveReqVO reqVO) {
        return success(trainingService.createDrill(reqVO));
    }
    @PutMapping("/drill/update")
    @Operation(summary = "修改消防演练记录")
    @PreAuthorize("@ss.hasPermission('safety:drill:update')")
    public CommonResult<Boolean> updateDrill(@Valid @RequestBody SafetyDrillSaveReqVO reqVO) {
        trainingService.updateDrill(reqVO);
        return success(true);
    }
    @DeleteMapping("/drill/delete")
    @Operation(summary = "删除消防演练记录")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:drill:delete')")
    public CommonResult<Boolean> deleteDrill(@RequestParam("id") Long id) {
        trainingService.deleteDrill(id);
        return success(true);
    }
    @GetMapping("/drill/get")
    @Operation(summary = "获得消防演练记录")
    @Parameter(name = "id", description = "编号", required = true)
    @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);
    }
    @GetMapping("/drill/page")
    @Operation(summary = "获得消防演练记录分页")
    @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));
        }
        return success(result);
    }
    // ==================== äº‹æ•… ====================
    @PostMapping("/accident/create")
    @Operation(summary = "新增事故登记")
    @PreAuthorize("@ss.hasPermission('safety:accident:create')")
    public CommonResult<Long> createAccident(@Valid @RequestBody SafetyAccidentSaveReqVO reqVO) {
        return success(trainingService.createAccident(reqVO));
    }
    @PutMapping("/accident/update")
    @Operation(summary = "修改事故登记")
    @PreAuthorize("@ss.hasPermission('safety:accident:update')")
    public CommonResult<Boolean> updateAccident(@Valid @RequestBody SafetyAccidentSaveReqVO reqVO) {
        trainingService.updateAccident(reqVO);
        return success(true);
    }
    @DeleteMapping("/accident/delete")
    @Operation(summary = "删除事故登记")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:accident:delete')")
    public CommonResult<Boolean> deleteAccident(@RequestParam("id") Long id) {
        trainingService.deleteAccident(id);
        return success(true);
    }
    @GetMapping("/accident/get")
    @Operation(summary = "获得事故登记")
    @Parameter(name = "id", description = "编号", required = true)
    @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);
    }
    @GetMapping("/accident/page")
    @Operation(summary = "获得事故登记分页")
    @PreAuthorize("@ss.hasPermission('safety:accident:query')")
    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));
        }
        return success(result);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyWorkPermitController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,147 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.permit.*;
import cn.iocoder.yudao.module.mes.service.safety.SafetyWorkPermitService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * å®‰å…¨ç”Ÿäº§ - é«˜å±ä½œä¸šç¥¨ï¼ˆåŠ¨ç« / é«˜åŽ‹å¸¦ç”µ / æœ‰é™ç©ºé—´ç­‰ï¼‰å®¡æ‰¹ä¸Žç”µå­ç•™å­˜
 *
 * @author è¶…级管理员
 */
@Tag(name = "管理后台 - å®‰å…¨ç”Ÿäº§-高危作业票")
@RestController
@RequestMapping("/safety/permit")
@Validated
public class SafetyWorkPermitController {
    @Resource
    private SafetyWorkPermitService permitService;
    @PostMapping("/create")
    @Operation(summary = "提交高危作业申请")
    @PreAuthorize("@ss.hasPermission('safety:permit:create')")
    public CommonResult<Long> create(@Valid @RequestBody SafetyWorkPermitSaveReqVO reqVO) {
        return success(permitService.createPermit(reqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "修改作业票")
    @PreAuthorize("@ss.hasPermission('safety:permit:update')")
    public CommonResult<Boolean> update(@Valid @RequestBody SafetyWorkPermitSaveReqVO reqVO) {
        permitService.updatePermit(reqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除作业票(连同监护记录)")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:permit:delete')")
    public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
        permitService.deletePermit(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得作业票")
    @Parameter(name = "id", description = "编号", required = true)
    @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);
    }
    @GetMapping("/page")
    @Operation(summary = "获得作业票分页")
    @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));
        }
        return success(result);
    }
    @PutMapping("/approve")
    @Operation(summary = "审批作业票(通过/驳回)")
    @PreAuthorize("@ss.hasPermission('safety:permit:approve')")
    public CommonResult<Boolean> approve(@Valid @RequestBody SafetyWorkPermitApproveReqVO reqVO) {
        permitService.approvePermit(reqVO);
        return success(true);
    }
    @PutMapping("/start")
    @Operation(summary = "开始作业")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:permit:update')")
    public CommonResult<Boolean> start(@RequestParam("id") Long id) {
        permitService.startPermit(id);
        return success(true);
    }
    @PutMapping("/close")
    @Operation(summary = "结束作业并归档")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:permit:update')")
    public CommonResult<Boolean> close(@RequestParam("id") Long id) {
        permitService.closePermit(id);
        return success(true);
    }
    @PutMapping("/cancel")
    @Operation(summary = "作废作业票")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:permit:update')")
    public CommonResult<Boolean> cancel(@RequestParam("id") Long id) {
        permitService.cancelPermit(id);
        return success(true);
    }
    // ==================== ç›‘护记录 ====================
    @GetMapping("/monitor/list")
    @Operation(summary = "获得作业监护记录列表")
    @Parameter(name = "permitId", description = "作业票编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:permit:query')")
    public CommonResult<List<SafetyWorkPermitMonitorRespVO>> monitorList(@RequestParam("permitId") Long permitId) {
        var monitors = permitService.getMonitorList(permitId);
        permitService.fillMonitorGuardians(monitors);
        return success(BeanUtils.toBean(monitors, SafetyWorkPermitMonitorRespVO.class));
    }
    @PostMapping("/monitor/create")
    @Operation(summary = "新增作业监护记录")
    @PreAuthorize("@ss.hasPermission('safety:permit:update')")
    public CommonResult<Long> createMonitor(@Valid @RequestBody SafetyWorkPermitMonitorSaveReqVO reqVO) {
        return success(permitService.createMonitor(reqVO));
    }
    @DeleteMapping("/monitor/delete")
    @Operation(summary = "删除作业监护记录")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('safety:permit:update')")
    public CommonResult<Boolean> deleteMonitor(@RequestParam("id") Long id) {
        permitService.deleteMonitor(id);
        return success(true);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/cert/SafetyCertificatePageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.cert;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰¹ç§ä½œä¸šè¯åˆ†é¡µ Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ç‰¹ç§ä½œä¸šè¯åˆ†é¡µ Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyCertificatePageReqVO extends PageParam {
    @Schema(description = "持证人(员工编号)")
    private Long employeeId;
    @Schema(description = "持证人姓名(模糊匹配)")
    private String employeeName;
    @Schema(description = "工号")
    private String employeeNo;
    @Schema(description = "所属部门")
    private String deptName;
    @Schema(description = "证件类型")
    private String certType;
    @Schema(description = "证件编号")
    private String certNo;
    @Schema(description = "状态")
    private String status;
    @Schema(description = "有效期开始")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate expireDateStart;
    @Schema(description = "有效期截止")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate expireDateEnd;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/cert/SafetyCertificateRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.cert;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰¹ç§ä½œä¸šè¯ Response VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ç‰¹ç§ä½œä¸šè¯ Response VO")
@Data
public class SafetyCertificateRespVO {
    private Long id;
    @Schema(description = "持证人(员工编号)")
    private Long employeeId;
    @Schema(description = "持证人关联的系统用户编号")
    private Long userId;
    private String employeeName;
    private String employeeNo;
    private String deptName;
    private String phone;
    private String certType;
    @Schema(description = "证件类型名称")
    private String certTypeName;
    private String certNo;
    private String issuer;
    private LocalDate issueDate;
    private LocalDate expireDate;
    private LocalDate reviewDate;
    private Integer warnDays;
    private String status;
    @Schema(description = "状态名称")
    private String statusName;
    @Schema(description = "距到期天数,负数表示已过期")
    private Long remainingDays;
    private String attachment;
    private String remark;
    private LocalDateTime createTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/cert/SafetyCertificateSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.cert;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰¹ç§ä½œä¸šè¯æ–°å¢ž/修改 Request VO
 *
 * <p>持证人通过 employeeId å…³è”员工档案,姓名与部门由后端从档案带出,
 * å‰ç«¯åªéœ€é€‰äººï¼Œä¸æ‰‹å·¥å¡«å†™ï¼Œé¿å…ä¸ŽäººåŠ›èµ„æºæ•°æ®ä¸ä¸€è‡´ã€‚</p>
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ç‰¹ç§ä½œä¸šè¯æ–°å¢ž/修改 Request VO")
@Data
public class SafetyCertificateSaveReqVO {
    private Long id;
    @Schema(description = "持证人(员工编号)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "请选择持证人")
    private Long employeeId;
    @Schema(description = "证件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "HIGH_VOLTAGE_OPERATION")
    @NotBlank(message = "证件类型不能为空")
    @Pattern(regexp = "LOW_VOLTAGE_ELECTRICIAN|HIGH_VOLTAGE_OPERATION|WELDING|HOIST|HEIGHT_WORK|LIMITED_SPACE|FORKLIFT|BOILER|OTHER",
            message = "证件类型不合法")
    private String certType;
    @Schema(description = "证件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "T330102199001011234")
    @NotBlank(message = "证件编号不能为空")
    @Size(max = 64)
    private String certNo;
    @Schema(description = "发证机关")
    @Size(max = 100)
    private String issuer;
    @Schema(description = "发证日期")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate issueDate;
    @Schema(description = "有效期至", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "有效期不能为空")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate expireDate;
    @Schema(description = "下次复审日期")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate reviewDate;
    @Schema(description = "到期预警提前天数", example = "30")
    @Min(value = 0, message = "预警天数不能为负数")
    @Max(value = 365, message = "预警天数不能超过 365 å¤©")
    private Integer warnDays;
    @Schema(description = "状态", example = "VALID")
    @Pattern(regexp = "VALID|EXPIRING|EXPIRED|REVOKED", message = "状态不合法")
    private String status;
    @Schema(description = "证件扫描件 URL,逗号分隔")
    @Size(max = 500)
    private String attachment;
    @Schema(description = "备注")
    @Size(max = 500)
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardAssignReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.hazard;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - é𐿂£åˆ†æ´¾ Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - é𐿂£åˆ†æ´¾ Request VO")
@Data
public class SafetyHazardAssignReqVO {
    @Schema(description = "隐患编号", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "隐患编号不能为空")
    private Long id;
    @Schema(description = "整改责任人(员工编号,关联 hrm_employee)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "请选择整改责任人")
    private Long assigneeId;
    @Schema(description = "整改期限")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate rectifyDeadline;
    @Schema(description = "分派说明")
    @Size(max = 500)
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.hazard;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - é𐿂£åˆ†é¡µ Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - é𐿂£åˆ†é¡µ Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyHazardPageReqVO extends PageParam {
    private String code;
    private String title;
    private String level;
    private String source;
    private String status;
    private String assignee;
    private String location;
    @Schema(description = "上报时间范围")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime[] reportTime;
    @Schema(description = "上报时间开始(内部使用)", hidden = true)
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime reportTimeStart;
    @Schema(description = "上报时间结束(内部使用)", hidden = true)
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime reportTimeEnd;
    public void normalizeTimeRange() {
        if (reportTime != null && reportTime.length == 2) {
            this.reportTimeStart = reportTime[0];
            this.reportTimeEnd = reportTime[1];
        }
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardRecheckReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.hazard;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
 * å®‰å…¨ç”Ÿäº§ - é𐿂£å¤æŸ¥ Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - é𐿂£å¤æŸ¥ Request VO")
@Data
public class SafetyHazardRecheckReqVO {
    @Schema(description = "隐患编号", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "隐患编号不能为空")
    private Long id;
    @Schema(description = "复查人(员工编号,关联 hrm_employee)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "请选择复查人")
    private Long recheckerId;
    @Schema(description = "复查结论:PASSED-通过,REJECTED-不通过", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank(message = "复查结论不能为空")
    @Pattern(regexp = "PASSED|REJECTED", message = "复查结论只能为通过或不通过")
    private String recheckResult;
    @Schema(description = "复查意见")
    @Size(max = 500)
    private String recheckRemark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardRectifyReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.hazard;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
 * å®‰å…¨ç”Ÿäº§ - é𐿂£æ•´æ”¹ Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - é𐿂£æ•´æ”¹ Request VO")
@Data
public class SafetyHazardRectifyReqVO {
    @Schema(description = "隐患编号", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "隐患编号不能为空")
    private Long id;
    @Schema(description = "整改措施与结果", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank(message = "整改措施与结果不能为空")
    @Size(max = 2000)
    private String rectifyDesc;
    @Schema(description = "整改后照片 URL,逗号分隔")
    @Size(max = 2000)
    private String rectifyPhotoUrls;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.hazard;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - é𐿂£ Response VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - é𐿂£ Response VO")
@Data
public class SafetyHazardRespVO {
    private Long id;
    private String code;
    private String title;
    private String level;
    @Schema(description = "隐患等级名称")
    private String levelName;
    private String source;
    @Schema(description = "来源名称")
    private String sourceName;
    private Long inspectionId;
    private Long pointId;
    private String pointName;
    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;
    @Schema(description = "是否已超期")
    private Boolean overdue;
    private String rectifyDesc;
    private String rectifyPhotoUrls;
    private LocalDateTime rectifyTime;
    @Schema(description = "复查人编号")
    private Long recheckerId;
    private String rechecker;
    private LocalDateTime recheckTime;
    private String recheckResult;
    @Schema(description = "复查结论名称")
    private String recheckResultName;
    private String recheckRemark;
    private String status;
    @Schema(description = "状态名称")
    private String statusName;
    private LocalDateTime closeTime;
    private String remark;
    private LocalDateTime createTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.hazard;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - é𐿂£ä¸ŠæŠ¥/修改 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - é𐿂£ä¸ŠæŠ¥/修改 Request VO")
@Data
public class SafetyHazardSaveReqVO {
    private Long id;
    @Schema(description = "隐患标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "配电室门未上锁")
    @NotBlank(message = "隐患标题不能为空")
    @Size(max = 200)
    private String title;
    @Schema(description = "隐患等级", example = "GENERAL")
    @Pattern(regexp = "GENERAL|MAJOR", message = "隐患等级不合法")
    private String level;
    @Schema(description = "来源", example = "INSPECTION")
    @Pattern(regexp = "INSPECTION|SELF_CHECK|REPORT|EXTERNAL", message = "隐患来源不合法")
    private String source;
    @Schema(description = "关联巡检记录编号")
    private Long inspectionId;
    @Schema(description = "关联巡检点位编号")
    private Long pointId;
    @Schema(description = "点位名称")
    @Size(max = 100)
    private String pointName;
    @Schema(description = "隐患位置")
    @Size(max = 200)
    private String location;
    @Schema(description = "隐患描述")
    @Size(max = 2000)
    private String description;
    @Schema(description = "现场照片 URL,逗号分隔(移动端拍照上传)")
    @Size(max = 2000)
    private String photoUrls;
    @Schema(description = "上报人(员工编号,关联 hrm_employee)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "请选择上报人")
    private Long reporterId;
    @Schema(description = "上报时间")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime reportTime;
    @Schema(description = "整改期限")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate rectifyDeadline;
    @Schema(description = "备注")
    @Size(max = 500)
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.inspection;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - å·¡æ£€è®°å½•分页 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - å·¡æ£€è®°å½•分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyInspectionPageReqVO extends PageParam {
    private Long pointId;
    private String pointType;
    private String inspector;
    private String result;
    @Schema(description = "巡检时间范围")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime[] inspectTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionPointPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.inspection;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * å®‰å…¨ç”Ÿäº§ - å·¡æ£€ç‚¹ä½åˆ†é¡µ Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - å·¡æ£€ç‚¹ä½åˆ†é¡µ Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyInspectionPointPageReqVO extends PageParam {
    private String code;
    private String name;
    private String pointType;
    private String workshopName;
    private Integer status;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionPointRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.inspection;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - å·¡æ£€ç‚¹ä½ Response VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - å·¡æ£€ç‚¹ä½ Response VO")
@Data
public class SafetyInspectionPointRespVO {
    private Long id;
    private String code;
    private String name;
    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 = "最近一次巡检时间")
    private LocalDateTime lastInspectTime;
    private String remark;
    private LocalDateTime createTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionPointSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.inspection;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Data;
/**
 * å®‰å…¨ç”Ÿäº§ - å·¡æ£€ç‚¹ä½æ–°å¢ž/修改 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - å·¡æ£€ç‚¹ä½æ–°å¢ž/修改 Request VO")
@Data
public class SafetyInspectionPointSaveReqVO {
    private Long id;
    @Schema(description = "点位编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "PD-001")
    @NotBlank(message = "点位编码不能为空")
    @Size(max = 64)
    private String code;
    @Schema(description = "点位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "1#高压配电室")
    @NotBlank(message = "点位名称不能为空")
    @Size(max = 100)
    private String name;
    @Schema(description = "点位类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "HIGH_VOLTAGE_ROOM")
    @NotBlank(message = "点位类型不能为空")
    @Pattern(regexp = "HIGH_VOLTAGE_ROOM|UNIT_AREA|DANGER_CHEMICAL|FIRE_AREA|WORKSHOP|OTHER", message = "点位类型不合法")
    private String pointType;
    @Schema(description = "所属车间(关联 mes_md_workshop)")
    private Long workshopId;
    @Schema(description = "具体位置")
    @Size(max = 200)
    private String location;
    @Schema(description = "巡检项目清单,按行分隔")
    @Size(max = 2000)
    private String inspectItems;
    @Schema(description = "巡检周期", example = "DAILY")
    @Pattern(regexp = "SHIFT|DAILY|WEEKLY|MONTHLY", message = "巡检周期不合法")
    private String inspectCycle;
    @Schema(description = "责任人(员工编号,关联 hrm_employee)")
    private Long ownerId;
    @Schema(description = "状态:0-启用,1-停用", example = "0")
    @NotNull(message = "状态不能为空")
    private Integer status;
    @Schema(description = "备注")
    @Size(max = 500)
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.inspection;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - å·¡æ£€è®°å½• Response VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - å·¡æ£€è®°å½• Response VO")
@Data
public class SafetyInspectionRespVO {
    private Long id;
    private Long pointId;
    private String pointName;
    private String pointType;
    @Schema(description = "点位类型名称")
    private String pointTypeName;
    private LocalDateTime inspectTime;
    @Schema(description = "巡检人编号")
    private Long inspectorId;
    private String inspector;
    private String result;
    @Schema(description = "巡检结果名称")
    private String resultName;
    private String content;
    private String photoUrls;
    private Integer hazardCount;
    private String remark;
    private LocalDateTime createTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.inspection;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - å·¡æ£€è®°å½•新增/修改 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - å·¡æ£€è®°å½•新增/修改 Request VO")
@Data
public class SafetyInspectionSaveReqVO {
    private Long id;
    @Schema(description = "巡检点位编号", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "巡检点位不能为空")
    private Long pointId;
    @Schema(description = "巡检时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "巡检时间不能为空")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime inspectTime;
    @Schema(description = "巡检人(员工编号,关联 hrm_employee)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "请选择巡检人")
    private Long inspectorId;
    @Schema(description = "巡检结果", example = "NORMAL")
    @Pattern(regexp = "NORMAL|ABNORMAL", message = "巡检结果只能为正常或异常")
    private String result;
    @Schema(description = "巡检内容与情况")
    @Size(max = 2000)
    private String content;
    @Schema(description = "现场照片 URL,逗号分隔")
    @Size(max = 2000)
    private String photoUrls;
    @Schema(description = "本次发现的隐患数", example = "0")
    @Min(value = 0, message = "隐患数不能为负数")
    private Integer hazardCount;
    @Schema(description = "备注")
    @Size(max = 500)
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/ledger/SafetyLedgerExportReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.ledger;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - æ ‡å‡†åŒ–台账导出 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - æ ‡å‡†åŒ–台账导出 Request VO")
@Data
public class SafetyLedgerExportReqVO {
    @Schema(description = "台账类型:ALL-全套,CERT-特种作业证,HAZARD-隐患,PERMIT-作业票,MATERIAL-物资,TRAINING-培训,DRILL-演练,ACCIDENT-事故,INSPECTION-巡检",
            example = "ALL")
    @Pattern(regexp = "ALL|CERT|HAZARD|PERMIT|MATERIAL|TRAINING|DRILL|ACCIDENT|INSPECTION", message = "台账类型不合法")
    private String type;
    @Schema(description = "开始日期")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate startDate;
    @Schema(description = "结束日期")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate endDate;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/ledger/SafetyOverviewRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.ledger;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
 * å®‰å…¨ç”Ÿäº§ - æ€»è§ˆç»Ÿè®¡ Response VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - å®‰å…¨ç”Ÿäº§æ€»è§ˆç»Ÿè®¡ Response VO")
@Data
public class SafetyOverviewRespVO {
    @Schema(description = "特种作业证总数")
    private Long certTotal;
    @Schema(description = "即将到期证件数")
    private Long certExpiring;
    @Schema(description = "已过期证件数")
    private Long certExpired;
    @Schema(description = "隐患总数")
    private Long hazardTotal;
    @Schema(description = "未闭环隐患数")
    private Long hazardOpen;
    @Schema(description = "已超期未闭环隐患数")
    private Long hazardOverdue;
    @Schema(description = "隐患闭环率(0-100)")
    private Integer hazardCloseRate;
    @Schema(description = "待审批作业票数")
    private Long permitPending;
    @Schema(description = "进行中作业票数")
    private Long permitInProgress;
    @Schema(description = "物资低于安全库存数")
    private Long materialBelowSafeStock;
    @Schema(description = "物资临近有效期数")
    private Long materialExpiring;
    @Schema(description = "本月培训场次")
    private Long trainingThisMonth;
    @Schema(description = "本月演练场次")
    private Long drillThisMonth;
    @Schema(description = "本月事故数")
    private Long accidentThisMonth;
    @Schema(description = "隐患趋势(近 6 ä¸ªæœˆï¼ŒæŒ‰æœˆï¼‰")
    private List<MonthlyCount> hazardTrend;
    @Schema(description = "事故趋势(近 6 ä¸ªæœˆï¼ŒæŒ‰æœˆï¼‰")
    private List<MonthlyCount> accidentTrend;
    /**
     * æŒ‰æœˆè®¡æ•°
     */
    @Data
    public static class MonthlyCount {
        @Schema(description = "月份,格式 yyyy-MM", example = "2026-10")
        private String month;
        @Schema(description = "数量")
        private Long count;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.material;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰©èµ„台账分页 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ç‰©èµ„台账分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyMaterialPageReqVO extends PageParam {
    private String code;
    private String name;
    private String category;
    private String storageLocation;
    private Integer status;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.material;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰©èµ„台账 Response VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ç‰©èµ„台账 Response VO")
@Data
public class SafetyMaterialRespVO {
    private Long id;
    private String code;
    private String name;
    private String category;
    @Schema(description = "类别名称")
    private String categoryName;
    private String spec;
    private String unit;
    private BigDecimal stockQty;
    private BigDecimal safeStock;
    @Schema(description = "是否低于安全库存")
    private Boolean belowSafeStock;
    private String storageLocation;
    private LocalDate expireDate;
    @Schema(description = "距有效期天数,负数表示已过期")
    private Long expireRemainingDays;
    private Integer validityMonths;
    @Schema(description = "保管人编号")
    private Long managerId;
    private String manager;
    private Integer status;
    private String remark;
    private LocalDateTime createTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,76 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.material;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰©èµ„台账新增/修改 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ç‰©èµ„台账新增/修改 Request VO")
@Data
public class SafetyMaterialSaveReqVO {
    private Long id;
    @Schema(description = "物资编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "PM-001")
    @NotBlank(message = "物资编码不能为空")
    @Size(max = 64)
    private String code;
    @Schema(description = "物资名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "10kV ç»ç¼˜æ‰‹å¥—")
    @NotBlank(message = "物资名称不能为空")
    @Size(max = 100)
    private String name;
    @Schema(description = "类别", requiredMode = Schema.RequiredMode.REQUIRED, example = "INSULATING")
    @NotBlank(message = "物资类别不能为空")
    @Pattern(regexp = "INSULATING|HAZARD_CHEMICAL|FIRE|PPE|OTHER", message = "物资类别不合法")
    private String category;
    @Schema(description = "规格型号")
    @Size(max = 100)
    private String spec;
    @Schema(description = "计量单位", example = "双")
    @Size(max = 20)
    private String unit;
    @Schema(description = "当前库存数量")
    @DecimalMin(value = "0", message = "库存数量不能为负数")
    private BigDecimal stockQty;
    @Schema(description = "安全库存")
    @DecimalMin(value = "0", message = "安全库存不能为负数")
    private BigDecimal safeStock;
    @Schema(description = "存放位置")
    @Size(max = 100)
    private String storageLocation;
    @Schema(description = "有效期至")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate expireDate;
    @Schema(description = "有效期月数")
    @Min(value = 0, message = "有效期月数不能为负数")
    private Integer validityMonths;
    @Schema(description = "保管人(员工编号,关联 hrm_employee)")
    private Long managerId;
    @Schema(description = "状态:0-启用,1-停用", example = "0")
    @NotNull(message = "状态不能为空")
    private Integer status;
    @Schema(description = "备注")
    @Size(max = 500)
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialStockPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.material;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰©èµ„出入库流水分页 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ç‰©èµ„出入库流水分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyMaterialStockPageReqVO extends PageParam {
    private Long materialId;
    private String materialName;
    private String category;
    private String bizType;
    @Schema(description = "业务日期范围")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate[] bizDate;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialStockRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.material;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰©èµ„出入库流水 Response VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ç‰©èµ„出入库流水 Response VO")
@Data
public class SafetyMaterialStockRespVO {
    private Long id;
    private Long materialId;
    private String materialName;
    private String category;
    @Schema(description = "类别名称")
    private String categoryName;
    private String bizType;
    @Schema(description = "业务类型名称")
    private String bizTypeName;
    private BigDecimal quantity;
    private BigDecimal stockBefore;
    private BigDecimal stockAfter;
    private LocalDate bizDate;
    private String operator;
    private String receiver;
    private String purpose;
    private String remark;
    private LocalDateTime createTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialStockSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.material;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰©èµ„出入库登记 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ç‰©èµ„出入库登记 Request VO")
@Data
public class SafetyMaterialStockSaveReqVO {
    @Schema(description = "物资编号", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "物资不能为空")
    private Long materialId;
    @Schema(description = "业务类型:IN-入库,OUT-出库", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank(message = "业务类型不能为空")
    @Pattern(regexp = "IN|OUT", message = "业务类型只能为入库或出库")
    private String bizType;
    @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
    @NotNull(message = "数量不能为空")
    @DecimalMin(value = "0", inclusive = false, message = "数量必须大于零")
    @Digits(integer = 15, fraction = 3)
    private BigDecimal quantity;
    @Schema(description = "业务日期")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate bizDate;
    @Schema(description = "经办人", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank(message = "经办人不能为空")
    @Size(max = 50)
    private String operator;
    @Schema(description = "领用人(出库填写)")
    @Size(max = 50)
    private String receiver;
    @Schema(description = "用途/去向")
    @Size(max = 200)
    private String purpose;
    @Schema(description = "备注")
    @Size(max = 500)
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/org/SafetyOrgRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.org;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
 * å®‰å…¨ç”Ÿäº§ - å®‰å…¨ç»„织 Response VO(树形)
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - å®‰å…¨ç»„织 Response VO")
@Data
public class SafetyOrgRespVO {
    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;
    private String duty;
    private Integer sort;
    private Integer status;
    private String remark;
    private LocalDateTime createTime;
    @Schema(description = "子组织")
    private List<SafetyOrgRespVO> children;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/org/SafetyOrgSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.org;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
 * å®‰å…¨ç”Ÿäº§ - å®‰å…¨ç»„织新增/修改 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - å®‰å…¨ç»„织新增/修改 Request VO")
@Data
public class SafetyOrgSaveReqVO {
    private Long id;
    @Schema(description = "父级编号,0 ä¸ºé¡¶çº§", example = "0")
    private Long parentId;
    @Schema(description = "组织名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "安全生产委员会")
    @NotBlank(message = "组织名称不能为空")
    @Size(max = 100, message = "组织名称长度不能超过 100 å­—")
    private String name;
    @Schema(description = "负责人(员工编号,关联 hrm_employee)")
    private Long leaderId;
    @Schema(description = "成员姓名,逗号分隔")
    @Size(max = 500)
    private String memberNames;
    @Schema(description = "安全职责")
    @Size(max = 500)
    private String duty;
    @Schema(description = "显示顺序", example = "1")
    @NotNull(message = "显示顺序不能为空")
    private Integer sort;
    @Schema(description = "状态:0-启用,1-停用", example = "0")
    @NotNull(message = "状态不能为空")
    private Integer status;
    @Schema(description = "备注")
    @Size(max = 500)
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitApproveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.permit;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
 * å®‰å…¨ç”Ÿäº§ - é«˜å±ä½œä¸šç¥¨å®¡æ‰¹ Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - é«˜å±ä½œä¸šç¥¨å®¡æ‰¹ Request VO")
@Data
public class SafetyWorkPermitApproveReqVO {
    @Schema(description = "作业票编号", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "作业票编号不能为空")
    private Long id;
    @Schema(description = "审批人(员工编号,关联 hrm_employee)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "请选择审批人")
    private Long approverId;
    @Schema(description = "审批结论:APPROVED-通过,REJECTED-驳回", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank(message = "审批结论不能为空")
    @Pattern(regexp = "APPROVED|REJECTED", message = "审批结论只能为通过或驳回")
    private String decision;
    @Schema(description = "审批意见")
    @Size(max = 500)
    private String approveComment;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitMonitorRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.permit;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - ä½œä¸šç›‘护记录 Response VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ä½œä¸šç›‘护记录 Response VO")
@Data
public class SafetyWorkPermitMonitorRespVO {
    private Long id;
    private Long permitId;
    private LocalDateTime monitorTime;
    @Schema(description = "监护人编号")
    private Long guardianId;
    private String guardian;
    private String monitorContent;
    private String abnormal;
    private String photoUrls;
    private LocalDateTime createTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitMonitorSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.permit;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - ä½œä¸šç›‘护记录新增 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ä½œä¸šç›‘护记录新增 Request VO")
@Data
public class SafetyWorkPermitMonitorSaveReqVO {
    private Long id;
    @Schema(description = "作业票编号", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "作业票编号不能为空")
    private Long permitId;
    @Schema(description = "监护时间")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime monitorTime;
    @Schema(description = "监护人(员工编号,关联 hrm_employee)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "请选择监护人")
    private Long guardianId;
    @Schema(description = "监护内容与情况")
    @Size(max = 1000)
    private String monitorContent;
    @Schema(description = "异常情况与处置")
    @Size(max = 500)
    private String abnormal;
    @Schema(description = "现场照片 URL,逗号分隔")
    @Size(max = 2000)
    private String photoUrls;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.permit;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - é«˜å±ä½œä¸šç¥¨åˆ†é¡µ Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - é«˜å±ä½œä¸šç¥¨åˆ†é¡µ Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyWorkPermitPageReqVO extends PageParam {
    private String code;
    private String title;
    private String permitType;
    private String status;
    private String riskLevel;
    private String applicant;
    private String workshopName;
    @Schema(description = "计划开始时间范围")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime[] planStartTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.permit;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - é«˜å±ä½œä¸šç¥¨ Response VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - é«˜å±ä½œä¸šç¥¨ Response VO")
@Data
public class SafetyWorkPermitRespVO {
    private Long id;
    private String code;
    private String permitType;
    @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;
    private LocalDateTime planEndTime;
    private LocalDateTime actualStartTime;
    private LocalDateTime actualEndTime;
    private String riskLevel;
    @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 = "状态名称")
    private String statusName;
    private String attachment;
    private String photoUrls;
    @Schema(description = "监护人是否必填(动火/高压带电/有限空间)")
    private Boolean guardianRequired;
    private String remark;
    private LocalDateTime createTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,84 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.permit;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - é«˜å±ä½œä¸šç¥¨ç”³è¯·/修改 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - é«˜å±ä½œä¸šç¥¨ç”³è¯·/修改 Request VO")
@Data
public class SafetyWorkPermitSaveReqVO {
    private Long id;
    @Schema(description = "作业类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "HOT_WORK")
    @NotBlank(message = "作业类型不能为空")
    @Pattern(regexp = "HOT_WORK|LIVE_HIGH_VOLTAGE|LIMITED_SPACE|WORK_AT_HEIGHT|LIFTING|TEMP_ELECTRIC|OTHER",
            message = "作业类型不合法")
    private String permitType;
    @Schema(description = "作业名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "2#主变检修动火")
    @NotBlank(message = "作业名称不能为空")
    @Size(max = 200)
    private String title;
    @Schema(description = "作业车间(关联 mes_md_workshop)")
    private Long workshopId;
    @Schema(description = "作业地点")
    @Size(max = 200)
    private String location;
    @Schema(description = "作业内容与范围")
    @Size(max = 2000)
    private String workContent;
    @Schema(description = "申请人(员工编号,关联 hrm_employee)", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "请选择申请人")
    private Long applicantId;
    @Schema(description = "申请时间")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime applyTime;
    @Schema(description = "计划开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "计划开始时间不能为空")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime planStartTime;
    @Schema(description = "计划结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "计划结束时间不能为空")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime planEndTime;
    @Schema(description = "风险等级", example = "HIGH")
    @Pattern(regexp = "LOW|NORMAL|HIGH|VERY_HIGH", message = "风险等级不合法")
    private String riskLevel;
    @Schema(description = "安全措施")
    @Size(max = 2000)
    private String safetyMeasures;
    @Schema(description = "监护人(员工编号,关联 hrm_employee)")
    private Long guardianId;
    @Schema(description = "作业票附件 URL,逗号分隔")
    @Size(max = 2000)
    private String attachment;
    @Schema(description = "现场照片 URL,逗号分隔")
    @Size(max = 2000)
    private String photoUrls;
    @Schema(description = "备注")
    @Size(max = 500)
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyAccidentPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.train;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - äº‹æ•…登记分页 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - äº‹æ•…登记分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyAccidentPageReqVO extends PageParam {
    private String code;
    private String title;
    private String accidentType;
    private String level;
    private String status;
    @Schema(description = "发生时间范围")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime[] occurTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyAccidentRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.train;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - äº‹æ•…登记 Response VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - äº‹æ•…登记 Response VO")
@Data
public class SafetyAccidentRespVO {
    private Long id;
    private String code;
    private String title;
    private String accidentType;
    @Schema(description = "事故类型名称")
    private String accidentTypeName;
    private String level;
    @Schema(description = "事故等级名称")
    private String levelName;
    private LocalDateTime occurTime;
    private String location;
    @Schema(description = "所属车间编号")
    private Long workshopId;
    private String workshopName;
    private String casualtyDesc;
    private BigDecimal economicLoss;
    private String involvedPersons;
    private String description;
    private String causeAnalysis;
    private String measures;
    @Schema(description = "登记人编号")
    private Long reporterId;
    private String reporter;
    private LocalDateTime reportTime;
    private String status;
    @Schema(description = "状态名称")
    private String statusName;
    private String attachment;
    private String remark;
    private LocalDateTime createTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyAccidentSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,91 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.train;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - äº‹æ•…登记新增/修改 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - äº‹æ•…登记新增/修改 Request VO")
@Data
public class SafetyAccidentSaveReqVO {
    private Long id;
    @Schema(description = "事故名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "10kV å¼€å…³æŸœè¯¯æ“ä½œè§¦ç”µ")
    @NotBlank(message = "事故名称不能为空")
    @Size(max = 200)
    private String title;
    @Schema(description = "事故类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "ELECTRIC_SHOCK")
    @NotBlank(message = "事故类型不能为空")
    @Pattern(regexp = "ELECTRIC_SHOCK|FIRE|FALL|OBJECT_STRIKE|MECHANICAL|CHEMICAL|VEHICLE|OTHER", message = "事故类型不合法")
    private String accidentType;
    @Schema(description = "事故等级", example = "GENERAL")
    @Pattern(regexp = "SLIGHT|GENERAL|SEVERE|MAJOR", message = "事故等级不合法")
    private String level;
    @Schema(description = "发生时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "发生时间不能为空")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime occurTime;
    @Schema(description = "发生地点")
    @Size(max = 200)
    private String location;
    @Schema(description = "所属车间(关联 mes_md_workshop)")
    private Long workshopId;
    @Schema(description = "伤亡情况")
    @Size(max = 500)
    private String casualtyDesc;
    @Schema(description = "直接经济损失(元)")
    @DecimalMin(value = "0", message = "经济损失不能为负数")
    private BigDecimal economicLoss;
    @Schema(description = "涉事人员姓名,逗号分隔")
    @Size(max = 500)
    private String involvedPersons;
    @Schema(description = "事故经过")
    @Size(max = 2000)
    private String description;
    @Schema(description = "原因分析")
    @Size(max = 2000)
    private String causeAnalysis;
    @Schema(description = "处理措施与整改要求")
    @Size(max = 2000)
    private String measures;
    @Schema(description = "登记人(员工编号,关联 hrm_employee)")
    private Long reporterId;
    @Schema(description = "登记时间")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime reportTime;
    @Schema(description = "状态", example = "REPORTED")
    @Pattern(regexp = "REPORTED|INVESTIGATING|HANDLED|CLOSED", message = "状态不合法")
    private String status;
    @Schema(description = "现场照片/调查报告 URL,逗号分隔")
    @Size(max = 2000)
    private String attachment;
    @Schema(description = "备注")
    @Size(max = 500)
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyDrillPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.train;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - æ¼”练记录分页 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - æ¼”练记录分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyDrillPageReqVO extends PageParam {
    private String title;
    private String drillType;
    private String organizer;
    @Schema(description = "演练日期范围")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate[] drillDate;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyDrillRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.train;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - æ¼”练记录 Response VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - æ¼”练记录 Response VO")
@Data
public class SafetyDrillRespVO {
    private Long id;
    private String title;
    private String drillType;
    @Schema(description = "演练类型名称")
    private String drillTypeName;
    private LocalDate drillDate;
    private String location;
    @Schema(description = "组织人编号")
    private Long organizerId;
    private String organizer;
    private String participants;
    private Integer participantCount;
    private String planDesc;
    private String processDesc;
    private String summary;
    private Integer durationMinutes;
    private String attachment;
    private String remark;
    private LocalDateTime createTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyDrillSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.train;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - æ¶ˆé˜²æ¼”练记录新增/修改 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - æ¶ˆé˜²æ¼”练记录新增/修改 Request VO")
@Data
public class SafetyDrillSaveReqVO {
    private Long id;
    @Schema(description = "演练名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026 å¹´åº¦æ¶ˆé˜²ç–æ•£æ¼”练")
    @NotBlank(message = "演练名称不能为空")
    @Size(max = 200)
    private String title;
    @Schema(description = "演练类型", example = "FIRE")
    @Pattern(regexp = "FIRE|EMERGENCY_EVAC|ELECTRIC_ACCIDENT|CHEMICAL_LEAK|OTHER", message = "演练类型不合法")
    private String drillType;
    @Schema(description = "演练日期", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "演练日期不能为空")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate drillDate;
    @Schema(description = "演练地点")
    @Size(max = 200)
    private String location;
    @Schema(description = "组织人(员工编号,关联 hrm_employee)")
    private Long organizerId;
    @Schema(description = "参加人员姓名,逗号分隔")
    @Size(max = 2000)
    private String participants;
    @Schema(description = "参加人数")
    @Min(value = 0, message = "参加人数不能为负数")
    private Integer participantCount;
    @Schema(description = "演练方案")
    @Size(max = 2000)
    private String planDesc;
    @Schema(description = "演练过程记录")
    @Size(max = 2000)
    private String processDesc;
    @Schema(description = "演练总结与改进")
    @Size(max = 2000)
    private String summary;
    @Schema(description = "演练时长(分钟)")
    @Min(value = 0, message = "演练时长不能为负数")
    private Integer durationMinutes;
    @Schema(description = "演练照片/记录表 URL,逗号分隔")
    @Size(max = 2000)
    private String attachment;
    @Schema(description = "备注")
    @Size(max = 500)
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyTrainingPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.train;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - åŸ¹è®­è®°å½•分页 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - åŸ¹è®­è®°å½•分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyTrainingPageReqVO extends PageParam {
    private String title;
    private String trainingType;
    private String trainer;
    @Schema(description = "培训日期范围")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate[] trainDate;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyTrainingRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.train;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - åŸ¹è®­è®°å½• Response VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - åŸ¹è®­è®°å½• Response VO")
@Data
public class SafetyTrainingRespVO {
    private Long id;
    private String title;
    private String trainingType;
    @Schema(description = "培训类型名称")
    private String trainingTypeName;
    private LocalDate trainDate;
    private BigDecimal durationHours;
    @Schema(description = "讲师/组织人编号")
    private Long trainerId;
    private String trainer;
    private String location;
    private String participants;
    private Integer participantCount;
    private String content;
    private String examResult;
    private String attachment;
    private String remark;
    private LocalDateTime createTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyTrainingSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.mes.controller.admin.safety.vo.train;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - åŸ¹è®­è®°å½•新增/修改 Request VO
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - åŸ¹è®­è®°å½•新增/修改 Request VO")
@Data
public class SafetyTrainingSaveReqVO {
    private Long id;
    @Schema(description = "培训主题", requiredMode = Schema.RequiredMode.REQUIRED, example = "年度安全规程培训")
    @NotBlank(message = "培训主题不能为空")
    @Size(max = 200)
    private String title;
    @Schema(description = "培训类型", example = "SAFETY")
    @Pattern(regexp = "SAFETY|SKILL|LAW|EMERGENCY|INDUCTION|OTHER", message = "培训类型不合法")
    private String trainingType;
    @Schema(description = "培训日期", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "培训日期不能为空")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate trainDate;
    @Schema(description = "培训学时", example = "2.0")
    @DecimalMin(value = "0", message = "培训学时不能为负数")
    private BigDecimal durationHours;
    @Schema(description = "讲师/组织人(员工编号,关联 hrm_employee)")
    private Long trainerId;
    @Schema(description = "培训地点")
    @Size(max = 200)
    private String location;
    @Schema(description = "参加人员姓名,逗号分隔")
    @Size(max = 2000)
    private String participants;
    @Schema(description = "参加人数")
    @Min(value = 0, message = "参加人数不能为负数")
    private Integer participantCount;
    @Schema(description = "培训内容")
    @Size(max = 2000)
    private String content;
    @Schema(description = "考核情况")
    @Size(max = 2000)
    private String examResult;
    @Schema(description = "签到表/课件/照片 URL,逗号分隔")
    @Size(max = 2000)
    private String attachment;
    @Schema(description = "备注")
    @Size(max = 500)
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyAccidentDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.safety;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - äº‹æ•…登记 DO
 *
 * @author è¶…级管理员
 */
@TableName("safety_accident")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyAccidentDO extends BaseDO {
    @TableId
    private Long id;
    /** äº‹æ•…编号 */
    private String code;
    /** äº‹æ•…名称 */
    private String title;
    /** äº‹æ•…类型,见 SafetyAccidentTypeEnum */
    private String accidentType;
    /** äº‹æ•…等级,见 SafetyAccidentLevelEnum */
    private String level;
    /** å‘生时间 */
    private LocalDateTime occurTime;
    /** å‘生地点 */
    private String location;
    /** æ‰€å±žè½¦é—´ï¼ˆå…³è” mes_md_workshop.id) */
    private Long workshopId;
    /** æ‰€å±žè½¦é—´åç§°ï¼ˆå¿«ç…§ï¼Œç”±è½¦é—´ä¸»æ•°æ®å¸¦å‡ºï¼‰ */
    private String workshopName;
    /** ä¼¤äº¡æƒ…况 */
    private String casualtyDesc;
    /** ç›´æŽ¥ç»æµŽæŸå¤±ï¼ˆå…ƒï¼‰ */
    private BigDecimal economicLoss;
    /** æ¶‰äº‹äººå‘˜å§“名,逗号分隔 */
    private String involvedPersons;
    /** äº‹æ•…经过 */
    private String description;
    /** åŽŸå› åˆ†æž */
    private String causeAnalysis;
    /** å¤„理措施与整改要求 */
    private String measures;
    /** ç™»è®°äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long reporterId;
    /** ç™»è®°äººå§“名(快照,由员工档案带出) */
    private String reporter;
    /** ç™»è®°æ—¶é—´ */
    private LocalDateTime reportTime;
    /** çŠ¶æ€ï¼Œè§ SafetyAccidentStatusEnum */
    private String status;
    /** çŽ°åœºç…§ç‰‡/调查报告 URL,逗号分隔 */
    private String attachment;
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyCertificateDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.safety;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰¹ç§ä½œä¸šè¯å°è´¦ DO
 *
 * <p>覆盖电工证、高压操作证等特种作业资格证件,按有效期自动判断状态并预警。</p>
 *
 * @author è¶…级管理员
 */
@TableName("safety_certificate")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyCertificateDO extends BaseDO {
    @TableId
    private Long id;
    /**
     * æŒè¯äººï¼ˆå‘˜å·¥ç¼–号)
     *
     * å…³è” HrmEmployeeDO#getId(),姓名/工号/部门均由此关联带出
     */
    private Long employeeId;
    /** æŒè¯äººå§“名(快照,由员工档案带出,用于员工档案变更后追溯开证时信息) */
    private String employeeName;
    /** å·¥å·ï¼ˆå¿«ç…§ï¼‰ */
    private String employeeNo;
    /** æ‰€å±žéƒ¨é—¨ï¼ˆå¿«ç…§ï¼‰ */
    private String deptName;
    /** è¯ä»¶ç±»åž‹ï¼Œè§ SafetyCertificateTypeEnum */
    private String certType;
    /** è¯ä»¶ç¼–号 */
    private String certNo;
    /** å‘证机关 */
    private String issuer;
    /** å‘证日期 */
    private LocalDate issueDate;
    /** æœ‰æ•ˆæœŸè‡³ */
    private LocalDate expireDate;
    /** ä¸‹æ¬¡å¤å®¡æ—¥æœŸ */
    private LocalDate reviewDate;
    /** åˆ°æœŸé¢„警提前天数 */
    private Integer warnDays;
    /** çŠ¶æ€ï¼Œè§ SafetyCertificateStatusEnum */
    private String status;
    /** è¯ä»¶æ‰«æä»¶ URL,逗号分隔 */
    private String attachment;
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyDrillDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.safety;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - æ¶ˆé˜²æ¼”练记录 DO
 *
 * @author è¶…级管理员
 */
@TableName("safety_drill")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyDrillDO extends BaseDO {
    @TableId
    private Long id;
    /** æ¼”练名称 */
    private String title;
    /** æ¼”练类型,见 SafetyDrillTypeEnum */
    private String drillType;
    /** æ¼”练日期 */
    private LocalDate drillDate;
    /** æ¼”练地点 */
    private String location;
    /** ç»„织人(员工编号,关联 hrm_employee.id) */
    private Long organizerId;
    /** ç»„织人姓名(快照,由员工档案带出) */
    private String organizer;
    /** å‚加人员姓名,逗号分隔 */
    private String participants;
    /** å‚加人数 */
    private Integer participantCount;
    /** æ¼”练方案 */
    private String planDesc;
    /** æ¼”练过程记录 */
    private String processDesc;
    /** æ¼”练总结与改进 */
    private String summary;
    /** æ¼”练时长(分钟) */
    private Integer durationMinutes;
    /** æ¼”练照片/记录表 URL,逗号分隔 */
    private String attachment;
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyHazardDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.safety;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - é𐿂£ç™»è®°ä¸Žé—­çޝ DO
 *
 * <p>完整闭环:上报 â†’ åˆ†æ´¾ â†’ æ•´æ”¹ â†’ å¤æŸ¥ï¼ˆé€šè¿‡åˆ™é—­çŽ¯ï¼Œä¸é€šè¿‡é€€å›žæ•´æ”¹ï¼‰ã€‚</p>
 *
 * @author è¶…级管理员
 */
@TableName("safety_hazard")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyHazardDO extends BaseDO {
    @TableId
    private Long id;
    /** é𐿂£ç¼–号 */
    private String code;
    /** é𐿂£æ ‡é¢˜ */
    private String title;
    /** é𐿂£ç­‰çº§ï¼Œè§ SafetyHazardLevelEnum */
    private String level;
    /** æ¥æºï¼Œè§ SafetyHazardSourceEnum */
    private String source;
    /** å…³è”巡检记录编号 */
    private Long inspectionId;
    /** å…³è”巡检点位编号 */
    private Long pointId;
    /** ç‚¹ä½åç§° */
    private String pointName;
    /** é𐿂£ä½ç½® */
    private String location;
    /** é𐿂£æè¿° */
    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;
    /** æ•´æ”¹æœŸé™ */
    private LocalDate rectifyDeadline;
    /** æ•´æ”¹æŽªæ–½ä¸Žç»“æžœ */
    private String rectifyDesc;
    /** æ•´æ”¹åŽç…§ç‰‡ URL,逗号分隔 */
    private String rectifyPhotoUrls;
    /** æ•´æ”¹å®Œæˆæ—¶é—´ */
    private LocalDateTime rectifyTime;
    /** å¤æŸ¥äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long recheckerId;
    /** å¤æŸ¥äººå§“名(快照,由员工档案带出) */
    private String rechecker;
    /** å¤æŸ¥æ—¶é—´ */
    private LocalDateTime recheckTime;
    /** å¤æŸ¥ç»“论:PASSED-通过,REJECTED-不通过 */
    private String recheckResult;
    /** å¤æŸ¥æ„è§ */
    private String recheckRemark;
    /** çŠ¶æ€ï¼Œè§ SafetyHazardStatusEnum */
    private String status;
    /** é—­çŽ¯æ—¶é—´ */
    private LocalDateTime closeTime;
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyInspectionDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.safety;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - å·¡æ£€è®°å½• DO
 *
 * @author è¶…级管理员
 */
@TableName("safety_inspection")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyInspectionDO extends BaseDO {
    @TableId
    private Long id;
    /** å·¡æ£€ç‚¹ä½ç¼–号 */
    private Long pointId;
    /** ç‚¹ä½åç§°ï¼ˆå¿«ç…§ï¼‰ */
    private String pointName;
    /** ç‚¹ä½ç±»åž‹ï¼ˆå¿«ç…§ï¼‰ */
    private String pointType;
    /** å·¡æ£€æ—¶é—´ */
    private LocalDateTime inspectTime;
    /** å·¡æ£€äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long inspectorId;
    /** å·¡æ£€äººå§“名(快照,由员工档案带出) */
    private String inspector;
    /** å·¡æ£€ç»“果:NORMAL-正常,ABNORMAL-异常 */
    private String result;
    /** å·¡æ£€å†…容与情况 */
    private String content;
    /** çŽ°åœºç…§ç‰‡ URL,逗号分隔 */
    private String photoUrls;
    /** æœ¬æ¬¡å‘现的隐患数 */
    private Integer hazardCount;
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyInspectionPointDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.safety;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * å®‰å…¨ç”Ÿäº§ - å·¡æ£€ç‚¹ä½ DO
 *
 * <p>高压配电室、机组区等日常安全巡检点位。</p>
 *
 * @author è¶…级管理员
 */
@TableName("safety_inspection_point")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyInspectionPointDO extends BaseDO {
    @TableId
    private Long id;
    /** ç‚¹ä½ç¼–码 */
    private String code;
    /** ç‚¹ä½åç§° */
    private String name;
    /** ç‚¹ä½ç±»åž‹ï¼Œè§ SafetyInspectionPointTypeEnum */
    private String pointType;
    /** æ‰€å±žè½¦é—´ï¼ˆå…³è” mes_md_workshop.id) */
    private Long workshopId;
    /** æ‰€å±žè½¦é—´åç§°ï¼ˆå¿«ç…§ï¼Œç”±è½¦é—´ä¸»æ•°æ®å¸¦å‡ºï¼‰ */
    private String workshopName;
    /** å…·ä½“位置 */
    private String location;
    /** å·¡æ£€é¡¹ç›®æ¸…单,按行分隔 */
    private String inspectItems;
    /** å·¡æ£€å‘¨æœŸï¼Œè§ SafetyInspectCycleEnum */
    private String inspectCycle;
    /** è´£ä»»äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long ownerId;
    /** è´£ä»»äººå§“名(快照,由员工档案带出) */
    private String ownerName;
    /** çŠ¶æ€ï¼š0-启用,1-停用 */
    private Integer status;
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyMaterialDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.safety;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - åº”急/安全物资台账 DO
 *
 * <p>电力绝缘用具、危化品、消防物资等。</p>
 *
 * @author è¶…级管理员
 */
@TableName("safety_material")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyMaterialDO extends BaseDO {
    @TableId
    private Long id;
    /** ç‰©èµ„编码 */
    private String code;
    /** ç‰©èµ„名称 */
    private String name;
    /** ç±»åˆ«ï¼Œè§ SafetyMaterialCategoryEnum */
    private String category;
    /** è§„格型号 */
    private String spec;
    /** è®¡é‡å•位 */
    private String unit;
    /** å½“前库存数量 */
    private BigDecimal stockQty;
    /** å®‰å…¨åº“存(低于则预警) */
    private BigDecimal safeStock;
    /** å­˜æ”¾ä½ç½® */
    private String storageLocation;
    /** æœ‰æ•ˆæœŸè‡³ï¼ˆç»ç¼˜/危化品必填) */
    private LocalDate expireDate;
    /** æœ‰æ•ˆæœŸæœˆæ•° */
    private Integer validityMonths;
    /** ä¿ç®¡äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long managerId;
    /** ä¿ç®¡äººå§“名(快照,由员工档案带出) */
    private String manager;
    /** çŠ¶æ€ï¼š0-启用,1-停用 */
    private Integer status;
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyMaterialStockDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.safety;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰©èµ„出入库流水 DO
 *
 * @author è¶…级管理员
 */
@TableName("safety_material_stock")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyMaterialStockDO extends BaseDO {
    @TableId
    private Long id;
    /** ç‰©èµ„编号 */
    private Long materialId;
    /** ç‰©èµ„名称(快照) */
    private String materialName;
    /** ç‰©èµ„类别(快照) */
    private String category;
    /** ä¸šåŠ¡ç±»åž‹ï¼šIN-入库,OUT-出库 */
    private String bizType;
    /** æ•°é‡ */
    private BigDecimal quantity;
    /** æ“ä½œå‰åº“å­˜ */
    private BigDecimal stockBefore;
    /** æ“ä½œåŽåº“å­˜ */
    private BigDecimal stockAfter;
    /** ä¸šåŠ¡æ—¥æœŸ */
    private LocalDate bizDate;
    /** ç»åŠžäºº */
    private String operator;
    /** é¢†ç”¨äººï¼ˆå‡ºåº“) */
    private String receiver;
    /** ç”¨é€”/去向 */
    private String purpose;
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyOrgDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.safety;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * å®‰å…¨ç”Ÿäº§ - å®‰å…¨ç»„织架构 DO
 *
 * @author è¶…级管理员
 */
@TableName("safety_org")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyOrgDO extends BaseDO {
    @TableId
    private Long id;
    /** çˆ¶çº§ç¼–号,0 ä¸ºé¡¶çº§ */
    private Long parentId;
    /** ç»„织名称,如安全生产委员会、安全管理部 */
    private String name;
    /** è´Ÿè´£äººï¼ˆå‘˜å·¥ç¼–号,关联 hrm_employee.id) */
    private Long leaderId;
    /** è´Ÿè´£äººå§“名(快照,由员工档案带出) */
    private String leaderName;
    /** è´Ÿè´£äººç”µè¯ */
    private String leaderPhone;
    /** æˆå‘˜å§“名,逗号分隔 */
    private String memberNames;
    /** å®‰å…¨èŒè´£ */
    private String duty;
    /** æ˜¾ç¤ºé¡ºåº */
    private Integer sort;
    /** çŠ¶æ€ï¼š0-启用,1-停用 */
    private Integer status;
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyTrainingDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.safety;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
 * å®‰å…¨ç”Ÿäº§ - å®‰å…¨åŸ¹è®­è®°å½• DO
 *
 * @author è¶…级管理员
 */
@TableName("safety_training")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyTrainingDO extends BaseDO {
    @TableId
    private Long id;
    /** åŸ¹è®­ä¸»é¢˜ */
    private String title;
    /** åŸ¹è®­ç±»åž‹ï¼Œè§ SafetyTrainingTypeEnum */
    private String trainingType;
    /** åŸ¹è®­æ—¥æœŸ */
    private LocalDate trainDate;
    /** åŸ¹è®­å­¦æ—¶ */
    private BigDecimal durationHours;
    /** è®²å¸ˆ/组织人(员工编号,关联 hrm_employee.id) */
    private Long trainerId;
    /** è®²å¸ˆ/组织人姓名(快照,由员工档案带出) */
    private String trainer;
    /** åŸ¹è®­åœ°ç‚¹ */
    private String location;
    /** å‚加人员姓名,逗号分隔 */
    private String participants;
    /** å‚加人数 */
    private Integer participantCount;
    /** åŸ¹è®­å†…容 */
    private String content;
    /** è€ƒæ ¸æƒ…况 */
    private String examResult;
    /** ç­¾åˆ°è¡¨/课件/照片 URL,逗号分隔 */
    private String attachment;
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyWorkPermitDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,77 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.safety;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - é«˜å±ä½œä¸šç¥¨ DO
 *
 * <p>动火、高压带电、有限空间等作业的线上审批与电子留存。</p>
 *
 * @author è¶…级管理员
 */
@TableName("safety_work_permit")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyWorkPermitDO extends BaseDO {
    @TableId
    private Long id;
    /** ä½œä¸šç¥¨ç¼–号 */
    private String code;
    /** ä½œä¸šç±»åž‹ï¼Œè§ SafetyPermitTypeEnum */
    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;
    /** è®¡åˆ’开始时间 */
    private LocalDateTime planStartTime;
    /** è®¡åˆ’结束时间 */
    private LocalDateTime planEndTime;
    /** å®žé™…开始时间 */
    private LocalDateTime actualStartTime;
    /** å®žé™…结束时间 */
    private LocalDateTime actualEndTime;
    /** é£Žé™©ç­‰çº§ï¼Œè§ SafetyRiskLevelEnum */
    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;
    /** ä½œä¸šç¥¨é™„ä»¶ URL,逗号分隔 */
    private String attachment;
    /** çŽ°åœºç…§ç‰‡ URL,逗号分隔 */
    private String photoUrls;
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyWorkPermitMonitorDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.safety;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
 * å®‰å…¨ç”Ÿäº§ - ä½œä¸šç›‘护记录 DO
 *
 * @author è¶…级管理员
 */
@TableName("safety_work_permit_monitor")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyWorkPermitMonitorDO extends BaseDO {
    @TableId
    private Long id;
    /** ä½œä¸šç¥¨ç¼–号 */
    private Long permitId;
    /** ç›‘护时间 */
    private LocalDateTime monitorTime;
    /** ç›‘护人(员工编号,关联 hrm_employee.id) */
    private Long guardianId;
    /** ç›‘护人姓名(快照,由员工档案带出) */
    private String guardian;
    /** ç›‘护内容与情况 */
    private String monitorContent;
    /** å¼‚常情况与处置 */
    private String abnormal;
    /** çŽ°åœºç…§ç‰‡ URL,逗号分隔 */
    private String photoUrls;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyAccidentMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.mes.dal.mysql.safety;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.train.SafetyAccidentPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyAccidentDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
 * å®‰å…¨ç”Ÿäº§ - äº‹æ•…登记 Mapper
 *
 * @author è¶…级管理员
 */
@Mapper
public interface SafetyAccidentMapper extends BaseMapperX<SafetyAccidentDO> {
    default PageResult<SafetyAccidentDO> selectPage(SafetyAccidentPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SafetyAccidentDO>()
                .likeIfPresent(SafetyAccidentDO::getCode, reqVO.getCode())
                .likeIfPresent(SafetyAccidentDO::getTitle, reqVO.getTitle())
                .eqIfPresent(SafetyAccidentDO::getAccidentType, reqVO.getAccidentType())
                .eqIfPresent(SafetyAccidentDO::getLevel, reqVO.getLevel())
                .eqIfPresent(SafetyAccidentDO::getStatus, reqVO.getStatus())
                .betweenIfPresent(SafetyAccidentDO::getOccurTime, reqVO.getOccurTime())
                .orderByDesc(SafetyAccidentDO::getOccurTime)
                .orderByDesc(SafetyAccidentDO::getId));
    }
    default List<SafetyAccidentDO> selectListBetween(java.time.LocalDateTime begin, java.time.LocalDateTime end) {
        return selectList(new LambdaQueryWrapperX<SafetyAccidentDO>()
                .geIfPresent(SafetyAccidentDO::getOccurTime, begin)
                .leIfPresent(SafetyAccidentDO::getOccurTime, end)
                .orderByAsc(SafetyAccidentDO::getOccurTime));
    }
    @Select("SELECT CONCAT(#{prefix}, LPAD(COUNT(*) + 1, 4, '0')) FROM safety_accident WHERE code LIKE CONCAT(#{prefix}, '%')")
    String selectNextCode(@Param("prefix") String prefix);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyCertificateMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.mes.dal.mysql.safety;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.cert.SafetyCertificatePageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyCertificateDO;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDate;
import java.util.List;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰¹ç§ä½œä¸šè¯ Mapper
 *
 * @author è¶…级管理员
 */
@Mapper
public interface SafetyCertificateMapper extends BaseMapperX<SafetyCertificateDO> {
    default PageResult<SafetyCertificateDO> selectPage(SafetyCertificatePageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SafetyCertificateDO>()
                .likeIfPresent(SafetyCertificateDO::getEmployeeName, reqVO.getEmployeeName())
                .likeIfPresent(SafetyCertificateDO::getEmployeeNo, reqVO.getEmployeeNo())
                .likeIfPresent(SafetyCertificateDO::getDeptName, reqVO.getDeptName())
                .eqIfPresent(SafetyCertificateDO::getCertType, reqVO.getCertType())
                .eqIfPresent(SafetyCertificateDO::getStatus, reqVO.getStatus())
                .likeIfPresent(SafetyCertificateDO::getCertNo, reqVO.getCertNo())
                .geIfPresent(SafetyCertificateDO::getExpireDate, reqVO.getExpireDateStart())
                .leIfPresent(SafetyCertificateDO::getExpireDate, reqVO.getExpireDateEnd())
                .orderByAsc(SafetyCertificateDO::getExpireDate)
                .orderByDesc(SafetyCertificateDO::getId));
    }
    default SafetyCertificateDO selectByCertNo(String certNo) {
        return selectOne(SafetyCertificateDO::getCertNo, certNo);
    }
    /** æŸ¥è¯¢å³å°†åˆ°æœŸæˆ–已过期的证件(expireDate <= æˆªæ­¢æ—¥æœŸï¼‰ */
    default List<SafetyCertificateDO> selectExpiringBefore(LocalDate deadline) {
        return selectList(new LambdaQueryWrapperX<SafetyCertificateDO>()
                .le(SafetyCertificateDO::getExpireDate, deadline)
                .orderByAsc(SafetyCertificateDO::getExpireDate));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyDrillMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.mes.dal.mysql.safety;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.train.SafetyDrillPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyDrillDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
 * å®‰å…¨ç”Ÿäº§ - æ¼”练记录 Mapper
 *
 * @author è¶…级管理员
 */
@Mapper
public interface SafetyDrillMapper extends BaseMapperX<SafetyDrillDO> {
    default PageResult<SafetyDrillDO> selectPage(SafetyDrillPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SafetyDrillDO>()
                .likeIfPresent(SafetyDrillDO::getTitle, reqVO.getTitle())
                .eqIfPresent(SafetyDrillDO::getDrillType, reqVO.getDrillType())
                .likeIfPresent(SafetyDrillDO::getOrganizer, reqVO.getOrganizer())
                .betweenIfPresent(SafetyDrillDO::getDrillDate, reqVO.getDrillDate())
                .orderByDesc(SafetyDrillDO::getDrillDate)
                .orderByDesc(SafetyDrillDO::getId));
    }
    default List<SafetyDrillDO> selectListBetween(java.time.LocalDate begin, java.time.LocalDate end) {
        return selectList(new LambdaQueryWrapperX<SafetyDrillDO>()
                .geIfPresent(SafetyDrillDO::getDrillDate, begin)
                .leIfPresent(SafetyDrillDO::getDrillDate, end)
                .orderByAsc(SafetyDrillDO::getDrillDate));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyHazardMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.mes.dal.mysql.safety;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.hazard.SafetyHazardPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyHazardDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
 * å®‰å…¨ç”Ÿäº§ - é𐿂£ Mapper
 *
 * @author è¶…级管理员
 */
@Mapper
public interface SafetyHazardMapper extends BaseMapperX<SafetyHazardDO> {
    default PageResult<SafetyHazardDO> selectPage(SafetyHazardPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SafetyHazardDO>()
                .likeIfPresent(SafetyHazardDO::getCode, reqVO.getCode())
                .likeIfPresent(SafetyHazardDO::getTitle, reqVO.getTitle())
                .eqIfPresent(SafetyHazardDO::getLevel, reqVO.getLevel())
                .eqIfPresent(SafetyHazardDO::getSource, reqVO.getSource())
                .eqIfPresent(SafetyHazardDO::getStatus, reqVO.getStatus())
                .likeIfPresent(SafetyHazardDO::getAssignee, reqVO.getAssignee())
                .likeIfPresent(SafetyHazardDO::getLocation, reqVO.getLocation())
                .geIfPresent(SafetyHazardDO::getReportTime, reqVO.getReportTimeStart())
                .leIfPresent(SafetyHazardDO::getReportTime, reqVO.getReportTimeEnd())
                .orderByDesc(SafetyHazardDO::getReportTime)
                .orderByDesc(SafetyHazardDO::getId));
    }
    /** è¶…过整改期限且未闭环的隐患 */
    default List<SafetyHazardDO> selectOverdue(java.time.LocalDate today) {
        return selectList(new LambdaQueryWrapperX<SafetyHazardDO>()
                .lt(SafetyHazardDO::getRectifyDeadline, today)
                .notIn(SafetyHazardDO::getStatus, "CLOSED")
                .orderByAsc(SafetyHazardDO::getRectifyDeadline));
    }
    /** ç”Ÿæˆé𐿂£ç¼–号:HZ + yyyyMMdd + 4 ä½æµæ°´ */
    @Select("SELECT CONCAT(#{prefix}, LPAD(COUNT(*) + 1, 4, '0')) FROM safety_hazard WHERE code LIKE CONCAT(#{prefix}, '%')")
    String selectNextCode(@Param("prefix") String prefix);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyInspectionMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.mes.dal.mysql.safety;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.inspection.SafetyInspectionPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyInspectionDO;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.List;
/**
 * å®‰å…¨ç”Ÿäº§ - å·¡æ£€è®°å½• Mapper
 *
 * @author è¶…级管理员
 */
@Mapper
public interface SafetyInspectionMapper extends BaseMapperX<SafetyInspectionDO> {
    default PageResult<SafetyInspectionDO> selectPage(SafetyInspectionPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SafetyInspectionDO>()
                .eqIfPresent(SafetyInspectionDO::getPointId, reqVO.getPointId())
                .eqIfPresent(SafetyInspectionDO::getPointType, reqVO.getPointType())
                .likeIfPresent(SafetyInspectionDO::getInspector, reqVO.getInspector())
                .eqIfPresent(SafetyInspectionDO::getResult, reqVO.getResult())
                .betweenIfPresent(SafetyInspectionDO::getInspectTime, reqVO.getInspectTime())
                .orderByDesc(SafetyInspectionDO::getInspectTime)
                .orderByDesc(SafetyInspectionDO::getId));
    }
    default List<SafetyInspectionDO> selectListByTimeRange(LocalDateTime begin, LocalDateTime end) {
        return selectList(new LambdaQueryWrapperX<SafetyInspectionDO>()
                .geIfPresent(SafetyInspectionDO::getInspectTime, begin)
                .leIfPresent(SafetyInspectionDO::getInspectTime, end)
                .orderByAsc(SafetyInspectionDO::getInspectTime));
    }
    default Long selectCountByPointId(Long pointId) {
        return selectCount(SafetyInspectionDO::getPointId, pointId);
    }
    /** æŸ¥è¯¢æŸç‚¹ä½æœ€è¿‘一次巡检记录 */
    default SafetyInspectionDO selectLatestByPointId(Long pointId) {
        return selectOne(new LambdaQueryWrapperX<SafetyInspectionDO>()
                .eq(SafetyInspectionDO::getPointId, pointId)
                .orderByDesc(SafetyInspectionDO::getInspectTime)
                .last("LIMIT 1"));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyInspectionPointMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.mes.dal.mysql.safety;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.inspection.SafetyInspectionPointPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyInspectionPointDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
 * å®‰å…¨ç”Ÿäº§ - å·¡æ£€ç‚¹ä½ Mapper
 *
 * @author è¶…级管理员
 */
@Mapper
public interface SafetyInspectionPointMapper extends BaseMapperX<SafetyInspectionPointDO> {
    default PageResult<SafetyInspectionPointDO> selectPage(SafetyInspectionPointPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SafetyInspectionPointDO>()
                .likeIfPresent(SafetyInspectionPointDO::getCode, reqVO.getCode())
                .likeIfPresent(SafetyInspectionPointDO::getName, reqVO.getName())
                .eqIfPresent(SafetyInspectionPointDO::getPointType, reqVO.getPointType())
                .likeIfPresent(SafetyInspectionPointDO::getWorkshopName, reqVO.getWorkshopName())
                .eqIfPresent(SafetyInspectionPointDO::getStatus, reqVO.getStatus())
                .orderByAsc(SafetyInspectionPointDO::getCode));
    }
    default SafetyInspectionPointDO selectByCode(String code) {
        return selectOne(SafetyInspectionPointDO::getCode, code);
    }
    default List<SafetyInspectionPointDO> selectListByStatus(Integer status) {
        return selectList(new LambdaQueryWrapperX<SafetyInspectionPointDO>()
                .eq(SafetyInspectionPointDO::getStatus, status)
                .orderByAsc(SafetyInspectionPointDO::getCode));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyMaterialMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.mes.dal.mysql.safety;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.material.SafetyMaterialPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyMaterialDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDate;
import java.util.List;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰©èµ„台账 Mapper
 *
 * @author è¶…级管理员
 */
@Mapper
public interface SafetyMaterialMapper extends BaseMapperX<SafetyMaterialDO> {
    default PageResult<SafetyMaterialDO> selectPage(SafetyMaterialPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SafetyMaterialDO>()
                .likeIfPresent(SafetyMaterialDO::getCode, reqVO.getCode())
                .likeIfPresent(SafetyMaterialDO::getName, reqVO.getName())
                .eqIfPresent(SafetyMaterialDO::getCategory, reqVO.getCategory())
                .eqIfPresent(SafetyMaterialDO::getStatus, reqVO.getStatus())
                .likeIfPresent(SafetyMaterialDO::getStorageLocation, reqVO.getStorageLocation())
                .orderByAsc(SafetyMaterialDO::getCategory)
                .orderByAsc(SafetyMaterialDO::getCode));
    }
    default SafetyMaterialDO selectByCode(String code) {
        return selectOne(SafetyMaterialDO::getCode, code);
    }
    /** é”å®šåº“存行,避免并发出入库导致库存错乱 */
    @Select("SELECT * FROM safety_material WHERE id = #{id} AND deleted = b'0' FOR UPDATE")
    SafetyMaterialDO selectByIdForUpdate(@Param("id") Long id);
    /** ä½ŽäºŽå®‰å…¨åº“存的物资 */
    default List<SafetyMaterialDO> selectBelowSafeStock() {
        return selectList(new LambdaQueryWrapperX<SafetyMaterialDO>()
                .apply("stock_qty < safe_stock")
                .orderByAsc(SafetyMaterialDO::getCode));
    }
    /** æœ‰æ•ˆæœŸä¸´è¿‘的物资 */
    default List<SafetyMaterialDO> selectExpiringBefore(LocalDate deadline) {
        return selectList(new LambdaQueryWrapperX<SafetyMaterialDO>()
                .isNotNull(SafetyMaterialDO::getExpireDate)
                .le(SafetyMaterialDO::getExpireDate, deadline)
                .orderByAsc(SafetyMaterialDO::getExpireDate));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyMaterialStockMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.mes.dal.mysql.safety;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.material.SafetyMaterialStockPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyMaterialStockDO;
import org.apache.ibatis.annotations.Mapper;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰©èµ„出入库流水 Mapper
 *
 * @author è¶…级管理员
 */
@Mapper
public interface SafetyMaterialStockMapper extends BaseMapperX<SafetyMaterialStockDO> {
    default PageResult<SafetyMaterialStockDO> selectPage(SafetyMaterialStockPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SafetyMaterialStockDO>()
                .eqIfPresent(SafetyMaterialStockDO::getMaterialId, reqVO.getMaterialId())
                .likeIfPresent(SafetyMaterialStockDO::getMaterialName, reqVO.getMaterialName())
                .eqIfPresent(SafetyMaterialStockDO::getCategory, reqVO.getCategory())
                .eqIfPresent(SafetyMaterialStockDO::getBizType, reqVO.getBizType())
                .betweenIfPresent(SafetyMaterialStockDO::getBizDate, reqVO.getBizDate())
                .orderByDesc(SafetyMaterialStockDO::getBizDate)
                .orderByDesc(SafetyMaterialStockDO::getId));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyOrgMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.mes.dal.mysql.safety;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyOrgDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
 * å®‰å…¨ç”Ÿäº§ - ç»„织架构 Mapper
 *
 * @author è¶…级管理员
 */
@Mapper
public interface SafetyOrgMapper extends BaseMapperX<SafetyOrgDO> {
    default List<SafetyOrgDO> selectListOrdered() {
        return selectList(new LambdaQueryWrapperX<SafetyOrgDO>()
                .orderByAsc(SafetyOrgDO::getSort)
                .orderByAsc(SafetyOrgDO::getId));
    }
    default List<SafetyOrgDO> selectListByParentId(Long parentId) {
        return selectList(new LambdaQueryWrapperX<SafetyOrgDO>()
                .eq(SafetyOrgDO::getParentId, parentId)
                .orderByAsc(SafetyOrgDO::getSort));
    }
    default Long selectCountByParentId(Long parentId) {
        return selectCount(SafetyOrgDO::getParentId, parentId);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyTrainingMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.mes.dal.mysql.safety;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.train.SafetyTrainingPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyTrainingDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
 * å®‰å…¨ç”Ÿäº§ - åŸ¹è®­è®°å½• Mapper
 *
 * @author è¶…级管理员
 */
@Mapper
public interface SafetyTrainingMapper extends BaseMapperX<SafetyTrainingDO> {
    default PageResult<SafetyTrainingDO> selectPage(SafetyTrainingPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SafetyTrainingDO>()
                .likeIfPresent(SafetyTrainingDO::getTitle, reqVO.getTitle())
                .eqIfPresent(SafetyTrainingDO::getTrainingType, reqVO.getTrainingType())
                .likeIfPresent(SafetyTrainingDO::getTrainer, reqVO.getTrainer())
                .betweenIfPresent(SafetyTrainingDO::getTrainDate, reqVO.getTrainDate())
                .orderByDesc(SafetyTrainingDO::getTrainDate)
                .orderByDesc(SafetyTrainingDO::getId));
    }
    default List<SafetyTrainingDO> selectListBetween(java.time.LocalDate begin, java.time.LocalDate end) {
        return selectList(new LambdaQueryWrapperX<SafetyTrainingDO>()
                .geIfPresent(SafetyTrainingDO::getTrainDate, begin)
                .leIfPresent(SafetyTrainingDO::getTrainDate, end)
                .orderByAsc(SafetyTrainingDO::getTrainDate));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyWorkPermitMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.mes.dal.mysql.safety;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.permit.SafetyWorkPermitPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyWorkPermitDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/**
 * å®‰å…¨ç”Ÿäº§ - é«˜å±ä½œä¸šç¥¨ Mapper
 *
 * @author è¶…级管理员
 */
@Mapper
public interface SafetyWorkPermitMapper extends BaseMapperX<SafetyWorkPermitDO> {
    default PageResult<SafetyWorkPermitDO> selectPage(SafetyWorkPermitPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SafetyWorkPermitDO>()
                .likeIfPresent(SafetyWorkPermitDO::getCode, reqVO.getCode())
                .likeIfPresent(SafetyWorkPermitDO::getTitle, reqVO.getTitle())
                .eqIfPresent(SafetyWorkPermitDO::getPermitType, reqVO.getPermitType())
                .eqIfPresent(SafetyWorkPermitDO::getStatus, reqVO.getStatus())
                .eqIfPresent(SafetyWorkPermitDO::getRiskLevel, reqVO.getRiskLevel())
                .likeIfPresent(SafetyWorkPermitDO::getApplicant, reqVO.getApplicant())
                .likeIfPresent(SafetyWorkPermitDO::getWorkshopName, reqVO.getWorkshopName())
                .betweenIfPresent(SafetyWorkPermitDO::getPlanStartTime, reqVO.getPlanStartTime())
                .orderByDesc(SafetyWorkPermitDO::getApplyTime)
                .orderByDesc(SafetyWorkPermitDO::getId));
    }
    @Select("SELECT CONCAT(#{prefix}, LPAD(COUNT(*) + 1, 4, '0')) FROM safety_work_permit WHERE code LIKE CONCAT(#{prefix}, '%')")
    String selectNextCode(@Param("prefix") String prefix);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/safety/SafetyWorkPermitMonitorMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.mes.dal.mysql.safety;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyWorkPermitMonitorDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
 * å®‰å…¨ç”Ÿäº§ - ä½œä¸šç›‘护记录 Mapper
 *
 * @author è¶…级管理员
 */
@Mapper
public interface SafetyWorkPermitMonitorMapper extends BaseMapperX<SafetyWorkPermitMonitorDO> {
    default List<SafetyWorkPermitMonitorDO> selectListByPermitId(Long permitId) {
        return selectList(new LambdaQueryWrapperX<SafetyWorkPermitMonitorDO>()
                .eq(SafetyWorkPermitMonitorDO::getPermitId, permitId)
                .orderByDesc(SafetyWorkPermitMonitorDO::getMonitorTime));
    }
    default int deleteByPermitId(Long permitId) {
        return delete(SafetyWorkPermitMonitorDO::getPermitId, permitId);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/SafetyErrorCodeConstants.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
package cn.iocoder.yudao.module.mes.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
 * MES æ¨¡å—错误码 - å®‰å…¨ç”Ÿäº§å­æ¨¡å—
 *
 * å®‰å…¨ç”Ÿäº§æ¨¡å—错误码区间:1_040_960_xxx
 *
 * @author è¶…级管理员
 */
public interface SafetyErrorCodeConstants {
    // ========== å®‰å…¨ç»„织架构 1_040_961_xxx ==========
    ErrorCode SAFETY_ORG_NOT_EXISTS = new ErrorCode(1_040_961_000, "安全组织不存在");
    ErrorCode SAFETY_ORG_PARENT_NOT_EXISTS = new ErrorCode(1_040_961_001, "父级安全组织不存在");
    ErrorCode SAFETY_ORG_PARENT_IS_SELF = new ErrorCode(1_040_961_002, "上级组织不能选择自己");
    ErrorCode SAFETY_ORG_PARENT_IS_CHILD = new ErrorCode(1_040_961_003, "上级组织不能选择自己的子组织");
    ErrorCode SAFETY_ORG_HAS_CHILDREN = new ErrorCode(1_040_961_004, "存在子组织,无法删除");
    // ========== ç‰¹ç§ä½œä¸šè¯ 1_040_962_xxx ==========
    ErrorCode SAFETY_CERT_NOT_EXISTS = new ErrorCode(1_040_962_000, "特种作业证不存在");
    ErrorCode SAFETY_CERT_NO_DUPLICATE = new ErrorCode(1_040_962_001, "证件编号已存在");
    ErrorCode SAFETY_CERT_EXPIRE_BEFORE_ISSUE = new ErrorCode(1_040_962_002, "有效期不能早于发证日期");
    ErrorCode SAFETY_CERT_EMPLOYEE_NOT_EXISTS = new ErrorCode(1_040_962_003, "持证人不存在,请从员工档案中选择");
    // ========== å·¡æ£€ç‚¹ä½ / å·¡æ£€è®°å½• 1_040_963_xxx ==========
    ErrorCode SAFETY_POINT_NOT_EXISTS = new ErrorCode(1_040_963_000, "巡检点位不存在");
    ErrorCode SAFETY_POINT_CODE_DUPLICATE = new ErrorCode(1_040_963_001, "点位编码已存在");
    ErrorCode SAFETY_POINT_DISABLED = new ErrorCode(1_040_963_002, "巡检点位已停用");
    ErrorCode SAFETY_INSPECTION_NOT_EXISTS = new ErrorCode(1_040_963_010, "巡检记录不存在");
    // ========== é𐿂£é—­çޝ 1_040_964_xxx ==========
    ErrorCode SAFETY_HAZARD_NOT_EXISTS = new ErrorCode(1_040_964_000, "隐患记录不存在");
    ErrorCode SAFETY_HAZARD_STATUS_ERROR = new ErrorCode(1_040_964_001, "当前隐患状态不允许该操作");
    ErrorCode SAFETY_HAZARD_ASSIGNEE_REQUIRED = new ErrorCode(1_040_964_002, "分派必须指定整改责任人");
    ErrorCode SAFETY_HAZARD_SAME_OPERATOR = new ErrorCode(1_040_964_003, "整改人与复查人不能是同一人");
    // ========== é«˜å±ä½œä¸šç¥¨ 1_040_965_xxx ==========
    ErrorCode SAFETY_PERMIT_NOT_EXISTS = new ErrorCode(1_040_965_000, "作业票不存在");
    ErrorCode SAFETY_PERMIT_STATUS_ERROR = new ErrorCode(1_040_965_001, "当前作业票状态不允许该操作");
    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 ==========
    ErrorCode SAFETY_MATERIAL_NOT_EXISTS = new ErrorCode(1_040_966_000, "物资不存在");
    ErrorCode SAFETY_MATERIAL_CODE_DUPLICATE = new ErrorCode(1_040_966_001, "物资编码已存在");
    ErrorCode SAFETY_MATERIAL_STOCK_NOT_ENOUGH = new ErrorCode(1_040_966_002, "库存不足,无法出库");
    ErrorCode SAFETY_MATERIAL_STOCK_RECORD_NOT_EXISTS = new ErrorCode(1_040_966_010, "出入库流水不存在");
    // ========== åŸ¹è®­ / æ¼”练 / äº‹æ•… 1_040_967_xxx ==========
    ErrorCode SAFETY_TRAINING_NOT_EXISTS = new ErrorCode(1_040_967_000, "培训记录不存在");
    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/enums/safety/SafetyAccidentLevelEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - äº‹æ•…等级枚举
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyAccidentLevelEnum {
    SLIGHT("SLIGHT", "轻伤事故"),
    GENERAL("GENERAL", "一般事故"),
    SEVERE("SEVERE", "重伤事故"),
    MAJOR("MAJOR", "重大事故");
    private final String level;
    private final String name;
    public static String getNameByLevel(String level) {
        for (SafetyAccidentLevelEnum value : values()) {
            if (value.getLevel().equals(level)) {
                return value.getName();
            }
        }
        return level;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyAccidentStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - äº‹æ•…处理状态枚举
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyAccidentStatusEnum {
    REPORTED("REPORTED", "已登记"),
    INVESTIGATING("INVESTIGATING", "调查中"),
    HANDLED("HANDLED", "已处理"),
    CLOSED("CLOSED", "已结案");
    private final String status;
    private final String name;
    public static String getNameByStatus(String status) {
        for (SafetyAccidentStatusEnum value : values()) {
            if (value.getStatus().equals(status)) {
                return value.getName();
            }
        }
        return status;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyAccidentTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - äº‹æ•…类型枚举
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyAccidentTypeEnum {
    ELECTRIC_SHOCK("ELECTRIC_SHOCK", "触电"),
    FIRE("FIRE", "火灾"),
    FALL("FALL", "高处坠落"),
    OBJECT_STRIKE("OBJECT_STRIKE", "物体打击"),
    MECHANICAL("MECHANICAL", "机械伤害"),
    CHEMICAL("CHEMICAL", "中毒窒息"),
    VEHICLE("VEHICLE", "车辆伤害"),
    OTHER("OTHER", "其他");
    private final String type;
    private final String name;
    public static String getNameByType(String type) {
        for (SafetyAccidentTypeEnum value : values()) {
            if (value.getType().equals(type)) {
                return value.getName();
            }
        }
        return type;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyCertificateStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰¹ç§ä½œä¸šè¯çŠ¶æ€æžšä¸¾
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyCertificateStatusEnum {
    VALID("VALID", "有效"),
    EXPIRING("EXPIRING", "即将到期"),
    EXPIRED("EXPIRED", "已过期"),
    REVOKED("REVOKED", "已注销");
    private final String status;
    private final String name;
    public static String getNameByStatus(String status) {
        for (SafetyCertificateStatusEnum value : values()) {
            if (value.getStatus().equals(status)) {
                return value.getName();
            }
        }
        return status;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyCertificateTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰¹ç§ä½œä¸šè¯ç±»åž‹æžšä¸¾
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyCertificateTypeEnum {
    LOW_VOLTAGE_ELECTRICIAN("LOW_VOLTAGE_ELECTRICIAN", "低压电工证"),
    HIGH_VOLTAGE_OPERATION("HIGH_VOLTAGE_OPERATION", "高压操作证"),
    WELDING("WELDING", "焊接与热切割作业证"),
    HOIST("HOIST", "起重机械作业证"),
    HEIGHT_WORK("HEIGHT_WORK", "高处作业证"),
    LIMITED_SPACE("LIMITED_SPACE", "有限空间作业证"),
    FORKLIFT("FORKLIFT", "叉车作业证"),
    BOILER("BOILER", "锅炉作业证"),
    OTHER("OTHER", "其他特种作业证");
    private final String type;
    private final String name;
    public static String getNameByType(String type) {
        for (SafetyCertificateTypeEnum value : values()) {
            if (value.getType().equals(type)) {
                return value.getName();
            }
        }
        return type;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyDrillTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - æ¼”练类型枚举
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyDrillTypeEnum {
    FIRE("FIRE", "消防演练"),
    EMERGENCY_EVAC("EMERGENCY_EVAC", "应急疏散演练"),
    ELECTRIC_ACCIDENT("ELECTRIC_ACCIDENT", "触电事故演练"),
    CHEMICAL_LEAK("CHEMICAL_LEAK", "危化品泄漏演练"),
    OTHER("OTHER", "其他演练");
    private final String type;
    private final String name;
    public static String getNameByType(String type) {
        for (SafetyDrillTypeEnum value : values()) {
            if (value.getType().equals(type)) {
                return value.getName();
            }
        }
        return type;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyHazardLevelEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - é𐿂£ç­‰çº§æžšä¸¾
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyHazardLevelEnum {
    GENERAL("GENERAL", "一般隐患"),
    MAJOR("MAJOR", "重大隐患");
    private final String level;
    private final String name;
    public static String getNameByLevel(String level) {
        for (SafetyHazardLevelEnum value : values()) {
            if (value.getLevel().equals(level)) {
                return value.getName();
            }
        }
        return level;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyHazardSourceEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - é𐿂£æ¥æºæžšä¸¾
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyHazardSourceEnum {
    INSPECTION("INSPECTION", "巡检发现"),
    SELF_CHECK("SELF_CHECK", "自查上报"),
    REPORT("REPORT", "员工上报"),
    EXTERNAL("EXTERNAL", "外部检查");
    private final String source;
    private final String name;
    public static String getNameBySource(String source) {
        for (SafetyHazardSourceEnum value : values()) {
            if (value.getSource().equals(source)) {
                return value.getName();
            }
        }
        return source;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyHazardStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - é𐿂£çŠ¶æ€æžšä¸¾
 *
 * <p>流转顺序:上报 â†’ åˆ†æ´¾ â†’ æ•´æ”¹ä¸­ â†’ å¾…复查 â†’ å·²é—­çޝ / å¤æŸ¥ä¸é€šè¿‡ï¼ˆé€€å›žæ•´æ”¹ï¼‰ã€‚</p>
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyHazardStatusEnum {
    REPORTED("REPORTED", "已上报"),
    ASSIGNED("ASSIGNED", "已分派"),
    RECTIFYING("RECTIFYING", "整改中"),
    RECHECKING("RECHECKING", "待复查"),
    REJECTED("REJECTED", "复查不通过"),
    CLOSED("CLOSED", "已闭环");
    private final String status;
    private final String name;
    public static String getNameByStatus(String status) {
        for (SafetyHazardStatusEnum value : values()) {
            if (value.getStatus().equals(status)) {
                return value.getName();
            }
        }
        return status;
    }
    public static boolean isFinished(String status) {
        return CLOSED.getStatus().equals(status);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyInspectCycleEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - å·¡æ£€å‘¨æœŸæžšä¸¾
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyInspectCycleEnum {
    SHIFT("SHIFT", "每班"),
    DAILY("DAILY", "每日"),
    WEEKLY("WEEKLY", "每周"),
    MONTHLY("MONTHLY", "每月");
    private final String cycle;
    private final String name;
    public static String getNameByCycle(String cycle) {
        for (SafetyInspectCycleEnum value : values()) {
            if (value.getCycle().equals(cycle)) {
                return value.getName();
            }
        }
        return cycle;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyInspectionPointTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - å·¡æ£€ç‚¹ä½ç±»åž‹æžšä¸¾
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyInspectionPointTypeEnum {
    HIGH_VOLTAGE_ROOM("HIGH_VOLTAGE_ROOM", "高压配电室"),
    UNIT_AREA("UNIT_AREA", "机组区"),
    DANGER_CHEMICAL("DANGER_CHEMICAL", "危化品库"),
    FIRE_AREA("FIRE_AREA", "消防重点部位"),
    WORKSHOP("WORKSHOP", "生产车间"),
    OTHER("OTHER", "其他");
    private final String type;
    private final String name;
    public static String getNameByType(String type) {
        for (SafetyInspectionPointTypeEnum value : values()) {
            if (value.getType().equals(type)) {
                return value.getName();
            }
        }
        return type;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyMaterialCategoryEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰©èµ„类别枚举
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyMaterialCategoryEnum {
    INSULATING("INSULATING", "电力绝缘用具"),
    HAZARD_CHEMICAL("HAZARD_CHEMICAL", "危化品"),
    FIRE("FIRE", "消防物资"),
    PPE("PPE", "劳保用品"),
    OTHER("OTHER", "其他");
    private final String category;
    private final String name;
    public static String getNameByCategory(String category) {
        for (SafetyMaterialCategoryEnum value : values()) {
            if (value.getCategory().equals(category)) {
                return value.getName();
            }
        }
        return category;
    }
    /** æ˜¯å¦éœ€è¦æœ‰æ•ˆæœŸç®¡ç†ï¼ˆç»ç¼˜ç”¨å…·ä¸Žå±åŒ–品有保质期) */
    public static boolean requiresExpireDate(String category) {
        return INSULATING.getCategory().equals(category) || HAZARD_CHEMICAL.getCategory().equals(category);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyPermitStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - é«˜å±ä½œä¸šç¥¨çŠ¶æ€æžšä¸¾
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyPermitStatusEnum {
    DRAFT("DRAFT", "草稿"),
    PENDING("PENDING", "待审批"),
    APPROVED("APPROVED", "已批准"),
    REJECTED("REJECTED", "已驳回"),
    IN_PROGRESS("IN_PROGRESS", "作业中"),
    CLOSED("CLOSED", "已完结"),
    CANCELLED("CANCELLED", "已作废");
    private final String status;
    private final String name;
    public static String getNameByStatus(String status) {
        for (SafetyPermitStatusEnum value : values()) {
            if (value.getStatus().equals(status)) {
                return value.getName();
            }
        }
        return status;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyPermitTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - é«˜å±ä½œä¸šç¥¨ç±»åž‹æžšä¸¾
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyPermitTypeEnum {
    HOT_WORK("HOT_WORK", "动火作业"),
    LIVE_HIGH_VOLTAGE("LIVE_HIGH_VOLTAGE", "高压带电作业"),
    LIMITED_SPACE("LIMITED_SPACE", "有限空间作业"),
    WORK_AT_HEIGHT("WORK_AT_HEIGHT", "高处作业"),
    LIFTING("LIFTING", "吊装作业"),
    TEMP_ELECTRIC("TEMP_ELECTRIC", "临时用电作业"),
    OTHER("OTHER", "其他作业");
    private final String type;
    private final String name;
    public static String getNameByType(String type) {
        for (SafetyPermitTypeEnum value : values()) {
            if (value.getType().equals(type)) {
                return value.getName();
            }
        }
        return type;
    }
    /** æ˜¯å¦å¿…须指定监护人 */
    public static boolean requiresGuardian(String type) {
        return HOT_WORK.getType().equals(type) || LIVE_HIGH_VOLTAGE.getType().equals(type)
                || LIMITED_SPACE.getType().equals(type);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyRiskLevelEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - é£Žé™©ç­‰çº§æžšä¸¾
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyRiskLevelEnum {
    LOW("LOW", "低"),
    NORMAL("NORMAL", "一般"),
    HIGH("HIGH", "高"),
    VERY_HIGH("VERY_HIGH", "极高");
    private final String level;
    private final String name;
    public static String getNameByLevel(String level) {
        for (SafetyRiskLevelEnum value : values()) {
            if (value.getLevel().equals(level)) {
                return value.getName();
            }
        }
        return level;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/safety/SafetyTrainingTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.mes.enums.safety;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®‰å…¨ç”Ÿäº§ - åŸ¹è®­ç±»åž‹æžšä¸¾
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum SafetyTrainingTypeEnum {
    SAFETY("SAFETY", "安全培训"),
    SKILL("SKILL", "技能培训"),
    LAW("LAW", "法规宣贯"),
    EMERGENCY("EMERGENCY", "应急演练培训"),
    INDUCTION("INDUCTION", "三级安全教育"),
    OTHER("OTHER", "其他");
    private final String type;
    private final String name;
    public static String getNameByType(String type) {
        for (SafetyTrainingTypeEnum value : values()) {
            if (value.getType().equals(type)) {
                return value.getName();
            }
        }
        return type;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyCertificateService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,233 @@
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;
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.controller.admin.safety.vo.cert.SafetyCertificatePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.cert.SafetyCertificateRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.cert.SafetyCertificateSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyCertificateDO;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyCertificateMapper;
import cn.iocoder.yudao.module.mes.enums.safety.SafetyCertificateStatusEnum;
import cn.iocoder.yudao.module.mes.enums.safety.SafetyCertificateTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
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.*;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰¹ç§ä½œä¸šè¯å°è´¦ Service
 *
 * <p>核心能力:证书到期自动预警。状态由 {@link #resolveStatus} æ ¹æ®æœ‰æ•ˆæœŸå®žæ—¶æŽ¨å¯¼ï¼Œ
 * ä¸ä¾èµ–手工维护,避免过期未及时更新状态。</p>
 *
 * <p>持证人通过 employeeId å…³è”人力资源的员工档案:
 * æ–°å¢ž/修改时从档案带出姓名、工号、部门并落为快照;查询时实时回填最新档案信息,
 * å‘˜å·¥è°ƒå²—后证件列表也会跟着显示新部门。</p>
 *
 * @author è¶…级管理员
 */
@Service
@Validated
public class SafetyCertificateService {
    /** é»˜è®¤æå‰é¢„警天数 */
    public static final int DEFAULT_WARN_DAYS = 30;
    @Resource
    private SafetyCertificateMapper certificateMapper;
    @Resource
    private HrmEmployeeApi hrmEmployeeApi;
    @Resource
    private SafetyRefResolver refResolver;
    public Long createCertificate(SafetyCertificateSaveReqVO reqVO) {
        reqVO.setId(null);
        validateCertificateForCreate(reqVO);
        SafetyCertificateDO cert = buildCertificate(reqVO, null);
        certificateMapper.insert(cert);
        return cert.getId();
    }
    public void updateCertificate(SafetyCertificateSaveReqVO reqVO) {
        SafetyCertificateDO old = validateCertificateExists(reqVO.getId());
        validateCertificateForUpdate(reqVO, old);
        SafetyCertificateDO cert = buildCertificate(reqVO, old);
        certificateMapper.updateById(cert);
    }
    public void deleteCertificate(Long id) {
        validateCertificateExists(id);
        certificateMapper.deleteById(id);
    }
    public SafetyCertificateDO getCertificate(Long id) {
        return validateCertificateExists(id);
    }
    public PageResult<SafetyCertificateDO> getCertificatePage(SafetyCertificatePageReqVO reqVO) {
        return certificateMapper.selectPage(reqVO);
    }
    /**
     * æŸ¥è¯¢å³å°†åˆ°æœŸ / å·²è¿‡æœŸçš„证件列表
     *
     * @param days æå‰é¢„警天数,为空使用默认 30 å¤©
     */
    public List<SafetyCertificateDO> getExpiringCertificates(Integer days) {
        int warnDays = days == null || days <= 0 ? DEFAULT_WARN_DAYS : days;
        return certificateMapper.selectExpiringBefore(LocalDate.now().plusDays(warnDays));
    }
    /**
     * æ‰¹é‡è¡¥å…¨æŒè¯äººçš„æœ€æ–°å‘˜å·¥æ¡£æ¡ˆä¿¡æ¯ï¼ˆå§“名、工号、部门、手机、关联用户)
     *
     * <p>查询时用档案实时值覆盖快照值,保证员工调岗后台账同步;
     * å‘˜å·¥æ¡£æ¡ˆå·²è¢«åˆ é™¤æ—¶å›žé€€åˆ°å¿«ç…§å€¼ï¼Œä¿è¯åŽ†å²è¯ä»¶ä»å¯å±•ç¤ºã€‚</p>
     */
    public void fillEmployeeInfo(List<SafetyCertificateDO> certs) {
        List<Long> employeeIds = certs.stream()
                .map(SafetyCertificateDO::getEmployeeId)
                .filter(Objects::nonNull).distinct().toList();
        // æ³¨æ„ï¼šemployeeMap åŽç»­ä¼šç”¨ cert.getEmployeeId()(可能为 null)去 get,
        // è€Œ Map.of() ä¸æŽ¥å— null key、get(null) ä¼šæŠ› NPE,所以空集合必须返回 HashMap
        Map<Long, HrmEmployeeSimpleDTO> employeeMap = employeeIds.isEmpty() ? new HashMap<>()
                : hrmEmployeeApi.getEmployeeList(employeeIds).stream()
                        .collect(Collectors.toMap(HrmEmployeeSimpleDTO::getId, e -> e, (a, b) -> a));
        certs.forEach(cert -> {
            HrmEmployeeSimpleDTO employee = employeeMap.get(cert.getEmployeeId());
            if (employee != null) {
                cert.setEmployeeName(employee.getName());
                cert.setEmployeeNo(employee.getEmployeeNo());
                cert.setDeptName(employee.getDeptName());
            }
        });
    }
    /**
     * æ‰¹é‡è¡¥å…¨å±•示字段,持证人档案一次性查好,避免逐条查询
     */
    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);
        respVO.setStatusName(SafetyCertificateStatusEnum.getNameByStatus(status));
        if (cert.getExpireDate() != null) {
            respVO.setRemainingDays(ChronoUnit.DAYS.between(LocalDate.now(), cert.getExpireDate()));
        }
        if (employee != null) {
            respVO.setEmployeeName(employee.getName());
            respVO.setEmployeeNo(employee.getEmployeeNo());
            respVO.setDeptName(employee.getDeptName());
            respVO.setPhone(employee.getPhone());
            respVO.setUserId(employee.getUserId());
        }
    }
    /**
     * æ ¹æ®æœ‰æ•ˆæœŸæŽ¨å¯¼çŠ¶æ€ï¼šå·²æ³¨é”€ä¿æŒä¸å˜ï¼›è¿‡æœŸ â†’ EXPIRED;在预警期内 â†’ EXPIRING;否则 VALID
     *
     * @param expireDate æœ‰æ•ˆæœŸ
     * @param warnDays   æå‰é¢„警天数
     * @param current    å½“前状态,用于保护 REVOKED
     */
    public static String resolveStatus(LocalDate expireDate, Integer warnDays, String current) {
        if (SafetyCertificateStatusEnum.REVOKED.getStatus().equals(current)) {
            return SafetyCertificateStatusEnum.REVOKED.getStatus();
        }
        if (expireDate == null) {
            return SafetyCertificateStatusEnum.VALID.getStatus();
        }
        LocalDate today = LocalDate.now();
        if (expireDate.isBefore(today)) {
            return SafetyCertificateStatusEnum.EXPIRED.getStatus();
        }
        int days = warnDays == null || warnDays <= 0 ? DEFAULT_WARN_DAYS : warnDays;
        if (!expireDate.isAfter(today.plusDays(days))) {
            return SafetyCertificateStatusEnum.EXPIRING.getStatus();
        }
        return SafetyCertificateStatusEnum.VALID.getStatus();
    }
    /**
     * æž„建 DO:持证人姓名/工号/部门从员工档案带出并落为快照
     */
    private SafetyCertificateDO buildCertificate(SafetyCertificateSaveReqVO reqVO, SafetyCertificateDO old) {
        HrmEmployeeSimpleDTO employee = hrmEmployeeApi.getEmployee(reqVO.getEmployeeId());
        if (employee == null) {
            throw exception(SAFETY_CERT_EMPLOYEE_NOT_EXISTS);
        }
        SafetyCertificateDO cert = BeanUtils.toBean(reqVO, SafetyCertificateDO.class);
        cert.setEmployeeId(employee.getId());
        cert.setEmployeeName(employee.getName());
        cert.setEmployeeNo(employee.getEmployeeNo());
        cert.setDeptName(employee.getDeptName());
        cert.setCertNo(StrUtil.trim(reqVO.getCertNo()));
        cert.setWarnDays(reqVO.getWarnDays() == null ? DEFAULT_WARN_DAYS : reqVO.getWarnDays());
        // å·²æ³¨é”€çŠ¶æ€ä¸å…è®¸é€šè¿‡ç¼–è¾‘æ”¹å›ž
        String currentStatus = old == null ? reqVO.getStatus() : old.getStatus();
        cert.setStatus(resolveStatus(reqVO.getExpireDate(), cert.getWarnDays(), currentStatus));
        cert.setRemark(reqVO.getRemark() == null ? "" : reqVO.getRemark().trim());
        return cert;
    }
    private void validateCertificateForCreate(SafetyCertificateSaveReqVO reqVO) {
        validateExpireAfterIssue(reqVO);
        if (certificateMapper.selectByCertNo(StrUtil.trim(reqVO.getCertNo())) != null) {
            throw exception(SAFETY_CERT_NO_DUPLICATE);
        }
    }
    private void validateCertificateForUpdate(SafetyCertificateSaveReqVO reqVO, SafetyCertificateDO old) {
        validateExpireAfterIssue(reqVO);
        SafetyCertificateDO byCertNo = certificateMapper.selectByCertNo(StrUtil.trim(reqVO.getCertNo()));
        if (byCertNo != null && !byCertNo.getId().equals(old.getId())) {
            throw exception(SAFETY_CERT_NO_DUPLICATE);
        }
    }
    private void validateExpireAfterIssue(SafetyCertificateSaveReqVO reqVO) {
        if (reqVO.getIssueDate() != null && reqVO.getExpireDate() != null
                && !Objects.requireNonNull(reqVO.getExpireDate()).isAfter(reqVO.getIssueDate())) {
            throw exception(SAFETY_CERT_EXPIRE_BEFORE_ISSUE);
        }
    }
    private SafetyCertificateDO validateCertificateExists(Long id) {
        SafetyCertificateDO cert = id == null ? null : certificateMapper.selectById(id);
        if (cert == null) {
            throw exception(SAFETY_CERT_NOT_EXISTS);
        }
        return cert;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyFileService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
package cn.iocoder.yudao.module.mes.service.safety;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
 * å®‰å…¨ç”Ÿäº§ - ç§»åŠ¨ç«¯é™„ä»¶ä¸Šä¼  Service
 *
 * <p>巡检、隐患、作业票、演练等环节的现场拍照统一走这里:
 * è½åˆ° infra æ–‡ä»¶å­˜å‚¨ï¼Œè¿”回可访问 URL,多个附件逗号拼接后存入业务表。</p>
 *
 * @author è¶…级管理员
 */
@Service
@Slf4j
public class SafetyFileService {
    /** å•个文件最大 20MB,手机原图裁切后足够 */
    private static final long MAX_SIZE = 20L * 1024 * 1024;
    /** å…è®¸çš„图片类型 */
    private static final List<String> ALLOWED_EXT = List.of(
            "jpg", "jpeg", "png", "webp", "heic", "gif", "bmp", "pdf", "doc", "docx", "xls", "xlsx");
    @Resource
    private FileApi fileApi;
    /**
     * ä¸Šä¼ å•个文件
     *
     * @param file     æ–‡ä»¶
     * @param bizType  ä¸šåŠ¡ç±»åž‹ï¼ˆsafety/inspection、safety/hazard ç­‰ï¼‰ï¼Œç”¨ä½œå­˜å‚¨ç›®å½•
     * @return æ–‡ä»¶è®¿é—® URL
     */
    public String upload(MultipartFile file, String bizType) {
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("上传文件不能为空");
        }
        if (file.getSize() > MAX_SIZE) {
            throw new IllegalArgumentException("单个文件不能超过 20MB");
        }
        String originalName = StrUtil.blankToDefault(file.getOriginalFilename(), "photo.jpg");
        String ext = StrUtil.subAfter(originalName, ".", true).toLowerCase();
        if (StrUtil.isNotBlank(ext) && !ALLOWED_EXT.contains(ext)) {
            throw new IllegalArgumentException("不支持的文件类型:" + ext);
        }
        byte[] content;
        try {
            content = IoUtil.readBytes(file.getInputStream());
        } catch (IOException e) {
            throw new IllegalStateException("读取上传文件失败", e);
        }
        String directory = StrUtil.blankToDefault(bizType, "safety") + "/" + java.time.LocalDate.now();
        return fileApi.createFile(content, originalName, directory, file.getContentType());
    }
    /**
     * æ‰¹é‡ä¸Šä¼ ï¼ˆç§»åŠ¨ç«¯ä¸€æ¬¡æ‹å¤šå¼ ï¼‰
     *
     * @return æ–‡ä»¶ URL åˆ—表
     */
    public List<String> uploadBatch(List<MultipartFile> files, String bizType) {
        if (files == null || files.isEmpty()) {
            return List.of();
        }
        if (files.size() > 9) {
            throw new IllegalArgumentException("单次最多上传 9 å¼ ");
        }
        List<String> urls = new ArrayList<>();
        for (MultipartFile file : files) {
            urls.add(upload(file, bizType));
        }
        return urls;
    }
    /**
     * åˆ é™¤é™„件(从逗号串里剔除某个 URL)
     */
    public String removeUrl(String urls, String target) {
        if (StrUtil.isBlank(urls) || StrUtil.isBlank(target)) {
            return urls;
        }
        return Arrays.stream(urls.split(","))
                .map(String::trim)
                .filter(url -> !url.equals(target.trim()))
                .reduce((a, b) -> a + "," + b)
                .orElse("");
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyHazardService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,220 @@
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;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.hazard.*;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyHazardDO;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyHazardMapper;
import cn.iocoder.yudao.module.mes.enums.safety.SafetyHazardLevelEnum;
import cn.iocoder.yudao.module.mes.enums.safety.SafetyHazardSourceEnum;
import cn.iocoder.yudao.module.mes.enums.safety.SafetyHazardStatusEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDate;
import java.time.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.*;
/**
 * å®‰å…¨ç”Ÿäº§ - é𐿂£ç™»è®°ä¸Žé—­çޝ Service
 *
 * <p>完整闭环:上报 â†’ åˆ†æ´¾ â†’ æ•´æ”¹ â†’ å¤æŸ¥ï¼ˆé€šè¿‡åˆ™é—­çŽ¯ï¼Œä¸é€šè¿‡é€€å›žæ•´æ”¹ï¼‰ã€‚
 * æ¯ä¸ªçŽ¯èŠ‚éƒ½æ ¡éªŒå½“å‰çŠ¶æ€ï¼Œé˜²æ­¢è·³æ­¥æˆ–é‡å¤æ“ä½œã€‚</p>
 *
 * @author è¶…级管理员
 */
@Service
@Validated
public class SafetyHazardService {
    private static final DateTimeFormatter CODE_DATE = DateTimeFormatter.ofPattern("yyyyMMdd");
    @Resource
    private SafetyHazardMapper hazardMapper;
    @Resource
    private SafetyRefResolver refResolver;
    /**
     * é𐿂£ä¸ŠæŠ¥ï¼ˆå«ç§»åŠ¨ç«¯æ‹ç…§ä¸Šä¼ çš„ photoUrls)
     */
    @Transactional(rollbackFor = Exception.class)
    public Long createHazard(SafetyHazardSaveReqVO reqVO) {
        reqVO.setId(null);
        SafetyHazardDO hazard = BeanUtils.toBean(reqVO, SafetyHazardDO.class);
        hazard.setCode(hazardMapper.selectNextCode("HZ" + LocalDate.now().format(CODE_DATE)));
        hazard.setTitle(StrUtil.trim(reqVO.getTitle()));
        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());
        hazard.setStatus(SafetyHazardStatusEnum.REPORTED.getStatus());
        hazard.setRemark(reqVO.getRemark() == null ? "" : reqVO.getRemark().trim());
        hazardMapper.insert(hazard);
        return hazard.getId();
    }
    /**
     * ä¿®æ”¹é𐿂£åŸºç¡€ä¿¡æ¯ï¼ˆä»…未分派前允许改关键字段)
     */
    public void updateHazard(SafetyHazardSaveReqVO reqVO) {
        SafetyHazardDO old = validateHazardExists(reqVO.getId());
        if (!SafetyHazardStatusEnum.REPORTED.getStatus().equals(old.getStatus())
                && !SafetyHazardStatusEnum.ASSIGNED.getStatus().equals(old.getStatus())) {
            throw exception(SAFETY_HAZARD_STATUS_ERROR);
        }
        SafetyHazardDO update = BeanUtils.toBean(reqVO, SafetyHazardDO.class);
        // ç¼–号与状态不允许通过普通编辑修改
        update.setCode(null);
        update.setStatus(null);
        hazardMapper.updateById(update);
    }
    /**
     * åˆ†æ´¾æ•´æ”¹è´£ä»»äºº
     */
    @Transactional(rollbackFor = Exception.class)
    public void assignHazard(SafetyHazardAssignReqVO reqVO) {
        SafetyHazardDO hazard = validateHazardExists(reqVO.getId());
        if (!SafetyHazardStatusEnum.REPORTED.getStatus().equals(hazard.getStatus())
                && !SafetyHazardStatusEnum.REJECTED.getStatus().equals(hazard.getStatus())) {
            throw exception(SAFETY_HAZARD_STATUS_ERROR);
        }
        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.setAssigneeId(assignee.getId());
        update.setAssignee(assignee.getName());
        update.setAssignTime(LocalDateTime.now());
        update.setRectifyDeadline(reqVO.getRectifyDeadline());
        update.setStatus(SafetyHazardStatusEnum.RECTIFYING.getStatus());
        if (StrUtil.isNotBlank(reqVO.getRemark())) {
            update.setRemark(reqVO.getRemark().trim());
        }
        hazardMapper.updateById(update);
    }
    /**
     * æäº¤æ•´æ”¹ç»“æžœ
     */
    @Transactional(rollbackFor = Exception.class)
    public void rectifyHazard(SafetyHazardRectifyReqVO reqVO) {
        SafetyHazardDO hazard = validateHazardExists(reqVO.getId());
        if (!SafetyHazardStatusEnum.RECTIFYING.getStatus().equals(hazard.getStatus())
                && !SafetyHazardStatusEnum.REJECTED.getStatus().equals(hazard.getStatus())) {
            throw exception(SAFETY_HAZARD_STATUS_ERROR);
        }
        SafetyHazardDO update = new SafetyHazardDO();
        update.setId(hazard.getId());
        update.setRectifyDesc(reqVO.getRectifyDesc().trim());
        update.setRectifyPhotoUrls(reqVO.getRectifyPhotoUrls());
        update.setRectifyTime(LocalDateTime.now());
        update.setStatus(SafetyHazardStatusEnum.RECHECKING.getStatus());
        hazardMapper.updateById(update);
    }
    /**
     * å¤æŸ¥ï¼šé€šè¿‡åˆ™é—­çŽ¯ï¼Œä¸é€šè¿‡é€€å›žæ•´æ”¹ã€‚
     *
     * <p>校验整改人与复查人不能是同一人,保证闭环的两级分离。</p>
     */
    @Transactional(rollbackFor = Exception.class)
    public void recheckHazard(SafetyHazardRecheckReqVO reqVO) {
        SafetyHazardDO hazard = validateHazardExists(reqVO.getId());
        if (!SafetyHazardStatusEnum.RECHECKING.getStatus().equals(hazard.getStatus())) {
            throw exception(SAFETY_HAZARD_STATUS_ERROR);
        }
        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.setRecheckerId(rechecker.getId());
        update.setRechecker(rechecker.getName());
        update.setRecheckTime(LocalDateTime.now());
        update.setRecheckResult(reqVO.getRecheckResult());
        update.setRecheckRemark(reqVO.getRecheckRemark());
        if (passed) {
            update.setStatus(SafetyHazardStatusEnum.CLOSED.getStatus());
            update.setCloseTime(LocalDateTime.now());
        } else {
            update.setStatus(SafetyHazardStatusEnum.REJECTED.getStatus());
        }
        hazardMapper.updateById(update);
    }
    public void deleteHazard(Long id) {
        validateHazardExists(id);
        hazardMapper.deleteById(id);
    }
    public SafetyHazardDO getHazard(Long id) {
        return validateHazardExists(id);
    }
    public PageResult<SafetyHazardDO> getHazardPage(SafetyHazardPageReqVO reqVO) {
        reqVO.normalizeTimeRange();
        return hazardMapper.selectPage(reqVO);
    }
    /**
     * æ‰¹é‡å›žå¡«ä¸ŠæŠ¥äºº / æ•´æ”¹è´£ä»»äºº / å¤æŸ¥äººçš„æœ€æ–°å§“名
     */
    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) {
        respVO.setLevelName(SafetyHazardLevelEnum.getNameByLevel(hazard.getLevel()));
        respVO.setSourceName(SafetyHazardSourceEnum.getNameBySource(hazard.getSource()));
        respVO.setStatusName(SafetyHazardStatusEnum.getNameByStatus(hazard.getStatus()));
        respVO.setRecheckResultName(switch (StrUtil.blankToDefault(hazard.getRecheckResult(), "")) {
            case "PASSED" -> "通过";
            case "REJECTED" -> "不通过";
            default -> "";
        });
        boolean overdue = hazard.getRectifyDeadline() != null
                && hazard.getRectifyDeadline().isBefore(LocalDate.now())
                && !SafetyHazardStatusEnum.isFinished(hazard.getStatus());
        respVO.setOverdue(overdue);
    }
    private SafetyHazardDO validateHazardExists(Long id) {
        SafetyHazardDO hazard = id == null ? null : hazardMapper.selectById(id);
        if (hazard == null) {
            throw exception(SAFETY_HAZARD_NOT_EXISTS);
        }
        return hazard;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyInspectionService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,223 @@
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;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.inspection.*;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyInspectionDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyInspectionPointDO;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyInspectionMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyInspectionPointMapper;
import cn.iocoder.yudao.module.mes.enums.safety.SafetyInspectCycleEnum;
import cn.iocoder.yudao.module.mes.enums.safety.SafetyInspectionPointTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.SafetyErrorCodeConstants.*;
/**
 * å®‰å…¨ç”Ÿäº§ - å·¡æ£€ç‚¹ä½ä¸Žå·¡æ£€è®°å½• Service
 *
 * <p>覆盖高压配电室、机组区等日常安全巡检。巡检发现异常时可直接转隐患上报。</p>
 *
 * @author è¶…级管理员
 */
@Service
@Validated
public class SafetyInspectionService {
    @Resource
    private SafetyInspectionPointMapper pointMapper;
    @Resource
    private SafetyInspectionMapper inspectionMapper;
    @Resource
    private SafetyRefResolver refResolver;
    // ==================== å·¡æ£€ç‚¹ä½ ====================
    public Long createPoint(SafetyInspectionPointSaveReqVO reqVO) {
        reqVO.setId(null);
        String code = StrUtil.trim(reqVO.getCode());
        if (pointMapper.selectByCode(code) != null) {
            throw exception(SAFETY_POINT_CODE_DUPLICATE);
        }
        SafetyInspectionPointDO point = BeanUtils.toBean(reqVO, SafetyInspectionPointDO.class);
        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();
    }
    public void updatePoint(SafetyInspectionPointSaveReqVO reqVO) {
        SafetyInspectionPointDO old = validatePointExists(reqVO.getId());
        String code = StrUtil.trim(reqVO.getCode());
        SafetyInspectionPointDO byCode = pointMapper.selectByCode(code);
        if (byCode != null && !byCode.getId().equals(old.getId())) {
            throw exception(SAFETY_POINT_CODE_DUPLICATE);
        }
        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);
    }
    public void deletePoint(Long id) {
        validatePointExists(id);
        pointMapper.deleteById(id);
    }
    public SafetyInspectionPointDO getPoint(Long id) {
        return validatePointExists(id);
    }
    public PageResult<SafetyInspectionPointDO> getPointPage(SafetyInspectionPointPageReqVO reqVO) {
        return pointMapper.selectPage(reqVO);
    }
    /**
     * å¯ç”¨ä¸­çš„巡检点位列表(移动端巡检选择用)
     */
    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) {
        respVO.setPointTypeName(SafetyInspectionPointTypeEnum.getNameByType(point.getPointType()));
        respVO.setInspectCycleName(SafetyInspectCycleEnum.getNameByCycle(point.getInspectCycle()));
    }
    // ==================== å·¡æ£€è®°å½• ====================
    public Long createInspection(SafetyInspectionSaveReqVO reqVO) {
        reqVO.setId(null);
        SafetyInspectionPointDO point = validatePointExists(reqVO.getPointId());
        if (Integer.valueOf(1).equals(point.getStatus())) {
            throw exception(SAFETY_POINT_DISABLED);
        }
        SafetyInspectionDO inspection = buildInspection(reqVO, point);
        inspectionMapper.insert(inspection);
        return inspection.getId();
    }
    public void updateInspection(SafetyInspectionSaveReqVO reqVO) {
        SafetyInspectionDO old = validateInspectionExists(reqVO.getId());
        SafetyInspectionPointDO point = validatePointExists(reqVO.getPointId());
        SafetyInspectionDO inspection = buildInspection(reqVO, point);
        inspection.setId(old.getId());
        inspectionMapper.updateById(inspection);
    }
    public void deleteInspection(Long id) {
        validateInspectionExists(id);
        inspectionMapper.deleteById(id);
    }
    public SafetyInspectionDO getInspection(Long id) {
        return validateInspectionExists(id);
    }
    public PageResult<SafetyInspectionDO> getInspectionPage(SafetyInspectionPageReqVO reqVO) {
        return inspectionMapper.selectPage(reqVO);
    }
    private SafetyInspectionDO buildInspection(SafetyInspectionSaveReqVO reqVO, SafetyInspectionPointDO point) {
        SafetyInspectionDO inspection = BeanUtils.toBean(reqVO, SafetyInspectionDO.class);
        inspection.setPointId(point.getId());
        inspection.setPointName(point.getName());
        inspection.setPointType(point.getPointType());
        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());
        inspection.setRemark(reqVO.getRemark() == null ? "" : reqVO.getRemark().trim());
        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);
        return latest == null ? null : latest.getInspectTime();
    }
    private SafetyInspectionPointDO validatePointExists(Long id) {
        SafetyInspectionPointDO point = id == null ? null : pointMapper.selectById(id);
        if (point == null) {
            throw exception(SAFETY_POINT_NOT_EXISTS);
        }
        return point;
    }
    private SafetyInspectionDO validateInspectionExists(Long id) {
        SafetyInspectionDO inspection = id == null ? null : inspectionMapper.selectById(id);
        if (inspection == null) {
            throw exception(SAFETY_INSPECTION_NOT_EXISTS);
        }
        return inspection;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyLedgerExportService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,334 @@
package cn.iocoder.yudao.module.mes.service.safety;
import cn.idev.excel.FastExcelFactory;
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.ledger.SafetyLedgerExportReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.*;
import cn.iocoder.yudao.module.mes.enums.safety.*;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.SafetyErrorCodeConstants.SAFETY_LEDGER_DATE_RANGE_INVALID;
/**
 * å®‰å…¨ç”Ÿäº§ - ç”µåŠ›è¡Œä¸šæ ‡å‡†åŒ–å°è´¦å¯¼å‡º Service
 *
 * <p>按「全套」导出时一次生成多 Sheet å·¥ä½œç°¿ï¼Œè¦†ç›–安全标准化检查所需的全部台账:
 * ç»„织架构、特种作业证、巡检记录、隐患台账、作业票、监护记录、物资台账、
 * å‡ºå…¥åº“流水、培训记录、演练记录、事故登记。</p>
 *
 * @author è¶…级管理员
 */
@Service
public class SafetyLedgerExportService {
    @Resource
    private SafetyOrgService orgService;
    @Resource
    private SafetyCertificateService certificateService;
    @Resource
    private SafetyInspectionService inspectionService;
    @Resource
    private SafetyHazardService hazardService;
    @Resource
    private SafetyWorkPermitService permitService;
    @Resource
    private SafetyMaterialService materialService;
    @Resource
    private SafetyTrainingService trainingService;
    /**
     * å¯¼å‡ºæ ‡å‡†åŒ–台账
     */
    public void export(SafetyLedgerExportReqVO reqVO, HttpServletResponse response) throws IOException {
        LocalDate start = reqVO.getStartDate();
        LocalDate end = reqVO.getEndDate();
        if (start != null && end != null && start.isAfter(end)) {
            throw exception(SAFETY_LEDGER_DATE_RANGE_INVALID);
        }
        String type = reqVO.getType() == null ? "ALL" : reqVO.getType();
        List<Sheet> sheets = new ArrayList<>();
        if ("ALL".equals(type) || "CERT".equals(type)) {
            sheets.add(certSheet());
        }
        if ("ALL".equals(type) || "INSPECTION".equals(type)) {
            sheets.add(inspectionSheet(start, end));
        }
        if ("ALL".equals(type) || "HAZARD".equals(type)) {
            sheets.add(hazardSheet(start, end));
        }
        if ("ALL".equals(type) || "PERMIT".equals(type)) {
            sheets.add(permitSheet(start, end));
            sheets.add(monitorSheet());
        }
        if ("ALL".equals(type) || "MATERIAL".equals(type)) {
            sheets.add(materialSheet());
            sheets.add(stockSheet(start, end));
        }
        if ("ALL".equals(type) || "TRAINING".equals(type)) {
            sheets.add(trainingSheet(start, end));
        }
        if ("ALL".equals(type) || "DRILL".equals(type)) {
            sheets.add(drillSheet(start, end));
        }
        if ("ALL".equals(type) || "ACCIDENT".equals(type)) {
            sheets.add(accidentSheet(start, end));
        }
        String filename = switch (type) {
            case "CERT" -> "特种作业证台账.xlsx";
            case "INSPECTION" -> "安全巡检台账.xlsx";
            case "HAZARD" -> "隐患台账.xlsx";
            case "PERMIT" -> "高危作业票台账.xlsx";
            case "MATERIAL" -> "应急物资台账.xlsx";
            case "TRAINING" -> "安全培训台账.xlsx";
            case "DRILL" -> "消防演练台账.xlsx";
            case "ACCIDENT" -> "事故登记台账.xlsx";
            default -> "安全生产标准化台账.xlsx";
        };
        write(response, filename, sheets);
    }
    // ==================== å„ Sheet ====================
    private Sheet certSheet() {
        List<String> headers = List.of("持证人", "工号", "部门", "证件类型", "证件编号", "发证机关",
                "发证日期", "有效期至", "下次复审", "状态", "预警天数", "备注");
        var pageReqVO = new cn.iocoder.yudao.module.mes.controller.admin.safety.vo.cert.SafetyCertificatePageReqVO();
        pageReqVO.setPageNo(1);
        pageReqVO.setPageSize(50_000);
        var certs = certificateService.getCertificatePage(pageReqVO).getList();
        // å¯¼å‡ºæ—¶ç”¨å‘˜å·¥æ¡£æ¡ˆçš„æœ€æ–°å§“名/工号/部门
        certificateService.fillEmployeeInfo(certs);
        List<List<Object>> rows = certs.stream()
                .map(c -> Arrays.<Object>asList(
                        str(c.getEmployeeName()), str(c.getEmployeeNo()), str(c.getDeptName()),
                        SafetyCertificateTypeEnum.getNameByType(c.getCertType()), str(c.getCertNo()),
                        str(c.getIssuer()), date(c.getIssueDate()), date(c.getExpireDate()),
                        date(c.getReviewDate()),
                        SafetyCertificateStatusEnum.getNameByStatus(SafetyCertificateService.resolveStatus(
                                c.getExpireDate(), c.getWarnDays(), c.getStatus())),
                        Objects.toString(c.getWarnDays(), ""), str(c.getRemark())))
                .toList();
        return new Sheet("特种作业证台账", headers, rows);
    }
    private Sheet inspectionSheet(LocalDate start, LocalDate end) {
        List<String> headers = List.of("巡检时间", "点位名称", "点位类型", "巡检人", "结果", "巡检情况", "发现隐患数");
        var reqVO = new cn.iocoder.yudao.module.mes.controller.admin.safety.vo.inspection.SafetyInspectionPageReqVO();
        reqVO.setPageNo(1);
        reqVO.setPageSize(50_000);
        if (start != null && end != null) {
            reqVO.setInspectTime(new java.time.LocalDateTime[]{start.atStartOfDay(), end.atTime(23, 59, 59)});
        }
        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()),
                        str(i.getInspector()),
                        "NORMAL".equals(i.getResult()) ? "正常" : "异常",
                        str(i.getContent()), Objects.toString(i.getHazardCount(), "0")))
                .toList();
        return new Sheet("安全巡检台账", headers, rows);
    }
    private Sheet hazardSheet(LocalDate start, LocalDate end) {
        List<String> headers = List.of("隐患编号", "标题", "等级", "来源", "位置", "上报人", "上报时间",
                "整改责任人", "整改期限", "整改措施", "整改时间", "复查人", "复查时间", "复查结论", "状态", "闭环时间");
        var reqVO = new cn.iocoder.yudao.module.mes.controller.admin.safety.vo.hazard.SafetyHazardPageReqVO();
        reqVO.setPageNo(1);
        reqVO.setPageSize(50_000);
        if (start != null && end != null) {
            reqVO.setReportTime(new java.time.LocalDateTime[]{start.atStartOfDay(), end.atTime(23, 59, 59)});
        }
        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()),
                        SafetyHazardSourceEnum.getNameBySource(h.getSource()),
                        str(h.getLocation()), str(h.getReporter()), dateTime(h.getReportTime()),
                        str(h.getAssignee()), date(h.getRectifyDeadline()), str(h.getRectifyDesc()),
                        dateTime(h.getRectifyTime()), str(h.getRechecker()), dateTime(h.getRecheckTime()),
                        "PASSED".equals(str(h.getRecheckResult())) ? "通过"
                                : "REJECTED".equals(str(h.getRecheckResult())) ? "不通过" : "",
                        SafetyHazardStatusEnum.getNameByStatus(h.getStatus()), dateTime(h.getCloseTime())))
                .toList();
        return new Sheet("隐患台账", headers, rows);
    }
    private Sheet permitSheet(LocalDate start, LocalDate end) {
        List<String> headers = List.of("作业票编号", "作业类型", "作业名称", "作业区域", "作业地点", "申请人",
                "申请时间", "计划开始", "计划结束", "实际开始", "实际结束", "风险等级", "监护人",
                "审批人", "审批时间", "审批意见", "状态");
        var reqVO = new cn.iocoder.yudao.module.mes.controller.admin.safety.vo.permit.SafetyWorkPermitPageReqVO();
        reqVO.setPageNo(1);
        reqVO.setPageSize(50_000);
        if (start != null && end != null) {
            reqVO.setPlanStartTime(new java.time.LocalDateTime[]{start.atStartOfDay(), end.atTime(23, 59, 59)});
        }
        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()),
                        str(p.getApplicant()), dateTime(p.getApplyTime()), dateTime(p.getPlanStartTime()),
                        dateTime(p.getPlanEndTime()), dateTime(p.getActualStartTime()), dateTime(p.getActualEndTime()),
                        SafetyRiskLevelEnum.getNameByLevel(p.getRiskLevel()), str(p.getGuardian()),
                        str(p.getApprover()), dateTime(p.getApproveTime()), str(p.getApproveComment()),
                        SafetyPermitStatusEnum.getNameByStatus(p.getStatus())))
                .toList();
        return new Sheet("高危作业票台账", headers, rows);
    }
    private Sheet monitorSheet() {
        List<String> headers = List.of("作业票编号", "监护时间", "监护人", "监护内容", "异常情况");
        var reqVO = new cn.iocoder.yudao.module.mes.controller.admin.safety.vo.permit.SafetyWorkPermitPageReqVO();
        reqVO.setPageNo(1);
        reqVO.setPageSize(5_000);
        List<List<Object>> rows = new ArrayList<>();
        for (SafetyWorkPermitDO permit : permitService.getPermitPage(reqVO).getList()) {
            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())));
            }
        }
        return new Sheet("作业监护记录", headers, rows);
    }
    private Sheet materialSheet() {
        List<String> headers = List.of("物资编码", "物资名称", "类别", "规格型号", "单位", "库存数量",
                "安全库存", "存放位置", "有效期至", "保管人", "状态");
        var reqVO = new cn.iocoder.yudao.module.mes.controller.admin.safety.vo.material.SafetyMaterialPageReqVO();
        reqVO.setPageNo(1);
        reqVO.setPageSize(50_000);
        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()),
                        str(m.getSpec()), str(m.getUnit()), number(m.getStockQty()), number(m.getSafeStock()),
                        str(m.getStorageLocation()), date(m.getExpireDate()), str(m.getManager()),
                        Integer.valueOf(0).equals(m.getStatus()) ? "启用" : "停用"))
                .toList();
        return new Sheet("应急物资台账", headers, rows);
    }
    private Sheet stockSheet(LocalDate start, LocalDate end) {
        List<String> headers = List.of("业务日期", "物资名称", "类别", "业务类型", "数量",
                "操作前库存", "操作后库存", "经办人", "领用人", "用途/去向");
        var reqVO = new cn.iocoder.yudao.module.mes.controller.admin.safety.vo.material.SafetyMaterialStockPageReqVO();
        reqVO.setPageNo(1);
        reqVO.setPageSize(50_000);
        if (start != null && end != null) {
            reqVO.setBizDate(new LocalDate[]{start, end});
        }
        List<List<Object>> rows = materialService.getStockPage(reqVO).getList().stream()
                .map(s -> Arrays.<Object>asList(
                        date(s.getBizDate()), str(s.getMaterialName()),
                        SafetyMaterialCategoryEnum.getNameByCategory(s.getCategory()),
                        "IN".equals(s.getBizType()) ? "入库" : "出库",
                        number(s.getQuantity()), number(s.getStockBefore()), number(s.getStockAfter()),
                        str(s.getOperator()), str(s.getReceiver()), str(s.getPurpose())))
                .toList();
        return new Sheet("物资出入库流水", headers, rows);
    }
    private Sheet trainingSheet(LocalDate start, LocalDate end) {
        List<String> headers = List.of("培训日期", "培训主题", "培训类型", "学时", "讲师/组织人",
                "培训地点", "参加人数", "培训内容", "考核情况");
        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()),
                        number(t.getDurationHours()), str(t.getTrainer()), str(t.getLocation()),
                        Objects.toString(t.getParticipantCount(), "0"),
                        str(t.getContent()), str(t.getExamResult())))
                .toList();
        return new Sheet("安全培训台账", headers, rows);
    }
    private Sheet drillSheet(LocalDate start, LocalDate end) {
        List<String> headers = List.of("演练日期", "演练名称", "演练类型", "地点", "组织人",
                "参加人数", "时长(分钟)", "演练过程", "总结与改进");
        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()),
                        str(d.getOrganizer()), Objects.toString(d.getParticipantCount(), "0"),
                        Objects.toString(d.getDurationMinutes(), ""),
                        str(d.getProcessDesc()), str(d.getSummary())))
                .toList();
        return new Sheet("消防演练台账", headers, rows);
    }
    private Sheet accidentSheet(LocalDate start, LocalDate end) {
        List<String> headers = List.of("事故编号", "事故名称", "事故类型", "等级", "发生时间", "发生地点",
                "伤亡情况", "经济损失(元)", "事故经过", "原因分析", "处理措施", "状态");
        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()),
                        SafetyAccidentLevelEnum.getNameByLevel(a.getLevel()),
                        dateTime(a.getOccurTime()), str(a.getLocation()), str(a.getCasualtyDesc()),
                        number(a.getEconomicLoss()), str(a.getDescription()), str(a.getCauseAnalysis()),
                        str(a.getMeasures()), SafetyAccidentStatusEnum.getNameByStatus(a.getStatus())))
                .toList();
        return new Sheet("事故登记台账", headers, rows);
    }
    // ==================== å·¥å…·æ–¹æ³• ====================
    private static String str(String value) {
        return value == null ? "" : value;
    }
    private static String date(LocalDate value) {
        return value == null ? "" : value.toString();
    }
    private static String dateTime(java.time.LocalDateTime value) {
        return value == null ? "" : value.toString().replace('T', ' ');
    }
    private static String number(java.math.BigDecimal value) {
        return value == null ? "" : value.stripTrailingZeros().toPlainString();
    }
    private record Sheet(String name, List<String> headers, List<List<Object>> rows) {
    }
    private void write(HttpServletResponse response, String filename, List<Sheet> sheets) throws IOException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
        try (var writer = FastExcelFactory.write(response.getOutputStream()).autoCloseStream(false).build()) {
            for (int i = 0; i < sheets.size(); i++) {
                Sheet sheet = sheets.get(i);
                writer.write(sheet.rows(), FastExcelFactory.writerSheet(i, sheet.name())
                        .head(sheet.headers().stream().map(List::of).toList()).build());
            }
        }
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyMaterialService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,192 @@
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;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.material.*;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyMaterialDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyMaterialStockDO;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyMaterialMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyMaterialStockMapper;
import cn.iocoder.yudao.module.mes.enums.safety.SafetyMaterialCategoryEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.SafetyErrorCodeConstants.*;
/**
 * å®‰å…¨ç”Ÿäº§ - åº”急物资台账与出入库 Service
 *
 * <p>覆盖电力绝缘用具、危化品、消防物资。出库时行锁校验库存,避免并发扣减为负。</p>
 *
 * @author è¶…级管理员
 */
@Service
@Validated
public class SafetyMaterialService {
    @Resource
    private SafetyMaterialMapper materialMapper;
    @Resource
    private SafetyRefResolver refResolver;
    @Resource
    private SafetyMaterialStockMapper stockMapper;
    // ==================== ç‰©èµ„台账 ====================
    public Long createMaterial(SafetyMaterialSaveReqVO reqVO) {
        reqVO.setId(null);
        String code = StrUtil.trim(reqVO.getCode());
        if (materialMapper.selectByCode(code) != null) {
            throw exception(SAFETY_MATERIAL_CODE_DUPLICATE);
        }
        SafetyMaterialDO material = BeanUtils.toBean(reqVO, SafetyMaterialDO.class);
        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);
        return material.getId();
    }
    public void updateMaterial(SafetyMaterialSaveReqVO reqVO) {
        SafetyMaterialDO old = validateMaterialExists(reqVO.getId());
        String code = StrUtil.trim(reqVO.getCode());
        SafetyMaterialDO byCode = materialMapper.selectByCode(code);
        if (byCode != null && !byCode.getId().equals(old.getId())) {
            throw exception(SAFETY_MATERIAL_CODE_DUPLICATE);
        }
        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);
    }
    public void deleteMaterial(Long id) {
        validateMaterialExists(id);
        materialMapper.deleteById(id);
    }
    public SafetyMaterialDO getMaterial(Long id) {
        return validateMaterialExists(id);
    }
    public PageResult<SafetyMaterialDO> getMaterialPage(SafetyMaterialPageReqVO reqVO) {
        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();
        BigDecimal safe = material.getSafeStock() == null ? BigDecimal.ZERO : material.getSafeStock();
        respVO.setBelowSafeStock(safe.signum() > 0 && stock.compareTo(safe) < 0);
        if (material.getExpireDate() != null) {
            respVO.setExpireRemainingDays(ChronoUnit.DAYS.between(LocalDate.now(), material.getExpireDate()));
        }
    }
    // ==================== å‡ºå…¥åº“ ====================
    /**
     * å‡ºå…¥åº“登记:行锁读取库存 â†’ è®¡ç®—变动 â†’ å†™æµæ°´ â†’ å›žå†™åº“å­˜
     */
    @Transactional(rollbackFor = Exception.class)
    public Long createStock(SafetyMaterialStockSaveReqVO reqVO) {
        SafetyMaterialDO material = materialMapper.selectByIdForUpdate(reqVO.getMaterialId());
        if (material == null) {
            throw exception(SAFETY_MATERIAL_NOT_EXISTS);
        }
        BigDecimal before = material.getStockQty() == null ? BigDecimal.ZERO : material.getStockQty();
        boolean inbound = "IN".equals(reqVO.getBizType());
        BigDecimal after = inbound ? before.add(reqVO.getQuantity()) : before.subtract(reqVO.getQuantity());
        if (!inbound && after.signum() < 0) {
            throw exception(SAFETY_MATERIAL_STOCK_NOT_ENOUGH);
        }
        SafetyMaterialStockDO stock = BeanUtils.toBean(reqVO, SafetyMaterialStockDO.class);
        stock.setId(null);
        stock.setMaterialName(material.getName());
        stock.setCategory(material.getCategory());
        stock.setQuantity(reqVO.getQuantity());
        stock.setStockBefore(before);
        stock.setStockAfter(after);
        stock.setBizDate(reqVO.getBizDate() == null ? LocalDate.now() : reqVO.getBizDate());
        stock.setOperator(StrUtil.trim(reqVO.getOperator()));
        stockMapper.insert(stock);
        SafetyMaterialDO update = new SafetyMaterialDO();
        update.setId(material.getId());
        update.setStockQty(after);
        materialMapper.updateById(update);
        return stock.getId();
    }
    public PageResult<SafetyMaterialStockDO> getStockPage(SafetyMaterialStockPageReqVO reqVO) {
        return stockMapper.selectPage(reqVO);
    }
    public static void fillStockDisplay(SafetyMaterialStockDO stock, SafetyMaterialStockRespVO respVO) {
        respVO.setCategoryName(SafetyMaterialCategoryEnum.getNameByCategory(stock.getCategory()));
        respVO.setBizTypeName("IN".equals(stock.getBizType()) ? "入库" : "出库");
    }
    /**
     * é¢„警清单:低于安全库存的物资
     */
    public List<SafetyMaterialDO> getBelowSafeStockList() {
        return materialMapper.selectBelowSafeStock();
    }
    /**
     * é¢„警清单:有效期临近(默认 30 å¤©å†…)的物资
     */
    public List<SafetyMaterialDO> getExpiringList(Integer days) {
        int warnDays = days == null || days <= 0 ? 30 : days;
        return materialMapper.selectExpiringBefore(LocalDate.now().plusDays(warnDays));
    }
    private SafetyMaterialDO validateMaterialExists(Long id) {
        SafetyMaterialDO material = id == null ? null : materialMapper.selectById(id);
        if (material == null) {
            throw exception(SAFETY_MATERIAL_NOT_EXISTS);
        }
        return material;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyOrgService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,157 @@
package cn.iocoder.yudao.module.mes.service.safety;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.org.SafetyOrgRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.org.SafetyOrgSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyOrgDO;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyOrgMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.SafetyErrorCodeConstants.*;
/**
 * å®‰å…¨ç”Ÿäº§ - å®‰å…¨ç»„织架构 Service
 *
 * @author è¶…级管理员
 */
@Service
@Validated
public class SafetyOrgService {
    @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();
    }
    public void updateOrg(SafetyOrgSaveReqVO reqVO) {
        validateOrgExists(reqVO.getId());
        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) {
        validateOrgExists(id);
        if (safetyOrgMapper.selectCountByParentId(id) > 0) {
            throw exception(SAFETY_ORG_HAS_CHILDREN);
        }
        safetyOrgMapper.deleteById(id);
    }
    public SafetyOrgDO getOrg(Long id) {
        return validateOrgExists(id);
    }
    /**
     * å–负责人最新档案(供单条查询回填用)
     */
    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<>())));
        List<SafetyOrgRespVO> roots = grouped.getOrDefault(0L, new ArrayList<>());
        if (CollUtil.isEmpty(roots)) {
            // å…¼å®¹ parentId ä¸ºç©ºçš„脏数据
            roots = nodes.stream().filter(n -> n.getParentId() == null || n.getParentId() == 0L).toList();
        }
        return roots;
    }
    private void validateParent(Long parentId, Long selfId) {
        if (parentId == null || parentId == 0L) {
            return;
        }
        if (parentId.equals(selfId)) {
            throw exception(SAFETY_ORG_PARENT_IS_SELF);
        }
        SafetyOrgDO parent = safetyOrgMapper.selectById(parentId);
        if (parent == null) {
            throw exception(SAFETY_ORG_PARENT_NOT_EXISTS);
        }
        // é˜²æ­¢æŠŠçˆ¶èŠ‚ç‚¹æŒ‚åˆ°è‡ªå·±çš„å­å­™ä¸‹ï¼Œå½¢æˆçŽ¯
        if (selfId != null && isDescendant(parentId, selfId)) {
            throw exception(SAFETY_ORG_PARENT_IS_CHILD);
        }
    }
    private boolean isDescendant(Long candidateId, Long ancestorId) {
        Long current = candidateId;
        int guard = 0;
        while (current != null && current != 0L && guard++ < 100) {
            if (current.equals(ancestorId)) {
                return true;
            }
            SafetyOrgDO node = safetyOrgMapper.selectById(current);
            if (node == null) {
                return false;
            }
            current = node.getParentId();
        }
        return false;
    }
    private SafetyOrgDO validateOrgExists(Long id) {
        if (id == null) {
            throw exception(SAFETY_ORG_NOT_EXISTS);
        }
        SafetyOrgDO org = safetyOrgMapper.selectById(id);
        if (org == null) {
            throw exception(SAFETY_ORG_NOT_EXISTS);
        }
        return org;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyOverviewService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,138 @@
package cn.iocoder.yudao.module.mes.service.safety;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.*;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.*;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.ledger.SafetyOverviewRespVO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
 * å®‰å…¨ç”Ÿäº§ - æ€»è§ˆç»Ÿè®¡ Service
 *
 * <p>提供首页 KPI ä¸Žè¶‹åŠ¿æ•°æ®ï¼Œå…¨éƒ¨åŸºäºŽçŽ°æœ‰å°è´¦å®žæ—¶è®¡ç®—ï¼Œä¸åšå†—ä½™ç»Ÿè®¡è¡¨ã€‚</p>
 *
 * @author è¶…级管理员
 */
@Service
public class SafetyOverviewService {
    private static final DateTimeFormatter MONTH_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM");
    /** è¶‹åŠ¿å±•ç¤ºæœˆæ•° */
    private static final int TREND_MONTHS = 6;
    @Resource
    private SafetyCertificateMapper certificateMapper;
    @Resource
    private SafetyHazardMapper hazardMapper;
    @Resource
    private SafetyWorkPermitMapper permitMapper;
    @Resource
    private SafetyMaterialMapper materialMapper;
    @Resource
    private SafetyTrainingMapper trainingMapper;
    @Resource
    private SafetyDrillMapper drillMapper;
    @Resource
    private SafetyAccidentMapper accidentMapper;
    public SafetyOverviewRespVO getOverview() {
        SafetyOverviewRespVO overview = new SafetyOverviewRespVO();
        LocalDate today = LocalDate.now();
        // ---------- ç‰¹ç§ä½œä¸šè¯ ----------
        List<SafetyCertificateDO> certs = certificateMapper.selectList();
        long expiring = 0;
        long expired = 0;
        for (SafetyCertificateDO cert : certs) {
            String status = SafetyCertificateService.resolveStatus(
                    cert.getExpireDate(), cert.getWarnDays(), cert.getStatus());
            if ("EXPIRED".equals(status)) {
                expired++;
            } else if ("EXPIRING".equals(status)) {
                expiring++;
            }
        }
        overview.setCertTotal((long) certs.size());
        overview.setCertExpiring(expiring);
        overview.setCertExpired(expired);
        // ---------- é𐿂£ ----------
        List<SafetyHazardDO> hazards = hazardMapper.selectList();
        long hazardOpen = hazards.stream()
                .filter(h -> !"CLOSED".equals(h.getStatus())).count();
        long hazardOverdue = hazardMapper.selectOverdue(today).size();
        long hazardClosed = hazards.size() - hazardOpen;
        overview.setHazardTotal((long) hazards.size());
        overview.setHazardOpen(hazardOpen);
        overview.setHazardOverdue(hazardOverdue);
        overview.setHazardCloseRate(hazards.isEmpty() ? 0
                : (int) Math.round(hazardClosed * 100.0 / hazards.size()));
        // ---------- ä½œä¸šç¥¨ ----------
        List<SafetyWorkPermitDO> permits = permitMapper.selectList();
        overview.setPermitPending(permits.stream()
                .filter(p -> "PENDING".equals(p.getStatus())).count());
        overview.setPermitInProgress(permits.stream()
                .filter(p -> "IN_PROGRESS".equals(p.getStatus())).count());
        // ---------- ç‰©èµ„ ----------
        overview.setMaterialBelowSafeStock((long) materialMapper.selectBelowSafeStock().size());
        overview.setMaterialExpiring((long) materialMapper.selectExpiringBefore(today.plusDays(30)).size());
        // ---------- æœ¬æœˆæ´»åЍ ----------
        YearMonth currentMonth = YearMonth.from(today);
        LocalDate monthStart = currentMonth.atDay(1);
        LocalDate monthEnd = currentMonth.atEndOfMonth();
        overview.setTrainingThisMonth((long) trainingMapper.selectListBetween(monthStart, monthEnd).size());
        overview.setDrillThisMonth((long) drillMapper.selectListBetween(monthStart, monthEnd).size());
        overview.setAccidentThisMonth((long) accidentMapper.selectListBetween(
                monthStart.atStartOfDay(), monthEnd.atTime(23, 59, 59)).size());
        // ---------- è¶‹åŠ¿ï¼ˆè¿‘ 6 ä¸ªæœˆï¼‰ ----------
        overview.setHazardTrend(buildTrend(hazards.stream().map(SafetyHazardDO::getReportTime).toList()));
        overview.setAccidentTrend(buildTrend(hazards.isEmpty() ? List.of()
                : accidentMapper.selectList().stream().map(SafetyAccidentDO::getOccurTime).toList()));
        return overview;
    }
    /**
     * æŒ‰æœˆèšåˆæ—¶é—´ç‚¹ï¼Œè¾“出近 N ä¸ªæœˆçš„连续序列(无数据的月份补 0)
     */
    private List<SafetyOverviewRespVO.MonthlyCount> buildTrend(List<LocalDateTime> times) {
        Map<String, Long> counts = new LinkedHashMap<>();
        YearMonth current = YearMonth.from(LocalDate.now());
        List<YearMonth> months = new ArrayList<>();
        for (int i = TREND_MONTHS - 1; i >= 0; i--) {
            YearMonth month = current.minusMonths(i);
            months.add(month);
            counts.put(month.format(MONTH_FORMAT), 0L);
        }
        for (LocalDateTime time : times) {
            if (time == null) {
                continue;
            }
            String key = YearMonth.from(time).format(MONTH_FORMAT);
            if (counts.containsKey(key)) {
                counts.put(key, counts.get(key) + 1);
            }
        }
        return months.stream().map(month -> {
            SafetyOverviewRespVO.MonthlyCount item = new SafetyOverviewRespVO.MonthlyCount();
            item.setMonth(month.format(MONTH_FORMAT));
            item.setCount(counts.get(month.format(MONTH_FORMAT)));
            return item;
        }).toList();
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyRefResolver.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
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.HashMap;
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 new HashMap<>();
        }
        List<Long> ids = employeeIds.stream().filter(Objects::nonNull).distinct().toList();
        if (ids.isEmpty()) {
            return new HashMap<>();
        }
        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 new HashMap<>();
        }
        List<Long> ids = workshopIds.stream().filter(Objects::nonNull).distinct().toList();
        if (ids.isEmpty()) {
            return new HashMap<>();
        }
        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
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,273 @@
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;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.train.*;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyAccidentDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyDrillDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyTrainingDO;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyAccidentMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyDrillMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyTrainingMapper;
import cn.iocoder.yudao.module.mes.enums.safety.*;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
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.*;
/**
 * å®‰å…¨ç”Ÿäº§ - åŸ¹è®­ã€æ¼”练、事故登记 Service
 *
 * <p>三类记录统一归档,供标准化台账导出使用。</p>
 *
 * @author è¶…级管理员
 */
@Service
@Validated
public class SafetyTrainingService {
    private static final DateTimeFormatter CODE_DATE = DateTimeFormatter.ofPattern("yyyyMMdd");
    @Resource
    private SafetyTrainingMapper trainingMapper;
    @Resource
    private SafetyRefResolver refResolver;
    @Resource
    private SafetyDrillMapper drillMapper;
    @Resource
    private SafetyAccidentMapper accidentMapper;
    // ==================== åŸ¹è®­ ====================
    public Long createTraining(SafetyTrainingSaveReqVO reqVO) {
        reqVO.setId(null);
        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);
        return training.getId();
    }
    public void updateTraining(SafetyTrainingSaveReqVO reqVO) {
        validateTrainingExists(reqVO.getId());
        SafetyTrainingDO training = BeanUtils.toBean(reqVO, SafetyTrainingDO.class);
        training.setTitle(StrUtil.trim(reqVO.getTitle()));
        training.setParticipantCount(resolveCount(reqVO.getParticipantCount(), reqVO.getParticipants()));
        trainingMapper.updateById(training);
    }
    public void deleteTraining(Long id) {
        validateTrainingExists(id);
        trainingMapper.deleteById(id);
    }
    public SafetyTrainingDO getTraining(Long id) {
        return validateTrainingExists(id);
    }
    public PageResult<SafetyTrainingDO> getTrainingPage(SafetyTrainingPageReqVO reqVO) {
        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()));
    }
    // ==================== æ¼”练 ====================
    public Long createDrill(SafetyDrillSaveReqVO reqVO) {
        reqVO.setId(null);
        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);
        return drill.getId();
    }
    public void updateDrill(SafetyDrillSaveReqVO reqVO) {
        validateDrillExists(reqVO.getId());
        SafetyDrillDO drill = BeanUtils.toBean(reqVO, SafetyDrillDO.class);
        drill.setTitle(StrUtil.trim(reqVO.getTitle()));
        drill.setParticipantCount(resolveCount(reqVO.getParticipantCount(), reqVO.getParticipants()));
        drillMapper.updateById(drill);
    }
    public void deleteDrill(Long id) {
        validateDrillExists(id);
        drillMapper.deleteById(id);
    }
    public SafetyDrillDO getDrill(Long id) {
        return validateDrillExists(id);
    }
    public PageResult<SafetyDrillDO> getDrillPage(SafetyDrillPageReqVO reqVO) {
        return drillMapper.selectPage(reqVO);
    }
    public static void fillDrillDisplay(SafetyDrillDO drill, SafetyDrillRespVO respVO) {
        respVO.setDrillTypeName(SafetyDrillTypeEnum.getNameByType(drill.getDrillType()));
    }
    // ==================== äº‹æ•… ====================
    public Long createAccident(SafetyAccidentSaveReqVO reqVO) {
        reqVO.setId(null);
        SafetyAccidentDO accident = BeanUtils.toBean(reqVO, SafetyAccidentDO.class);
        accident.setCode(accidentMapper.selectNextCode("AC" + LocalDate.now().format(CODE_DATE)));
        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);
        return accident.getId();
    }
    public void updateAccident(SafetyAccidentSaveReqVO reqVO) {
        validateAccidentExists(reqVO.getId());
        SafetyAccidentDO accident = BeanUtils.toBean(reqVO, SafetyAccidentDO.class);
        accident.setCode(null);
        applyAccidentRefs(accident, reqVO);
        accidentMapper.updateById(accident);
    }
    public void deleteAccident(Long id) {
        validateAccidentExists(id);
        accidentMapper.deleteById(id);
    }
    public SafetyAccidentDO getAccident(Long id) {
        return validateAccidentExists(id);
    }
    public PageResult<SafetyAccidentDO> getAccidentPage(SafetyAccidentPageReqVO reqVO) {
        return accidentMapper.selectPage(reqVO);
    }
    public static void fillAccidentDisplay(SafetyAccidentDO accident, SafetyAccidentRespVO respVO) {
        respVO.setAccidentTypeName(SafetyAccidentTypeEnum.getNameByType(accident.getAccidentType()));
        respVO.setLevelName(SafetyAccidentLevelEnum.getNameByLevel(accident.getLevel()));
        respVO.setStatusName(SafetyAccidentStatusEnum.getNameByStatus(accident.getStatus()));
    }
    // ==================== å°è´¦å¯¼å‡ºç”¨æŸ¥è¯¢ ====================
    public List<SafetyTrainingDO> listTrainingBetween(LocalDate begin, LocalDate end) {
        return trainingMapper.selectListBetween(begin, end);
    }
    public List<SafetyDrillDO> listDrillBetween(LocalDate begin, LocalDate end) {
        return drillMapper.selectListBetween(begin, end);
    }
    public List<SafetyAccidentDO> listAccidentBetween(LocalDate begin, LocalDate end) {
        return accidentMapper.selectListBetween(
                begin == null ? null : begin.atStartOfDay(),
                end == null ? null : end.atTime(23, 59, 59));
    }
    // ==================== ç§æœ‰ ====================
    /** å‚加人数未填时,按姓名逗号分隔数量推断 */
    private static int resolveCount(Integer count, String participants) {
        if (count != null && count > 0) {
            return count;
        }
        if (StrUtil.isBlank(participants)) {
            return 0;
        }
        return (int) java.util.Arrays.stream(participants.split("[,,、]"))
                .map(String::trim).filter(StrUtil::isNotBlank).count();
    }
    private SafetyTrainingDO validateTrainingExists(Long id) {
        SafetyTrainingDO training = id == null ? null : trainingMapper.selectById(id);
        if (training == null) {
            throw exception(SAFETY_TRAINING_NOT_EXISTS);
        }
        return training;
    }
    private SafetyDrillDO validateDrillExists(Long id) {
        SafetyDrillDO drill = id == null ? null : drillMapper.selectById(id);
        if (drill == null) {
            throw exception(SAFETY_DRILL_NOT_EXISTS);
        }
        return drill;
    }
    private SafetyAccidentDO validateAccidentExists(Long id) {
        SafetyAccidentDO accident = id == null ? null : accidentMapper.selectById(id);
        if (accident == null) {
            throw exception(SAFETY_ACCIDENT_NOT_EXISTS);
        }
        return accident;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyWorkPermitService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,286 @@
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;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.permit.*;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyWorkPermitDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyWorkPermitMonitorDO;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyWorkPermitMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyWorkPermitMonitorMapper;
import cn.iocoder.yudao.module.mes.enums.safety.SafetyPermitStatusEnum;
import cn.iocoder.yudao.module.mes.enums.safety.SafetyPermitTypeEnum;
import cn.iocoder.yudao.module.mes.enums.safety.SafetyRiskLevelEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDate;
import java.time.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.*;
/**
 * å®‰å…¨ç”Ÿäº§ - é«˜å±ä½œä¸šç¥¨ Service
 *
 * <p>动火、高压带电、有限空间等作业的线上审批与电子留存。
 * çŠ¶æ€æµè½¬ï¼šè‰ç¨¿ â†’ å¾…审批 â†’ å·²æ‰¹å‡† â†’ ä½œä¸šä¸­ â†’ å·²å®Œç»“;驳回/作废为终止态。</p>
 *
 * @author è¶…级管理员
 */
@Service
@Validated
public class SafetyWorkPermitService {
    private static final DateTimeFormatter CODE_DATE = DateTimeFormatter.ofPattern("yyyyMMdd");
    @Resource
    private SafetyWorkPermitMapper permitMapper;
    @Resource
    private SafetyRefResolver refResolver;
    @Resource
    private SafetyWorkPermitMonitorMapper monitorMapper;
    /**
     * æäº¤ä½œä¸šç”³è¯·ï¼ˆè‰ç¨¿æˆ–待审批)
     */
    @Transactional(rollbackFor = Exception.class)
    public Long createPermit(SafetyWorkPermitSaveReqVO reqVO) {
        reqVO.setId(null);
        validateTimeRange(reqVO);
        SafetyWorkPermitDO permit = BeanUtils.toBean(reqVO, SafetyWorkPermitDO.class);
        permit.setCode(permitMapper.selectNextCode("WP" + LocalDate.now().format(CODE_DATE)));
        permit.setTitle(StrUtil.trim(reqVO.getTitle()));
        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());
        permit.setRemark(reqVO.getRemark() == null ? "" : reqVO.getRemark().trim());
        permitMapper.insert(permit);
        return permit.getId();
    }
    /**
     * ä¿®æ”¹ä½œä¸šç¥¨ï¼ˆä»…草稿/待审批可改)
     */
    public void updatePermit(SafetyWorkPermitSaveReqVO reqVO) {
        SafetyWorkPermitDO old = validatePermitExists(reqVO.getId());
        ensureStatus(old, SafetyPermitStatusEnum.DRAFT, SafetyPermitStatusEnum.PENDING);
        validateTimeRange(reqVO);
        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());
    }
    /**
     * å®¡æ‰¹ä½œä¸šç¥¨
     *
     * <p>批准时校验高危作业(动火/高压带电/有限空间)必须指定监护人,
     * ä¿è¯ä½œä¸šç¥¨ç”µå­ç•™å­˜çš„完整性。</p>
     */
    @Transactional(rollbackFor = Exception.class)
    public void approvePermit(SafetyWorkPermitApproveReqVO reqVO) {
        SafetyWorkPermitDO permit = validatePermitExists(reqVO.getId());
        ensureStatus(permit, SafetyPermitStatusEnum.PENDING);
        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())
                && permit.getGuardianId() == null) {
            throw exception(SAFETY_PERMIT_GUARDIAN_REQUIRED);
        }
        SafetyWorkPermitDO update = new SafetyWorkPermitDO();
        update.setId(permit.getId());
        update.setApproverId(approver.getId());
        update.setApprover(approver.getName());
        update.setApproveTime(LocalDateTime.now());
        update.setApproveComment(reqVO.getApproveComment());
        update.setStatus(approved ? SafetyPermitStatusEnum.APPROVED.getStatus()
                : SafetyPermitStatusEnum.REJECTED.getStatus());
        permitMapper.updateById(update);
    }
    /**
     * å¼€å§‹ä½œä¸šï¼ˆé¡»å·²æ‰¹å‡†ï¼‰
     */
    public void startPermit(Long id) {
        SafetyWorkPermitDO permit = validatePermitExists(id);
        if (!SafetyPermitStatusEnum.APPROVED.getStatus().equals(permit.getStatus())) {
            throw exception(SAFETY_PERMIT_NOT_APPROVED);
        }
        SafetyWorkPermitDO update = new SafetyWorkPermitDO();
        update.setId(id);
        update.setActualStartTime(LocalDateTime.now());
        update.setStatus(SafetyPermitStatusEnum.IN_PROGRESS.getStatus());
        permitMapper.updateById(update);
    }
    /**
     * ç»“束作业并归档
     */
    public void closePermit(Long id) {
        SafetyWorkPermitDO permit = validatePermitExists(id);
        ensureStatus(permit, SafetyPermitStatusEnum.IN_PROGRESS);
        SafetyWorkPermitDO update = new SafetyWorkPermitDO();
        update.setId(id);
        update.setActualEndTime(LocalDateTime.now());
        update.setStatus(SafetyPermitStatusEnum.CLOSED.getStatus());
        permitMapper.updateById(update);
    }
    /**
     * ä½œåºŸä½œä¸šç¥¨ï¼ˆæœªå®Œç»“前均可作废)
     */
    public void cancelPermit(Long id) {
        SafetyWorkPermitDO permit = validatePermitExists(id);
        if (SafetyPermitStatusEnum.CLOSED.getStatus().equals(permit.getStatus())
                || SafetyPermitStatusEnum.CANCELLED.getStatus().equals(permit.getStatus())) {
            throw exception(SAFETY_PERMIT_STATUS_ERROR);
        }
        SafetyWorkPermitDO update = new SafetyWorkPermitDO();
        update.setId(id);
        update.setStatus(SafetyPermitStatusEnum.CANCELLED.getStatus());
        permitMapper.updateById(update);
    }
    @Transactional(rollbackFor = Exception.class)
    public void deletePermit(Long id) {
        validatePermitExists(id);
        monitorMapper.deleteByPermitId(id);
        permitMapper.deleteById(id);
    }
    public SafetyWorkPermitDO getPermit(Long id) {
        return validatePermitExists(id);
    }
    public PageResult<SafetyWorkPermitDO> getPermitPage(SafetyWorkPermitPageReqVO reqVO) {
        return permitMapper.selectPage(reqVO);
    }
    // ==================== ç›‘护记录 ====================
    public Long createMonitor(SafetyWorkPermitMonitorSaveReqVO reqVO) {
        SafetyWorkPermitDO permit = validatePermitExists(reqVO.getPermitId());
        if (!SafetyPermitStatusEnum.IN_PROGRESS.getStatus().equals(permit.getStatus())
                && !SafetyPermitStatusEnum.APPROVED.getStatus().equals(permit.getStatus())) {
            throw exception(SAFETY_PERMIT_STATUS_ERROR);
        }
        SafetyWorkPermitMonitorDO monitor = BeanUtils.toBean(reqVO, SafetyWorkPermitMonitorDO.class);
        monitor.setId(null);
        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();
    }
    public void deleteMonitor(Long id) {
        if (id == null || monitorMapper.selectById(id) == null) {
            throw exception(SAFETY_PERMIT_MONITOR_NOT_EXISTS);
        }
        monitorMapper.deleteById(id);
    }
    public List<SafetyWorkPermitMonitorDO> getMonitorList(Long permitId) {
        return monitorMapper.selectListByPermitId(permitId);
    }
    // ==================== å±•示字段 ====================
    /**
     * æ‰¹é‡å›žå¡«ä½œä¸šç¥¨çš„车间名与三个人员姓名(申请人/审批人/监护人)
     */
    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()));
        respVO.setStatusName(SafetyPermitStatusEnum.getNameByStatus(permit.getStatus()));
        respVO.setGuardianRequired(SafetyPermitTypeEnum.requiresGuardian(permit.getPermitType()));
    }
    private void validateTimeRange(SafetyWorkPermitSaveReqVO reqVO) {
        if (reqVO.getPlanStartTime() != null && reqVO.getPlanEndTime() != null
                && !reqVO.getPlanEndTime().isAfter(reqVO.getPlanStartTime())) {
            throw exception(SAFETY_PERMIT_TIME_INVALID);
        }
    }
    private void ensureStatus(SafetyWorkPermitDO permit, SafetyPermitStatusEnum... allowed) {
        for (SafetyPermitStatusEnum status : allowed) {
            if (status.getStatus().equals(permit.getStatus())) {
                return;
            }
        }
        throw exception(SAFETY_PERMIT_STATUS_ERROR);
    }
    private SafetyWorkPermitDO validatePermitExists(Long id) {
        SafetyWorkPermitDO permit = id == null ? null : permitMapper.selectById(id);
        if (permit == null) {
            throw exception(SAFETY_PERMIT_NOT_EXISTS);
        }
        return permit;
    }
}