|
|
@@ -0,0 +1,270 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.etotem.cfc.common.Result;
|
|
|
+import com.etotem.cfc.dto.DanReportEditDTO;
|
|
|
+import com.etotem.cfc.dto.DanReportUploadVO;
|
|
|
+import com.etotem.cfc.entity.DanAssessmentResult;
|
|
|
+import com.etotem.cfc.entity.DanReportUpload;
|
|
|
+import com.etotem.cfc.mapper.DanAssessmentResultMapper;
|
|
|
+import com.etotem.cfc.mapper.DanReportUploadMapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.fasterxml.jackson.core.type.TypeReference;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.io.IOException;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class DanReportUploadService {
|
|
|
+
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(DanReportUploadService.class);
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private DanAssessmentResultMapper resultMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private DanReportUploadMapper uploadMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private DanReportParseService danReportParseService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private FileStorageService fileStorageService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private DimensionScoreService dimensionScoreService;
|
|
|
+
|
|
|
+ private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+
|
|
|
+ // Phase 1: 上传并预览 PDF
|
|
|
+ public Result<Map<String, Object>> uploadAndParse(Long userId, Long memberId,
|
|
|
+ String dimension, MultipartFile file) {
|
|
|
+ if (file == null || file.isEmpty()) {
|
|
|
+ return Result.error("请选择文件");
|
|
|
+ }
|
|
|
+ if (!DanReportUpload.DIM_MIND.equals(dimension) && !DanReportUpload.DIM_WISDOM.equals(dimension)) {
|
|
|
+ return Result.error("dimension 必须为 mind 或 wisdom");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 保存文件
|
|
|
+ String fileUrl = fileStorageService.upload(file, "dan-reports");
|
|
|
+ if (fileUrl == null) {
|
|
|
+ return Result.error("文件保存失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 创建草稿记录
|
|
|
+ DanReportUpload upload = new DanReportUpload();
|
|
|
+ upload.setFamilyMemberId(memberId);
|
|
|
+ upload.setUploaderUserId(userId);
|
|
|
+ upload.setDimension(dimension);
|
|
|
+ upload.setFileUrl(fileUrl);
|
|
|
+ upload.setFileId(fileUrl); // 同义
|
|
|
+ upload.setFileName(file.getOriginalFilename());
|
|
|
+ upload.setFileSize(file.getSize());
|
|
|
+ upload.setUploadedAt(new Date());
|
|
|
+ upload.setDraftStatus(DanReportUpload.DRAFT_PENDING);
|
|
|
+ upload.setParseStatus(DanReportUpload.PARSE_PENDING);
|
|
|
+ upload.setEditCount(0);
|
|
|
+ upload.setCreatedAt(new Date());
|
|
|
+ upload.setUpdatedAt(new Date());
|
|
|
+ uploadMapper.insert(upload);
|
|
|
+
|
|
|
+ // 3. 同步解析
|
|
|
+ try {
|
|
|
+ DanReportParseService.DanParsedReport parsed =
|
|
|
+ danReportParseService.parse(file.getInputStream(), dimension);
|
|
|
+ upload.setParserVersion("v1");
|
|
|
+ upload.setParseStatus(DanReportUpload.PARSE_SUCCESS);
|
|
|
+ upload.setParsedItems(objectMapper.writeValueAsString(parsed.getItems()));
|
|
|
+ upload.setParsedExtra(objectMapper.writeValueAsString(parsed.getExtra()));
|
|
|
+ upload.setParsedSummary(parsed.getSummary());
|
|
|
+ upload.setParsedSuggestions(parsed.getSuggestions());
|
|
|
+ upload.setReportType(parsed.getReportType());
|
|
|
+ upload.setParsedAt(new Date());
|
|
|
+ uploadMapper.updateById(upload);
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error("DAN PDF 解析失败", e);
|
|
|
+ upload.setParseStatus(DanReportUpload.PARSE_FAILED);
|
|
|
+ upload.setParseError(e.getMessage());
|
|
|
+ uploadMapper.updateById(upload);
|
|
|
+ return Result.error("PDF解析失败: " + e.getMessage());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("DAN PDF 解析异常", e);
|
|
|
+ upload.setParseStatus(DanReportUpload.PARSE_FAILED);
|
|
|
+ upload.setParseError(e.getMessage());
|
|
|
+ uploadMapper.updateById(upload);
|
|
|
+ return Result.error("PDF解析异常: " + e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 返回预览
|
|
|
+ Map<String, Object> data = new LinkedHashMap<>();
|
|
|
+ data.put("uploadId", upload.getId());
|
|
|
+ data.put("dimension", dimension);
|
|
|
+ data.put("fileUrl", fileUrl);
|
|
|
+ data.put("itemsJson", upload.getParsedItems());
|
|
|
+ data.put("extraJson", upload.getParsedExtra());
|
|
|
+ data.put("summary", upload.getParsedSummary());
|
|
|
+ data.put("suggestions", upload.getParsedSuggestions());
|
|
|
+ data.put("message", "解析完成,请确认数据准确性");
|
|
|
+ return Result.success(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 编辑草稿
|
|
|
+ public Result<DanReportUploadVO> editDraft(Long uploadId, Long userId, DanReportEditDTO edit) {
|
|
|
+ DanReportUpload upload = uploadMapper.selectById(uploadId);
|
|
|
+ if (upload == null || upload.getDeletedAt() != null) {
|
|
|
+ return Result.error("上传记录不存在");
|
|
|
+ }
|
|
|
+ if (!DanReportUpload.DRAFT_PENDING.equals(upload.getDraftStatus())) {
|
|
|
+ return Result.error("只有 pending 状态的记录可以编辑");
|
|
|
+ }
|
|
|
+ if (edit.getItemsJson() != null) upload.setEditedItems(edit.getItemsJson());
|
|
|
+ if (edit.getExtraJson() != null) upload.setEditedExtra(edit.getExtraJson());
|
|
|
+ if (edit.getSummary() != null) upload.setEditedSummary(edit.getSummary());
|
|
|
+ if (edit.getSuggestions() != null) upload.setEditedSuggestions(edit.getSuggestions());
|
|
|
+ upload.setEditCount(upload.getEditCount() == null ? 1 : upload.getEditCount() + 1);
|
|
|
+ upload.setUpdatedAt(new Date());
|
|
|
+ uploadMapper.updateById(upload);
|
|
|
+ return Result.success(DanReportUploadVO.fromEntity(upload));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Phase 2: 确认入库(创建 dan_assessment_results 关联记录)
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public Result<DanReportUploadVO> confirmUpload(Long uploadId, Long userId,
|
|
|
+ String childName, String assessmentDateStr) {
|
|
|
+ DanReportUpload upload = uploadMapper.selectById(uploadId);
|
|
|
+ if (upload == null || upload.getDeletedAt() != null) {
|
|
|
+ return Result.error("上传记录不存在");
|
|
|
+ }
|
|
|
+ if (!DanReportUpload.DRAFT_PENDING.equals(upload.getDraftStatus())) {
|
|
|
+ return Result.error("此记录已" + upload.getDraftStatus());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建数值结果记录
|
|
|
+ DanAssessmentResult result = new DanAssessmentResult();
|
|
|
+ result.setFamilyMemberId(upload.getFamilyMemberId());
|
|
|
+ result.setTeacherId(upload.getUploaderUserId());
|
|
|
+ result.setSource(DanAssessmentResult.SOURCE_PARENT_UPLOAD);
|
|
|
+ result.setSourceReportId(upload.getId());
|
|
|
+ result.setDimension(upload.getDimension());
|
|
|
+ result.setReportFileId(upload.getFileUrl());
|
|
|
+ result.setDraftStatus("confirmed");
|
|
|
+ result.setAnalysisStatus("pending");
|
|
|
+ result.setStatus("completed");
|
|
|
+ result.setAssessmentDate(new Date());
|
|
|
+ result.setChildName(childName);
|
|
|
+ if (assessmentDateStr != null && !assessmentDateStr.isEmpty()) {
|
|
|
+ try {
|
|
|
+ result.setAssessmentDate(new SimpleDateFormat("yyyy-MM-dd").parse(assessmentDateStr));
|
|
|
+ } catch (Exception ignored) {}
|
|
|
+ }
|
|
|
+ // 优先使用 edited_*,否则 parsed_*
|
|
|
+ result.setStructuredAnalysis(upload.getEditedItems() != null
|
|
|
+ ? upload.getEditedItems()
|
|
|
+ : upload.getParsedItems());
|
|
|
+ result.setAnalysisReport(upload.getEditedSummary() != null
|
|
|
+ ? upload.getEditedSummary()
|
|
|
+ : upload.getParsedSummary());
|
|
|
+ result.setGrowthSuggestions(upload.getEditedSuggestions() != null
|
|
|
+ ? upload.getEditedSuggestions()
|
|
|
+ : upload.getParsedSuggestions());
|
|
|
+ result.setCreatedAt(new Date());
|
|
|
+ result.setUpdatedAt(new Date());
|
|
|
+ resultMapper.insert(result);
|
|
|
+
|
|
|
+ // 更新上传记录
|
|
|
+ upload.setDraftStatus(DanReportUpload.DRAFT_CONFIRMED);
|
|
|
+ upload.setConfirmedAt(new Date());
|
|
|
+ upload.setConfirmedBy(userId);
|
|
|
+ upload.setLinkedResultId(result.getId());
|
|
|
+ upload.setUpdatedAt(new Date());
|
|
|
+ uploadMapper.updateById(upload);
|
|
|
+
|
|
|
+ // 刷新五维能量
|
|
|
+ try {
|
|
|
+ dimensionScoreService.refreshFromDanAssessment(
|
|
|
+ upload.getFamilyMemberId(), result.getId(), upload.getDimension());
|
|
|
+ } catch (Exception ignored) {}
|
|
|
+
|
|
|
+ return Result.success(DanReportUploadVO.fromEntity(upload));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 规划师完成分析
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public Result<Void> completeAnalysis(Long uploadId, Long teacherId) {
|
|
|
+ DanReportUpload upload = uploadMapper.selectById(uploadId);
|
|
|
+ if (upload == null) {
|
|
|
+ return Result.error("记录不存在");
|
|
|
+ }
|
|
|
+ if (upload.getLinkedResultId() == null) {
|
|
|
+ return Result.error("未关联数值结果");
|
|
|
+ }
|
|
|
+ DanAssessmentResult result = resultMapper.selectById(upload.getLinkedResultId());
|
|
|
+ if (result == null) {
|
|
|
+ return Result.error("数值结果不存在");
|
|
|
+ }
|
|
|
+ if (!"pending".equals(result.getAnalysisStatus())) {
|
|
|
+ return Result.error("已经确认");
|
|
|
+ }
|
|
|
+ result.setAnalysisStatus("confirmed");
|
|
|
+ result.setUpdatedAt(new Date());
|
|
|
+ resultMapper.updateById(result);
|
|
|
+
|
|
|
+ // 二次刷新五维能量
|
|
|
+ try {
|
|
|
+ dimensionScoreService.refreshFromDanAssessment(
|
|
|
+ upload.getFamilyMemberId(), result.getId(), upload.getDimension());
|
|
|
+ } catch (Exception ignored) {}
|
|
|
+
|
|
|
+ return Result.success();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 列表查询
|
|
|
+ public Result<List<DanReportUploadVO>> listByMember(Long memberId, String dimension) {
|
|
|
+ LambdaQueryWrapper<DanReportUpload> q = new LambdaQueryWrapper<>();
|
|
|
+ q.eq(DanReportUpload::getFamilyMemberId, memberId)
|
|
|
+ .isNull(DanReportUpload::getDeletedAt)
|
|
|
+ .orderByDesc(DanReportUpload::getUploadedAt);
|
|
|
+ if (dimension != null && !dimension.isEmpty()) {
|
|
|
+ q.eq(DanReportUpload::getDimension, dimension);
|
|
|
+ }
|
|
|
+ List<DanReportUpload> list = uploadMapper.selectList(q);
|
|
|
+ java.util.List<DanReportUploadVO> result = new java.util.ArrayList<>();
|
|
|
+ for (DanReportUpload u : list) {
|
|
|
+ result.add(DanReportUploadVO.fromEntity(u));
|
|
|
+ }
|
|
|
+ return Result.success(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 丢弃草稿
|
|
|
+ public Result<Void> discardUpload(Long uploadId, Long userId) {
|
|
|
+ DanReportUpload upload = uploadMapper.selectById(uploadId);
|
|
|
+ if (upload == null) {
|
|
|
+ return Result.error("记录不存在");
|
|
|
+ }
|
|
|
+ upload.setDraftStatus(DanReportUpload.DRAFT_DISCARDED);
|
|
|
+ upload.setUpdatedAt(new Date());
|
|
|
+ uploadMapper.updateById(upload);
|
|
|
+ return Result.success();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 详情
|
|
|
+ public Result<DanReportUploadVO> getDetail(Long uploadId) {
|
|
|
+ DanReportUpload upload = uploadMapper.selectById(uploadId);
|
|
|
+ if (upload == null || upload.getDeletedAt() != null) {
|
|
|
+ return Result.error("记录不存在");
|
|
|
+ }
|
|
|
+ return Result.success(DanReportUploadVO.fromEntity(upload));
|
|
|
+ }
|
|
|
+}
|