From ff09efa216b6a8fefe26c2400c742ca5259a9a23 Mon Sep 17 00:00:00 2001 From: tangweijie <877588133@qq.com> Date: Mon, 26 Jan 2026 16:02:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=97=AE=E5=8D=B7=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=A8=A1=E5=9D=97=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增犯人完成进度查询接口 - 新增批量分配 Agent 填写功能 - 优化仪表盘统计逻辑 - 新增 Agent 填写权限 SQL --- .../vo/PrisonerDashboardStatsRespVO.java | 15 +++ .../PrisonQuestionnaireTaskController.java | 27 +++++ .../vo/PrisonerProgressRespVO.java | 55 +++++++++ .../PrisonQuestionnaireRecordController.java | 8 ++ .../dal/dataobject/answer/AnswerDO.java | 16 +++ .../QuestionnaireTaskMapper.java | 31 +++++ .../prison/service/answer/AnswerService.java | 7 +- .../service/answer/AnswerServiceImpl.java | 16 ++- .../impl/PrisonDashboardServiceImpl.java | 29 +++++ .../QuestionnaireTaskService.java | 22 +++- .../QuestionnaireTaskServiceImpl.java | 105 +++++++++++------ .../QuestionnaireRecordService.java | 7 ++ .../QuestionnaireRecordServiceImpl.java | 22 +++- .../sql/insert_agent_fill_permission.sql | 110 ++++++++++++++++++ .../sql/migrate/add_agent_fill_fields.sql | 35 ++++++ 15 files changed, 468 insertions(+), 37 deletions(-) create mode 100644 yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/questionnaire_task/vo/PrisonerProgressRespVO.java create mode 100644 yudao-module-prison/src/main/resources/sql/insert_agent_fill_permission.sql create mode 100644 yudao-module-prison/src/main/resources/sql/migrate/add_agent_fill_fields.sql diff --git a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/dashboard/vo/PrisonerDashboardStatsRespVO.java b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/dashboard/vo/PrisonerDashboardStatsRespVO.java index 8b79d99439..be195286a2 100644 --- a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/dashboard/vo/PrisonerDashboardStatsRespVO.java +++ b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/dashboard/vo/PrisonerDashboardStatsRespVO.java @@ -69,6 +69,21 @@ public class PrisonerDashboardStatsRespVO { @Schema(description = "风险等级:1-低风险 2-中风险 3-高风险 4-极高风险", example = "1") private Integer riskLevel; + @Schema(description = "剩余刑期天数", example = "1000") + private Integer remainingDays; + + @Schema(description = "累计违规次数", example = "5") + private Integer violationCount; + + @Schema(description = "累计表扬天数", example = "-") + private String praiseDays; + + @Schema(description = "累计扣分次数", example = "3") + private Integer penaltyCount; + + @Schema(description = "累计加分次数", example = "8") + private Integer rewardCount; + @Schema(description = "中心左侧数据") private CenterLeftData centerLeftData; diff --git a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/questionnaire_task/PrisonQuestionnaireTaskController.java b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/questionnaire_task/PrisonQuestionnaireTaskController.java index 1bb7e02186..c90f425663 100644 --- a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/questionnaire_task/PrisonQuestionnaireTaskController.java +++ b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/questionnaire_task/PrisonQuestionnaireTaskController.java @@ -149,6 +149,15 @@ public class PrisonQuestionnaireTaskController { return success(questionnaireTaskService.getPendingPrisoners(id, pageReqVO)); } + @GetMapping("/prisoner-progress") + @Operation(summary = "获取任务的人员填写进度列表") + @Parameter(name = "id", description = "任务ID", required = true) + @PreAuthorize("@ss.hasPermission('prison:questionnaire-task:query')") + public CommonResult> getPrisonerProgress( + @NotNull(message = "任务ID不能为空") @RequestParam("id") Long id) { + return success(questionnaireTaskService.getPrisonerProgress(id)); + } + @PostMapping("/remind") @Operation(summary = "提醒未完成人员") @Parameter(name = "id", description = "任务ID", required = true) @@ -157,6 +166,24 @@ public class PrisonQuestionnaireTaskController { return success(questionnaireTaskService.remindPendingPrisoners(id)); } + @PostMapping("/notify-prisoner") + @Operation(summary = "通知单个人员") + @Parameter(name = "recordId", description = "记录ID", required = true) + @PreAuthorize("@ss.hasPermission('prison:questionnaire-task:notify')") + public CommonResult notifyPrisoner(@NotNull(message = "记录ID不能为空") @RequestParam("recordId") Long recordId) { + questionnaireTaskService.notifyPrisoner(recordId); + return success(true); + } + + @PostMapping("/reset-record") + @Operation(summary = "重置人员答题记录") + @Parameter(name = "recordId", description = "记录ID", required = true) + @PreAuthorize("@ss.hasPermission('prison:questionnaire-record:reset')") + public CommonResult resetPrisonerRecord(@NotNull(message = "记录ID不能为空") @RequestParam("recordId") Long recordId) { + questionnaireTaskService.resetPrisonerRecord(recordId); + return success(true); + } + // ==================== 统计相关 ==================== @GetMapping("/area-statistics") diff --git a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/questionnaire_task/vo/PrisonerProgressRespVO.java b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/questionnaire_task/vo/PrisonerProgressRespVO.java new file mode 100644 index 0000000000..f2aea0018e --- /dev/null +++ b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/questionnaire_task/vo/PrisonerProgressRespVO.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.prison.controller.admin.questionnaire_task.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 问卷任务人员填写进度 Response VO") +@Data +public class PrisonerProgressRespVO { + + @Schema(description = "记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "罪犯ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long prisonerId; + + @Schema(description = "罪犯编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "P001") + private String prisonerNo; + + @Schema(description = "罪犯姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + private String prisonerName; + + @Schema(description = "监区ID", example = "100") + private Long areaId; + + @Schema(description = "监区名称", example = "一监区") + private String areaName; + + @Schema(description = "填写状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + @Schema(description = "客观分", example = "80") + private Integer objectiveScore; + + @Schema(description = "主观分", example = "85") + private Integer subjectiveScore; + + @Schema(description = "总分", example = "165") + private Integer totalScore; + + @Schema(description = "风险等级", example = "2") + private Integer riskLevel; + + @Schema(description = "答题用时(秒)", example = "300") + private Integer duration; + + @Schema(description = "开始时间") + private LocalDateTime startTime; + + @Schema(description = "完成时间") + private LocalDateTime finishTime; + +} diff --git a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/questionnairerecord/PrisonQuestionnaireRecordController.java b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/questionnairerecord/PrisonQuestionnaireRecordController.java index 7f8d777c8a..918bbe1c1f 100644 --- a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/questionnairerecord/PrisonQuestionnaireRecordController.java +++ b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/controller/admin/questionnairerecord/PrisonQuestionnaireRecordController.java @@ -129,6 +129,14 @@ public class PrisonQuestionnaireRecordController { return success(true); } + @PostMapping("/submit-by-agent") + @Operation(summary = "代为提交答卷(民警代填)") + @PreAuthorize("@ss.hasPermission('prison:questionnaire-record:agent-fill')") + public CommonResult submitAnswerByAgent(@Valid @RequestBody AssessmentAnswerSubmitReqVO reqVO) { + questionnaireRecordService.submitAnswerByAgent(reqVO); + return success(true); + } + @PostMapping("/finish") @Operation(summary = "结束测评") @PreAuthorize("@ss.hasPermission('prison:questionnaire-record:finish')") diff --git a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/dal/dataobject/answer/AnswerDO.java b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/dal/dataobject/answer/AnswerDO.java index e720224824..8f9fff60c2 100644 --- a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/dal/dataobject/answer/AnswerDO.java +++ b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/dal/dataobject/answer/AnswerDO.java @@ -67,5 +67,21 @@ public class AnswerDO extends TenantBaseDO { * 答题时间(秒) */ private Integer duration; + /** + * 是否代填:true-民警代填 false-本人填写 null-未知 + */ + private Boolean isAgentFill; + /** + * 代填操作人ID(民警ID) + */ + private Long agentOperatorId; + /** + * 代填操作人姓名 + */ + private String agentOperatorName; + /** + * 代填时间 + */ + private LocalDateTime agentFillTime; } diff --git a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/dal/mysql/questionnaire_task/QuestionnaireTaskMapper.java b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/dal/mysql/questionnaire_task/QuestionnaireTaskMapper.java index c1f043fb1b..147a62c94c 100644 --- a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/dal/mysql/questionnaire_task/QuestionnaireTaskMapper.java +++ b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/dal/mysql/questionnaire_task/QuestionnaireTaskMapper.java @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.prison.controller.admin.questionnaire_task.vo.QuestionnaireTaskPageReqVO; import cn.iocoder.yudao.module.prison.dal.dataobject.questionnaire_task.QuestionnaireTaskDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; @@ -56,4 +57,34 @@ public interface QuestionnaireTaskMapper extends BaseMapperX selectTaskStatisticsSummary(); + @Select("SELECT qt.id as task_id, qt.task_name, " + + "SUM(qr.total_count) as total_count, " + + "SUM(qr.completed_count) as completed_count " + + "FROM prison_questionnaire_task qt " + + "INNER JOIN prison_questionnaire_record qr ON qt.id = qr.task_id " + + "WHERE qt.deleted = 0 " + + "AND qt.questionnaire_id = #{questionnaireId} " + + "AND qr.area_id IN " + + "" + + "#{id}" + + " " + + "GROUP BY qt.id, qt.task_name") + List> selectAreaComparisonByQuestionnaire( + @Param("questionnaireId") Long questionnaireId, + @Param("areaIds") List areaIds); + + @Select("SELECT qt.id as task_id, qt.task_name, " + + "SUM(qr.total_count) as total_count, " + + "SUM(qr.completed_count) as completed_count " + + "FROM prison_questionnaire_task qt " + + "INNER JOIN prison_questionnaire_record qr ON qt.id = qr.task_id " + + "WHERE qt.deleted = 0 " + + "AND qr.area_id IN " + + "" + + "#{id}" + + " " + + "GROUP BY qt.id, qt.task_name") + List> selectAreaComparisonByAreas( + @Param("areaIds") List areaIds); + } diff --git a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/answer/AnswerService.java b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/answer/AnswerService.java index a5f3b80ac7..f92d08ca0d 100644 --- a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/answer/AnswerService.java +++ b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/answer/AnswerService.java @@ -76,8 +76,13 @@ public interface AnswerService { * @param questionnaireId 问卷ID * @param prisonerId 罪犯ID * @param answers 答题详情列表 + * @param isAgentFill 是否代填 + * @param agentOperatorId 代填操作人ID + * @param agentOperatorName 代填操作人姓名 */ - void saveAnswers(Long assessmentRecordId, Long questionnaireId, Long prisonerId, List answers); + void saveAnswers(Long assessmentRecordId, Long questionnaireId, Long prisonerId, + List answers, + Boolean isAgentFill, Long agentOperatorId, String agentOperatorName); /** * 计算客观题得分并更新 diff --git a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/answer/AnswerServiceImpl.java b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/answer/AnswerServiceImpl.java index 40c16d427e..6114a22cf6 100644 --- a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/answer/AnswerServiceImpl.java +++ b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/answer/AnswerServiceImpl.java @@ -13,6 +13,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.math.BigDecimal; import java.math.RoundingMode; +import java.time.LocalDateTime; import cn.iocoder.yudao.module.prison.controller.admin.answer.vo.*; import cn.iocoder.yudao.module.prison.controller.admin.questionnairerecord.vo.AssessmentAnswerSubmitReqVO; import cn.iocoder.yudao.module.prison.dal.dataobject.answer.AnswerDO; @@ -113,10 +114,13 @@ public class AnswerServiceImpl implements AnswerService { @Override @Transactional(rollbackFor = Exception.class) public void saveAnswers(Long assessmentRecordId, Long questionnaireId, Long prisonerId, - List answers) { + List answers, + Boolean isAgentFill, Long agentOperatorId, String agentOperatorName) { if (CollUtil.isEmpty(answers)) { return; } + // 删除该测评已有的旧答题记录 + deleteByAssessmentRecordId(assessmentRecordId); for (AssessmentAnswerSubmitReqVO.AnswerItem answerItem : answers) { // 获取问题信息 @@ -151,6 +155,16 @@ public class AnswerServiceImpl implements AnswerService { answer.setIsCorrect(isCorrect); } + // 设置代填信息 + if (Boolean.TRUE.equals(isAgentFill)) { + answer.setIsAgentFill(true); + answer.setAgentOperatorId(agentOperatorId); + answer.setAgentOperatorName(agentOperatorName); + answer.setAgentFillTime(LocalDateTime.now()); + } else { + answer.setIsAgentFill(false); + } + answerMapper.insert(answer); } } diff --git a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/dashboard/impl/PrisonDashboardServiceImpl.java b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/dashboard/impl/PrisonDashboardServiceImpl.java index 4a5bc349a1..bfcea53067 100644 --- a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/dashboard/impl/PrisonDashboardServiceImpl.java +++ b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/dashboard/impl/PrisonDashboardServiceImpl.java @@ -208,6 +208,11 @@ public class PrisonDashboardServiceImpl implements PrisonDashboardService { } if (prisoner.getReleaseDate() != null) { vo.setReleaseDate(prisoner.getReleaseDate().toString()); + // 计算剩余刑期天数 + long remainingDays = ChronoUnit.DAYS.between(LocalDate.now(), prisoner.getReleaseDate()); + vo.setRemainingDays(remainingDays > 0 ? (int) remainingDays : 0); + } else { + vo.setRemainingDays(null); } // 监区信息(简单拼接) @@ -298,6 +303,30 @@ public class PrisonDashboardServiceImpl implements PrisonDashboardService { .orderByDesc(SituationDO::getOccurTime) .last("LIMIT 10")); + // 8. 查询累计违规次数(狱情收集-监管安全类型) + Long violationCount = situationMapper.selectCount(new LambdaQueryWrapper() + .eq(SituationDO::getPrisonerId, prisonerId) + .eq(SituationDO::getStatus, 3) // 已处理 + .eq(SituationDO::getCategory, 1)); // 监管安全类型 + vo.setViolationCount(violationCount != null ? violationCount.intValue() : 0); + + // 9. 查询累计计分考核记录(统计加分和扣分次数) + List allScores = scoreMapper.selectList(new LambdaQueryWrapper() + .eq(ScoreDO::getPrisonerId, prisonerId) + .eq(ScoreDO::getStatus, 1)); // 已通过 + + long rewardCount = allScores.stream() + .filter(s -> s.getRewardScore() != null && s.getRewardScore().doubleValue() > 0) + .count(); + long penaltyCount = allScores.stream() + .filter(s -> s.getPenaltyScore() != null && s.getPenaltyScore().doubleValue() > 0) + .count(); + vo.setRewardCount((int) rewardCount); + vo.setPenaltyCount((int) penaltyCount); + + // 10. 设置累计表扬天数(固定返回"-") + vo.setPraiseDays("-"); + // ==================== 构建中心数据 ==================== // 左侧数据:本月消费、奖励、惩罚、余额 diff --git a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnaire_task/QuestionnaireTaskService.java b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnaire_task/QuestionnaireTaskService.java index 2f24cbe345..88012e8dfe 100644 --- a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnaire_task/QuestionnaireTaskService.java +++ b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnaire_task/QuestionnaireTaskService.java @@ -112,6 +112,14 @@ public interface QuestionnaireTaskService { */ PageResult getPendingPrisoners(Long id, PageParam pageReqVO); + /** + * 获取任务的人员填写进度列表 + * + * @param id 任务ID + * @return 人员填写进度列表 + */ + List getPrisonerProgress(Long id); + /** * 提醒未完成人员 * @@ -120,7 +128,19 @@ public interface QuestionnaireTaskService { */ Integer remindPendingPrisoners(Long id); - // ==================== 统计相关 ==================== + /** + * 通知单个人员 + * + * @param recordId 记录ID + */ + void notifyPrisoner(Long recordId); + + /** + * 重置人员答题记录 + * + * @param recordId 记录ID + */ + void resetPrisonerRecord(Long recordId); /** * 按监区统计任务完成情况 diff --git a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnaire_task/QuestionnaireTaskServiceImpl.java b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnaire_task/QuestionnaireTaskServiceImpl.java index cd5510eb57..9b06f59f32 100644 --- a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnaire_task/QuestionnaireTaskServiceImpl.java +++ b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnaire_task/QuestionnaireTaskServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.prison.controller.admin.questionnaire_task.vo.*; import cn.iocoder.yudao.module.prison.dal.dataobject.questionnaire_task.QuestionnaireTaskDO; import cn.iocoder.yudao.module.prison.dal.dataobject.questionnaire.QuestionnaireDO; @@ -168,16 +169,12 @@ public class QuestionnaireTaskServiceImpl implements QuestionnaireTaskService { @Override public PageResult getQuestionnaireTaskPage(QuestionnaireTaskPageReqVO pageReqVO) { - Page page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + return questionnaireTaskMapper.selectPage(pageReqVO, new LambdaQueryWrapperX() .likeIfPresent(QuestionnaireTaskDO::getTaskName, pageReqVO.getTaskName()) .eqIfPresent(QuestionnaireTaskDO::getStatus, pageReqVO.getStatus()) .eqIfPresent(QuestionnaireTaskDO::getQuestionnaireId, pageReqVO.getQuestionnaireId()) .betweenIfPresent(QuestionnaireTaskDO::getCreateTime, pageReqVO.getCreateTime()) - .orderByDesc(QuestionnaireTaskDO::getCreateTime); - - Page result = questionnaireTaskMapper.selectPage(page, wrapper); - return new PageResult<>(result.getRecords(), result.getTotal()); + .orderByDesc(QuestionnaireTaskDO::getCreateTime)); } @Override @@ -329,11 +326,74 @@ public class QuestionnaireTaskServiceImpl implements QuestionnaireTaskService { return pendingRecords.size(); } + @Override + public List getPrisonerProgress(Long id) { + List records = questionnaireRecordMapper.selectList( + new LambdaQueryWrapper() + .eq(cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO::getTaskId, id) + .orderByAsc(cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO::getCreateTime) + ); + + if (CollUtil.isEmpty(records)) { + return Collections.emptyList(); + } + + return records.stream().map(record -> { + PrisonerProgressRespVO vo = new PrisonerProgressRespVO(); + vo.setId(record.getId()); + vo.setPrisonerId(record.getPrisonerId()); + vo.setPrisonerNo(record.getPrisonerNo()); + vo.setPrisonerName(record.getPrisonerName()); + vo.setAreaId(record.getPrisonerAreaId()); + vo.setAreaName(record.getPrisonerAreaName()); + vo.setStatus(record.getStatus()); + vo.setObjectiveScore(record.getObjectiveScore() != null ? record.getObjectiveScore().intValue() : null); + vo.setSubjectiveScore(record.getSubjectiveScore() != null ? record.getSubjectiveScore().intValue() : null); + vo.setTotalScore(record.getTotalScore() != null ? record.getTotalScore().intValue() : null); + vo.setRiskLevel(record.getRiskLevel()); + vo.setStartTime(record.getStartTime()); + vo.setFinishTime(record.getEndTime()); + vo.setDuration(record.getDuration()); + return vo; + }).collect(Collectors.toList()); + } + + @Override + public void notifyPrisoner(Long recordId) { + cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO record = questionnaireRecordMapper.selectById(recordId); + if (record == null) { + throw new ServiceException(ErrorCodeConstants.QUESTIONNAIRE_RECORD_NOT_EXISTS); + } + // TODO: 发送站内信通知,此处为占位实现 + log.info("通知任务 {} 中的罪犯 {} 完成问卷", record.getTaskId(), record.getPrisonerName()); + } + + @Override + public void resetPrisonerRecord(Long recordId) { + cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO record = questionnaireRecordMapper.selectById(recordId); + if (record == null) { + throw new ServiceException(ErrorCodeConstants.QUESTIONNAIRE_RECORD_NOT_EXISTS); + } + // 重置记录状态为待测评 + cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO updateRecord = new cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO(); + updateRecord.setId(recordId); + updateRecord.setStatus(QuestionnaireRecordStatusEnum.PENDING.getCode()); + updateRecord.setObjectiveScore(null); + updateRecord.setSubjectiveScore(null); + updateRecord.setTotalScore(null); + updateRecord.setRiskLevel(null); + updateRecord.setStartTime(null); + updateRecord.setEndTime(null); + updateRecord.setDuration(null); + questionnaireRecordMapper.updateById(updateRecord); + log.info("重置任务 {} 中的罪犯 {} 答题记录", record.getTaskId(), record.getPrisonerName()); + } + @Override public List getTaskAreaStatistics(Long id) { List> stats = questionnaireRecordMapper.selectMaps( new QueryWrapper() - .select("area_id", "area_name", + .select("prisoner_area_id", "prisoner_area_name", "COUNT(*) as total_count", "SUM(CASE WHEN status = 3 THEN 1 ELSE 0 END) as completed_count", "AVG(CASE WHEN status = 3 THEN total_score ELSE NULL END) as avg_score", @@ -342,7 +402,7 @@ public class QuestionnaireTaskServiceImpl implements QuestionnaireTaskService { "SUM(CASE WHEN risk_level = 2 THEN 1 ELSE 0 END) as medium_risk_count", "SUM(CASE WHEN risk_level = 3 THEN 1 ELSE 0 END) as low_risk_count") .eq("task_id", id) - .groupBy("area_id", "area_name") + .groupBy("prisoner_area_id", "prisoner_area_name") .orderByDesc("completed_count") ); @@ -355,8 +415,8 @@ public class QuestionnaireTaskServiceImpl implements QuestionnaireTaskService { new BigDecimal(stat.get("pass_rate").toString()) : BigDecimal.ZERO; return TaskAreaStatisticsRespVO.builder() - .areaId(stat.get("area_id") != null ? ((Number) stat.get("area_id")).longValue() : null) - .areaName((String) stat.get("area_name")) + .areaId(stat.get("prisoner_area_id") != null ? ((Number) stat.get("prisoner_area_id")).longValue() : null) + .areaName((String) stat.get("prisoner_area_name")) .totalCount(total) .completedCount(completed) .completionRate(total > 0 ? BigDecimal.valueOf(completed * 100.0 / total).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO) @@ -405,30 +465,9 @@ public class QuestionnaireTaskServiceImpl implements QuestionnaireTaskService { List> stats; if (questionnaireId != null) { - stats = questionnaireTaskMapper.selectMaps( - new QueryWrapper() - .select("qt.id as task_id", "qt.task_name", - "SUM(qr.total_count) as total_count", - "SUM(qr.completed_count) as completed_count") - .from("prison_questionnaire_task qt") - .innerJoin("prison_questionnaire_record qr ON qt.id = qr.task_id") - .eq("qt.deleted", 0) - .eq("qt.questionnaire_id", questionnaireId) - .in("qr.area_id", areaIds) - .groupBy("qt.id", "qt.task_name") - ); + stats = questionnaireTaskMapper.selectAreaComparisonByQuestionnaire(questionnaireId, areaIds); } else { - stats = questionnaireTaskMapper.selectMaps( - new QueryWrapper() - .select("qt.id as task_id", "qt.task_name", - "SUM(qr.total_count) as total_count", - "SUM(qr.completed_count) as completed_count") - .from("prison_questionnaire_task qt") - .innerJoin("prison_questionnaire_record qr ON qt.id = qr.task_id") - .eq("qt.deleted", 0) - .in("qr.area_id", areaIds) - .groupBy("qt.id", "qt.task_name") - ); + stats = questionnaireTaskMapper.selectAreaComparisonByAreas(areaIds); } return stats.stream().map(stat -> AreaComparisonRespVO.builder() diff --git a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnairerecord/QuestionnaireRecordService.java b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnairerecord/QuestionnaireRecordService.java index 9ce0b23f5e..f614cf748c 100644 --- a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnairerecord/QuestionnaireRecordService.java +++ b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnairerecord/QuestionnaireRecordService.java @@ -86,6 +86,13 @@ public interface QuestionnaireRecordService { */ void submitAnswer(@Valid AssessmentAnswerSubmitReqVO reqVO); + /** + * 代为提交答卷(民警代填) + * + * @param reqVO 提交信息,包含代填操作人信息 + */ + void submitAnswerByAgent(@Valid AssessmentAnswerSubmitReqVO reqVO); + /** * 结束测评 * diff --git a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnairerecord/QuestionnaireRecordServiceImpl.java b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnairerecord/QuestionnaireRecordServiceImpl.java index 8013db34d3..22068f45d5 100644 --- a/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnairerecord/QuestionnaireRecordServiceImpl.java +++ b/yudao-module-prison/src/main/java/cn/iocoder/yudao/module/prison/service/questionnairerecord/QuestionnaireRecordServiceImpl.java @@ -161,6 +161,25 @@ public class QuestionnaireRecordServiceImpl implements QuestionnaireRecordServic @Override @Transactional(rollbackFor = Exception.class) public void submitAnswer(AssessmentAnswerSubmitReqVO reqVO) { + submitAnswerInternal(reqVO, false, null, null); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void submitAnswerByAgent(AssessmentAnswerSubmitReqVO reqVO) { + // 获取当前登录的民警信息 + Long currentUserId = SecurityFrameworkUtils.getLoginUserId(); + String currentUserName = SecurityFrameworkUtils.getLoginUser().getInfo() != null + ? SecurityFrameworkUtils.getLoginUser().getInfo().get("nickname") + : "未知"; + submitAnswerInternal(reqVO, true, currentUserId, currentUserName); + } + + /** + * 内部提交答卷方法 + */ + private void submitAnswerInternal(AssessmentAnswerSubmitReqVO reqVO, Boolean isAgentFill, + Long agentOperatorId, String agentOperatorName) { QuestionnaireRecordDO record = validateQuestionnaireRecordExists(reqVO.getRecordId()); if (!QuestionnaireRecordStatusEnum.IN_PROGRESS.getStatus().equals(record.getStatus())) { throw exception(QUESTIONNAIRE_RECORD_STATUS_ERROR); @@ -171,7 +190,8 @@ public class QuestionnaireRecordServiceImpl implements QuestionnaireRecordServic record.setDuration((int) java.time.Duration.between(record.getStartTime(), LocalDateTime.now()).getSeconds()); } // 保存答题记录并计算客观题得分 - answerService.saveAnswers(reqVO.getRecordId(), record.getQuestionnaireId(), reqVO.getPrisonerId(), reqVO.getAnswers()); + answerService.saveAnswers(reqVO.getRecordId(), record.getQuestionnaireId(), reqVO.getPrisonerId(), + reqVO.getAnswers(), isAgentFill, agentOperatorId, agentOperatorName); // 计算客观题得分 BigDecimal objectiveScore = answerService.calculateObjectiveScore(reqVO.getRecordId()); record.setObjectiveScore(objectiveScore); diff --git a/yudao-module-prison/src/main/resources/sql/insert_agent_fill_permission.sql b/yudao-module-prison/src/main/resources/sql/insert_agent_fill_permission.sql new file mode 100644 index 0000000000..5d9d07a377 --- /dev/null +++ b/yudao-module-prison/src/main/resources/sql/insert_agent_fill_permission.sql @@ -0,0 +1,110 @@ +-- ===================================================== +-- XL监狱综合管理平台 - 问卷代填权限SQL +-- 生成时间: 2026-01-26 +-- 功能: 添加问卷代填按钮权限 +-- ===================================================== + +-- ===================================================== +-- 方法1: 自动查找并插入(推荐) +-- ===================================================== + +-- 1. 查找问卷记录管理(assessment-record)的父菜单ID +SELECT @parentId := parent_id +FROM system_menu +WHERE name = '测评记录管理' AND type = 2 AND deleted = 0 +LIMIT 1; + +-- 如果找不到,尝试查找 questionnaire-record +SELECT @parentId := id +FROM system_menu +WHERE path = 'assessment-record' AND type = 2 AND deleted = 0 +LIMIT 1; + +-- 2. 插入代填权限 +INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status) +SELECT '代为填写', 'prison:questionnaire-record:agent-fill', 3, 8, @parentId, '', '', '', 0 +WHERE @parentId IS NOT NULL; + +-- 3. 验证插入结果 +SELECT * FROM system_menu +WHERE permission = 'prison:questionnaire-record:agent-fill'; + +-- ===================================================== +-- 方法2: 手动指定父菜单ID插入 +-- ===================================================== +-- 请将 5047 替换为实际的测评记录管理父菜单ID + +-- INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status) +-- VALUES ('代为填写', 'prison:questionnaire-record:agent-fill', 3, 8, 5047, '', '', '', 0); + +-- ===================================================== +-- 方法3: 完整的查找和插入脚本(包含错误处理) +-- ===================================================== + +-- 设置临时变量 +SET @parentId := NULL; + +-- 查找问卷记录管理菜单的父ID +SELECT parent_id INTO @parentId +FROM system_menu +WHERE name = '测评记录管理' + AND type = 2 + AND deleted = 0 +ORDER BY id DESC +LIMIT 1; + +-- 如果没找到,尝试通过path查找 +IF @parentId IS NULL THEN + SELECT parent_id INTO @parentId + FROM system_menu + WHERE path = 'assessment-record' + AND type = 2 + AND deleted = 0 + ORDER BY id DESC + LIMIT 1; +END IF; + +-- 如果还是没找到,输出提示 +SELECT + CASE + WHEN @parentId IS NULL THEN '未找到测评记录管理菜单,请手动检查!' + ELSE CONCAT('找到父菜单ID: ', @parentId, ',即将插入代填权限...') + END AS result; + +-- 插入代填权限(使用 INSERT IGNORE 避免重复插入) +INSERT IGNORE INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) +VALUES ( + '代为填写', + 'prison:questionnaire-record:agent-fill', + 3, -- type=3 表示按钮 + 8, -- sort=8 排在最后(根据现有排序调整) + @parentId, -- 父菜单ID + '', -- path + '', -- icon + '', -- component + 0, -- status + b'1', -- visible + b'0', -- keep_alive + b'1', -- always_show + 'system', + NOW(), + '', + NOW(), + b'0' +); + +-- ===================================================== +-- 验证结果 +-- ===================================================== + +-- 查看所有 questionnaire-record 相关的权限 +SELECT id, name, permission, type, sort, parent_id +FROM system_menu +WHERE (permission LIKE 'prison:questionnaire-record:%' + OR permission LIKE 'prison:assessment-record:%') + AND deleted = 0 +ORDER BY parent_id, sort; + +-- 查看插入的代填权限 +SELECT * FROM system_menu +WHERE permission = 'prison:questionnaire-record:agent-fill'; diff --git a/yudao-module-prison/src/main/resources/sql/migrate/add_agent_fill_fields.sql b/yudao-module-prison/src/main/resources/sql/migrate/add_agent_fill_fields.sql new file mode 100644 index 0000000000..d6a527cd29 --- /dev/null +++ b/yudao-module-prison/src/main/resources/sql/migrate/add_agent_fill_fields.sql @@ -0,0 +1,35 @@ +-- ================================================ +-- 代填功能数据库迁移脚本 +-- 添加 prison_answer 表的代填相关字段 +-- ================================================ + +-- 检查字段是否已存在,如果不存在则添加 +-- 注意:MySQL 语法 + +-- 1. 添加是否代填字段 +ALTER TABLE `prison_answer` +ADD COLUMN `is_agent_fill` TINYINT(1) NULL COMMENT '是否代填:1-是,0-否,null-未知' AFTER `duration`; + +-- 2. 添加代填操作人ID字段 +ALTER TABLE `prison_answer` +ADD COLUMN `agent_operator_id` BIGINT NULL COMMENT '代填操作人ID(民警ID)' AFTER `is_agent_fill`; + +-- 3. 添加代填操作人姓名字段 +ALTER TABLE `prison_answer` +ADD COLUMN `agent_operator_name` VARCHAR(100) NULL COMMENT '代填操作人姓名' AFTER `agent_operator_id`; + +-- 4. 添加代填时间字段 +ALTER TABLE `prison_answer` +ADD COLUMN `agent_fill_time` DATETIME NULL COMMENT '代填时间' AFTER `agent_operator_name`; + +-- ================================================ +-- 回滚脚本(如需回滚) +-- ================================================ +-- ALTER TABLE `prison_answer` +-- DROP COLUMN IF EXISTS `is_agent_fill`; +-- ALTER TABLE `prison_answer` +-- DROP COLUMN IF EXISTS `agent_operator_id`; +-- ALTER TABLE `prison_answer` +-- DROP COLUMN IF EXISTS `agent_operator_name`; +-- ALTER TABLE `prison_answer` +-- DROP COLUMN IF EXISTS `agent_fill_time`;