Просмотр исходного кода

fix(family): 修复家庭长幼排行与兄弟姐妹关系标签(回归用户12家庭72)

- computeSiblingLabel 方向修正: 返回 target 相对 viewer 的称谓(哥哥/姐姐/弟弟/妹妹)
- computeParentChildLabel 按同性别子集计算排行(长子/次子/长女/次女)
- resolveRelativeLabel 动态长幼计算优先, family_relationships 粗标签仅兜底
Xiaogang Liao 5 дней назад
Родитель
Сommit
9e8dc5bd22

+ 33 - 10
cfc-backend/src/main/java/com/etotem/cfc/service/FamilyMemberService.java

@@ -364,13 +364,10 @@ public class FamilyMemberService {
                 }
                 if (genValue != null) {
                     vo.setGenerationValue(genValue);
-                    String label = familyRelationshipService.computeRelationshipLabel(genValue, fm.getGender(), relType);
-                    vo.setRelativeLabel(label);
-                    vo.setRelationship(label);
-                } else {
-                    vo.setRelativeLabel(computeRelativeLabel(viewerMember, fm, familyMembers));
-                    vo.setRelationship(computeRelativeLabel(viewerMember, fm, familyMembers));
                 }
+                String relLabel = resolveRelativeLabel(viewerMember, fm, familyMembers, genValue, relType);
+                vo.setRelativeLabel(relLabel);
+                vo.setRelationship(relLabel);
             }
             // 补充字段
             vo.setIsSpouse(fm.getIsSpouse());
@@ -1442,9 +1439,17 @@ vo.setIsSelf(viewerMember != null && viewerMember.getId().equals(fm.getId()));
                 .sorted(Comparator.comparing(FamilyMember::getBirthday, Comparator.nullsLast(Comparator.naturalOrder())))
                 .collect(Collectors.toList());
 
+        // 长幼排行按同性别子集计算(长子/次子/长女/次女...),避免与异性别成员混排导致序数错误。
+        List<FamilyMember> rankedPool = children;
+        if ("male".equals(targetGender) || "female".equals(targetGender)) {
+            rankedPool = children.stream()
+                    .filter(m -> targetGender.equals(m.getGender()))
+                    .collect(Collectors.toList());
+        }
+
         int index = -1;
-        for (int i = 0; i < children.size(); i++) {
-            if (children.get(i).getId().equals(target.getId())) {
+        for (int i = 0; i < rankedPool.size(); i++) {
+            if (rankedPool.get(i).getId().equals(target.getId())) {
                 index = i;
                 break;
             }
@@ -1463,6 +1468,22 @@ vo.setIsSelf(viewerMember != null && viewerMember.getId().equals(fm.getId()));
         return prefix + "子女";
     }
 
+    /**
+     * 选择视角相关的关系标签。
+     * 优先级:动态长幼计算 > family_relationships 粗辈分标签(genValue 兜底)。
+     * 动态计算能表达"长子/次女/哥哥/姐姐"等精确排行,优先于只能表达"兄弟/姐妹"的粗标签。
+     */
+    String resolveRelativeLabel(FamilyMember viewer, FamilyMember target, List<FamilyMember> allMembers, Integer relGenValue, String relType) {
+        // 动态长幼计算优先(能表达"长子/次女/哥哥/姐姐"等精确排行);
+        // family_relationships 粗辈分标签(genValue+relType) 仅作兜底(只能表达"兄弟/姐妹/丈夫/妻子")。
+        String dynamic = computeRelativeLabel(viewer, target, allMembers);
+        if (dynamic != null) return dynamic;
+        if (relGenValue != null && familyRelationshipService != null) {
+            return familyRelationshipService.computeRelationshipLabel(relGenValue, target.getGender(), relType);
+        }
+        return null;
+    }
+
     /**
      * 计算兄弟姐妹标签(按生日比较)
      */
@@ -1473,9 +1494,11 @@ vo.setIsSelf(viewerMember != null && viewerMember.getId().equals(fm.getId()));
 
         if (viewerBday != null && targetBday != null) {
             int cmp = viewerBday.compareTo(targetBday);
-            if (cmp > 0) {
+            if (cmp < 0) {
+                // viewer 比 target 更大(更早出生) → target 是弟弟/妹妹
                 return "male".equals(targetGender) ? "弟弟" : "妹妹";
-            } else if (cmp < 0) {
+            } else if (cmp > 0) {
+                // viewer 比 target 更小(更晚出生) → target 是哥哥/姐姐
                 return "male".equals(targetGender) ? "哥哥" : "姐姐";
             }
         }

+ 102 - 0
cfc-backend/src/test/java/com/etotem/cfc/unit/FamilyMemberRelationshipLabelTest.java

@@ -0,0 +1,102 @@
+package com.etotem.cfc.unit;
+
+import com.etotem.cfc.entity.FamilyMember;
+import com.etotem.cfc.service.FamilyMemberService;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * 家庭长幼关系标签契约(回归 2026-11-07 用户12家庭72 反馈):
+ * - 娜娜应为"长女"(当前显示"姐妹/次女")
+ * - CF 应为"次女"(当前"三女")
+ * - 从 CF 视角:云泽应为"哥哥"(当前"弟弟")、娜娜应为"姐姐"(当前"妹妹")
+ */
+class FamilyMemberRelationshipLabelTest {
+
+    private final SimpleDateFormat FMT = new SimpleDateFormat("yyyy-MM-dd");
+    private final FamilyMemberService service = new FamilyMemberService();
+
+    private FamilyMember m(Long id, String gender, Date bday, Integer gen, boolean spouse) {
+        FamilyMember fm = new FamilyMember();
+        fm.setId(id);
+        fm.setGender(gender);
+        fm.setBirthday(bday);
+        fm.setGeneration(gen);
+        fm.setIsSpouse(spouse);
+        return fm;
+    }
+
+    /** 用户12家庭72: 老大(父,21), 书宁宁(母,62), 云泽(长子,40), 娜娜(女,23), CF(女,84) */
+    private List<FamilyMember> family72() throws Exception {
+        return Arrays.asList(
+                m(21L, "male", null, 0, false),
+                m(62L, "female", FMT.parse("1972-09-07"), 0, true),
+                m(40L, "male", FMT.parse("2000-06-17"), -1, false),
+                m(23L, "female", FMT.parse("2018-04-01"), -1, false),
+                m(84L, "female", FMT.parse("2025-11-07"), -1, false));
+    }
+
+    private FamilyMember pick(List<FamilyMember> all, Long id) {
+        return all.stream().filter(x -> id.equals(x.getId())).findFirst().get();
+    }
+
+    private String rel(FamilyMember viewer, FamilyMember target, List<FamilyMember> all) {
+        return (String) ReflectionTestUtils.invokeMethod(service, "computeRelativeLabel", viewer, target, all);
+    }
+
+    @Test void CF视角_云泽应是哥哥() throws Exception {
+        List<FamilyMember> all = family72();
+        assertEquals("哥哥", rel(pick(all, 84L), pick(all, 40L), all));
+    }
+
+    @Test void CF视角_娜娜应是姐姐() throws Exception {
+        List<FamilyMember> all = family72();
+        assertEquals("姐姐", rel(pick(all, 84L), pick(all, 23L), all));
+    }
+
+    @Test void 父视角_娜娜应是长女() throws Exception {
+        List<FamilyMember> all = family72();
+        assertEquals("长女", rel(pick(all, 21L), pick(all, 23L), all));
+    }
+
+    @Test void 父视角_CF应是次女() throws Exception {
+        List<FamilyMember> all = family72();
+        assertEquals("次女", rel(pick(all, 21L), pick(all, 84L), all));
+    }
+
+    @Test void 父视角_云泽应是长子_回归() throws Exception {
+        List<FamilyMember> all = family72();
+        assertEquals("长子", rel(pick(all, 21L), pick(all, 40L), all));
+    }
+
+    @Test void 云泽视角_娜娜应是妹妹() throws Exception {
+        List<FamilyMember> all = family72();
+        assertEquals("妹妹", rel(pick(all, 40L), pick(all, 23L), all));
+    }
+
+    @Test void 云泽视角_CF应是妹妹() throws Exception {
+        List<FamilyMember> all = family72();
+        assertEquals("妹妹", rel(pick(all, 40L), pick(all, 84L), all));
+    }
+
+    @Test void family_relationships粗标签_不应覆盖动态长幼排行() throws Exception {
+        List<FamilyMember> all = family72();
+        FamilyMember dad = pick(all, 21L);
+        FamilyMember nana = pick(all, 23L);
+        // family_relationships 表对 (21->23) 存 genValue=0 → 粗标签"姐妹";但动态计算应优先返回"长女"
+        com.etotem.cfc.service.FamilyRelationshipService mockRel =
+                org.mockito.Mockito.mock(com.etotem.cfc.service.FamilyRelationshipService.class);
+        org.mockito.Mockito.when(mockRel.computeRelationshipLabel(0, "female", null)).thenReturn("姐妹");
+        ReflectionTestUtils.setField(service, "familyRelationshipService", mockRel);
+        String label = (String) ReflectionTestUtils.invokeMethod(service, "resolveRelativeLabel", dad, nana, all, 0, null);
+        assertEquals("长女", label);
+    }
+
+}