|
|
@@ -26,10 +26,13 @@ import java.io.FileInputStream;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStream;
|
|
|
import java.text.SimpleDateFormat;
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.Date;
|
|
|
import java.util.LinkedHashMap;
|
|
|
+import java.util.LinkedHashSet;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
import com.etotem.cfc.util.SortUtil;
|
|
|
|
|
|
@Service
|
|
|
@@ -617,4 +620,131 @@ if (!DanReportUpload.DIM_MIND.equals(dimension) && !DanReportUpload.DIM_WISDOM.e
|
|
|
}
|
|
|
return Result.success(vo);
|
|
|
}
|
|
|
+
|
|
|
+ /** DAN 报告两版对比:按 uploaded_at 先后排序,较早的为基准。
|
|
|
+ * 返回 parsed/edited items 差异列表(数值 delta + 新增/删除标记)。
|
|
|
+ */
|
|
|
+ public Result<Map<String, Object>> compareUploads(Long uploadIdA, Long uploadIdB) {
|
|
|
+ if (uploadIdA == null || uploadIdB == null) {
|
|
|
+ return Result.error("缺少 uploadIdA 或 uploadIdB");
|
|
|
+ }
|
|
|
+ DanReportUpload uploadA = uploadMapper.selectById(uploadIdA);
|
|
|
+ DanReportUpload uploadB = uploadMapper.selectById(uploadIdB);
|
|
|
+ if (uploadA == null || uploadB == null) {
|
|
|
+ return Result.error("上传记录不存在");
|
|
|
+ }
|
|
|
+ if (!uploadA.getFamilyMemberId().equals(uploadB.getFamilyMemberId())) {
|
|
|
+ return Result.error("两次上传必须属于同一成员");
|
|
|
+ }
|
|
|
+ // 以 uploaded_at 先后顺序排序:较早的为基准
|
|
|
+ DanReportUpload first = uploadA.getUploadedAt() != null && uploadB.getUploadedAt() != null
|
|
|
+ && uploadA.getUploadedAt().before(uploadB.getUploadedAt()) ? uploadA : uploadB;
|
|
|
+ DanReportUpload second = first == uploadA ? uploadB : uploadA;
|
|
|
+ // 使用 edited_* 优先,否则 parsed_*
|
|
|
+ String firstItems = first.getEditedItems() != null ? first.getEditedItems() : first.getParsedItems();
|
|
|
+ String firstSummary = first.getEditedSummary() != null ? first.getEditedSummary() : first.getParsedSummary();
|
|
|
+ String secondItems = second.getEditedItems() != null ? second.getEditedItems() : second.getParsedItems();
|
|
|
+ String secondSummary = second.getEditedSummary() != null ? second.getEditedSummary() : second.getParsedSummary();
|
|
|
+ List<Map<String, Object>> diff = diffDeltas(firstItems, secondItems, first, second);
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
+ result.put("uploadIdA", uploadA.getId());
|
|
|
+ result.put("uploadIdB", uploadB.getId());
|
|
|
+ result.put("firstUploadId", first.getId());
|
|
|
+ result.put("secondUploadId", second.getId());
|
|
|
+ result.put("orderBy", "uploaded_at_asc");
|
|
|
+ result.put("deltas", diff);
|
|
|
+ result.put("firstSummary", firstSummary);
|
|
|
+ result.put("secondSummary", secondSummary);
|
|
|
+ return Result.success(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 指定两次上传的 items 差异:以 code 对齐,数值算 delta,单边项标 added/removed */
|
|
|
+ private List<Map<String, Object>> diffDeltas(String itemsJson1, String itemsJson2,
|
|
|
+ DanReportUpload first, DanReportUpload second) {
|
|
|
+ List<Map<String, Object>> result = new ArrayList<>();
|
|
|
+ if (itemsJson1 == null || itemsJson2 == null) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ List<Map<String, Object>> list1, list2;
|
|
|
+ try {
|
|
|
+ list1 = objectMapper.readValue(itemsJson1,
|
|
|
+ new TypeReference<List<Map<String, Object>>>() {});
|
|
|
+ list2 = objectMapper.readValue(itemsJson2,
|
|
|
+ new TypeReference<List<Map<String, Object>>>() {});
|
|
|
+ } catch (Exception e) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ if (list1.isEmpty() || list2.isEmpty()) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ // 以 code 为键建立映射(code 唯一标识一项指标)
|
|
|
+ Map<String, Map<String, Object>> map1 = new LinkedHashMap<>();
|
|
|
+ for (Map<String, Object> item : list1) {
|
|
|
+ String code = item.get("code") != null ? String.valueOf(item.get("code")) : null;
|
|
|
+ if (code != null) map1.put(code, item);
|
|
|
+ }
|
|
|
+ Map<String, Map<String, Object>> map2 = new LinkedHashMap<>();
|
|
|
+ for (Map<String, Object> item : list2) {
|
|
|
+ String code = item.get("code") != null ? String.valueOf(item.get("code")) : null;
|
|
|
+ if (code != null) map2.put(code, item);
|
|
|
+ }
|
|
|
+ // 合并所有 codes(保持先出现顺序 + 新增 codes)
|
|
|
+ LinkedHashSet<String> codes = new LinkedHashSet<>();
|
|
|
+ for (Map<String, Object> item : list1) {
|
|
|
+ String code = item.get("code") != null ? String.valueOf(item.get("code")) : null;
|
|
|
+ if (code != null) codes.add(code);
|
|
|
+ }
|
|
|
+ for (Map<String, Object> item : list2) {
|
|
|
+ String code = item.get("code") != null ? String.valueOf(item.get("code")) : null;
|
|
|
+ if (code != null) codes.add(code);
|
|
|
+ }
|
|
|
+ for (String code : codes) {
|
|
|
+ Map<String, Object> m1 = map1.get(code);
|
|
|
+ Map<String, Object> m2 = map2.get(code);
|
|
|
+ if (m1 != null && m2 != null) {
|
|
|
+ Map<String, Object> delta = new LinkedHashMap<>();
|
|
|
+ delta.put("status", "modified");
|
|
|
+ delta.put("code", code);
|
|
|
+ delta.put("name1", m1.get("name"));
|
|
|
+ delta.put("name2", m2.get("name"));
|
|
|
+ delta.put("category1", m1.get("category"));
|
|
|
+ delta.put("category2", m2.get("category"));
|
|
|
+ Object v1 = m1.get("value");
|
|
|
+ Object v2 = m2.get("value");
|
|
|
+ if (v1 instanceof Number && v2 instanceof Number) {
|
|
|
+ double d1 = ((Number) v1).doubleValue();
|
|
|
+ double d2 = ((Number) v2).doubleValue();
|
|
|
+ delta.put("value1", v1);
|
|
|
+ delta.put("value2", v2);
|
|
|
+ delta.put("valueDelta", Math.round((d2 - d1) * 10.0) / 10.0);
|
|
|
+ delta.put("change", Math.abs(d2 - d1) > 0.001 ? "increase" : "no_change");
|
|
|
+ } else {
|
|
|
+ delta.put("value1", v1);
|
|
|
+ delta.put("value2", v2);
|
|
|
+ delta.put("valueDelta", null);
|
|
|
+ delta.put("change", "text_changed");
|
|
|
+ }
|
|
|
+ result.add(delta);
|
|
|
+ } else if (m1 == null) {
|
|
|
+ Map<String, Object> added = new LinkedHashMap<>();
|
|
|
+ added.put("status", "added");
|
|
|
+ added.put("code", code);
|
|
|
+ added.put("name2", m2.get("name"));
|
|
|
+ added.put("value2", m2.get("value"));
|
|
|
+ added.put("category2", m2.get("category"));
|
|
|
+ added.put("from", second.getUploadedAt() != null ? second.getUploadedAt().toString() : "unknown");
|
|
|
+ result.add(added);
|
|
|
+ } else {
|
|
|
+ Map<String, Object> removed = new LinkedHashMap<>();
|
|
|
+ removed.put("status", "removed");
|
|
|
+ removed.put("code", code);
|
|
|
+ removed.put("name1", m1.get("name"));
|
|
|
+ removed.put("value1", m1.get("value"));
|
|
|
+ removed.put("category1", m1.get("category"));
|
|
|
+ removed.put("from", first.getUploadedAt() != null ? first.getUploadedAt().toString() : "unknown");
|
|
|
+ result.add(removed);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
}
|