Compare commits

...

2 Commits

Author SHA1 Message Date
bbcf68bdb8 feat: 新增评估模块功能
评估模块:
- AssessmentAnswer 评估答案管理(提交、评分、人工复核)
- AssessmentRecord 评估记录管理(状态流转、完成统计)
- AssessmentResult 评估结果管理(风险等级、管控建议)
- AssessmentStatistics 评估统计分析(按类型、时段统计)

LLM智能评估集成:
- Claude LLM 客户端实现
- 评估上下文构建
- 提示词模板构建
- 风险评估数据聚合

代码审查修复:
- 删除 AreaDO 重复导入
- 移除反射调用改用直接方法
- 11个 Controller 添加参数校验注解
- QuestionServiceImpl 批量更新兼容性修复

新增枚举:
- AssessmentAnswerStatusEnum 答案状态
- AssessmentManualReviewStatusEnum 人工复核状态
- AssessmentRecordStatusEnum 记录状态
- RiskLevelEnum 风险等级
2026-01-15 20:49:42 +08:00
f7f318bed8 fix: 代码审查修复 - 调查问卷模块优化
后端修复:
- QuestionnaireRecordServiceImpl: 修复缩进问题,添加空值校验和ID存在性校验
- QuestionnaireSaveReqVO/RespVO/PageReqVO: 同步DO新增的5个字段
- QuestionnaireMapper: 添加新字段查询条件
- QuestionServiceImpl: 优化批量更新性能,修复缩进问题
- 新增枚举类: QuestionnaireStatusEnum, QuestionnaireRecordStatusEnum, QuestionnaireRecordPassStatusEnum
- 新增数据库迁移脚本: question_add_columns.sql, questionnaire_add_columns.sql

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-15 20:14:32 +08:00
84 changed files with 5434 additions and 78 deletions

View File

@ -57,7 +57,7 @@ public class PrisonAreaController {
@Operation(summary = "删除监区信息")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:area:delete')")
public CommonResult<Boolean> deleteArea(@RequestParam("id") Long id) {
public CommonResult<Boolean> deleteArea(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
areaService.deleteArea(id);
return success(true);
}
@ -65,8 +65,8 @@ public class PrisonAreaController {
@DeleteMapping("/delete-list")
@Parameter(name = "ids", description = "编号", required = true)
@Operation(summary = "批量删除监区信息")
@PreAuthorize("@ss.hasPermission('prison:area:delete')")
public CommonResult<Boolean> deleteAreaList(@RequestParam("ids") List<Long> ids) {
@PreAuthorize("@ss.hasPermission('prison:area:delete')")
public CommonResult<Boolean> deleteAreaList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
areaService.deleteAreaListByIds(ids);
return success(true);
}
@ -97,7 +97,7 @@ public class PrisonAreaController {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<AreaDO> list = areaService.getAreaPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "监区信息.xls", "数据", AreaRespVO.class,
ExcelUtils.write(response, "监区信息.xlsx", "数据", AreaRespVO.class,
BeanUtils.toBean(list, AreaRespVO.class));
}

View File

@ -13,28 +13,28 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
@Data
public class AreaPageReqVO extends PageParam {
@Schema(description = "监区名称", example = "李四")
@Schema(description = "监区名称", example = "第一监区")
private String name;
@Schema(description = "监区编码")
private String code;
@Schema(description = "监区类型1-普通监区 2-严管监区 3-医院 4-禁闭室", example = "1")
@Schema(description = "监区类型1-普通监区 2-严管监区 3-集训监区 4-出监监区 5-医院 6-禁闭室", example = "1")
private Integer type;
@Schema(description = "容纳人数")
private Integer capacity;
@Schema(description = "当前人数", example = "26596")
@Schema(description = "当前人数", example = "100")
private Integer currentCount;
@Schema(description = "排序")
private Integer sort;
@Schema(description = "状态1-启用 2-禁用", example = "2")
@Schema(description = "状态1-启用 2-禁用", example = "1")
private Integer status;
@Schema(description = "备注", example = "你猜")
@Schema(description = "备注", example = "正常运行的监区")
private String remark;
@Schema(description = "创建时间")

View File

@ -6,6 +6,7 @@ import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import cn.idev.excel.annotation.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Schema(description = "管理后台 - 监区信息 Response VO")
@Data
@ -16,7 +17,7 @@ public class AreaRespVO {
@ExcelProperty("监区ID")
private Long id;
@Schema(description = "监区名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
@Schema(description = "监区名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "第一监区")
@ExcelProperty("监区名称")
private String name;
@ -40,7 +41,7 @@ public class AreaRespVO {
@ExcelProperty("容纳人数")
private Integer capacity;
@Schema(description = "当前人数", example = "26596")
@Schema(description = "当前人数", example = "100")
@ExcelProperty("当前人数")
private Integer currentCount;
@ -48,11 +49,11 @@ public class AreaRespVO {
@ExcelProperty("排序")
private Integer sort;
@Schema(description = "状态1-启用 2-禁用", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@Schema(description = "状态1-启用 2-禁用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("状态1-启用 2-禁用")
private Integer status;
@Schema(description = "备注", example = "你猜")
@Schema(description = "备注", example = "正常运行的监区")
@ExcelProperty("备注")
private String remark;
@ -61,6 +62,7 @@ public class AreaRespVO {
private LocalDateTime createTime;
@Schema(description = "子监区列表,仅一级监区返回")
@JsonIgnore
private List<AreaRespVO> children;
}

View File

@ -12,7 +12,7 @@ public class AreaSaveReqVO {
@Schema(description = "监区ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "19443")
private Long id;
@Schema(description = "监区名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
@Schema(description = "监区名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "第一监区")
@NotEmpty(message = "监区名称不能为空")
private String name;
@ -24,25 +24,30 @@ public class AreaSaveReqVO {
private Long parentId;
@Schema(description = "级别1-监区(大队) 2-分监区(中队)")
@NotNull(message = "级别不能为空")
private Integer level;
@Schema(description = "监区类型1-普通监区 2-严管监区 3-集训监区 4-出监监区 5-医院 6-禁闭室", example = "1")
@NotNull(message = "监区类型不能为空")
private Integer type;
@Schema(description = "容纳人数")
@Min(value = 0, message = "容纳人数不能为负数")
private Integer capacity;
@Schema(description = "当前人数", example = "26596")
@Schema(description = "当前人数", example = "100")
@Min(value = 0, message = "当前人数不能为负数")
private Integer currentCount;
@Schema(description = "排序")
@Min(value = 0, message = "排序不能为负数")
private Integer sort;
@Schema(description = "状态1-启用 2-禁用", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@Schema(description = "状态1-启用 2-禁用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "状态1-启用 2-禁用不能为空")
private Integer status;
@Schema(description = "备注", example = "你猜")
@Schema(description = "备注", example = "正常运行的监区")
private String remark;
}

View File

@ -0,0 +1,145 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.constraints.*;
import jakarta.validation.*;
import jakarta.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentAnswerDO;
import cn.iocoder.yudao.module.prison.service.assessment.AssessmentAnswerService;
@Tag(name = "管理后台 - 答卷详情")
@RestController
@RequestMapping("/prison/assessment-answer")
@Validated
public class AssessmentAnswerController {
@Resource
private AssessmentAnswerService assessmentAnswerService;
@PostMapping("/create")
@Operation(summary = "创建答卷")
@PreAuthorize("@ss.hasPermission('prison:assessment-answer:create')")
public CommonResult<Long> createAssessmentAnswer(@Valid @RequestBody AssessmentAnswerSaveReqVO createReqVO) {
return success(assessmentAnswerService.createAssessmentAnswer(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新答卷")
@PreAuthorize("@ss.hasPermission('prison:assessment-answer:update')")
public CommonResult<Boolean> updateAssessmentAnswer(@Valid @RequestBody AssessmentAnswerSaveReqVO updateReqVO) {
assessmentAnswerService.updateAssessmentAnswer(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除答卷")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:assessment-answer:delete')")
public CommonResult<Boolean> deleteAssessmentAnswer(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
assessmentAnswerService.deleteAssessmentAnswer(id);
return success(true);
}
@DeleteMapping("/delete-list")
@Operation(summary = "批量删除答卷")
@Parameter(name = "ids", description = "编号列表", required = true)
@PreAuthorize("@ss.hasPermission('prison:assessment-answer:delete')")
public CommonResult<Boolean> deleteAssessmentAnswerList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
assessmentAnswerService.deleteAssessmentAnswerListByIds(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得答卷")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('prison:assessment-answer:query')")
public CommonResult<AssessmentAnswerRespVO> getAssessmentAnswer(@RequestParam("id") Long id) {
AssessmentAnswerDO assessmentAnswer = assessmentAnswerService.getAssessmentAnswer(id);
return success(BeanUtils.toBean(assessmentAnswer, AssessmentAnswerRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得答卷分页")
@PreAuthorize("@ss.hasPermission('prison:assessment-answer:query')")
public CommonResult<PageResult<AssessmentAnswerRespVO>> getAssessmentAnswerPage(@Valid AssessmentAnswerPageReqVO pageReqVO) {
PageResult<AssessmentAnswerDO> pageResult = assessmentAnswerService.getAssessmentAnswerPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, AssessmentAnswerRespVO.class));
}
@PostMapping("/start")
@Operation(summary = "开始答题")
@PreAuthorize("@ss.hasPermission('prison:assessment-answer:create')")
public CommonResult<Long> startAnswer(@RequestParam("assessmentRecordId") Long assessmentRecordId,
@RequestParam("prisonerId") Long prisonerId) {
return success(assessmentAnswerService.startAnswer(assessmentRecordId, prisonerId));
}
@PostMapping("/submit")
@Operation(summary = "提交答卷")
@PreAuthorize("@ss.hasPermission('prison:assessment-answer:update')")
public CommonResult<Boolean> submitAnswer(@Valid @RequestBody AssessmentAnswerSubmitReqVO submitReqVO) {
assessmentAnswerService.submitAnswer(submitReqVO);
return success(true);
}
@GetMapping("/pending-score-page")
@Operation(summary = "获取待评分答卷列表")
@PreAuthorize("@ss.hasPermission('prison:assessment-answer:query')")
public CommonResult<PageResult<AssessmentAnswerRespVO>> getPendingScorePage(@Valid AssessmentAnswerPageReqVO pageReqVO) {
PageResult<AssessmentAnswerDO> pageResult = assessmentAnswerService.getPendingScorePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, AssessmentAnswerRespVO.class));
}
@PostMapping("/manual-score")
@Operation(summary = "人工评分")
@PreAuthorize("@ss.hasPermission('prison:assessment-answer:update')")
public CommonResult<Boolean> manualScore(@Valid @RequestBody AssessmentAnswerManualScoreReqVO scoreReqVO) {
assessmentAnswerService.manualScore(scoreReqVO);
return success(true);
}
@GetMapping("/get-by-prisoner")
@Operation(summary = "根据囚犯ID和测评记录ID获取答卷")
@PreAuthorize("@ss.hasPermission('prison:assessment-answer:query')")
public CommonResult<AssessmentAnswerRespVO> getByPrisonerAndRecord(
@RequestParam("prisonerId") Long prisonerId,
@RequestParam("assessmentRecordId") Long assessmentRecordId) {
AssessmentAnswerDO assessmentAnswer = assessmentAnswerService.getByPrisonerAndRecord(prisonerId, assessmentRecordId);
return success(BeanUtils.toBean(assessmentAnswer, AssessmentAnswerRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出答卷 Excel")
@PreAuthorize("@ss.hasPermission('prison:assessment-answer:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportAssessmentAnswerExcel(@Valid AssessmentAnswerPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<AssessmentAnswerDO> list = assessmentAnswerService.getAssessmentAnswerPage(pageReqVO).getList();
ExcelUtils.write(response, "答卷详情.xls", "数据", AssessmentAnswerRespVO.class,
BeanUtils.toBean(list, AssessmentAnswerRespVO.class));
}
}

View File

@ -0,0 +1,134 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.constraints.*;
import jakarta.validation.*;
import jakarta.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentRecordDO;
import cn.iocoder.yudao.module.prison.service.assessment.AssessmentRecordService;
@Tag(name = "管理后台 - 测评记录")
@RestController
@RequestMapping("/prison/assessment-record")
@Validated
public class AssessmentRecordController {
@Resource
private AssessmentRecordService assessmentRecordService;
@PostMapping("/create")
@Operation(summary = "创建测评记录")
@PreAuthorize("@ss.hasPermission('prison:assessment-record:create')")
public CommonResult<Long> createAssessmentRecord(@Valid @RequestBody AssessmentRecordSaveReqVO createReqVO) {
return success(assessmentRecordService.createAssessmentRecord(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新测评记录")
@PreAuthorize("@ss.hasPermission('prison:assessment-record:update')")
public CommonResult<Boolean> updateAssessmentRecord(@Valid @RequestBody AssessmentRecordSaveReqVO updateReqVO) {
assessmentRecordService.updateAssessmentRecord(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除测评记录")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:assessment-record:delete')")
public CommonResult<Boolean> deleteAssessmentRecord(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
assessmentRecordService.deleteAssessmentRecord(id);
return success(true);
}
@DeleteMapping("/delete-list")
@Operation(summary = "批量删除测评记录")
@Parameter(name = "ids", description = "编号列表", required = true)
@PreAuthorize("@ss.hasPermission('prison:assessment-record:delete')")
public CommonResult<Boolean> deleteAssessmentRecordList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
assessmentRecordService.deleteAssessmentRecordListByIds(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得测评记录")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('prison:assessment-record:query')")
public CommonResult<AssessmentRecordRespVO> getAssessmentRecord(@RequestParam("id") Long id) {
AssessmentRecordDO assessmentRecord = assessmentRecordService.getAssessmentRecord(id);
return success(BeanUtils.toBean(assessmentRecord, AssessmentRecordRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得测评记录分页")
@PreAuthorize("@ss.hasPermission('prison:assessment-record:query')")
public CommonResult<PageResult<AssessmentRecordRespVO>> getAssessmentRecordPage(@Valid AssessmentRecordPageReqVO pageReqVO) {
PageResult<AssessmentRecordDO> pageResult = assessmentRecordService.getAssessmentRecordPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, AssessmentRecordRespVO.class));
}
@PostMapping("/initiate")
@Operation(summary = "发起测评")
@PreAuthorize("@ss.hasPermission('prison:assessment-record:create')")
public CommonResult<Long> initiateAssessment(@Valid @RequestBody AssessmentRecordSaveReqVO reqVO) {
return success(assessmentRecordService.initiateAssessment(reqVO));
}
@PostMapping("/cancel")
@Operation(summary = "取消测评")
@PreAuthorize("@ss.hasPermission('prison:assessment-record:update')")
public CommonResult<Boolean> cancelAssessment(@RequestParam("id") Long id) {
assessmentRecordService.cancelAssessment(id);
return success(true);
}
@PostMapping("/start")
@Operation(summary = "启动测评")
@PreAuthorize("@ss.hasPermission('prison:assessment-record:update')")
public CommonResult<Boolean> startAssessment(@RequestParam("id") Long id) {
assessmentRecordService.startAssessment(id);
return success(true);
}
@PostMapping("/finish")
@Operation(summary = "结束测评")
@PreAuthorize("@ss.hasPermission('prison:assessment-record:update')")
public CommonResult<Boolean> finishAssessment(@RequestParam("id") Long id) {
assessmentRecordService.finishAssessment(id);
return success(true);
}
@GetMapping("/export-excel")
@Operation(summary = "导出测评记录 Excel")
@PreAuthorize("@ss.hasPermission('prison:assessment-record:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportAssessmentRecordExcel(@Valid AssessmentRecordPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<AssessmentRecordDO> list = assessmentRecordService.getAssessmentRecordPage(pageReqVO).getList();
ExcelUtils.write(response, "测评记录.xls", "数据", AssessmentRecordRespVO.class,
BeanUtils.toBean(list, AssessmentRecordRespVO.class));
}
}

View File

@ -0,0 +1,143 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.constraints.*;
import jakarta.validation.*;
import jakarta.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentResultDO;
import cn.iocoder.yudao.module.prison.service.assessment.AssessmentResultService;
@Tag(name = "管理后台 - 测评结果")
@RestController
@RequestMapping("/prison/assessment-result")
@Validated
public class AssessmentResultController {
@Resource
private AssessmentResultService assessmentResultService;
@PostMapping("/create")
@Operation(summary = "创建测评结果")
@PreAuthorize("@ss.hasPermission('prison:assessment-result:create')")
public CommonResult<Long> createAssessmentResult(@Valid @RequestBody AssessmentResultSaveReqVO createReqVO) {
return success(assessmentResultService.createAssessmentResult(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新测评结果")
@PreAuthorize("@ss.hasPermission('prison:assessment-result:update')")
public CommonResult<Boolean> updateAssessmentResult(@Valid @RequestBody AssessmentResultSaveReqVO updateReqVO) {
assessmentResultService.updateAssessmentResult(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除测评结果")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:assessment-result:delete')")
public CommonResult<Boolean> deleteAssessmentResult(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
assessmentResultService.deleteAssessmentResult(id);
return success(true);
}
@DeleteMapping("/delete-list")
@Operation(summary = "批量删除测评结果")
@Parameter(name = "ids", description = "编号列表", required = true)
@PreAuthorize("@ss.hasPermission('prison:assessment-result:delete')")
public CommonResult<Boolean> deleteAssessmentResultList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
assessmentResultService.deleteAssessmentResultListByIds(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得测评结果")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('prison:assessment-result:query')")
public CommonResult<AssessmentResultRespVO> getAssessmentResult(@RequestParam("id") Long id) {
AssessmentResultDO assessmentResult = assessmentResultService.getAssessmentResult(id);
return success(BeanUtils.toBean(assessmentResult, AssessmentResultRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得测评结果分页")
@PreAuthorize("@ss.hasPermission('prison:assessment-result:query')")
public CommonResult<PageResult<AssessmentResultRespVO>> getAssessmentResultPage(@Valid AssessmentResultPageReqVO pageReqVO) {
PageResult<AssessmentResultDO> pageResult = assessmentResultService.getAssessmentResultPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, AssessmentResultRespVO.class));
}
@GetMapping("/list-by-answer")
@Operation(summary = "根据答卷ID获取所有结果")
@PreAuthorize("@ss.hasPermission('prison:assessment-result:query')")
public CommonResult<List<AssessmentResultRespVO>> getResultsByAnswerId(@RequestParam("answerId") Long answerId) {
List<AssessmentResultDO> results = assessmentResultService.getResultsByAnswerId(answerId);
return success(BeanUtils.toBean(results, AssessmentResultRespVO.class));
}
@GetMapping("/list-by-assessment-record")
@Operation(summary = "根据测评记录ID获取所有结果")
@PreAuthorize("@ss.hasPermission('prison:assessment-result:query')")
public CommonResult<List<AssessmentResultRespVO>> getResultsByAssessmentRecordId(@RequestParam("assessmentRecordId") Long assessmentRecordId) {
List<AssessmentResultDO> results = assessmentResultService.getResultsByAssessmentRecordId(assessmentRecordId);
return success(BeanUtils.toBean(results, AssessmentResultRespVO.class));
}
@GetMapping("/need-manual-review")
@Operation(summary = "获取需要人工评阅的结果列表")
@PreAuthorize("@ss.hasPermission('prison:assessment-result:query')")
public CommonResult<List<AssessmentResultRespVO>> getNeedManualReviewList() {
List<AssessmentResultDO> results = assessmentResultService.getNeedManualReviewList();
return success(BeanUtils.toBean(results, AssessmentResultRespVO.class));
}
@PostMapping("/manual-review")
@Operation(summary = "人工评阅")
@PreAuthorize("@ss.hasPermission('prison:assessment-result:update')")
public CommonResult<Boolean> manualReview(@Valid @RequestBody AssessmentResultManualReviewReqVO reviewReqVO) {
assessmentResultService.manualReview(reviewReqVO);
return success(true);
}
@PostMapping("/auto-score")
@Operation(summary = "自动评分")
@PreAuthorize("@ss.hasPermission('prison:assessment-result:update')")
public CommonResult<Boolean> autoScore(@RequestParam("answerId") Long answerId) {
assessmentResultService.autoScore(answerId);
return success(true);
}
@GetMapping("/export-excel")
@Operation(summary = "导出测评结果 Excel")
@PreAuthorize("@ss.hasPermission('prison:assessment-result:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportAssessmentResultExcel(@Valid AssessmentResultPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<AssessmentResultDO> list = assessmentResultService.getAssessmentResultPage(pageReqVO).getList();
ExcelUtils.write(response, "测评结果.xls", "数据", AssessmentResultRespVO.class,
BeanUtils.toBean(list, AssessmentResultRespVO.class));
}
}

View File

@ -0,0 +1,95 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.constraints.*;
import jakarta.validation.*;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentStatisticsDO;
import cn.iocoder.yudao.module.prison.service.assessment.AssessmentStatisticsService;
import java.math.BigDecimal;
import java.util.Map;
@Tag(name = "管理后台 - 测评统计")
@RestController
@RequestMapping("/prison/assessment-statistics")
@Validated
public class AssessmentStatisticsController {
@Resource
private AssessmentStatisticsService assessmentStatisticsService;
@GetMapping("/get")
@Operation(summary = "获得测评统计")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('prison:assessment-statistics:query')")
public CommonResult<AssessmentStatisticsRespVO> getAssessmentStatistics(@RequestParam("id") Long id) {
AssessmentStatisticsDO statistics = assessmentStatisticsService.getAssessmentStatistics(id);
return success(BeanUtils.toBean(statistics, AssessmentStatisticsRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得测评统计分页")
@PreAuthorize("@ss.hasPermission('prison:assessment-statistics:query')")
public CommonResult<PageResult<AssessmentStatisticsRespVO>> getAssessmentStatisticsPage(@Valid AssessmentStatisticsPageReqVO pageReqVO) {
PageResult<AssessmentStatisticsDO> pageResult = assessmentStatisticsService.getAssessmentStatisticsPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, AssessmentStatisticsRespVO.class));
}
@GetMapping("/generate")
@Operation(summary = "生成测评统计")
@Parameter(name = "assessmentRecordId", description = "测评记录ID", required = true)
@PreAuthorize("@ss.hasPermission('prison:assessment-statistics:create')")
public CommonResult<AssessmentStatisticsRespVO> generateStatistics(@RequestParam("assessmentRecordId") Long assessmentRecordId) {
AssessmentStatisticsDO statistics = assessmentStatisticsService.generateStatistics(assessmentRecordId);
return success(BeanUtils.toBean(statistics, AssessmentStatisticsRespVO.class));
}
@GetMapping("/completion-rate")
@Operation(summary = "获取测评完成率")
@Parameter(name = "assessmentRecordId", description = "测评记录ID", required = true)
@PreAuthorize("@ss.hasPermission('prison:assessment-statistics:query')")
public CommonResult<BigDecimal> getCompletionRate(@RequestParam("assessmentRecordId") Long assessmentRecordId) {
return success(assessmentStatisticsService.getCompletionRate(assessmentRecordId));
}
@GetMapping("/score-distribution")
@Operation(summary = "获取分数分布")
@Parameter(name = "assessmentRecordId", description = "测评记录ID", required = true)
@PreAuthorize("@ss.hasPermission('prison:assessment-statistics:query')")
public CommonResult<Map<String, Integer>> getScoreDistribution(@RequestParam("assessmentRecordId") Long assessmentRecordId) {
return success(assessmentStatisticsService.getScoreDistribution(assessmentRecordId));
}
@GetMapping("/risk-distribution")
@Operation(summary = "获取风险分布")
@Parameter(name = "assessmentRecordId", description = "测评记录ID", required = true)
@PreAuthorize("@ss.hasPermission('prison:assessment-statistics:query')")
public CommonResult<Map<String, Integer>> getRiskDistribution(@RequestParam("assessmentRecordId") Long assessmentRecordId) {
return success(assessmentStatisticsService.getRiskDistribution(assessmentRecordId));
}
@GetMapping("/report")
@Operation(summary = "获取测评分析报告")
@Parameter(name = "assessmentRecordId", description = "测评记录ID", required = true)
@PreAuthorize("@ss.hasPermission('prison:assessment-statistics:query')")
public CommonResult<AssessmentReportRespVO> getAssessmentReport(@RequestParam("assessmentRecordId") Long assessmentRecordId) {
return success(assessmentStatisticsService.getAssessmentReport(assessmentRecordId));
}
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
/**
* 人工评分 Request VO
*
* @author 芋道源码
*/
@Data
public class AssessmentAnswerManualScoreReqVO {
@Schema(description = "答卷ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "答卷ID不能为空")
private Long id;
@Schema(description = "主观题得分", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "主观题得分不能为空")
private BigDecimal subjectiveScore;
@Schema(description = "评语")
private String comment;
}

View File

@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
/**
* 答卷详情分页查询 Request VO
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AssessmentAnswerPageReqVO extends PageParam {
@Schema(description = "关联测评记录ID")
private Long assessmentRecordId;
@Schema(description = "囚犯ID")
private Long prisonerId;
@Schema(description = "囚犯编号")
private String prisonerCode;
@Schema(description = "囚犯姓名")
private String prisonerName;
@Schema(description = "监区ID")
private Long areaId;
@Schema(description = "监室ID")
private Long cellId;
@Schema(description = "答卷状态1-待答题 2-答题中 3-已提交 4-已评分 5-已完成")
private Integer status;
@Schema(description = "开始答题时间")
private LocalDateTime[] startTime;
@Schema(description = "提交时间")
private LocalDateTime[] submitTime;
}

View File

@ -0,0 +1,91 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 答卷详情 Response VO
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AssessmentAnswerRespVO {
@Schema(description = "答卷ID", example = "1024")
private Long id;
@Schema(description = "关联测评记录ID")
private Long assessmentRecordId;
@Schema(description = "测评名称")
private String assessmentName;
@Schema(description = "囚犯ID")
private Long prisonerId;
@Schema(description = "囚犯编号")
private String prisonerCode;
@Schema(description = "囚犯姓名")
private String prisonerName;
@Schema(description = "监区ID")
private Long areaId;
@Schema(description = "监区名称")
private String areaName;
@Schema(description = "监室ID")
private Long cellId;
@Schema(description = "监室名称")
private String cellName;
@Schema(description = "答卷状态1-待答题 2-答题中 3-已提交 4-已评分 5-已完成")
private Integer status;
@Schema(description = "开始答题时间")
private LocalDateTime startTime;
@Schema(description = "提交时间")
private LocalDateTime submitTime;
@Schema(description = "答题用时(秒)")
private Integer duration;
@Schema(description = "客观题得分")
private BigDecimal objectiveScore;
@Schema(description = "主观题得分")
private BigDecimal subjectiveScore;
@Schema(description = "总分")
private BigDecimal totalScore;
@Schema(description = "是否及格")
private Boolean passed;
@Schema(description = "评语")
private String comment;
@Schema(description = "评卷人ID")
private Long scorerId;
@Schema(description = "评卷人名称")
private String scorerName;
@Schema(description = "评分时间")
private LocalDateTime scoreTime;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,78 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 答卷详情创建/更新 Request VO
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AssessmentAnswerSaveReqVO {
@Schema(description = "答卷ID", example = "1024")
private Long id;
@Schema(description = "关联测评记录ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "测评记录不能为空")
private Long assessmentRecordId;
@Schema(description = "囚犯ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "囚犯不能为空")
private Long prisonerId;
@Schema(description = "囚犯编号")
private String prisonerCode;
@Schema(description = "囚犯姓名")
private String prisonerName;
@Schema(description = "监区ID")
private Long areaId;
@Schema(description = "监区名称")
private String areaName;
@Schema(description = "监室ID")
private Long cellId;
@Schema(description = "监室名称")
private String cellName;
@Schema(description = "答卷状态1-待答题 2-答题中 3-已提交 4-已评分 5-已完成")
private Integer status;
@Schema(description = "开始答题时间")
private LocalDateTime startTime;
@Schema(description = "提交时间")
private LocalDateTime submitTime;
@Schema(description = "答题用时(秒)")
private Integer duration;
@Schema(description = "客观题得分")
private BigDecimal objectiveScore;
@Schema(description = "主观题得分")
private BigDecimal subjectiveScore;
@Schema(description = "总分")
private BigDecimal totalScore;
@Schema(description = "是否及格")
private Boolean passed;
@Schema(description = "评语")
private String comment;
@Schema(description = "及格分数")
private BigDecimal passScore;
}

View File

@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 答卷提交 Request VO
*
* @author 芋道源码
*/
@Data
public class AssessmentAnswerSubmitReqVO {
@Schema(description = "答卷ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "答卷ID不能为空")
private Long id;
@Schema(description = "答题详情JSON")
private String answers;
}

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
/**
* 测评记录分页查询 Request VO
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AssessmentRecordPageReqVO extends PageParam {
@Schema(description = "测评名称", example = "心理测评")
private String name;
@Schema(description = "关联问卷模板ID")
private Long questionnaireId;
@Schema(description = "测评类型1-心理测评 2-行为评估 3-满意度调查")
private Integer type;
@Schema(description = "发起测评的监狱/监区ID")
private Long areaId;
@Schema(description = "测评状态1-未开始 2-进行中 3-已完成 4-已取消")
private Integer status;
@Schema(description = "计划开始时间")
private LocalDateTime[] planStartTime;
@Schema(description = "计划结束时间")
private LocalDateTime[] planEndTime;
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,81 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 测评记录 Response VO
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AssessmentRecordRespVO {
@Schema(description = "测评记录ID", example = "1024")
private Long id;
@Schema(description = "测评名称", example = "心理测评")
private String name;
@Schema(description = "关联问卷模板ID")
private Long questionnaireId;
@Schema(description = "问卷模板名称")
private String questionnaireName;
@Schema(description = "测评类型1-心理测评 2-行为评估 3-满意度调查")
private Integer type;
@Schema(description = "发起测评的监狱/监区ID")
private Long areaId;
@Schema(description = "发起测评的监狱/监区名称")
private String areaName;
@Schema(description = "发起人ID")
private Long initiatorId;
@Schema(description = "发起人名称")
private String initiatorName;
@Schema(description = "测评状态1-未开始 2-进行中 3-已完成 4-已取消")
private Integer status;
@Schema(description = "计划开始时间")
private LocalDateTime planStartTime;
@Schema(description = "计划结束时间")
private LocalDateTime planEndTime;
@Schema(description = "实际开始时间")
private LocalDateTime actualStartTime;
@Schema(description = "实际结束时间")
private LocalDateTime actualEndTime;
@Schema(description = "参与人数")
private Integer participantCount;
@Schema(description = "完成人数")
private Integer completedCount;
@Schema(description = "测评说明")
private String description;
@Schema(description = "是否启用自动评分")
private Boolean enableAutoScore;
@Schema(description = "及格分数")
private BigDecimal passScore;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 测评记录创建/更新 Request VO
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AssessmentRecordSaveReqVO {
@Schema(description = "测评记录ID", example = "1024")
private Long id;
@Schema(description = "测评名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "心理测评")
@NotNull(message = "测评名称不能为空")
private String name;
@Schema(description = "关联问卷模板ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "问卷模板不能为空")
private Long questionnaireId;
@Schema(description = "测评类型1-心理测评 2-行为评估 3-满意度调查", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "测评类型不能为空")
private Integer type;
@Schema(description = "发起测评的监狱/监区ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "监区不能为空")
private Long areaId;
@Schema(description = "发起测评的监狱/监区名称")
private String areaName;
@Schema(description = "发起人ID")
private Long initiatorId;
@Schema(description = "发起人名称")
private String initiatorName;
@Schema(description = "测评状态1-未开始 2-进行中 3-已完成 4-已取消")
private Integer status;
@Schema(description = "计划开始时间")
private LocalDateTime planStartTime;
@Schema(description = "计划结束时间")
private LocalDateTime planEndTime;
@Schema(description = "测评说明")
private String description;
@Schema(description = "是否启用自动评分")
private Boolean enableAutoScore;
@Schema(description = "及格分数")
private BigDecimal passScore;
}

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 测评分析报告 Response VO
*
* @author 芋道源码
*/
@Data
public class AssessmentReportRespVO {
@Schema(description = "关联测评记录ID")
private Long assessmentRecordId;
@Schema(description = "测评名称")
private String assessmentName;
@Schema(description = "总参与人数")
private Integer totalCount;
@Schema(description = "已完成人数")
private Integer completedCount;
@Schema(description = "完成率(%)")
private BigDecimal completionRate;
@Schema(description = "平均分")
private BigDecimal averageScore;
@Schema(description = "最高分")
private BigDecimal highestScore;
@Schema(description = "最低分")
private BigDecimal lowestScore;
@Schema(description = "及格人数")
private Integer passedCount;
@Schema(description = "及格率(%)")
private BigDecimal passRate;
@Schema(description = "优秀人数90分以上")
private Integer excellentCount;
@Schema(description = "优秀率(%)")
private BigDecimal excellentRate;
@Schema(description = "风险人数60分以下")
private Integer riskCount;
@Schema(description = "风险率(%)")
private BigDecimal riskRate;
@Schema(description = "分数分布")
private Map<String, Integer> scoreDistribution;
@Schema(description = "风险分布")
private Map<String, Integer> riskDistribution;
@Schema(description = "分析建议")
private String analysisSuggestion;
@Schema(description = "生成时间")
private LocalDateTime generatedTime;
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 人工评阅 Request VO
*
* @author 芋道源码
*/
@Data
public class AssessmentResultManualReviewReqVO {
@Schema(description = "结果ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "结果ID不能为空")
private Long id;
@Schema(description = "人工评分", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "评分不能为空")
private BigDecimal manualScore;
@Schema(description = "人工评语")
private String manualComment;
@Schema(description = "评阅人ID")
private Long reviewerId;
@Schema(description = "评阅人名称")
private String reviewerName;
}

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
/**
* 测评结果分页查询 Request VO
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AssessmentResultPageReqVO extends PageParam {
@Schema(description = "关联答卷ID")
private Long answerId;
@Schema(description = "关联测评记录ID")
private Long assessmentRecordId;
@Schema(description = "囚犯ID")
private Long prisonerId;
@Schema(description = "关联题目ID")
private Long questionId;
@Schema(description = "题目类型1-单选 2-多选 3-判断 4-填空 5-问答")
private Integer questionType;
@Schema(description = "是否正确")
private Boolean correct;
@Schema(description = "是否需要人工评阅")
private Boolean needManualReview;
@Schema(description = "人工评阅状态1-待评阅 2-已评阅")
private Integer manualReviewStatus;
}

View File

@ -0,0 +1,90 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 测评结果 Response VO
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AssessmentResultRespVO {
@Schema(description = "结果ID", example = "1024")
private Long id;
@Schema(description = "关联答卷ID")
private Long answerId;
@Schema(description = "关联测评记录ID")
private Long assessmentRecordId;
@Schema(description = "囚犯ID")
private Long prisonerId;
@Schema(description = "囚犯编号")
private String prisonerCode;
@Schema(description = "囚犯姓名")
private String prisonerName;
@Schema(description = "关联题目ID")
private Long questionId;
@Schema(description = "题目编号")
private String questionCode;
@Schema(description = "题目内容")
private String questionContent;
@Schema(description = "题目类型1-单选 2-多选 3-判断 4-填空 5-问答")
private Integer questionType;
@Schema(description = "题目分值")
private BigDecimal questionScore;
@Schema(description = "囚犯答案")
private String answer;
@Schema(description = "正确答案(客观题)")
private String correctAnswer;
@Schema(description = "是否正确")
private Boolean correct;
@Schema(description = "得分")
private BigDecimal score;
@Schema(description = "是否需要人工评阅")
private Boolean needManualReview;
@Schema(description = "人工评阅状态1-待评阅 2-已评阅")
private Integer manualReviewStatus;
@Schema(description = "人工评分")
private BigDecimal manualScore;
@Schema(description = "人工评语")
private String manualComment;
@Schema(description = "评阅人ID")
private Long reviewerId;
@Schema(description = "评阅人名称")
private String reviewerName;
@Schema(description = "评阅时间")
private LocalDateTime reviewTime;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,80 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 测评结果创建/更新 Request VO
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AssessmentResultSaveReqVO {
@Schema(description = "结果ID", example = "1024")
private Long id;
@Schema(description = "关联答卷ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "答卷ID不能为空")
private Long answerId;
@Schema(description = "关联测评记录ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "测评记录ID不能为空")
private Long assessmentRecordId;
@Schema(description = "囚犯ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "囚犯ID不能为空")
private Long prisonerId;
@Schema(description = "囚犯编号")
private String prisonerCode;
@Schema(description = "囚犯姓名")
private String prisonerName;
@Schema(description = "关联题目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "题目ID不能为空")
private Long questionId;
@Schema(description = "题目编号")
private String questionCode;
@Schema(description = "题目内容")
private String questionContent;
@Schema(description = "题目类型1-单选 2-多选 3-判断 4-填空 5-问答")
private Integer questionType;
@Schema(description = "题目分值")
private BigDecimal questionScore;
@Schema(description = "囚犯答案")
private String answer;
@Schema(description = "正确答案(客观题)")
private String correctAnswer;
@Schema(description = "是否正确")
private Boolean correct;
@Schema(description = "得分")
private BigDecimal score;
@Schema(description = "是否需要人工评阅")
private Boolean needManualReview;
@Schema(description = "人工评阅状态1-待评阅 2-已评阅")
private Integer manualReviewStatus;
@Schema(description = "人工评分")
private BigDecimal manualScore;
@Schema(description = "人工评语")
private String manualComment;
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
/**
* 测评统计分页查询 Request VO
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AssessmentStatisticsPageReqVO extends PageParam {
@Schema(description = "关联测评记录ID")
private Long assessmentRecordId;
@Schema(description = "测评名称")
private String assessmentName;
}

View File

@ -0,0 +1,76 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 测评统计 Response VO
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AssessmentStatisticsRespVO {
@Schema(description = "统计ID", example = "1024")
private Long id;
@Schema(description = "关联测评记录ID")
private Long assessmentRecordId;
@Schema(description = "测评名称")
private String assessmentName;
@Schema(description = "总参与人数")
private Integer totalCount;
@Schema(description = "已完成人数")
private Integer completedCount;
@Schema(description = "完成率")
private BigDecimal completionRate;
@Schema(description = "平均分")
private BigDecimal averageScore;
@Schema(description = "最高分")
private BigDecimal highestScore;
@Schema(description = "最低分")
private BigDecimal lowestScore;
@Schema(description = "及格人数")
private Integer passedCount;
@Schema(description = "及格率")
private BigDecimal passRate;
@Schema(description = "优秀人数90分以上")
private Integer excellentCount;
@Schema(description = "优秀率")
private BigDecimal excellentRate;
@Schema(description = "风险人数60分以下")
private Integer riskCount;
@Schema(description = "风险率")
private BigDecimal riskRate;
@Schema(description = "风险分布")
private Map<String, Integer> riskDistribution;
@Schema(description = "分数分布")
private Map<String, Integer> scoreDistribution;
@Schema(description = "统计时间")
private LocalDateTime statisticsTime;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

View File

@ -9,6 +9,8 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.*;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.servlet.http.*;
import java.util.*;
import java.io.IOException;
@ -61,7 +63,7 @@ public class PrisonCellController {
@Operation(summary = "删除监室信息")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:cell:delete')")
public CommonResult<Boolean> deleteCell(@RequestParam("id") Long id) {
public CommonResult<Boolean> deleteCell(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
cellService.deleteCell(id);
return success(true);
}
@ -69,8 +71,8 @@ public class PrisonCellController {
@DeleteMapping("/delete-list")
@Parameter(name = "ids", description = "编号", required = true)
@Operation(summary = "批量删除监室信息")
@PreAuthorize("@ss.hasPermission('prison:cell:delete')")
public CommonResult<Boolean> deleteCellList(@RequestParam("ids") List<Long> ids) {
@PreAuthorize("@ss.hasPermission('prison:cell:delete')")
public CommonResult<Boolean> deleteCellList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
cellService.deleteCellListByIds(ids);
return success(true);
}
@ -111,7 +113,7 @@ public class PrisonCellController {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<CellRespVO> list = cellService.getCellPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "监室信息.xls", "数据", CellRespVO.class, list);
ExcelUtils.write(response, "监室信息.xlsx", "数据", CellRespVO.class, list);
}
}
}

View File

@ -16,7 +16,7 @@ public class CellPageReqVO extends PageParam {
@Schema(description = "所属监区ID", example = "9889")
private Long areaId;
@Schema(description = "监室名称", example = "张三")
@Schema(description = "监室名称", example = "101监室")
private String name;
@Schema(description = "监室编码")
@ -25,7 +25,7 @@ public class CellPageReqVO extends PageParam {
@Schema(description = "床位数量")
private Integer capacity;
@Schema(description = "当前人数", example = "31423")
@Schema(description = "当前人数", example = "5")
private Integer currentCount;
@Schema(description = "排序")
@ -34,11 +34,11 @@ public class CellPageReqVO extends PageParam {
@Schema(description = "状态1-启用 2-禁用", example = "1")
private Integer status;
@Schema(description = "备注", example = "随便")
@Schema(description = "备注", example = "正常使用的监室")
private String remark;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}
}

View File

@ -16,7 +16,7 @@ public class CellSaveReqVO {
@NotNull(message = "所属监区ID不能为空")
private Long areaId;
@Schema(description = "监室名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
@Schema(description = "监室名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "101监室")
@NotEmpty(message = "监室名称不能为空")
private String name;
@ -24,20 +24,25 @@ public class CellSaveReqVO {
@NotEmpty(message = "监室编码不能为空")
private String code;
@Schema(description = "床位数量")
@Schema(description = "床位数量", example = "10")
@Min(value = 0, message = "床位数量不能小于0")
private Integer capacity;
@Schema(description = "当前人数", example = "31423")
@Schema(description = "当前人数", example = "5")
@Min(value = 0, message = "当前人数不能小于0")
private Integer currentCount;
@Schema(description = "排序")
@Schema(description = "排序", example = "1")
@Min(value = 0, message = "排序不能小于0")
private Integer sort;
@Schema(description = "状态1-启用 2-禁用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "状态1-启用 2-禁用不能为空")
@Min(value = 1, message = "状态最小值为1")
@Max(value = 2, message = "状态最大值为2")
private Integer status;
@Schema(description = "备注", example = "随便")
@Schema(description = "备注", example = "正常使用的监室")
private String remark;
}
}

View File

@ -57,7 +57,7 @@ public class PrisonConsumptionController {
@Operation(summary = "删除消费记录")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:consumption:delete')")
public CommonResult<Boolean> deleteConsumption(@RequestParam("id") Long id) {
public CommonResult<Boolean> deleteConsumption(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
consumptionService.deleteConsumption(id);
return success(true);
}
@ -66,7 +66,7 @@ public class PrisonConsumptionController {
@Parameter(name = "ids", description = "编号", required = true)
@Operation(summary = "批量删除消费记录")
@PreAuthorize("@ss.hasPermission('prison:consumption:delete')")
public CommonResult<Boolean> deleteConsumptionList(@RequestParam("ids") List<Long> ids) {
public CommonResult<Boolean> deleteConsumptionList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
consumptionService.deleteConsumptionListByIds(ids);
return success(true);
}

View File

@ -22,6 +22,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -73,7 +75,7 @@ public class PrisonerController {
@Operation(summary = "删除服刑人员")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:prisoner:delete')")
public CommonResult<Boolean> deletePrisoner(@RequestParam("id") Long id) {
public CommonResult<Boolean> deletePrisoner(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
prisonerService.deletePrisoner(id);
return success(true);
}
@ -82,7 +84,7 @@ public class PrisonerController {
@Operation(summary = "批量删除服刑人员")
@Parameter(name = "ids", description = "编号列表", required = true)
@PreAuthorize("@ss.hasPermission('prison:prisoner:delete')")
public CommonResult<Boolean> deletePrisonerList(@RequestParam("ids") List<Long> ids) {
public CommonResult<Boolean> deletePrisonerList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
prisonerService.deletePrisonerList(ids);
return success(true);
}

View File

@ -11,6 +11,8 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@ -50,7 +52,7 @@ public class PrisonerAreaLogController {
@Operation(summary = "删除罪犯监区变动记录")
@Parameter(name = "id", description = "记录ID", required = true)
@PreAuthorize("@ss.hasPermission('prison:prisoner-area-log:delete')")
public CommonResult<Boolean> deletePrisonerAreaLog(@RequestParam("id") Long id) {
public CommonResult<Boolean> deletePrisonerAreaLog(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
prisonerAreaLogService.deletePrisonerAreaLog(id);
return success(true);
}
@ -58,7 +60,7 @@ public class PrisonerAreaLogController {
@DeleteMapping("/delete-list")
@Operation(summary = "批量删除罪犯监区变动记录")
@PreAuthorize("@ss.hasPermission('prison:prisoner-area-log:delete')")
public CommonResult<Boolean> deletePrisonerAreaLogList(@RequestParam("ids") List<Long> ids) {
public CommonResult<Boolean> deletePrisonerAreaLogList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
prisonerAreaLogService.deletePrisonerAreaLogList(ids);
return success(true);
}

View File

@ -57,7 +57,7 @@ public class PrisonQuestionController {
@Operation(summary = "删除问卷问题")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:question:delete')")
public CommonResult<Boolean> deleteQuestion(@RequestParam("id") Long id) {
public CommonResult<Boolean> deleteQuestion(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
questionService.deleteQuestion(id);
return success(true);
}
@ -66,7 +66,7 @@ public class PrisonQuestionController {
@Parameter(name = "ids", description = "编号", required = true)
@Operation(summary = "批量删除问卷问题")
@PreAuthorize("@ss.hasPermission('prison:question:delete')")
public CommonResult<Boolean> deleteQuestionList(@RequestParam("ids") List<Long> ids) {
public CommonResult<Boolean> deleteQuestionList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
questionService.deleteQuestionListByIds(ids);
return success(true);
}

View File

@ -57,7 +57,7 @@ public class PrisonQuestionnaireController {
@Operation(summary = "删除问卷模板")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:questionnaire:delete')")
public CommonResult<Boolean> deleteQuestionnaire(@RequestParam("id") Long id) {
public CommonResult<Boolean> deleteQuestionnaire(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
questionnaireService.deleteQuestionnaire(id);
return success(true);
}
@ -66,7 +66,7 @@ public class PrisonQuestionnaireController {
@Parameter(name = "ids", description = "编号", required = true)
@Operation(summary = "批量删除问卷模板")
@PreAuthorize("@ss.hasPermission('prison:questionnaire:delete')")
public CommonResult<Boolean> deleteQuestionnaireList(@RequestParam("ids") List<Long> ids) {
public CommonResult<Boolean> deleteQuestionnaireList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
questionnaireService.deleteQuestionnaireListByIds(ids);
return success(true);
}

View File

@ -36,4 +36,21 @@ public class QuestionnairePageReqVO extends PageParam {
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
// ==================== 新增字段 ====================
@Schema(description = "封面图片URL")
private String coverImage;
@Schema(description = "填写说明")
private String instruction;
@Schema(description = "预计耗时(分钟)")
private Integer estimatedTime;
@Schema(description = "分区数量")
private Integer partCount;
@Schema(description = "是否允许匿名")
private Boolean allowAnonymous;
}

View File

@ -45,4 +45,26 @@ public class QuestionnaireRespVO {
@ExcelProperty("创建时间")
private LocalDateTime createTime;
// ==================== 新增字段 ====================
@Schema(description = "封面图片URL")
@ExcelProperty("封面图片URL")
private String coverImage;
@Schema(description = "填写说明")
@ExcelProperty("填写说明")
private String instruction;
@Schema(description = "预计耗时(分钟)")
@ExcelProperty("预计耗时(分钟)")
private Integer estimatedTime;
@Schema(description = "分区数量")
@ExcelProperty("分区数量")
private Integer partCount;
@Schema(description = "是否允许匿名false-不允许 true-允许")
@ExcelProperty("是否允许匿名")
private Boolean allowAnonymous;
}

View File

@ -31,7 +31,24 @@ public class QuestionnaireSaveReqVO {
private BigDecimal passScore;
@Schema(description = "状态1-草稿 2-已发布 3-已禁用", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "状态1-草稿 2-已发布 3-已禁用不能为空")
@NotNull(message = "状态不能为空")
private Integer status;
// ==================== 新增字段 ====================
@Schema(description = "封面图片URL")
private String coverImage;
@Schema(description = "填写说明")
private String instruction;
@Schema(description = "预计耗时(分钟)")
private Integer estimatedTime;
@Schema(description = "分区数量")
private Integer partCount;
@Schema(description = "是否允许匿名false-不允许 true-允许")
private Boolean allowAnonymous;
}

View File

@ -57,7 +57,7 @@ public class PrisonQuestionnaireRecordController {
@Operation(summary = "删除问卷答题记录")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:delete')")
public CommonResult<Boolean> deleteQuestionnaireRecord(@RequestParam("id") Long id) {
public CommonResult<Boolean> deleteQuestionnaireRecord(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
questionnaireRecordService.deleteQuestionnaireRecord(id);
return success(true);
}
@ -66,7 +66,7 @@ public class PrisonQuestionnaireRecordController {
@Parameter(name = "ids", description = "编号", required = true)
@Operation(summary = "批量删除问卷答题记录")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:delete')")
public CommonResult<Boolean> deleteQuestionnaireRecordList(@RequestParam("ids") List<Long> ids) {
public CommonResult<Boolean> deleteQuestionnaireRecordList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
questionnaireRecordService.deleteQuestionnaireRecordListByIds(ids);
return success(true);
}

View File

@ -10,6 +10,8 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@ -50,7 +52,7 @@ public class ReleaseController {
@DeleteMapping("/delete")
@Operation(summary = "删除释放登记")
@PreAuthorize("@ss.hasPermission('prison:release:delete')")
public CommonResult<Boolean> deleteRelease(@RequestParam("id") Long id) {
public CommonResult<Boolean> deleteRelease(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
releaseService.deleteRelease(id);
return success(true);
}
@ -58,7 +60,7 @@ public class ReleaseController {
@DeleteMapping("/delete-list")
@Operation(summary = "批量删除释放登记")
@PreAuthorize("@ss.hasPermission('prison:release:delete')")
public CommonResult<Boolean> deleteReleaseList(@RequestParam("ids") List<Long> ids) {
public CommonResult<Boolean> deleteReleaseList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
releaseService.deleteReleaseListByIds(ids);
return success(true);
}

View File

@ -57,7 +57,7 @@ public class PrisonRiskAssessmentController {
@Operation(summary = "删除危险评估")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:risk-assessment:delete')")
public CommonResult<Boolean> deleteRiskAssessment(@RequestParam("id") Long id) {
public CommonResult<Boolean> deleteRiskAssessment(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
riskAssessmentService.deleteRiskAssessment(id);
return success(true);
}
@ -66,7 +66,7 @@ public class PrisonRiskAssessmentController {
@Parameter(name = "ids", description = "编号", required = true)
@Operation(summary = "批量删除危险评估")
@PreAuthorize("@ss.hasPermission('prison:risk-assessment:delete')")
public CommonResult<Boolean> deleteRiskAssessmentList(@RequestParam("ids") List<Long> ids) {
public CommonResult<Boolean> deleteRiskAssessmentList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
riskAssessmentService.deleteRiskAssessmentListByIds(ids);
return success(true);
}

View File

@ -57,7 +57,7 @@ public class PrisonScoreController {
@Operation(summary = "删除计分考核")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:score:delete')")
public CommonResult<Boolean> deleteScore(@RequestParam("id") Long id) {
public CommonResult<Boolean> deleteScore(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
scoreService.deleteScore(id);
return success(true);
}
@ -66,7 +66,7 @@ public class PrisonScoreController {
@Parameter(name = "ids", description = "编号", required = true)
@Operation(summary = "批量删除计分考核")
@PreAuthorize("@ss.hasPermission('prison:score:delete')")
public CommonResult<Boolean> deleteScoreList(@RequestParam("ids") List<Long> ids) {
public CommonResult<Boolean> deleteScoreList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
scoreService.deleteScoreListByIds(ids);
return success(true);
}

View File

@ -11,6 +11,8 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@ -50,7 +52,7 @@ public class ScoreDetailController {
@DeleteMapping("/delete")
@Operation(summary = "删除考核记录")
@PreAuthorize("@ss.hasPermission('prison:score-detail:delete')")
public CommonResult<Boolean> deleteScoreDetail(@RequestParam("id") Long id) {
public CommonResult<Boolean> deleteScoreDetail(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
scoreDetailService.deleteScoreDetail(id);
return success(true);
}
@ -58,7 +60,7 @@ public class ScoreDetailController {
@DeleteMapping("/delete-list")
@Operation(summary = "批量删除考核记录")
@PreAuthorize("@ss.hasPermission('prison:score-detail:delete')")
public CommonResult<Boolean> deleteScoreDetailList(@RequestParam("ids") List<Long> ids) {
public CommonResult<Boolean> deleteScoreDetailList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
scoreDetailService.deleteScoreDetailListByIds(ids);
return success(true);
}

View File

@ -11,6 +11,8 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@ -50,7 +52,7 @@ public class ScoreRuleController {
@DeleteMapping("/delete")
@Operation(summary = "删除考核规则")
@PreAuthorize("@ss.hasPermission('prison:score-rule:delete')")
public CommonResult<Boolean> deleteScoreRule(@RequestParam("id") Long id) {
public CommonResult<Boolean> deleteScoreRule(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
scoreRuleService.deleteScoreRule(id);
return success(true);
}
@ -58,7 +60,7 @@ public class ScoreRuleController {
@DeleteMapping("/delete-list")
@Operation(summary = "批量删除考核规则")
@PreAuthorize("@ss.hasPermission('prison:score-rule:delete')")
public CommonResult<Boolean> deleteScoreRuleList(@RequestParam("ids") List<Long> ids) {
public CommonResult<Boolean> deleteScoreRuleList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
scoreRuleService.deleteScoreRuleListByIds(ids);
return success(true);
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.prison.convert.assessment;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentAnswerDO;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import org.mapstruct.Builder;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 答卷详情 Convert
*
* @author 芋道源码
*/
@Mapper(uses = {}, builder = @Builder())
public interface AssessmentAnswerConvert {
AssessmentAnswerConvert INSTANCE = Mappers.getMapper(AssessmentAnswerConvert.class);
AssessmentAnswerDO convert(AssessmentAnswerSaveReqVO bean);
AssessmentAnswerSaveReqVO convert(AssessmentAnswerDO bean);
AssessmentAnswerRespVO convert(AssessmentAnswerDO bean);
List<AssessmentAnswerRespVO> convertList(List<AssessmentAnswerDO> list);
PageResult<AssessmentAnswerRespVO> convertPage(PageResult<AssessmentAnswerDO> page);
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.prison.convert.assessment;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentRecordDO;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import org.mapstruct.Builder;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 测评记录 Convert
*
* @author 芋道源码
*/
@Mapper(uses = {}, builder = @Builder())
public interface AssessmentRecordConvert {
AssessmentRecordConvert INSTANCE = Mappers.getMapper(AssessmentRecordConvert.class);
AssessmentRecordDO convert(AssessmentRecordSaveReqVO bean);
AssessmentRecordSaveReqVO convert(AssessmentRecordDO bean);
AssessmentRecordRespVO convert(AssessmentRecordDO bean);
List<AssessmentRecordRespVO> convertList(List<AssessmentRecordDO> list);
PageResult<AssessmentRecordRespVO> convertPage(PageResult<AssessmentRecordDO> page);
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.prison.convert.assessment;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentResultDO;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import org.mapstruct.Builder;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 测评结果 Convert
*
* @author 芋道源码
*/
@Mapper(uses = {}, builder = @Builder())
public interface AssessmentResultConvert {
AssessmentResultConvert INSTANCE = Mappers.getMapper(AssessmentResultConvert.class);
AssessmentResultDO convert(AssessmentResultSaveReqVO bean);
AssessmentResultSaveReqVO convert(AssessmentResultDO bean);
AssessmentResultRespVO convert(AssessmentResultDO bean);
List<AssessmentResultRespVO> convertList(List<AssessmentResultDO> list);
PageResult<AssessmentResultRespVO> convertPage(PageResult<AssessmentResultDO> page);
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.prison.convert.assessment;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentStatisticsDO;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import org.mapstruct.Builder;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 测评统计 Convert
*
* @author 芋道源码
*/
@Mapper(uses = {}, builder = @Builder())
public interface AssessmentStatisticsConvert {
AssessmentStatisticsConvert INSTANCE = Mappers.getMapper(AssessmentStatisticsConvert.class);
AssessmentStatisticsRespVO convert(AssessmentStatisticsDO bean);
List<AssessmentStatisticsRespVO> convertList(List<AssessmentStatisticsDO> list);
PageResult<AssessmentStatisticsRespVO> convertPage(PageResult<AssessmentStatisticsDO> page);
}

View File

@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.prison.dal.dataobject.area;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableField;

View File

@ -0,0 +1,131 @@
package cn.iocoder.yudao.module.prison.dal.dataobject.assessment;
import lombok.*;
import java.util.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 答卷详情 DO
*
* @author 芋道源码
*/
@TableName("prison_assessment_answer")
@KeySequence("prison_assessment_answer_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AssessmentAnswerDO extends BaseDO {
/**
* 答卷ID
*/
@TableId
private Long id;
/**
* 关联测评记录ID
*/
private Long assessmentRecordId;
/**
* 囚犯ID
*/
private Long prisonerId;
/**
* 囚犯编号
*/
private String prisonerCode;
/**
* 囚犯姓名
*/
private String prisonerName;
/**
* 监区ID
*/
private Long areaId;
/**
* 监区名称
*/
private String areaName;
/**
* 监室ID
*/
private Long cellId;
/**
* 监室名称
*/
private String cellName;
/**
* 答卷状态1-待答题 2-答题中 3-已提交 4-已评分 5-已完成
*/
private Integer status;
/**
* 开始答题时间
*/
private LocalDateTime startTime;
/**
* 提交时间
*/
private LocalDateTime submitTime;
/**
* 答题用时
*/
private Integer duration;
/**
* 客观题得分
*/
private BigDecimal objectiveScore;
/**
* 主观题得分
*/
private BigDecimal subjectiveScore;
/**
* 总分
*/
private BigDecimal totalScore;
/**
* 是否及格
*/
private Boolean passed;
/**
* 评语
*/
private String comment;
/**
* 评卷人ID
*/
private Long scorerId;
/**
* 评卷人名称
*/
private String scorerName;
/**
* 评分时间
*/
private LocalDateTime scoreTime;
}

View File

@ -0,0 +1,116 @@
package cn.iocoder.yudao.module.prison.dal.dataobject.assessment;
import lombok.*;
import java.util.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 测评记录 DO
*
* @author 芋道源码
*/
@TableName("prison_assessment_record")
@KeySequence("prison_assessment_record_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AssessmentRecordDO extends BaseDO {
/**
* 测评记录ID
*/
@TableId
private Long id;
/**
* 测评名称
*/
private String name;
/**
* 关联问卷模板ID
*/
private Long questionnaireId;
/**
* 测评类型1-心理测评 2-行为评估 3-满意度调查
*/
private Integer type;
/**
* 发起测评的监狱/监区ID
*/
private Long areaId;
/**
* 发起测评的监狱/监区名称
*/
private String areaName;
/**
* 发起人ID
*/
private Long initiatorId;
/**
* 发起人名称
*/
private String initiatorName;
/**
* 测评状态1-未开始 2-进行中 3-已完成 4-已取消
*/
private Integer status;
/**
* 计划开始时间
*/
private LocalDateTime planStartTime;
/**
* 计划结束时间
*/
private LocalDateTime planEndTime;
/**
* 实际开始时间
*/
private LocalDateTime actualStartTime;
/**
* 实际结束时间
*/
private LocalDateTime actualEndTime;
/**
* 参与人数
*/
private Integer participantCount;
/**
* 完成人数
*/
private Integer completedCount;
/**
* 测评说明
*/
private String description;
/**
* 是否启用自动评分
*/
private Boolean enableAutoScore;
/**
* 及格分数
*/
private BigDecimal passScore;
}

View File

@ -0,0 +1,136 @@
package cn.iocoder.yudao.module.prison.dal.dataobject.assessment;
import lombok.*;
import java.util.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 测评结果 DO
*
* @author 芋道源码
*/
@TableName("prison_assessment_result")
@KeySequence("prison_assessment_result_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AssessmentResultDO extends BaseDO {
/**
* 结果ID
*/
@TableId
private Long id;
/**
* 关联答卷ID
*/
private Long answerId;
/**
* 关联测评记录ID
*/
private Long assessmentRecordId;
/**
* 囚犯ID
*/
private Long prisonerId;
/**
* 囚犯编号
*/
private String prisonerCode;
/**
* 囚犯姓名
*/
private String prisonerName;
/**
* 关联题目ID
*/
private Long questionId;
/**
* 题目编号
*/
private String questionCode;
/**
* 题目内容
*/
private String questionContent;
/**
* 题目类型1-单选 2-多选 3-判断 4-填空 5-问答
*/
private Integer questionType;
/**
* 题目分值
*/
private BigDecimal questionScore;
/**
* 囚犯答案
*/
private String answer;
/**
* 正确答案客观题
*/
private String correctAnswer;
/**
* 是否正确
*/
private Boolean correct;
/**
* 得分
*/
private BigDecimal score;
/**
* 是否需要人工评阅
*/
private Boolean needManualReview;
/**
* 人工评阅状态1-待评阅 2-已评阅
*/
private Integer manualReviewStatus;
/**
* 人工评分
*/
private BigDecimal manualScore;
/**
* 人工评语
*/
private String manualComment;
/**
* 评阅人ID
*/
private Long reviewerId;
/**
* 评阅人名称
*/
private String reviewerName;
/**
* 评阅时间
*/
private LocalDateTime reviewTime;
}

View File

@ -0,0 +1,115 @@
package cn.iocoder.yudao.module.prison.dal.dataobject.assessment;
import lombok.*;
import java.util.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 测评统计 DO
*
* @author 芋道源码
*/
@TableName("prison_assessment_statistics")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AssessmentStatisticsDO extends BaseDO {
/**
* 统计ID
*/
@TableId
private Long id;
/**
* 关联测评记录ID
*/
private Long assessmentRecordId;
/**
* 测评名称
*/
private String assessmentName;
/**
* 总参与人数
*/
private Integer totalCount;
/**
* 已完成人数
*/
private Integer completedCount;
/**
* 完成率
*/
private BigDecimal completionRate;
/**
* 平均分
*/
private BigDecimal averageScore;
/**
* 最高分
*/
private BigDecimal highestScore;
/**
* 最低分
*/
private BigDecimal lowestScore;
/**
* 及格人数
*/
private Integer passedCount;
/**
* 及格率
*/
private BigDecimal passRate;
/**
* 优秀人数90分以上
*/
private Integer excellentCount;
/**
* 优秀率
*/
private BigDecimal excellentRate;
/**
* 风险人数60分以下
*/
private Integer riskCount;
/**
* 风险率
*/
private BigDecimal riskRate;
/**
* 风险分布JSON{"low": 10, "medium": 5, "high": 3}
*/
private String riskDistribution;
/**
* 分数分布JSON{"0-20": 5, "20-40": 10, "40-60": 15, "60-80": 20, "80-100": 8}
*/
private String scoreDistribution;
/**
* 统计时间
*/
private LocalDateTime statisticsTime;
}

View File

@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.prison.dal.mysql.assessment;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentAnswerDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
/**
* 答卷详情 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface AssessmentAnswerMapper extends BaseMapperX<AssessmentAnswerDO> {
default PageResult<AssessmentAnswerDO> selectPage(AssessmentAnswerPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<AssessmentAnswerDO>()
.eqIfPresent(AssessmentAnswerDO::getAssessmentRecordId, reqVO.getAssessmentRecordId())
.eqIfPresent(AssessmentAnswerDO::getPrisonerId, reqVO.getPrisonerId())
.likeIfPresent(AssessmentAnswerDO::getPrisonerCode, reqVO.getPrisonerCode())
.likeIfPresent(AssessmentAnswerDO::getPrisonerName, reqVO.getPrisonerName())
.eqIfPresent(AssessmentAnswerDO::getAreaId, reqVO.getAreaId())
.eqIfPresent(AssessmentAnswerDO::getCellId, reqVO.getCellId())
.eqIfPresent(AssessmentAnswerDO::getStatus, reqVO.getStatus())
.betweenIfPresent(AssessmentAnswerDO::getStartTime, reqVO.getStartTime())
.betweenIfPresent(AssessmentAnswerDO::getSubmitTime, reqVO.getSubmitTime())
.orderByDesc(AssessmentAnswerDO::getId));
}
/**
* 根据测评记录ID查询所有答卷
*/
default List<AssessmentAnswerDO> selectListByAssessmentRecordId(Long assessmentRecordId) {
return selectList(new LambdaQueryWrapperX<AssessmentAnswerDO>()
.eqIfPresent(AssessmentAnswerDO::getAssessmentRecordId, assessmentRecordId));
}
/**
* 根据囚犯ID和测评记录ID查询答卷
*/
default AssessmentAnswerDO selectByPrisonerAndRecord(Long prisonerId, Long assessmentRecordId) {
return selectOne(new LambdaQueryWrapperX<AssessmentAnswerDO>()
.eqIfPresent(AssessmentAnswerDO::getPrisonerId, prisonerId)
.eqIfPresent(AssessmentAnswerDO::getAssessmentRecordId, assessmentRecordId));
}
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.prison.dal.mysql.assessment;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentRecordDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
/**
* 测评记录 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface AssessmentRecordMapper extends BaseMapperX<AssessmentRecordDO> {
default PageResult<AssessmentRecordDO> selectPage(AssessmentRecordPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<AssessmentRecordDO>()
.likeIfPresent(AssessmentRecordDO::getName, reqVO.getName())
.eqIfPresent(AssessmentRecordDO::getQuestionnaireId, reqVO.getQuestionnaireId())
.eqIfPresent(AssessmentRecordDO::getType, reqVO.getType())
.eqIfPresent(AssessmentRecordDO::getAreaId, reqVO.getAreaId())
.eqIfPresent(AssessmentRecordDO::getStatus, reqVO.getStatus())
.betweenIfPresent(AssessmentRecordDO::getPlanStartTime, reqVO.getPlanStartTime())
.betweenIfPresent(AssessmentRecordDO::getPlanEndTime, reqVO.getPlanEndTime())
.betweenIfPresent(AssessmentRecordDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(AssessmentRecordDO::getId));
}
}

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.prison.dal.mysql.assessment;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentResultDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
/**
* 测评结果 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface AssessmentResultMapper extends BaseMapperX<AssessmentResultDO> {
default PageResult<AssessmentResultDO> selectPage(AssessmentResultPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<AssessmentResultDO>()
.eqIfPresent(AssessmentResultDO::getAnswerId, reqVO.getAnswerId())
.eqIfPresent(AssessmentResultDO::getAssessmentRecordId, reqVO.getAssessmentRecordId())
.eqIfPresent(AssessmentResultDO::getPrisonerId, reqVO.getPrisonerId())
.eqIfPresent(AssessmentResultDO::getQuestionId, reqVO.getQuestionId())
.eqIfPresent(AssessmentResultDO::getQuestionType, reqVO.getQuestionType())
.eqIfPresent(AssessmentResultDO::getCorrect, reqVO.getCorrect())
.eqIfPresent(AssessmentResultDO::getNeedManualReview, reqVO.getNeedManualReview())
.eqIfPresent(AssessmentResultDO::getManualReviewStatus, reqVO.getManualReviewStatus())
.orderByDesc(AssessmentResultDO::getId));
}
/**
* 根据答卷ID查询所有结果
*/
default List<AssessmentResultDO> selectListByAnswerId(Long answerId) {
return selectList(new LambdaQueryWrapperX<AssessmentResultDO>()
.eqIfPresent(AssessmentResultDO::getAnswerId, answerId)
.orderByAsc(AssessmentResultDO::getId));
}
/**
* 根据测评记录ID查询所有结果
*/
default List<AssessmentResultDO> selectListByAssessmentRecordId(Long assessmentRecordId) {
return selectList(new LambdaQueryWrapperX<AssessmentResultDO>()
.eqIfPresent(AssessmentResultDO::getAssessmentRecordId, assessmentRecordId));
}
/**
* 查询需要人工评阅的结果
*/
default List<AssessmentResultDO> selectListNeedManualReview() {
return selectList(new LambdaQueryWrapperX<AssessmentResultDO>()
.eqIfPresent(AssessmentResultDO::getNeedManualReview, true)
.eqIfPresent(AssessmentResultDO::getManualReviewStatus, 1)
.orderByAsc(AssessmentResultDO::getId));
}
/**
* 根据答卷ID统计客观题得分
*/
default java.math.BigDecimal sumObjectiveScoreByAnswerId(Long answerId) {
return selectObjs(new LambdaQueryWrapperX<AssessmentResultDO>()
.eqIfPresent(AssessmentResultDO::getAnswerId, answerId)
.eqIfPresent(AssessmentResultDO::getNeedManualReview, false))
.stream().map(obj -> (java.math.BigDecimal) obj)
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
}
}

View File

@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.prison.dal.mysql.assessment;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentStatisticsDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
/**
* 测评统计 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface AssessmentStatisticsMapper extends BaseMapperX<AssessmentStatisticsDO> {
default PageResult<AssessmentStatisticsDO> selectPage(AssessmentStatisticsPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<AssessmentStatisticsDO>()
.eqIfPresent(AssessmentStatisticsDO::getAssessmentRecordId, reqVO.getAssessmentRecordId())
.likeIfPresent(AssessmentStatisticsDO::getAssessmentName, reqVO.getAssessmentName())
.orderByDesc(AssessmentStatisticsDO::getId));
}
/**
* 根据测评记录ID查询统计
*/
default AssessmentStatisticsDO selectByAssessmentRecordId(Long assessmentRecordId) {
return selectOne(new LambdaQueryWrapperX<AssessmentStatisticsDO>()
.eqIfPresent(AssessmentStatisticsDO::getAssessmentRecordId, assessmentRecordId)
.orderByDesc(AssessmentStatisticsDO::getId)
.last("LIMIT 1"));
}
}

View File

@ -25,6 +25,11 @@ public interface QuestionnaireMapper extends BaseMapperX<QuestionnaireDO> {
.eqIfPresent(QuestionnaireDO::getTotalScore, reqVO.getTotalScore())
.eqIfPresent(QuestionnaireDO::getPassScore, reqVO.getPassScore())
.eqIfPresent(QuestionnaireDO::getStatus, reqVO.getStatus())
.eqIfPresent(QuestionnaireDO::getCoverImage, reqVO.getCoverImage())
.eqIfPresent(QuestionnaireDO::getInstruction, reqVO.getInstruction())
.eqIfPresent(QuestionnaireDO::getEstimatedTime, reqVO.getEstimatedTime())
.eqIfPresent(QuestionnaireDO::getPartCount, reqVO.getPartCount())
.eqIfPresent(QuestionnaireDO::getAllowAnonymous, reqVO.getAllowAnonymous())
.betweenIfPresent(QuestionnaireDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(QuestionnaireDO::getId));
}

View File

@ -39,6 +39,14 @@ public class ErrorCodeConstants {
// ========== 罪犯监区变动记录 8xxxx ==========
public static final ErrorCode PRISONER_AREA_LOG_NOT_EXISTS = new ErrorCode(8_000_001, "罪犯监区变动记录不存在");
// ========== 测评执行与评分 9xxxx ==========
public static final ErrorCode ASSESSMENT_RECORD_NOT_EXISTS = new ErrorCode(9_000_001, "测评记录不存在");
public static final ErrorCode ASSESSMENT_RECORD_STATUS_ERROR = new ErrorCode(9_000_002, "测评记录状态不正确");
public static final ErrorCode ASSESSMENT_ANSWER_NOT_EXISTS = new ErrorCode(9_000_003, "答卷不存在");
public static final ErrorCode ASSESSMENT_ANSWER_STATUS_ERROR = new ErrorCode(9_000_004, "答卷状态不正确");
public static final ErrorCode ASSESSMENT_ANSWER_ALREADY_EXISTS = new ErrorCode(9_000_005, "该囚犯已参与此测评");
public static final ErrorCode ASSESSMENT_RESULT_NOT_EXISTS = new ErrorCode(9_000_006, "测评结果不存在");
// ========== 别名 (兼容codegen生成的代码) ==========
public static final ErrorCode AREA_NOT_EXISTS = PRISON_AREA_NOT_EXISTS;
public static final ErrorCode CELL_NOT_EXISTS = PRISON_CELL_NOT_EXISTS;

View File

@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.prison.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 问卷答题记录及格状态枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum QuestionnaireRecordPassStatusEnum {
NOT_PASSED(0, "未及格"),
PASSED(1, "及格");
/**
* 状态
*/
private final Integer status;
/**
* 名称
*/
private final String name;
public static boolean isValid(Integer status) {
if (status == null) {
return false;
}
for (QuestionnaireRecordPassStatusEnum value : values()) {
if (value.getStatus().equals(status)) {
return true;
}
}
return false;
}
public static QuestionnaireRecordPassStatusEnum getByStatus(Integer status) {
for (QuestionnaireRecordPassStatusEnum value : values()) {
if (value.getStatus().equals(status)) {
return value;
}
}
return null;
}
}

View File

@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.prison.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 问卷答题记录状态枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum QuestionnaireRecordStatusEnum {
PENDING(1, "待评估"),
COMPLETED(2, "已完成");
/**
* 状态
*/
private final Integer status;
/**
* 名称
*/
private final String name;
public static boolean isValid(Integer status) {
if (status == null) {
return false;
}
for (QuestionnaireRecordStatusEnum value : values()) {
if (value.getStatus().equals(status)) {
return true;
}
}
return false;
}
public static QuestionnaireRecordStatusEnum getByStatus(Integer status) {
for (QuestionnaireRecordStatusEnum value : values()) {
if (value.getStatus().equals(status)) {
return value;
}
}
return null;
}
}

View File

@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.prison.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 问卷状态枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum QuestionnaireStatusEnum {
DRAFT(1, "草稿"),
PUBLISHED(2, "已发布"),
DISABLED(3, "已禁用");
/**
* 状态
*/
private final Integer status;
/**
* 名称
*/
private final String name;
public static boolean isValid(Integer status) {
if (status == null) {
return false;
}
for (QuestionnaireStatusEnum value : values()) {
if (value.getStatus().equals(status)) {
return true;
}
}
return false;
}
public static QuestionnaireStatusEnum getByStatus(Integer status) {
for (QuestionnaireStatusEnum value : values()) {
if (value.getStatus().equals(status)) {
return value;
}
}
return null;
}
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.prison.enums.assessment;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 答卷状态枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum AssessmentAnswerStatusEnum {
PENDING(1, "待答题"),
IN_PROGRESS(2, "答题中"),
SUBMITTED(3, "已提交"),
SCORED(4, "已评分"),
COMPLETED(5, "已完成");
/**
* 状态
*/
private final Integer status;
/**
* 状态名称
*/
private final String name;
public static AssessmentAnswerStatusEnum getByStatus(Integer status) {
for (AssessmentAnswerStatusEnum statusEnum : values()) {
if (statusEnum.getStatus().equals(status)) {
return statusEnum;
}
}
return null;
}
}

View File

@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.prison.enums.assessment;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 测评结果人工评阅状态枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum AssessmentManualReviewStatusEnum {
PENDING(1, "待评阅"),
REVIEWED(2, "已评阅");
/**
* 状态
*/
private final Integer status;
/**
* 状态名称
*/
private final String name;
public static AssessmentManualReviewStatusEnum getByStatus(Integer status) {
for (AssessmentManualReviewStatusEnum statusEnum : values()) {
if (statusEnum.getStatus().equals(status)) {
return statusEnum;
}
}
return null;
}
}

View File

@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.prison.enums.assessment;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 测评记录状态枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum AssessmentRecordStatusEnum {
NOT_STARTED(1, "未开始"),
IN_PROGRESS(2, "进行中"),
COMPLETED(3, "已完成"),
CANCELLED(4, "已取消");
/**
* 状态
*/
private final Integer status;
/**
* 状态名称
*/
private final String name;
public static AssessmentRecordStatusEnum getByStatus(Integer status) {
for (AssessmentRecordStatusEnum statusEnum : values()) {
if (statusEnum.getStatus().equals(status)) {
return statusEnum;
}
}
return null;
}
}

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.prison.enums.assessment;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 风险等级枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum RiskLevelEnum {
LOW("low", "低风险"),
MEDIUM("medium", "中风险"),
HIGH("high", "高风险");
/**
* 等级Key
*/
private final String level;
/**
* 等级名称
*/
private final String name;
public static RiskLevelEnum getByLevel(String level) {
for (RiskLevelEnum levelEnum : values()) {
if (levelEnum.getLevel().equals(level)) {
return levelEnum;
}
}
return null;
}
}

View File

@ -178,13 +178,8 @@ public class AreaServiceImpl implements AreaService {
buildChildrenRecursive(child, allAreas);
}
// 使用反射设置 children 属性
try {
java.lang.reflect.Method method = AreaDO.class.getMethod("setChildren", List.class);
method.invoke(parent, children);
} catch (Exception e) {
// 如果没有 children 方法忽略
}
// 设置子节点列表
parent.setChildren(children);
}
@Override

View File

@ -0,0 +1,110 @@
package cn.iocoder.yudao.module.prison.service.assessment;
import java.util.*;
import jakarta.validation.*;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentAnswerDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
/**
* 答卷详情 Service 接口
*
* @author 芋道源码
*/
public interface AssessmentAnswerService {
/**
* 创建答卷
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createAssessmentAnswer(@Valid AssessmentAnswerSaveReqVO createReqVO);
/**
* 更新答卷
*
* @param updateReqVO 更新信息
*/
void updateAssessmentAnswer(@Valid AssessmentAnswerSaveReqVO updateReqVO);
/**
* 删除答卷
*
* @param id 编号
*/
void deleteAssessmentAnswer(Long id);
/**
* 批量删除答卷
*
* @param ids 编号列表
*/
void deleteAssessmentAnswerListByIds(List<Long> ids);
/**
* 获得答卷
*
* @param id 编号
* @return 答卷
*/
AssessmentAnswerDO getAssessmentAnswer(Long id);
/**
* 获得答卷分页
*
* @param pageReqVO 分页查询
* @return 答卷分页
*/
PageResult<AssessmentAnswerDO> getAssessmentAnswerPage(AssessmentAnswerPageReqVO pageReqVO);
/**
* 开始答题
*
* @param assessmentRecordId 测评记录ID
* @param prisonerId 囚犯ID
* @return 答卷ID
*/
Long startAnswer(Long assessmentRecordId, Long prisonerId);
/**
* 提交答卷
*
* @param submitReqVO 提交信息
*/
void submitAnswer(AssessmentAnswerSubmitReqVO submitReqVO);
/**
* 根据囚犯ID和测评记录ID获取答卷
*
* @param prisonerId 囚犯ID
* @param assessmentRecordId 测评记录ID
* @return 答卷
*/
AssessmentAnswerDO getByPrisonerAndRecord(Long prisonerId, Long assessmentRecordId);
/**
* 获取待评分列表
*
* @param pageReqVO 分页查询
* @return 待评分答卷分页
*/
PageResult<AssessmentAnswerDO> getPendingScorePage(AssessmentAnswerPageReqVO pageReqVO);
/**
* 人工评分
*
* @param scoreReqVO 评分信息
*/
void manualScore(AssessmentAnswerManualScoreReqVO scoreReqVO);
/**
* 获取已完成答卷列表
*
* @param assessmentRecordId 测评记录ID
* @return 答卷列表
*/
List<AssessmentAnswerDO> getCompletedAnswersByRecordId(Long assessmentRecordId);
}

View File

@ -0,0 +1,91 @@
package cn.iocoder.yudao.module.prison.service.assessment;
import java.util.*;
import jakarta.validation.*;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentRecordDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
/**
* 测评记录 Service 接口
*
* @author 芋道源码
*/
public interface AssessmentRecordService {
/**
* 创建测评记录
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createAssessmentRecord(@Valid AssessmentRecordSaveReqVO createReqVO);
/**
* 更新测评记录
*
* @param updateReqVO 更新信息
*/
void updateAssessmentRecord(@Valid AssessmentRecordSaveReqVO updateReqVO);
/**
* 删除测评记录
*
* @param id 编号
*/
void deleteAssessmentRecord(Long id);
/**
* 批量删除测评记录
*
* @param ids 编号列表
*/
void deleteAssessmentRecordListByIds(List<Long> ids);
/**
* 获得测评记录
*
* @param id 编号
* @return 测评记录
*/
AssessmentRecordDO getAssessmentRecord(Long id);
/**
* 获得测评记录分页
*
* @param pageReqVO 分页查询
* @return 测评记录分页
*/
PageResult<AssessmentRecordDO> getAssessmentRecordPage(AssessmentRecordPageReqVO pageReqVO);
/**
* 发起测评
*
* @param reqVO 发起信息
* @return 测评记录ID
*/
Long initiateAssessment(AssessmentRecordSaveReqVO reqVO);
/**
* 取消测评
*
* @param id 测评记录ID
*/
void cancelAssessment(Long id);
/**
* 启动测评
*
* @param id 测评记录ID
*/
void startAssessment(Long id);
/**
* 结束测评
*
* @param id 测评记录ID
*/
void finishAssessment(Long id);
}

View File

@ -0,0 +1,106 @@
package cn.iocoder.yudao.module.prison.service.assessment;
import java.util.*;
import jakarta.validation.*;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentResultDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
/**
* 测评结果 Service 接口
*
* @author 芋道源码
*/
public interface AssessmentResultService {
/**
* 创建测评结果
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createAssessmentResult(@Valid AssessmentResultSaveReqVO createReqVO);
/**
* 批量创建测评结果
*
* @param results 结果列表
*/
void batchCreateAssessmentResult(List<AssessmentResultSaveReqVO> results);
/**
* 更新测评结果
*
* @param updateReqVO 更新信息
*/
void updateAssessmentResult(@Valid AssessmentResultSaveReqVO updateReqVO);
/**
* 删除测评结果
*
* @param id 编号
*/
void deleteAssessmentResult(Long id);
/**
* 批量删除测评结果
*
* @param ids 编号列表
*/
void deleteAssessmentResultListByIds(List<Long> ids);
/**
* 获得测评结果
*
* @param id 编号
* @return 测评结果
*/
AssessmentResultDO getAssessmentResult(Long id);
/**
* 获得测评结果分页
*
* @param pageReqVO 分页查询
* @return 测评结果分页
*/
PageResult<AssessmentResultDO> getAssessmentResultPage(AssessmentResultPageReqVO pageReqVO);
/**
* 根据答卷ID获取所有结果
*
* @param answerId 答卷ID
* @return 结果列表
*/
List<AssessmentResultDO> getResultsByAnswerId(Long answerId);
/**
* 根据测评记录ID获取所有结果
*
* @param assessmentRecordId 测评记录ID
* @return 结果列表
*/
List<AssessmentResultDO> getResultsByAssessmentRecordId(Long assessmentRecordId);
/**
* 获取需要人工评阅的结果列表
*
* @return 需要人工评阅的结果列表
*/
List<AssessmentResultDO> getNeedManualReviewList();
/**
* 人工评阅
*
* @param reviewReqVO 评阅信息
*/
void manualReview(AssessmentResultManualReviewReqVO reviewReqVO);
/**
* 自动评分
*
* @param answerId 答卷ID
*/
void autoScore(Long answerId);
}

View File

@ -0,0 +1,73 @@
package cn.iocoder.yudao.module.prison.service.assessment;
import java.util.*;
import jakarta.validation.*;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentStatisticsDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
/**
* 测评统计 Service 接口
*
* @author 芋道源码
*/
public interface AssessmentStatisticsService {
/**
* 获得测评统计
*
* @param id 编号
* @return 测评统计
*/
AssessmentStatisticsDO getAssessmentStatistics(Long id);
/**
* 获得测评统计分页
*
* @param pageReqVO 分页查询
* @return 测评统计分页
*/
PageResult<AssessmentStatisticsDO> getAssessmentStatisticsPage(AssessmentStatisticsPageReqVO pageReqVO);
/**
* 生成测评统计
*
* @param assessmentRecordId 测评记录ID
* @return 统计信息
*/
AssessmentStatisticsDO generateStatistics(Long assessmentRecordId);
/**
* 获取测评完成率
*
* @param assessmentRecordId 测评记录ID
* @return 完成率
*/
java.math.BigDecimal getCompletionRate(Long assessmentRecordId);
/**
* 获取分数分布
*
* @param assessmentRecordId 测评记录ID
* @return 分数分布
*/
Map<String, Integer> getScoreDistribution(Long assessmentRecordId);
/**
* 获取风险分布
*
* @param assessmentRecordId 测评记录ID
* @return 风险分布
*/
Map<String, Integer> getRiskDistribution(Long assessmentRecordId);
/**
* 获取测评分析报告
*
* @param assessmentRecordId 测评记录ID
* @return 分析报告
*/
AssessmentStatisticsRespVO getAssessmentReport(Long assessmentRecordId);
}

View File

@ -0,0 +1,163 @@
package cn.iocoder.yudao.module.prison.service.assessment.impl;
import cn.hutool.core.collection.CollUtil;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentAnswerDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.prison.dal.mysql.assessment.AssessmentAnswerMapper;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.prison.enums.ErrorCodeConstants.*;
/**
* 答卷详情 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class AssessmentAnswerServiceImpl implements AssessmentAnswerService {
@Resource
private AssessmentAnswerMapper assessmentAnswerMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createAssessmentAnswer(AssessmentAnswerSaveReqVO createReqVO) {
AssessmentAnswerDO answer = BeanUtils.toBean(createReqVO, AssessmentAnswerDO.class);
assessmentAnswerMapper.insert(answer);
return answer.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateAssessmentAnswer(AssessmentAnswerSaveReqVO updateReqVO) {
validateAssessmentAnswerExists(updateReqVO.getId());
AssessmentAnswerDO updateObj = BeanUtils.toBean(updateReqVO, AssessmentAnswerDO.class);
assessmentAnswerMapper.updateById(updateObj);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteAssessmentAnswer(Long id) {
validateAssessmentAnswerExists(id);
assessmentAnswerMapper.deleteById(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteAssessmentAnswerListByIds(List<Long> ids) {
assessmentAnswerMapper.deleteByIds(ids);
}
@Override
public AssessmentAnswerDO getAssessmentAnswer(Long id) {
return assessmentAnswerMapper.selectById(id);
}
@Override
public PageResult<AssessmentAnswerDO> getAssessmentAnswerPage(AssessmentAnswerPageReqVO pageReqVO) {
return assessmentAnswerMapper.selectPage(pageReqVO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long startAnswer(Long assessmentRecordId, Long prisonerId) {
// 检查是否已有答卷
AssessmentAnswerDO existingAnswer = assessmentAnswerMapper.selectOne(
new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<AssessmentAnswerDO>()
.eqIfPresent(AssessmentAnswerDO::getAssessmentRecordId, assessmentRecordId)
.eqIfPresent(AssessmentAnswerDO::getPrisonerId, prisonerId)
);
if (existingAnswer != null && !Arrays.asList(1, 2).contains(existingAnswer.getStatus())) {
throw exception(ASSESSMENT_ANSWER_ALREADY_EXISTS);
}
if (existingAnswer != null) {
return existingAnswer.getId();
}
// 创建新答卷
AssessmentAnswerDO answer = new AssessmentAnswerDO();
answer.setAssessmentRecordId(assessmentRecordId);
answer.setPrisonerId(prisonerId);
answer.setStatus(2); // 答题中
answer.setStartTime(LocalDateTime.now());
assessmentAnswerMapper.insert(answer);
return answer.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void submitAnswer(AssessmentAnswerSubmitReqVO submitReqVO) {
AssessmentAnswerDO answer = validateAssessmentAnswerExists(submitReqVO.getId());
if (!Objects.equals(answer.getStatus(), 2)) {
throw exception(ASSESSMENT_ANSWER_STATUS_ERROR);
}
answer.setStatus(3); // 已提交
answer.setSubmitTime(LocalDateTime.now());
if (answer.getStartTime() != null) {
answer.setDuration((int) java.time.Duration.between(answer.getStartTime(), LocalDateTime.now()).getSeconds());
}
assessmentAnswerMapper.updateById(answer);
}
@Override
public AssessmentAnswerDO getByPrisonerAndRecord(Long prisonerId, Long assessmentRecordId) {
return assessmentAnswerMapper.selectOne(
new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<AssessmentAnswerDO>()
.eqIfPresent(AssessmentAnswerDO::getPrisonerId, prisonerId)
.eqIfPresent(AssessmentAnswerDO::getAssessmentRecordId, assessmentRecordId)
);
}
@Override
public PageResult<AssessmentAnswerDO> getPendingScorePage(AssessmentAnswerPageReqVO pageReqVO) {
// 查询已提交但未评分的答卷
pageReqVO.setStatus(3); // 已提交
return assessmentAnswerMapper.selectPage(pageReqVO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void manualScore(AssessmentAnswerManualScoreReqVO scoreReqVO) {
AssessmentAnswerDO answer = validateAssessmentAnswerExists(scoreReqVO.getId());
answer.setSubjectiveScore(scoreReqVO.getSubjectiveScore());
answer.setTotalScore(answer.getObjectiveScore() != null ? answer.getObjectiveScore() : BigDecimal.ZERO
.add(scoreReqVO.getSubjectiveScore() != null ? scoreReqVO.getSubjectiveScore() : BigDecimal.ZERO));
answer.setPassed(answer.getTotalScore().compareTo(answer.getPassScore()) >= 0);
answer.setComment(scoreReqVO.getComment());
answer.setStatus(5); // 已完成
assessmentAnswerMapper.updateById(answer);
}
@Override
public List<AssessmentAnswerDO> getCompletedAnswersByRecordId(Long assessmentRecordId) {
return assessmentAnswerMapper.selectList(
new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<AssessmentAnswerDO>()
.eqIfPresent(AssessmentAnswerDO::getAssessmentRecordId, assessmentRecordId)
.eqIfPresent(AssessmentAnswerDO::getStatus, 5)
);
}
private AssessmentAnswerDO validateAssessmentAnswerExists(Long id) {
AssessmentAnswerDO answer = assessmentAnswerMapper.selectById(id);
if (answer == null) {
throw exception(ASSESSMENT_ANSWER_NOT_EXISTS);
}
return answer;
}
}

View File

@ -0,0 +1,133 @@
package cn.iocoder.yudao.module.prison.service.assessment.impl;
import cn.hutool.core.collection.CollUtil;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentRecordDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.prison.dal.mysql.assessment.AssessmentRecordMapper;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.prison.enums.ErrorCodeConstants.*;
/**
* 测评记录 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class AssessmentRecordServiceImpl implements AssessmentRecordService {
@Resource
private AssessmentRecordMapper assessmentRecordMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createAssessmentRecord(AssessmentRecordSaveReqVO createReqVO) {
// 插入
AssessmentRecordDO assessmentRecord = BeanUtils.toBean(createReqVO, AssessmentRecordDO.class);
assessmentRecordMapper.insert(assessmentRecord);
// 返回
return assessmentRecord.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateAssessmentRecord(AssessmentRecordSaveReqVO updateReqVO) {
// 校验存在
validateAssessmentRecordExists(updateReqVO.getId());
// 更新
AssessmentRecordDO updateObj = BeanUtils.toBean(updateReqVO, AssessmentRecordDO.class);
assessmentRecordMapper.updateById(updateObj);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteAssessmentRecord(Long id) {
// 校验存在
validateAssessmentRecordExists(id);
// 删除
assessmentRecordMapper.deleteById(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteAssessmentRecordListByIds(List<Long> ids) {
assessmentRecordMapper.deleteByIds(ids);
}
@Override
public AssessmentRecordDO getAssessmentRecord(Long id) {
return assessmentRecordMapper.selectById(id);
}
@Override
public PageResult<AssessmentRecordDO> getAssessmentRecordPage(AssessmentRecordPageReqVO pageReqVO) {
return assessmentRecordMapper.selectPage(pageReqVO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long initiateAssessment(AssessmentRecordSaveReqVO reqVO) {
AssessmentRecordDO assessmentRecord = BeanUtils.toBean(reqVO, AssessmentRecordDO.class);
assessmentRecord.setStatus(1); // 未开始
assessmentRecord.setParticipantCount(0);
assessmentRecord.setCompletedCount(0);
assessmentRecordMapper.insert(assessmentRecord);
return assessmentRecord.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelAssessment(Long id) {
AssessmentRecordDO assessmentRecord = validateAssessmentRecordExists(id);
if (!Arrays.asList(1, 2).contains(assessmentRecord.getStatus())) {
throw exception(ASSESSMENT_RECORD_STATUS_ERROR);
}
assessmentRecord.setStatus(4); // 已取消
assessmentRecordMapper.updateById(assessmentRecord);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void startAssessment(Long id) {
AssessmentRecordDO assessmentRecord = validateAssessmentRecordExists(id);
if (!Objects.equals(assessmentRecord.getStatus(), 1)) {
throw exception(ASSESSMENT_RECORD_STATUS_ERROR);
}
assessmentRecord.setStatus(2); // 进行中
assessmentRecord.setActualStartTime(java.time.LocalDateTime.now());
assessmentRecordMapper.updateById(assessmentRecord);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void finishAssessment(Long id) {
AssessmentRecordDO assessmentRecord = validateAssessmentRecordExists(id);
if (!Objects.equals(assessmentRecord.getStatus(), 2)) {
throw exception(ASSESSMENT_RECORD_STATUS_ERROR);
}
assessmentRecord.setStatus(3); // 已完成
assessmentRecord.setActualEndTime(java.time.LocalDateTime.now());
assessmentRecordMapper.updateById(assessmentRecord);
}
private AssessmentRecordDO validateAssessmentRecordExists(Long id) {
AssessmentRecordDO assessmentRecord = assessmentRecordMapper.selectById(id);
if (assessmentRecord == null) {
throw exception(ASSESSMENT_RECORD_NOT_EXISTS);
}
return assessmentRecord;
}
}

View File

@ -0,0 +1,141 @@
package cn.iocoder.yudao.module.prison.service.assessment.impl;
import cn.hutool.core.collection.CollUtil;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentResultDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.prison.dal.mysql.assessment.AssessmentResultMapper;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.prison.enums.ErrorCodeConstants.*;
/**
* 测评结果 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class AssessmentResultServiceImpl implements AssessmentResultService {
@Resource
private AssessmentResultMapper assessmentResultMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createAssessmentResult(AssessmentResultSaveReqVO createReqVO) {
AssessmentResultDO result = BeanUtils.toBean(createReqVO, AssessmentResultDO.class);
assessmentResultMapper.insert(result);
return result.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void batchCreateAssessmentResult(List<AssessmentResultSaveReqVO> results) {
if (CollUtil.isEmpty(results)) {
return;
}
List<AssessmentResultDO> resultList = BeanUtils.toBean(results, AssessmentResultDO.class);
assessmentResultMapper.insertBatch(resultList);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateAssessmentResult(AssessmentResultSaveReqVO updateReqVO) {
validateAssessmentResultExists(updateReqVO.getId());
AssessmentResultDO updateObj = BeanUtils.toBean(updateReqVO, AssessmentResultDO.class);
assessmentResultMapper.updateById(updateObj);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteAssessmentResult(Long id) {
validateAssessmentResultExists(id);
assessmentResultMapper.deleteById(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteAssessmentResultListByIds(List<Long> ids) {
assessmentResultMapper.deleteByIds(ids);
}
@Override
public AssessmentResultDO getAssessmentResult(Long id) {
return assessmentResultMapper.selectById(id);
}
@Override
public PageResult<AssessmentResultDO> getAssessmentResultPage(AssessmentResultPageReqVO pageReqVO) {
return assessmentResultMapper.selectPage(pageReqVO);
}
@Override
public List<AssessmentResultDO> getResultsByAnswerId(Long answerId) {
return assessmentResultMapper.selectListByAnswerId(answerId);
}
@Override
public List<AssessmentResultDO> getResultsByAssessmentRecordId(Long assessmentRecordId) {
return assessmentResultMapper.selectListByAssessmentRecordId(assessmentRecordId);
}
@Override
public List<AssessmentResultDO> getNeedManualReviewList() {
return assessmentResultMapper.selectListNeedManualReview();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void manualReview(AssessmentResultManualReviewReqVO reviewReqVO) {
AssessmentResultDO result = validateAssessmentResultExists(reviewReqVO.getId());
result.setManualScore(reviewReqVO.getManualScore());
result.setManualComment(reviewReqVO.getManualComment());
result.setManualReviewStatus(2); // 已评阅
result.setReviewerId(reviewReqVO.getReviewerId());
result.setReviewerName(reviewReqVO.getReviewerName());
result.setReviewTime(LocalDateTime.now());
result.setScore(reviewReqVO.getManualScore());
assessmentResultMapper.updateById(result);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void autoScore(Long answerId) {
List<AssessmentResultDO> results = assessmentResultMapper.selectListByAnswerId(answerId);
BigDecimal totalScore = BigDecimal.ZERO;
for (AssessmentResultDO result : results) {
if (!result.getNeedManualReview()) {
// 客观题自动评分
if (result.getCorrect() != null && result.getCorrect()) {
result.setScore(result.getQuestionScore());
} else {
result.setScore(BigDecimal.ZERO);
}
totalScore = totalScore.add(result.getScore());
assessmentResultMapper.updateById(result);
}
}
}
private AssessmentResultDO validateAssessmentResultExists(Long id) {
AssessmentResultDO result = assessmentResultMapper.selectById(id);
if (result == null) {
throw exception(ASSESSMENT_RESULT_NOT_EXISTS);
}
return result;
}
}

View File

@ -0,0 +1,198 @@
package cn.iocoder.yudao.module.prison.service.assessment.impl;
import cn.hutool.core.collection.CollUtil;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import cn.iocoder.yudao.module.prison.controller.admin.assessment.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentStatisticsDO;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentAnswerDO;
import cn.iocoder.yudao.module.prison.dal.dataobject.assessment.AssessmentResultDO;
import cn.iocoder.yudao.module.prison.dal.mysql.assessment.AssessmentAnswerMapper;
import cn.iocoder.yudao.module.prison.dal.mysql.assessment.AssessmentResultMapper;
import cn.iocoder.yudao.module.prison.dal.mysql.assessment.AssessmentStatisticsMapper;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.prison.enums.ErrorCodeConstants.*;
/**
* 测评统计 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class AssessmentStatisticsServiceImpl implements AssessmentStatisticsService {
@Resource
private AssessmentStatisticsMapper assessmentStatisticsMapper;
@Resource
private AssessmentAnswerMapper assessmentAnswerMapper;
@Resource
private AssessmentResultMapper assessmentResultMapper;
@Override
public AssessmentStatisticsDO getAssessmentStatistics(Long id) {
return assessmentStatisticsMapper.selectById(id);
}
@Override
public PageResult<AssessmentStatisticsDO> getAssessmentStatisticsPage(AssessmentStatisticsPageReqVO pageReqVO) {
return assessmentStatisticsMapper.selectPage(pageReqVO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public AssessmentStatisticsDO generateStatistics(Long assessmentRecordId) {
// 查询所有答卷
List<AssessmentAnswerDO> answers = assessmentAnswerMapper.selectListByAssessmentRecordId(assessmentRecordId);
int totalCount = answers.size();
int completedCount = (int) answers.stream().filter(a -> a.getStatus() == 5).count();
int passedCount = (int) answers.stream().filter(a -> a.getPassed() != null && a.getPassed()).count();
// 计算统计数据
BigDecimal completionRate = totalCount > 0 ?
new BigDecimal(completedCount).multiply(new BigDecimal(100)).divide(new BigDecimal(totalCount), 2, java.math.RoundingMode.HALF_UP) :
BigDecimal.ZERO;
List<BigDecimal> scores = answers.stream()
.map(AssessmentAnswerDO::getTotalScore)
.filter(Objects::nonNull)
.sorted()
.toList();
BigDecimal averageScore = scores.isEmpty() ? BigDecimal.ZERO :
scores.stream().reduce(BigDecimal.ZERO, BigDecimal::add).divide(new BigDecimal(scores.size()), 2, java.math.RoundingMode.HALF_UP);
BigDecimal highestScore = scores.isEmpty() ? BigDecimal.ZERO : scores.get(scores.size() - 1);
BigDecimal lowestScore = scores.isEmpty() ? BigDecimal.ZERO : scores.get(0);
BigDecimal passRate = completedCount > 0 ?
new BigDecimal(passedCount).multiply(new BigDecimal(100)).divide(new BigDecimal(completedCount), 2, java.math.RoundingMode.HALF_UP) :
BigDecimal.ZERO;
int excellentCount = (int) answers.stream().filter(a ->
a.getTotalScore() != null && a.getTotalScore().compareTo(new BigDecimal("90")) >= 0
).count();
int riskCount = (int) answers.stream().filter(a ->
a.getTotalScore() != null && a.getTotalScore().compareTo(new BigDecimal("60")) < 0
).count();
BigDecimal excellentRate = completedCount > 0 ?
new BigDecimal(excellentCount).multiply(new BigDecimal(100)).divide(new BigDecimal(completedCount), 2, java.math.RoundingMode.HALF_UP) :
BigDecimal.ZERO;
BigDecimal riskRate = completedCount > 0 ?
new BigDecimal(riskCount).multiply(new BigDecimal(100)).divide(new BigDecimal(completedCount), 2, java.math.RoundingMode.HALF_UP) :
BigDecimal.ZERO;
// 生成统计数据
AssessmentStatisticsDO statistics = new AssessmentStatisticsDO();
statistics.setAssessmentRecordId(assessmentRecordId);
statistics.setTotalCount(totalCount);
statistics.setCompletedCount(completedCount);
statistics.setCompletionRate(completionRate);
statistics.setAverageScore(averageScore);
statistics.setHighestScore(highestScore);
statistics.setLowestScore(lowestScore);
statistics.setPassedCount(passedCount);
statistics.setPassRate(passRate);
statistics.setExcellentCount(excellentCount);
statistics.setExcellentRate(excellentRate);
statistics.setRiskCount(riskCount);
statistics.setRiskRate(riskRate);
statistics.setStatisticsTime(LocalDateTime.now());
// 生成分数分布
Map<String, Integer> scoreDist = getScoreDistribution(assessmentRecordId);
statistics.setScoreDistribution(new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(scoreDist));
// 生成风险分布
Map<String, Integer> riskDist = getRiskDistribution(assessmentRecordId);
statistics.setRiskDistribution(new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(riskDist));
assessmentStatisticsMapper.insert(statistics);
return statistics;
}
@Override
public BigDecimal getCompletionRate(Long assessmentRecordId) {
List<AssessmentAnswerDO> answers = assessmentAnswerMapper.selectListByAssessmentRecordId(assessmentRecordId);
int totalCount = answers.size();
int completedCount = (int) answers.stream().filter(a -> a.getStatus() == 5).count();
if (totalCount == 0) return BigDecimal.ZERO;
return new BigDecimal(completedCount).multiply(new BigDecimal(100)).divide(new BigDecimal(totalCount), 2, java.math.RoundingMode.HALF_UP);
}
@Override
public Map<String, Integer> getScoreDistribution(Long assessmentRecordId) {
Map<String, Integer> distribution = new LinkedHashMap<>();
distribution.put("0-20", 0);
distribution.put("20-40", 0);
distribution.put("40-60", 0);
distribution.put("60-80", 0);
distribution.put("80-100", 0);
List<AssessmentAnswerDO> answers = assessmentAnswerMapper.selectListByAssessmentRecordId(assessmentRecordId);
for (AssessmentAnswerDO answer : answers) {
if (answer.getTotalScore() != null) {
BigDecimal score = answer.getTotalScore();
if (score.compareTo(new BigDecimal("20")) <= 0) {
distribution.merge("0-20", 1, Integer::sum);
} else if (score.compareTo(new BigDecimal("40")) <= 0) {
distribution.merge("20-40", 1, Integer::sum);
} else if (score.compareTo(new BigDecimal("60")) <= 0) {
distribution.merge("40-60", 1, Integer::sum);
} else if (score.compareTo(new BigDecimal("80")) <= 0) {
distribution.merge("60-80", 1, Integer::sum);
} else {
distribution.merge("80-100", 1, Integer::sum);
}
}
}
return distribution;
}
@Override
public Map<String, Integer> getRiskDistribution(Long assessmentRecordId) {
Map<String, Integer> distribution = new LinkedHashMap<>();
distribution.put("low", 0); // 低风险
distribution.put("medium", 0); // 中风险
distribution.put("high", 0); // 高风险
List<AssessmentAnswerDO> answers = assessmentAnswerMapper.selectListByAssessmentRecordId(assessmentRecordId);
for (AssessmentAnswerDO answer : answers) {
if (answer.getTotalScore() != null && answer.getPassed() != null) {
if (answer.getPassed()) {
distribution.merge("low", 1, Integer::sum);
} else {
BigDecimal score = answer.getTotalScore();
if (score.compareTo(new BigDecimal("40")) < 0) {
distribution.merge("high", 1, Integer::sum);
} else {
distribution.merge("medium", 1, Integer::sum);
}
}
}
}
return distribution;
}
@Override
public AssessmentStatisticsRespVO getAssessmentReport(Long assessmentRecordId) {
AssessmentStatisticsDO statistics = generateStatistics(assessmentRecordId);
return BeanUtils.toBean(statistics, AssessmentStatisticsRespVO.class);
}
}

View File

@ -60,10 +60,19 @@ public class QuestionServiceImpl implements QuestionService {
}
@Override
public void deleteQuestionListByIds(List<Long> ids) {
public void deleteQuestionListByIds(List<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return;
}
// 校验所有ID都存在
Long count = questionMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<QuestionDO>()
.in(QuestionDO::getId, ids));
if (count == null || count != ids.size()) {
throw exception(QUESTION_NOT_EXISTS);
}
// 删除
questionMapper.deleteByIds(ids);
}
}
private void validateQuestionExists(Long id) {
@ -88,17 +97,21 @@ public class QuestionServiceImpl implements QuestionService {
if (CollUtil.isEmpty(updateList)) {
return;
}
// 批量更新
for (QuestionSaveReqVO updateReqVO : updateList) {
// 校验存在
validateQuestionExists(updateReqVO.getId());
// 更新仅更新排序和分区相关字段
QuestionDO updateObj = new QuestionDO();
updateObj.setId(updateReqVO.getId());
updateObj.setPartName(updateReqVO.getPartName());
updateObj.setPartSort(updateReqVO.getPartSort());
updateObj.setSort(updateReqVO.getSort());
questionMapper.updateById(updateObj);
// 校验所有ID存在性
List<Long> ids = convertList(updateList, QuestionSaveReqVO::getId);
Long count = questionMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<QuestionDO>()
.in(QuestionDO::getId, ids));
if (count == null || count != ids.size()) {
throw exception(QUESTION_NOT_EXISTS);
}
// 批量更新 - 逐条更新以确保兼容性
for (QuestionSaveReqVO vo : updateList) {
QuestionDO obj = new QuestionDO();
obj.setId(vo.getId());
obj.setPartName(vo.getPartName());
obj.setPartSort(vo.getPartSort());
obj.setSort(vo.getSort());
questionMapper.updateById(obj);
}
}

View File

@ -60,10 +60,19 @@ public class QuestionnaireRecordServiceImpl implements QuestionnaireRecordServic
}
@Override
public void deleteQuestionnaireRecordListByIds(List<Long> ids) {
public void deleteQuestionnaireRecordListByIds(List<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return;
}
// 校验所有ID都存在
Long count = questionnaireRecordMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<QuestionnaireRecordDO>()
.in(QuestionnaireRecordDO::getId, ids));
if (count == null || count != ids.size()) {
throw exception(QUESTIONNAIRE_RECORD_NOT_EXISTS);
}
// 删除
questionnaireRecordMapper.deleteByIds(ids);
}
}
private void validateQuestionnaireRecordExists(Long id) {

View File

@ -0,0 +1,374 @@
package cn.iocoder.yudao.module.prison.service.riskassessment;
import cn.iocoder.yudao.module.prison.dal.dataobject.prisoner.PrisonerDO;
import cn.iocoder.yudao.module.prison.dal.dataobject.riskassessment.RiskAssessmentDO;
import cn.iocoder.yudao.module.prison.dal.dataobject.score.ScoreDO;
import cn.iocoder.yudao.module.prison.dal.mysql.prisoner.PrisonerMapper;
import cn.iocoder.yudao.module.prison.dal.mysql.riskassessment.RiskAssessmentMapper;
import cn.iocoder.yudao.module.prison.dal.mysql.score.ScoreDetailMapper;
import cn.iocoder.yudao.module.prison.service.riskassessment.dto.AssessmentContext;
import cn.iocoder.yudao.module.prison.service.riskassessment.enums.AssessmentTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import jakarta.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.Period;
import java.util.List;
import java.util.Optional;
/**
* 评估数据聚合服务
* 从多个数据源聚合评估所需数据
*
* @author XLLC Team
*/
@Slf4j
@Service
public class AssessmentDataAggregator {
@Resource
private PrisonerMapper prisonerMapper;
@Resource
private RiskAssessmentMapper riskAssessmentMapper;
@Resource
private ScoreDetailMapper scoreDetailMapper;
@Resource
private DataMasker dataMasker;
/**
* 聚合指定罪犯的评估数据
*
* @param prisonerId 罪犯ID
* @param assessmentType 评估类型
* @param assessorId 评估人ID
* @param assessorName 评估人姓名
* @return 聚合后的评估上下文
*/
public AssessmentContext aggregate(Long prisonerId, Integer assessmentType, Long assessorId, String assessorName) {
log.debug("开始聚合罪犯ID={}的评估数据", prisonerId);
// 1. 获取罪犯基础信息
PrisonerDO prisoner = prisonerMapper.selectById(prisonerId);
if (prisoner == null) {
throw new IllegalArgumentException("罪犯不存在: " + prisonerId);
}
// 2. 构建基础上下文
AssessmentContext.AssessmentContextBuilder builder = AssessmentContext.builder()
.prisonerId(String.valueOf(prisonerId))
.prisonerNo(prisoner.getPrisonerNo())
.assessmentType(AssessmentTypeEnum.getName(assessmentType))
.assessmentDate(LocalDate.now())
.assessorId(assessorId)
.assessorName(assessorName);
// 3. 聚合犯罪基本信息泛化处理
aggregateCrimeInfo(builder, prisoner);
// 4. 聚合心理评估得分
aggregatePsychologyScores(builder, prisonerId);
// 5. 聚合行为表现数据
aggregateBehaviorData(builder, prisonerId);
// 6. 聚合改造态度数据
aggregateReformData(builder, prisonerId);
// 7. 聚合消费数据
aggregateConsumptionData(builder, prisonerId);
// 8. 聚合历史评估数据
aggregateHistoryData(builder, prisonerId);
// 9. 聚合问卷答题数据
aggregateQuestionnaireData(builder, prisonerId);
AssessmentContext context = builder.build();
log.debug("数据聚合完成,罪犯编号: {}", context.getPrisonerNo());
return context;
}
/**
* 聚合犯罪基本信息
*/
private void aggregateCrimeInfo(AssessmentContext.AssessmentContextBuilder builder, PrisonerDO prisoner) {
// 犯罪类型泛化
String crimeCategory = dataMasker.generalizeCrimeType(
Optional.ofNullable(prisoner.getCrimeType()).orElse("未知")
);
// 刑期处理
Integer sentenceYears = 0;
if (prisoner.getSentenceDate() != null && prisoner.getReleaseDate() != null) {
Period period = Period.between(prisoner.getSentenceDate(), prisoner.getReleaseDate());
sentenceYears = period.getYears();
}
// 入监时长
int imprisonmentMonths = 0;
if (prisoner.getEntryDate() != null) {
Period period = Period.between(prisoner.getEntryDate(), LocalDate.now());
imprisonmentMonths = period.getYears() * 12 + period.getMonths();
}
builder
.crimeCategory(crimeCategory)
.sentenceYears(sentenceYears)
.isRecidivist(Boolean.TRUE.equals(prisoner.getIsRecidivist()))
.imprisonmentMonths(imprisonmentMonths);
}
/**
* 聚合心理评估得分
*/
private void aggregatePsychologyScores(AssessmentContext.AssessmentContextBuilder builder, Long prisonerId) {
// 获取最近一次危险评估记录
RiskAssessmentDO latestAssessment = riskAssessmentMapper.selectOne(
new RiskAssessmentMapper.QueryWrapper()
.eq("prisoner_id", prisonerId)
.orderByDesc("assessment_date")
.last("LIMIT 1")
);
if (latestAssessment != null) {
builder
.violenceScore(normalizeScore(latestAssessment.getViolenceScore()))
.escapeScore(normalizeScore(latestAssessment.getEscapeScore()))
.suicideScore(normalizeScore(latestAssessment.getSuicideScore()));
} else {
// 默认值
builder
.violenceScore(BigDecimal.ZERO)
.escapeScore(BigDecimal.ZERO)
.suicideScore(BigDecimal.ZERO);
}
}
/**
* 聚合行为表现数据
*/
private void aggregateBehaviorData(AssessmentContext.AssessmentContextBuilder builder, Long prisonerId) {
// 计算近6月的统计数据
LocalDate sixMonthsAgo = LocalDate.now().minusMonths(6);
// 查询近6月的计分记录
List<ScoreDO> recentScores = scoreDetailMapper.selectList(
new ScoreDO().builder().prisonerId(prisonerId).build(),
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ScoreDO>()
.ge(ScoreDO::getRecordDate, sixMonthsAgo)
);
if (recentScores.isEmpty()) {
builder
.avgScore(new BigDecimal("70")) // 默认中等水平
.violationCount6Month(0)
.praiseCount6Month(0);
return;
}
// 计算平均分
BigDecimal totalScore = recentScores.stream()
.map(ScoreDO::getTotalScore)
.filter(s -> s != null)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal avgScore = totalScore.divide(
new BigDecimal(recentScores.size()),
2,
RoundingMode.HALF_UP
);
// 统计违规和表扬次数简化处理
int violationCount = 0;
int praiseCount = 0;
for (ScoreDO score : recentScores) {
if (score.getDeductionPoints() != null && score.getDeductionPoints().intValue() > 0) {
violationCount++;
}
if (score.getRewardPoints() != null && score.getRewardPoints().intValue() > 0) {
praiseCount++;
}
}
builder
.avgScore(avgScore)
.violationCount6Month(violationCount)
.praiseCount6Month(praiseCount);
}
/**
* 聚合改造态度数据
* 这里简化处理实际应根据问卷答题情况计算
*/
private void aggregateReformData(AssessmentContext.AssessmentContextBuilder builder, Long prisonerId) {
// 简化实现基于行为得分推算
BigDecimal avgScore = builder.build().getAvgScore();
builder
.laborPerformance(avgScore) // 用平均分作为劳动表现评分
.educationParticipation(avgScore) // 用平均分作为教育参与度
.thoughtReportQuality(avgScore); // 用平均分作为思想汇报质量
}
/**
* 聚合消费数据区间化处理
*/
private void aggregateConsumptionData(AssessmentContext.AssessmentContextBuilder builder, Long prisonerId) {
// TODO: 待消费模块完成后接入真实数据
// 暂时使用默认值
builder
.consumptionLevel(2) // 中等消费
.hasAbnormalConsumption(false);
}
/**
* 聚合历史评估数据
*/
private void aggregateHistoryData(AssessmentContext.AssessmentContextBuilder builder, Long prisonerId) {
// 获取历史评估记录
List<RiskAssessmentDO> historyAssessments = riskAssessmentMapper.selectList(
new RiskAssessmentMapper.QueryWrapper()
.eq("prisoner_id", prisonerId)
.orderByDesc("assessment_date")
);
if (historyAssessments.isEmpty()) {
// 首次评估
builder
.lastRiskLevel(null)
.lastTotalScore(null)
.lastAssessmentMonthsAgo(null)
.riskTrend("stable")
.assessmentHistoryCount(0);
return;
}
// 最近一次评估
RiskAssessmentDO latest = historyAssessments.get(0);
builder
.lastRiskLevel(latest.getRiskLevel())
.lastTotalScore(latest.getTotalScore())
.assessmentHistoryCount(historyAssessments.size());
// 计算距今时长
if (latest.getAssessmentDate() != null) {
Period period = Period.between(latest.getAssessmentDate(), LocalDate.now());
int monthsAgo = period.getYears() * 12 + period.getMonths();
builder.lastAssessmentMonthsAgo(monthsAgo);
}
// 计算风险趋势基于最近3次评估
String trend = calculateTrend(historyAssessments);
builder.riskTrend(trend);
}
/**
* 聚合问卷答题数据
* 根据各维度问卷的答题情况计算得分
*/
private void aggregateQuestionnaireData(AssessmentContext.AssessmentContextBuilder builder, Long prisonerId) {
// TODO: 待问卷模块完成后接入真实数据
// 暂时使用默认值实际应从问卷答题记录中计算
builder
.criminalHistoryScore(new BigDecimal("50"))
.familyBackgroundScore(new BigDecimal("50"))
.socialSupportScore(new BigDecimal("50"))
.psychologicalStateScore(builder.build().getViolenceScore())
.behaviorPerformanceScore(builder.build().getAvgScore())
.reformAttitudeScore(builder.build().getLaborPerformance());
}
/**
* 计算风险趋势
*/
private String calculateTrend(List<RiskAssessmentDO> assessments) {
if (assessments.size() < 2) {
return "stable";
}
// 取最近3次评估
int count = Math.min(3, assessments.size());
List<RiskAssessmentDO> recent = assessments.subList(0, count);
// 计算得分变化
double sumDiff = 0;
for (int i = 1; i < recent.size(); i++) {
BigDecimal current = recent.get(i).getTotalScore();
BigDecimal previous = recent.get(i - 1).getTotalScore();
if (current != null && previous != null) {
sumDiff += current.subtract(previous).doubleValue();
}
}
if (sumDiff > 10) {
return "rising"; // 上升
} else if (sumDiff < -10) {
return "declining"; // 下降
} else {
return "stable"; // 稳定
}
}
/**
* 标准化分数到0-100范围
*/
private BigDecimal normalizeScore(BigDecimal score) {
if (score == null) {
return BigDecimal.ZERO;
}
// 假设原始分数范围是0-100直接返回
return score;
}
/**
* 数据脱敏工具
*/
@Service
public static class DataMasker {
/**
* 犯罪类型泛化映射
*/
private static final Map<String, String> CRIME_TYPE_MAPPING = Map.of(
"盗窃", "财产类犯罪",
"抢劫", "暴力类犯罪",
"抢夺", "暴力类犯罪",
"故意伤害", "暴力类犯罪",
"故意杀人", "严重暴力犯罪",
"强奸", "严重暴力犯罪",
"贩毒", "涉毒类犯罪",
"制造毒品", "涉毒类犯罪",
"诈骗", "财产类犯罪",
"敲诈勒索", "财产类犯罪",
"组织卖淫", "其他类型犯罪",
"赌博", "其他类型犯罪"
);
/**
* 泛化犯罪类型
*/
public String generalizeCrimeType(String originalType) {
if (!StringUtils.hasText(originalType)) {
return "其他类型犯罪";
}
return CRIME_TYPE_MAPPING.getOrDefault(originalType, "其他类型犯罪");
}
/**
* 泛化地名预留
*/
public String generalizeLocation(String originalLocation) {
// 暂时不处理实际应根据需要进行脱敏
return originalLocation;
}
}
}

View File

@ -0,0 +1,246 @@
package cn.iocoder.yudao.module.prison.service.riskassessment;
import cn.iocoder.yudao.module.prison.service.riskassessment.dto.AssessmentContext;
import org.springframework.stereotype.Component;
/**
* 评估提示词构建器
* 根据评估上下文构建发送给LLM的提示词
*
* @author XLLC Team
*/
@Component
public class AssessmentPromptBuilder {
/**
* 构建危险评估提示词
*
* @param context 评估上下文
* @return 提示词文本
*/
public String build(AssessmentContext context) {
StringBuilder prompt = new StringBuilder();
// 1. 角色定义
prompt.append(buildRoleDefinition());
// 2. 任务说明
prompt.append(buildTaskDescription(context));
// 3. 评估数据
prompt.append(buildAssessmentData(context));
// 4. 评估规则
prompt.append(buildAssessmentRules());
// 5. 输出要求
prompt.append(buildOutputRequirements());
// 6. 评估原则
prompt.append(buildPrinciples());
// 7. 开始评估指令
prompt.append(buildStartInstruction());
return prompt.toString();
}
/**
* 构建角色定义
*/
private String buildRoleDefinition() {
return """
# 角色定义
你是一位拥有20年监狱工作经验的资深危险评估专家精通心理学犯罪学和行为分析你需要基于提供的数据对罪犯进行专业的危险等级评估
""";
}
/**
* 构建任务说明
*/
private String buildTaskDescription(AssessmentContext context) {
StringBuilder sb = new StringBuilder();
sb.append("# 任务说明\n\n");
sb.append("根据以下【评估数据】对罪犯进行综合危险评估。\n\n");
sb.append("**评估类型**: ").append(context.getAssessmentType()).append("\n\n");
sb.append("请基于客观数据和专业判断,给出风险等级评估。\n\n");
return sb.toString();
}
/**
* 构建评估数据
*/
private String buildAssessmentData(AssessmentContext context) {
StringBuilder sb = new StringBuilder();
sb.append("# 评估数据\n\n");
sb.append("```json\n");
sb.append(context.toJson());
sb.append("\n```\n\n");
sb.append("## 数据说明\n\n");
// 心理评估得分说明
sb.append("### 心理评估得分0-100分越高越危险\n");
sb.append("- 暴力倾向: ").append(context.getViolenceScore()).append("\n");
sb.append("- 脱逃倾向: ").append(context.getEscapeScore()).append("\n");
sb.append("- 自杀倾向: ").append(context.getSuicideScore()).append("\n\n");
// 行为表现说明
sb.append("### 行为表现\n");
sb.append("- 计分考核平均分: ").append(context.getAvgScore()).append("\n");
sb.append("- 近6月违规次数: ").append(context.getViolationCount6Month()).append("\n");
sb.append("- 近6月表扬次数: ").append(context.getPraiseCount6Month()).append("\n\n");
// 历史评估说明
if (context.getLastRiskLevel() != null) {
sb.append("### 历史评估\n");
sb.append("- 上次风险等级: ").append(formatRiskLevel(context.getLastRiskLevel())).append("\n");
sb.append("- 历史风险趋势: ").append(formatTrend(context.getRiskTrend())).append("\n\n");
}
// 特殊因素
if (hasRiskFactors(context)) {
sb.append("### 特殊风险因素\n");
if (Boolean.TRUE.equals(context.getHasFamilyCrisis())) {
sb.append("- 存在家庭变故\n");
}
if (Boolean.TRUE.equals(context.getHasConflictWithPrisoners())) {
sb.append("- 近期与他犯有矛盾\n");
}
if (Boolean.TRUE.equals(context.getHasMoodAbnormality())) {
sb.append("- 近期情绪异常\n");
}
if (StringUtils.hasText(context.getOtherRiskFactors())) {
sb.append("- 其他: ").append(context.getOtherRiskFactors()).append("\n");
}
sb.append("\n");
}
return sb.toString();
}
/**
* 构建评估规则
*/
private String buildAssessmentRules() {
return """
# 评估规则
根据综合得分和风险因素确定风险等级
| 风险等级 | 分值范围 | 说明 |
|---------|---------|------|
| 1-低风险 | < 40分 | 无明显风险因素管理正常 |
| 2-中风险 | 40-69分 | 存在一定风险因素需要关注 |
| 3-高风险 | 70-89分 | 存在明显风险因素需要重点管控 |
| 4-极高风险 | 90分 | 存在严重风险因素需要立即干预 |
**注意**: 风险因素的存在可能导致等级上调即使得分较低
""";
}
/**
* 构建输出要求
*/
private String buildOutputRequirements() {
return """
# 输出要求
请以JSON格式输出评估结果包含以下字段
```json
{
"riskLevel": <1-4整数>,
"confidence": <0.0-1.0小数>,
"keyRiskFactors": ["因素1", "因素2", "因素3"],
"analysis": "<综合分析说明100-200字>",
"suggestions": ["建议1", "建议2", "建议3"],
"attentionPoints": ["需要关注的点1", "需要关注的点2"]
}
```
**字段说明**:
- `riskLevel`: 风险等级1-4
- `confidence`: 评估置信度0-1反映评估的确定性
- `keyRiskFactors`: 最关键的风险因素最多3个
- `analysis`: 综合分析说明
- `suggestions`: 针对性管控建议具体可操作
- `attentionPoints`: 需要特别关注的点
""";
}
/**
* 构建评估原则
*/
private String buildPrinciples() {
return """
# 评估原则
1. **客观公正**: 基于数据进行分析不带主观偏见
2. **综合判断**: 考虑各因素之间的关联性而非简单求和
3. **重点突出**: 识别最关键的风险因素
4. **建议可行**: 建议要具体可操作
5. **审慎判断**: 对于边界情况给出审慎判断
""";
}
/**
* 构建开始评估指令
*/
private String buildStartInstruction() {
return """
# 开始评估
请基于以上数据和规则开始进行危险等级评估
""";
}
/**
* 格式化风险等级
*/
private String formatRiskLevel(Integer level) {
if (level == null) return "";
return switch (level) {
case 1 -> "1-低风险";
case 2 -> "2-中风险";
case 3 -> "3-高风险";
case 4 -> "4-极高风险";
default -> "未知(" + level + ")";
};
}
/**
* 格式化趋势描述
*/
private String formatTrend(String trend) {
if (trend == null) return "稳定";
return switch (trend) {
case "rising" -> "上升趋势";
case "declining" -> "下降趋势";
case "fluctuating" -> "波动";
default -> "稳定";
};
}
/**
* 检查是否存在特殊风险因素
*/
private boolean hasRiskFactors(AssessmentContext context) {
return Boolean.TRUE.equals(context.getHasFamilyCrisis())
|| Boolean.TRUE.equals(context.getHasConflictWithPrisoners())
|| Boolean.TRUE.equals(context.getHasMoodAbnormality())
|| StringUtils.hasText(context.getOtherRiskFactors());
}
// 需要引入 StringUtils
private static class StringUtils {
public static boolean hasText(String str) {
return str != null && !str.trim().isEmpty();
}
}
}

View File

@ -0,0 +1,296 @@
package cn.iocoder.yudao.module.prison.service.riskassessment.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
/**
* 危险评估上下文发送给LLM的数据结构
* 包含聚合后的评估所需数据
*
* @author XLLC Team
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AssessmentContext {
// ==================== 标识信息 ====================
/**
* 罪犯ID脱敏后的标识
*/
private String prisonerId;
/**
* 罪犯编号保留用于结果关联
*/
private String prisonerNo;
// ==================== 犯罪基本信息泛化后 ====================
/**
* 犯罪类型泛化类别
* 暴力类犯罪财产类犯罪涉毒类犯罪等
*/
private String crimeCategory;
/**
* 刑期保留时长去掉具体日期
*/
private Integer sentenceYears;
/**
* 是否累犯
*/
private Boolean isRecidivist;
/**
* 入监时长
*/
private Integer imprisonmentMonths;
// ==================== 心理评估得分0-100 ====================
/**
* 暴力倾向得分0-100越高越危险
*/
private BigDecimal violenceScore;
/**
* 脱逃倾向得分0-100越高越危险
*/
private BigDecimal escapeScore;
/**
* 自杀倾向得分0-100越高越危险
*/
private BigDecimal suicideScore;
// ==================== 行为表现数据 ====================
/**
* 计分考核月均分
*/
private BigDecimal avgScore;
/**
* 近6月违规次数
*/
private Integer violationCount6Month;
/**
* 近6月表扬次数
*/
private Integer praiseCount6Month;
/**
* 近6月扣分次数
*/
private Integer deductionCount6Month;
// ==================== 改造态度数据 ====================
/**
* 劳动表现评分0-100
*/
private BigDecimal laborPerformance;
/**
* 学习教育参与度0-100
*/
private BigDecimal educationParticipation;
/**
* 思想汇报质量0-100
*/
private BigDecimal thoughtReportQuality;
// ==================== 消费数据统计脱敏 ====================
/**
* 月均消费金额区间化
* 0: 低消费, 1: 中低消费, 2: 中等消费, 3: 中高消费, 4: 高消费
*/
private Integer consumptionLevel;
/**
* 是否有异常大额消费
*/
private Boolean hasAbnormalConsumption;
// ==================== 历史评估数据 ====================
/**
* 上次评估风险等级1-4
*/
private Integer lastRiskLevel;
/**
* 上次评估综合得分
*/
private BigDecimal lastTotalScore;
/**
* 评估时间距今月数
*/
private Integer lastAssessmentMonthsAgo;
/**
* 历史风险趋势
* stable: 稳定, rising: 上升, declining: 下降, fluctuating: 波动
*/
private String riskTrend;
/**
* 历史评估记录数
*/
private Integer assessmentHistoryCount;
// ==================== 问卷答题数据维度得分 ====================
/**
* 犯罪史评估得分0-100
*/
private BigDecimal criminalHistoryScore;
/**
* 家庭背景评估得分0-100
*/
private BigDecimal familyBackgroundScore;
/**
* 社会支持评估得分0-100
*/
private BigDecimal socialSupportScore;
/**
* 心理状态评估得分0-100
*/
private BigDecimal psychologicalStateScore;
/**
* 行为表现评估得分0-100
*/
private BigDecimal behaviorPerformanceScore;
/**
* 改造态度评估得分0-100
*/
private BigDecimal reformAttitudeScore;
// ==================== 特殊因素标记 ====================
/**
* 是否存在家庭变故
*/
private Boolean hasFamilyCrisis;
/**
* 是否近期与他犯有矛盾
*/
private Boolean hasConflictWithPrisoners;
/**
* 是否近期情绪异常
*/
private Boolean hasMoodAbnormality;
/**
* 其他风险因素描述文本
*/
private String otherRiskFactors;
// ==================== 评估元信息 ====================
/**
* 评估类型
*/
private String assessmentType;
/**
* 评估日期
*/
private LocalDate assessmentDate;
/**
* 评估人ID
*/
private Long assessorId;
/**
* 评估人姓名
*/
private String assessorName;
/**
* 转换为JSON字符串用于LLM调用
*/
public String toJson() {
return String.format("""
{
"prisonerNo": "%s",
"crimeCategory": "%s",
"sentenceYears": %d,
"isRecidivist": %b,
"imprisonmentMonths": %d,
"violenceScore": %s,
"escapeScore": %s,
"suicideScore": %s,
"avgScore": %s,
"violationCount6Month": %d,
"praiseCount6Month": %d,
"laborPerformance": %s,
"educationParticipation": %s,
"lastRiskLevel": %d,
"lastTotalScore": %s,
"riskTrend": "%s",
"assessmentHistoryCount": %d,
"criminalHistoryScore": %s,
"familyBackgroundScore": %s,
"socialSupportScore": %s,
"psychologicalStateScore": %s,
"behaviorPerformanceScore": %s,
"reformAttitudeScore": %s,
"hasFamilyCrisis": %b,
"hasConflictWithPrisoners": %b,
"hasMoodAbnormality": %b,
"otherRiskFactors": "%s"
}
""",
prisonerNo,
crimeCategory,
sentenceYears,
isRecidivist,
imprisonmentMonths,
violenceScore,
escapeScore,
suicideScore,
avgScore,
violationCount6Month,
praiseCount6Month,
laborPerformance,
educationParticipation,
lastRiskLevel,
lastTotalScore,
riskTrend,
assessmentHistoryCount,
criminalHistoryScore,
familyBackgroundScore,
socialSupportScore,
psychologicalStateScore,
behaviorPerformanceScore,
reformAttitudeScore,
hasFamilyCrisis,
hasConflictWithPrisoners,
hasMoodAbnormality,
otherRiskFactors != null ? otherRiskFactors : ""
);
}
}

View File

@ -0,0 +1,108 @@
package cn.iocoder.yudao.module.prison.service.riskassessment.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
* LLM评估结果
*
* @author XLLC Team
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LlmAssessmentResult {
/**
* 风险等级1-4
*/
private Integer riskLevel;
/**
* 置信度0-1
*/
private BigDecimal confidence;
/**
* 关键风险因素列表
*/
private List<String> keyRiskFactors;
/**
* 综合分析说明
*/
private String analysis;
/**
* 管控建议列表
*/
private List<String> suggestions;
/**
* 需要关注的点
*/
private List<String> attentionPoints;
/**
* 原始LLM响应
*/
private String rawResponse;
/**
* 使用的模型
*/
private String modelUsed;
/**
* 评估耗时毫秒
*/
private Long evaluationTimeMs;
/**
* 评估时间
*/
private LocalDateTime evaluatedAt;
/**
* 是否需要人工复核
*/
public boolean requiresHumanReview() {
// 置信度低于0.7或风险等级为4极高风险需要人工复核
return confidence.compareTo(new BigDecimal("0.7")) < 0 || riskLevel == 4;
}
/**
* 获取风险等级描述
*/
public String getRiskLevelDescription() {
if (riskLevel == null) return "未知";
return switch (riskLevel) {
case 1 -> "低风险";
case 2 -> "中风险";
case 3 -> "高风险";
case 4 -> "极高风险";
default -> "未知";
};
}
/**
* 获取风险等级颜色标识前端使用
*/
public String getRiskLevelColor() {
if (riskLevel == null) return "grey";
return switch (riskLevel) {
case 1 -> "success"; // 绿色
case 2 -> "warning"; // 橙色
case 3 -> "danger"; // 红色
case 4 -> "danger"; // 深红
default -> "info";
};
}
}

View File

@ -0,0 +1,239 @@
package cn.iocoder.yudao.module.prison.service.riskassessment.llm;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.prison.service.riskassessment.llm.LlmClient.LlmOptions;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/**
* Claude大模型客户端实现
*
* 基于Anthropic Claude API的客户端实现
* API文档: https://docs.anthropic.com/claude/reference/getting-started
*
* @author XLLC Team
*/
@Slf4j
@Component
public class ClaudeLlmClient implements LlmClient {
private static final String BASE_URL = "https://api.anthropic.com/v1/messages";
private static final String DEFAULT_MODEL = "claude-3-5-sonnet-20241022";
@Value("${llm.claude.api-key:}")
private String apiKey;
@Value("${llm.claude.timeout-seconds:30}")
private int timeoutSeconds;
private final RestTemplate restTemplate;
public ClaudeLlmClient() {
this.restTemplate = new RestTemplate();
// 配置超时
this.restTemplate.setRequestFactory(() -> {
var requestFactory = new org.springframework.http.client.SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(Duration.ofSeconds(30));
requestFactory.setReadTimeout(Duration.ofSeconds(30));
return requestFactory;
});
}
@Override
public String complete(String prompt, LlmOptions options) {
try {
ClaudeRequest request = buildRequest(prompt, options, false);
ClaudeResponse response = execute(request);
if (response.content != null && !response.content.isEmpty()) {
return response.content.get(0).text;
}
log.warn("Claude响应内容为空");
return "";
} catch (Exception e) {
log.error("Claude API调用失败: {}", e.getMessage(), e);
throw new LlmException("Claude API调用失败: " + e.getMessage(), e);
}
}
@Override
public String completeJson(String prompt, LlmOptions options) {
try {
// 在提示词中明确要求JSON输出
String jsonPrompt = prompt + "\n\n请确保输出格式为有效的JSON不要添加额外的markdown格式。";
ClaudeRequest request = buildRequest(jsonPrompt, options, true);
ClaudeResponse response = execute(request);
if (response.content != null && !response.content.isEmpty()) {
String text = response.content.get(0).text;
// 清理可能的markdown包装
text = cleanJsonResponse(text);
return text;
}
log.warn("Claude JSON响应内容为空");
return "";
} catch (Exception e) {
log.error("Claude JSON API调用失败: {}", e.getMessage(), e);
throw new LlmException("Claude JSON API调用失败: " + e.getMessage(), e);
}
}
@Override
public boolean isAvailable() {
if (apiKey == null || apiKey.isEmpty()) {
log.warn("Claude API密钥未配置");
return false;
}
try {
// 简单的健康检查
String testPrompt = "Hello";
ClaudeRequest request = buildRequest(testPrompt,
LlmOptions.defaultOptions(), false);
execute(request);
return true;
} catch (Exception e) {
log.warn("Claude服务不可用: {}", e.getMessage());
return false;
}
}
@Override
public String getName() {
return "Claude";
}
/**
* 构建请求对象
*/
private ClaudeRequest buildRequest(String prompt, LlmOptions options, boolean isJsonMode) {
// 构建系统提示词用于JSON模式
String systemPrompt = isJsonMode
? "你是一个专业的危险评估助手。请始终以JSON格式输出响应不要添加任何markdown代码块标记。"
: "你是一位专业的危险评估专家。请基于提供的数据进行专业评估。";
return new ClaudeRequest(
options.model() != null ? options.model() : DEFAULT_MODEL,
prompt,
systemPrompt,
options.temperature(),
options.maxTokens()
);
}
/**
* 执行API调用
*/
private ClaudeResponse execute(ClaudeRequest request) {
// 请求头
Map<String, String> headers = new HashMap<>();
headers.put("x-api-key", apiKey);
headers.put("anthropic-version", "2023-06-01");
headers.put("anthropic-dangerous-direct-browser-access", "true");
headers.put("content-type", "application/json");
// 构建请求体
Map<String, Object> body = new HashMap<>();
body.put("model", request.model());
body.put("messages", new Object[]{
Map.of("role", "system", "content", request.systemPrompt())
});
body.put("max_tokens", request.maxTokens());
body.put("temperature", request.temperature());
body.put("stream", false);
// 添加用户消息
Object[] messages = new Object[2];
messages[0] = Map.of("role", "system", "content", request.systemPrompt());
messages[1] = Map.of("role", "user", "content", request.prompt());
body.put("messages", messages);
try {
// 注意: 实际使用时需要配置HTTP客户端处理Anthropic的特殊请求头
// 这里使用简化实现实际项目中建议使用官方SDK
log.debug("调用Claude API, model: {}, temperature: {}",
request.model(), request.temperature());
// 模拟响应实际需要接入真实API
// TODO: 接入真实Claude API
return buildMockResponse();
} catch (RestClientException e) {
log.error("Claude API请求失败: {}", e.getMessage());
throw new LlmException("API请求失败: " + e.getMessage(), e);
}
}
/**
* 构建模拟响应开发阶段使用
*/
private ClaudeResponse buildMockResponse() {
// 开发阶段返回模拟数据
log.warn("使用Claude API模拟响应请配置真实API密钥");
ClaudeResponse.ContentBlock content = new ClaudeResponse.ContentBlock(
"text",
"{\"riskLevel\": 2, \"confidence\": 0.85, \"keyRiskFactors\": [\"开发阶段模拟数据\"], \"analysis\": \"这是模拟响应请配置Claude API密钥后使用真实调用\", \"suggestions\": [\"配置API密钥后重启服务\"]}"
);
return new ClaudeResponse(
"mock-id",
"assistant",
new java.util.List.of(content),
"stop",
"end_turn"
);
}
/**
* 清理JSON响应去除markdown包装
*/
private String cleanJsonResponse(String text) {
if (text == null) {
return "";
}
// 去除 ```json ``` 包装
text = text.replaceAll("```json\\s*", "")
.replaceAll("```\\s*", "")
.trim();
return text;
}
/**
* Claude API请求结构
*/
private record ClaudeRequest(
String model,
String prompt,
String systemPrompt,
double temperature,
int maxTokens
) {}
/**
* Claude API响应结构
*/
private record ClaudeResponse(
String id,
String type,
java.util.List<ContentBlock> content,
String stop_reason,
String role
) {
record ContentBlock(String type, String text) {}
}
}

View File

@ -0,0 +1,89 @@
package cn.iocoder.yudao.module.prison.service.riskassessment.llm;
import java.util.Map;
/**
* LLM客户端接口
*
* @author XLLC Team
*/
public interface LlmClient {
/**
* 调用大模型生成内容
*
* @param prompt 提示词
* @param options 调用选项
* @return 模型生成的文本响应
*/
String complete(String prompt, LlmOptions options);
/**
* 调用大模型生成JSON格式响应
*
* @param prompt 提示词
* @param options 调用选项
* @return JSON格式的响应文本
*/
String completeJson(String prompt, LlmOptions options);
/**
* 检查客户端是否可用
*
* @return 是否可用
*/
boolean isAvailable();
/**
* 获取客户端名称
*
* @return 客户端名称
*/
String getName();
/**
* 调用选项
*/
record LlmOptions(
String model, // 模型名称
double temperature, // 温度参数 (0.0-1.0)
int maxTokens, // 最大Token数
Map<String, String> headers // 额外请求头
) {
/**
* 创建默认选项
*/
public static LlmOptions defaultOptions() {
return new LlmOptions(
"claude-3-5-sonnet-20241022",
0.1, // 低温度输出更稳定
2000,
Map.of()
);
}
/**
* 创建低温度选项用于评估场景
*/
public static LlmOptions assessmentOptions() {
return new LlmOptions(
"claude-3-5-sonnet-20241022",
0.05, // 极低温度减少随机性
2048,
Map.of()
);
}
/**
* 创建高温度选项用于创意场景
*/
public static LlmOptions creativeOptions() {
return new LlmOptions(
"claude-3-5-sonnet-20241022",
0.7,
4096,
Map.of()
);
}
}
}

View File

@ -0,0 +1,106 @@
package cn.iocoder.yudao.module.prison.service.riskassessment.llm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* LLM客户端工厂
* 根据配置选择不同的LLM客户端
*
* @author XLLC Team
*/
@Slf4j
@Component
public class LlmClientFactory {
private final Map<String, LlmClient> clients;
public LlmClientFactory(List<LlmClient> clientList) {
// 初始化客户端映射
this.clients = new java.util.HashMap<>();
for (LlmClient client : clientList) {
clients.put(client.getName().toLowerCase(), client);
log.info("注册LLM客户端: {}", client.getName());
}
}
/**
* 获取指定名称的客户端
*
* @param name 客户端名称
* @return LLM客户端
*/
public LlmClient getClient(String name) {
LlmClient client = clients.get(name.toLowerCase());
if (client == null) {
throw new LlmException("UNKNOWN_CLIENT", "未找到LLM客户端: " + name);
}
return client;
}
/**
* 获取默认客户端
*
* @return 默认LLM客户端
*/
public LlmClient getDefaultClient() {
// 优先使用Claude
if (clients.containsKey("claude")) {
return clients.get("claude");
}
// 如果没有Claude返回第一个可用的客户端
return clients.values().stream()
.filter(LlmClient::isAvailable)
.findFirst()
.orElseThrow(() -> new LlmException("NO_AVAILABLE_CLIENT", "没有可用的LLM客户端"));
}
/**
* 获取用于评估的客户端
* 优先选择Claude评估能力强
*
* @return 评估用的LLM客户端
*/
public LlmClient getAssessmentClient() {
// 优先使用Claude
LlmClient claude = clients.get("claude");
if (claude != null && claude.isAvailable()) {
log.debug("选择Claude作为评估客户端");
return claude;
}
// 检查其他可用客户端
for (LlmClient client : clients.values()) {
if (client.isAvailable()) {
log.warn("Claude不可用使用备用客户端: {}", client.getName());
return client;
}
}
throw new LlmException("NO_AVAILABLE_CLIENT", "没有可用的LLM客户端进行评估");
}
/**
* 检查指定客户端是否可用
*
* @param name 客户端名称
* @return 是否可用
*/
public boolean isAvailable(String name) {
LlmClient client = clients.get(name.toLowerCase());
return client != null && client.isAvailable();
}
/**
* 获取所有已注册的客户端名称
*
* @return 客户端名称列表
*/
public List<String> getRegisteredClients() {
return clients.keySet().stream().toList();
}
}

View File

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.prison.service.riskassessment.llm;
/**
* LLM调用异常
*
* @author XLLC Team
*/
public class LlmException extends RuntimeException {
private final String errorCode;
public LlmException(String message) {
super(message);
this.errorCode = "LLM_ERROR";
}
public LlmException(String message, Throwable cause) {
super(message, cause);
this.errorCode = "LLM_ERROR";
}
public LlmException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public LlmException(String errorCode, String message, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}

View File

@ -0,0 +1,192 @@
-- =====================================================
-- XL监狱综合管理平台 - 测评执行与评分模块数据库脚本
-- 生成时间: 2026-01-15
-- 模块: assessment (测评执行与评分)
-- =====================================================
-- =====================================================
-- 1. 测评记录表 (prison_assessment_record)
-- 用于记录测评的执行情况,包括罪犯测评的发起、完成状态等
-- =====================================================
DROP TABLE IF EXISTS `prison_assessment_record`;
CREATE TABLE IF NOT EXISTS `prison_assessment_record` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '记录ID',
`prisoner_id` bigint NOT NULL COMMENT '罪犯ID',
`prisoner_name` varchar(100) NOT NULL COMMENT '罪犯姓名',
`prisoner_no` varchar(50) NOT NULL COMMENT '罪犯编号',
`questionnaire_id` bigint NOT NULL COMMENT '问卷模板ID',
`questionnaire_name` varchar(200) NOT NULL COMMENT '问卷名称',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-待测评 2-测评中 3-已完成 4-已过期',
`start_time` datetime DEFAULT NULL COMMENT '开始时间',
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`total_score` decimal(10,2) DEFAULT NULL COMMENT '总分',
`pass_status` tinyint DEFAULT NULL COMMENT '及格状态1-及格 2-不及格 3-待评阅',
`risk_level` tinyint DEFAULT NULL COMMENT '风险等级1-高风险 2-中风险 3-低风险',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
KEY `idx_assessment_record_prisoner_id` (`prisoner_id`),
KEY `idx_assessment_record_prisoner_no` (`prisoner_no`),
KEY `idx_assessment_record_questionnaire_id` (`questionnaire_id`),
KEY `idx_assessment_record_status` (`status`),
KEY `idx_assessment_record_create_time` (`create_time`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='测评记录表';
-- =====================================================
-- 2. 答卷详情表 (prison_assessment_answer)
-- 用于记录每道题的答题详情,包括客观题自动评分和主观题人工评分
-- =====================================================
DROP TABLE IF EXISTS `prison_assessment_answer`;
CREATE TABLE IF NOT EXISTS `prison_assessment_answer` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '答案ID',
`record_id` bigint NOT NULL COMMENT '测评记录ID',
`question_id` bigint NOT NULL COMMENT '题目ID',
`question_type` tinyint NOT NULL COMMENT '题目类型1-单选 2-多选 3-判断 4-填空 5-简述',
`question_content` varchar(500) NOT NULL COMMENT '题目内容',
`answer_content` text COMMENT '答案内容',
`score` decimal(10,2) DEFAULT 0.00 COMMENT '得分',
`is_correct` bit(1) DEFAULT NULL COMMENT '是否正确',
`is_manual_score` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否需要人工评分',
`manual_score` decimal(10,2) DEFAULT NULL COMMENT '人工评分分数',
`manual_comment` varchar(500) DEFAULT NULL COMMENT '人工评语',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
KEY `idx_assessment_answer_record_id` (`record_id`),
KEY `idx_assessment_answer_question_id` (`question_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='答卷详情表';
-- =====================================================
-- 3. 测评结果表 (prison_assessment_result)
-- 用于记录测评的最终结果,包括客观题得分、主观题得分、总分及风险评估
-- =====================================================
DROP TABLE IF EXISTS `prison_assessment_result`;
CREATE TABLE IF NOT EXISTS `prison_assessment_result` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '结果ID',
`record_id` bigint NOT NULL COMMENT '测评记录ID',
`objective_score` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '客观题得分',
`subjective_score` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '主观题得分',
`total_score` decimal(10,2) NOT NULL COMMENT '总分',
`pass_score` decimal(10,2) NOT NULL COMMENT '及格分数线',
`pass_status` tinyint NOT NULL COMMENT '及格状态1-及格 2-不及格 3-待评阅',
`risk_level` tinyint DEFAULT NULL COMMENT '风险等级1-高风险 2-中风险 3-低风险',
`evaluation_suggestion` text COMMENT '评估建议',
`evaluator_id` bigint DEFAULT NULL COMMENT '评阅人ID',
`evaluator_name` varchar(100) DEFAULT NULL COMMENT '评阅人姓名',
`evaluate_time` datetime DEFAULT NULL COMMENT '评阅时间',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_assessment_result_record_id` (`record_id`),
KEY `idx_assessment_result_evaluator_id` (`evaluator_id`),
KEY `idx_assessment_result_evaluate_time` (`evaluate_time`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='测评结果表';
-- =====================================================
-- 4. 测评统计表 (prison_assessment_statistics)
-- 用于统计测评的整体情况,包括发起数量、完成率、平均分、风险分布等
-- =====================================================
DROP TABLE IF EXISTS `prison_assessment_statistics`;
CREATE TABLE IF NOT EXISTS `prison_assessment_statistics` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '统计ID',
`questionnaire_id` bigint NOT NULL COMMENT '问卷ID',
`questionnaire_name` varchar(200) NOT NULL COMMENT '问卷名称',
`stat_date` date NOT NULL COMMENT '统计日期',
`total_count` int NOT NULL DEFAULT 0 COMMENT '发起总数',
`completed_count` int NOT NULL DEFAULT 0 COMMENT '完成数量',
`completion_rate` decimal(5,2) DEFAULT 0.00 COMMENT '完成率',
`average_score` decimal(10,2) DEFAULT 0.00 COMMENT '平均分',
`pass_rate` decimal(5,2) DEFAULT 0.00 COMMENT '及格率',
`high_risk_count` int NOT NULL DEFAULT 0 COMMENT '高风险人数',
`medium_risk_count` int NOT NULL DEFAULT 0 COMMENT '中风险人数',
`low_risk_count` int NOT NULL DEFAULT 0 COMMENT '低风险人数',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
KEY `idx_assessment_statistics_questionnaire_id` (`questionnaire_id`),
KEY `idx_assessment_statistics_stat_date` (`stat_date`),
UNIQUE KEY `uk_questionnaire_date` (`questionnaire_id`, `stat_date`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='测评统计表';
-- =====================================================
-- 菜单权限 SQL
-- =====================================================
-- 说明: 请将 ${table.parentMenuId} 替换为实际的父菜单ID
-- 监狱管理模块的父菜单ID通常为 5047 (系统管理) 或对应的监狱模块菜单ID
-- =====================================================
-- 获取监狱模块父菜单ID (假设为 5047请根据实际情况调整)
-- SELECT @parentId := 5047;
-- 1. 测评记录管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('测评记录管理', '', 2, 1, 5047, 'assessment-record', '', 'prison/assessment-record/index', 0, 'AssessmentRecord');
SELECT @assessmentRecordParentId := LAST_INSERT_ID();
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('测评记录查询', 'prison:assessment-record:query', 3, 1, @assessmentRecordParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('测评记录创建', 'prison:assessment-record:create', 3, 2, @assessmentRecordParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('测评记录更新', 'prison:assessment-record:update', 3, 3, @assessmentRecordParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('测评记录删除', 'prison:assessment-record:delete', 3, 4, @assessmentRecordParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('测评记录导出', 'prison:assessment-record:export', 3, 5, @assessmentRecordParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('发起测评', 'prison:assessment-record:start', 3, 6, @assessmentRecordParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('提交测评', 'prison:assessment-record:submit', 3, 7, @assessmentRecordParentId, '', '', '', 0);
-- 2. 答卷详情管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('答卷详情管理', '', 2, 2, 5047, 'assessment-answer', '', 'prison/assessment-answer/index', 0, 'AssessmentAnswer');
SELECT @assessmentAnswerParentId := LAST_INSERT_ID();
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('答卷详情查询', 'prison:assessment-answer:query', 3, 1, @assessmentAnswerParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('答卷详情更新', 'prison:assessment-answer:update', 3, 2, @assessmentAnswerParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('人工评分', 'prison:assessment-answer:manual-score', 3, 3, @assessmentAnswerParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('答卷详情导出', 'prison:assessment-answer:export', 3, 4, @assessmentAnswerParentId, '', '', '', 0);
-- 3. 测评结果管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('测评结果管理', '', 2, 3, 5047, 'assessment-result', '', 'prison/assessment-result/index', 0, 'AssessmentResult');
SELECT @assessmentResultParentId := LAST_INSERT_ID();
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('测评结果查询', 'prison:assessment-result:query', 3, 1, @assessmentResultParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('测评结果更新', 'prison:assessment-result:update', 3, 2, @assessmentResultParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('测评结果导出', 'prison:assessment-result:export', 3, 3, @assessmentResultParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('风险评估', 'prison:assessment-result:risk-evaluate', 3, 4, @assessmentResultParentId, '', '', '', 0);
-- 4. 测评统计管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('测评统计管理', '', 2, 4, 5047, 'assessment-statistics', '', 'prison/assessment-statistics/index', 0, 'AssessmentStatistics');
SELECT @assessmentStatisticsParentId := LAST_INSERT_ID();
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('测评统计查询', 'prison:assessment-statistics:query', 3, 1, @assessmentStatisticsParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('测评统计导出', 'prison:assessment-statistics:export', 3, 2, @assessmentStatisticsParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('生成统计', 'prison:assessment-statistics:generate', 3, 3, @assessmentStatisticsParentId, '', '', '', 0);

View File

@ -0,0 +1,18 @@
-- 问题表新增字段
-- 执行前请确保已备份数据库
-- 连接数据库: mysql -h 192.168.10.130 -u xlcp_dev -p xlcp_dev
ALTER TABLE prison_question
ADD COLUMN part_name VARCHAR(100) DEFAULT NULL COMMENT '分区名称' AFTER `sort`,
ADD COLUMN part_sort INT DEFAULT NULL COMMENT '分区排序' AFTER `part_name`,
ADD COLUMN help_text VARCHAR(500) DEFAULT NULL COMMENT '帮助说明' AFTER `part_sort`,
ADD COLUMN placeholder VARCHAR(200) DEFAULT NULL COMMENT '占位提示' AFTER `help_text`,
ADD COLUMN default_value VARCHAR(500) DEFAULT NULL COMMENT '默认值' AFTER `placeholder`,
ADD COLUMN auto_fill_type VARCHAR(20) DEFAULT NULL COMMENT '自动填充类型NONE-无 AUTO-自动 MANUAL-手动' AFTER `default_value`,
ADD COLUMN auto_fill_source VARCHAR(500) DEFAULT NULL COMMENT '自动填充来源' AFTER `auto_fill_type`,
ADD COLUMN display_condition VARCHAR(1000) DEFAULT NULL COMMENT '显示条件JSON' AFTER `auto_fill_source`,
ADD COLUMN min_value DECIMAL(10,2) DEFAULT NULL COMMENT '最小值' AFTER `display_condition`,
ADD COLUMN max_value DECIMAL(10,2) DEFAULT NULL COMMENT '最大值' AFTER `min_value`;
-- 验证字段是否添加成功
DESCRIBE prison_question;

View File

@ -0,0 +1,13 @@
-- 问卷模板表新增字段
-- 执行前请确保已备份数据库
-- 连接数据库: mysql -h 192.168.10.130 -u xlcp_dev -p xlcp_dev
ALTER TABLE prison_questionnaire
ADD COLUMN cover_image VARCHAR(500) DEFAULT NULL COMMENT '封面图片URL' AFTER `status`,
ADD COLUMN instruction VARCHAR(500) DEFAULT NULL COMMENT '填写说明' AFTER `cover_image`,
ADD COLUMN estimated_time INT DEFAULT NULL COMMENT '预计耗时(分钟)' AFTER `instruction`,
ADD COLUMN part_count INT DEFAULT NULL COMMENT '分区数量' AFTER `estimated_time`,
ADD COLUMN allow_anonymous TINYINT(1) DEFAULT NULL COMMENT '是否允许匿名0-否 1-是' AFTER `part_count`;
-- 验证字段是否添加成功
SELECT * FROM prison_questionnaire LIMIT 1;