tuqin
7 天以前 bc136ac1a5fa5b29dc4d06a5de9bc3332b2aea47
feat(mes): 新增安全生产模块 - 应急处置预案(一期)

背景:蓝羽此前无任何安全生产功能。经全仓库排查(22 个后端分支 + 21 个前端分支),
完整安全生产模块不存在,仅新高电力分支有应急处置预案,作为安全生产第一个能力点先落地。

后端新增(10 个文件):
- controller/admin/pro/emergencyplan:分页/详情/新增/修改/删除/导入导出
- dal/dataobject|mysql/pro/emergencyplan:DO 与 Mapper
- service/pro/emergencyplan:预案业务逻辑

补齐的缺失依赖:
- ErrorCodeConstants 补 MES_PRO_EMERGENCY_PLAN_NOT_EXISTS / CODE_DUPLICATE /
IMPORT_LIST_IS_EMPTY(蓝羽基线不存在,不补编译不过)
- DictTypeConstants 补 MES_PRO_EMERGENCY_PLAN_TYPE

SQL(mes_safety_emergency_plan_init.sql,可重复执行):
- 建表 mes_pro_emergency_plan
- 新建一级菜单「安全生产」(/safety),便于后续能力点归入
- 挂入应急处置预案菜单 + 5 个按钮权限,授权超管
- 补预案类型字典(停电/设备故障/火灾/自然灾害/其他)

后续能力点(风险分级管控、隐患排查治理、特种作业票证、安全教育培训、
可视化看板、统计分析)分期补充。
已添加11个文件
已修改2个文件
707 ■■■■■ 文件已修改
sql/mysql/mes_safety_emergency_plan_init.sql 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/MesProEmergencyPlanController.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanImportExcelVO.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanImportRespVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanPageReqVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanRespVO.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanSaveReqVO.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/emergencyplan/MesProEmergencyPlanDO.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/emergencyplan/MesProEmergencyPlanMapper.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/emergencyplan/MesProEmergencyPlanService.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/emergencyplan/MesProEmergencyPlanServiceImpl.java 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/mes_safety_emergency_plan_init.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,104 @@
-- =====================================================================
-- å®‰å…¨ç”Ÿäº§æ¨¡å— â€”— ä¸€æœŸï¼šåº”急处置预案
--
-- èƒŒæ™¯ï¼šè“ç¾½æ­¤å‰æ²¡æœ‰ä»»ä½•安全生产相关功能。经全仓库(22 ä¸ªåŽç«¯åˆ†æ”¯ + 21 ä¸ªå‰ç«¯åˆ†æ”¯ï¼‰
--       æŽ’查确认,仅「新高电力」分支存在应急处置预案(MesProEmergencyPlan),
--       ä½œä¸ºå®‰å…¨ç”Ÿäº§æ¨¡å—的第一个能力点先落地,其余能力点(风险分级管控、
--       é𐿂£æŽ’查治理、特种作业票证、安全教育培训、可视化看板、统计分析)后续分期补充。
--
-- å†…容:
--   1. å»ºè¡¨ mes_pro_emergency_plan
--   2. æ–°å»ºä¸€çº§èœå•「安全生产」(/safety),便于后续能力点归入
--   3. æŒ‚入「应急处置预案」菜单及其按钮权限
--   4. æŽˆæƒç»™è¶…级管理员
--
-- ç‰¹æ€§ï¼šå¯é‡å¤æ‰§è¡Œ
-- =====================================================================
-- ---------------------------------------------------------------------
-- 1. å»ºè¡¨
-- ---------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `mes_pro_emergency_plan` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `code` varchar(64) NOT NULL COMMENT '预案编码',
  `name` varchar(200) DEFAULT NULL COMMENT '预案名称',
  `plan_type` tinyint DEFAULT NULL COMMENT '预案类型(1=停电,2=设备故障,3=火灾,4=自然灾害,5=其他)',
  `content` text COMMENT '处置内容',
  `org_unit` varchar(200) DEFAULT NULL COMMENT '责任单位/部门',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态(0=草稿,10=已发布)',
  `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`) USING BTREE,
  UNIQUE KEY `uk_code` (`code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='MES åº”急处置预案';
-- ---------------------------------------------------------------------
-- 2. èœå•:安全生产(一级目录)
-- ---------------------------------------------------------------------
DELETE FROM system_role_menu WHERE menu_id BETWEEN 1065910 AND 1065919;
DELETE FROM system_menu WHERE id BETWEEN 1065910 AND 1065919;
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 (1065910, '安全生产', '', 1, 65, 0, '/safety', 'ep:warning',
        NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), 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 (1065911, '应急处置预案', '', 2, 1, 1065910, 'emergency-plan', 'ep:document',
        'mes/pro/emergencyplan/index', 'EmergencyPlan', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
       (1065912, '应急预案查询', 'mes:pro-emergency-plan:query', 3, 1, 1065911, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
       (1065913, '应急预案创建', 'mes:pro-emergency-plan:create', 3, 2, 1065911, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
       (1065914, '应急预案更新', 'mes:pro-emergency-plan:update', 3, 3, 1065911, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
       (1065915, '应急预案删除', 'mes:pro-emergency-plan:delete', 3, 4, 1065911, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
       (1065916, '应急预案导入', 'mes:pro-emergency-plan:import', 3, 5, 1065911, '', '', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- ---------------------------------------------------------------------
-- 4. æŽˆæƒç»™è¶…级管理员
-- ---------------------------------------------------------------------
INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted)
SELECT 1, m.id, '1', NOW(), '1', NOW(), b'0'
FROM system_menu m
WHERE m.id BETWEEN 1065910 AND 1065919;
-- ---------------------------------------------------------------------
-- 5. å­—典:应急处置预案类型
-- ---------------------------------------------------------------------
DELETE FROM system_dict_data WHERE dict_type = 'mes_pro_emergency_plan_type';
DELETE FROM system_dict_type WHERE type = 'mes_pro_emergency_plan_type';
INSERT INTO system_dict_type (name, type, status, remark, creator, create_time, updater, update_time, deleted)
VALUES ('MES åº”急处置预案类型', 'mes_pro_emergency_plan_type', 0,
        'MES åº”急处置预案类型(1=停电,2=设备故障,3=火灾,4=自然灾害,5=其他)',
        '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_dict_data (sort, label, value, dict_type, status, color_type, creator, create_time, updater, update_time, deleted)
VALUES (1, '停电', '1', 'mes_pro_emergency_plan_type', 0, 'danger', '1', NOW(), '1', NOW(), b'0'),
       (2, '设备故障', '2', 'mes_pro_emergency_plan_type', 0, 'warning', '1', NOW(), '1', NOW(), b'0'),
       (3, '火灾', '3', 'mes_pro_emergency_plan_type', 0, 'error', '1', NOW(), '1', NOW(), b'0'),
       (4, '自然灾害', '4', 'mes_pro_emergency_plan_type', 0, 'processing', '1', NOW(), '1', NOW(), b'0'),
       (5, '其他', '5', 'mes_pro_emergency_plan_type', 0, 'default', '1', NOW(), '1', NOW(), b'0');
-- ---------------------------------------------------------------------
-- 6. éªŒè¯
-- ---------------------------------------------------------------------
SELECT id, parent_id, name, path, component, permission, type
FROM system_menu
WHERE id BETWEEN 1065910 AND 1065919
ORDER BY id;
SELECT dict_type, label, value
FROM system_dict_data
WHERE dict_type = 'mes_pro_emergency_plan_type'
ORDER BY sort;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/MesProEmergencyPlanController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan;
import cn.hutool.core.collection.CollUtil;
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.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanImportExcelVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanImportRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.emergencyplan.MesProEmergencyPlanDO;
import cn.iocoder.yudao.module.mes.service.pro.emergencyplan.MesProEmergencyPlanService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
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.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - MES åº”急处置预案")
@RestController
@RequestMapping("/mes/pro/emergency-plan")
@Validated
public class MesProEmergencyPlanController {
    @Resource
    private MesProEmergencyPlanService planService;
    @PostMapping("/create")
    @Operation(summary = "创建应急处置预案")
    @PreAuthorize("@ss.hasPermission('mes:pro-emergency-plan:create')")
    public CommonResult<Long> createPlan(@Valid @RequestBody MesProEmergencyPlanSaveReqVO createReqVO) {
        return success(planService.createPlan(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新应急处置预案")
    @PreAuthorize("@ss.hasPermission('mes:pro-emergency-plan:update')")
    public CommonResult<Boolean> updatePlan(@Valid @RequestBody MesProEmergencyPlanSaveReqVO updateReqVO) {
        planService.updatePlan(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除应急处置预案")
    @Parameter(name = "ids", description = "编号数组", required = true)
    @PreAuthorize("@ss.hasPermission('mes:pro-emergency-plan:delete')")
    public CommonResult<Boolean> deletePlan(@RequestParam("ids") List<Long> ids) {
        planService.deletePlan(ids);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得应急处置预案")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    public CommonResult<MesProEmergencyPlanRespVO> getPlan(@RequestParam("id") Long id) {
        MesProEmergencyPlanDO plan = planService.getPlan(id);
        return success(plan != null ? BeanUtils.toBean(plan, MesProEmergencyPlanRespVO.class) : null);
    }
    @GetMapping("/page")
    @Operation(summary = "获得应急处置预案分页")
    public CommonResult<PageResult<MesProEmergencyPlanRespVO>> getPlanPage(@Valid MesProEmergencyPlanPageReqVO pageReqVO) {
        PageResult<MesProEmergencyPlanDO> pageResult = planService.getPlanPage(pageReqVO);
        if (CollUtil.isEmpty(pageResult.getList())) {
            return success(PageResult.empty(pageResult.getTotal()));
        }
        return success(BeanUtils.toBean(pageResult, MesProEmergencyPlanRespVO.class));
    }
    @PutMapping("/publish")
    @Operation(summary = "发布应急处置预案")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('mes:pro-emergency-plan:update')")
    public CommonResult<Boolean> publishPlan(@RequestParam("id") Long id) {
        planService.publishPlan(id);
        return success(true);
    }
    @GetMapping("/get-import-template")
    @Operation(summary = "获得应急处置预案导入模板")
    public void importTemplate(HttpServletResponse response) throws IOException {
        List<MesProEmergencyPlanImportExcelVO> list = Collections.singletonList(
                MesProEmergencyPlanImportExcelVO.builder()
                        .name("变电站停电处置预案").planType(1).content("1. ç«‹å³éš”离故障...")
                        .orgUnit("运维部").remark("").build()
        );
        ExcelUtils.write(response, "应急处置预案导入模板.xls", "应急处置预案列表", MesProEmergencyPlanImportExcelVO.class, list);
    }
    @PostMapping("/import")
    @Operation(summary = "导入应急处置预案")
    @Parameters({
            @Parameter(name = "file", description = "Excel æ–‡ä»¶", required = true),
            @Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true")
    })
    @PreAuthorize("@ss.hasPermission('mes:pro-emergency-plan:import')")
    public CommonResult<MesProEmergencyPlanImportRespVO> importExcel(@RequestParam("file") MultipartFile file,
                                                                     @RequestParam(value = "updateSupport", required = false,
                                                                             defaultValue = "false") Boolean updateSupport) throws Exception {
        List<MesProEmergencyPlanImportExcelVO> list = ExcelUtils.read(file, MesProEmergencyPlanImportExcelVO.class);
        return success(planService.importPlanList(list, updateSupport));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanImportExcelVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo;
import cn.idev.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * MES åº”急处置预案 Excel å¯¼å…¥ VO
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MesProEmergencyPlanImportExcelVO {
    @ExcelProperty("预案名称")
    private String name;
    @ExcelProperty("预案类型(1=停电 2=设备故障 3=火灾 4=自然灾害 5=其他)")
    private Integer planType;
    @ExcelProperty("处置内容")
    private String content;
    @ExcelProperty("责任单位/部门")
    private String orgUnit;
    @ExcelProperty("备注")
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanImportRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - MES åº”急处置预案导入 Response VO")
@Data
@Builder
public class MesProEmergencyPlanImportRespVO {
    @Schema(description = "创建成功的预案名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
    private List<String> createNames;
    @Schema(description = "更新成功的预案名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
    private List<String> updateNames;
    @Schema(description = "导入失败的预案集合,key ä¸ºé¢„案名称,value ä¸ºå¤±è´¥åŽŸå› ", requiredMode = Schema.RequiredMode.REQUIRED)
    private Map<String, String> failureNames;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - MES åº”急处置预案分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MesProEmergencyPlanPageReqVO extends PageParam {
    @Schema(description = "预案编码", example = "YJ20260818001")
    private String code;
    @Schema(description = "预案名称", example = "变电站停电处置预案")
    private String name;
    @Schema(description = "预案类型(1=停电,2=设备故障,3=火灾,4=自然灾害,5=其他)", example = "1")
    private Integer planType;
    @Schema(description = "状态(0=草稿,10=已发布)", example = "0")
    private Integer status;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - MES åº”急处置预案 Response VO")
@Data
@ExcelIgnoreUnannotated
public class MesProEmergencyPlanRespVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @ExcelProperty("编号")
    private Long id;
    @Schema(description = "预案编码", example = "YJ20260818001")
    @ExcelProperty("预案编码")
    private String code;
    @Schema(description = "预案名称", example = "变电站停电处置预案")
    @ExcelProperty("预案名称")
    private String name;
    @Schema(description = "预案类型", example = "1")
    @ExcelProperty(value = "预案类型", converter = DictConvert.class)
    @DictFormat(cn.iocoder.yudao.module.mes.enums.DictTypeConstants.MES_PRO_EMERGENCY_PLAN_TYPE)
    private Integer planType;
    @Schema(description = "处置内容", example = "1. ç«‹å³éš”离故障...")
    private String content;
    @Schema(description = "责任单位/部门", example = "运维部")
    @ExcelProperty("责任单位")
    private String orgUnit;
    @Schema(description = "状态(0=草稿,10=已发布)", example = "0")
    private Integer status;
    @Schema(description = "备注", example = "备注")
    @ExcelProperty("备注")
    private String remark;
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @ExcelProperty("创建时间")
    private LocalDateTime createTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/emergencyplan/vo/MesProEmergencyPlanSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - MES åº”急处置预案新增/修改 Request VO")
@Data
public class MesProEmergencyPlanSaveReqVO {
    @Schema(description = "编号(修改时必填)", example = "1")
    private Long id;
    @Schema(description = "预案名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "变电站停电处置预案")
    @NotEmpty(message = "预案名称不能为空")
    private String name;
    @Schema(description = "预案类型(1=停电,2=设备故障,3=火灾,4=自然灾害,5=其他)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "预案类型不能为空")
    private Integer planType;
    @Schema(description = "处置内容", example = "1. ç«‹å³éš”离故障...")
    private String content;
    @Schema(description = "责任单位/部门", example = "运维部")
    private String orgUnit;
    @Schema(description = "备注", example = "备注")
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/emergencyplan/MesProEmergencyPlanDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,77 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.pro.emergencyplan;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
 * MES åº”急处置预案 DO
 *
 * @author xiaoyi
 */
@TableName("mes_pro_emergency_plan")
@KeySequence("mes_pro_emergency_plan_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MesProEmergencyPlanDO extends BaseDO {
    /**
     * ç¼–号
     */
    @TableId
    private Long id;
    /**
     * é¢„案编码
     */
    private String code;
    /**
     * é¢„案名称
     */
    private String name;
    /**
     * é¢„案类型
     *
     * 1: åœç”µ
     * 2: è®¾å¤‡æ•…éšœ
     * 3: ç«ç¾
     * 4: è‡ªç„¶ç¾å®³
     * 5: å…¶ä»–
     *
     * å­—å…¸ {@link DictTypeConstants#MES_PRO_EMERGENCY_PLAN_TYPE}
     */
    private Integer planType;
    /**
     * å¤„置内容
     */
    private String content;
    /**
     * è´£ä»»å•位/部门
     */
    private String orgUnit;
    /**
     * çŠ¶æ€
     *
     * 0: è‰ç¨¿
     * 10: å·²å‘布
     */
    private Integer status;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/emergencyplan/MesProEmergencyPlanMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.mes.dal.mysql.pro.emergencyplan;
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.pro.emergencyplan.vo.MesProEmergencyPlanPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.emergencyplan.MesProEmergencyPlanDO;
import org.apache.ibatis.annotations.Mapper;
/**
 * MES åº”急处置预案 Mapper
 *
 * @author xiaoyi
 */
@Mapper
public interface MesProEmergencyPlanMapper extends BaseMapperX<MesProEmergencyPlanDO> {
    default MesProEmergencyPlanDO selectByCode(String code) {
        return selectOne(MesProEmergencyPlanDO::getCode, code);
    }
    default MesProEmergencyPlanDO selectByName(String name) {
        return selectOne(MesProEmergencyPlanDO::getName, name);
    }
    default PageResult<MesProEmergencyPlanDO> selectPage(MesProEmergencyPlanPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<MesProEmergencyPlanDO>()
                .likeIfPresent(MesProEmergencyPlanDO::getCode, reqVO.getCode())
                .likeIfPresent(MesProEmergencyPlanDO::getName, reqVO.getName())
                .eqIfPresent(MesProEmergencyPlanDO::getPlanType, reqVO.getPlanType())
                .eqIfPresent(MesProEmergencyPlanDO::getStatus, reqVO.getStatus())
                .orderByDesc(MesProEmergencyPlanDO::getId));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java
@@ -116,4 +116,7 @@
    String MES_PD_ARCHIVE_STATUS = "mes_pd_archive_status";               // å½’档状态
    String MES_PD_PROCESS_PARAM_TYPE = "mes_pd_process_param_type";                   // å·¥è‰ºå‚数类型
    String MES_PD_PROCESS_PARAM_TEMPLATE_TYPE = "mes_pd_process_param_template_type"; // å·¥è‰ºå‚数模板类型
    // ========== å®‰å…¨ç”Ÿäº§ (Safety) ==========
    String MES_PRO_EMERGENCY_PLAN_TYPE = "mes_pro_emergency_plan_type"; // åº”急处置预案类型
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -841,4 +841,9 @@
    ErrorCode EM_ALERT_NOT_EXISTS = new ErrorCode(1_040_902_030, "能耗预警不存在");
    ErrorCode EM_ALERT_ALREADY_HANDLED = new ErrorCode(1_040_902_031, "能耗预警已处理,请勿重复操作");
    // ========== MES å®‰å…¨ç”Ÿäº§ - åº”急处置预案(1-040-903-000) ==========
    ErrorCode MES_PRO_EMERGENCY_PLAN_NOT_EXISTS = new ErrorCode(1_040_903_000, "应急处置预案不存在");
    ErrorCode MES_PRO_EMERGENCY_PLAN_CODE_DUPLICATE = new ErrorCode(1_040_903_001, "预案编码已存在");
    ErrorCode MES_PRO_EMERGENCY_PLAN_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_040_903_002, "导入数据不能为空");
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/emergencyplan/MesProEmergencyPlanService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.mes.service.pro.emergencyplan;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanImportExcelVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanImportRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.emergencyplan.MesProEmergencyPlanDO;
import java.util.Collection;
import java.util.List;
/**
 * MES åº”急处置预案 Service æŽ¥å£
 *
 * @author xiaoyi
 */
public interface MesProEmergencyPlanService {
    Long createPlan(MesProEmergencyPlanSaveReqVO createReqVO);
    void updatePlan(MesProEmergencyPlanSaveReqVO updateReqVO);
    void deletePlan(Collection<Long> ids);
    MesProEmergencyPlanDO getPlan(Long id);
    PageResult<MesProEmergencyPlanDO> getPlanPage(MesProEmergencyPlanPageReqVO pageReqVO);
    void publishPlan(Long id);
    MesProEmergencyPlanImportRespVO importPlanList(List<MesProEmergencyPlanImportExcelVO> importList, boolean updateSupport);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/emergencyplan/MesProEmergencyPlanServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,163 @@
package cn.iocoder.yudao.module.mes.service.pro.emergencyplan;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
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.pro.emergencyplan.vo.MesProEmergencyPlanImportExcelVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanImportRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.emergencyplan.vo.MesProEmergencyPlanSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.emergencyplan.MesProEmergencyPlanDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.emergencyplan.MesProEmergencyPlanMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MES_PRO_EMERGENCY_PLAN_IMPORT_LIST_IS_EMPTY;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_WORK_ORDER_NOT_EXISTS;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_WORK_ORDER_NOT_PREPARE;
/**
 * MES åº”急处置预案 Service å®žçŽ°ç±»
 *
 * @author xiaoyi
 */
@Service
@Validated
public class MesProEmergencyPlanServiceImpl implements MesProEmergencyPlanService {
    private static final Integer STATUS_DRAFT = 0;
    private static final Integer STATUS_PUBLISHED = 10;
    @Resource
    private MesProEmergencyPlanMapper planMapper;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createPlan(MesProEmergencyPlanSaveReqVO createReqVO) {
        String code = generateCode();
        if (planMapper.selectByCode(code) != null) {
            throw exception(PRO_WORK_ORDER_NOT_EXISTS);
        }
        MesProEmergencyPlanDO plan = BeanUtils.toBean(createReqVO, MesProEmergencyPlanDO.class, req -> req
                .setCode(code)
                .setStatus(STATUS_DRAFT));
        planMapper.insert(plan);
        return plan.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePlan(MesProEmergencyPlanSaveReqVO updateReqVO) {
        MesProEmergencyPlanDO oldPlan = validatePlanCanModify(updateReqVO.getId());
        MesProEmergencyPlanDO updateObj = BeanUtils.toBean(updateReqVO, MesProEmergencyPlanDO.class, req -> req
                .setCode(oldPlan.getCode())
                .setStatus(oldPlan.getStatus()));
        planMapper.updateById(updateObj);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deletePlan(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        for (Long id : ids) {
            validatePlanCanModify(id);
            planMapper.deleteById(id);
        }
    }
    @Override
    public MesProEmergencyPlanDO getPlan(Long id) {
        return planMapper.selectById(id);
    }
    @Override
    public PageResult<MesProEmergencyPlanDO> getPlanPage(MesProEmergencyPlanPageReqVO pageReqVO) {
        return planMapper.selectPage(pageReqVO);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void publishPlan(Long id) {
        MesProEmergencyPlanDO plan = validatePlanExists(id);
        if (!Objects.equals(plan.getStatus(), STATUS_DRAFT)) {
            throw exception(PRO_WORK_ORDER_NOT_PREPARE);
        }
        planMapper.updateById(new MesProEmergencyPlanDO().setId(id).setStatus(STATUS_PUBLISHED));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MesProEmergencyPlanImportRespVO importPlanList(List<MesProEmergencyPlanImportExcelVO> importList,
                                                          boolean updateSupport) {
        if (CollUtil.isEmpty(importList)) {
            throw exception(MES_PRO_EMERGENCY_PLAN_IMPORT_LIST_IS_EMPTY);
        }
        MesProEmergencyPlanImportRespVO respVO = MesProEmergencyPlanImportRespVO.builder()
                .createNames(new ArrayList<>()).updateNames(new ArrayList<>())
                .failureNames(new LinkedHashMap<>()).build();
        AtomicInteger index = new AtomicInteger(1);
        importList.forEach(importItem -> {
            int currentIndex = index.getAndIncrement();
            String key = StrUtil.blankToDefault(importItem.getName(), "第 " + currentIndex + " è¡Œ");
            if (StrUtil.isBlank(importItem.getName())) {
                respVO.getFailureNames().put(key, "预案名称不能为空");
                return;
            }
            MesProEmergencyPlanSaveReqVO reqVO = BeanUtils.toBean(importItem, MesProEmergencyPlanSaveReqVO.class);
            MesProEmergencyPlanDO existPlan = planMapper.selectByName(importItem.getName());
            try {
                if (existPlan == null) {
                    reqVO.setId(null);
                    createPlan(reqVO);
                    respVO.getCreateNames().add(importItem.getName());
                } else if (updateSupport) {
                    reqVO.setId(existPlan.getId());
                    updatePlan(reqVO);
                    respVO.getUpdateNames().add(importItem.getName());
                } else {
                    respVO.getFailureNames().put(key, "预案名称已存在");
                }
            } catch (ServiceException e) {
                respVO.getFailureNames().put(key, e.getMessage());
            }
        });
        return respVO;
    }
    private String generateCode() {
        return "YJ" + DateUtil.format(java.time.LocalDateTime.now(), "yyyyMMddHHmmss")
                + String.format("%03d", (int) (Math.random() * 1000));
    }
    private MesProEmergencyPlanDO validatePlanExists(Long id) {
        MesProEmergencyPlanDO plan = planMapper.selectById(id);
        if (plan == null) {
            throw exception(PRO_WORK_ORDER_NOT_EXISTS);
        }
        return plan;
    }
    private MesProEmergencyPlanDO validatePlanCanModify(Long id) {
        MesProEmergencyPlanDO plan = validatePlanExists(id);
        if (!Objects.equals(plan.getStatus(), STATUS_DRAFT)) {
            throw exception(PRO_WORK_ORDER_NOT_PREPARE);
        }
        return plan;
    }
}