package com.etotem.cfc.controller.guide; import javax.annotation.Resource; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.etotem.cfc.common.Result; import com.etotem.cfc.dto.TaskReviewDTO; import com.etotem.cfc.entity.FamilyMember; import com.etotem.cfc.entity.GuideFamily; import com.etotem.cfc.entity.Task; import com.etotem.cfc.entity.User; import com.etotem.cfc.mapper.FamilyMemberMapper; import com.etotem.cfc.mapper.TaskMapper; import com.etotem.cfc.mapper.UserMapper; import com.etotem.cfc.service.GuideFamilyAccessGuard; import com.etotem.cfc.service.GuideFamilyService; import com.etotem.cfc.service.MembershipService; import com.etotem.cfc.service.TaskService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.*; import java.time.LocalDate; import java.time.ZoneId; import java.util.*; import java.util.stream.Collectors; import com.etotem.cfc.util.ParamUtils; @Tag(name = "指导师-家庭任务管理", description = "指导师查看家庭任务、审核任务等接口") @RestController @RequestMapping("/api/guide/families") public class GuideFamilyTaskController { @Resource private TaskService taskService; @Resource private FamilyMemberMapper familyMemberMapper; @Resource private TaskMapper taskMapper; @Resource private UserMapper userMapper; @Resource private MembershipService membershipService; @Resource private GuideFamilyService guideFamilyService; @Resource private GuideFamilyAccessGuard guideFamilyAccessGuard; /** * 规划师解除家庭绑定(仅限解除属于自己的绑定) */ @Operation(summary = "规划师解除家庭绑定") @PostMapping("/unbind") public Result> unbindFamily(@RequestAttribute("userId") Long guideId, @RequestAttribute("role") String role, @RequestBody Map params) { if (!"teacher".equals(role)) { return Result.error("Access denied"); } Long id = ParamUtils.getLong(params.get("id")); if (id == null) { return Result.error("缺少绑定记录ID"); } try { GuideFamily gf = guideFamilyService.unbindByGuide(id, guideId); Map result = new HashMap<>(); result.put("familyId", gf.getFamilyId()); result.put("serviceType", gf.getServiceType()); return Result.success(result); } catch (RuntimeException e) { return Result.error(400, e.getMessage()); } } /** * 会员任务校验:task.memberOnly==1 且家庭非会员 → 拒绝(提示开通会员) */ private Result checkMemberOnly(Long taskId) { Task task = taskMapper.selectById(taskId); if (task == null) { return Result.error("任务不存在"); } if (task.getMemberOnly() != null && task.getMemberOnly() == 1) { com.etotem.cfc.dto.MembershipLevelDTO level = membershipService.getCurrentLevel(task.getFamilyId()); boolean isMember = level != null && !"FREE".equals(level.getLevelCode()); if (!isMember) { return Result.error("该任务是会员任务,请先开通会员后再操作"); } } return null; } /** * 获取指导师关联的家庭列表(带简要任务统计) */ @Operation(summary = "获取关联家庭列表") @ApiResponses({ @ApiResponse(responseCode = "200", description = "成功返回家庭列表"), @ApiResponse(responseCode = "403", description = "Access denied - 非教师"), @ApiResponse(responseCode = "401", description = "未登录") }) @PostMapping("/bound-families") public Result>> getBoundFamilies(@RequestAttribute("userId") Long guideId, @RequestAttribute("role") String role) { if (!"teacher".equals(role)) { return Result.error("Access denied"); } // 获取当前指导师信息 User guide = userMapper.selectById(guideId); if (guide == null || guide.getTeacherFamilyIds() == null || guide.getTeacherFamilyIds().isEmpty()) { return Result.success(new ArrayList<>()); } // 解析家庭ID列表(逗号分隔) List familyIds = Arrays.stream(guide.getTeacherFamilyIds().split(",")) .map(String::trim) .filter(s -> !s.isEmpty()) .map(Long::parseLong) .collect(Collectors.toList()); if (familyIds.isEmpty()) { return Result.success(new ArrayList<>()); } List> result = new ArrayList<>(); for (Long familyId : familyIds) { // 获取家庭的家长用户 List parents = userMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper() .eq("family_id", familyId) .eq("role", "parent") ); if (parents.isEmpty()) { continue; } User parent = parents.get(0); Map item = new HashMap<>(); item.put("familyId", familyId); item.put("familyName", "家庭" + familyId); item.put("parentName", parent.getNickname()); // 获取家庭孩子列表 List children = familyMemberMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper() .eq("family_id", familyId) ); item.put("childrenCount", children.size()); // 获取待审核任务数量 long pendingCount = 0; for (FamilyMember child : children) { List pendingTasks = taskService.getPendingReviewTasksForFamilyMember(child.getId()); pendingCount += pendingTasks.size(); } item.put("pendingReviewCount", pendingCount); result.add(item); } return Result.success(result); } /** * 获取指定家庭的所有孩子任务概览 */ @Operation(summary = "获取家庭任务概览") @ApiResponses({ @ApiResponse(responseCode = "200", description = "成功返回概览"), @ApiResponse(responseCode = "403", description = "Access denied"), @ApiResponse(responseCode = "401", description = "未登录") }) @PostMapping("/{familyId}/overview") public Result> getFamilyTaskOverview( @RequestAttribute("userId") Long guideId, @RequestAttribute("role") String role, @PathVariable Long familyId) { if (!"teacher".equals(role)) { return Result.error("Access denied"); } Result denied = requireBound(guideId, familyId); if (denied != null) return Result.error(denied.getCode(), denied.getMessage()); List children = familyMemberMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper() .eq("family_id", familyId) ); List> childTaskList = new ArrayList<>(); int totalPending = 0; int totalCompleted = 0; int totalOverdue = 0; for (FamilyMember child : children) { Map childInfo = new HashMap<>(); childInfo.put("memberId", child.getId()); childInfo.put("childName", child.getNickname()); childInfo.put("totalPoints", child.getTotalPoints()); childInfo.put("streakDays", child.getStreakDays()); // 获取今日任务 List todayTasks = taskService.getTodayTasks(child.getId()); childInfo.put("todayTaskCount", todayTasks.size()); childInfo.put("todayCompletedCount", todayTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count()); // 获取待审核任务 List pendingTasks = taskService.getPendingReviewTasksForFamilyMember(child.getId()); childInfo.put("pendingReviewCount", pendingTasks.size()); totalPending += pendingTasks.size(); // 统计已完成和超时 List allTasks = taskService.getAllTasksByFamilyMember(child.getId()); totalCompleted += allTasks.stream() .filter(t -> "completed".equals(t.getStatus())) .count(); totalOverdue += allTasks.stream() .filter(t -> "overdue".equals(t.getStatus())) .count(); childTaskList.add(childInfo); } Map result = new HashMap<>(); result.put("familyId", familyId); result.put("children", childTaskList); result.put("totalFamilyMemberren", children.size()); result.put("totalPendingReview", totalPending); result.put("totalCompleted", totalCompleted); result.put("totalOverdue", totalOverdue); return Result.success(result); } /** * 获取家庭孩子的任务列表 */ @Operation(summary = "获取孩子任务列表") @ApiResponses({ @ApiResponse(responseCode = "200", description = "成功返回分页任务"), @ApiResponse(responseCode = "403", description = "Access denied"), @ApiResponse(responseCode = "401", description = "未登录") }) @PostMapping("/{familyId}/children/{memberId}/tasks") public Result> getFamilyMemberTasks( @RequestAttribute("userId") Long guideId, @PathVariable Long familyId, @PathVariable Long memberId, @RequestAttribute("role") String role, @RequestBody(required = false) Map params) { if (!"teacher".equals(role)) { return Result.error("Access denied"); } Result denied = requireBound(guideId, familyId); if (denied != null) return Result.error(denied.getCode(), denied.getMessage()); Integer page = params != null && params.get("page") != null ? Integer.valueOf(params.get("page").toString()) : 1; Integer size = params != null && params.get("size") != null ? Integer.valueOf(params.get("size").toString()) : 10; String status = params != null && params.get("status") != null ? params.get("status").toString() : null; Page tasks = taskService.getTaskHistoryByFamilyMember(memberId, page, size, status); return Result.success(tasks); } /** * 获取家庭孩子的待审核任务 */ @Operation(summary = "获取待审核任务列表") @ApiResponses({ @ApiResponse(responseCode = "200", description = "成功返回待审核任务"), @ApiResponse(responseCode = "403", description = "Access denied"), @ApiResponse(responseCode = "401", description = "未登录") }) @PostMapping("/{familyId}/children/{memberId}/pending-review") public Result> getPendingReviewTasks( @RequestAttribute("userId") Long guideId, @PathVariable Long familyId, @PathVariable Long memberId, @RequestAttribute("role") String role) { if (!"teacher".equals(role)) { return Result.error("Access denied"); } Result denied = requireBound(guideId, familyId); if (denied != null) return Result.error(denied.getCode(), denied.getMessage()); List tasks = taskService.getPendingReviewTasksForFamilyMember(memberId); return Result.success(tasks); } /** * 审核任务(指导师) */ @Operation(summary = "审核任务") @ApiResponses({ @ApiResponse(responseCode = "200", description = "审核成功返回布尔值"), @ApiResponse(responseCode = "403", description = "Access denied"), @ApiResponse(responseCode = "401", description = "未登录") }) @PostMapping("/{familyId}/tasks/{taskId}/review") public Result reviewTask( @RequestAttribute("userId") Long guideId, @PathVariable Long familyId, @PathVariable Long taskId, @RequestAttribute("role") String role, @RequestBody TaskReviewDTO dto) { if (!"teacher".equals(role)) { return Result.error("Access denied"); } Result denied = requireBound(guideId, familyId); if (denied != null) return Result.error(denied.getCode(), denied.getMessage()); Result memberCheck = checkMemberOnly(taskId); if (memberCheck != null) { return Result.error(memberCheck.getMessage()); } boolean success = taskService.reviewTask(taskId, dto); return Result.success(success); } /** * 批量审核任务 */ @Operation(summary = "批量审核任务") @ApiResponses({ @ApiResponse(responseCode = "200", description = "批量审核成功返回统计"), @ApiResponse(responseCode = "403", description = "Access denied"), @ApiResponse(responseCode = "401", description = "未登录") }) @PostMapping("/{familyId}/tasks/batch-review") public Result> batchReviewTasks( @RequestAttribute("userId") Long guideId, @PathVariable Long familyId, @RequestAttribute("role") String role, @RequestBody Map params) { @SuppressWarnings("unchecked") List rawTaskIds = (List) params.get("taskIds"); List taskIds = new ArrayList<>(); if (rawTaskIds != null) { for (Object taskId : rawTaskIds) { taskIds.add(Long.valueOf(taskId.toString())); } } Boolean approved = params.get("approved") != null ? Boolean.valueOf(params.get("approved").toString()) : Boolean.FALSE; if (!"teacher".equals(role)) { return Result.error("Access denied"); } Result denied = requireBound(guideId, familyId); if (denied != null) return Result.error(denied.getCode(), denied.getMessage()); int successCount = 0; int failCount = 0; for (Long taskId : taskIds) { try { // 会员任务校验:审核通过会员任务需家庭为会员 Result memberCheck = checkMemberOnly(taskId); if (memberCheck != null) { failCount++; continue; } TaskReviewDTO dto = new TaskReviewDTO(); dto.setApproved(approved); dto.setComment(approved ? "指导师审核通过" : "指导师审核拒绝"); if (taskService.reviewTask(taskId, dto)) { successCount++; } else { failCount++; } } catch (Exception e) { failCount++; } } Map result = new HashMap<>(); result.put("successCount", successCount); result.put("failCount", failCount); return Result.success(result); } /** * 指导师修改任务 */ @Operation(summary = "修改任务") @ApiResponses({ @ApiResponse(responseCode = "200", description = "修改成功"), @ApiResponse(responseCode = "403", description = "Access denied"), @ApiResponse(responseCode = "404", description = "任务不存在") }) @PostMapping("/{familyId}/tasks/{taskId}") public Result updateTask( @RequestAttribute("userId") Long guideId, @PathVariable Long familyId, @PathVariable Long taskId, @RequestAttribute("role") String role, @RequestBody Map taskData) { if (!"teacher".equals(role)) { return Result.error("Access denied"); } Result denied = requireBound(guideId, familyId); if (denied != null) return Result.error(denied.getCode(), denied.getMessage()); Task task = taskMapper.selectById(taskId); if (task == null) { return Result.error("任务不存在"); } // 验证任务属于该家庭 if (!familyId.equals(task.getFamilyId())) { return Result.error("任务不属于该家庭"); } // 更新任务字段 if (taskData.containsKey("title")) { task.setTitle((String) taskData.get("title")); } if (taskData.containsKey("description")) { task.setDescription((String) taskData.get("description")); } if (taskData.containsKey("points")) { task.setPoints((Integer) taskData.get("points")); } if (taskData.containsKey("deadline")) { task.setDeadline(new java.util.Date((Long) taskData.get("deadline"))); } if (taskData.containsKey("status")) { task.setStatus((String) taskData.get("status")); } task.setUpdatedAt(new java.util.Date()); taskMapper.updateById(task); return Result.success(true); } /** * 指导师删除任务 */ @Operation(summary = "删除任务") @ApiResponses({ @ApiResponse(responseCode = "200", description = "删除成功"), @ApiResponse(responseCode = "403", description = "Access denied"), @ApiResponse(responseCode = "404", description = "任务不存在") }) @PostMapping("/{familyId}/tasks/{taskId}/delete") public Result deleteTask( @RequestAttribute("userId") Long guideId, @PathVariable Long familyId, @PathVariable Long taskId, @RequestAttribute("role") String role) { if (!"teacher".equals(role)) { return Result.error("Access denied"); } Result denied = requireBound(guideId, familyId); if (denied != null) return Result.error(denied.getCode(), denied.getMessage()); Task task = taskMapper.selectById(taskId); if (task == null) { return Result.error("任务不存在"); } // 验证任务属于该家庭 if (!familyId.equals(task.getFamilyId())) { return Result.error("任务不属于该家庭"); } // 指导师可以删除任何任务(不只是创建者) task.setStatus("cancelled"); task.setUpdatedAt(new java.util.Date()); taskMapper.updateById(task); return Result.success(true); } /** * 获取规划师首页统计(实际数据) */ @Operation(summary = "获取规划师首页统计") @PostMapping("/dashboard-stats") public Result> getDashboardStats(@RequestAttribute("userId") Long guideId, @RequestAttribute("role") String role) { if (!"teacher".equals(role)) { return Result.error("Access denied"); } User guide = userMapper.selectById(guideId); long totalFamilies = 0; long todayCompleted = 0; long pendingReview = 0; if (guide != null && guide.getTeacherFamilyIds() != null && !guide.getTeacherFamilyIds().isEmpty()) { List familyIds = Arrays.stream(guide.getTeacherFamilyIds().split(",")) .map(String::trim).filter(s -> !s.isEmpty()).map(Long::parseLong) .collect(Collectors.toList()); totalFamilies = familyIds.size(); // 今天0点 Date todayStart = Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant()); for (Long familyId : familyIds) { List children = familyMemberMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(FamilyMember::getFamilyId, familyId)); for (FamilyMember child : children) { // 今日完成任务:status=completed 且 completedAt >= todayStart long completed = taskMapper.selectCount( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(Task::getFamilyMemberId, child.getId()) .eq(Task::getStatus, "completed") .ge(Task::getCompletedAt, todayStart)); todayCompleted += completed; // 待审核任务 long pending = taskMapper.selectCount( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(Task::getFamilyMemberId, child.getId()) .eq(Task::getNeedReview, 1) .eq(Task::getStatus, "pending_review")); pendingReview += pending; } } } Map stats = new HashMap<>(); stats.put("totalFamilies", totalFamilies); stats.put("todayCompletedTasks", todayCompleted); stats.put("pendingReviewTasks", pendingReview); return Result.success(stats); } /** * 家庭归属校验;返回 null 表示放行,非 null 为 403 响应。 */ private Result requireBound(Long guideId, Long familyId) { return guideFamilyAccessGuard.checkBinding(guideId, familyId); } }