Ver Fonte

feat(risk): 移动端任务按 time_slot 拆 N 个待办(每日多时段场景)

- MobileInspectionTaskVo 新增 slot / allTimeSlots 字段
- queryMobileList / queryQualityMobileList:
  按 task.time_slot(逗号分隔)拆成多个 vo,每个 vo.planStartTime/planEndTime
  改为今天 + slot 起止时间
- 新增 expandByTimeSlots / parseTimeSlots / mergeDateTime 私有方法
- punchStatus 共享 task 级别(同 task 一次打卡所有 slot 都视为已打卡)
- 未打卡 slot 当前时间 > planEndTime → 单标 OVERDUE
yangjingjing há 2 dias atrás
pai
commit
5c9845d4bd

+ 14 - 0
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/domain/vo/MobileInspectionTaskVo.java

@@ -97,4 +97,18 @@ public class MobileInspectionTaskVo implements Serializable {
 
     /** 巡检项目列表(详情页用,前端 InspectionItem 字段:itemCategory/itemContent/itemStandard/needPhoto/result/...) */
     private List<RiskInspectionTaskItemVo> items;
+
+    // ============ 时间槽拆分(每日任务多时段) ============
+
+    /**
+     * 当前时间段(每日任务按 time_slot 拆出 N 个待办,每个待办对应一个 slot)
+     * 格式: "HH:mm-HH:mm" (如 "08:00-08:30")
+     * 非每日任务或单时段任务该字段为空
+     */
+    private String slot;
+
+    /**
+     * 当前 task 全部时间段(详情页显示用,逗号分隔)
+     */
+    private String allTimeSlots;
 }

+ 98 - 12
ruoyi-modules/ruoyi-risk/src/main/java/org/dromara/risk/service/impl/RiskInspectionTaskServiceImpl.java

@@ -34,6 +34,8 @@ import org.springframework.util.Assert;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
@@ -321,14 +323,16 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
         applyStatusFilter(lqw, status);
         lqw.orderByDesc(RiskInspectionTask::getPlanStartTime);
         Page<RiskInspectionTask> page = baseMapper.selectPage(pageQuery.build(), lqw);
-        List<MobileInspectionTaskVo> rows = page.getRecords().stream()
-            .map(this::convertToMobileVo)
-            .toList();
+        // 2. 实体 → 基础 vo + 按 time_slot 拆分为多个"待办"(每日任务多时段 → 移动端 N 条)
+        List<MobileInspectionTaskVo> rows = new ArrayList<>();
+        for (RiskInspectionTask task : page.getRecords()) {
+            rows.addAll(expandByTimeSlots(task));
+        }
         return TableDataInfo.build(new Page<MobileInspectionTaskVo>()
             .setRecords(rows)
-            .setTotal(page.getTotal())
-            .setSize(page.getSize())
-            .setCurrent(page.getCurrent()));
+            .setTotal((long) rows.size())
+            .setSize((long) rows.size())
+            .setCurrent(1L));
     }
 
     /**
@@ -347,14 +351,96 @@ public class RiskInspectionTaskServiceImpl implements IRiskInspectionTaskService
         applyStatusFilter(lqw, status);
         lqw.orderByDesc(RiskInspectionTask::getPlanStartTime);
         Page<RiskInspectionTask> page = baseMapper.selectPage(pageQuery.build(), lqw);
-        List<MobileInspectionTaskVo> rows = page.getRecords().stream()
-            .map(this::convertToMobileVo)
-            .toList();
+        // 同 queryMobileList:按 time_slot 拆分
+        List<MobileInspectionTaskVo> rows = new ArrayList<>();
+        for (RiskInspectionTask task : page.getRecords()) {
+            rows.addAll(expandByTimeSlots(task));
+        }
         return TableDataInfo.build(new Page<MobileInspectionTaskVo>()
             .setRecords(rows)
-            .setTotal(page.getTotal())
-            .setSize(page.getSize())
-            .setCurrent(page.getCurrent()));
+            .setTotal((long) rows.size())
+            .setSize((long) rows.size())
+            .setCurrent(1L));
+    }
+
+    /**
+     * 按 task.time_slot 把单条 task 拆成 N 个移动端 vo(每日多时段场景)
+     * <p>
+     * 拆分规则:
+     * <ul>
+     *   <li>time_slot 为空 / 解析不出 slot → 原样返回 1 个 vo(非每日任务或单时段任务)</li>
+     *   <li>time_slot = "08:00-08:30,10:00-10:30,12:00-12:30" → 返回 3 个 vo</li>
+     *   <li>每个 vo 的 planStartTime/planEndTime = 今天 + slot 起止时间(独立时段)</li>
+     *   <li>每个 vo 的 punchStatus 共享 task 级别(同一 task 一次打卡所有 slot 都视为已打卡)</li>
+     *   <li>若 task 还没打卡且当前时间已过 slot.planEndTime → 单独把该 slot 标记为 OVERDUE</li>
+     * </ul>
+     */
+    private List<MobileInspectionTaskVo> expandByTimeSlots(RiskInspectionTask task) {
+        List<MobileInspectionTaskVo> result = new ArrayList<>();
+        // 1) 基础 vo(含 task 级别 punchStatus/打卡时间等)
+        MobileInspectionTaskVo baseVo = convertToMobileVo(task);
+        // 2) 解析 slot 列表
+        List<String> slots = parseTimeSlots(task.getTimeSlot());
+        baseVo.setAllTimeSlots(task.getTimeSlot());
+        if (slots.isEmpty()) {
+            // 无 time_slot(每周/每月/单时段)→ 原样返回
+            result.add(baseVo);
+            return result;
+        }
+        // 3) 每个 slot 复制一份,planStartTime/planEndTime 改为今天+slot 时间
+        LocalDate today = LocalDate.now();
+        Date now = new Date();
+        boolean isPunchable = "PENDING".equals(baseVo.getPunchStatus());
+        for (String slot : slots) {
+            MobileInspectionTaskVo vo = new MobileInspectionTaskVo();
+            BeanUtil.copyProperties(baseVo, vo);
+            vo.setSlot(slot);
+            String[] parts = slot.split("-");
+            if (parts.length == 2) {
+                vo.setPlanStartTime(mergeDateTime(today, parts[0].trim()));
+                vo.setPlanEndTime(mergeDateTime(today, parts[1].trim()));
+                // 未打卡的 slot:如果当前时间已过 slot.planEndTime → 标 OVERDUE
+                if (isPunchable && vo.getPlanEndTime() != null && vo.getPlanEndTime().before(now)) {
+                    vo.setPunchStatus("OVERDUE");
+                }
+            }
+            result.add(vo);
+        }
+        return result;
+    }
+
+    /**
+     * 解析 task.time_slot(逗号分隔)为 slot 列表
+     * 格式: "08:00-08:30,10:00-10:30,12:00-12:30"
+     */
+    private List<String> parseTimeSlots(String timeSlot) {
+        List<String> slots = new ArrayList<>();
+        if (StringUtils.isBlank(timeSlot)) {
+            return slots;
+        }
+        for (String s : timeSlot.split(",")) {
+            String trimmed = s.trim();
+            if (!trimmed.isEmpty() && trimmed.contains("-")) {
+                slots.add(trimmed);
+            }
+        }
+        return slots;
+    }
+
+    /**
+     * 合并日期 + HH:mm 为 Date
+     */
+    private Date mergeDateTime(LocalDate date, String hhmm) {
+        try {
+            String[] parts = hhmm.split(":");
+            int hour = Integer.parseInt(parts[0]);
+            int minute = parts.length > 1 ? Integer.parseInt(parts[1]) : 0;
+            LocalDateTime dt = date.atTime(hour, minute);
+            return Date.from(dt.atZone(ZoneId.systemDefault()).toInstant());
+        } catch (Exception e) {
+            log.warn("解析时间段失败: date={}, hhmm={}", date, hhmm);
+            return null;
+        }
     }
 
     /**