Pārlūkot izejas kodu

feat(check): 实现班组任务可见性及自动化功能

- 新增 checkDeptIds 字段用于存储用户部门及上级部门ID列表
- 重构查询条件支持班组任务按部门层级可见
- 实现 fillCheckDeptIds 方法填充用户部门链
- 支持班组模式下任务分配给整个部门而非个人
- 自动化生成首期保养任务功能
- 优化邮件模板避免重复签名显示
- 完善台时/公里类型任务的防重复生成机制
dazhaxie 2 nedēļas atpakaļ
vecāks
revīzija
20966370e1

+ 4 - 0
platform-dao/src/main/java/com/platform/dao/dto/check/CheckJobDTO.java

@@ -114,6 +114,10 @@ public class CheckJobDTO extends BaseDTO implements Serializable {
      */
     private String checkDeptId;
     /**
+     * 用户所属部门及所有上级部门ID列表(用于班组任务可见性判断)
+     */
+    private List<String> checkDeptIds;
+    /**
      * 设备等级
      */
     private Integer sbLevel;

+ 18 - 2
platform-dao/src/main/resources/mapper/check/CheckJobMapper.xml

@@ -242,7 +242,15 @@
             </foreach>
         </if>
         <if test="checkUserId != null and checkUserId != ''">
-            and checkjob.check_user_id = #{checkUserId}
+            and (
+                checkjob.check_user_id = #{checkUserId}
+                <if test="checkDeptIds != null and checkDeptIds.size() > 0">
+                    OR (checkjob.check_dept_id IS NOT NULL AND checkjob.check_dept_id IN
+                    <foreach item="deptId" collection="checkDeptIds" open="(" close=")" separator=",">
+                        #{deptId}
+                    </foreach>)
+                </if>
+            )
         </if>
         <if test="checkDeptId != null and checkDeptId != ''">
             and checkjob.check_dept_id = #{checkDeptId}
@@ -421,7 +429,15 @@
             </foreach>
         </if>
         <if test="checkUserId != null and checkUserId != ''">
-            and checkjob_inner.check_user_id = #{checkUserId}
+            and (
+                checkjob_inner.check_user_id = #{checkUserId}
+                <if test="checkDeptIds != null and checkDeptIds.size() > 0">
+                    OR (checkjob_inner.check_dept_id IS NOT NULL AND checkjob_inner.check_dept_id IN
+                    <foreach item="deptId" collection="checkDeptIds" open="(" close=")" separator=",">
+                        #{deptId}
+                    </foreach>)
+                </if>
+            )
         </if>
         <if test="checkDeptId != null and checkDeptId != ''">
             and checkjob_inner.check_dept_id = #{checkDeptId}

+ 0 - 4
platform-rest/src/main/resources/templates/repairCheckEmail.ftl

@@ -30,7 +30,3 @@
 <div>
     <a target="_blank" href="${url}">点击链接跳转处理报修单</a>
 </div>
-<br/>
-<div style="color:#999;font-size:12px;margin-top:20px;border-top:1px solid #eee;padding-top:10px;">
-    此邮件由设备管理系统自动发送,请勿直接回复。
-</div>

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

@@ -44,7 +44,3 @@
 <div>
     <a target="_blank" href="${url}">点击链接跳转处理维修单</a>
 </div>
-<br/>
-<div style="color:#999;font-size:12px;margin-top:20px;border-top:1px solid #eee;padding-top:10px;">
-    此邮件由设备管理系统自动发送,请勿直接回复。
-</div>

+ 0 - 4
platform-rest/src/main/resources/templates/repairEmail.ftl

@@ -30,7 +30,3 @@
 <div>
     <a target="_blank" href="${url}">点击链接跳转处理报修单</a>
 </div>
-<br/>
-<div style="color:#999;font-size:12px;margin-top:20px;border-top:1px solid #eee;padding-top:10px;">
-    此邮件由设备管理系统自动发送,请勿直接回复。
-</div>

+ 0 - 4
platform-rest/src/main/resources/templates/repairRefuseEmail.ftl

@@ -30,7 +30,3 @@
 <div>
     <a target="_blank" href="${url}">点击链接跳转处理报修单</a>
 </div>
-<br/>
-<div style="color:#999;font-size:12px;margin-top:20px;border-top:1px solid #eee;padding-top:10px;">
-    此邮件由设备管理系统自动发送,请勿直接回复。
-</div>

+ 61 - 2
platform-service/src/main/java/com/platform/service/check/impl/CheckJobServiceImpl.java

@@ -35,8 +35,10 @@ 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.util.CustomExcelImportUtil;
+import com.platform.dao.vo.SysUserDeptVO;
 import com.platform.dao.vo.SysUserVO;
 import com.platform.dao.vo.query.check.*;
+import com.platform.dao.vo.query.upms.SysDeptVO;
 import com.platform.dao.vo.report.CheckItemVO;
 import com.platform.dao.vo.report.CheckJobReportVO;
 import com.platform.dao.vo.report.CheckJobReportWeekHoursVO;
@@ -490,6 +492,8 @@ public class CheckJobServiceImpl extends BaseServiceImpl<CheckJobMapper, CheckJo
             // checkJobDTO.setSearchEndTime(LocalDateTime.parse(lastDay, df));
         }
         checkJobDTO.setCheckUserId(userInfo.getUserId());
+        // 班组任务可见性:包含用户所属部门及所有上级部门ID
+        fillCheckDeptIds(checkJobDTO, userInfo);
         checkJobDTO.setType(2);
         List<Integer> statusList = new ArrayList<>();
         statusList.add(CheckJobStatusEnum.NOT_EXECUTE.getValue());
@@ -579,6 +583,9 @@ public class CheckJobServiceImpl extends BaseServiceImpl<CheckJobMapper, CheckJo
         String superUser = ConfigCache.getLabelByValue(SysConfigEnum.CHECK_JOB_SUPER_USER.name());
         if (superUser != null && Arrays.asList(superUser.split(",")).contains(userInfo.getUsername())) {
             model.setCheckUserId(null);
+        } else {
+            // 班组任务可见性:获取用户所属部门及所有上级部门ID,用于匹配班组任务的checkDeptId
+            fillCheckDeptIds(model, userInfo);
         }
 
         // 修复分页重复:先分页获取任务ID,再根据ID查询完整数据
@@ -635,6 +642,42 @@ public class CheckJobServiceImpl extends BaseServiceImpl<CheckJobMapper, CheckJo
     }
 
     /**
+     * 填充班组任务可见性所需的部门链:用户所属部门 + 所有上级部门ID。
+     * 查询任务时,任务的 check_dept_id 命中该链中任意部门,用户即可看到该班组任务,
+     * 因此选择上级部门时,其所有下级部门的人员也能看到并执行任务。
+     *
+     * @param model    查询条件
+     * @param userInfo 当前用户
+     */
+    private void fillCheckDeptIds(CheckJobDTO model, UserInfo userInfo) {
+        try {
+            SysUserDeptVO userDept = com.platform.dao.util.UserUtil.getUserDeptAllowNull(userInfo);
+            if (userDept == null) {
+                log.warn("fillCheckDeptIds: 用户 {} 未关联部门,班组任务将不可见", userInfo.getUserId());
+                return;
+            }
+            List<String> deptIds = new ArrayList<>();
+            deptIds.add(userDept.getDeptId());
+            log.info("fillCheckDeptIds: 用户 {} 所属部门 {}", userInfo.getUserId(), userDept.getDeptId());
+            // 获取所有上级部门(班组→项目部→分公司→集团)
+            com.platform.dao.dto.upms.SysDeptDTO deptQuery = new com.platform.dao.dto.upms.SysDeptDTO();
+            deptQuery.setDescendant(userDept.getDeptId());
+            com.platform.dao.mapper.upms.SysDeptRelationMapper deptRelationMapper =
+                    com.platform.common.util.SpringContextHolder.getBean(com.platform.dao.mapper.upms.SysDeptRelationMapper.class);
+            List<SysDeptVO> parentDepts = deptRelationMapper.allParentList(deptQuery);
+            if (!CollectionUtils.isEmpty(parentDepts)) {
+                for (SysDeptVO dept : parentDepts) {
+                    deptIds.add(dept.getDeptId());
+                }
+            }
+            log.info("fillCheckDeptIds: 用户 {} 部门链 {}", userInfo.getUserId(), deptIds);
+            model.setCheckDeptIds(deptIds);
+        } catch (Exception e) {
+            log.warn("获取用户部门层级失败,班组任务可能不可见: {}", e.getMessage());
+        }
+    }
+
+    /**
      * 获取当日移动端现场任务:当日的任务:主要是现场人员的任务
      * <p>
      * 如果有sbId,则说明是查询某一台设备的任务,需要查询设备的显示使用人员账号
@@ -690,7 +733,10 @@ public class CheckJobServiceImpl extends BaseServiceImpl<CheckJobMapper, CheckJo
         model.setSearchEndTime(DateUtils.getLastDayOfThisMonth());
         if (model.getStandardLevel() == null) {
             if (!SecurityUtils.isRole(SysRoleCodeEnum.MM.name())) {
-                model.setCheckUserId(SecurityUtils.getUserInfo().getUserId());
+                UserInfo userInfo = SecurityUtils.getUserInfo();
+                model.setCheckUserId(userInfo.getUserId());
+                // 班组任务可见性:包含用户所属部门及所有上级部门ID
+                fillCheckDeptIds(model, userInfo);
             }
         }
         List<Integer> statusList = new ArrayList<Integer>();
@@ -712,6 +758,8 @@ public class CheckJobServiceImpl extends BaseServiceImpl<CheckJobMapper, CheckJo
         if (model.getFilter() != null && DataFilterTypeEnum.SELF.getValue() == model.getFilter().intValue()) {
             UserInfo userInfo = SecurityUtils.getUserInfo();
             model.setCheckUserId(userInfo.getUserId());
+            // 班组任务可见性:包含用户所属部门及所有上级部门ID
+            fillCheckDeptIds(model, userInfo);
         }
         if (model.getSearchMonth() != null) {
             LocalDate firstDay = model.getSearchMonth().with(TemporalAdjusters.firstDayOfMonth());
@@ -795,6 +843,8 @@ public class CheckJobServiceImpl extends BaseServiceImpl<CheckJobMapper, CheckJo
         if (model.getFilter() != null && DataFilterTypeEnum.SELF.getValue() == model.getFilter().intValue()) {
             UserInfo userInfo = SecurityUtils.getUserInfo();
             model.setCheckUserId(userInfo.getUserId());
+            // 班组任务可见性:包含用户所属部门及所有上级部门ID
+            fillCheckDeptIds(model, userInfo);
         }
         if (model.getSearchMonth() != null) {
             LocalDate firstDay = model.getSearchMonth().with(TemporalAdjusters.firstDayOfMonth());
@@ -823,6 +873,8 @@ public class CheckJobServiceImpl extends BaseServiceImpl<CheckJobMapper, CheckJo
         if (model.getFilter() != null && DataFilterTypeEnum.SELF.getValue() == model.getFilter().intValue()) {
             UserInfo userInfo = SecurityUtils.getUserInfo();
             model.setCheckUserId(userInfo.getUserId());
+            // 班组任务可见性:包含用户所属部门及所有上级部门ID
+            fillCheckDeptIds(model, userInfo);
         }
         PageHelper.startPage(pageNum, pageSize);
         AbstractPageResultBean<CheckJob> pageInfo = new MyPage(mapper.selectList(model));
@@ -1570,6 +1622,8 @@ public class CheckJobServiceImpl extends BaseServiceImpl<CheckJobMapper, CheckJo
             UserInfo userInfo = SecurityUtils.getUser().getUserInfo();
             checkJob.setUpdateUserId(userInfo.getUserId());
             checkJob.setUpdateUserName(userInfo.getUsername());
+            // 记录实际执行人:接收任务时确定由谁执行该任务(班组模式下尤其重要)
+            checkJob.setActualUser(userInfo.getRealName());
         }
         mapper.updateByPrimaryKeySelective(checkJob);
     }
@@ -1679,6 +1733,7 @@ public class CheckJobServiceImpl extends BaseServiceImpl<CheckJobMapper, CheckJo
                 nextCheckJob.setSbId(checkStandard.getSbId());
                 nextCheckJob.setReceiveOvertime(false);
                 nextCheckJob.setCheckUserId(checkJob.getCheckUserId());
+                nextCheckJob.setCheckDeptId(checkJob.getCheckDeptId());
                 nextCheckJob.setPartId(checkStandard.getPart());
                 nextCheckJob.setStandardId(checkStandard.getId());
                 nextCheckJob.setType(checkStandard.getType());
@@ -1708,8 +1763,11 @@ public class CheckJobServiceImpl extends BaseServiceImpl<CheckJobMapper, CheckJo
         String savePeriod = "一年";
         // 维保工时
         String wbHours = detail.getRealHours();
-        // 维保人员
+        // 维保人员:班组任务无指定点检人,回退使用实际执行人
         String wbUser = detail.getCheckUserName();
+        if (StringUtils.isBlank(wbUser)) {
+            wbUser = checkJob.getActualUser();
+        }
 
         // 根据标准 id 查询保养项集合
         List<CheckRequirement> dbRequirementList = requirementMapper.selectByStandardId(checkJob.getStandardId());
@@ -2068,6 +2126,7 @@ public class CheckJobServiceImpl extends BaseServiceImpl<CheckJobMapper, CheckJo
             nextCheckJob.setCreatedTime(LocalDateTime.now());
             nextCheckJob.setSbId(checkStandard.getSbId());
             nextCheckJob.setCheckUserId(checkJob.getCheckUserId());
+            nextCheckJob.setCheckDeptId(checkJob.getCheckDeptId());
             nextCheckJob.setPartId(checkStandard.getPart());
             nextCheckJob.setStandardId(checkStandard.getId());
             nextCheckJob.setType(checkStandard.getType());

+ 67 - 3
platform-service/src/main/java/com/platform/service/check/impl/CheckStandardServiceImpl.java

@@ -42,6 +42,7 @@ import com.platform.service.event.WorkplaceBacklogEvent;
 import com.platform.service.part.PartInfoService;
 import com.platform.service.util.SysFileUtils;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
@@ -64,6 +65,7 @@ import java.util.stream.Collectors;
  */
 @AllArgsConstructor
 @Service("checkStandardService")
+@Slf4j
 public class CheckStandardServiceImpl extends BaseServiceImpl<CheckStandardMapper, CheckStandard, CheckStandardDTO> implements CheckStandardService {
     private final CheckRequirementMapper checkRequirementMapper;
     private CheckStandardParamService paramService;
@@ -347,10 +349,18 @@ public class CheckStandardServiceImpl extends BaseServiceImpl<CheckStandardMappe
         // 4. 保存保养要求关联关系
         saveStandardRequirements(checkStandard, allRequirements);
 
-        // 5. 返回包含保养要求的结果
-//        return checkStandardMapper.selectCheckStandardWithRequirements(model.getId());
-
+        // 5. 自动生成首期保养任务
+        try {
+            CheckJobDTO jobDTO = new CheckJobDTO();
+            jobDTO.setStandardId(checkStandard.getId());
+            // startTime 不传则默认今天,generate() 会计算 endTime 并更新 nextDate
+            this.generate(jobDTO);
+            log.info("新增保养标准 {} 自动生成首期任务成功", checkStandard.getId());
+        } catch (Exception e) {
+            log.warn("新增保养标准 {} 自动生成首期任务失败: {}", checkStandard.getId(), e.getMessage());
+        }
 
+        // 6. 返回包含保养要求的结果
         return checkStandard;
     }
 
@@ -416,8 +426,41 @@ public class CheckStandardServiceImpl extends BaseServiceImpl<CheckStandardMappe
                 break;
         }
         model.setNextDate(nextDate);
+        
+        // 查询原始标准,用于比较执行人信息是否发生变化
+        CheckStandard originalStandard = super.getModelById(model.getId());
+        
         super.modModelByDTO(model);
 
+        // 如果执行人信息发生变化,同步更新所有未执行任务
+        if (originalStandard != null) {
+            boolean userTypeChanged = !java.util.Objects.equals(model.getCheckUserType(), originalStandard.getCheckUserType());
+            boolean userIdChanged = !java.util.Objects.equals(model.getCheckUserId(), originalStandard.getCheckUserId());
+            boolean deptIdChanged = !java.util.Objects.equals(model.getCheckDeptId(), originalStandard.getCheckDeptId());
+            
+            if (userTypeChanged || userIdChanged || deptIdChanged) {
+                Weekend<CheckJob> jobWeekend = new Weekend<>(CheckJob.class);
+                jobWeekend.weekendCriteria()
+                        .andEqualTo(CheckJob::getStandardId, model.getId())
+                        .andEqualTo(CheckJob::getStatus, CheckJobStatusEnum.NOT_EXECUTE.getValue());
+                
+                CheckJob updateJob = new CheckJob();
+                
+                if (CheckUserTypeEnum.TEAM.getValue().equals(model.getCheckUserType())) {
+                    // 班组模式:更新 check_dept_id
+                    updateJob.setCheckDeptId(model.getCheckDeptId());
+                    log.info("保养标准 {} 切换到班组模式,更新 check_dept_id={}", model.getId(), model.getCheckDeptId());
+                } else {
+                    // 非班组模式:更新 check_user_id
+                    updateJob.setCheckUserId(model.getCheckUserId());
+                    log.info("保养标准 {} 切换到非班组模式,更新 check_user_id={}", model.getId(), model.getCheckUserId());
+                }
+                
+                int updated = checkJobMapper.updateByExampleSelective(updateJob, jobWeekend);
+                log.info("保养标准 {} 修改执行人信息,已更新 {} 条未执行任务", model.getId(), updated);
+            }
+        }
+
         //List<CheckRequirement> allRequirements = new ArrayList<>();`
 
 /*        for (CheckRequirement requirement : model.getRequirementList()) {
@@ -596,6 +639,21 @@ public class CheckStandardServiceImpl extends BaseServiceImpl<CheckStandardMappe
         LocalDateTime now = LocalDateTime.now();
         LocalDate currentDate = now.toLocalDate();
         CheckStandard standard = super.getModelById(checkJobDTO.getStandardId());
+        // 台时/公里类型的任务由设备运行数据驱动自动生成,不支持手动插入
+        if (CheckPlanPeriodTypeEnum.MILES.getValue().equals(standard.getPeriodType())
+                || CheckPlanPeriodTypeEnum.TAISHI.getValue().equals(standard.getPeriodType())) {
+            throw new BusinessException("台时/公里类型的保养任务由系统根据设备运行数据自动生成,不支持手动插入");
+        }
+        // 防重:存在未完成任务时不允许重复生成,直接去完成已有任务即可
+        List<Integer> unfinishedStatus = new ArrayList<>();
+        unfinishedStatus.add(CheckJobStatusEnum.NOT_EXECUTE.getValue());
+        unfinishedStatus.add(CheckJobStatusEnum.EXECUTING.getValue());
+        Weekend<CheckJob> jobWeekend = new Weekend<>(CheckJob.class);
+        jobWeekend.weekendCriteria().andEqualTo(CheckJob::getStandardId, standard.getId())
+                .andIn(CheckJob::getStatus, unfinishedStatus);
+        if (checkJobMapper.selectCountByExample(jobWeekend) > 0) {
+            throw new BusinessException("该保养标准已存在未完成的任务,请先完成现有任务");
+        }
         CheckJob checkJob = new CheckJob();
         checkJob.setSbId(standard.getSbId());
         checkJob.setStatus(CheckJobStatusEnum.NOT_EXECUTE.getValue());
@@ -625,6 +683,10 @@ public class CheckStandardServiceImpl extends BaseServiceImpl<CheckStandardMappe
                 checkJob.setCheckUserId(sbInfo.getRepairUser());
             } else if (standard.getCheckUserType().equals(CheckUserTypeEnum.ZHIDING.getValue())) {
                 checkJob.setCheckUserId(sbInfo.getRepairUser());
+            } else if (standard.getCheckUserType().equals(CheckUserTypeEnum.TEAM.getValue())) {
+                // 班组模式:设置班组ID,不指定具体执行人
+                checkJob.setCheckDeptId(standard.getCheckDeptId());
+                checkJob.setCheckUserId(null);
             }
         }
         checkJob.setId(IdGeneratorUtils.getObjectId());
@@ -634,6 +696,8 @@ public class CheckStandardServiceImpl extends BaseServiceImpl<CheckStandardMappe
         checkJob.setRequirement(standard.getRequirement());
         checkJob.setRemark(standard.getRemark());
         checkJob.setName(standard.getName());
+        checkJob.setStandardHours(standard.getStandardHours());
+        checkJob.setReceiveOvertime(false);
         checkJobMapper.insert(checkJob);
 
         // 更新标准的上次和下次时间

+ 9 - 2
platform-service/src/main/java/com/platform/service/mail/MailServiceImpl.java

@@ -23,6 +23,13 @@ import java.io.File;
 @Service("mailService")
 public class MailServiceImpl implements MailService {
     /**
+     * 邮件正文底部签名。
+     * 仅拼接在实际外发的邮件内容上,不影响系统站内信(同一份 content 会先存入通知表)
+     */
+    private static final String MAIL_FOOTER = "<br/><div style=\"color:#999;font-size:12px;margin-top:20px;"
+            + "border-top:1px solid #eee;padding-top:10px;\">此邮件由设备管理系统自动发送,请勿直接回复。</div>";
+
+    /**
      * Spring Boot 提供了一个发送邮件的简单抽象,使用的是下面这个接口,这里直接注入即可使用
      */
     @Autowired
@@ -85,8 +92,8 @@ public class MailServiceImpl implements MailService {
             messageHelper.setTo(to);
             //邮件主题
             message.setSubject(subject);
-            //邮件内容,html格式
-            messageHelper.setText(content, true);
+            //邮件内容,html格式,末尾追加仅邮件可见的签名
+            messageHelper.setText((content == null ? "" : content) + MAIL_FOOTER, true);
             //发送
             mailSender.send(message);
             //日志信息