Prechádzať zdrojové kódy

feat(check): 新增班组未完成保养任务邮件提醒功能

- 在CheckJobMapper中新增selectUnfinishedTeamJobs方法用于查询班组未完成任务
- 在CheckJobMapper.xml中添加对应的SQL查询语句
- 在CheckJobService中定义sendTeamUnfinishedNotice接口方法
- 在CheckJobServiceImpl中实现邮件提醒逻辑,包括按班组分组和发送邮件
- 在CheckJobTask中添加定时任务每天9点执行提醒功能
- 创建checkTeamUnfinishedEmail.ftl邮件模板
- 修改ExportRepairWorkHourSbVO和RepairWorkHourReportVO增加设备类型字段
- 更新RepairApplicationFormMapper.xml查询结果包含设备类型
- 调整设备状态变更逻辑,停用设备不再删除历史任务
- 在SbStatusLogController中优化设备状态变更处理流程
- 添加BAN_ZHANG角色枚举值用于识别班长身份
dazhaxie 1 týždeň pred
rodič
commit
8980e6a42b

+ 4 - 0
platform-dao/src/main/java/com/platform/dao/enums/SysRoleCodeEnum.java

@@ -135,6 +135,10 @@ public enum SysRoleCodeEnum {
      * 报修单最后审核人员
      */
     REPAIR_EXAMINE,
+    /**
+     * 班长(班组负责人,接收本班组未完成保养任务的邮件提醒)
+     */
+    BAN_ZHANG,
     ;
 
 }

+ 7 - 0
platform-dao/src/main/java/com/platform/dao/mapper/check/CheckJobMapper.java

@@ -76,4 +76,11 @@ public interface CheckJobMapper extends MyMapper<CheckJob> {
      * @return
      */
     List<CheckJobVO> selectList2ByIds(CheckJobDTO dto);
+
+    /**
+     * 查询执行方式为“班组”且未完成(未执行/执行中/已逾期)的保养任务,
+     * 用于每日定时向对应班长发送邮件提醒。返回结果包含设备名称、设备编号、班组ID及班组名称。
+     * @return 未完成的班组保养任务列表
+     */
+    List<CheckJobVO> selectUnfinishedTeamJobs();
 }

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

@@ -137,6 +137,34 @@ public class MessageTemplateUtil {
     }
 
     /**
+     * 班组未完成保养任务提醒邮件内容(发送给班长)
+     *
+     * @param deptName 班组名称
+     * @param date     统计日期
+     * @param devices  未完成设备明细,每项包含 sbName、sbNo、statusName
+     * @return 渲染后的 HTML 邮件内容
+     */
+    public static String getTeamUnfinishedEmailContent(String deptName, String date, List<Map<String, Object>> devices) {
+        // 首先配置 FreeMarker 模版位置
+        Configuration configuration = new Configuration(Configuration.VERSION_2_3_0);
+        ClassLoader loader = MessageTemplateUtil.class.getClassLoader();
+        configuration.setClassLoaderForTemplateLoading(loader, "templates");
+        try {
+            Template template = configuration.getTemplate("checkTeamUnfinishedEmail.ftl");
+            StringWriter mail = new StringWriter();
+            Map<String, Object> map = new HashMap<>();
+            map.put("deptName", StringUtils.isBlank(deptName) ? "班组" : deptName);
+            map.put("date", date);
+            map.put("total", devices == null ? 0 : devices.size());
+            map.put("devices", devices);
+            template.process(map, mail);
+            return mail.toString();
+        } catch (IOException | TemplateException e) {
+            throw new BusinessException("班组未完成保养邮件模板解析出错。" + e.getMessage());
+        }
+    }
+
+    /**
      * 获取维修通知
      *
      * @param url

+ 10 - 4
platform-dao/src/main/java/com/platform/dao/vo/export/report/ExportRepairWorkHourSbVO.java

@@ -25,26 +25,32 @@ public class ExportRepairWorkHourSbVO implements Serializable {
     private String sbName;
 
     /**
+     * 设备类型
+     */
+    @Excel(name = "设备类型", orderNum = "3")
+    private String sbTypeName;
+
+    /**
      * 维修次数
      */
-    @Excel(name = "维修次数", orderNum = "3")
+    @Excel(name = "维修次数", orderNum = "4")
     private Integer num;
 
     /**
      * 总维修工时(小时)
      */
-    @Excel(name = "总维修工时(小时)", orderNum = "4")
+    @Excel(name = "总维修工时(小时)", orderNum = "5")
     private Double totalRepairHours;
 
     /**
      * 平均工时(小时)
      */
-    @Excel(name = "平均工时(小时)", orderNum = "5")
+    @Excel(name = "平均工时(小时)", orderNum = "6")
     private Double avgRepairHours;
 
     /**
      * 总停机修复时长(小时)
      */
-    @Excel(name = "总停机修复时长(小时)", orderNum = "6")
+    @Excel(name = "总停机修复时长(小时)", orderNum = "7")
     private Double totalDealHours;
 }

+ 5 - 0
platform-dao/src/main/java/com/platform/dao/vo/report/RepairWorkHourReportVO.java

@@ -40,6 +40,11 @@ public class RepairWorkHourReportVO implements Serializable {
     private String sbName;
 
     /**
+     * 设备类型名称
+     */
+    private String sbTypeName;
+
+    /**
      * 维修次数
      */
     private Integer num;

+ 25 - 0
platform-dao/src/main/resources/mapper/check/CheckJobMapper.xml

@@ -1064,4 +1064,29 @@
             #{item}
         </foreach>
     </select>
+
+    <!-- 查询执行方式为班组且未完成的保养任务,用于每日定时提醒班长 -->
+    <select id="selectUnfinishedTeamJobs" resultType="com.platform.dao.vo.query.check.CheckJobVO">
+        select
+        checkjob.id as id,
+        checkjob.name as name,
+        checkjob.sb_id as sbId,
+        checkjob.standard_id as standardId,
+        checkjob.status as status,
+        checkjob.check_dept_id as checkDeptId,
+        checkjob.start_time as startTime,
+        checkjob.end_time as endTime,
+        sbinfo.name as sbName,
+        sbinfo.no as sbNo,
+        dept.name as checkDeptName
+        from t_check_job as checkjob
+        inner join t_check_standard standard on standard.id = checkjob.standard_id
+        left join t_sb_info sbinfo on sbinfo.id = checkjob.sb_id
+        left join t_sys_dept dept on checkjob.check_dept_id = dept.dept_id
+        where checkjob.type = 2
+        and standard.check_user_type = 4
+        and checkjob.check_dept_id is not null
+        and checkjob.status != 3
+        order by checkjob.check_dept_id asc, sbinfo.no asc
+    </select>
 </mapper>

+ 2 - 0
platform-dao/src/main/resources/mapper/repair/RepairApplicationFormMapper.xml

@@ -210,12 +210,14 @@
         select application.sb_id as sbId,
         sb.no as sbNo,
         sb.name as sbName,
+        sbType.name as sbTypeName,
         count(1) as num,
         IFNULL(sum(application.repair_minutes), 0) as totalRepairHours,
         IFNULL(avg(application.repair_minutes), 0) as avgRepairHours,
         IFNULL(sum(application.deal_minutes), 0) as totalDealHours
         from t_repair_application_form application
         left join t_sb_info sb on application.sb_id = sb.id
+        left join t_sb_type sbType on sb.type_id = sbType.id
         <where>
             application.repair_end_time is not null
             and application.sb_id is not null and application.sb_id != ''

+ 14 - 16
platform-rest/src/main/java/com/platform/rest/controller/sb/SbStatusLogController.java

@@ -5,12 +5,8 @@ import com.platform.common.util.SecurityUtils;
 import com.platform.common.util.StringUtils;
 import com.platform.dao.dto.check.CheckJobDTO;
 import com.platform.dao.dto.sb.SbStatusLogDTO;
-import com.platform.dao.entity.check.CheckJob;
-import com.platform.dao.entity.repair.RepairApplicationForm;
 import com.platform.dao.entity.sb.SbStatusLog;
 import com.platform.dao.enums.SbInfoStatusEnum;
-import com.platform.dao.mapper.check.CheckJobMapper;
-import com.platform.dao.mapper.repair.RepairApplicationFormMapper;
 import com.platform.dao.mapper.sb.SbStatusLogMapper;
 import com.platform.dao.vo.export.sb.ExportSbStatusLogVO2;
 import com.platform.service.check.CheckJobService;
@@ -26,6 +22,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import com.platform.common.bean.AbstractPageResultBean;
 import com.platform.rest.log.annotation.SysLog;
 import java.util.List;
@@ -37,6 +34,7 @@ import javax.servlet.http.HttpServletResponse;
  * @Date 2021-05-15 18:37:43
  * @Version Copyright (c) 2020,北京乾元坤和科技有限公司 All rights reserved.
  */
+@Slf4j
 @RestController
 @AllArgsConstructor
 @RequestMapping("/sb/status-logs")
@@ -44,10 +42,8 @@ public class SbStatusLogController {
 
   private final  SbStatusLogService sbStatusLogService;
 
-  private final CheckJobMapper checkJobMapper;
-
   private final SbStatusLogMapper sbStatusLogMapper;
-  private final RepairApplicationFormMapper repairApplicationFormMapper;
+  private final CheckJobService checkJobService;
 
   /**
    * 通过id查询单条记录
@@ -73,16 +69,18 @@ public class SbStatusLogController {
       if(StringUtils.isBlank(sbStatusLogDTO.getActualUser())){
           sbStatusLogDTO.setActualUser(SecurityUtils.getUserInfo().getRealName());
       }
-      if (!sbStatusLogDTO.getAfterStatus().equals(SbInfoStatusEnum.IN_USE.getValue())){
-          //修改状态后不为使用中,删除保养任务
-          CheckJob checkJob = new CheckJob();
-          checkJob.setSbId(sbStatusLogDTO.getSbId());
-          checkJobMapper.delete(checkJob);
-          RepairApplicationForm form = new RepairApplicationForm();
-          form.setSbId(sbStatusLogDTO.getSbId());
-          repairApplicationFormMapper.delete(form);
+      // 设备停用(维修停用/永久停用等任意非“使用中”状态)不再删除历史保养记录与报修单,
+      // 仅由保养任务生成逻辑(generateJobNew/finishJob)按设备状态自动暂停新任务生成。
+      SbStatusLog saved = sbStatusLogService.saveModelByDTOAndUserInfo(sbStatusLogDTO, SecurityUtils.getUserInfo());
+      // 恢复“使用中”时按启用的保养标准补生成保养任务(保底);生成失败仅记录日志,由每日定时任务兜底。
+      if (SbInfoStatusEnum.IN_USE.getValue().equals(sbStatusLogDTO.getAfterStatus())) {
+          try {
+              checkJobService.generateJobsForSb(sbStatusLogDTO.getSbId());
+          } catch (Exception e) {
+              log.error("设备 {} 恢复使用时生成保养任务失败", sbStatusLogDTO.getSbId(), e);
+          }
       }
-      return new R<>(sbStatusLogService.saveModelByDTOAndUserInfo(sbStatusLogDTO, SecurityUtils.getUserInfo()));
+      return new R<>(saved);
   }
 
   /**

+ 7 - 0
platform-rest/src/main/java/com/platform/rest/task/CheckJobTask.java

@@ -68,4 +68,11 @@ public class CheckJobTask {
     public void sendEmail(){
         sbInfoMeasureService.sendEmail();
     }
+
+    /**
+     * 定时器-每天早上9点,给存在未完成班组保养任务的班长发送邮件提醒
+     */
+    public void sendTeamUnfinishedNotice(){
+        checkJobService.sendTeamUnfinishedNotice();
+    }
 }

+ 19 - 0
platform-rest/src/main/resources/templates/checkTeamUnfinishedEmail.ftl

@@ -0,0 +1,19 @@
+<div>尊敬的${deptName}班长:</div>
+<div>截至 ${date},您所在班组仍有 <b>${total}</b> 台设备的保养任务尚未完成,明细如下:</div>
+<table border="1" cellpadding="5" cellspacing="0" style="border-collapse:collapse;margin-top:8px;">
+    <tr style="background-color:#f2f2f2;">
+        <th>序号</th>
+        <th>设备名称</th>
+        <th>设备编号</th>
+        <th>任务状态</th>
+    </tr>
+    <#list devices as item>
+    <tr>
+        <td>${item_index + 1}</td>
+        <td>${item.sbName}</td>
+        <td>${item.sbNo}</td>
+        <td>${item.statusName}</td>
+    </tr>
+    </#list>
+</table>
+<div style="margin-top:10px;">请及时安排班组人员完成上述设备的保养工作。</div>

+ 6 - 0
platform-service/src/main/java/com/platform/service/check/CheckJobService.java

@@ -184,6 +184,12 @@ public interface CheckJobService extends IBaseService<CheckJob, CheckJobDTO> {
     void initJobNew();
 
     /**
+     * 定时提醒:查询执行方式为“班组”且未完成的保养任务,
+     * 按班组汇总后给对应班长发送邮件通知(含设备名称与编号)。
+     */
+    void sendTeamUnfinishedNotice();
+
+    /**
      * 下载PDF文件
      * @param id 任务ID
      * @param response HttpServletResponse

+ 79 - 0
platform-service/src/main/java/com/platform/service/check/impl/CheckJobServiceImpl.java

@@ -24,6 +24,7 @@ import com.platform.dao.dto.check.CheckPlanDTO;
 import com.platform.dao.dto.check.CheckProjectPlanRelationDTO;
 import com.platform.dao.dto.check.CheckStandardDTO;
 import com.platform.dao.dto.sb.SbInfoDTO;
+import com.platform.dao.dto.upms.SysUserDTO;
 import com.platform.dao.entity.check.*;
 import com.platform.dao.entity.sb.SbInfo;
 import com.platform.dao.entity.upms.SysConfig;
@@ -34,7 +35,9 @@ import com.platform.dao.mapper.repair.RepairApplicationFormMapper;
 import com.platform.dao.mapper.sb.SbInfoMapper;
 import com.platform.dao.mapper.sb.SbPositionMapper;
 import com.platform.dao.mapper.upms.SysFileMapper;
+import com.platform.dao.mapper.upms.SysUserMapper;
 import com.platform.dao.util.CustomExcelImportUtil;
+import com.platform.dao.util.MessageTemplateUtil;
 import com.platform.dao.vo.SysUserDeptVO;
 import com.platform.dao.vo.SysUserVO;
 import com.platform.dao.vo.query.check.*;
@@ -49,6 +52,7 @@ import com.platform.dao.vo.tuicalendar.TuiCalendar;
 import com.platform.dao.vo.tuicalendar.TuiCalendarUtil;
 import com.platform.service.base.impl.BaseServiceImpl;
 import com.platform.service.check.CheckJobService;
+import com.platform.service.mail.MailService;
 import com.platform.service.sb.SbInfoService;
 import com.platform.service.upms.SysConfigService;
 import com.platform.service.upms.SysUserDeptService;
@@ -120,6 +124,10 @@ public class CheckJobServiceImpl extends BaseServiceImpl<CheckJobMapper, CheckJo
     private CheckRequirementMapper requirementMapper;
     @Autowired
     private RepairApplicationFormMapper repairApplicationFormMapper;
+    @Autowired
+    private SysUserMapper sysUserMapper;
+    @Autowired
+    private MailService mailService;
 
     @Value("${upload.root-dir}")
     private String UPLOAD_ROOT_FOLDER;
@@ -2736,6 +2744,77 @@ public class CheckJobServiceImpl extends BaseServiceImpl<CheckJobMapper, CheckJo
     }
 
     @Override
+    public void sendTeamUnfinishedNotice() {
+        // 查询执行方式为班组且未完成的保养任务
+        List<CheckJobVO> jobs = checkJobMapper.selectUnfinishedTeamJobs();
+        if (CollectionUtils.isEmpty(jobs)) {
+            log.info("班组未完成保养提醒:无未完成的班组保养任务,跳过");
+            return;
+        }
+        // 按班组(check_dept_id)分组
+        Map<String, List<CheckJobVO>> deptJobMap = jobs.stream()
+                .filter(job -> StringUtils.isNotBlank(job.getCheckDeptId()))
+                .collect(Collectors.groupingBy(CheckJobVO::getCheckDeptId, LinkedHashMap::new, Collectors.toList()));
+        String date = DateUtils.dateToString(LocalDate.now(), DateUtils.PATTERN_YMD);
+        for (Map.Entry<String, List<CheckJobVO>> entry : deptJobMap.entrySet()) {
+            String deptId = entry.getKey();
+            List<CheckJobVO> deptJobs = entry.getValue();
+            // 查询该班组下的班长
+            SysUserDTO query = new SysUserDTO();
+            query.setDeptIds(Collections.singletonList(deptId));
+            query.setRoleCode(SysRoleCodeEnum.BAN_ZHANG.name());
+            List<SysUserVO> leaders = sysUserMapper.selectDeptRoleUser(query);
+            if (CollectionUtils.isEmpty(leaders)) {
+                log.warn("班组 {} 未配置班长,跳过未完成保养提醒", deptId);
+                continue;
+            }
+            // 按设备去重(同一设备可能存在多个未完成任务),保留设备名称与编号
+            String deptName = deptJobs.get(0).getCheckDeptName();
+            Map<String, Map<String, Object>> deviceMap = new LinkedHashMap<>();
+            for (CheckJobVO job : deptJobs) {
+                String key = StringUtils.isNotBlank(job.getSbId()) ? job.getSbId()
+                        : (job.getSbName() + "_" + job.getSbNo());
+                if (deviceMap.containsKey(key)) {
+                    continue;
+                }
+                Map<String, Object> device = new HashMap<>();
+                device.put("sbName", StringUtils.isBlank(job.getSbName()) ? "未知设备" : job.getSbName());
+                device.put("sbNo", StringUtils.isBlank(job.getSbNo()) ? "无" : job.getSbNo());
+                device.put("statusName", getCheckJobStatusName(job.getStatus()));
+                deviceMap.put(key, device);
+            }
+            List<Map<String, Object>> devices = new ArrayList<>(deviceMap.values());
+            String content = MessageTemplateUtil.getTeamUnfinishedEmailContent(deptName, date, devices);
+            for (SysUserVO leader : leaders) {
+                if (StringUtils.isNotBlank(leader.getEmail())) {
+                    mailService.sendHtmlMail(leader.getEmail(), "设备保养任务未完成提醒", content);
+                } else {
+                    log.warn("班长 {} 未配置邮箱,无法发送未完成保养提醒", leader.getRealName());
+                }
+            }
+        }
+    }
+
+    /**
+     * 保养任务状态文字描述
+     */
+    private String getCheckJobStatusName(Integer status) {
+        if (status == null) {
+            return "未完成";
+        }
+        if (CheckJobStatusEnum.NOT_EXECUTE.getValue().equals(status)) {
+            return "未执行";
+        }
+        if (CheckJobStatusEnum.EXECUTING.getValue().equals(status)) {
+            return "执行中";
+        }
+        if (Integer.valueOf(4).equals(status)) {
+            return "已逾期";
+        }
+        return "未完成";
+    }
+
+    @Override
     @Transactional(rollbackFor = Exception.class)
     public void generateJobsForSb(String sbId) {
         if (StringUtils.isBlank(sbId)) {

+ 2 - 12
platform-service/src/main/java/com/platform/service/sb/impl/SbInfoServiceImpl.java

@@ -517,18 +517,8 @@ public class SbInfoServiceImpl extends BaseServiceImpl<SbInfoMapper, SbInfo, SbI
 
     @Override
     public void modModelByDTO(SbInfoDTO model) {
-        SbInfo info = new SbInfo();
-        info.setId(model.getId());
-        info = mapper.selectOne(info);
-        if (!info.getStatus().equals(model.getStatus()) && SbInfoStatusEnum.IN_USE.equals(info.getStatus())) {
-            CheckJob checkJob = new CheckJob();
-            checkJob.setSbId(model.getId());
-            checkJobMapper.delete(checkJob);
-            RepairApplicationForm form = new RepairApplicationForm();
-            form.setSbId(model.getId());
-            repairApplicationFormMapper.delete(form);
-        }
-
+        // 设备状态变更(含停用)不再删除历史保养任务与报修单;停用期间由生成逻辑按设备状态自动暂停,
+        // 恢复“使用中”时在 setSbStatusLogAndStopLog 中调用 generateJobsForSb 补生成保养任务。
         if (model.getIsFinancing() == null) {
             model.setIsFinancing(SbInfoFinancingEnum.NOT.getValue());
         }