|
@@ -321,7 +321,7 @@ public List<Task> getTodayTasks(Long memberId, String dimensionCode) {
|
|
|
.eq(Task::getFamilyMemberId, memberId)
|
|
.eq(Task::getFamilyMemberId, memberId)
|
|
|
.ge(Task::getDeadline, startOfDay)
|
|
.ge(Task::getDeadline, startOfDay)
|
|
|
.lt(Task::getDeadline, endOfDay)
|
|
.lt(Task::getDeadline, endOfDay)
|
|
|
- .in(Task::getStatus, Arrays.asList("pending", "completed", "locked"))
|
|
|
|
|
|
|
+ .in(Task::getStatus, Arrays.asList("pending", "completed", "locked", "pending_review"))
|
|
|
.orderByAsc(Task::getDeadline);
|
|
.orderByAsc(Task::getDeadline);
|
|
|
|
|
|
|
|
if (dimensionCode != null && !dimensionCode.isEmpty()) {
|
|
if (dimensionCode != null && !dimensionCode.isEmpty()) {
|
|
@@ -386,7 +386,7 @@ public List<Task> getTodayTasks(Long memberId, String dimensionCode) {
|
|
|
.eq(Task::getCategory, category)
|
|
.eq(Task::getCategory, category)
|
|
|
.ge(Task::getDeadline, startOfDay)
|
|
.ge(Task::getDeadline, startOfDay)
|
|
|
.lt(Task::getDeadline, endOfDay)
|
|
.lt(Task::getDeadline, endOfDay)
|
|
|
- .in(Task::getStatus, Arrays.asList("pending", "completed"))
|
|
|
|
|
|
|
+ .in(Task::getStatus, Arrays.asList("pending", "completed", "pending_review"))
|
|
|
.orderByAsc(Task::getDeadline)));
|
|
.orderByAsc(Task::getDeadline)));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -505,8 +505,8 @@ public List<Task> getTodayTasks(Long memberId, String dimensionCode) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if ("completed".equals(task.getStatus())) {
|
|
|
|
|
- return null; // 任务已完成,不能重复提交
|
|
|
|
|
|
|
+ if ("completed".equals(task.getStatus()) || "pending_review".equals(task.getStatus())) {
|
|
|
|
|
+ return null; // 任务已完成或已提交待审核,不能重复提交
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// TASK-SYS-248: 前置任务校验——前置未完成则拒绝
|
|
// TASK-SYS-248: 前置任务校验——前置未完成则拒绝
|
|
@@ -554,200 +554,57 @@ public List<Task> getTodayTasks(Long memberId, String dimensionCode) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Date now = new Date();
|
|
Date now = new Date();
|
|
|
- int pointsEarned;
|
|
|
|
|
|
|
|
|
|
// 检查是否为小游戏任务
|
|
// 检查是否为小游戏任务
|
|
|
if ("小游戏类".equals(task.getCategory()) && task.getMinigameCode() != null) {
|
|
if ("小游戏类".equals(task.getCategory()) && task.getMinigameCode() != null) {
|
|
|
- // 小游戏任务不通过此接口完成,需要通过小游戏界面完成
|
|
|
|
|
- // 这里只返回null,实际完成逻辑在completeMinigameTask方法中
|
|
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- pointsEarned = task.getPoints();
|
|
|
|
|
-boolean isEarly = now.before(task.getDeadline());
|
|
|
|
|
-boolean isLateGrace = false;
|
|
|
|
|
-
|
|
|
|
|
-// 检查是否在宽限期内
|
|
|
|
|
-if (!isEarly) {
|
|
|
|
|
-long diffMinutes = (now.getTime() - task.getDeadline().getTime()) / (1000 * 60);
|
|
|
|
|
-isLateGrace = diffMinutes <= LATE_GRACE_MINUTES;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-FamilyMember child = familyMemberMapper.selectById(memberId);
|
|
|
|
|
-
|
|
|
|
|
-// POINT-002: 计算积分(支持自定义超额规则)
|
|
|
|
|
-String bonusRuleJson = task.getBonusRule();
|
|
|
|
|
-if (bonusRuleJson != null && !bonusRuleJson.isEmpty()) {
|
|
|
|
|
-// 解析自定义超额积分规则
|
|
|
|
|
-pointsEarned = calculatePointsWithBonusRule(task, now, isEarly, isLateGrace, child);
|
|
|
|
|
-} else {
|
|
|
|
|
-// 使用默认规则
|
|
|
|
|
-if (isEarly || isLateGrace) {
|
|
|
|
|
-// 提前完成或宽限期内完成,获得基础分 + 提前奖励
|
|
|
|
|
-pointsEarned += EARLY_BONUS;
|
|
|
|
|
-} else {
|
|
|
|
|
- // 超时,检查是否开启扣分
|
|
|
|
|
- pointsEarned = calculateLatePenalty(task, child, memberId);
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
- // 更新任务状态
|
|
|
|
|
- task.setStatus("completed");
|
|
|
|
|
- task.setCompletedAt(now);
|
|
|
|
|
- task.setUpdatedAt(new Date());
|
|
|
|
|
- taskMapper.updateById(task);
|
|
|
|
|
-
|
|
|
|
|
- // 更新孩子积分
|
|
|
|
|
- child.setTotalPoints(child.getTotalPoints() + pointsEarned);
|
|
|
|
|
-
|
|
|
|
|
- // 更新连续打卡
|
|
|
|
|
- Date lastTaskDate = child.getLastTaskDate();
|
|
|
|
|
- Date today = new Date();
|
|
|
|
|
- Calendar cal = Calendar.getInstance();
|
|
|
|
|
- cal.add(Calendar.DAY_OF_MONTH, -1);
|
|
|
|
|
- Date yesterday = cal.getTime();
|
|
|
|
|
-
|
|
|
|
|
- if (lastTaskDate == null || lastTaskDate.before(yesterday)) {
|
|
|
|
|
- child.setStreakDays(1); // 重新开始
|
|
|
|
|
- } else if (lastTaskDate != null && isSameDay(lastTaskDate, today)) {
|
|
|
|
|
- // 今天已经完成过任务,不增加天数
|
|
|
|
|
- } else {
|
|
|
|
|
- child.setStreakDays(child.getStreakDays() + 1);
|
|
|
|
|
- }
|
|
|
|
|
- child.setLastTaskDate(today);
|
|
|
|
|
- child.setUpdatedAt(new Date());
|
|
|
|
|
- familyMemberMapper.updateById(child);
|
|
|
|
|
-
|
|
|
|
|
-// 检查并发放打卡里程碑奖励
|
|
|
|
|
-int milestoneReward = streakService.checkAndAwardMilestone(memberId, child.getStreakDays());
|
|
|
|
|
-if (milestoneReward > 0) {
|
|
|
|
|
-pointsEarned += milestoneReward;
|
|
|
|
|
-log.info("孩子{}获得打卡里程碑奖励{}积分", memberId, milestoneReward);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 记录积分流水
|
|
|
|
|
-PointsLog pointsLog = new PointsLog();
|
|
|
|
|
-pointsLog.setChildId(memberId);
|
|
|
|
|
-pointsLog.setTaskId(taskId);
|
|
|
|
|
-pointsLog.setAmount(pointsEarned);
|
|
|
|
|
-if (pointsEarned > 0) {
|
|
|
|
|
-pointsLog.setType(isEarly ? "bonus" : "earn");
|
|
|
|
|
-} else if (pointsEarned < 0) {
|
|
|
|
|
-pointsLog.setType("penalty");
|
|
|
|
|
-} else {
|
|
|
|
|
-pointsLog.setType("earn");
|
|
|
|
|
-}
|
|
|
|
|
-pointsLog.setDescription("完成任务: " + task.getTitle());
|
|
|
|
|
-pointsLog.setCreatedAt(new Date());
|
|
|
|
|
-
|
|
|
|
|
-// POINT-009: 设置积分过期时间
|
|
|
|
|
-if (pointsEarned > 0 && task.getPointsExpireDays() != null && task.getPointsExpireDays() > 0) {
|
|
|
|
|
-Calendar expireCal = Calendar.getInstance();
|
|
|
|
|
-expireCal.add(Calendar.DAY_OF_MONTH, task.getPointsExpireDays());
|
|
|
|
|
-pointsLog.setExpireAt(expireCal.getTime());
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
- pointsLogMapper.insert(pointsLog);
|
|
|
|
|
-
|
|
|
|
|
- // 五维能量发放(与积分并行,独立系统,失败不影响积分)
|
|
|
|
|
- try {
|
|
|
|
|
- int energyAmount = Math.abs(pointsEarned);
|
|
|
|
|
- if (energyAmount > 0) {
|
|
|
|
|
- energyService.awardEnergy(memberId, "task", taskId, energyAmount,
|
|
|
|
|
- "完成任务: " + task.getTitle(), null);
|
|
|
|
|
- }
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
-log.warn("能量发放失败(不影响积分): memberId={}, taskId={}, error={}",
|
|
|
|
|
- memberId, taskId, e.getMessage());
|
|
|
|
|
|
|
+ // 需审核的任务:提交后进入待审核,审核通过后再发放积分
|
|
|
|
|
+ if (task.getNeedReview() != null && task.getNeedReview() == 1) {
|
|
|
|
|
+ return submitTaskForReview(task, memberId, now, content, contentType);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- try { growthTaskService.updateProgress(child.getUserId(), "DAILY_TASK", 1); } catch (Exception e) { log.warn("成长任务进度更新失败: memberId={}, error={}", memberId, e.getMessage()); }
|
|
|
|
|
-
|
|
|
|
|
- // TASK-SYS-248: 更新任务执行进度(两阶段 execution 收尾 + 填写内容落库)
|
|
|
|
|
- try {
|
|
|
|
|
- TaskExecution activeExec = null;
|
|
|
|
|
- if (task.getStartRequired() != null && task.getStartRequired() == 1) {
|
|
|
|
|
- activeExec = taskExecutionMapper.selectOne(
|
|
|
|
|
- new LambdaQueryWrapper<TaskExecution>()
|
|
|
|
|
- .eq(TaskExecution::getTaskId, taskId)
|
|
|
|
|
- .eq(TaskExecution::getMemberId, memberId)
|
|
|
|
|
- .eq(TaskExecution::getStatus, "started")
|
|
|
|
|
- .last("LIMIT 1"));
|
|
|
|
|
- }
|
|
|
|
|
- if (activeExec != null) {
|
|
|
|
|
- activeExec.setStatus("finished");
|
|
|
|
|
- activeExec.setFinishedAt(now);
|
|
|
|
|
- activeExec.setContent(content);
|
|
|
|
|
- activeExec.setContentType(contentType);
|
|
|
|
|
- taskExecutionMapper.updateById(activeExec);
|
|
|
|
|
- } else if (content != null && !content.trim().isEmpty()) {
|
|
|
|
|
- // 非两阶段任务的填写内容,落一条 finished 记录
|
|
|
|
|
- TaskExecution exec = new TaskExecution();
|
|
|
|
|
- exec.setTaskId(taskId);
|
|
|
|
|
- exec.setMemberId(memberId);
|
|
|
|
|
- exec.setStatus("finished");
|
|
|
|
|
- exec.setStartedAt(now);
|
|
|
|
|
- exec.setFinishedAt(now);
|
|
|
|
|
- exec.setContent(content);
|
|
|
|
|
- exec.setContentType(contentType);
|
|
|
|
|
- exec.setCreatedAt(now);
|
|
|
|
|
- taskExecutionMapper.insert(exec);
|
|
|
|
|
- }
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.warn("任务执行进度记录失败(不影响完成): taskId={}, error={}", taskId, e.getMessage());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // TASK-SYS-248: 前置链解锁——本任务完成后解锁依赖它的后置任务
|
|
|
|
|
- try {
|
|
|
|
|
- List<Task> nextTasks = taskMapper.selectList(
|
|
|
|
|
- new LambdaQueryWrapper<Task>()
|
|
|
|
|
- .eq(Task::getPrerequisiteTaskId, taskId));
|
|
|
|
|
- for (Task next : nextTasks) {
|
|
|
|
|
- if ("locked".equals(next.getStatus())) {
|
|
|
|
|
- next.setStatus("pending");
|
|
|
|
|
- next.setUpdatedAt(new Date());
|
|
|
|
|
- taskMapper.updateById(next);
|
|
|
|
|
- log.info("前置任务{}完成,解锁后置任务{}", taskId, next.getId());
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.warn("后置任务解锁失败: taskId={}, error={}", taskId, e.getMessage());
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ recordTaskExecution(task, memberId, now, content, contentType);
|
|
|
|
|
+ int pointsEarned = awardTaskCompletionRewards(task, memberId, now);
|
|
|
|
|
+ FamilyMember child = familyMemberMapper.selectById(memberId);
|
|
|
|
|
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
result.put("pointsEarned", pointsEarned);
|
|
result.put("pointsEarned", pointsEarned);
|
|
|
-result.put("newBalance", child.getTotalPoints());
|
|
|
|
|
-result.put("needReview", task.getNeedReview() == 1);
|
|
|
|
|
-
|
|
|
|
|
-return result;
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ result.put("newBalance", child != null ? child.getTotalPoints() : 0);
|
|
|
|
|
+ result.put("needReview", false);
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ @Transactional
|
|
|
public boolean reviewTask(Long taskId, TaskReviewDTO dto) {
|
|
public boolean reviewTask(Long taskId, TaskReviewDTO dto) {
|
|
|
Task task = taskMapper.selectById(taskId);
|
|
Task task = taskMapper.selectById(taskId);
|
|
|
if (task == null) {
|
|
if (task == null) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 兼容两种审核字段:approved 和 result
|
|
|
|
|
|
|
+ if (!"pending_review".equals(task.getStatus())) {
|
|
|
|
|
+ return "completed".equals(task.getStatus());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
boolean isApproved;
|
|
boolean isApproved;
|
|
|
if (dto.getApproved() != null) {
|
|
if (dto.getApproved() != null) {
|
|
|
- // 使用 approved 字段(推荐)
|
|
|
|
|
isApproved = dto.getApproved();
|
|
isApproved = dto.getApproved();
|
|
|
} else if ("pass".equals(dto.getResult())) {
|
|
} else if ("pass".equals(dto.getResult())) {
|
|
|
- // 兼容 result 字段
|
|
|
|
|
isApproved = true;
|
|
isApproved = true;
|
|
|
} else {
|
|
} else {
|
|
|
isApproved = false;
|
|
isApproved = false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 更新任务状态
|
|
|
|
|
if (isApproved) {
|
|
if (isApproved) {
|
|
|
- task.setStatus("completed");
|
|
|
|
|
|
|
+ Date completionTime = task.getCompletedAt() != null ? task.getCompletedAt() : new Date();
|
|
|
|
|
+ awardTaskCompletionRewards(task, task.getFamilyMemberId(), completionTime);
|
|
|
} else {
|
|
} else {
|
|
|
- task.setStatus("pending"); // 审核不通过,任务重置
|
|
|
|
|
|
|
+ task.setStatus("pending");
|
|
|
|
|
+ task.setCompletedAt(null);
|
|
|
|
|
+ task.setUpdatedAt(new Date());
|
|
|
|
|
+ taskMapper.updateById(task);
|
|
|
}
|
|
}
|
|
|
- task.setUpdatedAt(new Date());
|
|
|
|
|
- taskMapper.updateById(task);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
@@ -793,7 +650,7 @@ return result;
|
|
|
LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
|
|
LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
|
|
|
wrapper.eq(Task::getFamilyId, user.getFamilyId())
|
|
wrapper.eq(Task::getFamilyId, user.getFamilyId())
|
|
|
.eq(Task::getNeedReview, 1)
|
|
.eq(Task::getNeedReview, 1)
|
|
|
- .eq(Task::getStatus, "completed")
|
|
|
|
|
|
|
+ .eq(Task::getStatus, "pending_review")
|
|
|
.orderByDesc(Task::getCompletedAt);
|
|
.orderByDesc(Task::getCompletedAt);
|
|
|
|
|
|
|
|
SortUtil.applySort(wrapper);
|
|
SortUtil.applySort(wrapper);
|
|
@@ -904,6 +761,186 @@ return result;
|
|
|
.set(Task::getUpdatedAt, new Date()));
|
|
.set(Task::getUpdatedAt, new Date()));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private Map<String, Object> submitTaskForReview(Task task, Long memberId, Date now, String content, String contentType) {
|
|
|
|
|
+ task.setStatus("pending_review");
|
|
|
|
|
+ task.setCompletedAt(now);
|
|
|
|
|
+ task.setUpdatedAt(now);
|
|
|
|
|
+ taskMapper.updateById(task);
|
|
|
|
|
+
|
|
|
|
|
+ recordTaskExecution(task, memberId, now, content, contentType);
|
|
|
|
|
+
|
|
|
|
|
+ FamilyMember child = familyMemberMapper.selectById(memberId);
|
|
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
|
|
+ result.put("pointsEarned", 0);
|
|
|
|
|
+ result.put("newBalance", child != null ? child.getTotalPoints() : 0);
|
|
|
|
|
+ result.put("needReview", true);
|
|
|
|
|
+ result.put("message", "已提交,待审核");
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int awardTaskCompletionRewards(Task task, Long memberId, Date completionTime) {
|
|
|
|
|
+ Long taskId = task.getId();
|
|
|
|
|
+ int pointsEarned = task.getPoints() != null ? task.getPoints() : 0;
|
|
|
|
|
+ boolean isEarly = task.getDeadline() != null && completionTime.before(task.getDeadline());
|
|
|
|
|
+ boolean isLateGrace = false;
|
|
|
|
|
+
|
|
|
|
|
+ if (!isEarly && task.getDeadline() != null) {
|
|
|
|
|
+ long diffMinutes = (completionTime.getTime() - task.getDeadline().getTime()) / (1000 * 60);
|
|
|
|
|
+ isLateGrace = diffMinutes <= LATE_GRACE_MINUTES;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ FamilyMember child = familyMemberMapper.selectById(memberId);
|
|
|
|
|
+ if (child == null) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String bonusRuleJson = task.getBonusRule();
|
|
|
|
|
+ if (bonusRuleJson != null && !bonusRuleJson.isEmpty()) {
|
|
|
|
|
+ pointsEarned = calculatePointsWithBonusRule(task, completionTime, isEarly, isLateGrace, child);
|
|
|
|
|
+ } else if (isEarly || isLateGrace) {
|
|
|
|
|
+ pointsEarned += EARLY_BONUS;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ pointsEarned = calculateLatePenalty(task, child, memberId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ task.setStatus("completed");
|
|
|
|
|
+ task.setCompletedAt(completionTime);
|
|
|
|
|
+ task.setUpdatedAt(new Date());
|
|
|
|
|
+ taskMapper.updateById(task);
|
|
|
|
|
+
|
|
|
|
|
+ child.setTotalPoints(child.getTotalPoints() + pointsEarned);
|
|
|
|
|
+
|
|
|
|
|
+ Date lastTaskDate = child.getLastTaskDate();
|
|
|
|
|
+ Date today = new Date();
|
|
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
|
|
+ cal.add(Calendar.DAY_OF_MONTH, -1);
|
|
|
|
|
+ Date yesterday = cal.getTime();
|
|
|
|
|
+
|
|
|
|
|
+ if (lastTaskDate == null || lastTaskDate.before(yesterday)) {
|
|
|
|
|
+ child.setStreakDays(1);
|
|
|
|
|
+ } else if (!isSameDay(lastTaskDate, today)) {
|
|
|
|
|
+ child.setStreakDays(child.getStreakDays() + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ child.setLastTaskDate(today);
|
|
|
|
|
+ child.setUpdatedAt(new Date());
|
|
|
|
|
+ familyMemberMapper.updateById(child);
|
|
|
|
|
+
|
|
|
|
|
+ int milestoneReward = streakService.checkAndAwardMilestone(memberId, child.getStreakDays());
|
|
|
|
|
+ if (milestoneReward > 0) {
|
|
|
|
|
+ pointsEarned += milestoneReward;
|
|
|
|
|
+ log.info("孩子{}获得打卡里程碑奖励{}积分", memberId, milestoneReward);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ PointsLog pointsLog = new PointsLog();
|
|
|
|
|
+ pointsLog.setChildId(memberId);
|
|
|
|
|
+ pointsLog.setTaskId(taskId);
|
|
|
|
|
+ pointsLog.setAmount(pointsEarned);
|
|
|
|
|
+ if (pointsEarned > 0) {
|
|
|
|
|
+ pointsLog.setType(isEarly ? "bonus" : "earn");
|
|
|
|
|
+ } else if (pointsEarned < 0) {
|
|
|
|
|
+ pointsLog.setType("penalty");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ pointsLog.setType("earn");
|
|
|
|
|
+ }
|
|
|
|
|
+ pointsLog.setDescription("完成任务: " + task.getTitle());
|
|
|
|
|
+ pointsLog.setCreatedAt(new Date());
|
|
|
|
|
+
|
|
|
|
|
+ if (pointsEarned > 0 && task.getPointsExpireDays() != null && task.getPointsExpireDays() > 0) {
|
|
|
|
|
+ Calendar expireCal = Calendar.getInstance();
|
|
|
|
|
+ expireCal.add(Calendar.DAY_OF_MONTH, task.getPointsExpireDays());
|
|
|
|
|
+ pointsLog.setExpireAt(expireCal.getTime());
|
|
|
|
|
+ }
|
|
|
|
|
+ pointsLogMapper.insert(pointsLog);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ int energyAmount = Math.abs(pointsEarned);
|
|
|
|
|
+ if (energyAmount > 0) {
|
|
|
|
|
+ energyService.awardEnergy(memberId, "task", taskId, energyAmount,
|
|
|
|
|
+ "完成任务: " + task.getTitle(), null);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("能量发放失败(不影响积分): memberId={}, taskId={}, error={}",
|
|
|
|
|
+ memberId, taskId, e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ growthTaskService.updateProgress(child.getUserId(), "DAILY_TASK", 1);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("成长任务进度更新失败: memberId={}, error={}", memberId, e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ unlockPrerequisiteTasks(taskId);
|
|
|
|
|
+ return pointsEarned;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void recordTaskExecution(Task task, Long memberId, Date now, String content, String contentType) {
|
|
|
|
|
+ Long taskId = task.getId();
|
|
|
|
|
+ try {
|
|
|
|
|
+ TaskExecution activeExec = null;
|
|
|
|
|
+ if (task.getStartRequired() != null && task.getStartRequired() == 1) {
|
|
|
|
|
+ activeExec = taskExecutionMapper.selectOne(
|
|
|
|
|
+ new LambdaQueryWrapper<TaskExecution>()
|
|
|
|
|
+ .eq(TaskExecution::getTaskId, taskId)
|
|
|
|
|
+ .eq(TaskExecution::getMemberId, memberId)
|
|
|
|
|
+ .eq(TaskExecution::getStatus, "started")
|
|
|
|
|
+ .last("LIMIT 1"));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (activeExec != null) {
|
|
|
|
|
+ activeExec.setStatus("finished");
|
|
|
|
|
+ activeExec.setFinishedAt(now);
|
|
|
|
|
+ activeExec.setContent(content);
|
|
|
|
|
+ activeExec.setContentType(contentType);
|
|
|
|
|
+ taskExecutionMapper.updateById(activeExec);
|
|
|
|
|
+ } else if (content != null && !content.trim().isEmpty()) {
|
|
|
|
|
+ TaskExecution exec = new TaskExecution();
|
|
|
|
|
+ exec.setTaskId(taskId);
|
|
|
|
|
+ exec.setMemberId(memberId);
|
|
|
|
|
+ exec.setStatus("finished");
|
|
|
|
|
+ exec.setStartedAt(now);
|
|
|
|
|
+ exec.setFinishedAt(now);
|
|
|
|
|
+ exec.setContent(content);
|
|
|
|
|
+ exec.setContentType(contentType);
|
|
|
|
|
+ exec.setCreatedAt(now);
|
|
|
|
|
+ taskExecutionMapper.insert(exec);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("任务执行进度记录失败(不影响完成): taskId={}, error={}", taskId, e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void unlockPrerequisiteTasks(Long taskId) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<Task> nextTasks = taskMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<Task>()
|
|
|
|
|
+ .eq(Task::getPrerequisiteTaskId, taskId));
|
|
|
|
|
+ for (Task next : nextTasks) {
|
|
|
|
|
+ if ("locked".equals(next.getStatus())) {
|
|
|
|
|
+ next.setStatus("pending");
|
|
|
|
|
+ next.setUpdatedAt(new Date());
|
|
|
|
|
+ taskMapper.updateById(next);
|
|
|
|
|
+ log.info("前置任务{}完成,解锁后置任务{}", taskId, next.getId());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("后置任务解锁失败: taskId={}, error={}", taskId, e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 管理员/平台查询待审核任务
|
|
|
|
|
+ */
|
|
|
|
|
+ public List<Task> getAllPendingReviewTasks(Long familyId) {
|
|
|
|
|
+ LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<Task>()
|
|
|
|
|
+ .eq(Task::getNeedReview, 1)
|
|
|
|
|
+ .eq(Task::getStatus, "pending_review")
|
|
|
|
|
+ .orderByDesc(Task::getCompletedAt);
|
|
|
|
|
+ if (familyId != null) {
|
|
|
|
|
+ wrapper.eq(Task::getFamilyId, familyId);
|
|
|
|
|
+ }
|
|
|
|
|
+ SortUtil.applySort(wrapper);
|
|
|
|
|
+ return taskMapper.selectList(wrapper);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private boolean isSameDay(Date date1, Date date2) {
|
|
private boolean isSameDay(Date date1, Date date2) {
|
|
|
Calendar cal1 = Calendar.getInstance();
|
|
Calendar cal1 = Calendar.getInstance();
|
|
|
cal1.setTime(date1);
|
|
cal1.setTime(date1);
|
|
@@ -920,7 +957,7 @@ return result;
|
|
|
return taskMapper.selectList(new LambdaQueryWrapper<Task>()
|
|
return taskMapper.selectList(new LambdaQueryWrapper<Task>()
|
|
|
.eq(Task::getFamilyMemberId, memberId)
|
|
.eq(Task::getFamilyMemberId, memberId)
|
|
|
.eq(Task::getNeedReview, 1)
|
|
.eq(Task::getNeedReview, 1)
|
|
|
- .eq(Task::getStatus, "completed")
|
|
|
|
|
|
|
+ .eq(Task::getStatus, "pending_review")
|
|
|
.orderByDesc(Task::getCompletedAt));
|
|
.orderByDesc(Task::getCompletedAt));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1151,8 +1188,8 @@ return 0; // 达到每日扣分上限
|
|
|
return taskMapper.selectList(new LambdaQueryWrapper<Task>()
|
|
return taskMapper.selectList(new LambdaQueryWrapper<Task>()
|
|
|
.eq(Task::getFamilyMemberId, familyMemberId)
|
|
.eq(Task::getFamilyMemberId, familyMemberId)
|
|
|
.eq(Task::getNeedReview, 1)
|
|
.eq(Task::getNeedReview, 1)
|
|
|
- .in(Task::getStatus, Arrays.asList("completed", "pending"))
|
|
|
|
|
- .orderByDesc(Task::getCreatedAt));
|
|
|
|
|
|
|
+ .eq(Task::getStatus, "pending_review")
|
|
|
|
|
+ .orderByDesc(Task::getCompletedAt));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|