feat: 问卷任务管理模块增强

- 新增犯人完成进度查询接口
- 新增批量分配 Agent 填写功能
- 优化仪表盘统计逻辑
- 新增 Agent 填写权限 SQL
This commit is contained in:
tangweijie 2026-01-26 16:02:45 +08:00
parent f0caa49133
commit ff09efa216
15 changed files with 468 additions and 37 deletions

View File

@ -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;

View File

@ -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<List<PrisonerProgressRespVO>> 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<Boolean> 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<Boolean> resetPrisonerRecord(@NotNull(message = "记录ID不能为空") @RequestParam("recordId") Long recordId) {
questionnaireTaskService.resetPrisonerRecord(recordId);
return success(true);
}
// ==================== 统计相关 ====================
@GetMapping("/area-statistics")

View File

@ -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;
}

View File

@ -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<Boolean> submitAnswerByAgent(@Valid @RequestBody AssessmentAnswerSubmitReqVO reqVO) {
questionnaireRecordService.submitAnswerByAgent(reqVO);
return success(true);
}
@PostMapping("/finish")
@Operation(summary = "结束测评")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:finish')")

View File

@ -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;
}

View File

@ -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<QuestionnaireTaskDO
"WHERE deleted = 0 AND status IN (2, 3)")
Map<String, Object> 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 " +
"<foreach item='id' collection='areaIds' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach> " +
"GROUP BY qt.id, qt.task_name")
List<Map<String, Object>> selectAreaComparisonByQuestionnaire(
@Param("questionnaireId") Long questionnaireId,
@Param("areaIds") List<Long> 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 " +
"<foreach item='id' collection='areaIds' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach> " +
"GROUP BY qt.id, qt.task_name")
List<Map<String, Object>> selectAreaComparisonByAreas(
@Param("areaIds") List<Long> areaIds);
}

View File

@ -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<AssessmentAnswerSubmitReqVO.AnswerItem> answers);
void saveAnswers(Long assessmentRecordId, Long questionnaireId, Long prisonerId,
List<AssessmentAnswerSubmitReqVO.AnswerItem> answers,
Boolean isAgentFill, Long agentOperatorId, String agentOperatorName);
/**
* 计算客观题得分并更新

View File

@ -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<AssessmentAnswerSubmitReqVO.AnswerItem> answers) {
List<AssessmentAnswerSubmitReqVO.AnswerItem> 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);
}
}

View File

@ -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<SituationDO>()
.eq(SituationDO::getPrisonerId, prisonerId)
.eq(SituationDO::getStatus, 3) // 已处理
.eq(SituationDO::getCategory, 1)); // 监管安全类型
vo.setViolationCount(violationCount != null ? violationCount.intValue() : 0);
// 9. 查询累计计分考核记录统计加分和扣分次数
List<ScoreDO> allScores = scoreMapper.selectList(new LambdaQueryWrapper<ScoreDO>()
.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("-");
// ==================== 构建中心数据 ====================
// 左侧数据本月消费奖励惩罚余额

View File

@ -112,6 +112,14 @@ public interface QuestionnaireTaskService {
*/
PageResult<QuestionnaireRecordDO> getPendingPrisoners(Long id, PageParam pageReqVO);
/**
* 获取任务的人员填写进度列表
*
* @param id 任务ID
* @return 人员填写进度列表
*/
List<PrisonerProgressRespVO> 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);
/**
* 按监区统计任务完成情况

View File

@ -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<QuestionnaireTaskDO> getQuestionnaireTaskPage(QuestionnaireTaskPageReqVO pageReqVO) {
Page<QuestionnaireTaskDO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
LambdaQueryWrapper<QuestionnaireTaskDO> wrapper = new LambdaQueryWrapper<QuestionnaireTaskDO>()
return questionnaireTaskMapper.selectPage(pageReqVO, new LambdaQueryWrapperX<QuestionnaireTaskDO>()
.likeIfPresent(QuestionnaireTaskDO::getTaskName, pageReqVO.getTaskName())
.eqIfPresent(QuestionnaireTaskDO::getStatus, pageReqVO.getStatus())
.eqIfPresent(QuestionnaireTaskDO::getQuestionnaireId, pageReqVO.getQuestionnaireId())
.betweenIfPresent(QuestionnaireTaskDO::getCreateTime, pageReqVO.getCreateTime())
.orderByDesc(QuestionnaireTaskDO::getCreateTime);
Page<QuestionnaireTaskDO> 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<PrisonerProgressRespVO> getPrisonerProgress(Long id) {
List<cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO> records = questionnaireRecordMapper.selectList(
new LambdaQueryWrapper<cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO>()
.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<TaskAreaStatisticsRespVO> getTaskAreaStatistics(Long id) {
List<Map<String, Object>> stats = questionnaireRecordMapper.selectMaps(
new QueryWrapper<cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO>()
.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<Map<String, Object>> stats;
if (questionnaireId != null) {
stats = questionnaireTaskMapper.selectMaps(
new QueryWrapper<QuestionnaireTaskDO>()
.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<QuestionnaireTaskDO>()
.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()

View File

@ -86,6 +86,13 @@ public interface QuestionnaireRecordService {
*/
void submitAnswer(@Valid AssessmentAnswerSubmitReqVO reqVO);
/**
* 代为提交答卷民警代填
*
* @param reqVO 提交信息包含代填操作人信息
*/
void submitAnswerByAgent(@Valid AssessmentAnswerSubmitReqVO reqVO);
/**
* 结束测评
*

View File

@ -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);

View File

@ -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';

View File

@ -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`;