xiaoyi
2026-10-29 2af6d5d996786d9b6b859b748af2ab9b71ad23e1
feat(safety): 新增安全生产模块(电力行业安全标准化)

覆盖 7 个业务域,前后端完整实现:

1. 安全组织架构 + 特种作业证台账
- safety_org 树形组织,改父级时检测环
- safety_certificate 电工证/高压操作证等,状态由有效期实时推导(过期/即将到期/有效)
2. 安全巡检 + 隐患闭环
- safety_inspection_point / safety_inspection 高压配电室、机组区日常巡检
- safety_hazard 上报 → 分派 → 整改 → 复查 → 闭环,状态机严格校验
- 整改人与复查人不可同一人,超期未闭环自动标记
3. 高危作业票审批
- safety_work_permit 动火/高压带电/有限空间等,草稿 → 待审批 → 批准 → 作业中 → 完结
- safety_work_permit_monitor 监护记录电子留存
- 批准时强制校验动火/高压带电/有限空间必须指定监护人
4. 应急物资出入库
- safety_material 绝缘用具/危化品/消防物资台账,低于安全库存与临近有效期预警
- safety_material_stock 流水带前后库存,出库行锁校验防超卖
5. 培训 / 演练 / 事故登记
- safety_training、safety_drill、safety_accident 电子化归档
6. 电力行业安全标准化台账导出
- 全套导出为 11 Sheet 单文件,也可按单类导出
7. 移动端现场拍照
- POST /safety/file/upload 与 /upload-batch,落 infra 文件存储

其他:
- yudao-module-mes/pom.xml 新增 yudao-module-infra 依赖(拍照上传需 FileApi)
- SQL:建表 12 张 + 菜单 79 条(顶级 /safety,sort=67)

验证:mvn compile 通过(yudao-module-mes、yudao-server 全链路)
已添加102个文件
已修改1个文件
6866 ■■■■■ 文件已修改
sql/mysql/safety_module_menu_20261029.sql 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/safety_module_tables_20261029.sql 343 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyCertificateController.java 99 ●●●●● 补丁 | 查看 | 原始文档 | 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 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyInspectionController.java 155 ●●●●● 补丁 | 查看 | 原始文档 | 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 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyOrgController.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyTrainingController.java 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyWorkPermitController.java 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/cert/SafetyCertificatePageReqVO.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/cert/SafetyCertificateRespVO.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/cert/SafetyCertificateSaveReqVO.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardAssignReqVO.java 38 ●●●●● 补丁 | 查看 | 原始文档 | 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 37 ●●●●● 补丁 | 查看 | 原始文档 | 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 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/hazard/SafetyHazardSaveReqVO.java 74 ●●●●● 补丁 | 查看 | 原始文档 | 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 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionPointSaveReqVO.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionRespVO.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/inspection/SafetyInspectionSaveReqVO.java 55 ●●●●● 补丁 | 查看 | 原始文档 | 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 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/material/SafetyMaterialSaveReqVO.java 77 ●●●●● 补丁 | 查看 | 原始文档 | 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 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/org/SafetyOrgSaveReqVO.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitApproveReqVO.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitMonitorRespVO.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitMonitorSaveReqVO.java 48 ●●●●● 补丁 | 查看 | 原始文档 | 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 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/permit/SafetyWorkPermitSaveReqVO.java 87 ●●●●● 补丁 | 查看 | 原始文档 | 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 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyAccidentSaveReqVO.java 93 ●●●●● 补丁 | 查看 | 原始文档 | 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 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyDrillSaveReqVO.java 75 ●●●●● 补丁 | 查看 | 原始文档 | 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 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/vo/train/SafetyTrainingSaveReqVO.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyAccidentDO.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyCertificateDO.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyDrillDO.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyHazardDO.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyInspectionDO.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyInspectionPointDO.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyMaterialDO.java 52 ●●●●● 补丁 | 查看 | 原始文档 | 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 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyTrainingDO.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyWorkPermitDO.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/safety/SafetyWorkPermitMonitorDO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | 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 61 ●●●●● 补丁 | 查看 | 原始文档 | 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 164 ●●●●● 补丁 | 查看 | 原始文档 | 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 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyInspectionService.java 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyLedgerExportService.java 316 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyMaterialService.java 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyOrgService.java 123 ●●●●● 补丁 | 查看 | 原始文档 | 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/SafetyTrainingService.java 217 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/safety/SafetyWorkPermitService.java 226 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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
@@ -23,6 +23,12 @@
            <artifactId>yudao-module-system</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- infra:安全生产模块的现场拍照上传走 FileApi -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-infra</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- MES åˆ¶é€ æ‰§è¡Œ API -->
        <dependency>
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/safety/SafetyCertificateController.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.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);
        SafetyCertificateService.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);
        var result = BeanUtils.toBean(page, SafetyCertificateRespVO.class);
        for (int i = 0; i < page.getList().size(); i++) {
            SafetyCertificateService.fillDisplayFields(page.getList().get(i), result.getList().get(i));
        }
        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<SafetyCertificateRespVO> list = certificateService.getExpiringCertificates(days).stream()
                .map(cert -> {
                    var resp = BeanUtils.toBean(cert, SafetyCertificateRespVO.class);
                    SafetyCertificateService.fillDisplayFields(cert, resp);
                    return resp;
                }).toList();
        return success(list);
    }
}
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,104 @@
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);
        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);
        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,155 @@
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);
        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);
        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() {
        List<SafetyInspectionPointRespVO> list = inspectionService.getEnabledPointList().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);
        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);
        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,132 @@
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);
        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);
        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() {
        List<SafetyMaterialRespVO> list = materialService.getBelowSafeStockList().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) {
        List<SafetyMaterialRespVO> list = materialService.getExpiringList(days).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,91 @@
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);
        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,181 @@
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);
        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);
        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);
        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);
        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);
        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);
        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,144 @@
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);
        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);
        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) {
        return success(BeanUtils.toBean(permitService.getMonitorList(permitId),
                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,47 @@
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 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,40 @@
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;
    private String employeeName;
    private String employeeNo;
    private String deptName;
    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,79 @@
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
 *
 * @author è¶…级管理员
 */
@Schema(description = "管理后台 - ç‰¹ç§ä½œä¸šè¯æ–°å¢ž/修改 Request VO")
@Data
public class SafetyCertificateSaveReqVO {
    private Long id;
    @Schema(description = "持证人姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
    @NotBlank(message = "持证人姓名不能为空")
    @Size(max = 50)
    private String employeeName;
    @Schema(description = "工号", example = "EMP001")
    @Size(max = 50)
    private String employeeNo;
    @Schema(description = "所属部门", example = "电气车间")
    @Size(max = 100)
    private String deptName;
    @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,38 @@
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 = "整改责任人", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
    @NotBlank(message = "整改责任人不能为空")
    @Size(max = 50)
    private String assignee;
    @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,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.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 = "复查人", requiredMode = Schema.RequiredMode.REQUIRED, example = "钱七")
    @NotBlank(message = "复查人不能为空")
    @Size(max = 50)
    private String rechecker;
    @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,56 @@
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;
    private String reporter;
    private LocalDateTime reportTime;
    private String assignee;
    private LocalDateTime assignTime;
    private LocalDate rectifyDeadline;
    @Schema(description = "是否已超期")
    private Boolean overdue;
    private String rectifyDesc;
    private String rectifyPhotoUrls;
    private LocalDateTime rectifyTime;
    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,74 @@
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 = "上报人", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
    @NotBlank(message = "上报人不能为空")
    @Size(max = 50)
    private String reporter;
    @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,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 SafetyInspectionPointRespVO {
    private Long id;
    private String code;
    private String name;
    private String pointType;
    @Schema(description = "点位类型名称")
    private String pointTypeName;
    private String workshopName;
    private String location;
    private String inspectItems;
    private String inspectCycle;
    @Schema(description = "巡检周期名称")
    private String inspectCycleName;
    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,61 @@
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 = "所属车间/区域")
    @Size(max = 100)
    private String workshopName;
    @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 = "责任人")
    @Size(max = 50)
    private String ownerName;
    @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,34 @@
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;
    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,55 @@
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 = "巡检人", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
    @NotBlank(message = "巡检人不能为空")
    @Size(max = 50)
    private String inspector;
    @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,41 @@
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;
    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,77 @@
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 = "保管人")
    @Size(max = 50)
    private String manager;
    @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,33 @@
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;
    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,56 @@
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 = "负责人姓名")
    @Size(max = 50)
    private String leaderName;
    @Schema(description = "负责人电话")
    @Size(max = 30)
    private String leaderPhone;
    @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,37 @@
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 = "审批人", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank(message = "审批人不能为空")
    @Size(max = 50)
    private String approver;
    @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,26 @@
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;
    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,48 @@
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 = "监护人", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank(message = "监护人不能为空")
    @Size(max = 50)
    private String guardian;
    @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,50 @@
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;
    private String workshopName;
    private String location;
    private String workContent;
    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;
    private String approver;
    private LocalDateTime approveTime;
    private String approveComment;
    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,87 @@
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 = "作业区域/车间")
    @Size(max = 100)
    private String workshopName;
    @Schema(description = "作业地点")
    @Size(max = 200)
    private String location;
    @Schema(description = "作业内容与范围")
    @Size(max = 2000)
    private String workContent;
    @Schema(description = "申请人", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotBlank(message = "申请人不能为空")
    @Size(max = 50)
    private String applicant;
    @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 = "监护人")
    @Size(max = 50)
    private String guardian;
    @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,45 @@
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;
    private String workshopName;
    private String casualtyDesc;
    private BigDecimal economicLoss;
    private String involvedPersons;
    private String description;
    private String causeAnalysis;
    private String measures;
    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,93 @@
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 = "所属车间/区域")
    @Size(max = 100)
    private String workshopName;
    @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 = "登记人")
    @Size(max = 50)
    private String reporter;
    @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,36 @@
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;
    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,75 @@
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 = "组织人")
    @Size(max = 50)
    private String organizer;
    @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,36 @@
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;
    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,72 @@
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 = "讲师/组织人")
    @Size(max = 50)
    private String trainer;
    @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,60 @@
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;
    /** æ‰€å±žè½¦é—´/区域 */
    private String workshopName;
    /** ä¼¤äº¡æƒ…况 */
    private String casualtyDesc;
    /** ç›´æŽ¥ç»æµŽæŸå¤±ï¼ˆå…ƒï¼‰ */
    private BigDecimal economicLoss;
    /** æ¶‰äº‹äººå‘˜å§“名,逗号分隔 */
    private String involvedPersons;
    /** äº‹æ•…经过 */
    private String description;
    /** åŽŸå› åˆ†æž */
    private String causeAnalysis;
    /** å¤„理措施与整改要求 */
    private String measures;
    /** ç™»è®°äºº */
    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,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
 *
 * <p>覆盖电工证、高压操作证等特种作业资格证件,按有效期自动判断状态并预警。</p>
 *
 * @author è¶…级管理员
 */
@TableName("safety_certificate")
@Data
@EqualsAndHashCode(callSuper = true)
public class SafetyCertificateDO extends BaseDO {
    @TableId
    private Long id;
    /** æŒè¯äººå§“名 */
    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,49 @@
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;
    /** ç»„织人 */
    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,76 @@
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;
    /** ä¸ŠæŠ¥äºº */
    private String reporter;
    /** ä¸ŠæŠ¥æ—¶é—´ */
    private LocalDateTime reportTime;
    /** æ•´æ”¹è´£ä»»äººï¼ˆåˆ†æ´¾ï¼‰ */
    private String assignee;
    /** åˆ†æ´¾æ—¶é—´ */
    private LocalDateTime assignTime;
    /** æ•´æ”¹æœŸé™ */
    private LocalDate rectifyDeadline;
    /** æ•´æ”¹æŽªæ–½ä¸Žç»“æžœ */
    private String rectifyDesc;
    /** æ•´æ”¹åŽç…§ç‰‡ URL,逗号分隔 */
    private String rectifyPhotoUrls;
    /** æ•´æ”¹å®Œæˆæ—¶é—´ */
    private LocalDateTime rectifyTime;
    /** å¤æŸ¥äºº */
    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,43 @@
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;
    /** å·¡æ£€äºº */
    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,43 @@
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;
    /** æ‰€å±žè½¦é—´/区域 */
    private String workshopName;
    /** å…·ä½“位置 */
    private String location;
    /** å·¡æ£€é¡¹ç›®æ¸…单,按行分隔 */
    private String inspectItems;
    /** å·¡æ£€å‘¨æœŸï¼Œè§ SafetyInspectCycleEnum */
    private String inspectCycle;
    /** è´£ä»»äºº */
    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,52 @@
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;
    /** ä¿ç®¡äºº */
    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,39 @@
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;
    /** è´Ÿè´£äººå§“名 */
    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,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_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;
    /** è®²å¸ˆ/组织人 */
    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,69 @@
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;
    /** ä½œä¸šåŒºåŸŸ/车间 */
    private String workshopName;
    /** ä½œä¸šåœ°ç‚¹ */
    private String location;
    /** ä½œä¸šå†…容与范围 */
    private String workContent;
    /** ç”³è¯·äºº */
    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;
    /** å®¡æ‰¹äºº */
    private String approver;
    /** å®¡æ‰¹æ—¶é—´ */
    private LocalDateTime approveTime;
    /** å®¡æ‰¹æ„è§ */
    private String approveComment;
    /** ç›‘护人 */
    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,36 @@
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;
    /** ç›‘护人 */
    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,61 @@
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, "有效期不能早于发证日期");
    // ========== å·¡æ£€ç‚¹ä½ / å·¡æ£€è®°å½• 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_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_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,164 @@
package cn.iocoder.yudao.module.mes.service.safety;
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.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.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.*;
/**
 * å®‰å…¨ç”Ÿäº§ - ç‰¹ç§ä½œä¸šè¯å°è´¦ Service
 *
 * <p>核心能力:证书到期自动预警。状态由 {@link #resolveStatus} æ ¹æ®æœ‰æ•ˆæœŸå®žæ—¶æŽ¨å¯¼ï¼Œ
 * ä¸ä¾èµ–手工维护,避免过期未及时更新状态。</p>
 *
 * @author è¶…级管理员
 */
@Service
@Validated
public class SafetyCertificateService {
    /** é»˜è®¤æå‰é¢„警天数 */
    public static final int DEFAULT_WARN_DAYS = 30;
    @Resource
    private SafetyCertificateMapper certificateMapper;
    public Long createCertificate(SafetyCertificateSaveReqVO reqVO) {
        reqVO.setId(null);
        validateCertificateForCreate(reqVO);
        SafetyCertificateDO cert = buildCertificate(reqVO);
        certificateMapper.insert(cert);
        return cert.getId();
    }
    public void updateCertificate(SafetyCertificateSaveReqVO reqVO) {
        SafetyCertificateDO old = validateCertificateExists(reqVO.getId());
        validateCertificateForUpdate(reqVO, old);
        SafetyCertificateDO cert = buildCertificate(reqVO);
        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));
    }
    /**
     * åˆ†é¡µç»“果的展示字段补全(类型名称、状态名称、剩余天数)
     */
    public static void fillDisplayFields(SafetyCertificateDO cert, SafetyCertificateRespVO respVO) {
        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()));
        }
    }
    /**
     * æ ¹æ®æœ‰æ•ˆæœŸæŽ¨å¯¼çŠ¶æ€ï¼šå·²æ³¨é”€ä¿æŒä¸å˜ï¼›è¿‡æœŸ â†’ 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();
    }
    private SafetyCertificateDO buildCertificate(SafetyCertificateSaveReqVO reqVO) {
        SafetyCertificateDO cert = BeanUtils.toBean(reqVO, SafetyCertificateDO.class);
        cert.setEmployeeName(StrUtil.trim(reqVO.getEmployeeName()));
        cert.setCertNo(StrUtil.trim(reqVO.getCertNo()));
        cert.setWarnDays(reqVO.getWarnDays() == null ? DEFAULT_WARN_DAYS : reqVO.getWarnDays());
        cert.setStatus(resolveStatus(reqVO.getExpireDate(), cert.getWarnDays(), reqVO.getStatus()));
        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);
        }
        // ä¿ç•™åŽŸçŠ¶æ€ä¸­çš„ã€Œå·²æ³¨é”€ã€ï¼Œä¸å…è®¸é€šè¿‡ç¼–è¾‘éšæ„æ”¹å›ž
        if (SafetyCertificateStatusEnum.REVOKED.getStatus().equals(old.getStatus())
                && !SafetyCertificateStatusEnum.REVOKED.getStatus().equals(reqVO.getStatus())) {
            reqVO.setStatus(SafetyCertificateStatusEnum.REVOKED.getStatus());
        }
    }
    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,191 @@
package cn.iocoder.yudao.module.mes.service.safety;
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 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;
    /**
     * é𐿂£ä¸ŠæŠ¥ï¼ˆå«ç§»åŠ¨ç«¯æ‹ç…§ä¸Šä¼ çš„ 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.setReporter(StrUtil.trim(reqVO.getReporter()));
        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 (StrUtil.isBlank(reqVO.getAssignee())) {
            throw exception(SAFETY_HAZARD_ASSIGNEE_REQUIRED);
        }
        SafetyHazardDO update = new SafetyHazardDO();
        update.setId(hazard.getId());
        update.setAssignee(StrUtil.trim(reqVO.getAssignee()));
        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);
        }
        String rechecker = StrUtil.trim(reqVO.getRechecker());
        if (StrUtil.isNotBlank(hazard.getAssignee()) && hazard.getAssignee().equals(rechecker)) {
            throw exception(SAFETY_HAZARD_SAME_OPERATOR);
        }
        boolean passed = "PASSED".equals(reqVO.getRecheckResult());
        SafetyHazardDO update = new SafetyHazardDO();
        update.setId(hazard.getId());
        update.setRechecker(rechecker);
        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 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,162 @@
package cn.iocoder.yudao.module.mes.service.safety;
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;
    // ==================== å·¡æ£€ç‚¹ä½ ====================
    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()));
        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()));
        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);
    }
    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.setInspector(StrUtil.trim(reqVO.getInspector()));
        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 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,316 @@
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);
        List<List<Object>> rows = certificateService.getCertificatePage(pageReqVO)
                .getList().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)});
        }
        List<List<Object>> rows = inspectionService.getInspectionPage(reqVO).getList().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)});
        }
        List<List<Object>> rows = hazardService.getHazardPage(reqVO).getList().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)});
        }
        List<List<Object>> rows = permitService.getPermitPage(reqVO).getList().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()) {
            for (SafetyWorkPermitMonitorDO monitor : permitService.getMonitorList(permit.getId())) {
                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);
        List<List<Object>> rows = materialService.getMaterialPage(reqVO).getList().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("培训日期", "培训主题", "培训类型", "学时", "讲师/组织人",
                "培训地点", "参加人数", "培训内容", "考核情况");
        List<List<Object>> rows = trainingService.listTrainingBetween(start, end).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("演练日期", "演练名称", "演练类型", "地点", "组织人",
                "参加人数", "时长(分钟)", "演练过程", "总结与改进");
        List<List<Object>> rows = trainingService.listDrillBetween(start, end).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("事故编号", "事故名称", "事故类型", "等级", "发生时间", "发生地点",
                "伤亡情况", "经济损失(元)", "事故经过", "原因分析", "处理措施", "状态");
        List<List<Object>> rows = trainingService.listAccidentBetween(start, end).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,164 @@
package cn.iocoder.yudao.module.mes.service.safety;
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 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(), "个"));
        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()));
        // åº“存数量只能通过出入库单变动,禁止直接编辑
        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);
    }
    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,123 @@
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;
    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());
        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());
        safetyOrgMapper.updateById(org);
    }
    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 List<SafetyOrgRespVO> getOrgTree() {
        List<SafetyOrgDO> all = safetyOrgMapper.selectListOrdered();
        List<SafetyOrgRespVO> nodes = BeanUtils.toBean(all, SafetyOrgRespVO.class);
        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/SafetyTrainingService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,217 @@
package cn.iocoder.yudao.module.mes.service.safety;
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 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.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);
    }
    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.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()));
        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);
        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,226 @@
package cn.iocoder.yudao.module.mes.service.safety;
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 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()));
        permit.setApplicant(StrUtil.trim(reqVO.getApplicant()));
        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);
        permitMapper.updateById(update);
    }
    /**
     * å®¡æ‰¹ä½œä¸šç¥¨
     *
     * <p>批准时校验高危作业(动火/高压带电/有限空间)必须指定监护人,
     * ä¿è¯ä½œä¸šç¥¨ç”µå­ç•™å­˜çš„完整性。</p>
     */
    @Transactional(rollbackFor = Exception.class)
    public void approvePermit(SafetyWorkPermitApproveReqVO reqVO) {
        SafetyWorkPermitDO permit = validatePermitExists(reqVO.getId());
        ensureStatus(permit, SafetyPermitStatusEnum.PENDING);
        String approver = StrUtil.trim(reqVO.getApprover());
        // ç”³è¯·äººä¸èƒ½å®¡æ‰¹è‡ªå·±çš„作业票
        if (approver.equals(permit.getApplicant())) {
            throw exception(SAFETY_PERMIT_STATUS_ERROR);
        }
        boolean approved = "APPROVED".equals(reqVO.getDecision());
        if (approved && SafetyPermitTypeEnum.requiresGuardian(permit.getPermitType())
                && StrUtil.isBlank(permit.getGuardian())) {
            throw exception(SAFETY_PERMIT_GUARDIAN_REQUIRED);
        }
        SafetyWorkPermitDO update = new SafetyWorkPermitDO();
        update.setId(permit.getId());
        update.setApprover(approver);
        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);
        monitor.setGuardian(StrUtil.trim(reqVO.getGuardian()));
        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 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;
    }
}