|
|
@@ -98,70 +98,53 @@ public class TaskService {
|
|
|
calendar.add(Calendar.DAY_OF_MONTH, 1);
|
|
|
Date endOfDay = calendar.getTime();
|
|
|
|
|
|
- return taskMapper.selectList(new LambdaQueryWrapper<Task>()
|
|
|
- .eq(Task::getChildId, childId)
|
|
|
- .ge(Task::getDeadline, startOfDay)
|
|
|
- .lt(Task::getDeadline, endOfDay)
|
|
|
- .in(Task::getStatus, Arrays.asList("pending", "completed"))
|
|
|
- .orderByAsc(Task::getDeadline));
|
|
|
- }
|
|
|
+return taskMapper.selectList(new LambdaQueryWrapper<Task>()
|
|
|
+.eq(Task::getChildId, childId)
|
|
|
+.ge(Task::getDeadline, startOfDay)
|
|
|
+.lt(Task::getDeadline, endOfDay)
|
|
|
+.in(Task::getStatus, Arrays.asList("pending", "completed"))
|
|
|
+.orderByAsc(Task::getDeadline));
|
|
|
+}
|
|
|
|
|
|
- @Transactional
|
|
|
- public Map<String, Object> completeTask(Long taskId, Long childId, String photoUrl) {
|
|
|
- Task task = taskMapper.selectById(taskId);
|
|
|
- if (task == null || !task.getChildId().equals(childId)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
+@Transactional
|
|
|
+public Map<String, Object> completeTask(Long taskId, Long childId, String photoUrl) {
|
|
|
+Task task = taskMapper.selectById(taskId);
|
|
|
+if (task == null || !task.getChildId().equals(childId)) {
|
|
|
+return null;
|
|
|
+}
|
|
|
|
|
|
- if ("completed".equals(task.getStatus())) {
|
|
|
- return null; // 任务已完成,不能重复提交
|
|
|
- }
|
|
|
+if ("completed".equals(task.getStatus())) {
|
|
|
+return null; // 任务已完成,不能重复提交
|
|
|
+}
|
|
|
|
|
|
- Date now = new Date();
|
|
|
- int pointsEarned = task.getPoints();
|
|
|
- boolean isEarly = now.before(task.getDeadline());
|
|
|
- boolean isLateGrace = false;
|
|
|
+Date now = new Date();
|
|
|
+int 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;
|
|
|
- }
|
|
|
+// 检查是否在宽限期内
|
|
|
+if (!isEarly) {
|
|
|
+long diffMinutes = (now.getTime() - task.getDeadline().getTime()) / (1000 * 60);
|
|
|
+isLateGrace = diffMinutes <= LATE_GRACE_MINUTES;
|
|
|
+}
|
|
|
|
|
|
- Child child = childMapper.selectById(childId);
|
|
|
-
|
|
|
- // 计算积分
|
|
|
- if (isEarly || isLateGrace) {
|
|
|
- // 提前完成或宽限期内完成,获得基础分 + 提前奖励
|
|
|
- pointsEarned += EARLY_BONUS;
|
|
|
- } else {
|
|
|
+Child child = childMapper.selectById(childId);
|
|
|
+
|
|
|
+// 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 {
|
|
|
// 超时,检查是否开启扣分
|
|
|
- if (child.getPenaltyEnabled() != null && child.getPenaltyEnabled() == 1) {
|
|
|
- // 检查今日已扣分数
|
|
|
- Calendar cal = Calendar.getInstance();
|
|
|
- cal.set(Calendar.HOUR_OF_DAY, 0);
|
|
|
- cal.set(Calendar.MINUTE, 0);
|
|
|
- cal.set(Calendar.SECOND, 0);
|
|
|
- Date startOfDay = cal.getTime();
|
|
|
-
|
|
|
- Integer todayPenalty = pointsLogMapper.selectSum(
|
|
|
- new LambdaQueryWrapper<PointsLog>()
|
|
|
- .eq(PointsLog::getChildId, childId)
|
|
|
- .eq(PointsLog::getType, "penalty")
|
|
|
- .ge(PointsLog::getCreatedAt, startOfDay)
|
|
|
- );
|
|
|
-
|
|
|
- int todayPenaltyTotal = todayPenalty != null ? Math.abs(todayPenalty) : 0;
|
|
|
- if (todayPenaltyTotal < PENALTY_MAX_DAILY) {
|
|
|
- int penalty = Math.min(task.getPoints(), PENALTY_MAX_DAILY - todayPenaltyTotal);
|
|
|
- pointsEarned = -penalty;
|
|
|
- } else {
|
|
|
- pointsEarned = 0; // 达到每日扣分上限
|
|
|
- }
|
|
|
- } else {
|
|
|
- pointsEarned = 0; // 扣分未开启
|
|
|
- }
|
|
|
+ pointsEarned = calculateLatePenalty(task, child);
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
// 更新任务状态
|
|
|
task.setStatus("completed");
|
|
|
@@ -190,36 +173,44 @@ public class TaskService {
|
|
|
child.setUpdatedAt(new Date());
|
|
|
childMapper.updateById(child);
|
|
|
|
|
|
- // 检查并发放打卡里程碑奖励
|
|
|
- int milestoneReward = streakService.checkAndAwardMilestone(childId, child.getStreakDays());
|
|
|
- if (milestoneReward > 0) {
|
|
|
- pointsEarned += milestoneReward;
|
|
|
- log.info("孩子{}获得打卡里程碑奖励{}积分", childId, milestoneReward);
|
|
|
- }
|
|
|
+// 检查并发放打卡里程碑奖励
|
|
|
+int milestoneReward = streakService.checkAndAwardMilestone(childId, child.getStreakDays());
|
|
|
+if (milestoneReward > 0) {
|
|
|
+pointsEarned += milestoneReward;
|
|
|
+log.info("孩子{}获得打卡里程碑奖励{}积分", childId, milestoneReward);
|
|
|
+}
|
|
|
|
|
|
- // 记录积分流水
|
|
|
- PointsLog log = new PointsLog();
|
|
|
- log.setChildId(childId);
|
|
|
- log.setTaskId(taskId);
|
|
|
- log.setAmount(pointsEarned);
|
|
|
- if (pointsEarned > 0) {
|
|
|
- log.setType(isEarly ? "bonus" : "earn");
|
|
|
- } else if (pointsEarned < 0) {
|
|
|
- log.setType("penalty");
|
|
|
- } else {
|
|
|
- log.setType("earn");
|
|
|
- }
|
|
|
- log.setDescription("完成任务: " + task.getTitle());
|
|
|
- log.setCreatedAt(new Date());
|
|
|
- pointsLogMapper.insert(log);
|
|
|
+// 记录积分流水
|
|
|
+PointsLog pointsLog = new PointsLog();
|
|
|
+pointsLog.setChildId(childId);
|
|
|
+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());
|
|
|
+}
|
|
|
|
|
|
- Map<String, Object> result = new HashMap<>();
|
|
|
- result.put("pointsEarned", pointsEarned);
|
|
|
- result.put("newBalance", child.getTotalPoints());
|
|
|
- result.put("needReview", task.getNeedReview() == 1);
|
|
|
-
|
|
|
- return result;
|
|
|
- }
|
|
|
+pointsLogMapper.insert(pointsLog);
|
|
|
+
|
|
|
+Map<String, Object> result = new HashMap<>();
|
|
|
+result.put("pointsEarned", pointsEarned);
|
|
|
+result.put("newBalance", child.getTotalPoints());
|
|
|
+result.put("needReview", task.getNeedReview() == 1);
|
|
|
+
|
|
|
+return result;
|
|
|
+}
|
|
|
|
|
|
public boolean reviewTask(Long taskId, TaskReviewDTO dto) {
|
|
|
Task task = taskMapper.selectById(taskId);
|
|
|
@@ -414,10 +405,121 @@ public class TaskService {
|
|
|
.eq(Task::getChildId, childId)
|
|
|
.orderByDesc(Task::getCreatedAt);
|
|
|
|
|
|
- if (status != null && !status.isEmpty()) {
|
|
|
- wrapper.eq(Task::getStatus, status);
|
|
|
- }
|
|
|
-
|
|
|
- return taskMapper.selectPage(pageParam, wrapper);
|
|
|
- }
|
|
|
+if (status != null && !status.isEmpty()) {
|
|
|
+wrapper.eq(Task::getStatus, status);
|
|
|
+}
|
|
|
+
|
|
|
+return taskMapper.selectPage(pageParam, wrapper);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+* POINT-002: 根据自定义规则计算超额积分
|
|
|
+* bonusRule JSON格式: {
|
|
|
+* "earlyBonus": 1, // 提前完成额外积分
|
|
|
+* "earlyMinutes": 30, // 提前多少分钟才算提前(可选)
|
|
|
+* "qualityBonus": 2, // 超质完成额外积分(家长评分)
|
|
|
+* "qualityThreshold": 90 // 质量评分阈值(可选)
|
|
|
+* }
|
|
|
+*/
|
|
|
+private int calculatePointsWithBonusRule(Task task, Date completedAt, boolean isEarly, boolean isLateGrace, Child child) {
|
|
|
+int points = task.getPoints();
|
|
|
+
|
|
|
+try {
|
|
|
+// 解析JSON规则
|
|
|
+String json = task.getBonusRule();
|
|
|
+if (json == null || json.isEmpty()) {
|
|
|
+return isEarly || isLateGrace ? points + EARLY_BONUS : calculateLatePenalty(task, child, child.getId());
|
|
|
+}
|
|
|
+
|
|
|
+// 简单JSON解析(避免引入额外依赖)
|
|
|
+Integer earlyBonus = extractJsonValue(json, "earlyBonus");
|
|
|
+Integer earlyMinutes = extractJsonValue(json, "earlyMinutes");
|
|
|
+Integer qualityBonus = extractJsonValue(json, "qualityBonus");
|
|
|
+
|
|
|
+// 提前完成奖励
|
|
|
+if (isEarly || isLateGrace) {
|
|
|
+if (earlyBonus != null) {
|
|
|
+points += earlyBonus;
|
|
|
+} else {
|
|
|
+points += EARLY_BONUS; // 默认+1
|
|
|
+}
|
|
|
+
|
|
|
+// 如果设置了提前分钟数,检查是否满足条件
|
|
|
+if (earlyMinutes != null && task.getDeadline() != null) {
|
|
|
+long diffMinutes = (task.getDeadline().getTime() - completedAt.getTime()) / (1000 * 60);
|
|
|
+if (diffMinutes < earlyMinutes) {
|
|
|
+// 不满足提前分钟数,只给基础分
|
|
|
+points = task.getPoints();
|
|
|
+}
|
|
|
+}
|
|
|
+} else {
|
|
|
+// 超时,计算扣分
|
|
|
+points = calculateLatePenalty(task, child, child.getId());
|
|
|
+}
|
|
|
+
|
|
|
+// 超质完成奖励(需要家长后续评分,这里先不处理)
|
|
|
+// qualityBonus 会在家长审核时根据评分添加
|
|
|
+
|
|
|
+} catch (Exception e) {
|
|
|
+log.warn("解析超额积分规则失败: {}", e.getMessage());
|
|
|
+// 解析失败,使用默认规则
|
|
|
+if (isEarly || isLateGrace) {
|
|
|
+points += EARLY_BONUS;
|
|
|
+} else {
|
|
|
+points = calculateLatePenalty(task, child, child.getId());
|
|
|
+}
|
|
|
+}
|
|
|
+
|
|
|
+return points;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+* 从简单JSON字符串中提取整数值
|
|
|
+*/
|
|
|
+private Integer extractJsonValue(String json, String key) {
|
|
|
+try {
|
|
|
+String pattern = "\"" + key + "\"\\s*:\\s*(\\d+)";
|
|
|
+java.util.regex.Pattern p = java.util.regex.Pattern.compile(pattern);
|
|
|
+java.util.regex.Matcher m = p.matcher(json);
|
|
|
+if (m.find()) {
|
|
|
+return Integer.parseInt(m.group(1));
|
|
|
+}
|
|
|
+} catch (Exception e) {
|
|
|
+log.warn("提取JSON值失败: key={}", key);
|
|
|
+}
|
|
|
+return null;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+* 计算超时扣分
|
|
|
+*/
|
|
|
+private int calculateLatePenalty(Task task, Child child, Long childId) {
|
|
|
+int points = task.getPoints();
|
|
|
+
|
|
|
+if (child.getPenaltyEnabled() != null && child.getPenaltyEnabled() == 1) {
|
|
|
+// 检查今日已扣分数
|
|
|
+Calendar cal = Calendar.getInstance();
|
|
|
+cal.set(Calendar.HOUR_OF_DAY, 0);
|
|
|
+cal.set(Calendar.MINUTE, 0);
|
|
|
+cal.set(Calendar.SECOND, 0);
|
|
|
+Date startOfDay = cal.getTime();
|
|
|
+
|
|
|
+Integer todayPenalty = pointsLogMapper.selectSum(
|
|
|
+new LambdaQueryWrapper<PointsLog>()
|
|
|
+.eq(PointsLog::getChildId, childId)
|
|
|
+.eq(PointsLog::getType, "penalty")
|
|
|
+.ge(PointsLog::getCreatedAt, startOfDay)
|
|
|
+);
|
|
|
+
|
|
|
+int todayPenaltyTotal = todayPenalty != null ? Math.abs(todayPenalty) : 0;
|
|
|
+if (todayPenaltyTotal < PENALTY_MAX_DAILY) {
|
|
|
+int penalty = Math.min(task.getPoints(), PENALTY_MAX_DAILY - todayPenaltyTotal);
|
|
|
+return -penalty;
|
|
|
+} else {
|
|
|
+return 0; // 达到每日扣分上限
|
|
|
+}
|
|
|
+} else {
|
|
|
+return 0; // 扣分未开启
|
|
|
+}
|
|
|
+}
|
|
|
}
|