|
|
@@ -19,6 +19,7 @@ import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
+import java.util.Calendar;
|
|
|
import java.util.Date;
|
|
|
import java.util.List;
|
|
|
import java.util.stream.Collectors;
|
|
|
@@ -179,6 +180,104 @@ public class TaskPlanService implements TaskPlanServiceInterface {
|
|
|
return count;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 统一方案任务生成器(设计: 2026-09-04-plan-to-task-unification-design.md)
|
|
|
+ * 引导任务 + 从 task_template_items(按 sort_order)批量生成日常任务。
|
|
|
+ * 幂等:sourceType=plan & sourceId=planId 已存在任务则跳过。
|
|
|
+ * deadline:当天 23:59:59;familyMemberId:统一设置;memberOnly=1。
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ public int generateTasksFromTemplate(TaskPlanInstance plan, Long operatorId) {
|
|
|
+ if (plan == null) {
|
|
|
+ throw new IllegalStateException("方案不存在");
|
|
|
+ }
|
|
|
+ Long planId = plan.getId();
|
|
|
+ // 幂等:该方案已生成过任务则跳过
|
|
|
+ Long existing = taskMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<Task>()
|
|
|
+ .eq(Task::getSourceType, "plan")
|
|
|
+ .eq(Task::getSourceId, planId));
|
|
|
+ if (existing != null && existing > 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ Date now = new Date();
|
|
|
+ Date deadline = buildTodayDeadline();
|
|
|
+ int count = 0;
|
|
|
+
|
|
|
+ // 1. 引导任务(方案启动)
|
|
|
+ Task guideTask = new Task();
|
|
|
+ guideTask.setFamilyId(plan.getFamilyId());
|
|
|
+ guideTask.setChildId(plan.getChildId());
|
|
|
+ guideTask.setFamilyMemberId(plan.getChildId());
|
|
|
+ guideTask.setCreatorId(operatorId);
|
|
|
+ guideTask.setExecutorType("child");
|
|
|
+ guideTask.setExecutorId(plan.getChildId());
|
|
|
+ guideTask.setTitle("方案启动: " + plan.getName());
|
|
|
+ guideTask.setDescription("请阅读并开始执行测评方案");
|
|
|
+ guideTask.setPoints(2);
|
|
|
+ guideTask.setCategory("成长");
|
|
|
+ guideTask.setStatus("pending");
|
|
|
+ guideTask.setSourceType("plan");
|
|
|
+ guideTask.setSourceId(planId);
|
|
|
+ guideTask.setNeedReview(0);
|
|
|
+ guideTask.setIsTemplate(0);
|
|
|
+ guideTask.setMemberOnly(1);
|
|
|
+ guideTask.setDeadline(deadline);
|
|
|
+ guideTask.setCreatedAt(now);
|
|
|
+ guideTask.setUpdatedAt(now);
|
|
|
+ taskMapper.insert(guideTask);
|
|
|
+ count++;
|
|
|
+
|
|
|
+ // 2. 模板任务(task_template_items 为唯一来源)
|
|
|
+ if (plan.getPackageId() != null) {
|
|
|
+ List<TaskTemplateItem> items = templateItemMapper.selectList(
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<TaskTemplateItem>()
|
|
|
+ .eq("package_id", plan.getPackageId())
|
|
|
+ .orderByAsc("sort_order"));
|
|
|
+ for (TaskTemplateItem item : items) {
|
|
|
+ if (item.getTaskName() == null || item.getTaskName().isEmpty()) continue;
|
|
|
+ Task task = new Task();
|
|
|
+ task.setFamilyId(plan.getFamilyId());
|
|
|
+ task.setChildId(plan.getChildId());
|
|
|
+ task.setFamilyMemberId(plan.getChildId());
|
|
|
+ task.setCreatorId(operatorId);
|
|
|
+ task.setExecutorType("child");
|
|
|
+ task.setExecutorId(plan.getChildId());
|
|
|
+ task.setTitle(item.getTaskName());
|
|
|
+ task.setDescription(item.getDescription());
|
|
|
+ task.setPoints(item.getPoints() != null ? item.getPoints() : 2);
|
|
|
+ task.setCategory("成长");
|
|
|
+ task.setStatus("pending");
|
|
|
+ task.setSourceType("plan");
|
|
|
+ task.setSourceId(planId);
|
|
|
+ task.setNeedReview(0);
|
|
|
+ task.setIsTemplate(0);
|
|
|
+ task.setMemberOnly(1);
|
|
|
+ task.setFrequency(item.getFrequency() != null ? item.getFrequency() : "daily");
|
|
|
+ task.setDuration(item.getDuration());
|
|
|
+ task.setDeadline(deadline);
|
|
|
+ task.setCreatedAt(now);
|
|
|
+ task.setUpdatedAt(now);
|
|
|
+ taskMapper.insert(task);
|
|
|
+ count++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("方案统一生成任务: planId={}, familyId={}, taskCount={}", planId, plan.getFamilyId(), count);
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 当天 23:59:59 截止(与 TaskService.getTodayTasks 区间对齐) */
|
|
|
+ private Date buildTodayDeadline() {
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.set(Calendar.HOUR_OF_DAY, 23);
|
|
|
+ cal.set(Calendar.MINUTE, 59);
|
|
|
+ cal.set(Calendar.SECOND, 59);
|
|
|
+ cal.set(Calendar.MILLISECOND, 0);
|
|
|
+ return cal.getTime();
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 获取计划详情
|
|
|
*/
|