refactor: 重构评估模块为答题模块,整合到问卷记录服务

主要变更:
- 删除 assessment 模块,原有功能整合到 questionnaire-record
- 新增 answer 模块处理答题记录
- QuestionnaireRecordServiceImpl 扩展测评执行、评分、统计功能
- 更新枚举类状态定义(1-待测评 2-测评中 3-已完成 4-已过期 5-已取消)
- 消费记录模块新增明细相关接口

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
tangweijie 2026-01-15 22:35:42 +08:00
parent bbcf68bdb8
commit dc65ef8d24
100 changed files with 3839 additions and 4684 deletions

View File

@ -0,0 +1,112 @@
package cn.iocoder.yudao.module.prison.controller.admin.answer;
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.answer.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.answer.AnswerDO;
import cn.iocoder.yudao.module.prison.service.answer.AnswerService;
@Tag(name = "管理后台 - 问卷答题记录")
@RestController
@RequestMapping("/prison/answer")
@Validated
public class PrisonAnswerController {
@Resource
private AnswerService answerService;
@PostMapping("/create")
@Operation(summary = "创建答题记录")
@PreAuthorize("@ss.hasPermission('prison:answer:create')")
public CommonResult<Long> createAnswer(@Valid @RequestBody AnswerSaveReqVO createReqVO) {
return success(answerService.createAnswer(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新答题记录")
@PreAuthorize("@ss.hasPermission('prison:answer:update')")
public CommonResult<Boolean> updateAnswer(@Valid @RequestBody AnswerSaveReqVO updateReqVO) {
answerService.updateAnswer(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除答题记录")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:answer:delete')")
public CommonResult<Boolean> deleteAnswer(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
answerService.deleteAnswer(id);
return success(true);
}
@PostMapping("/delete-list")
@Operation(summary = "批量删除答题记录")
@PreAuthorize("@ss.hasPermission('prison:answer:delete')")
public CommonResult<Boolean> deleteAnswerList(@NotEmpty(message = "编号列表不能为空") @RequestBody List<Long> ids) {
answerService.deleteAnswerListByIds(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得答题记录")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('prison:answer:query')")
public CommonResult<AnswerRespVO> getAnswer(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
AnswerDO answer = answerService.getAnswer(id);
return success(BeanUtils.toBean(answer, AnswerRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得答题记录分页")
@PreAuthorize("@ss.hasPermission('prison:answer:query')")
public CommonResult<PageResult<AnswerRespVO>> getAnswerPage(@Valid AnswerPageReqVO pageReqVO) {
PageResult<AnswerDO> pageResult = answerService.getAnswerPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, AnswerRespVO.class));
}
@GetMapping("/list-by-assessment-record")
@Operation(summary = "根据测评记录ID查询答题列表")
@PreAuthorize("@ss.hasPermission('prison:answer:query')")
public CommonResult<List<AnswerRespVO>> getAnswersByAssessmentRecordId(
@NotNull(message = "测评记录ID不能为空") @RequestParam("assessmentRecordId") Long assessmentRecordId) {
List<AnswerDO> list = answerService.getAnswersByAssessmentRecordId(assessmentRecordId);
return success(BeanUtils.toBean(list, AnswerRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出答题记录 Excel")
@PreAuthorize("@ss.hasPermission('prison:answer:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportAnswerExcel(@Valid AnswerPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<AnswerDO> list = answerService.getAnswerPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "答题记录.xls", "数据", AnswerRespVO.class,
BeanUtils.toBean(list, AnswerRespVO.class));
}
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.prison.controller.admin.answer.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import jakarta.validation.constraints.*;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
@Schema(description = "管理后台 - 问卷答题记录分页 Request VO")
@Data
public class AnswerPageReqVO extends PageParam {
@Schema(description = "测评记录ID")
private Long assessmentRecordId;
@Schema(description = "问题ID")
private Long questionId;
@Schema(description = "问卷ID")
private Long questionnaireId;
@Schema(description = "罪犯ID")
private Long prisonerId;
@Schema(description = "问题类型1-单选 2-多选 3-填空 4-评分 5-日期 6-数字")
private Integer questionType;
@Schema(description = "创建时间")
private Date[] createTime;
}

View File

@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.prison.controller.admin.answer.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 问卷答题记录 Response VO")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AnswerRespVO {
@Schema(description = "答题记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "测评记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long assessmentRecordId;
@Schema(description = "问题ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long questionId;
@Schema(description = "问卷ID", example = "1024")
private Long questionnaireId;
@Schema(description = "罪犯ID", example = "1024")
private Long prisonerId;
@Schema(description = "问题类型1-单选 2-多选 3-填空 4-评分 5-日期 6-数字", example = "1")
private Integer questionType;
@Schema(description = "答案内容(填空题、评分题等)")
private String answerText;
@Schema(description = "选项ID列表JSON数组如 [1,2,3]")
private String optionIds;
@Schema(description = "得分")
private BigDecimal score;
@Schema(description = "是否正确null-未评分 false-错误 true-正确")
private Boolean isCorrect;
@Schema(description = "答题时间(秒)")
private Integer duration;
@Schema(description = "创建者", example = "芋艿")
private String creator;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.prison.controller.admin.answer.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
@Schema(description = "管理后台 - 问卷答题记录新增/修改 Request VO")
@Data
public class AnswerSaveReqVO {
@Schema(description = "答题记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "26045")
private Long id;
@Schema(description = "测评记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "测评记录ID不能为空")
private Long assessmentRecordId;
@Schema(description = "问题ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "问题ID不能为空")
private Long questionId;
@Schema(description = "问卷ID")
private Long questionnaireId;
@Schema(description = "罪犯ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "罪犯ID不能为空")
private Long prisonerId;
@Schema(description = "问题类型1-单选 2-多选 3-填空 4-评分 5-日期 6-数字", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "问题类型不能为空")
private Integer questionType;
@Schema(description = "答案内容(填空题、评分题等)")
private String answerText;
@Schema(description = "选项ID列表JSON数组如 [1,2,3]")
private String optionIds;
@Schema(description = "得分")
private BigDecimal score;
@Schema(description = "是否正确null-未评分 false-错误 true-正确")
private Boolean isCorrect;
@Schema(description = "答题时间(秒)")
private Integer duration;
}

View File

@ -1,145 +0,0 @@
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

@ -1,134 +0,0 @@
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

@ -1,143 +0,0 @@
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

@ -1,95 +0,0 @@
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

@ -1,47 +0,0 @@
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

@ -1,91 +0,0 @@
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

@ -1,78 +0,0 @@
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

@ -1,24 +0,0 @@
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

@ -1,44 +0,0 @@
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

@ -1,81 +0,0 @@
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

@ -1,65 +0,0 @@
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

@ -1,71 +0,0 @@
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

@ -1,34 +0,0 @@
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

@ -1,44 +0,0 @@
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

@ -1,90 +0,0 @@
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

@ -1,80 +0,0 @@
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

@ -1,26 +0,0 @@
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

@ -1,76 +0,0 @@
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

@ -27,9 +27,12 @@ import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.prison.controller.admin.consumption.vo.*; import cn.iocoder.yudao.module.prison.controller.admin.consumption.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDO; import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDO;
import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDetailDO;
import cn.iocoder.yudao.module.prison.service.consumption.ConsumptionService; import cn.iocoder.yudao.module.prison.service.consumption.ConsumptionService;
import cn.iocoder.yudao.module.prison.convert.consumption.ConsumptionConvert;
import cn.iocoder.yudao.module.prison.convert.consumption.ConsumptionDetailConvert;
@Tag(name = "管理后台 - 消费记录") @Tag(name = "管理后台 - 消费订单")
@RestController @RestController
@RequestMapping("/prison/consumption") @RequestMapping("/prison/consumption")
@Validated @Validated
@ -39,14 +42,14 @@ public class PrisonConsumptionController {
private ConsumptionService consumptionService; private ConsumptionService consumptionService;
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建消费记录") @Operation(summary = "创建消费订单")
@PreAuthorize("@ss.hasPermission('prison:consumption:create')") @PreAuthorize("@ss.hasPermission('prison:consumption:create')")
public CommonResult<Long> createConsumption(@Valid @RequestBody ConsumptionSaveReqVO createReqVO) { public CommonResult<Long> createConsumption(@Valid @RequestBody ConsumptionSaveReqVO createReqVO) {
return success(consumptionService.createConsumption(createReqVO)); return success(consumptionService.createConsumption(createReqVO));
} }
@PutMapping("/update") @PutMapping("/update")
@Operation(summary = "更新消费记录") @Operation(summary = "更新消费订单")
@PreAuthorize("@ss.hasPermission('prison:consumption:update')") @PreAuthorize("@ss.hasPermission('prison:consumption:update')")
public CommonResult<Boolean> updateConsumption(@Valid @RequestBody ConsumptionSaveReqVO updateReqVO) { public CommonResult<Boolean> updateConsumption(@Valid @RequestBody ConsumptionSaveReqVO updateReqVO) {
consumptionService.updateConsumption(updateReqVO); consumptionService.updateConsumption(updateReqVO);
@ -54,8 +57,8 @@ public class PrisonConsumptionController {
} }
@DeleteMapping("/delete") @DeleteMapping("/delete")
@Operation(summary = "删除消费记录") @Operation(summary = "删除消费订单")
@Parameter(name = "id", description = "编号", required = true) @Parameter(name = "id", description = "订单编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:consumption:delete')") @PreAuthorize("@ss.hasPermission('prison:consumption:delete')")
public CommonResult<Boolean> deleteConsumption(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) { public CommonResult<Boolean> deleteConsumption(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
consumptionService.deleteConsumption(id); consumptionService.deleteConsumption(id);
@ -63,33 +66,51 @@ public class PrisonConsumptionController {
} }
@DeleteMapping("/delete-list") @DeleteMapping("/delete-list")
@Parameter(name = "ids", description = "编号", required = true) @Parameter(name = "ids", description = "编号列表", required = true)
@Operation(summary = "批量删除消费记录") @Operation(summary = "批量删除消费订单")
@PreAuthorize("@ss.hasPermission('prison:consumption:delete')") @PreAuthorize("@ss.hasPermission('prison:consumption:delete')")
public CommonResult<Boolean> deleteConsumptionList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) { public CommonResult<Boolean> deleteConsumptionList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
consumptionService.deleteConsumptionListByIds(ids); consumptionService.deleteConsumptionListByIds(ids);
return success(true); return success(true);
} }
@GetMapping("/get") @GetMapping("/get")
@Operation(summary = "获得消费记录") @Operation(summary = "获得消费订单详情")
@Parameter(name = "id", description = "编号", required = true, example = "1024") @Parameter(name = "id", description = "订单编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('prison:consumption:query')") @PreAuthorize("@ss.hasPermission('prison:consumption:query')")
public CommonResult<ConsumptionRespVO> getConsumption(@RequestParam("id") Long id) { public CommonResult<ConsumptionRespVO> getConsumption(@RequestParam("id") Long id) {
ConsumptionDO consumption = consumptionService.getConsumption(id); ConsumptionDO consumption = consumptionService.getConsumption(id);
return success(BeanUtils.toBean(consumption, ConsumptionRespVO.class)); if (consumption == null) {
return success(null);
}
// 转换主表数据
ConsumptionRespVO respVO = ConsumptionConvert.INSTANCE.convert(consumption);
// 查询并转换明细列表
List<ConsumptionDetailDO> detailList = consumptionService.getConsumptionDetailList(id);
respVO.setDetails(ConsumptionDetailConvert.INSTANCE.convertListResp(detailList));
return success(respVO);
} }
@GetMapping("/page") @GetMapping("/page")
@Operation(summary = "获得消费记录分页") @Operation(summary = "获得消费订单分页")
@PreAuthorize("@ss.hasPermission('prison:consumption:query')") @PreAuthorize("@ss.hasPermission('prison:consumption:query')")
public CommonResult<PageResult<ConsumptionRespVO>> getConsumptionPage(@Valid ConsumptionPageReqVO pageReqVO) { public CommonResult<PageResult<ConsumptionRespVO>> getConsumptionPage(@Valid ConsumptionPageReqVO pageReqVO) {
PageResult<ConsumptionDO> pageResult = consumptionService.getConsumptionPage(pageReqVO); PageResult<ConsumptionDO> pageResult = consumptionService.getConsumptionPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, ConsumptionRespVO.class)); return success(ConsumptionConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/detail-list")
@Operation(summary = "获得消费订单明细列表")
@Parameter(name = "consumptionId", description = "消费订单ID", required = true)
@PreAuthorize("@ss.hasPermission('prison:consumption:query')")
public CommonResult<List<ConsumptionDetailRespVO>> getConsumptionDetailList(
@RequestParam("consumptionId") Long consumptionId) {
List<ConsumptionDetailDO> detailList = consumptionService.getConsumptionDetailList(consumptionId);
return success(ConsumptionDetailConvert.INSTANCE.convertListResp(detailList));
} }
@GetMapping("/export-excel") @GetMapping("/export-excel")
@Operation(summary = "导出消费记录 Excel") @Operation(summary = "导出消费订单 Excel")
@PreAuthorize("@ss.hasPermission('prison:consumption:export')") @PreAuthorize("@ss.hasPermission('prison:consumption:export')")
@ApiAccessLog(operateType = EXPORT) @ApiAccessLog(operateType = EXPORT)
public void exportConsumptionExcel(@Valid ConsumptionPageReqVO pageReqVO, public void exportConsumptionExcel(@Valid ConsumptionPageReqVO pageReqVO,
@ -97,8 +118,8 @@ public class PrisonConsumptionController {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<ConsumptionDO> list = consumptionService.getConsumptionPage(pageReqVO).getList(); List<ConsumptionDO> list = consumptionService.getConsumptionPage(pageReqVO).getList();
// 导出 Excel // 导出 Excel
ExcelUtils.write(response, "消费记录.xls", "数据", ConsumptionRespVO.class, ExcelUtils.write(response, "消费订单.xls", "数据", ConsumptionRespVO.class,
BeanUtils.toBean(list, ConsumptionRespVO.class)); ConsumptionConvert.INSTANCE.convertList(list));
} }
} }

View File

@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.prison.controller.admin.consumption.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.math.BigDecimal;
import cn.idev.excel.annotation.*;
@Schema(description = "管理后台 - 消费订单明细 Response VO")
@Data
@ExcelIgnoreUnannotated
public class ConsumptionDetailRespVO {
@Schema(description = "明细ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4042")
@ExcelProperty("明细ID")
private Long id;
@Schema(description = "消费订单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@ExcelProperty("消费订单ID")
private Long consumptionId;
@Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("商品名称")
private String goodsName;
@Schema(description = "商品编码")
@ExcelProperty("商品编码")
private String goodsCode;
@Schema(description = "商品单价", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("商品单价")
private BigDecimal goodsPrice;
@Schema(description = "商品数量", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("商品数量")
private Integer goodsCount;
@Schema(description = "小计金额", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("小计金额")
private BigDecimal subtotal;
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.prison.controller.admin.consumption.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import java.math.BigDecimal;
import jakarta.validation.constraints.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import cn.idev.excel.annotation.*;
@Schema(description = "管理后台 - 消费订单明细新增/修改 Request VO")
@Data
public class ConsumptionDetailSaveReqVO {
@Schema(description = "明细ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4042")
private Long id;
@Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "商品名称不能为空")
private String goodsName;
@Schema(description = "商品编码")
private String goodsCode;
@Schema(description = "商品单价", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "商品单价不能为空")
private BigDecimal goodsPrice;
@Schema(description = "商品数量", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "商品数量不能为空")
private Integer goodsCount;
}

View File

@ -7,35 +7,39 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import java.math.BigDecimal; import java.math.BigDecimal;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Size;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 消费记录分页 Request VO") @Schema(description = "管理后台 - 消费订单分页 Request VO")
@Data @Data
public class ConsumptionPageReqVO extends PageParam { public class ConsumptionPageReqVO extends PageParam {
@Schema(description = "罪犯ID", example = "25932") @Schema(description = "罪犯ID", example = "25932")
@Min(value = 1, message = "罪犯ID必须为正数")
private Long prisonerId; private Long prisonerId;
@Schema(description = "罪犯编号") @Schema(description = "罪犯编号")
@Size(max = 50, message = "罪犯编号长度不能超过50个字符")
private String prisonerNo; private String prisonerNo;
@Schema(description = "类型1-存款 2-消费 3-转账", example = "1") @Schema(description = "类型1-购物 2-餐饮 3-医疗 4-通讯 5-其他", example = "1")
@Min(value = 1, message = "类型最小值为1")
@Max(value = 5, message = "类型最大值为5")
private Integer type; private Integer type;
@Schema(description = "金额") @Schema(description = "订单总金额")
private BigDecimal amount; @Min(value = 0, message = "订单总金额不能为负数")
private BigDecimal totalAmount;
@Schema(description = "账户余额") @Schema(description = "账户余额")
@Min(value = 0, message = "账户余额不能为负数")
private BigDecimal balance; private BigDecimal balance;
@Schema(description = "商品名称", example = "芋艿")
private String goodsName;
@Schema(description = "商品数量", example = "3906")
private Integer goodsCount;
@Schema(description = "订单号") @Schema(description = "订单号")
@Size(max = 64, message = "订单号长度不能超过64个字符")
private String orderNo; private String orderNo;
@Schema(description = "交易时间") @Schema(description = "交易时间")
@ -43,13 +47,16 @@ public class ConsumptionPageReqVO extends PageParam {
private LocalDateTime[] tradeTime; private LocalDateTime[] tradeTime;
@Schema(description = "状态1-成功 2-失败", example = "1") @Schema(description = "状态1-成功 2-失败", example = "1")
@Min(value = 1, message = "状态最小值为1")
@Max(value = 2, message = "状态最大值为2")
private Integer status; private Integer status;
@Schema(description = "备注", example = "你说的对")
private String remark;
@Schema(description = "创建时间") @Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime; private LocalDateTime[] createTime;
} @Schema(description = "备注")
@Size(max = 500, message = "备注长度不能超过500个字符")
private String remark;
}

View File

@ -8,13 +8,13 @@ import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import cn.idev.excel.annotation.*; import cn.idev.excel.annotation.*;
@Schema(description = "管理后台 - 消费记录 Response VO") @Schema(description = "管理后台 - 消费订单 Response VO")
@Data @Data
@ExcelIgnoreUnannotated @ExcelIgnoreUnannotated
public class ConsumptionRespVO { public class ConsumptionRespVO {
@Schema(description = "记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4042") @Schema(description = "消费ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4042")
@ExcelProperty("记录ID") @ExcelProperty("消费ID")
private Long id; private Long id;
@Schema(description = "罪犯ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25932") @Schema(description = "罪犯ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25932")
@ -25,39 +25,31 @@ public class ConsumptionRespVO {
@ExcelProperty("罪犯编号") @ExcelProperty("罪犯编号")
private String prisonerNo; private String prisonerNo;
@Schema(description = "类型1-存款 2-消费 3-转账", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("类型1-存款 2-消费 3-转账")
private Integer type;
@Schema(description = "金额", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("金额")
private BigDecimal amount;
@Schema(description = "账户余额")
@ExcelProperty("账户余额")
private BigDecimal balance;
@Schema(description = "商品名称", example = "芋艿")
@ExcelProperty("商品名称")
private String goodsName;
@Schema(description = "商品数量", example = "3906")
@ExcelProperty("商品数量")
private Integer goodsCount;
@Schema(description = "订单号") @Schema(description = "订单号")
@ExcelProperty("订单号") @ExcelProperty("订单号")
private String orderNo; private String orderNo;
@Schema(description = "类型1-购物 2-餐饮 3-医疗 4-通讯 5-其他", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("类型")
private Integer type;
@Schema(description = "订单总金额", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("订单总金额")
private BigDecimal totalAmount;
@Schema(description = "账户余额(消费后)")
@ExcelProperty("账户余额")
private BigDecimal balance;
@Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("交易时间") @ExcelProperty("交易时间")
private LocalDateTime tradeTime; private LocalDateTime tradeTime;
@Schema(description = "状态1-成功 2-失败", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @Schema(description = "状态1-成功 2-失败", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("状态1-成功 2-失败") @ExcelProperty("状态")
private Integer status; private Integer status;
@Schema(description = "备注", example = "你说的对") @Schema(description = "备注", example = "订单备注")
@ExcelProperty("备注") @ExcelProperty("备注")
private String remark; private String remark;
@ -65,4 +57,7 @@ public class ConsumptionRespVO {
@ExcelProperty("创建时间") @ExcelProperty("创建时间")
private LocalDateTime createTime; private LocalDateTime createTime;
} @Schema(description = "消费明细列表")
private List<ConsumptionDetailRespVO> details;
}

View File

@ -8,11 +8,11 @@ import java.math.BigDecimal;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Schema(description = "管理后台 - 消费记录新增/修改 Request VO") @Schema(description = "管理后台 - 消费订单新增/修改 Request VO")
@Data @Data
public class ConsumptionSaveReqVO { public class ConsumptionSaveReqVO {
@Schema(description = "记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4042") @Schema(description = "消费ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4042")
private Long id; private Long id;
@Schema(description = "罪犯ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25932") @Schema(description = "罪犯ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25932")
@ -23,35 +23,32 @@ public class ConsumptionSaveReqVO {
@NotEmpty(message = "罪犯编号不能为空") @NotEmpty(message = "罪犯编号不能为空")
private String prisonerNo; private String prisonerNo;
@Schema(description = "类型1-存款 2-消费 3-转账", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "类型1-存款 2-消费 3-转账不能为空")
private Integer type;
@Schema(description = "金额", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "金额不能为空")
private BigDecimal amount;
@Schema(description = "账户余额")
private BigDecimal balance;
@Schema(description = "商品名称", example = "芋艿")
private String goodsName;
@Schema(description = "商品数量", example = "3906")
private Integer goodsCount;
@Schema(description = "订单号") @Schema(description = "订单号")
private String orderNo; private String orderNo;
@Schema(description = "类型1-购物 2-餐饮 3-医疗 4-通讯 5-其他", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "类型不能为空")
private Integer type;
@Schema(description = "订单总金额", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "订单总金额不能为空")
private BigDecimal totalAmount;
@Schema(description = "账户余额(消费后)")
private BigDecimal balance;
@Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "交易时间不能为空") @NotNull(message = "交易时间不能为空")
private LocalDateTime tradeTime; private LocalDateTime tradeTime;
@Schema(description = "状态1-成功 2-失败", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @Schema(description = "状态1-成功 2-失败", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "状态1-成功 2-失败不能为空") @NotNull(message = "状态不能为空")
private Integer status; private Integer status;
@Schema(description = "备注", example = "你说的对") @Schema(description = "备注", example = "订单备注")
private String remark; private String remark;
} @Schema(description = "消费明细列表")
private List<ConsumptionDetailSaveReqVO> details;
}

View File

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

View File

@ -13,6 +13,7 @@ import jakarta.validation.*;
import jakarta.servlet.http.*; import jakarta.servlet.http.*;
import java.util.*; import java.util.*;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
@ -29,7 +30,7 @@ import cn.iocoder.yudao.module.prison.controller.admin.questionnairerecord.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO; import cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO;
import cn.iocoder.yudao.module.prison.service.questionnairerecord.QuestionnaireRecordService; import cn.iocoder.yudao.module.prison.service.questionnairerecord.QuestionnaireRecordService;
@Tag(name = "管理后台 - 问卷答题记录") @Tag(name = "管理后台 - 问卷答题记录/测评记录")
@RestController @RestController
@RequestMapping("/prison/questionnaire-record") @RequestMapping("/prison/questionnaire-record")
@Validated @Validated
@ -38,6 +39,8 @@ public class PrisonQuestionnaireRecordController {
@Resource @Resource
private QuestionnaireRecordService questionnaireRecordService; private QuestionnaireRecordService questionnaireRecordService;
// ==================== 基础 CRUD ====================
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建问卷答题记录") @Operation(summary = "创建问卷答题记录")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:create')") @PreAuthorize("@ss.hasPermission('prison:questionnaire-record:create')")
@ -62,11 +65,10 @@ public class PrisonQuestionnaireRecordController {
return success(true); return success(true);
} }
@DeleteMapping("/delete-list") @PostMapping("/delete-list")
@Parameter(name = "ids", description = "编号", required = true)
@Operation(summary = "批量删除问卷答题记录") @Operation(summary = "批量删除问卷答题记录")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:delete')") @PreAuthorize("@ss.hasPermission('prison:questionnaire-record:delete')")
public CommonResult<Boolean> deleteQuestionnaireRecordList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) { public CommonResult<Boolean> deleteQuestionnaireRecordList(@NotEmpty(message = "编号列表不能为空") @RequestBody List<Long> ids) {
questionnaireRecordService.deleteQuestionnaireRecordListByIds(ids); questionnaireRecordService.deleteQuestionnaireRecordListByIds(ids);
return success(true); return success(true);
} }
@ -75,7 +77,7 @@ public class PrisonQuestionnaireRecordController {
@Operation(summary = "获得问卷答题记录") @Operation(summary = "获得问卷答题记录")
@Parameter(name = "id", description = "编号", required = true, example = "1024") @Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:query')") @PreAuthorize("@ss.hasPermission('prison:questionnaire-record:query')")
public CommonResult<QuestionnaireRecordRespVO> getQuestionnaireRecord(@RequestParam("id") Long id) { public CommonResult<QuestionnaireRecordRespVO> getQuestionnaireRecord(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
QuestionnaireRecordDO questionnaireRecord = questionnaireRecordService.getQuestionnaireRecord(id); QuestionnaireRecordDO questionnaireRecord = questionnaireRecordService.getQuestionnaireRecord(id);
return success(BeanUtils.toBean(questionnaireRecord, QuestionnaireRecordRespVO.class)); return success(BeanUtils.toBean(questionnaireRecord, QuestionnaireRecordRespVO.class));
} }
@ -101,4 +103,87 @@ public class PrisonQuestionnaireRecordController {
BeanUtils.toBean(list, QuestionnaireRecordRespVO.class)); BeanUtils.toBean(list, QuestionnaireRecordRespVO.class));
} }
} // ==================== 测评执行相关 ====================
@PostMapping("/initiate")
@Operation(summary = "发起测评")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:initiate')")
public CommonResult<Long> initiateAssessment(@Valid @RequestBody AssessmentInitiateReqVO reqVO) {
return success(questionnaireRecordService.initiateAssessment(reqVO));
}
@PostMapping("/start")
@Operation(summary = "开始测评")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:start')")
public CommonResult<Boolean> startAssessment(@NotNull(message = "记录ID不能为空") @RequestParam("id") Long id,
@NotNull(message = "罪犯ID不能为空") @RequestParam("prisonerId") Long prisonerId) {
questionnaireRecordService.startAssessment(id, prisonerId);
return success(true);
}
@PostMapping("/submit")
@Operation(summary = "提交答卷")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:submit')")
public CommonResult<Boolean> submitAnswer(@Valid @RequestBody AssessmentAnswerSubmitReqVO reqVO) {
questionnaireRecordService.submitAnswer(reqVO);
return success(true);
}
@PostMapping("/finish")
@Operation(summary = "结束测评")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:finish')")
public CommonResult<Boolean> finishAssessment(@NotNull(message = "记录ID不能为空") @RequestParam("id") Long id) {
questionnaireRecordService.finishAssessment(id);
return success(true);
}
@PostMapping("/cancel")
@Operation(summary = "取消测评")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:cancel')")
public CommonResult<Boolean> cancelAssessment(@NotNull(message = "记录ID不能为空") @RequestParam("id") Long id) {
questionnaireRecordService.cancelAssessment(id);
return success(true);
}
// ==================== 评分相关 ====================
@PostMapping("/auto-score")
@Operation(summary = "自动评分")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:score')")
public CommonResult<Boolean> autoScore(@NotNull(message = "记录ID不能为空") @RequestParam("id") Long id) {
questionnaireRecordService.autoScore(id);
return success(true);
}
@PostMapping("/manual-score")
@Operation(summary = "人工评分")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:score')")
public CommonResult<Boolean> manualScore(@Valid @RequestBody AssessmentManualScoreReqVO reqVO) {
questionnaireRecordService.manualScore(reqVO);
return success(true);
}
// ==================== 统计相关 ====================
@GetMapping("/completion-rate")
@Operation(summary = "获取完成率")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:query')")
public CommonResult<BigDecimal> getCompletionRate(@NotNull(message = "测评记录ID不能为空") @RequestParam("assessmentRecordId") Long assessmentRecordId) {
return success(questionnaireRecordService.getCompletionRate(assessmentRecordId));
}
@GetMapping("/score-distribution")
@Operation(summary = "获取分数分布")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:query')")
public CommonResult<Map<String, Integer>> getScoreDistribution(@NotNull(message = "测评记录ID不能为空") @RequestParam("assessmentRecordId") Long assessmentRecordId) {
return success(questionnaireRecordService.getScoreDistribution(assessmentRecordId));
}
@GetMapping("/risk-distribution")
@Operation(summary = "获取风险分布")
@PreAuthorize("@ss.hasPermission('prison:questionnaire-record:query')")
public CommonResult<Map<String, Integer>> getRiskDistribution(@NotNull(message = "测评记录ID不能为空") @RequestParam("assessmentRecordId") Long assessmentRecordId) {
return success(questionnaireRecordService.getRiskDistribution(assessmentRecordId));
}
}

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.prison.controller.admin.questionnairerecord.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* 提交答卷 Request VO
*
* @author 芋道源码
*/
@Data
public class AssessmentAnswerSubmitReqVO {
@Schema(description = "测评记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "测评记录不能为空")
private Long recordId;
@Schema(description = "罪犯ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "罪犯不能为空")
private Long prisonerId;
@Schema(description = "答案列表")
private List<AnswerItem> answers;
@Data
public static class AnswerItem {
@Schema(description = "题目ID", example = "1024")
private Long questionId;
@Schema(description = "答案内容")
private String answer;
@Schema(description = "选项ID列表多选用")
private List<Long> optionIds;
}
}

View File

@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.prison.controller.admin.questionnairerecord.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 发起测评 Request VO
*
* @author 芋道源码
*/
@Data
public class AssessmentInitiateReqVO {
@Schema(description = "问卷ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "问卷不能为空")
private Long questionnaireId;
@Schema(description = "罪犯ID列表", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "罪犯不能为空")
private List<Long> prisonerIds;
@Schema(description = "测评说明")
private String remark;
@Schema(description = "截止日期")
private LocalDateTime deadline;
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.prison.controller.admin.assessment.vo; package cn.iocoder.yudao.module.prison.controller.admin.questionnairerecord.vo;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
@ -11,11 +11,11 @@ import java.math.BigDecimal;
* @author 芋道源码 * @author 芋道源码
*/ */
@Data @Data
public class AssessmentAnswerManualScoreReqVO { public class AssessmentManualScoreReqVO {
@Schema(description = "答卷ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "测评记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "答卷ID不能为空") @NotNull(message = "测评记录不能为空")
private Long id; private Long recordId;
@Schema(description = "主观题得分", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "主观题得分", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "主观题得分不能为空") @NotNull(message = "主观题得分不能为空")
@ -24,4 +24,7 @@ public class AssessmentAnswerManualScoreReqVO {
@Schema(description = "评语") @Schema(description = "评语")
private String comment; private String comment;
@Schema(description = "风险等级1-高风险 2-中风险 3-低风险")
private Integer riskLevel;
} }

View File

@ -10,34 +10,51 @@ import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 问卷答题记录分页 Request VO") /**
* 管理后台 - 问卷答题记录/测评记录分页 Request VO
*
* @author 芋道源码
*/
@Data @Data
public class QuestionnaireRecordPageReqVO extends PageParam { public class QuestionnaireRecordPageReqVO extends PageParam {
@Schema(description = "问卷ID", example = "18966") @Schema(description = "问卷ID", example = "18966")
private Long questionnaireId; private Long questionnaireId;
@Schema(description = "问卷名称", example = "心理测评")
private String questionnaireName;
@Schema(description = "罪犯ID", example = "4071") @Schema(description = "罪犯ID", example = "4071")
private Long prisonerId; private Long prisonerId;
@Schema(description = "罪犯编号") @Schema(description = "罪犯编号")
private String prisonerNo; private String prisonerNo;
@Schema(description = "得分") @Schema(description = "罪犯姓名")
private BigDecimal totalScore; private String prisonerName;
@Schema(description = "是否及格1-及格 2-不及格", example = "2") @Schema(description = "状态1-待测评 2-测评中 3-已完成 4-已过期 5-已取消", example = "1")
private Integer status;
@Schema(description = "及格状态1-及格 2-不及格 3-待评阅", example = "1")
private Integer passStatus; private Integer passStatus;
@Schema(description = "答题时间") @Schema(description = "风险等级1-高风险 2-中风险 3-低风险", example = "3")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private Integer riskLevel;
private LocalDateTime[] answerTime;
@Schema(description = "状态1-已完成 2-已过期", example = "1") @Schema(description = "总分")
private Integer status; private BigDecimal totalScore;
@Schema(description = "测评开始时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] startTime;
@Schema(description = "测评截止时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] deadline;
@Schema(description = "创建时间") @Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime; private LocalDateTime[] createTime;
} }

View File

@ -8,6 +8,11 @@ import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import cn.idev.excel.annotation.*; import cn.idev.excel.annotation.*;
/**
* 管理后台 - 问卷答题记录/测评记录 Response VO
*
* @author 芋道源码
*/
@Schema(description = "管理后台 - 问卷答题记录 Response VO") @Schema(description = "管理后台 - 问卷答题记录 Response VO")
@Data @Data
@ExcelIgnoreUnannotated @ExcelIgnoreUnannotated
@ -17,10 +22,18 @@ public class QuestionnaireRecordRespVO {
@ExcelProperty("记录ID") @ExcelProperty("记录ID")
private Long id; private Long id;
// ==================== 问卷信息 ====================
@Schema(description = "问卷ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "18966") @Schema(description = "问卷ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "18966")
@ExcelProperty("问卷ID") @ExcelProperty("问卷ID")
private Long questionnaireId; private Long questionnaireId;
@Schema(description = "问卷名称")
@ExcelProperty("问卷名称")
private String questionnaireName;
// ==================== 罪犯信息 ====================
@Schema(description = "罪犯ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4071") @Schema(description = "罪犯ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4071")
@ExcelProperty("罪犯ID") @ExcelProperty("罪犯ID")
private Long prisonerId; private Long prisonerId;
@ -29,24 +42,90 @@ public class QuestionnaireRecordRespVO {
@ExcelProperty("罪犯编号") @ExcelProperty("罪犯编号")
private String prisonerNo; private String prisonerNo;
@Schema(description = "得分") @Schema(description = "罪犯姓名")
@ExcelProperty("得分") @ExcelProperty("罪犯姓名")
private String prisonerName;
// ==================== 测评状态 ====================
@Schema(description = "状态1-待测评 2-测评中 3-已完成 4-已过期 5-已取消", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("状态")
private Integer status;
// ==================== 测评时间 ====================
@Schema(description = "开始时间")
@ExcelProperty("开始时间")
private LocalDateTime startTime;
@Schema(description = "结束时间")
@ExcelProperty("结束时间")
private LocalDateTime endTime;
@Schema(description = "截止日期")
@ExcelProperty("截止日期")
private LocalDateTime deadline;
// ==================== 评分信息 ====================
@Schema(description = "客观题得分")
@ExcelProperty("客观题得分")
private BigDecimal objectiveScore;
@Schema(description = "主观题得分")
@ExcelProperty("主观题得分")
private BigDecimal subjectiveScore;
@Schema(description = "总分")
@ExcelProperty("总分")
private BigDecimal totalScore; private BigDecimal totalScore;
@Schema(description = "是否及格1-及格 2-不及格", example = "2") @Schema(description = "及格分数")
@ExcelProperty("是否及格1-及格 2-不及格") @ExcelProperty("及格分数")
private BigDecimal passScore;
@Schema(description = "及格状态1-及格 2-不及格 3-待评阅", example = "2")
@ExcelProperty("及格状态")
private Integer passStatus; private Integer passStatus;
@Schema(description = "答题时间", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "风险等级1-高风险 2-中风险 3-低风险", example = "3")
@ExcelProperty("答题时间") @ExcelProperty("风险等级")
private LocalDateTime answerTime; private Integer riskLevel;
@Schema(description = "状态1-已完成 2-已过期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") // ==================== 评阅信息 ====================
@ExcelProperty("状态1-已完成 2-已过期")
private Integer status; @Schema(description = "评阅人ID")
private Long evaluatorId;
@Schema(description = "评阅人姓名")
@ExcelProperty("评阅人")
private String evaluatorName;
@Schema(description = "评阅时间")
@ExcelProperty("评阅时间")
private LocalDateTime evaluateTime;
// ==================== 统计信息 ====================
@Schema(description = "参与人数")
private Integer participantCount;
@Schema(description = "完成人数")
private Integer completedCount;
@Schema(description = "答题用时(秒)")
@ExcelProperty("答题用时")
private Integer duration;
// ==================== 备注 ====================
@Schema(description = "备注")
private String remark;
// ==================== 通用字段 ====================
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间") @ExcelProperty("创建时间")
private LocalDateTime createTime; private LocalDateTime createTime;
} }

View File

@ -8,37 +8,100 @@ import java.math.BigDecimal;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Schema(description = "管理后台 - 问卷答题记录新增/修改 Request VO") /**
* 管理后台 - 问卷答题记录/测评记录新增/修改 Request VO
*
* @author 芋道源码
*/
@Data @Data
public class QuestionnaireRecordSaveReqVO { public class QuestionnaireRecordSaveReqVO {
@Schema(description = "记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "8596") @Schema(description = "记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "8596")
private Long id; private Long id;
// ==================== 问卷信息 ====================
@Schema(description = "问卷ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "18966") @Schema(description = "问卷ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "18966")
@NotNull(message = "问卷ID不能为空") @NotNull(message = "问卷ID不能为空")
private Long questionnaireId; private Long questionnaireId;
@Schema(description = "问卷名称")
private String questionnaireName;
// ==================== 罪犯信息 ====================
@Schema(description = "罪犯ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4071") @Schema(description = "罪犯ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "4071")
@NotNull(message = "罪犯ID不能为空") @NotNull(message = "罪犯ID不能为空")
private Long prisonerId; private Long prisonerId;
@Schema(description = "罪犯编号", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "罪犯编号")
@NotEmpty(message = "罪犯编号不能为空")
private String prisonerNo; private String prisonerNo;
@Schema(description = "得分") @Schema(description = "罪犯姓名")
private BigDecimal totalScore; private String prisonerName;
@Schema(description = "是否及格1-及格 2-不及格", example = "2") // ==================== 测评状态 ====================
private Integer passStatus;
@Schema(description = "答题时间", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "状态1-待测评 2-测评中 3-已完成 4-已过期 5-已取消", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "答题时间不能为空") @NotNull(message = "状态不能为空")
private LocalDateTime answerTime;
@Schema(description = "状态1-已完成 2-已过期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "状态1-已完成 2-已过期不能为空")
private Integer status; private Integer status;
} // ==================== 测评时间 ====================
@Schema(description = "开始时间")
private LocalDateTime startTime;
@Schema(description = "结束时间")
private LocalDateTime endTime;
@Schema(description = "截止日期")
private LocalDateTime deadline;
// ==================== 评分信息 ====================
@Schema(description = "客观题得分")
private BigDecimal objectiveScore;
@Schema(description = "主观题得分")
private BigDecimal subjectiveScore;
@Schema(description = "总分")
private BigDecimal totalScore;
@Schema(description = "及格分数")
private BigDecimal passScore;
@Schema(description = "及格状态1-及格 2-不及格 3-待评阅", example = "2")
private Integer passStatus;
@Schema(description = "风险等级1-高风险 2-中风险 3-低风险", example = "3")
private Integer riskLevel;
// ==================== 评阅信息 ====================
@Schema(description = "评阅人ID")
private Long evaluatorId;
@Schema(description = "评阅人姓名")
private String evaluatorName;
@Schema(description = "评阅时间")
private LocalDateTime evaluateTime;
// ==================== 统计信息 ====================
@Schema(description = "参与人数")
private Integer participantCount;
@Schema(description = "完成人数")
private Integer completedCount;
@Schema(description = "答题用时(秒)")
private Integer duration;
// ==================== 备注 ====================
@Schema(description = "备注")
private String remark;
}

View File

@ -1,104 +0,0 @@
package cn.iocoder.yudao.module.prison.controller.admin.riskassessment;
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.riskassessment.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.riskassessment.RiskAssessmentDO;
import cn.iocoder.yudao.module.prison.service.riskassessment.RiskAssessmentService;
@Tag(name = "管理后台 - 危险评估")
@RestController
@RequestMapping("/prison/risk-assessment")
@Validated
public class PrisonRiskAssessmentController {
@Resource
private RiskAssessmentService riskAssessmentService;
@PostMapping("/create")
@Operation(summary = "创建危险评估")
@PreAuthorize("@ss.hasPermission('prison:risk-assessment:create')")
public CommonResult<Long> createRiskAssessment(@Valid @RequestBody RiskAssessmentSaveReqVO createReqVO) {
return success(riskAssessmentService.createRiskAssessment(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新危险评估")
@PreAuthorize("@ss.hasPermission('prison:risk-assessment:update')")
public CommonResult<Boolean> updateRiskAssessment(@Valid @RequestBody RiskAssessmentSaveReqVO updateReqVO) {
riskAssessmentService.updateRiskAssessment(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除危险评估")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('prison:risk-assessment:delete')")
public CommonResult<Boolean> deleteRiskAssessment(@NotNull(message = "编号不能为空") @RequestParam("id") Long id) {
riskAssessmentService.deleteRiskAssessment(id);
return success(true);
}
@DeleteMapping("/delete-list")
@Parameter(name = "ids", description = "编号", required = true)
@Operation(summary = "批量删除危险评估")
@PreAuthorize("@ss.hasPermission('prison:risk-assessment:delete')")
public CommonResult<Boolean> deleteRiskAssessmentList(@NotEmpty(message = "编号列表不能为空") @RequestParam("ids") List<Long> ids) {
riskAssessmentService.deleteRiskAssessmentListByIds(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得危险评估")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('prison:risk-assessment:query')")
public CommonResult<RiskAssessmentRespVO> getRiskAssessment(@RequestParam("id") Long id) {
RiskAssessmentDO riskAssessment = riskAssessmentService.getRiskAssessment(id);
return success(BeanUtils.toBean(riskAssessment, RiskAssessmentRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得危险评估分页")
@PreAuthorize("@ss.hasPermission('prison:risk-assessment:query')")
public CommonResult<PageResult<RiskAssessmentRespVO>> getRiskAssessmentPage(@Valid RiskAssessmentPageReqVO pageReqVO) {
PageResult<RiskAssessmentDO> pageResult = riskAssessmentService.getRiskAssessmentPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, RiskAssessmentRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出危险评估 Excel")
@PreAuthorize("@ss.hasPermission('prison:risk-assessment:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportRiskAssessmentExcel(@Valid RiskAssessmentPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<RiskAssessmentDO> list = riskAssessmentService.getRiskAssessmentPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "危险评估.xls", "数据", RiskAssessmentRespVO.class,
BeanUtils.toBean(list, RiskAssessmentRespVO.class));
}
}

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.prison.convert.answer;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.prison.controller.admin.answer.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.answer.AnswerDO;
import org.mapstruct.Builder;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 问卷答题记录 Convert
*
* @author 芋道源码
*/
@Mapper(builder = @Builder(disableBuilder = true))
public interface AnswerConvert {
AnswerConvert INSTANCE = Mappers.getMapper(AnswerConvert.class);
/**
* DO -> RespVO
*/
AnswerRespVO convert(AnswerDO bean);
/**
* DO -> RespVO List
*/
List<AnswerRespVO> convertList(List<AnswerDO> list);
/**
* PageResult DO -> PageResult RespVO
*/
PageResult<AnswerRespVO> convertPage(PageResult<AnswerDO> page);
/**
* SaveReqVO -> DO
*/
AnswerDO convert(AnswerSaveReqVO bean);
}

View File

@ -1,34 +0,0 @@
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

@ -1,34 +0,0 @@
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

@ -1,34 +0,0 @@
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

@ -1,30 +0,0 @@
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

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.prison.convert.consumption;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.prison.controller.admin.consumption.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDO;
import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDetailDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 消费订单 Convert
*
* @author xl
*/
@Mapper
public interface ConsumptionConvert {
ConsumptionConvert INSTANCE = Mappers.getMapper(ConsumptionConvert.class);
ConsumptionDO convert(ConsumptionSaveReqVO bean);
ConsumptionRespVO convert(ConsumptionDO bean);
List<ConsumptionRespVO> convertList(List<ConsumptionDO> list);
PageResult<ConsumptionRespVO> convertPage(PageResult<ConsumptionDO> page);
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.prison.convert.consumption;
import java.util.*;
import cn.iocoder.yudao.module.prison.controller.admin.consumption.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDetailDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 消费明细 Convert
*
* @author xl
*/
@Mapper
public interface ConsumptionDetailConvert {
ConsumptionDetailConvert INSTANCE = Mappers.getMapper(ConsumptionDetailConvert.class);
ConsumptionDetailDO convert(ConsumptionDetailSaveReqVO bean);
ConsumptionDetailRespVO convert(ConsumptionDetailDO bean);
List<ConsumptionDetailDO> convertList(List<ConsumptionDetailSaveReqVO> list);
List<ConsumptionDetailRespVO> convertListResp(List<ConsumptionDetailDO> list);
}

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.prison.dal.dataobject.answer;
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_answer")
@KeySequence("prison_answer_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AnswerDO extends BaseDO {
/**
* 答题记录ID
*/
@TableId
private Long id;
/**
* 测评记录ID
*/
private Long assessmentRecordId;
/**
* 问题ID
*/
private Long questionId;
/**
* 问卷ID冗余
*/
private Long questionnaireId;
/**
* 罪犯ID
*/
private Long prisonerId;
/**
* 问题类型1-单选 2-多选 3-填空 4-评分 5-日期 6-数字
*/
private Integer questionType;
/**
* 答案内容填空题评分题等
*/
private String answerText;
/**
* 选项ID列表JSON数组 [1,2,3]
*/
private String optionIds;
/**
* 得分
*/
private BigDecimal score;
/**
* 是否正确null-未评分 false-错误 true-正确
*/
private Boolean isCorrect;
/**
* 答题时间
*/
private Integer duration;
}

View File

@ -1,131 +0,0 @@
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

@ -1,116 +0,0 @@
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

@ -1,136 +0,0 @@
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

@ -1,115 +0,0 @@
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

@ -3,17 +3,14 @@ package cn.iocoder.yudao.module.prison.dal.dataobject.consumption;
import lombok.*; import lombok.*;
import java.util.*; import java.util.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/** /**
* 消费记录 DO * 消费订单 DO
* *
* @author 芋道源码 * @author xl
*/ */
@TableName("prison_consumption") @TableName("prison_consumption")
@KeySequence("prison_consumption_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写 @KeySequence("prison_consumption_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@ -26,7 +23,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
public class ConsumptionDO extends BaseDO { public class ConsumptionDO extends BaseDO {
/** /**
* 记录ID * 消费ID
*/ */
@TableId @TableId
private Long id; private Long id;
@ -38,30 +35,22 @@ public class ConsumptionDO extends BaseDO {
* 罪犯编号 * 罪犯编号
*/ */
private String prisonerNo; private String prisonerNo;
/**
* 类型1-存款 2-消费 3-转账
*/
private Integer type;
/**
* 金额
*/
private BigDecimal amount;
/**
* 账户余额
*/
private BigDecimal balance;
/**
* 商品名称
*/
private String goodsName;
/**
* 商品数量
*/
private Integer goodsCount;
/** /**
* 订单号 * 订单号
*/ */
private String orderNo; private String orderNo;
/**
* 类型1-购物 2-餐饮 3-医疗 4-通讯 5-其他
*/
private Integer type;
/**
* 订单总金额
*/
private BigDecimal totalAmount;
/**
* 账户余额消费后
*/
private BigDecimal balance;
/** /**
* 交易时间 * 交易时间
*/ */
@ -75,5 +64,4 @@ public class ConsumptionDO extends BaseDO {
*/ */
private String remark; private String remark;
}
}

View File

@ -0,0 +1,58 @@
package cn.iocoder.yudao.module.prison.dal.dataobject.consumption;
import lombok.*;
import java.util.*;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 消费明细 DO
*
* @author xl
*/
@TableName("prison_consumption_detail")
@KeySequence("prison_consumption_detail_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ConsumptionDetailDO extends BaseDO {
/**
* 明细ID
*/
@TableId
private Long id;
/**
* 消费订单ID
*/
private Long consumptionId;
/**
* 罪犯ID冗余便于查询
*/
private Long prisonerId;
/**
* 商品名称
*/
private String goodsName;
/**
* 商品编码
*/
private String goodsCode;
/**
* 商品单价
*/
private BigDecimal goodsPrice;
/**
* 商品数量
*/
private Integer goodsCount;
/**
* 小计金额
*/
private BigDecimal subtotal;
}

View File

@ -4,18 +4,16 @@ import lombok.*;
import java.util.*; import java.util.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/** /**
* 问卷答题记录 DO * 问卷答题记录 / 测评记录 DO
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@TableName("prison_questionnaire_record") @TableName("prison_questionnaire_record")
@KeySequence("prison_questionnaire_record_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写 @KeySequence("prison_questionnaire_record_seq")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)
@ -29,10 +27,20 @@ public class QuestionnaireRecordDO extends BaseDO {
*/ */
@TableId @TableId
private Long id; private Long id;
// ==================== 问卷信息 ====================
/** /**
* 问卷ID * 问卷ID
*/ */
private Long questionnaireId; private Long questionnaireId;
/**
* 问卷名称
*/
private String questionnaireName;
// ==================== 罪犯信息 ====================
/** /**
* 罪犯ID * 罪犯ID
*/ */
@ -42,21 +50,94 @@ public class QuestionnaireRecordDO extends BaseDO {
*/ */
private String prisonerNo; private String prisonerNo;
/** /**
* 得分 * 罪犯姓名
*/ */
private BigDecimal totalScore; private String prisonerName;
// ==================== 测评状态 ====================
/** /**
* 是否及格1-及格 2-不及格 * 状态1-待测评 2-测评中 3-已完成 4-已过期 5-已取消
*/
private Integer passStatus;
/**
* 答题时间
*/
private LocalDateTime answerTime;
/**
* 状态1-已完成 2-已过期
*/ */
private Integer status; private Integer status;
// ==================== 测评时间 ====================
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 结束时间
*/
private LocalDateTime endTime;
/**
* 截止日期
*/
private LocalDateTime deadline;
// ==================== 评分信息 ====================
/**
* 客观题得分
*/
private BigDecimal objectiveScore;
/**
* 主观题得分
*/
private BigDecimal subjectiveScore;
/**
* 总分
*/
private BigDecimal totalScore;
/**
* 及格分数
*/
private BigDecimal passScore;
/**
* 及格状态1-及格 2-不及格 3-待评阅
*/
private Integer passStatus;
/**
* 风险等级1-高风险 2-中风险 3-低风险
*/
private Integer riskLevel;
// ==================== 评阅信息 ====================
/**
* 评阅人ID
*/
private Long evaluatorId;
/**
* 评阅人姓名
*/
private String evaluatorName;
/**
* 评阅时间
*/
private LocalDateTime evaluateTime;
// ==================== 统计信息 ====================
/**
* 参与人数
*/
private Integer participantCount;
/**
* 完成人数
*/
private Integer completedCount;
/**
* 答题用时
*/
private Integer duration;
// ==================== 备注 ====================
/**
* 备注
*/
private String remark;
} }

View File

@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.prison.dal.mysql.answer;
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.answer.AnswerDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.prison.controller.admin.answer.vo.*;
/**
* 问卷答题记录 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface AnswerMapper extends BaseMapperX<AnswerDO> {
default PageResult<AnswerDO> selectPage(AnswerPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<AnswerDO>()
.eqIfPresent(AnswerDO::getAssessmentRecordId, reqVO.getAssessmentRecordId())
.eqIfPresent(AnswerDO::getQuestionId, reqVO.getQuestionId())
.eqIfPresent(AnswerDO::getQuestionnaireId, reqVO.getQuestionnaireId())
.eqIfPresent(AnswerDO::getPrisonerId, reqVO.getPrisonerId())
.eqIfPresent(AnswerDO::getQuestionType, reqVO.getQuestionType())
.betweenIfPresent(AnswerDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(AnswerDO::getId));
}
/**
* 根据测评记录ID查询所有答题记录
*/
default List<AnswerDO> selectListByAssessmentRecordId(Long assessmentRecordId) {
return selectList(new LambdaQueryWrapperX<AnswerDO>()
.eqIfPresent(AnswerDO::getAssessmentRecordId, assessmentRecordId)
.orderByAsc(AnswerDO::getId));
}
/**
* 根据测评记录ID和问题ID查询答题记录
*/
default AnswerDO selectByAssessmentRecordIdAndQuestionId(Long assessmentRecordId, Long questionId) {
return selectOne(new LambdaQueryWrapperX<AnswerDO>()
.eqIfPresent(AnswerDO::getAssessmentRecordId, assessmentRecordId)
.eqIfPresent(AnswerDO::getQuestionId, questionId));
}
/**
* 根据测评记录ID删除所有答题记录
*/
default int deleteByAssessmentRecordId(Long assessmentRecordId) {
return delete(new LambdaQueryWrapperX<AnswerDO>()
.eqIfPresent(AnswerDO::getAssessmentRecordId, assessmentRecordId));
}
}

View File

@ -1,51 +0,0 @@
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

@ -1,33 +0,0 @@
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

@ -1,71 +0,0 @@
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

@ -1,37 +0,0 @@
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

@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.prison.dal.mysql.consumption;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDetailDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* 消费明细 Mapper
*
* @author xl
*/
@Mapper
public interface ConsumptionDetailMapper extends BaseMapperX<ConsumptionDetailDO> {
/**
* 根据消费订单ID列表删除
*/
default void deleteListByConsumptionIds(Collection<Long> consumptionIds) {
delete(new LambdaQueryWrapperX<ConsumptionDetailDO>()
.in(ConsumptionDetailDO::getConsumptionId, consumptionIds));
}
/**
* 根据消费订单ID删除
*/
default void deleteListByConsumptionId(Long consumptionId) {
delete(new LambdaQueryWrapperX<ConsumptionDetailDO>()
.eq(ConsumptionDetailDO::getConsumptionId, consumptionId));
}
/**
* 根据消费订单ID查询明细列表
*/
default List<ConsumptionDetailDO> selectListByConsumptionId(Long consumptionId) {
return selectList(new LambdaQueryWrapperX<ConsumptionDetailDO>()
.eq(ConsumptionDetailDO::getConsumptionId, consumptionId));
}
}

View File

@ -10,9 +10,9 @@ import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.prison.controller.admin.consumption.vo.*; import cn.iocoder.yudao.module.prison.controller.admin.consumption.vo.*;
/** /**
* 消费记录 Mapper * 消费订单 Mapper
* *
* @author 芋道源码 * @author xl
*/ */
@Mapper @Mapper
public interface ConsumptionMapper extends BaseMapperX<ConsumptionDO> { public interface ConsumptionMapper extends BaseMapperX<ConsumptionDO> {
@ -22,10 +22,8 @@ public interface ConsumptionMapper extends BaseMapperX<ConsumptionDO> {
.eqIfPresent(ConsumptionDO::getPrisonerId, reqVO.getPrisonerId()) .eqIfPresent(ConsumptionDO::getPrisonerId, reqVO.getPrisonerId())
.eqIfPresent(ConsumptionDO::getPrisonerNo, reqVO.getPrisonerNo()) .eqIfPresent(ConsumptionDO::getPrisonerNo, reqVO.getPrisonerNo())
.eqIfPresent(ConsumptionDO::getType, reqVO.getType()) .eqIfPresent(ConsumptionDO::getType, reqVO.getType())
.eqIfPresent(ConsumptionDO::getAmount, reqVO.getAmount()) .eqIfPresent(ConsumptionDO::getTotalAmount, reqVO.getTotalAmount())
.eqIfPresent(ConsumptionDO::getBalance, reqVO.getBalance()) .eqIfPresent(ConsumptionDO::getBalance, reqVO.getBalance())
.likeIfPresent(ConsumptionDO::getGoodsName, reqVO.getGoodsName())
.eqIfPresent(ConsumptionDO::getGoodsCount, reqVO.getGoodsCount())
.eqIfPresent(ConsumptionDO::getOrderNo, reqVO.getOrderNo()) .eqIfPresent(ConsumptionDO::getOrderNo, reqVO.getOrderNo())
.betweenIfPresent(ConsumptionDO::getTradeTime, reqVO.getTradeTime()) .betweenIfPresent(ConsumptionDO::getTradeTime, reqVO.getTradeTime())
.eqIfPresent(ConsumptionDO::getStatus, reqVO.getStatus()) .eqIfPresent(ConsumptionDO::getStatus, reqVO.getStatus())
@ -34,4 +32,4 @@ public interface ConsumptionMapper extends BaseMapperX<ConsumptionDO> {
.orderByDesc(ConsumptionDO::getId)); .orderByDesc(ConsumptionDO::getId));
} }
} }

View File

@ -24,7 +24,6 @@ public interface QuestionnaireRecordMapper extends BaseMapperX<QuestionnaireReco
.eqIfPresent(QuestionnaireRecordDO::getPrisonerNo, reqVO.getPrisonerNo()) .eqIfPresent(QuestionnaireRecordDO::getPrisonerNo, reqVO.getPrisonerNo())
.eqIfPresent(QuestionnaireRecordDO::getTotalScore, reqVO.getTotalScore()) .eqIfPresent(QuestionnaireRecordDO::getTotalScore, reqVO.getTotalScore())
.eqIfPresent(QuestionnaireRecordDO::getPassStatus, reqVO.getPassStatus()) .eqIfPresent(QuestionnaireRecordDO::getPassStatus, reqVO.getPassStatus())
.betweenIfPresent(QuestionnaireRecordDO::getAnswerTime, reqVO.getAnswerTime())
.eqIfPresent(QuestionnaireRecordDO::getStatus, reqVO.getStatus()) .eqIfPresent(QuestionnaireRecordDO::getStatus, reqVO.getStatus())
.betweenIfPresent(QuestionnaireRecordDO::getCreateTime, reqVO.getCreateTime()) .betweenIfPresent(QuestionnaireRecordDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(QuestionnaireRecordDO::getId)); .orderByDesc(QuestionnaireRecordDO::getId));

View File

@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.prison.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.HashMap;
import java.util.Map;
/**
* 消费状态枚举
*
* @author xl
*/
@Getter
@AllArgsConstructor
public enum ConsumptionStatusEnum {
SUCCESS(1, "成功", "success"),
FAILED(2, "失败", "failed");
/**
* 状态编码
*/
private final Integer code;
/**
* 状态名称
*/
private final String name;
/**
* 状态标识用于字典
*/
private final String type;
private static final Map<Integer, ConsumptionStatusEnum> MAP = new HashMap<>();
static {
for (ConsumptionStatusEnum item : ConsumptionStatusEnum.values()) {
MAP.put(item.getCode(), item);
}
}
public static ConsumptionStatusEnum fromCode(Integer code) {
return MAP.get(code);
}
public boolean isValid() {
return MAP.containsKey(this.code);
}
}

View File

@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.prison.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.HashMap;
import java.util.Map;
/**
* 消费类型枚举
*
* @author xl
*/
@Getter
@AllArgsConstructor
public enum ConsumptionTypeEnum {
SHOPPING(1, "购物", "shopping"),
DINING(2, "餐饮", "dining"),
MEDICAL(3, "医疗", "medical"),
COMMUNICATION(4, "通讯", "communication"),
OTHER(5, "其他", "other");
/**
* 类型编码
*/
private final Integer code;
/**
* 类型名称
*/
private final String name;
/**
* 类型标识用于字典
*/
private final String type;
private static final Map<Integer, ConsumptionTypeEnum> MAP = new HashMap<>();
static {
for (ConsumptionTypeEnum item : ConsumptionTypeEnum.values()) {
MAP.put(item.getCode(), item);
}
}
public static ConsumptionTypeEnum fromCode(Integer code) {
return MAP.get(code);
}
public boolean isValid() {
return MAP.containsKey(this.code);
}
}

View File

@ -35,6 +35,9 @@ public class ErrorCodeConstants {
// ========== 消费记录 7xxxx ========== // ========== 消费记录 7xxxx ==========
public static final ErrorCode PRISON_CONSUMPTION_NOT_EXISTS = new ErrorCode(7_000_001, "消费记录不存在"); public static final ErrorCode PRISON_CONSUMPTION_NOT_EXISTS = new ErrorCode(7_000_001, "消费记录不存在");
public static final ErrorCode PRISON_CONSUMPTION_AMOUNT_MISMATCH = new ErrorCode(7_000_002, "消费明细金额与订单总金额不一致");
public static final ErrorCode PRISON_CONSUMPTION_DETAIL_EMPTY = new ErrorCode(7_000_003, "消费明细不能为空");
public static final ErrorCode PRISON_CONSUMPTION_DETAIL_INVALID = new ErrorCode(7_000_004, "消费明细数据不合法,请检查商品名称、单价和数量");
// ========== 罪犯监区变动记录 8xxxx ========== // ========== 罪犯监区变动记录 8xxxx ==========
public static final ErrorCode PRISONER_AREA_LOG_NOT_EXISTS = new ErrorCode(8_000_001, "罪犯监区变动记录不存在"); public static final ErrorCode PRISONER_AREA_LOG_NOT_EXISTS = new ErrorCode(8_000_001, "罪犯监区变动记录不存在");
@ -56,5 +59,9 @@ public class ErrorCodeConstants {
public static final ErrorCode QUESTIONNAIRE_NOT_EXISTS = PRISON_QUESTIONNAIRE_NOT_EXISTS; public static final ErrorCode QUESTIONNAIRE_NOT_EXISTS = PRISON_QUESTIONNAIRE_NOT_EXISTS;
public static final ErrorCode QUESTION_NOT_EXISTS = PRISON_QUESTION_NOT_EXISTS; public static final ErrorCode QUESTION_NOT_EXISTS = PRISON_QUESTION_NOT_EXISTS;
public static final ErrorCode QUESTIONNAIRE_RECORD_NOT_EXISTS = PRISON_QUESTIONNAIRE_RECORD_NOT_EXISTS; public static final ErrorCode QUESTIONNAIRE_RECORD_NOT_EXISTS = PRISON_QUESTIONNAIRE_RECORD_NOT_EXISTS;
public static final ErrorCode QUESTIONNAIRE_RECORD_STATUS_ERROR = new ErrorCode(6_000_004, "问卷答题记录状态不合法");
// ========== 答卷管理 10xxxx ==========
public static final ErrorCode ANSWER_NOT_EXISTS = new ErrorCode(10_000_001, "答卷记录不存在");
} }

View File

@ -5,6 +5,7 @@ import lombok.Getter;
/** /**
* 问卷答题记录及格状态枚举 * 问卷答题记录及格状态枚举
* 状态1-及格 2-不及格 3-待评阅
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@ -12,8 +13,9 @@ import lombok.Getter;
@AllArgsConstructor @AllArgsConstructor
public enum QuestionnaireRecordPassStatusEnum { public enum QuestionnaireRecordPassStatusEnum {
NOT_PASSED(0, "未及格"), PASSED(1, "及格"),
PASSED(1, "及格"); FAILED(2, "不及格"),
PENDING(3, "待评阅");
/** /**
* 状态 * 状态
@ -38,6 +40,9 @@ public enum QuestionnaireRecordPassStatusEnum {
} }
public static QuestionnaireRecordPassStatusEnum getByStatus(Integer status) { public static QuestionnaireRecordPassStatusEnum getByStatus(Integer status) {
if (status == null) {
return null;
}
for (QuestionnaireRecordPassStatusEnum value : values()) { for (QuestionnaireRecordPassStatusEnum value : values()) {
if (value.getStatus().equals(status)) { if (value.getStatus().equals(status)) {
return value; return value;
@ -45,4 +50,5 @@ public enum QuestionnaireRecordPassStatusEnum {
} }
return null; return null;
} }
} }

View File

@ -5,6 +5,7 @@ import lombok.Getter;
/** /**
* 问卷答题记录状态枚举 * 问卷答题记录状态枚举
* 状态1-待测评 2-测评中 3-已完成 4-已过期 5-已取消
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@ -12,8 +13,11 @@ import lombok.Getter;
@AllArgsConstructor @AllArgsConstructor
public enum QuestionnaireRecordStatusEnum { public enum QuestionnaireRecordStatusEnum {
PENDING(1, "待评估"), PENDING(1, "待测评"),
COMPLETED(2, "已完成"); IN_PROGRESS(2, "测评中"),
COMPLETED(3, "已完成"),
EXPIRED(4, "已过期"),
CANCELLED(5, "已取消");
/** /**
* 状态 * 状态
@ -45,4 +49,12 @@ public enum QuestionnaireRecordStatusEnum {
} }
return null; return null;
} }
/**
* 判断是否为待测评或测评中状态可进行状态流转
*/
public static boolean canTransition(Integer status) {
return PENDING.getStatus().equals(status) || IN_PROGRESS.getStatus().equals(status);
}
} }

View File

@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.prison.enums.questionnaire;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 测评及格状态枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum AssessmentPassStatusEnum {
PASSED(1, "及格"),
FAILED(2, "不及格"),
PENDING(3, "待评阅");
/**
* 状态
*/
private final Integer status;
/**
* 名称
*/
private final String name;
public static AssessmentPassStatusEnum getByStatus(Integer status) {
for (AssessmentPassStatusEnum statusEnum : values()) {
if (statusEnum.getStatus().equals(status)) {
return statusEnum;
}
}
return null;
}
}

View File

@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.prison.enums.questionnaire;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 测评记录状态枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum AssessmentRecordStatusEnum {
PENDING(1, "待测评"),
IN_PROGRESS(2, "测评中"),
COMPLETED(3, "已完成"),
EXPIRED(4, "已过期"),
CANCELLED(5, "已取消");
/**
* 状态
*/
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,37 @@
package cn.iocoder.yudao.module.prison.enums.questionnaire;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 风险等级枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum RiskLevelEnum {
HIGH(1, "高风险"),
MEDIUM(2, "中风险"),
LOW(3, "低风险");
/**
* 等级
*/
private final Integer level;
/**
* 名称
*/
private final String name;
public static RiskLevelEnum getByLevel(Integer level) {
for (RiskLevelEnum levelEnum : values()) {
if (levelEnum.getLevel().equals(level)) {
return levelEnum;
}
}
return null;
}
}

View File

@ -0,0 +1,97 @@
package cn.iocoder.yudao.module.prison.service.answer;
import java.util.*;
import jakarta.validation.*;
import cn.iocoder.yudao.module.prison.controller.admin.answer.vo.*;
import cn.iocoder.yudao.module.prison.controller.admin.questionnairerecord.vo.AssessmentAnswerSubmitReqVO;
import cn.iocoder.yudao.module.prison.dal.dataobject.answer.AnswerDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import java.math.BigDecimal;
/**
* 问卷答题记录 Service 接口
*
* @author 芋道源码
*/
public interface AnswerService {
/**
* 创建答题记录
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createAnswer(@Valid AnswerSaveReqVO createReqVO);
/**
* 更新答题记录
*
* @param updateReqVO 更新信息
*/
void updateAnswer(@Valid AnswerSaveReqVO updateReqVO);
/**
* 删除答题记录
*
* @param id 编号
*/
void deleteAnswer(Long id);
/**
* 批量删除答题记录
*
* @param ids 编号列表
*/
void deleteAnswerListByIds(List<Long> ids);
/**
* 获得答题记录
*
* @param id 编号
* @return 答题记录
*/
AnswerDO getAnswer(Long id);
/**
* 获得答题记录分页
*
* @param pageReqVO 分页查询
* @return 答题记录分页
*/
PageResult<AnswerDO> getAnswerPage(AnswerPageReqVO pageReqVO);
/**
* 根据测评记录ID查询所有答题记录
*
* @param assessmentRecordId 测评记录ID
* @return 答题记录列表
*/
List<AnswerDO> getAnswersByAssessmentRecordId(Long assessmentRecordId);
/**
* 批量保存答题记录提交答卷时使用
*
* @param assessmentRecordId 测评记录ID
* @param questionnaireId 问卷ID
* @param prisonerId 罪犯ID
* @param answers 答题详情列表
*/
void saveAnswers(Long assessmentRecordId, Long questionnaireId, Long prisonerId, List<AssessmentAnswerSubmitReqVO.AnswerItem> answers);
/**
* 计算客观题得分并更新
*
* @param assessmentRecordId 测评记录ID
* @return 总客观题得分
*/
BigDecimal calculateObjectiveScore(Long assessmentRecordId);
/**
* 根据测评记录ID删除所有答题记录
*
* @param assessmentRecordId 测评记录ID
*/
void deleteByAssessmentRecordId(Long assessmentRecordId);
}

View File

@ -0,0 +1,373 @@
package cn.iocoder.yudao.module.prison.service.answer;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.math.RoundingMode;
import cn.iocoder.yudao.module.prison.controller.admin.answer.vo.*;
import cn.iocoder.yudao.module.prison.controller.admin.questionnairerecord.vo.AssessmentAnswerSubmitReqVO;
import cn.iocoder.yudao.module.prison.dal.dataobject.answer.AnswerDO;
import cn.iocoder.yudao.module.prison.dal.dataobject.question.QuestionDO;
import cn.iocoder.yudao.module.prison.dal.mysql.answer.AnswerMapper;
import cn.iocoder.yudao.module.prison.dal.mysql.question.QuestionMapper;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.prison.enums.QuestionnaireRecordPassStatusEnum;
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 AnswerServiceImpl implements AnswerService {
@Resource
private AnswerMapper answerMapper;
@Resource
private QuestionMapper questionMapper;
// 问题类型常量
private static final int QUESTION_TYPE_SINGLE = 1; // 单选
private static final int QUESTION_TYPE_MULTI = 2; // 多选
private static final int QUESTION_TYPE_FILL = 3; // 填空
private static final int QUESTION_TYPE_SCORE = 4; // 评分
private static final int QUESTION_TYPE_DATE = 5; // 日期
private static final int QUESTION_TYPE_NUMBER = 6; // 数字
@Override
public Long createAnswer(AnswerSaveReqVO createReqVO) {
// 插入
AnswerDO answer = BeanUtils.toBean(createReqVO, AnswerDO.class);
answerMapper.insert(answer);
return answer.getId();
}
@Override
public void updateAnswer(AnswerSaveReqVO updateReqVO) {
// 校验存在
validateAnswerExists(updateReqVO.getId());
// 更新
AnswerDO updateObj = BeanUtils.toBean(updateReqVO, AnswerDO.class);
answerMapper.updateById(updateObj);
}
@Override
public void deleteAnswer(Long id) {
// 校验存在
validateAnswerExists(id);
// 删除
answerMapper.deleteById(id);
}
@Override
public void deleteAnswerListByIds(List<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return;
}
// 校验所有ID都存在
Long count = answerMapper.selectCount(new LambdaQueryWrapper<AnswerDO>()
.in(AnswerDO::getId, ids));
if (count == null || count != ids.size()) {
throw exception(ANSWER_NOT_EXISTS);
}
// 删除
answerMapper.deleteByIds(ids);
}
private void validateAnswerExists(Long id) {
if (answerMapper.selectById(id) == null) {
throw exception(ANSWER_NOT_EXISTS);
}
}
@Override
public AnswerDO getAnswer(Long id) {
return answerMapper.selectById(id);
}
@Override
public PageResult<AnswerDO> getAnswerPage(AnswerPageReqVO pageReqVO) {
return answerMapper.selectPage(pageReqVO);
}
@Override
public List<AnswerDO> getAnswersByAssessmentRecordId(Long assessmentRecordId) {
return answerMapper.selectListByAssessmentRecordId(assessmentRecordId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveAnswers(Long assessmentRecordId, Long questionnaireId, Long prisonerId,
List<AssessmentAnswerSubmitReqVO.AnswerItem> answers) {
if (CollUtil.isEmpty(answers)) {
return;
}
for (AssessmentAnswerSubmitReqVO.AnswerItem answerItem : answers) {
// 获取问题信息
QuestionDO question = questionMapper.selectById(answerItem.getQuestionId());
if (question == null) {
continue;
}
// 创建答题记录
AnswerDO answer = new AnswerDO();
answer.setAssessmentRecordId(assessmentRecordId);
answer.setQuestionId(answerItem.getQuestionId());
answer.setQuestionnaireId(questionnaireId);
answer.setPrisonerId(prisonerId);
answer.setQuestionType(question.getType());
// 设置答案内容
if (answerItem.getOptionIds() != null && !answerItem.getOptionIds().isEmpty()) {
answer.setOptionIds(JSONArray.toJSONString(answerItem.getOptionIds()));
}
if (answerItem.getAnswer() != null) {
answer.setAnswerText(answerItem.getAnswer());
}
// 计算得分单选/多选题
BigDecimal score = calculateQuestionScore(question, answerItem);
answer.setScore(score);
// 判断是否正确单选/多选题
if (question.getType() == QUESTION_TYPE_SINGLE || question.getType() == QUESTION_TYPE_MULTI) {
Boolean isCorrect = isAnswerCorrect(question, answerItem);
answer.setIsCorrect(isCorrect);
}
answerMapper.insert(answer);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public BigDecimal calculateObjectiveScore(Long assessmentRecordId) {
// 获取该测评的所有答题记录
List<AnswerDO> answers = answerMapper.selectListByAssessmentRecordId(assessmentRecordId);
if (CollUtil.isEmpty(answers)) {
return BigDecimal.ZERO;
}
// 计算客观题得分单选多选
BigDecimal totalScore = BigDecimal.ZERO;
for (AnswerDO answer : answers) {
if (answer.getScore() != null) {
totalScore = totalScore.add(answer.getScore());
}
}
return totalScore.setScale(2, RoundingMode.HALF_UP);
}
@Override
public void deleteByAssessmentRecordId(Long assessmentRecordId) {
answerMapper.deleteByAssessmentRecordId(assessmentRecordId);
}
/**
* 计算单题得分
*/
private BigDecimal calculateQuestionScore(QuestionDO question, AssessmentAnswerSubmitReqVO.AnswerItem answerItem) {
if (question.getType() == QUESTION_TYPE_SINGLE || question.getType() == QUESTION_TYPE_MULTI) {
// 单选/多选题根据选项计算得分
return calculateChoiceQuestionScore(question, answerItem);
} else if (question.getType() == QUESTION_TYPE_SCORE) {
// 评分题直接使用用户评分
try {
if (answerItem.getAnswer() != null) {
return new BigDecimal(answerItem.getAnswer());
}
} catch (NumberFormatException e) {
return BigDecimal.ZERO;
}
} else if (question.getType() == QUESTION_TYPE_NUMBER) {
// 数字题根据范围计算部分分数
return calculateNumberQuestionScore(question, answerItem);
}
// 填空题日期题暂不计算分数
return BigDecimal.ZERO;
}
/**
* 计算选择题得分
* 选项格式: [{label:"选项1",score:10,isCorrect:true},...]
*/
private BigDecimal calculateChoiceQuestionScore(QuestionDO question, AssessmentAnswerSubmitReqVO.AnswerItem answerItem) {
if (question.getOptions() == null || question.getOptions().isEmpty()) {
return BigDecimal.ZERO;
}
try {
JSONArray options = JSON.parseArray(question.getOptions());
if (options.isEmpty()) {
return BigDecimal.ZERO;
}
// 获取问题总分
BigDecimal questionScore = question.getScore() != null ? question.getScore() : BigDecimal.ZERO;
if (question.getType() == QUESTION_TYPE_SINGLE) {
// 单选题选对得满分选错得0分
if (answerItem.getOptionIds() == null || answerItem.getOptionIds().isEmpty()) {
return BigDecimal.ZERO;
}
Long selectedOptionId = answerItem.getOptionIds().get(0);
for (int i = 0; i < options.size(); i++) {
JSONObject option = options.getJSONObject(i);
if (option.getLong("id") != null && option.getLong("id").equals(selectedOptionId)) {
// 检查是否正确选项
if (option.getBoolean("isCorrect") != null && option.getBoolean("isCorrect")) {
return questionScore;
}
}
}
return BigDecimal.ZERO;
} else if (question.getType() == QUESTION_TYPE_MULTI) {
// 多选题部分得分逻辑
if (answerItem.getOptionIds() == null || answerItem.getOptionIds().isEmpty()) {
return BigDecimal.ZERO;
}
// 统计正确选项和用户选择
int correctCount = 0;
int userSelectCount = answerItem.getOptionIds().size();
for (int i = 0; i < options.size(); i++) {
JSONObject option = options.getJSONObject(i);
if (option.getBoolean("isCorrect") != null && option.getBoolean("isCorrect")) {
correctCount++;
}
}
// 用户选择的正确选项数量
int userCorrectCount = 0;
for (Long selectedOptionId : answerItem.getOptionIds()) {
for (int i = 0; i < options.size(); i++) {
JSONObject option = options.getJSONObject(i);
if (option.getLong("id") != null && option.getLong("id").equals(selectedOptionId)
&& option.getBoolean("isCorrect") != null && option.getBoolean("isCorrect")) {
userCorrectCount++;
break;
}
}
}
// 计算得分按正确比例给分
if (correctCount == 0) {
return BigDecimal.ZERO;
}
// 选对得满分选错不扣分但按比例得分
double ratio = (double) userCorrectCount / correctCount;
return questionScore.multiply(BigDecimal.valueOf(ratio)).setScale(2, RoundingMode.HALF_UP);
}
} catch (Exception e) {
// 解析失败返回0分
return BigDecimal.ZERO;
}
return BigDecimal.ZERO;
}
/**
* 计算数字题得分
*/
private BigDecimal calculateNumberQuestionScore(QuestionDO question, AssessmentAnswerSubmitReqVO.AnswerItem answerItem) {
if (answerItem.getAnswer() == null || answerItem.getAnswer().isEmpty()) {
return BigDecimal.ZERO;
}
try {
double userValue = Double.parseDouble(answerItem.getAnswer());
Integer minValue = question.getMinValue();
Integer maxValue = question.getMaxValue();
BigDecimal questionScore = question.getScore() != null ? question.getScore() : BigDecimal.ZERO;
// 如果有范围限制按比例给分
if (minValue != null && maxValue != null && maxValue > minValue) {
if (userValue < minValue) {
return BigDecimal.ZERO;
} else if (userValue >= maxValue) {
return questionScore;
} else {
double ratio = (userValue - minValue) / (maxValue - minValue);
return questionScore.multiply(BigDecimal.valueOf(ratio)).setScale(2, RoundingMode.HALF_UP);
}
}
// 没有范围限制给满分
return questionScore;
} catch (NumberFormatException e) {
return BigDecimal.ZERO;
}
}
/**
* 判断答案是否正确单选/多选题
*/
private Boolean isAnswerCorrect(QuestionDO question, AssessmentAnswerSubmitReqVO.AnswerItem answerItem) {
if (question.getOptions() == null || question.getOptions().isEmpty()) {
return false;
}
try {
JSONArray options = JSON.parseArray(question.getOptions());
if (question.getType() == QUESTION_TYPE_SINGLE) {
// 单选题检查是否选择了正确选项
if (answerItem.getOptionIds() == null || answerItem.getOptionIds().isEmpty()) {
return false;
}
Long selectedOptionId = answerItem.getOptionIds().get(0);
for (int i = 0; i < options.size(); i++) {
JSONObject option = options.getJSONObject(i);
if (option.getLong("id") != null && option.getLong("id").equals(selectedOptionId)) {
return option.getBoolean("isCorrect") != null && option.getBoolean("isCorrect");
}
}
return false;
} else if (question.getType() == QUESTION_TYPE_MULTI) {
// 多选题检查是否全部选择正确选项且没有选择错误选项
if (answerItem.getOptionIds() == null || answerItem.getOptionIds().isEmpty()) {
return false;
}
Set<Long> correctOptionIds = new HashSet<>();
Set<Long> userSelectedIds = new HashSet<>(answerItem.getOptionIds());
for (int i = 0; i < options.size(); i++) {
JSONObject option = options.getJSONObject(i);
if (option.getBoolean("isCorrect") != null && option.getBoolean("isCorrect")) {
correctOptionIds.add(option.getLong("id"));
} else if (option.getBoolean("isCorrect") != null && !option.getBoolean("isCorrect")
&& userSelectedIds.contains(option.getLong("id"))) {
// 选择了错误选项
return false;
}
}
// 用户必须选择所有正确选项
return correctOptionIds.equals(userSelectedIds);
}
} catch (Exception e) {
return false;
}
return false;
}
}

View File

@ -1,110 +0,0 @@
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

@ -1,91 +0,0 @@
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

@ -1,106 +0,0 @@
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

@ -1,73 +0,0 @@
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

@ -1,163 +0,0 @@
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

@ -1,133 +0,0 @@
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

@ -1,141 +0,0 @@
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

@ -1,198 +0,0 @@
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

@ -4,59 +4,68 @@ import java.util.*;
import jakarta.validation.*; import jakarta.validation.*;
import cn.iocoder.yudao.module.prison.controller.admin.consumption.vo.*; import cn.iocoder.yudao.module.prison.controller.admin.consumption.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDO; import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDO;
import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDetailDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
/** /**
* 消费记录 Service 接口 * 消费订单 Service 接口
* *
* @author 芋道源码 * @author xl
*/ */
public interface ConsumptionService { public interface ConsumptionService {
/** /**
* 创建消费记录 * 创建消费订单
* *
* @param createReqVO 创建信息 * @param createReqVO 创建信息
* @return 编号 * @return 订单编号
*/ */
Long createConsumption(@Valid ConsumptionSaveReqVO createReqVO); Long createConsumption(@Valid ConsumptionSaveReqVO createReqVO);
/** /**
* 更新消费记录 * 更新消费订单
* *
* @param updateReqVO 更新信息 * @param updateReqVO 更新信息
*/ */
void updateConsumption(@Valid ConsumptionSaveReqVO updateReqVO); void updateConsumption(@Valid ConsumptionSaveReqVO updateReqVO);
/** /**
* 删除消费记录 * 删除消费订单
* *
* @param id 编号 * @param id 订单编号
*/ */
void deleteConsumption(Long id); void deleteConsumption(Long id);
/** /**
* 批量删除消费记录 * 批量删除消费订单
* *
* @param ids 编号 * @param ids 订单编号列表
*/ */
void deleteConsumptionListByIds(List<Long> ids); void deleteConsumptionListByIds(List<Long> ids);
/** /**
* 获得消费记录 * 获得消费订单
* *
* @param id 编号 * @param id 订单编号
* @return 消费记录 * @return 消费订单
*/ */
ConsumptionDO getConsumption(Long id); ConsumptionDO getConsumption(Long id);
/** /**
* 获得消费记录分页 * 获得消费订单分页
* *
* @param pageReqVO 分页查询 * @param pageReqVO 分页查询
* @return 消费记录分页 * @return 消费订单分页
*/ */
PageResult<ConsumptionDO> getConsumptionPage(ConsumptionPageReqVO pageReqVO); PageResult<ConsumptionDO> getConsumptionPage(ConsumptionPageReqVO pageReqVO);
} /**
* 获得消费订单明细列表
*
* @param consumptionId 消费订单ID
* @return 消费明细列表
*/
List<ConsumptionDetailDO> getConsumptionDetailList(Long consumptionId);
}

View File

@ -1,29 +1,34 @@
package cn.iocoder.yudao.module.prison.service.consumption; package cn.iocoder.yudao.module.prison.service.consumption;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.*; import java.util.*;
import java.math.BigDecimal;
import cn.iocoder.yudao.module.prison.controller.admin.consumption.vo.*; import cn.iocoder.yudao.module.prison.controller.admin.consumption.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDO; import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDO;
import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDetailDO;
import cn.iocoder.yudao.module.prison.dal.mysql.consumption.ConsumptionMapper;
import cn.iocoder.yudao.module.prison.dal.mysql.consumption.ConsumptionDetailMapper;
import cn.iocoder.yudao.module.prison.convert.consumption.ConsumptionConvert;
import cn.iocoder.yudao.module.prison.convert.consumption.ConsumptionDetailConvert;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.hutool.core.util.NumberUtil;
import cn.iocoder.yudao.module.prison.dal.mysql.consumption.ConsumptionMapper;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList;
import static cn.iocoder.yudao.module.prison.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.prison.enums.ErrorCodeConstants.*;
/** /**
* 消费记录 Service 实现类 * 消费订单 Service 实现类
* *
* @author 芋道源码 * @author xl
*/ */
@Service @Service
@Validated @Validated
@ -32,39 +37,84 @@ public class ConsumptionServiceImpl implements ConsumptionService {
@Resource @Resource
private ConsumptionMapper consumptionMapper; private ConsumptionMapper consumptionMapper;
@Resource
private ConsumptionDetailMapper consumptionDetailMapper;
@Override @Override
@Transactional(rollbackFor = Exception.class)
public Long createConsumption(ConsumptionSaveReqVO createReqVO) { public Long createConsumption(ConsumptionSaveReqVO createReqVO) {
// 插入 // 1. 生成订单号
ConsumptionDO consumption = BeanUtils.toBean(createReqVO, ConsumptionDO.class); String orderNo = generateOrderNo();
// 2. 插入消费订单
ConsumptionDO consumption = ConsumptionConvert.INSTANCE.convert(createReqVO);
consumption.setOrderNo(orderNo);
consumptionMapper.insert(consumption); consumptionMapper.insert(consumption);
// 3. 插入消费明细
if (CollUtil.isNotEmpty(createReqVO.getDetails())) {
for (ConsumptionDetailSaveReqVO detailVO : createReqVO.getDetails()) {
ConsumptionDetailDO detail = ConsumptionDetailConvert.INSTANCE.convert(detailVO);
detail.setConsumptionId(consumption.getId());
detail.setPrisonerId(createReqVO.getPrisonerId());
// 计算小计金额
detail.setSubtotal(NumberUtil.mul(detail.getGoodsPrice(), detail.getGoodsCount()));
consumptionDetailMapper.insert(detail);
}
}
// 返回 // 返回
return consumption.getId(); return consumption.getId();
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void updateConsumption(ConsumptionSaveReqVO updateReqVO) { public void updateConsumption(ConsumptionSaveReqVO updateReqVO) {
// 校验存在 // 校验订单存在
validateConsumptionExists(updateReqVO.getId()); validateConsumptionExists(updateReqVO.getId());
// 更新
ConsumptionDO updateObj = BeanUtils.toBean(updateReqVO, ConsumptionDO.class); // 1. 更新消费订单
ConsumptionDO updateObj = ConsumptionConvert.INSTANCE.convert(updateReqVO);
consumptionMapper.updateById(updateObj); consumptionMapper.updateById(updateObj);
}
@Override // 2. 删除原有明细
public void deleteConsumption(Long id) { consumptionDetailMapper.deleteListByConsumptionId(updateReqVO.getId());
// 校验存在
validateConsumptionExists(id);
// 删除
consumptionMapper.deleteById(id);
}
@Override // 3. 插入新的消费明细
public void deleteConsumptionListByIds(List<Long> ids) { if (CollUtil.isNotEmpty(updateReqVO.getDetails())) {
// 删除 for (ConsumptionDetailSaveReqVO detailVO : updateReqVO.getDetails()) {
consumptionMapper.deleteByIds(ids); ConsumptionDetailDO detail = ConsumptionDetailConvert.INSTANCE.convert(detailVO);
detail.setConsumptionId(updateReqVO.getId());
detail.setPrisonerId(updateReqVO.getPrisonerId());
// 计算小计金额
detail.setSubtotal(NumberUtil.mul(detail.getGoodsPrice(), detail.getGoodsCount()));
consumptionDetailMapper.insert(detail);
}
} }
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteConsumption(Long id) {
// 校验订单存在
validateConsumptionExists(id);
// 1. 删除消费订单
consumptionMapper.deleteById(id);
// 2. 删除消费明细
consumptionDetailMapper.deleteListByConsumptionId(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteConsumptionListByIds(List<Long> ids) {
// 批量删除订单
consumptionMapper.deleteByIds(ids);
// 批量删除明细
consumptionDetailMapper.deleteListByConsumptionIds(ids);
}
private void validateConsumptionExists(Long id) { private void validateConsumptionExists(Long id) {
if (consumptionMapper.selectById(id) == null) { if (consumptionMapper.selectById(id) == null) {
@ -82,4 +132,16 @@ public class ConsumptionServiceImpl implements ConsumptionService {
return consumptionMapper.selectPage(pageReqVO); return consumptionMapper.selectPage(pageReqVO);
} }
} @Override
public List<ConsumptionDetailDO> getConsumptionDetailList(Long consumptionId) {
return consumptionDetailMapper.selectList(ConsumptionDetailDO::getConsumptionId, consumptionId);
}
/**
* 生成订单号
*/
private String generateOrderNo() {
return "CS" + System.currentTimeMillis();
}
}

View File

@ -60,10 +60,10 @@ public class QuestionnaireServiceImpl implements QuestionnaireService {
} }
@Override @Override
public void deleteQuestionnaireListByIds(List<Long> ids) { public void deleteQuestionnaireListByIds(List<Long> ids) {
// 删除 // 删除
questionnaireMapper.deleteByIds(ids); questionnaireMapper.deleteByIds(ids);
} }
private void validateQuestionnaireExists(Long id) { private void validateQuestionnaireExists(Long id) {

View File

@ -6,9 +6,11 @@ import cn.iocoder.yudao.module.prison.controller.admin.questionnairerecord.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO; import cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import java.math.BigDecimal;
import java.util.Map;
/** /**
* 问卷答题记录 Service 接口 * 问卷答题记录 / 测评记录 Service 接口
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@ -37,10 +39,10 @@ public interface QuestionnaireRecordService {
void deleteQuestionnaireRecord(Long id); void deleteQuestionnaireRecord(Long id);
/** /**
* 批量删除问卷答题记录 * 批量删除问卷答题记录
* *
* @param ids 编号 * @param ids 编号列表
*/ */
void deleteQuestionnaireRecordListByIds(List<Long> ids); void deleteQuestionnaireRecordListByIds(List<Long> ids);
/** /**
@ -59,4 +61,85 @@ public interface QuestionnaireRecordService {
*/ */
PageResult<QuestionnaireRecordDO> getQuestionnaireRecordPage(QuestionnaireRecordPageReqVO pageReqVO); PageResult<QuestionnaireRecordDO> getQuestionnaireRecordPage(QuestionnaireRecordPageReqVO pageReqVO);
} // ==================== 测评执行相关 ====================
/**
* 发起测评
*
* @param reqVO 发起信息
* @return 测评记录ID
*/
Long initiateAssessment(@Valid AssessmentInitiateReqVO reqVO);
/**
* 开始测评
*
* @param id 测评记录ID
* @param prisonerId 罪犯ID
*/
void startAssessment(Long id, Long prisonerId);
/**
* 提交答卷
*
* @param reqVO 提交信息
*/
void submitAnswer(@Valid AssessmentAnswerSubmitReqVO reqVO);
/**
* 结束测评
*
* @param id 测评记录ID
*/
void finishAssessment(Long id);
/**
* 取消测评
*
* @param id 测评记录ID
*/
void cancelAssessment(Long id);
// ==================== 评分相关 ====================
/**
* 自动评分
*
* @param id 测评记录ID
*/
void autoScore(Long id);
/**
* 人工评分
*
* @param reqVO 评分信息
*/
void manualScore(@Valid AssessmentManualScoreReqVO reqVO);
// ==================== 统计相关 ====================
/**
* 获取完成率
*
* @param assessmentRecordId 测评记录ID
* @return 完成率
*/
BigDecimal getCompletionRate(Long assessmentRecordId);
/**
* 获取分数分布
*
* @param assessmentRecordId 测评记录ID
* @return 分数分布
*/
Map<String, Integer> getScoreDistribution(Long assessmentRecordId);
/**
* 获取风险分布
*
* @param assessmentRecordId 测评记录ID
* @return 风险分布
*/
Map<String, Integer> getRiskDistribution(Long assessmentRecordId);
}

View File

@ -7,21 +7,26 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.*; import java.util.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import cn.iocoder.yudao.module.prison.controller.admin.questionnairerecord.vo.*; import cn.iocoder.yudao.module.prison.controller.admin.questionnairerecord.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO; import cn.iocoder.yudao.module.prison.dal.dataobject.questionnairerecord.QuestionnaireRecordDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.prison.dal.mysql.questionnairerecord.QuestionnaireRecordMapper; import cn.iocoder.yudao.module.prison.dal.mysql.questionnairerecord.QuestionnaireRecordMapper;
import cn.iocoder.yudao.module.prison.dal.mysql.questionnaire.QuestionnaireMapper;
import cn.iocoder.yudao.module.prison.dal.dataobject.questionnaire.QuestionnaireDO;
import cn.iocoder.yudao.module.prison.service.answer.AnswerService;
import cn.iocoder.yudao.module.prison.enums.QuestionnaireRecordPassStatusEnum;
import cn.iocoder.yudao.module.prison.enums.QuestionnaireRecordStatusEnum;
import cn.iocoder.yudao.module.prison.enums.RiskLevelEnum;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList;
import static cn.iocoder.yudao.module.prison.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.prison.enums.ErrorCodeConstants.*;
/** /**
* 问卷答题记录 Service 实现类 * 问卷答题记录 / 测评记录 Service 实现类
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@ -32,13 +37,17 @@ public class QuestionnaireRecordServiceImpl implements QuestionnaireRecordServic
@Resource @Resource
private QuestionnaireRecordMapper questionnaireRecordMapper; private QuestionnaireRecordMapper questionnaireRecordMapper;
@Resource
private QuestionnaireMapper questionnaireMapper;
@Resource
private AnswerService answerService;
@Override @Override
public Long createQuestionnaireRecord(QuestionnaireRecordSaveReqVO createReqVO) { public Long createQuestionnaireRecord(QuestionnaireRecordSaveReqVO createReqVO) {
// 插入 // 插入
QuestionnaireRecordDO questionnaireRecord = BeanUtils.toBean(createReqVO, QuestionnaireRecordDO.class); QuestionnaireRecordDO questionnaireRecord = BeanUtils.toBean(createReqVO, QuestionnaireRecordDO.class);
questionnaireRecordMapper.insert(questionnaireRecord); questionnaireRecordMapper.insert(questionnaireRecord);
// 返回
return questionnaireRecord.getId(); return questionnaireRecord.getId();
} }
@ -74,11 +83,12 @@ public class QuestionnaireRecordServiceImpl implements QuestionnaireRecordServic
questionnaireRecordMapper.deleteByIds(ids); questionnaireRecordMapper.deleteByIds(ids);
} }
private QuestionnaireRecordDO validateQuestionnaireRecordExists(Long id) {
private void validateQuestionnaireRecordExists(Long id) { QuestionnaireRecordDO record = questionnaireRecordMapper.selectById(id);
if (questionnaireRecordMapper.selectById(id) == null) { if (record == null) {
throw exception(QUESTIONNAIRE_RECORD_NOT_EXISTS); throw exception(QUESTIONNAIRE_RECORD_NOT_EXISTS);
} }
return record;
} }
@Override @Override
@ -91,4 +101,207 @@ public class QuestionnaireRecordServiceImpl implements QuestionnaireRecordServic
return questionnaireRecordMapper.selectPage(pageReqVO); return questionnaireRecordMapper.selectPage(pageReqVO);
} }
} // ==================== 测评执行相关 ====================
@Override
@Transactional(rollbackFor = Exception.class)
public Long initiateAssessment(AssessmentInitiateReqVO reqVO) {
// 获取问卷信息
QuestionnaireDO questionnaire = questionnaireMapper.selectById(reqVO.getQuestionnaireId());
if (questionnaire == null) {
throw exception(QUESTIONNAIRE_NOT_EXISTS);
}
String questionnaireName = questionnaire.getTitle();
QuestionnaireRecordDO lastRecord = null;
for (Long prisonerId : reqVO.getPrisonerIds()) {
QuestionnaireRecordDO record = new QuestionnaireRecordDO();
record.setQuestionnaireId(reqVO.getQuestionnaireId());
record.setQuestionnaireName(questionnaireName);
record.setPrisonerId(prisonerId);
record.setStatus(QuestionnaireRecordStatusEnum.PENDING.getStatus()); // 待测评
record.setDeadline(reqVO.getDeadline());
record.setRemark(reqVO.getRemark());
record.setParticipantCount(0);
record.setCompletedCount(0);
questionnaireRecordMapper.insert(record);
lastRecord = record;
}
// 返回最后创建的记录ID
return lastRecord != null ? lastRecord.getId() : null;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void startAssessment(Long id, Long prisonerId) {
QuestionnaireRecordDO record = validateQuestionnaireRecordExists(id);
if (!QuestionnaireRecordStatusEnum.PENDING.getStatus().equals(record.getStatus())) {
throw exception(QUESTIONNAIRE_RECORD_STATUS_ERROR);
}
record.setStatus(QuestionnaireRecordStatusEnum.IN_PROGRESS.getStatus()); // 测评中
record.setStartTime(LocalDateTime.now());
questionnaireRecordMapper.updateById(record);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void submitAnswer(AssessmentAnswerSubmitReqVO reqVO) {
QuestionnaireRecordDO record = validateQuestionnaireRecordExists(reqVO.getRecordId());
if (!QuestionnaireRecordStatusEnum.IN_PROGRESS.getStatus().equals(record.getStatus())) {
throw exception(QUESTIONNAIRE_RECORD_STATUS_ERROR);
}
record.setStatus(QuestionnaireRecordStatusEnum.COMPLETED.getStatus()); // 已完成
record.setEndTime(LocalDateTime.now());
if (record.getStartTime() != null) {
record.setDuration((int) java.time.Duration.between(record.getStartTime(), LocalDateTime.now()).getSeconds());
}
// 保存答题记录并计算客观题得分
answerService.saveAnswers(reqVO.getRecordId(), record.getQuestionnaireId(), reqVO.getPrisonerId(), reqVO.getAnswers());
// 计算客观题得分
BigDecimal objectiveScore = answerService.calculateObjectiveScore(reqVO.getRecordId());
record.setObjectiveScore(objectiveScore);
record.setPassStatus(QuestionnaireRecordPassStatusEnum.PENDING.getStatus()); // 待评阅
questionnaireRecordMapper.updateById(record);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void finishAssessment(Long id) {
QuestionnaireRecordDO record = validateQuestionnaireRecordExists(id);
if (!QuestionnaireRecordStatusEnum.canTransition(record.getStatus())) {
throw exception(QUESTIONNAIRE_RECORD_STATUS_ERROR);
}
record.setStatus(QuestionnaireRecordStatusEnum.EXPIRED.getStatus()); // 已过期
questionnaireRecordMapper.updateById(record);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelAssessment(Long id) {
QuestionnaireRecordDO record = validateQuestionnaireRecordExists(id);
if (!QuestionnaireRecordStatusEnum.PENDING.getStatus().equals(record.getStatus())) {
throw exception(QUESTIONNAIRE_RECORD_STATUS_ERROR);
}
record.setStatus(QuestionnaireRecordStatusEnum.CANCELLED.getStatus()); // 已取消
questionnaireRecordMapper.updateById(record);
}
// ==================== 评分相关 ====================
@Override
@Transactional(rollbackFor = Exception.class)
public void autoScore(Long id) {
QuestionnaireRecordDO record = validateQuestionnaireRecordExists(id);
// 重新计算客观题得分覆盖之前的分数
BigDecimal objectiveScore = answerService.calculateObjectiveScore(id);
record.setObjectiveScore(objectiveScore);
// 如果有待主观题需要人工评阅保持待评阅状态
// 如果全部是客观题直接设置及格/不及格状态
record.setPassStatus(objectiveScore.compareTo(record.getPassScore()) >= 0
? QuestionnaireRecordPassStatusEnum.PASSED.getStatus()
: QuestionnaireRecordPassStatusEnum.FAILED.getStatus());
questionnaireRecordMapper.updateById(record);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void manualScore(AssessmentManualScoreReqVO reqVO) {
QuestionnaireRecordDO record = validateQuestionnaireRecordExists(reqVO.getRecordId());
// 计算总分
BigDecimal objectiveScore = record.getObjectiveScore() != null ? record.getObjectiveScore() : BigDecimal.ZERO;
BigDecimal subjectiveScore = reqVO.getSubjectiveScore() != null ? reqVO.getSubjectiveScore() : BigDecimal.ZERO;
BigDecimal totalScore = objectiveScore.add(subjectiveScore);
record.setSubjectiveScore(reqVO.getSubjectiveScore());
record.setTotalScore(totalScore);
// 及格/不及格判断
BigDecimal passScore = record.getPassScore() != null ? record.getPassScore() : BigDecimal.ZERO;
record.setPassStatus(totalScore.compareTo(passScore) >= 0
? QuestionnaireRecordPassStatusEnum.PASSED.getStatus()
: QuestionnaireRecordPassStatusEnum.FAILED.getStatus());
record.setRiskLevel(reqVO.getRiskLevel());
questionnaireRecordMapper.updateById(record);
}
// ==================== 统计相关 ====================
@Override
public BigDecimal getCompletionRate(Long assessmentRecordId) {
QuestionnaireRecordDO record = validateQuestionnaireRecordExists(assessmentRecordId);
if (record.getParticipantCount() == null || record.getParticipantCount() == 0) {
return BigDecimal.ZERO;
}
return BigDecimal.valueOf(record.getCompletedCount())
.divide(BigDecimal.valueOf(record.getParticipantCount()), 4, java.math.RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
}
@Override
public Map<String, Integer> getScoreDistribution(Long assessmentRecordId) {
Map<String, Integer> distribution = new HashMap<>();
distribution.put("0-20", 0);
distribution.put("21-40", 0);
distribution.put("41-60", 0);
distribution.put("61-80", 0);
distribution.put("81-100", 0);
// 先获取记录获取 questionnaireId
QuestionnaireRecordDO record = questionnaireRecordMapper.selectById(assessmentRecordId);
if (record == null) {
return distribution;
}
// 查询该测评下所有已完成记录统计分数分布
List<QuestionnaireRecordDO> records = questionnaireRecordMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<QuestionnaireRecordDO>()
.eq(QuestionnaireRecordDO::getQuestionnaireId, record.getQuestionnaireId())
.eq(QuestionnaireRecordDO::getStatus, QuestionnaireRecordStatusEnum.COMPLETED.getStatus()));
for (QuestionnaireRecordDO r : records) {
if (r.getTotalScore() == null) continue;
int score = r.getTotalScore().intValue();
if (score <= 20) distribution.put("0-20", distribution.get("0-20") + 1);
else if (score <= 40) distribution.put("21-40", distribution.get("21-40") + 1);
else if (score <= 60) distribution.put("41-60", distribution.get("41-60") + 1);
else if (score <= 80) distribution.put("61-80", distribution.get("61-80") + 1);
else distribution.put("81-100", distribution.get("81-100") + 1);
}
return distribution;
}
@Override
public Map<String, Integer> getRiskDistribution(Long assessmentRecordId) {
Map<String, Integer> distribution = new HashMap<>();
distribution.put("high", 0);
distribution.put("medium", 0);
distribution.put("low", 0);
// 先获取记录获取 questionnaireId
QuestionnaireRecordDO record = questionnaireRecordMapper.selectById(assessmentRecordId);
if (record == null) {
return distribution;
}
// 查询该测评下所有记录统计风险分布
List<QuestionnaireRecordDO> records = questionnaireRecordMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<QuestionnaireRecordDO>()
.eq(QuestionnaireRecordDO::getQuestionnaireId, record.getQuestionnaireId()));
for (QuestionnaireRecordDO r : records) {
if (r.getRiskLevel() == null) continue;
if (RiskLevelEnum.HIGH.getValue().equals(r.getRiskLevel())) {
distribution.put("high", distribution.get("high") + 1);
} else if (RiskLevelEnum.MEDIUM.getValue().equals(r.getRiskLevel())) {
distribution.put("medium", distribution.get("medium") + 1);
} else if (RiskLevelEnum.LOW.getValue().equals(r.getRiskLevel())) {
distribution.put("low", distribution.get("low") + 1);
}
}
return distribution;
}
}

View File

@ -1,374 +0,0 @@
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

@ -1,246 +0,0 @@
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

@ -1,108 +0,0 @@
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

@ -1,239 +0,0 @@
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

@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.prison.service.riskassessment.llm; package cn.iocoder.yudao.module.prison.service.riskassessment.llm;
import java.util.Map; import java.util.List;
/** /**
* LLM客户端接口 * LLM客户端接口
@ -9,81 +9,108 @@ import java.util.Map;
*/ */
public interface LlmClient { 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(); String getName();
/** /**
* 调用选项 * 检查客户端是否可用
*/ */
record LlmOptions( boolean isAvailable();
String model, // 模型名称
double temperature, // 温度参数 (0.0-1.0) /**
int maxTokens, // 最大Token数 * 简单文本补全
Map<String, String> headers // 额外请求头 *
) { * @param prompt 提示词
* @param options 选项
* @return 生成的文本
*/
String complete(String prompt, LlmOptions options);
/**
* JSON格式文本补全
*
* @param prompt 提示词
* @param options 选项
* @return 生成的JSON文本
*/
String completeJson(String prompt, LlmOptions options);
/**
* LLM选项配置
*/
class LlmOptions {
/**
* 最大token数
*/
private int maxTokens = 4096;
/**
* 温度参数 (0-2)
*/
private float temperature = 0.7f;
/**
* 系统提示词
*/
private String systemPrompt;
/**
* 是否需要JSON格式响应
*/
private boolean jsonMode = false;
// Getters and Setters
public int getMaxTokens() {
return maxTokens;
}
public void setMaxTokens(int maxTokens) {
this.maxTokens = maxTokens;
}
public float getTemperature() {
return temperature;
}
public void setTemperature(float temperature) {
this.temperature = temperature;
}
public String getSystemPrompt() {
return systemPrompt;
}
public void setSystemPrompt(String systemPrompt) {
this.systemPrompt = systemPrompt;
}
public boolean isJsonMode() {
return jsonMode;
}
public void setJsonMode(boolean jsonMode) {
this.jsonMode = jsonMode;
}
/** /**
* 创建默认选项 * 创建默认选项
*/ */
public static LlmOptions defaultOptions() { public static LlmOptions defaultOptions() {
return new LlmOptions( return new LlmOptions();
"claude-3-5-sonnet-20241022",
0.1, // 低温度输出更稳定
2000,
Map.of()
);
} }
/** /**
* 创建低温度选项用于评估场景 * 创建评估专用选项
*/ */
public static LlmOptions assessmentOptions() { public static LlmOptions assessmentOptions() {
return new LlmOptions( LlmOptions options = new LlmOptions();
"claude-3-5-sonnet-20241022", options.setMaxTokens(2048);
0.05, // 极低温度减少随机性 options.setTemperature(0.3f); // 评估场景使用较低温度减少随机性
2048, options.setJsonMode(true);
Map.of() return options;
);
}
/**
* 创建高温度选项用于创意场景
*/
public static LlmOptions creativeOptions() {
return new LlmOptions(
"claude-3-5-sonnet-20241022",
0.7,
4096,
Map.of()
);
} }
} }
} }

View File

@ -1,106 +1,48 @@
package cn.iocoder.yudao.module.prison.service.riskassessment.llm; package cn.iocoder.yudao.module.prison.service.riskassessment.llm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/** /**
* LLM客户端工厂 * LLM客户端工厂
* 根据配置选择不同的LLM客户端 * 根据配置返回对应的LLM客户端
* *
* @author XLLC Team * @author XLLC Team
*/ */
@Slf4j
@Component @Component
public class LlmClientFactory { public class LlmClientFactory {
private final Map<String, LlmClient> clients; private final OpenAiCompatibleClient openAiCompatibleClient;
public LlmClientFactory(List<LlmClient> clientList) { public LlmClientFactory(OpenAiCompatibleClient openAiCompatibleClient) {
// 初始化客户端映射 this.openAiCompatibleClient = openAiCompatibleClient;
this.clients = new java.util.HashMap<>(); }
for (LlmClient client : clientList) {
clients.put(client.getName().toLowerCase(), client); /**
log.info("注册LLM客户端: {}", client.getName()); * 获取评估专用的LLM客户端
} */
public LlmClient getAssessmentClient() {
// 当前只支持OpenAI兼容客户端
return openAiCompatibleClient;
} }
/** /**
* 获取指定名称的客户端 * 获取指定名称的客户端
*
* @param name 客户端名称
* @return LLM客户端
*/ */
public LlmClient getClient(String name) { public LlmClient getClient(String clientName) {
LlmClient client = clients.get(name.toLowerCase()); if (clientName == null) {
if (client == null) { return getAssessmentClient();
throw new LlmException("UNKNOWN_CLIENT", "未找到LLM客户端: " + name);
} }
return client;
return switch (clientName.toLowerCase()) {
case "openai", "oneapi", "openai-compatible" -> openAiCompatibleClient;
default -> getAssessmentClient();
};
} }
/** /**
* 获取默认客户端 * 检查是否有可用的客户端
*
* @return 默认LLM客户端
*/ */
public LlmClient getDefaultClient() { public boolean hasAvailableClient() {
// 优先使用Claude return openAiCompatibleClient.isAvailable();
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

@ -1,35 +1,17 @@
package cn.iocoder.yudao.module.prison.service.riskassessment.llm; package cn.iocoder.yudao.module.prison.service.riskassessment.llm;
/** /**
* LLM调用异常 * LLM操作异常
* *
* @author XLLC Team * @author XLLC Team
*/ */
public class LlmException extends RuntimeException { public class LlmException extends RuntimeException {
private final String errorCode;
public LlmException(String message) { public LlmException(String message) {
super(message); super(message);
this.errorCode = "LLM_ERROR";
} }
public LlmException(String message, Throwable cause) { public LlmException(String message, Throwable cause) {
super(message, 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,173 @@
package cn.iocoder.yudao.module.prison.service.riskassessment.llm;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* OpenAI兼容客户端
* 支持OneAPI等统一接口服务
*
* @author XLLC Team
*/
@Slf4j
@Component
public class OpenAiCompatibleClient implements LlmClient {
private static final String DEFAULT_BASE_URL = "https://oneapi.gongjulian.cn/v1";
private static final String DEFAULT_MODEL = "deepseek-ai/deepseek-v3.2";
@Value("${llm.local.base-url:}")
private String baseUrl;
@Value("${llm.local.api-key:}")
private String apiKey;
@Value("${llm.local.model:}")
private String model;
@Value("${llm.local.timeout-seconds:120}")
private int timeoutSeconds;
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
public OpenAiCompatibleClient() {
this.restTemplate = new RestTemplate();
this.objectMapper = new ObjectMapper();
}
@Override
public String getName() {
return "OpenAI Compatible (OneAPI)";
}
@Override
public boolean isAvailable() {
try {
// 尝试获取模型列表来检查连接
String url = getBaseUrl() + "/models";
HttpHeaders headers = createHeaders();
HttpEntity<String> entity = new HttpEntity<>(headers);
restTemplate.exchange(url, HttpMethod.GET, entity, String.class,
Duration.ofSeconds(5));
return true;
} catch (Exception e) {
log.warn("LLM客户端不可用: {}", e.getMessage());
return false;
}
}
@Override
public String complete(String prompt, LlmOptions options) {
String url = getBaseUrl() + "/chat/completions";
HttpHeaders headers = createHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// 构建请求体
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", getModel());
// 消息
List<Map<String, String>> messages = new ArrayList<>();
if (options.getSystemPrompt() != null && !options.getSystemPrompt().isEmpty()) {
Map<String, String> systemMessage = new HashMap<>();
systemMessage.put("role", "system");
systemMessage.put("content", options.getSystemPrompt());
messages.add(systemMessage);
}
Map<String, String> userMessage = new HashMap<>();
userMessage.put("role", "user");
userMessage.put("content", prompt);
messages.add(userMessage);
requestBody.put("messages", messages);
// 参数
if (options.getMaxTokens() > 0) {
requestBody.put("max_tokens", options.getMaxTokens());
}
if (options.getTemperature() >= 0 && options.getTemperature() <= 2) {
requestBody.put("temperature", options.getTemperature());
}
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
try {
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.POST, entity, String.class,
Duration.ofSeconds(timeoutSeconds));
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
return parseContent(response.getBody());
}
throw new LlmException("LLM调用失败: " + response.getStatusCode());
} catch (RestClientException e) {
log.error("LLM调用异常", e);
throw new LlmException("LLM调用异常: " + e.getMessage());
}
}
@Override
public String completeJson(String prompt, LlmOptions options) {
// 设置JSON模式
LlmOptions jsonOptions = new LlmOptions();
jsonOptions.setMaxTokens(options.getMaxTokens());
jsonOptions.setTemperature(options.getTemperature());
jsonOptions.setSystemPrompt(options.getSystemPrompt());
jsonOptions.setJsonMode(true);
// 在提示词中强调JSON格式
String jsonPrompt = prompt + "\n\n请严格按照JSON格式回复不要包含其他内容。";
return complete(jsonPrompt, jsonOptions);
}
private String getBaseUrl() {
return baseUrl != null && !baseUrl.isEmpty() ? baseUrl : DEFAULT_BASE_URL;
}
private String getModel() {
return model != null && !model.isEmpty() ? model : DEFAULT_MODEL;
}
private HttpHeaders createHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + apiKey);
headers.set("Content-Type", MediaType.APPLICATION_JSON_VALUE);
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
return headers;
}
private String parseContent(String responseBody) {
try {
JsonNode root = objectMapper.readTree(responseBody);
JsonNode choices = root.path("choices");
if (choices.isArray() && choices.size() > 0) {
JsonNode message = choices.get(0).path("message");
if (!message.isMissingNode()) {
return message.path("content").asText();
}
}
throw new LlmException("解析LLM响应失败");
} catch (Exception e) {
log.error("解析LLM响应异常", e);
throw new LlmException("解析LLM响应异常: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,59 @@
-- =====================================================
-- XL监狱综合管理平台 - 问卷答题记录模块数据库脚本
-- 生成时间: 2026-01-15
-- =====================================================
-- =====================================================
-- 1. 问卷答题记录表 (prison_answer)
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_answer` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '答题记录ID',
`assessment_record_id` bigint NOT NULL COMMENT '测评记录ID',
`question_id` bigint NOT NULL COMMENT '问题ID',
`questionnaire_id` bigint DEFAULT NULL COMMENT '问卷ID冗余',
`prisoner_id` bigint NOT NULL COMMENT '罪犯ID',
`question_type` tinyint NOT NULL DEFAULT 1 COMMENT '问题类型1-单选 2-多选 3-填空 4-评分 5-日期 6-数字',
`answer_text` text COMMENT '答案内容(填空题、评分题等)',
`option_ids` text COMMENT '选项ID列表JSON数组如 [1,2,3]',
`score` decimal(10,2) DEFAULT NULL COMMENT '得分',
`is_correct` tinyint(1) DEFAULT NULL COMMENT '是否正确null-未评分 false-错误 true-正确',
`duration` int 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_prison_answer_assessment_record_id` (`assessment_record_id`),
KEY `idx_prison_answer_question_id` (`question_id`),
KEY `idx_prison_answer_prisoner_id` (`prisoner_id`),
KEY `idx_prison_answer_questionnaire_id` (`questionnaire_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='问卷答题记录表';
-- =====================================================
-- 2. 序列 (适用于 PostgreSQL、Oracle、DB2 等数据库)
-- =====================================================
-- MySQL 不需要此序列,使用 AUTO_INCREMENT
-- PostgreSQL: CREATE SEQUENCE prison_answer_seq;
-- Oracle: CREATE SEQUENCE prison_answer_seq MINVALUE 1 START WITH 1 INCREMENT BY 1 CACHE 20;
-- =====================================================
-- 3. 菜单权限 SQL
-- =====================================================
-- 注意: 执行前请确保问卷管理菜单 (id=5047) 已存在
-- 8. 答卷管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status, component_name)
VALUES ('答卷管理', '', 2, 8, 5047, 'answer', '', 'prison/answer/index', 0, 'Answer');
SELECT @answerParentId := LAST_INSERT_ID();
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('答卷查询', 'prison:answer:query', 3, 1, @answerParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('答卷创建', 'prison:answer:create', 3, 2, @answerParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('答卷更新', 'prison:answer:update', 3, 3, @answerParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('答卷删除', 'prison:answer:delete', 3, 4, @answerParentId, '', '', '', 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('答卷导出', 'prison:answer:export', 3, 5, @answerParentId, '', '', '', 0);

View File

@ -0,0 +1,250 @@
-- =====================================================
-- XL监狱综合管理平台 - 测评执行模块数据库迁移脚本
-- 生成时间: 2026-01-15
-- 说明: 将 assessment 模块功能整合到 questionnairerecord 模块
-- 兼容 MySQL 8.0
-- =====================================================
-- =====================================================
-- 1. 更新 prison_questionnaire_record 表结构
-- 添加测评执行所需字段
-- =====================================================
-- 添加问卷名称字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'questionnaire_name') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `questionnaire_name` varchar(200) DEFAULT NULL COMMENT '问卷名称' AFTER `questionnaire_id`;
END IF;
-- 添加罪犯姓名字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'prisoner_name') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `prisoner_name` varchar(100) DEFAULT NULL COMMENT '罪犯姓名' AFTER `prisoner_no`;
END IF;
-- 添加开始时间字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'start_time') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `start_time` datetime DEFAULT NULL COMMENT '开始时间';
END IF;
-- 添加结束时间字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'end_time') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `end_time` datetime DEFAULT NULL COMMENT '结束时间';
END IF;
-- 添加截止日期字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'deadline') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `deadline` datetime DEFAULT NULL COMMENT '截止日期';
END IF;
-- 添加客观题得分字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'objective_score') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `objective_score` decimal(10,2) DEFAULT 0.00 COMMENT '客观题得分';
END IF;
-- 添加主观题得分字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'subjective_score') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `subjective_score` decimal(10,2) DEFAULT 0.00 COMMENT '主观题得分';
END IF;
-- 添加总分字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'total_score') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `total_score` decimal(10,2) DEFAULT NULL COMMENT '总分';
END IF;
-- 添加及格分数字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'pass_score') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `pass_score` decimal(10,2) DEFAULT NULL COMMENT '及格分数';
END IF;
-- 添加及格状态字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'pass_status') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `pass_status` tinyint DEFAULT NULL COMMENT '及格状态1-及格 2-不及格 3-待评阅';
END IF;
-- 添加风险等级字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'risk_level') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `risk_level` tinyint DEFAULT NULL COMMENT '风险等级1-高风险 2-中风险 3-低风险';
END IF;
-- 添加评阅人ID字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'evaluator_id') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `evaluator_id` bigint DEFAULT NULL COMMENT '评阅人ID';
END IF;
-- 添加评阅人姓名字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'evaluator_name') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `evaluator_name` varchar(100) DEFAULT NULL COMMENT '评阅人姓名';
END IF;
-- 添加评阅时间字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'evaluate_time') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `evaluate_time` datetime DEFAULT NULL COMMENT '评阅时间';
END IF;
-- 添加参与人数字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'participant_count') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `participant_count` int DEFAULT 0 COMMENT '参与人数';
END IF;
-- 添加完成人数字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'completed_count') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `completed_count` int DEFAULT 0 COMMENT '完成人数';
END IF;
-- 添加答题用时字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'duration') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `duration` int DEFAULT NULL COMMENT '答题用时(秒)';
END IF;
-- 添加备注字段
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prison_questionnaire_record'
AND COLUMN_NAME = 'remark') THEN
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `remark` varchar(500) DEFAULT NULL COMMENT '备注';
END IF;
-- =====================================================
-- 2. 创建答卷详情表 (prison_questionnaire_answer)
-- 用于记录每道题的答题详情
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_questionnaire_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_questionnaire_answer_record_id` (`record_id`),
KEY `idx_questionnaire_answer_question_id` (`question_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='问卷答题详情表';
-- =====================================================
-- 3. 创建测评统计表 (prison_questionnaire_statistics)
-- 用于统计测评的整体情况
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_questionnaire_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_questionnaire_statistics_questionnaire_id` (`questionnaire_id`),
KEY `idx_questionnaire_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='问卷测评统计表';
-- =====================================================
-- 4. 更新菜单权限
-- =====================================================
-- 获取问卷记录父菜单ID
-- SELECT @parentId := id FROM system_menu WHERE name = '问卷记录管理' LIMIT 1;
-- 添加测评相关权限
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
SELECT '发起测评', 'prison:questionnaire-record:initiate', 3, 6, parent_id, '', '', '', 0
FROM (SELECT id as parent_id FROM system_menu WHERE name = '问卷记录管理' LIMIT 1) t
WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE name = '发起测评' AND parent_id = (SELECT id FROM system_menu WHERE name = '问卷记录管理' LIMIT 1));
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
SELECT '开始测评', 'prison:questionnaire-record:start', 3, 7, parent_id, '', '', '', 0
FROM (SELECT id as parent_id FROM system_menu WHERE name = '问卷记录管理' LIMIT 1) t
WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE name = '开始测评' AND parent_id = (SELECT id FROM system_menu WHERE name = '问卷记录管理' LIMIT 1));
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
SELECT '提交答卷', 'prison:questionnaire-record:submit', 3, 8, parent_id, '', '', '', 0
FROM (SELECT id as parent_id FROM system_menu WHERE name = '问卷记录管理' LIMIT 1) t
WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE name = '提交答卷' AND parent_id = (SELECT id FROM system_menu WHERE name = '问卷记录管理' LIMIT 1));
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
SELECT '结束测评', 'prison:questionnaire-record:finish', 3, 9, parent_id, '', '', '', 0
FROM (SELECT id as parent_id FROM system_menu WHERE name = '问卷记录管理' LIMIT 1) t
WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE name = '结束测评' AND parent_id = (SELECT id FROM system_menu WHERE name = '问卷记录管理' LIMIT 1));
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
SELECT '取消测评', 'prison:questionnaire-record:cancel', 3, 10, parent_id, '', '', '', 0
FROM (SELECT id as parent_id FROM system_menu WHERE name = '问卷记录管理' LIMIT 1) t
WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE name = '取消测评' AND parent_id = (SELECT id FROM system_menu WHERE name = '问卷记录管理' LIMIT 1));
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
SELECT '人工评分', 'prison:questionnaire-record:score', 3, 11, parent_id, '', '', '', 0
FROM (SELECT id as parent_id FROM system_menu WHERE name = '问卷记录管理' LIMIT 1) t
WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE name = '人工评分' AND parent_id = (SELECT id FROM system_menu WHERE name = '问卷记录管理' LIMIT 1));
SELECT '数据库迁移脚本执行完成!';

View File

@ -0,0 +1,171 @@
-- =====================================================
-- XL监狱综合管理平台 - 测评执行模块数据库迁移脚本
-- 简化版(直接执行,无需 IF 判断)
-- =====================================================
-- =====================================================
-- 1. 更新 prison_questionnaire_record 表结构
-- 添加测评执行所需字段
-- =====================================================
-- 添加问卷名称字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `questionnaire_name` varchar(200) DEFAULT NULL COMMENT '问卷名称' AFTER `questionnaire_id`;
-- 添加罪犯姓名字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `prisoner_name` varchar(100) DEFAULT NULL COMMENT '罪犯姓名' AFTER `prisoner_no`;
-- 添加开始时间字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `start_time` datetime DEFAULT NULL COMMENT '开始时间';
-- 添加结束时间字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `end_time` datetime DEFAULT NULL COMMENT '结束时间';
-- 添加截止日期字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `deadline` datetime DEFAULT NULL COMMENT '截止日期';
-- 添加客观题得分字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `objective_score` decimal(10,2) DEFAULT 0.00 COMMENT '客观题得分';
-- 添加主观题得分字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `subjective_score` decimal(10,2) DEFAULT 0.00 COMMENT '主观题得分';
-- 添加总分字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `total_score` decimal(10,2) DEFAULT NULL COMMENT '总分';
-- 添加及格分数字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `pass_score` decimal(10,2) DEFAULT NULL COMMENT '及格分数';
-- 添加及格状态字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `pass_status` tinyint DEFAULT NULL COMMENT '及格状态1-及格 2-不及格 3-待评阅';
-- 添加风险等级字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `risk_level` tinyint DEFAULT NULL COMMENT '风险等级1-高风险 2-中风险 3-低风险';
-- 添加评阅人ID字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `evaluator_id` bigint DEFAULT NULL COMMENT '评阅人ID';
-- 添加评阅人姓名字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `evaluator_name` varchar(100) DEFAULT NULL COMMENT '评阅人姓名';
-- 添加评阅时间字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `evaluate_time` datetime DEFAULT NULL COMMENT '评阅时间';
-- 添加参与人数字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `participant_count` int DEFAULT 0 COMMENT '参与人数';
-- 添加完成人数字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `completed_count` int DEFAULT 0 COMMENT '完成人数';
-- 添加答题用时字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `duration` int DEFAULT NULL COMMENT '答题用时(秒)';
-- 添加备注字段
ALTER TABLE `prison_questionnaire_record`
ADD COLUMN `remark` varchar(500) DEFAULT NULL COMMENT '备注';
-- =====================================================
-- 2. 创建答卷详情表 (prison_questionnaire_answer)
-- =====================================================
CREATE TABLE `prison_questionnaire_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_questionnaire_answer_record_id` (`record_id`),
KEY `idx_questionnaire_answer_question_id` (`question_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='问卷答题详情表';
-- =====================================================
-- 3. 创建测评统计表 (prison_questionnaire_statistics)
-- =====================================================
CREATE TABLE `prison_questionnaire_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_questionnaire_statistics_questionnaire_id` (`questionnaire_id`),
KEY `idx_questionnaire_statistics_stat_date` (`stat_date`),
UNIQUE KEY `uk_questionnaire_date` (`questionnaire_id`, `stat_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='问卷测评统计表';
-- =====================================================
-- 4. 添加菜单权限
-- =====================================================
-- 发起测评
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
SELECT '发起测评', 'prison:questionnaire-record:initiate', 3, 6, id, '', '', '', 0
FROM system_menu WHERE name = '问卷记录管理' LIMIT 1;
-- 开始测评
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
SELECT '开始测评', 'prison:questionnaire-record:start', 3, 7, id, '', '', '', 0
FROM system_menu WHERE name = '问卷记录管理' LIMIT 1;
-- 提交答卷
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
SELECT '提交答卷', 'prison:questionnaire-record:submit', 3, 8, id, '', '', '', 0
FROM system_menu WHERE name = '问卷记录管理' LIMIT 1;
-- 结束测评
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
SELECT '结束测评', 'prison:questionnaire-record:finish', 3, 9, id, '', '', '', 0
FROM system_menu WHERE name = '问卷记录管理' LIMIT 1;
-- 取消测评
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
SELECT '取消测评', 'prison:questionnaire-record:cancel', 3, 10, id, '', '', '', 0
FROM system_menu WHERE name = '问卷记录管理' LIMIT 1;
-- 人工评分
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status)
SELECT '人工评分', 'prison:questionnaire-record:score', 3, 11, id, '', '', '', 0
FROM system_menu WHERE name = '问卷记录管理' LIMIT 1;
SELECT '数据库迁移完成!';

View File

@ -0,0 +1,71 @@
-- =====================================================
-- 消费模块重构:主从表设计迁移脚本
-- 执行时间2026-01-15
-- 数据库xlcp_dev
-- =====================================================
-- 注意:执行前请备份数据库!
-- =====================================================
-- 1. 修改消费订单表结构prison_consumption
-- =====================================================
-- 1.1 添加 order_no 字段
-- 如果报错"Duplicate column",说明字段已存在,可以跳过
ALTER TABLE `prison_consumption`
ADD COLUMN `order_no` varchar(64) DEFAULT NULL COMMENT '订单号' AFTER `prisoner_no`;
-- 1.2 修改 type 字段注释
ALTER TABLE `prison_consumption`
MODIFY COLUMN `type` tinyint NOT NULL DEFAULT 1 COMMENT '类型1-购物 2-餐饮 3-医疗 4-通讯 5-其他';
-- 1.3 重命名 amount 为 total_amount
-- 如果报错"Unknown column",说明字段已被修改,可以跳过
ALTER TABLE `prison_consumption`
CHANGE COLUMN `amount` `total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额';
-- 1.4 删除商品相关字段
-- 如果报错"Unknown column",说明字段已被删除,可以跳过
ALTER TABLE `prison_consumption`
DROP COLUMN `goods_name`;
ALTER TABLE `prison_consumption`
DROP COLUMN `goods_count`;
-- 1.5 添加索引
ALTER TABLE `prison_consumption`
ADD INDEX `idx_prison_consumption_order_no` (`order_no`);
-- =====================================================
-- 2. 创建消费明细表prison_consumption_detail
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_consumption_detail` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '明细ID',
`consumption_id` bigint NOT NULL COMMENT '消费订单ID',
`prisoner_id` bigint NOT NULL COMMENT '罪犯ID冗余便于查询',
`goods_name` varchar(100) NOT NULL COMMENT '商品名称',
`goods_code` varchar(50) DEFAULT NULL COMMENT '商品编码',
`goods_price` decimal(10,2) NOT NULL COMMENT '商品单价',
`goods_count` int NOT NULL COMMENT '商品数量',
`subtotal` decimal(10,2) NOT NULL COMMENT '小计金额',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`),
KEY `idx_consumption_detail_consumption_id` (`consumption_id`),
KEY `idx_consumption_detail_prisoner_id` (`prisoner_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='消费明细表';
-- =====================================================
-- 3. 验证结果
-- =====================================================
-- 查看表结构
DESCRIBE prison_consumption;
DESCRIBE prison_consumption_detail;
SELECT '消费模块表结构修改完成!' AS status;

View File

@ -55,18 +55,16 @@ CREATE TABLE IF NOT EXISTS `prison_cell` (
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='监室信息表'; ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='监室信息表';
-- ===================================================== -- =====================================================
-- 3. 消费记录表 (prison_consumption) -- 3. 消费订单表 (prison_consumption)
-- ===================================================== -- =====================================================
CREATE TABLE IF NOT EXISTS `prison_consumption` ( CREATE TABLE IF NOT EXISTS `prison_consumption` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '记录ID', `id` bigint NOT NULL AUTO_INCREMENT COMMENT '消费ID',
`prisoner_id` bigint NOT NULL COMMENT '罪犯ID', `prisoner_id` bigint NOT NULL COMMENT '罪犯ID',
`prisoner_no` varchar(50) NOT NULL COMMENT '罪犯编号', `prisoner_no` varchar(50) NOT NULL COMMENT '罪犯编号',
`type` tinyint NOT NULL DEFAULT 1 COMMENT '类型1-存款 2-消费 3-转账',
`amount` decimal(10,2) NOT NULL COMMENT '金额',
`balance` decimal(10,2) NOT NULL COMMENT '账户余额',
`goods_name` varchar(200) DEFAULT NULL COMMENT '商品名称',
`goods_count` int DEFAULT 0 COMMENT '商品数量',
`order_no` varchar(64) DEFAULT NULL COMMENT '订单号', `order_no` varchar(64) DEFAULT NULL COMMENT '订单号',
`type` tinyint NOT NULL DEFAULT 1 COMMENT '类型1-购物 2-餐饮 3-医疗 4-通讯 5-其他',
`total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额',
`balance` decimal(10,2) NOT NULL COMMENT '账户余额(消费后)',
`trade_time` datetime NOT NULL COMMENT '交易时间', `trade_time` datetime NOT NULL COMMENT '交易时间',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-成功 2-失败', `status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-成功 2-失败',
`remark` varchar(500) DEFAULT NULL COMMENT '备注', `remark` varchar(500) DEFAULT NULL COMMENT '备注',
@ -79,8 +77,30 @@ CREATE TABLE IF NOT EXISTS `prison_consumption` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `idx_prison_consumption_prisoner_id` (`prisoner_id`), KEY `idx_prison_consumption_prisoner_id` (`prisoner_id`),
KEY `idx_prison_consumption_prisoner_no` (`prisoner_no`), KEY `idx_prison_consumption_prisoner_no` (`prisoner_no`),
KEY `idx_prison_consumption_order_no` (`order_no`),
KEY `idx_prison_consumption_trade_time` (`trade_time`) KEY `idx_prison_consumption_trade_time` (`trade_time`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='消费记录表'; ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='消费订单表';
-- =====================================================
-- 3.1 消费明细表 (prison_consumption_detail)
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_consumption_detail` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '明细ID',
`consumption_id` bigint NOT NULL COMMENT '消费订单ID',
`prisoner_id` bigint NOT NULL COMMENT '罪犯ID冗余便于查询',
`goods_name` varchar(100) NOT NULL COMMENT '商品名称',
`goods_code` varchar(50) DEFAULT NULL COMMENT '商品编码',
`goods_price` decimal(10,2) NOT NULL COMMENT '商品单价',
`goods_count` int NOT NULL COMMENT '商品数量',
`subtotal` decimal(10,2) NOT NULL COMMENT '小计金额',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT 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_consumption_detail_consumption_id` (`consumption_id`),
KEY `idx_consumption_detail_prisoner_id` (`prisoner_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='消费明细表';
-- ===================================================== -- =====================================================
-- 4. 问卷模板表 (prison_questionnaire) -- 4. 问卷模板表 (prison_questionnaire)

View File

@ -0,0 +1,70 @@
-- =====================================================
-- XL监狱综合管理平台 - LLM调用日志表
-- 生成时间: 2026-01-15
-- =====================================================
-- =====================================================
-- LLM调用日志表 (prison_risk_assessment_llm_log)
-- =====================================================
CREATE TABLE IF NOT EXISTS `prison_risk_assessment_llm_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志ID',
`assessment_id` bigint DEFAULT NULL COMMENT '关联的评估记录ID',
`prisoner_id` bigint NOT NULL COMMENT '罪犯ID',
`prisoner_no` varchar(50) NOT NULL COMMENT '罪犯编号',
`model` varchar(100) NOT NULL COMMENT '使用的模型',
`prompt` text NOT NULL COMMENT '发送的提示词(脱敏后)',
`response` text NOT NULL COMMENT '模型响应原始内容',
`risk_level` tinyint DEFAULT NULL COMMENT '评估风险等级',
`confidence` decimal(4,2) DEFAULT NULL COMMENT '置信度',
`evaluation_time_ms` int DEFAULT NULL COMMENT '评估耗时(毫秒)',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态1-成功 2-失败 3-降级',
`error_message` text DEFAULT NULL COMMENT '错误信息',
`requires_human_review` bit(1) DEFAULT b'0 COMMENT '',
`assessor_id` bigint DEFAULT NULL COMMENT '评估人ID',
`assessor_name` varchar(100) 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_llm_log_prisoner_id` (`prisoner_id`),
KEY `idx_llm_log_assessment_id` (`assessment_id`),
KEY `idx_llm_log_create_time` (`create_time`),
KEY `idx_llm_log_status` (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='LLM危险评估调用日志表';
-- =====================================================
-- 菜单权限配置
-- =====================================================
-- 危险评估模块菜单(已有)
-- LLM评估功能权限
-- 提示:以下权限需要在前端菜单配置中手动添加
-- 路径:系统管理 -> 菜单管理 -> 监狱管理 -> 危险评估
-- 添加子菜单LLM智能评估
-- 权限标识prison:risk-assessment:llm-assess
-- SQL示例添加权限如果使用代码生成器则自动生成
-- INSERT INTO system_permission (name, permission, type, sort, path, icon, component)
-- VALUES ('LLM智能评估', 'prison:risk-assessment:llm-assess', 2, 10, 'llm-assess', 'icon', 'prison/riskassessment/llm-assess');
-- INSERT INTO system_menu_permission (menu_id, permission_id)
-- SELECT m.id, p.id FROM system_menu m, system_permission p
-- WHERE m.path = 'riskassessment' AND p.permission = 'prison:risk-assessment:llm-assess';
-- =====================================================
-- 配置项说明
-- =====================================================
-- 在 application.yaml 或环境配置文件中添加LLM配置
--
-- # Claude API配置
-- llm:
-- claude:
-- api-key: your-api-key-here
-- timeout-seconds: 30
--
-- 或使用环境变量:
-- LLM_CLAUDE_API_KEY=your-api-key-here

View File

@ -0,0 +1,230 @@
package cn.iocoder.yudao.module.prison.controller.admin.consumption;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.prison.controller.admin.consumption.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDO;
import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDetailDO;
import cn.iocoder.yudao.module.prison.service.consumption.ConsumptionService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
/**
* 消费订单 Controller 测试类
*
* @author xl
*/
@WebMvcTest(PrisonConsumptionController.class)
class PrisonConsumptionControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private ConsumptionService consumptionService;
@Test
@WithMockUser
void testGetConsumptionPage_Success() throws Exception {
// 准备测试数据
ConsumptionDO consumption = new ConsumptionDO();
consumption.setId(1L);
consumption.setPrisonerId(100L);
consumption.setPrisonerNo("PRISONER001");
consumption.setOrderNo("CS1234567890");
consumption.setType(1);
consumption.setTotalAmount(new BigDecimal("100.00"));
consumption.setBalance(new BigDecimal("900.00"));
consumption.setTradeTime(LocalDateTime.now());
consumption.setStatus(1);
PageResult<ConsumptionDO> pageResult = new PageResult<>();
pageResult.setList(Collections.singletonList(consumption));
pageResult.setTotal(1L);
when(consumptionService.getConsumptionPage(any(ConsumptionPageReqVO.class))).thenReturn(pageResult);
// 执行测试
mockMvc.perform(get("/prison/consumption/page")
.param("pageNo", "1")
.param("pageSize", "10")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.list[0].id").value(1))
.andExpect(jsonPath("$.data.list[0].prisonerNo").value("PRISONER001"))
.andExpect(jsonPath("$.data.list[0].type").value(1))
.andExpect(jsonPath("$.data.total").value(1));
}
@Test
@WithMockUser
void testGetConsumption_Success() throws Exception {
// 准备测试数据
ConsumptionDO consumption = new ConsumptionDO();
consumption.setId(1L);
consumption.setPrisonerId(100L);
consumption.setPrisonerNo("PRISONER001");
consumption.setOrderNo("CS1234567890");
consumption.setType(1);
consumption.setTotalAmount(new BigDecimal("100.00"));
consumption.setStatus(1);
ConsumptionDetailDO detail = new ConsumptionDetailDO();
detail.setId(1L);
detail.setConsumptionId(1L);
detail.setGoodsName("商品A");
detail.setGoodsPrice(new BigDecimal("50.00"));
detail.setGoodsCount(2);
when(consumptionService.getConsumption(1L)).thenReturn(consumption);
when(consumptionService.getConsumptionDetailList(1L)).thenReturn(Collections.singletonList(detail));
// 执行测试
mockMvc.perform(get("/prison/consumption/get")
.param("id", "1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.id").value(1))
.andExpect(jsonPath("$.data.prisonerNo").value("PRISONER001"))
.andExpect(jsonPath("$.data.details[0].goodsName").value("商品A"));
}
@Test
@WithMockUser
void testGetConsumption_NotFound() throws Exception {
when(consumptionService.getConsumption(999L)).thenReturn(null);
mockMvc.perform(get("/prison/consumption/get")
.param("id", "999")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data").isEmpty());
}
@Test
@WithMockUser
void testDeleteConsumption_Success() throws Exception {
// 执行测试 - 删除成功
mockMvc.perform(delete("/prison/consumption/delete")
.param("id", "1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data").value(true));
}
@Test
@WithMockUser
void testDeleteConsumption_ValidationError() throws Exception {
// 缺少必需参数 id
mockMvc.perform(delete("/prison/consumption/delete")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}
@Test
@WithMockUser
void testDeleteConsumptionList_Success() throws Exception {
// 执行测试 - 批量删除
mockMvc.perform(delete("/prison/consumption/delete-list")
.param("ids", "1,2,3")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data").value(true));
}
@Test
@WithMockUser
void testDeleteConsumptionList_ValidationError() throws Exception {
// 批量删除 ids 为空
mockMvc.perform(delete("/prison/consumption/delete-list")
.param("ids", "")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}
@Test
@WithMockUser
void testGetConsumptionDetailList_Success() throws Exception {
ConsumptionDetailDO detail = new ConsumptionDetailDO();
detail.setId(1L);
detail.setConsumptionId(1L);
detail.setGoodsName("商品A");
detail.setGoodsPrice(new BigDecimal("25.50"));
detail.setGoodsCount(4);
when(consumptionService.getConsumptionDetailList(1L))
.thenReturn(Collections.singletonList(detail));
mockMvc.perform(get("/prison/consumption/detail-list")
.param("consumptionId", "1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data[0].goodsName").value("商品A"))
.andExpect(jsonPath("$.data[0].goodsPrice").value(25.5));
}
@Test
@WithMockUser
void testExportConsumptionExcel_Success() throws Exception {
ConsumptionDO consumption = new ConsumptionDO();
consumption.setId(1L);
consumption.setPrisonerNo("PRISONER001");
consumption.setType(1);
consumption.setTotalAmount(new BigDecimal("100.00"));
PageResult<ConsumptionDO> pageResult = new PageResult<>();
pageResult.setList(Collections.singletonList(consumption));
pageResult.setTotal(1L);
when(consumptionService.getConsumptionPage(any(ConsumptionPageReqVO.class))).thenReturn(pageResult);
mockMvc.perform(get("/prison/consumption/export-excel")
.param("pageNo", "1")
.param("pageSize", "10")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
@Test
@WithMockUser
void testGetConsumptionPage_WithFilters() throws Exception {
// 测试带筛选条件的分页查询
ConsumptionDO consumption = new ConsumptionDO();
consumption.setId(1L);
consumption.setPrisonerId(100L);
consumption.setPrisonerNo("PRISONER001");
consumption.setType(1);
consumption.setStatus(1);
PageResult<ConsumptionDO> pageResult = new PageResult<>();
pageResult.setList(Collections.singletonList(consumption));
pageResult.setTotal(1L);
when(consumptionService.getConsumptionPage(any(ConsumptionPageReqVO.class))).thenReturn(pageResult);
mockMvc.perform(get("/prison/consumption/page")
.param("pageNo", "1")
.param("pageSize", "10")
.param("prisonerNo", "PRISONER001")
.param("type", "1")
.param("status", "1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.total").value(1));
}
}

View File

@ -0,0 +1,283 @@
package cn.iocoder.yudao.module.prison.service.consumption;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.prison.controller.admin.consumption.vo.*;
import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDetailDO;
import cn.iocoder.yudao.module.prison.dal.dataobject.consumption.ConsumptionDO;
import cn.iocoder.yudao.module.prison.dal.mysql.consumption.ConsumptionDetailMapper;
import cn.iocoder.yudao.module.prison.dal.mysql.consumption.ConsumptionMapper;
import cn.iocoder.yudao.module.prison.service.consumption.impl.ConsumptionServiceImpl;
import cn.iocoder.yudao.module.prison.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.prison.enums.ConsumptionTypeEnum;
import cn.iocoder.yudao.module.prison.enums.ConsumptionStatusEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
/**
* 消费订单 Service 测试类
*
* @author xl
*/
@ExtendWith(MockitoExtension.class)
class ConsumptionServiceTest {
@InjectMocks
private ConsumptionServiceImpl consumptionService;
@Mock
private ConsumptionMapper consumptionMapper;
@Mock
private ConsumptionDetailMapper consumptionDetailMapper;
private ConsumptionSaveReqVO createReqVO;
private ConsumptionDO consumptionDO;
@BeforeEach
void setUp() {
// 准备创建请求VO
createReqVO = new ConsumptionSaveReqVO();
createReqVO.setPrisonerId(100L);
createReqVO.setPrisonerNo("PRISONER001");
createReqVO.setType(1);
createReqVO.setTotalAmount(new BigDecimal("100.00"));
createReqVO.setBalance(new BigDecimal("900.00"));
createReqVO.setStatus(1);
createReqVO.setTradeTime(LocalDateTime.now());
// 准备明细数据
ConsumptionDetailSaveReqVO detail1 = new ConsumptionDetailSaveReqVO();
detail1.setGoodsName("商品A");
detail1.setGoodsPrice(new BigDecimal("30.00"));
detail1.setGoodsCount(2);
detail1.setSubtotal(new BigDecimal("60.00"));
ConsumptionDetailSaveReqVO detail2 = new ConsumptionDetailSaveReqVO();
detail2.setGoodsName("商品B");
detail2.setGoodsPrice(new BigDecimal("20.00"));
detail2.setGoodsCount(2);
detail2.setSubtotal(new BigDecimal("40.00"));
createReqVO.setDetails(Arrays.asList(detail1, detail2));
// 准备DO对象
consumptionDO = new ConsumptionDO();
consumptionDO.setId(1L);
consumptionDO.setPrisonerId(100L);
consumptionDO.setPrisonerNo("PRISONER001");
consumptionDO.setOrderNo("CS1234567890");
consumptionDO.setType(1);
consumptionDO.setTotalAmount(new BigDecimal("100.00"));
consumptionDO.setStatus(1);
consumptionDO.setCreateTime(LocalDateTime.now());
consumptionDO.setUpdateTime(LocalDateTime.now());
}
@Test
void testCreateConsumption_Success() {
// 设置Mapper行为
when(consumptionMapper.insert(any(ConsumptionDO.class))).thenReturn(1);
doNothing().when(consumptionDetailMapper).insertBatch(anyList());
// 执行测试
Long result = consumptionService.createConsumption(createReqVO);
// 验证结果
assertNotNull(result);
assertEquals(1L, result);
verify(consumptionMapper).insert(any(ConsumptionDO.class));
verify(consumptionDetailMapper).insertBatch(anyList());
}
@Test
void testCreateConsumption_DetailEmpty() {
// 明细为空
createReqVO.setDetails(null);
// 执行测试并验证异常
assertThrows(ServiceException.class, () -> {
consumptionService.createConsumption(createReqVO);
});
verify(consumptionMapper, never()).insert(any());
}
@Test
void testUpdateConsumption_Success() {
// 准备更新数据
ConsumptionSaveReqVO updateReqVO = new ConsumptionSaveReqVO();
updateReqVO.setId(1L);
updateReqVO.setPrisonerId(100L);
updateReqVO.setType(2);
updateReqVO.setTotalAmount(new BigDecimal("150.00"));
ConsumptionDetailSaveReqVO detail = new ConsumptionDetailSaveReqVO();
detail.setGoodsName("商品C");
detail.setGoodsPrice(new BigDecimal("75.00"));
detail.setGoodsCount(2);
detail.setSubtotal(new BigDecimal("150.00"));
updateReqVO.setDetails(Collections.singletonList(detail));
// 设置Mapper行为
when(consumptionMapper.selectById(1L)).thenReturn(consumptionDO);
when(consumptionMapper.updateById(any(ConsumptionDO.class))).thenReturn(1);
doNothing().when(consumptionDetailMapper).deleteByConsumptionId(1L);
doNothing().when(consumptionDetailMapper).insertBatch(anyList());
// 执行测试
consumptionService.updateConsumption(updateReqVO);
// 验证结果
verify(consumptionMapper).selectById(1L);
verify(consumptionMapper).updateById(any(ConsumptionDO.class));
verify(consumptionDetailMapper).deleteByConsumptionId(1L);
verify(consumptionDetailMapper).insertBatch(anyList());
}
@Test
void testUpdateConsumption_NotFound() {
ConsumptionSaveReqVO updateReqVO = new ConsumptionSaveReqVO();
updateReqVO.setId(999L);
when(consumptionMapper.selectById(999L)).thenReturn(null);
assertThrows(ServiceException.class, () -> {
consumptionService.updateConsumption(updateReqVO);
});
verify(consumptionMapper, never()).updateById(any());
}
@Test
void testDeleteConsumption_Success() {
when(consumptionMapper.selectById(1L)).thenReturn(consumptionDO);
when(consumptionMapper.deleteById(1L)).thenReturn(1);
doNothing().when(consumptionDetailMapper).deleteByConsumptionId(1L);
consumptionService.deleteConsumption(1L);
verify(consumptionMapper).deleteById(1L);
verify(consumptionDetailMapper).deleteByConsumptionId(1L);
}
@Test
void testDeleteConsumption_NotFound() {
when(consumptionMapper.selectById(999L)).thenReturn(null);
assertThrows(ServiceException.class, () -> {
consumptionService.deleteConsumption(999L);
});
verify(consumptionMapper, never()).deleteById(any());
}
@Test
void testDeleteConsumptionListByIds_Success() {
List<Long> ids = Arrays.asList(1L, 2L, 3L);
when(consumptionMapper.deleteBatchIds(ids)).thenReturn(3);
doNothing().when(consumptionDetailMapper).deleteByConsumptionIds(ids);
consumptionService.deleteConsumptionListByIds(ids);
verify(consumptionMapper).deleteBatchIds(ids);
verify(consumptionDetailMapper).deleteByConsumptionIds(ids);
}
@Test
void testGetConsumption_Success() {
when(consumptionMapper.selectById(1L)).thenReturn(consumptionDO);
ConsumptionDO result = consumptionService.getConsumption(1L);
assertNotNull(result);
assertEquals(1L, result.getId());
assertEquals("PRISONER001", result.getPrisonerNo());
}
@Test
void testGetConsumption_NotFound() {
when(consumptionMapper.selectById(999L)).thenReturn(null);
ConsumptionDO result = consumptionService.getConsumption(999L);
assertNull(result);
}
@Test
void testGetConsumptionPage_Success() {
ConsumptionPageReqVO pageReqVO = new ConsumptionPageReqVO();
pageReqVO.setPageNo(1);
pageReqVO.setPageSize(10);
Page<ConsumptionDO> page = new Page<>(1, 10);
page.setRecords(Collections.singletonList(consumptionDO));
page.setTotal(1);
when(consumptionMapper.selectPage(any(Page.class), any(LambdaQueryWrapper.class)))
.thenReturn(page);
cn.iocoder.yudao.framework.common.pojo.PageResult<ConsumptionDO> result =
consumptionService.getConsumptionPage(pageReqVO);
assertNotNull(result);
assertEquals(1, result.getTotal());
assertEquals(1, result.getList().size());
assertEquals("PRISONER001", result.getList().get(0).getPrisonerNo());
}
@Test
void testGetConsumptionDetailList_Success() {
ConsumptionDetailDO detail = new ConsumptionDetailDO();
detail.setId(1L);
detail.setConsumptionId(1L);
detail.setGoodsName("商品A");
detail.setGoodsPrice(new BigDecimal("50.00"));
detail.setGoodsCount(2);
when(consumptionDetailMapper.selectListByConsumptionId(1L))
.thenReturn(Collections.singletonList(detail));
List<ConsumptionDetailDO> result = consumptionService.getConsumptionDetailList(1L);
assertNotNull(result);
assertEquals(1, result.size());
assertEquals("商品A", result.get(0).getGoodsName());
}
@Test
void testExportExcel_Success() {
ConsumptionPageReqVO pageReqVO = new ConsumptionPageReqVO();
pageReqVO.setPageNo(1);
pageReqVO.setPageSize(10);
Page<ConsumptionDO> page = new Page<>(1, 10);
page.setRecords(Collections.singletonList(consumptionDO));
page.setTotal(1);
when(consumptionMapper.selectPage(any(Page.class), any(LambdaQueryWrapper.class)))
.thenReturn(page);
cn.iocoder.yudao.framework.common.pojo.PageResult<ConsumptionDO> result =
consumptionService.getConsumptionPage(pageReqVO);
assertNotNull(result);
assertEquals(1, result.getTotal());
}
}

View File

@ -274,4 +274,25 @@ justauth:
cache: cache:
type: REDIS type: REDIS
prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE:: prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE::
timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟 timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟
--- #################### LLM危险评估大模型配置 ####################
# OneAPI统一接口配置用于危险评估智能分析
llm:
# 本地OneAPI服务
local:
base-url: ${LLM_BASE_URL:https://oneapi.gongjulian.cn/v1} # OneAPI服务地址
api-key: ${LLM_API_KEY:sk-lB2Fc9ssY5UuwmiV5dD441F997364d29Be547e008dF5Cf41} # API密钥建议通过环境变量配置
model: ${LLM_MODEL:deepseek-ai/deepseek-v3.2} # 使用的模型
timeout-seconds: ${LLM_TIMEOUT:120} # 请求超时时间
# Claude可选需要时取消注释
# claude:
# api-key: ${CLAUDE_API_KEY:}
# timeout-seconds: 30
# model: claude-3-5-sonnet-20241022
# OpenAI GPT-4可选
# openai:
# api-key: ${OPENAI_API_KEY:}
# model: gpt-4o
# timeout-seconds: 60