|
|
@@ -0,0 +1,327 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
+import com.etotem.cfc.entity.HealthReport;
|
|
|
+import com.etotem.cfc.entity.ReportTransferRequest;
|
|
|
+import com.etotem.cfc.entity.User;
|
|
|
+import com.etotem.cfc.mapper.HealthReportMapper;
|
|
|
+import com.etotem.cfc.mapper.ReportTransferRequestMapper;
|
|
|
+import com.etotem.cfc.mapper.UserMapper;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.LinkedHashSet;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 健康报告跨家庭转移服务
|
|
|
+ *
|
|
|
+ * <p>同一份报告(file_hash 相同)被不同家庭上传时的归属转移审批:</p>
|
|
|
+ * <ol>
|
|
|
+ * <li>申请方家庭发起转移申请(createRequest)→ 通知原家庭</li>
|
|
|
+ * <li>原归属家庭同意(approve)→ health_reports.family_id 转移至申请方、subject_id 置空、
|
|
|
+ * 原成员重算维度分与食材推荐、原家庭不再可见(列表按 family_id 过滤)</li>
|
|
|
+ * <li>原归属家庭拒绝(deny)→ 报告留在原家庭</li>
|
|
|
+ * </ol>
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class ReportTransferService {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ReportTransferRequestMapper transferRequestMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private HealthReportMapper healthReportMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private UserMapper userMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private JdbcTemplate jdbcTemplate;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private DimensionScoreService dimensionScoreService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private FoodRecommendService foodRecommendService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private NotificationDispatchService notificationDispatchService;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发起跨家庭报告转移申请(申请方家庭)
|
|
|
+ *
|
|
|
+ * @param reportId 报告ID(上传重复检测返回的既有报告)
|
|
|
+ * @param requesterFamilyId 申请方家庭ID(当前登录用户所属家庭)
|
|
|
+ * @param requesterUserId 申请方用户ID
|
|
|
+ * @return 创建的转移申请
|
|
|
+ */
|
|
|
+ public ReportTransferRequest createRequest(Long reportId, Long requesterFamilyId, Long requesterUserId) {
|
|
|
+ if (reportId == null || requesterFamilyId == null) {
|
|
|
+ throw new RuntimeException("参数不完整");
|
|
|
+ }
|
|
|
+ HealthReport report = healthReportMapper.selectById(reportId);
|
|
|
+ if (report == null || !"active".equals(report.getStatus())) {
|
|
|
+ throw new RuntimeException("报告不存在或已删除");
|
|
|
+ }
|
|
|
+ if (report.getFamilyId() == null) {
|
|
|
+ throw new RuntimeException("报告未归属任何家庭,无需转移");
|
|
|
+ }
|
|
|
+ if (requesterFamilyId.equals(report.getFamilyId())) {
|
|
|
+ throw new RuntimeException("该报告已归属当前家庭");
|
|
|
+ }
|
|
|
+ // 同报告同申请家庭只允许一条待处理申请
|
|
|
+ List<ReportTransferRequest> pending = transferRequestMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ReportTransferRequest>()
|
|
|
+ .eq(ReportTransferRequest::getReportId, reportId)
|
|
|
+ .eq(ReportTransferRequest::getToFamilyId, requesterFamilyId)
|
|
|
+ .eq(ReportTransferRequest::getStatus, ReportTransferRequest.STATUS_PENDING)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+ if (!pending.isEmpty()) {
|
|
|
+ throw new RuntimeException("转移申请已提交,等待原家庭同意");
|
|
|
+ }
|
|
|
+
|
|
|
+ ReportTransferRequest request = new ReportTransferRequest();
|
|
|
+ request.setReportId(reportId);
|
|
|
+ request.setFromFamilyId(report.getFamilyId());
|
|
|
+ request.setToFamilyId(requesterFamilyId);
|
|
|
+ request.setRequesterUserId(requesterUserId);
|
|
|
+ request.setStatus(ReportTransferRequest.STATUS_PENDING);
|
|
|
+ request.setCreatedAt(new Date());
|
|
|
+ request.setUpdatedAt(new Date());
|
|
|
+ transferRequestMapper.insert(request);
|
|
|
+ log.info("跨家庭报告转移申请已创建: requestId={}, reportId={}, fromFamily={}, toFamily={}",
|
|
|
+ request.getId(), reportId, report.getFamilyId(), requesterFamilyId);
|
|
|
+
|
|
|
+ // 通知原家庭(含报告上传者)
|
|
|
+ notifyFamilyUsers(report.getFamilyId(), report.getUserId(), "report_transfer_request",
|
|
|
+ "报告转移申请",
|
|
|
+ "有其他家庭申请将您的健康报告转入其家庭,请到「健康报告」列表处理。",
|
|
|
+ request.getId());
|
|
|
+ return request;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同意转移(原归属家庭)
|
|
|
+ *
|
|
|
+ * <p>报告 family_id 转移至申请方、subject_id 置空(由新家庭重新归属);
|
|
|
+ * 原成员重算维度分与食材推荐;同报告其他待处理申请改由新归属家庭处理。</p>
|
|
|
+ *
|
|
|
+ * @param requestId 转移申请ID
|
|
|
+ * @param approverFamilyId 同意方家庭ID(必须为申请的原归属家庭)
|
|
|
+ * @param approverUserId 同意方用户ID
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ public void approve(Long requestId, Long approverFamilyId, Long approverUserId) {
|
|
|
+ ReportTransferRequest request = getRequest(requestId);
|
|
|
+ if (!ReportTransferRequest.STATUS_PENDING.equals(request.getStatus())) {
|
|
|
+ throw new RuntimeException("该申请已处理,请勿重复操作");
|
|
|
+ }
|
|
|
+ if (!request.getFromFamilyId().equals(approverFamilyId)) {
|
|
|
+ throw new RuntimeException("无权处理该申请");
|
|
|
+ }
|
|
|
+ HealthReport report = healthReportMapper.selectById(request.getReportId());
|
|
|
+ if (report == null || !"active".equals(report.getStatus())) {
|
|
|
+ throw new RuntimeException("报告不存在或已删除");
|
|
|
+ }
|
|
|
+ if (!request.getFromFamilyId().equals(report.getFamilyId())) {
|
|
|
+ throw new RuntimeException("报告归属已变更,请刷新后重试");
|
|
|
+ }
|
|
|
+
|
|
|
+ Long oldSubjectId = report.getSubjectId();
|
|
|
+
|
|
|
+ // 1. 报告转入申请方家庭,解除原成员归属(JdbcTemplate 以支持 subject_id = NULL)
|
|
|
+ jdbcTemplate.update(
|
|
|
+ "UPDATE health_reports SET family_id = ?, subject_id = NULL, updated_at = NOW() WHERE id = ?",
|
|
|
+ request.getToFamilyId(), report.getId());
|
|
|
+
|
|
|
+ // 2. 原成员重算维度分与食材推荐(移除该报告影响,与 deleteReport 同惯例)
|
|
|
+ if (oldSubjectId != null) {
|
|
|
+ try {
|
|
|
+ dimensionScoreService.refreshFromGutReport(oldSubjectId, report.getId());
|
|
|
+ log.info("报告转移后已重算原成员{}的7维健康评分", oldSubjectId);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("报告转移后刷新原成员{}健康评分失败: {}", oldSubjectId, e.getMessage());
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ foodRecommendService.recalculateForUser(oldSubjectId);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("报告转移后刷新原成员{}食材推荐指数失败: {}", oldSubjectId, e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 同报告其他待处理申请改由新归属家庭处理(原家庭已不再拥有该报告)
|
|
|
+ transferRequestMapper.update(null, new LambdaUpdateWrapper<ReportTransferRequest>()
|
|
|
+ .eq(ReportTransferRequest::getReportId, report.getId())
|
|
|
+ .eq(ReportTransferRequest::getStatus, ReportTransferRequest.STATUS_PENDING)
|
|
|
+ .ne(ReportTransferRequest::getId, request.getId())
|
|
|
+ .set(ReportTransferRequest::getFromFamilyId, request.getToFamilyId())
|
|
|
+ .set(ReportTransferRequest::getUpdatedAt, new Date()));
|
|
|
+
|
|
|
+ // 4. 更新本申请状态
|
|
|
+ request.setStatus(ReportTransferRequest.STATUS_APPROVED);
|
|
|
+ request.setHandledBy(approverUserId);
|
|
|
+ request.setHandledAt(new Date());
|
|
|
+ request.setUpdatedAt(new Date());
|
|
|
+ transferRequestMapper.updateById(request);
|
|
|
+
|
|
|
+ // 5. 通知申请方
|
|
|
+ notifyFamilyUsers(request.getToFamilyId(), request.getRequesterUserId(), "report_transfer_result",
|
|
|
+ "报告转移申请已同意",
|
|
|
+ "您申请的健康报告已转入您的家庭,请到「健康报告」列表查看。",
|
|
|
+ request.getId());
|
|
|
+ log.info("跨家庭报告转移申请已同意: requestId={}, reportId={}, fromFamily={}, toFamily={}",
|
|
|
+ request.getId(), report.getId(), request.getFromFamilyId(), request.getToFamilyId());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 拒绝转移(原归属家庭)
|
|
|
+ *
|
|
|
+ * @param requestId 转移申请ID
|
|
|
+ * @param approverFamilyId 拒绝方家庭ID(必须为申请的原归属家庭)
|
|
|
+ * @param approverUserId 拒绝方用户ID
|
|
|
+ * @param remark 拒绝备注(可空)
|
|
|
+ */
|
|
|
+ public void deny(Long requestId, Long approverFamilyId, Long approverUserId, String remark) {
|
|
|
+ ReportTransferRequest request = getRequest(requestId);
|
|
|
+ if (!ReportTransferRequest.STATUS_PENDING.equals(request.getStatus())) {
|
|
|
+ throw new RuntimeException("该申请已处理,请勿重复操作");
|
|
|
+ }
|
|
|
+ if (!request.getFromFamilyId().equals(approverFamilyId)) {
|
|
|
+ throw new RuntimeException("无权处理该申请");
|
|
|
+ }
|
|
|
+ request.setStatus(ReportTransferRequest.STATUS_DENIED);
|
|
|
+ request.setHandledBy(approverUserId);
|
|
|
+ request.setHandledAt(new Date());
|
|
|
+ request.setRemark(remark);
|
|
|
+ request.setUpdatedAt(new Date());
|
|
|
+ transferRequestMapper.updateById(request);
|
|
|
+
|
|
|
+ // 通知申请方
|
|
|
+ notifyFamilyUsers(request.getToFamilyId(), request.getRequesterUserId(), "report_transfer_result",
|
|
|
+ "报告转移申请被拒绝",
|
|
|
+ "您申请的健康报告转移未获对方家庭同意。",
|
|
|
+ request.getId());
|
|
|
+ log.info("跨家庭报告转移申请已拒绝: requestId={}, reportId={}, byFamily={}",
|
|
|
+ request.getId(), request.getReportId(), approverFamilyId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询本家庭相关的转移申请
|
|
|
+ *
|
|
|
+ * @param familyId 当前登录用户所属家庭
|
|
|
+ * @return {incoming: 待本家庭处理(原归属方), outgoing: 本家庭发起的(申请方)}
|
|
|
+ */
|
|
|
+ public Map<String, Object> listRequests(Long familyId) {
|
|
|
+ List<ReportTransferRequest> incoming = transferRequestMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ReportTransferRequest>()
|
|
|
+ .eq(ReportTransferRequest::getFromFamilyId, familyId)
|
|
|
+ .eq(ReportTransferRequest::getStatus, ReportTransferRequest.STATUS_PENDING)
|
|
|
+ .orderByDesc(ReportTransferRequest::getCreatedAt));
|
|
|
+ List<ReportTransferRequest> outgoing = transferRequestMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ReportTransferRequest>()
|
|
|
+ .eq(ReportTransferRequest::getToFamilyId, familyId)
|
|
|
+ .orderByDesc(ReportTransferRequest::getCreatedAt)
|
|
|
+ .last("LIMIT 50"));
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("incoming", enrichWithReport(incoming));
|
|
|
+ result.put("outgoing", enrichWithReport(outgoing));
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private ReportTransferRequest getRequest(Long requestId) {
|
|
|
+ if (requestId == null) {
|
|
|
+ throw new RuntimeException("申请ID不能为空");
|
|
|
+ }
|
|
|
+ ReportTransferRequest request = transferRequestMapper.selectById(requestId);
|
|
|
+ if (request == null) {
|
|
|
+ throw new RuntimeException("申请不存在");
|
|
|
+ }
|
|
|
+ return request;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量拼接报告摘要(personName/reportType/reportDate/overallScore),供前端列表展示
|
|
|
+ */
|
|
|
+ private List<Map<String, Object>> enrichWithReport(List<ReportTransferRequest> requests) {
|
|
|
+ List<Map<String, Object>> list = new ArrayList<>();
|
|
|
+ if (requests == null || requests.isEmpty()) {
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+ Set<Long> reportIds = new LinkedHashSet<>();
|
|
|
+ for (ReportTransferRequest r : requests) {
|
|
|
+ reportIds.add(r.getReportId());
|
|
|
+ }
|
|
|
+ Map<Long, HealthReport> reportMap = new HashMap<>();
|
|
|
+ List<HealthReport> reports = healthReportMapper.selectBatchIds(reportIds);
|
|
|
+ for (HealthReport r : reports) {
|
|
|
+ reportMap.put(r.getId(), r);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (ReportTransferRequest r : requests) {
|
|
|
+ Map<String, Object> m = new LinkedHashMap<>();
|
|
|
+ m.put("id", r.getId());
|
|
|
+ m.put("reportId", r.getReportId());
|
|
|
+ m.put("status", r.getStatus());
|
|
|
+ m.put("remark", r.getRemark());
|
|
|
+ m.put("createdAt", r.getCreatedAt());
|
|
|
+ m.put("handledAt", r.getHandledAt());
|
|
|
+ HealthReport report = reportMap.get(r.getReportId());
|
|
|
+ if (report != null) {
|
|
|
+ m.put("personName", report.getPersonName());
|
|
|
+ m.put("reportType", report.getReportType());
|
|
|
+ m.put("reportDate", report.getReportDate());
|
|
|
+ m.put("overallScore", report.getOverallScore());
|
|
|
+ m.put("reportDeleted", false);
|
|
|
+ } else {
|
|
|
+ m.put("personName", "报告已删除");
|
|
|
+ m.put("reportType", null);
|
|
|
+ m.put("reportDate", null);
|
|
|
+ m.put("overallScore", null);
|
|
|
+ m.put("reportDeleted", true);
|
|
|
+ }
|
|
|
+ list.add(m);
|
|
|
+ }
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 站内通知指定家庭的全部可登录用户(兜底额外通知者,如报告上传者)
|
|
|
+ *
|
|
|
+ * <p>模板缺失时回退默认文案,恒走 in_app 通道;通知失败不影响主流程。</p>
|
|
|
+ */
|
|
|
+ private void notifyFamilyUsers(Long familyId, Long fallbackUserId, String type,
|
|
|
+ String title, String content, Long refId) {
|
|
|
+ try {
|
|
|
+ Set<Long> userIds = new LinkedHashSet<>();
|
|
|
+ if (familyId != null) {
|
|
|
+ List<User> users = userMapper.selectList(new LambdaQueryWrapper<User>()
|
|
|
+ .eq(User::getFamilyId, familyId));
|
|
|
+ for (User u : users) {
|
|
|
+ if (u.getId() != null) {
|
|
|
+ userIds.add(u.getId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (fallbackUserId != null) {
|
|
|
+ userIds.add(fallbackUserId);
|
|
|
+ }
|
|
|
+ for (Long uid : userIds) {
|
|
|
+ notificationDispatchService.sendByTemplate(type, uid, "report_transfer_request", refId, title, content);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("报告转移通知发送失败: {}", e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|