Prechádzať zdrojové kódy

feat(email): 添加派工转派邮件模板功能

- 新增 getFreemarkerHtmlContentDispatch 方法用于生成富文本邮件内容
- 添加 repairDispatchEmail.ftl 模板文件支持HTML格式邮件
- 在派工和转派流程中集成富文本邮件通知功能
- 实现系统域名配置检测机制,支持降级到简单文本通知
- 重构设备位置分布算法,从条带分布改为整体网格分布
- 移除不必要的 LinkedHashMap 和 Collectors 导入依赖
dazhaxie 2 dní pred
rodič
commit
1d67f8c910

+ 55 - 0
platform-dao/src/main/java/com/platform/dao/util/MessageTemplateUtil.java

@@ -292,6 +292,61 @@ public class MessageTemplateUtil {
         }
     }
 
+    /**
+     * 获取派工/转派通知邮件HTML内容
+     *
+     * @param url             维修单详情链接
+     * @param model           报修单DTO
+     * @param vo              设备信息VO
+     * @param title           邮件标题(如"派工通知"、"转派通知")
+     * @param repairUserName  被指派维修人姓名
+     * @param dispatchRemark  派工/转派备注
+     * @param level           紧急等级 1-一级 2-二级 3-三级
+     * @return HTML内容
+     */
+    public static String getFreemarkerHtmlContentDispatch(String url, RepairApplicationFormDTO model, SbInfoVO vo,
+                                                          String title, String repairUserName, String dispatchRemark, Integer level) {
+        Configuration configuration = new Configuration(Configuration.VERSION_2_3_0);
+        ClassLoader loader = MessageTemplateUtil.class.getClassLoader();
+        configuration.setClassLoaderForTemplateLoading(loader, "templates");
+        try {
+            Template template = configuration.getTemplate("repairDispatchEmail.ftl");
+            StringWriter mail = new StringWriter();
+            Map<String, Object> map = new HashMap<>();
+            map.put("title", title != null ? title : "派工通知");
+            map.put("no", model.getNo());
+            map.put("sbName", vo.getName());
+            map.put("sbNo", vo.getNo());
+            map.put("sbCph", model.getSbCph() == null ? "无" : model.getSbCph());
+            map.put("actualUser", model.getActualUser() == null ? "无" : model.getActualUser());
+            if (StringUtils.isBlank(model.getContent())) {
+                map.put("repairDesc", "无法断定,请维修人员现场检查");
+            } else {
+                map.put("repairDesc", model.getContent());
+            }
+            // 紧急等级转换
+            String levelStr;
+            if (level != null) {
+                switch (level) {
+                    case 1: levelStr = "紧急(一级)"; break;
+                    case 2: levelStr = "一般(二级)"; break;
+                    case 3: levelStr = "低(三级)"; break;
+                    default: levelStr = "未知"; break;
+                }
+            } else {
+                levelStr = "未设置";
+            }
+            map.put("levelStr", levelStr);
+            map.put("repairUserName", repairUserName != null ? repairUserName : "未指定");
+            map.put("dispatchRemark", dispatchRemark != null ? dispatchRemark : "");
+            map.put("url", url);
+            template.process(map, mail);
+            return mail.toString();
+        } catch (IOException | TemplateException e) {
+            throw new BusinessException("派工/转派邮件模板解析出错。" + e.getMessage());
+        }
+    }
+
     public static String getRepairOk(String id) {
         return String.format("您维修【%s】已验收,该任务已完结", id);
     }

+ 46 - 0
platform-rest/src/main/resources/templates/repairDispatchEmail.ftl

@@ -0,0 +1,46 @@
+<div>${title}</div>
+<div>您有新的维修单等待处理:
+    <table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;">
+        <tr>
+            <td>维修单号</td>
+            <td>${no}</td>
+        </tr>
+        <tr>
+            <td>设备名称</td>
+            <td>${sbName}</td>
+        </tr>
+        <tr>
+            <td>设备编号</td>
+            <td>${sbNo}</td>
+        </tr>
+        <tr>
+            <td>设备使用位置</td>
+            <td>${sbCph}</td>
+        </tr>
+        <tr>
+            <td>报修人</td>
+            <td>${actualUser}</td>
+        </tr>
+        <tr>
+            <td>故障描述</td>
+            <td>${repairDesc}</td>
+        </tr>
+        <tr>
+            <td>紧急等级</td>
+            <td>${levelStr}</td>
+        </tr>
+        <tr>
+            <td>指派维修人</td>
+            <td>${repairUserName}</td>
+        </tr>
+        <#if dispatchRemark?? && dispatchRemark != "">
+        <tr>
+            <td>派工备注</td>
+            <td>${dispatchRemark}</td>
+        </tr>
+        </#if>
+    </table>
+</div>
+<div>
+    <a target="_blank" href="${url}">点击链接跳转处理维修单</a>
+</div>

+ 52 - 11
platform-service/src/main/java/com/platform/service/repair/impl/RepairApplicationFormServiceImpl.java

@@ -778,11 +778,33 @@ public class RepairApplicationFormServiceImpl
         super.modModelByPrimaryKey(applicationForm);
 
         SysUser repairUser = userMapper.selectByPrimaryKey(model.getRepairUserId());
-        SpringContextHolder.publishEvent(new WorkplaceBacklogEvent(WorkplaceBacklogTypeEnum.REPAIR.getValue(),
-                WorkplaceBacklogDetailTypeEnum.REPAIR_DISPATCH.getValue(),
-                applicationForm.getId(), MessageTemplateUtil.getRepairDispatch(applicationForm.getNo()),
-                applicationForm.getId(), ListUtils.newArrayList(model.getRepairUserId()),
-                ListUtils.newArrayList(repairUser.getEmail())));
+        // 查询设备信息,构建富文本邮件内容
+        SbInfoVO sbInfoVO = sbInfoService.getById(applicationForm.getSbId());
+        String domain = ConfigCache.getLabelByValueAllowNull(SysConfigEnum.SYSTEM_DOMAIN.name());
+        if (StringUtils.isNotBlank(domain)) {
+            if (!domain.endsWith("/")) {
+                domain = domain + "/";
+            }
+            String repairUrl = domain + "repair/form?no=" + applicationForm.getNo();
+            String repairUserName = repairUser != null ? repairUser.getRealName() : null;
+            String emailContent = MessageTemplateUtil.getFreemarkerHtmlContentDispatch(
+                    repairUrl,
+                    BeanConverterUtil.copyObjectProperties(applicationForm, RepairApplicationFormDTO.class),
+                    sbInfoVO, "派工通知", repairUserName, applicationForm.getRepairDispatchRemark(),
+                    applicationForm.getLevel());
+            SpringContextHolder.publishEvent(new WorkplaceBacklogEvent(WorkplaceBacklogTypeEnum.REPAIR.getValue(),
+                    WorkplaceBacklogDetailTypeEnum.REPAIR_DISPATCH.getValue(),
+                    applicationForm.getId(), emailContent,
+                    applicationForm.getId(), ListUtils.newArrayList(model.getRepairUserId()),
+                    ListUtils.newArrayList(repairUser.getEmail())));
+        } else {
+            // 系统域名未配置时降级为简单文本通知
+            SpringContextHolder.publishEvent(new WorkplaceBacklogEvent(WorkplaceBacklogTypeEnum.REPAIR.getValue(),
+                    WorkplaceBacklogDetailTypeEnum.REPAIR_DISPATCH.getValue(),
+                    applicationForm.getId(), MessageTemplateUtil.getRepairDispatch(applicationForm.getNo()),
+                    applicationForm.getId(), ListUtils.newArrayList(model.getRepairUserId()),
+                    ListUtils.newArrayList(repairUser.getEmail())));
+        }
     }
 
     /**
@@ -1195,15 +1217,34 @@ public class RepairApplicationFormServiceImpl
         applicationForm.setUpdateTime(LocalDateTime.now());
         super.modModelByPrimaryKey(applicationForm);
 
-        // 通知新维修人
+        // 通知新维修人(含富文本邮件)
         if (StringUtils.isNotBlank(applicationForm.getRepairUserId())) {
             SysUser repairUser = userMapper.selectByPrimaryKey(applicationForm.getRepairUserId());
             if (repairUser != null) {
-                SpringContextHolder.publishEvent(new WorkplaceBacklogEvent(WorkplaceBacklogTypeEnum.REPAIR.getValue(),
-                        WorkplaceBacklogDetailTypeEnum.REPAIR_DISPATCH.getValue(),
-                        applicationForm.getId(), MessageTemplateUtil.getRepairDispatch(applicationForm.getNo()),
-                        applicationForm.getId(), ListUtils.newArrayList(applicationForm.getRepairUserId()),
-                        ListUtils.newArrayList(repairUser.getEmail())));
+                SbInfoVO sbInfoVO = sbInfoService.getById(applicationForm.getSbId());
+                String domain = ConfigCache.getLabelByValueAllowNull(SysConfigEnum.SYSTEM_DOMAIN.name());
+                if (StringUtils.isNotBlank(domain)) {
+                    if (!domain.endsWith("/")) {
+                        domain = domain + "/";
+                    }
+                    String repairUrl = domain + "repair/form?no=" + applicationForm.getNo();
+                    String emailContent = MessageTemplateUtil.getFreemarkerHtmlContentDispatch(
+                            repairUrl,
+                            BeanConverterUtil.copyObjectProperties(applicationForm, RepairApplicationFormDTO.class),
+                            sbInfoVO, "转派通知", repairUser.getRealName(),
+                            applicationForm.getRepairDispatchRemark(), applicationForm.getLevel());
+                    SpringContextHolder.publishEvent(new WorkplaceBacklogEvent(WorkplaceBacklogTypeEnum.REPAIR.getValue(),
+                            WorkplaceBacklogDetailTypeEnum.REPAIR_DISPATCH.getValue(),
+                            applicationForm.getId(), emailContent,
+                            applicationForm.getId(), ListUtils.newArrayList(applicationForm.getRepairUserId()),
+                            ListUtils.newArrayList(repairUser.getEmail())));
+                } else {
+                    SpringContextHolder.publishEvent(new WorkplaceBacklogEvent(WorkplaceBacklogTypeEnum.REPAIR.getValue(),
+                            WorkplaceBacklogDetailTypeEnum.REPAIR_DISPATCH.getValue(),
+                            applicationForm.getId(), MessageTemplateUtil.getRepairDispatch(applicationForm.getNo()),
+                            applicationForm.getId(), ListUtils.newArrayList(applicationForm.getRepairUserId()),
+                            ListUtils.newArrayList(repairUser.getEmail())));
+                }
             }
         }
     }

+ 22 - 50
platform-service/src/main/java/com/platform/service/sb/impl/SbPositionServiceImpl.java

@@ -35,10 +35,8 @@ import tk.mybatis.mapper.weekend.WeekendCriteria;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.time.LocalDateTime;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 /**
  * @Description 设备位置 service 实现类
@@ -269,58 +267,32 @@ public class SbPositionServiceImpl extends BaseServiceImpl<SbPositionMapper, SbP
             throw new BusinessException("所选位置下没有设备");
         }
 
-        // 3. 按 positionId 分组,保持插入顺序
-        Map<String, List<SbInfo>> positionDeviceMap = new LinkedHashMap<>();
-        for (String positionId : positionIds) {
-            List<SbInfo> devices = allDevices.stream()
-                    .filter(d -> positionId.equals(d.getPositionId()))
-                    .collect(Collectors.toList());
-            if (!devices.isEmpty()) {
-                positionDeviceMap.put(positionId, devices);
-            }
-        }
+        // 3. 整体网格分布:把所有设备当作一个整体,在整个矩形内均匀排列
+        int n = allDevices.size();
+        int cols = (int) Math.ceil(Math.sqrt(n));
+        int rows = (int) Math.ceil((double) n / cols);
+        double lngStep = cols > 1 ? (lngMax - lngMin) / (cols - 1) : 0;
+        double latStep = rows > 1 ? (latMax - latMin) / (rows - 1) : 0;
 
-        int positionCount = positionDeviceMap.size();
-        double bandHeight = (latMax - latMin) / positionCount;
-        log.info("初始化打点:矩形边界 lng[{}, {}] lat[{}, {}],位置数{},设备总数{}",
-                lngMin, lngMax, latMin, latMax, positionCount, allDevices.size());
+        log.info("初始化打点:矩形边界 lng[{}, {}] lat[{}, {}],设备总数{},网格 {}x{}",
+                lngMin, lngMax, latMin, latMax, n, cols, rows);
 
-        // 4. 每个位置分配一个条带,条带内网格分布
-        int bandIndex = 0;
+        // 4. 均匀分布所有设备
         int totalUpdated = 0;
-        for (Map.Entry<String, List<SbInfo>> entry : positionDeviceMap.entrySet()) {
-            List<SbInfo> devices = entry.getValue();
-            int n = devices.size();
-
-            double bandTop = latMax - bandIndex * bandHeight;
-            double bandBottom = bandTop - bandHeight;
+        for (int i = 0; i < n; i++) {
+            int row = i / cols;
+            int col = i % cols;
+            double lng = lngMin + col * lngStep;
+            double lat = latMax - row * latStep;
 
-            // 计算条带内网格
-            int cols = (int) Math.ceil(Math.sqrt(n));
-            int rows = (int) Math.ceil((double) n / cols);
-            double lngStep = cols > 1 ? (lngMax - lngMin) / (cols - 1) : 0;
-            double latStep = rows > 1 ? (bandTop - bandBottom) / (rows - 1) : 0;
-
-            for (int i = 0; i < n; i++) {
-                int row = i / cols;
-                int col = i % cols;
-                double lng = lngMin + col * lngStep;
-                double lat = bandTop - row * latStep;
-
-                SbInfo update = new SbInfo();
-                update.setId(devices.get(i).getId());
-                update.setZjm(BigDecimal.valueOf(lng).setScale(6, RoundingMode.HALF_UP).toPlainString());
-                update.setJbdh(BigDecimal.valueOf(lat).setScale(6, RoundingMode.HALF_UP).toPlainString());
-                update.setMapName(mapName);
-                update.setIsShow(isShow);
-                sbInfoMapper.updateByPrimaryKeySelective(update);
-                totalUpdated++;
-            }
-            log.info("位置[{}] 设备数{},网格 {}x{},条带 lat[{}, {}]",
-                    entry.getKey(), n, cols, rows,
-                    BigDecimal.valueOf(bandBottom).setScale(6, RoundingMode.HALF_UP),
-                    BigDecimal.valueOf(bandTop).setScale(6, RoundingMode.HALF_UP));
-            bandIndex++;
+            SbInfo update = new SbInfo();
+            update.setId(allDevices.get(i).getId());
+            update.setZjm(BigDecimal.valueOf(lng).setScale(6, RoundingMode.HALF_UP).toPlainString());
+            update.setJbdh(BigDecimal.valueOf(lat).setScale(6, RoundingMode.HALF_UP).toPlainString());
+            update.setMapName(mapName);
+            update.setIsShow(isShow);
+            sbInfoMapper.updateByPrimaryKeySelective(update);
+            totalUpdated++;
         }
         log.info("初始化打点完成,共更新{}台设备坐标", totalUpdated);
     }