|
|
@@ -250,7 +250,7 @@ public class FamilyMemberService {
|
|
|
vo.setSource("family_member");
|
|
|
vo.setUserId(fm.getUserId());
|
|
|
vo.setIsAdmin(isAdmin);
|
|
|
- vo.setNickname(fm.getNickname());
|
|
|
+ vo.setNickname(resolveMemberNickname(fm));
|
|
|
vo.setPhone(fm.getPhone());
|
|
|
vo.setAvatar(fm.getAvatar());
|
|
|
vo.setGender(fm.getGender());
|
|
|
@@ -291,19 +291,13 @@ public class FamilyMemberService {
|
|
|
vo.setIntimacyScore(fm.getIntimacyScore());
|
|
|
vo.setCommunicationScore(fm.getCommunicationScore());
|
|
|
vo.setIsSelf(viewerMember != null && viewerMember.getId().equals(fm.getId()));
|
|
|
+ // 设置 isSwitchable:同家庭非自己成员均可切换视角(含被邀请的能登录成员)
|
|
|
+ vo.setIsSwitchable(!(viewerMember != null && viewerMember.getId().equals(fm.getId())));
|
|
|
vo.setLastInteractionAt(fm.getLastInteractionAt());
|
|
|
vo.setStreakDays(fm.getStreakDays());
|
|
|
// 兼容旧前端字段
|
|
|
vo.setMemberId(fm.getId());
|
|
|
- vo.setName(fm.getNickname());
|
|
|
- // 设置 isSwitchable:仅无 openid 的不可登录成员可被切换
|
|
|
- if (fm.getUserId() != null) {
|
|
|
- User memberUser = userMapper.selectById(fm.getUserId());
|
|
|
- String openid = memberUser != null ? memberUser.getOpenid() : null;
|
|
|
- vo.setIsSwitchable(openid == null || openid.isEmpty());
|
|
|
- } else {
|
|
|
- vo.setIsSwitchable(true);
|
|
|
- }
|
|
|
+ vo.setName(resolveMemberNickname(fm));
|
|
|
result.add(vo);
|
|
|
}
|
|
|
|
|
|
@@ -343,6 +337,7 @@ public class FamilyMemberService {
|
|
|
vo.setGeneration(0);
|
|
|
vo.setIsAdmin(isAdmin);
|
|
|
vo.setIsSelf(p.getId().equals(userId));
|
|
|
+ vo.setIsSwitchable(!p.getId().equals(userId)); // 非自己即可切换(含 source='parent' 段)
|
|
|
vo.setShowToFamily(p.getShowToFamily() != null ? p.getShowToFamily() : 1);
|
|
|
vo.setIsSpouse(false);
|
|
|
vo.setTotalPoints(p.getTotalPoints());
|
|
|
@@ -364,23 +359,46 @@ public class FamilyMemberService {
|
|
|
// 从 family_members 查找
|
|
|
FamilyMember member = familyMemberMapper.selectById(memberId);
|
|
|
if (member != null && member.getFamilyId().equals(user.getFamilyId())) {
|
|
|
- // 【重构】成员切换校验:仅不可登录成员可被切换
|
|
|
- User memberUser = member.getUserId() != null ? userMapper.selectById(member.getUserId()) : null;
|
|
|
- String openid = memberUser != null ? memberUser.getOpenid() : null;
|
|
|
- boolean canLogin = openid != null && !openid.isEmpty();
|
|
|
- if (canLogin) {
|
|
|
- throw new RuntimeException("该成员可通过微信登录,不能切换为TA的身份");
|
|
|
+ // 不能切换到自己
|
|
|
+ if (member.getUserId() != null && member.getUserId().equals(userId)) {
|
|
|
+ throw new RuntimeException("不能切换到自己");
|
|
|
+ }
|
|
|
+ String effectiveRole = computeEffectiveRole(member);
|
|
|
+ // 直接登录的孩子端不能切换到家长端
|
|
|
+ if ("child".equals(user.getRole()) && "parent".equals(effectiveRole)) {
|
|
|
+ throw new RuntimeException("孩子账号不能切换到家长视角");
|
|
|
}
|
|
|
|
|
|
SwitchMemberVO vo = new SwitchMemberVO();
|
|
|
vo.setMemberId(member.getId());
|
|
|
vo.setSource("family_member");
|
|
|
- vo.setEffectiveRole(computeEffectiveRole(member));
|
|
|
+ vo.setEffectiveRole(effectiveRole);
|
|
|
vo.setNickname(member.getNickname());
|
|
|
vo.setIsSwitchable(true);
|
|
|
return vo;
|
|
|
}
|
|
|
|
|
|
+ // 兼容 source='parent':无 family_members 记录的 users 表家长成员(memberId 即 userId)
|
|
|
+ Long familyId = user.getFamilyId();
|
|
|
+ User targetParent = userMapper.selectById(memberId);
|
|
|
+ if (targetParent != null && familyId.equals(targetParent.getFamilyId())) {
|
|
|
+ // 不能切换到自己
|
|
|
+ if (targetParent.getId().equals(userId)) {
|
|
|
+ throw new RuntimeException("不能切换到自己");
|
|
|
+ }
|
|
|
+ // 直接登录的孩子端不能切换到家长端
|
|
|
+ if ("child".equals(user.getRole())) {
|
|
|
+ throw new RuntimeException("孩子账号不能切换到家长视角");
|
|
|
+ }
|
|
|
+ SwitchMemberVO vo = new SwitchMemberVO();
|
|
|
+ vo.setMemberId(targetParent.getId());
|
|
|
+ vo.setSource("parent");
|
|
|
+ vo.setEffectiveRole("parent");
|
|
|
+ vo.setNickname(targetParent.getNickname());
|
|
|
+ vo.setIsSwitchable(true);
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
throw new RuntimeException("成员不存在或不属于您的家庭");
|
|
|
}
|
|
|
|
|
|
@@ -408,18 +426,34 @@ public class FamilyMemberService {
|
|
|
throw new RuntimeException("只有家庭管理员才能踢出成员");
|
|
|
}
|
|
|
|
|
|
- // 从 family_members 踢出
|
|
|
+ Long familyId = operator.getFamilyId();
|
|
|
+
|
|
|
+ // 从 family_members 查找
|
|
|
FamilyMember member = familyMemberMapper.selectById(memberId);
|
|
|
- if (member == null || !member.getFamilyId().equals(operator.getFamilyId())) {
|
|
|
- throw new RuntimeException("成员不存在");
|
|
|
- }
|
|
|
- // 不能踢出家庭创建者
|
|
|
- if (member.getUserId() != null && Long.valueOf(member.getUserId()).equals(family.getCreatorId())) {
|
|
|
- throw new RuntimeException("不能踢出家庭创建者,请先转让管理员权限");
|
|
|
+ String memberNickname = null;
|
|
|
+ if (member != null && familyId.equals(member.getFamilyId())) {
|
|
|
+ // 不能踢出家庭创建者
|
|
|
+ if (member.getUserId() != null && Long.valueOf(member.getUserId()).equals(family.getCreatorId())) {
|
|
|
+ throw new RuntimeException("不能踢出家庭创建者,请先转让管理员权限");
|
|
|
+ }
|
|
|
+ memberNickname = member.getNickname();
|
|
|
+ } else {
|
|
|
+ // 兼容 source='parent':无 family_members 记录的 users 表家长成员(memberId 即 userId)
|
|
|
+ User targetUser = userMapper.selectById(memberId);
|
|
|
+ member = null;
|
|
|
+ if (targetUser != null && familyId.equals(targetUser.getFamilyId())) {
|
|
|
+ // 不能踢出家庭创建者
|
|
|
+ if (targetUser.getId().equals(family.getCreatorId())) {
|
|
|
+ throw new RuntimeException("不能踢出家庭创建者,请先转让管理员权限");
|
|
|
+ }
|
|
|
+ memberNickname = targetUser.getNickname();
|
|
|
+ } else {
|
|
|
+ throw new RuntimeException("成员不存在");
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- Long oldFamilyId = member.getFamilyId();
|
|
|
- Long targetUserId = member.getUserId();
|
|
|
+ Long oldFamilyId = familyId;
|
|
|
+ Long targetUserId = (member != null) ? member.getUserId() : memberId;
|
|
|
|
|
|
// 收回该家庭发放给该成员的人口券
|
|
|
try {
|
|
|
@@ -428,8 +462,10 @@ public class FamilyMemberService {
|
|
|
log.warn("收回人口券异常: familyId={}, memberId={}", oldFamilyId, memberId, e);
|
|
|
}
|
|
|
|
|
|
- // 清理关系记录
|
|
|
- familyRelationshipService.deleteRelationsForMember(memberId);
|
|
|
+ // 清理关系记录(source='parent' 的 users 表成员无关系记录,跳过)
|
|
|
+ if (member != null) {
|
|
|
+ familyRelationshipService.deleteRelationsForMember(memberId);
|
|
|
+ }
|
|
|
|
|
|
if (targetUserId != null) {
|
|
|
User targetUser = userMapper.selectById(targetUserId);
|
|
|
@@ -441,19 +477,23 @@ public class FamilyMemberService {
|
|
|
if (ownFamilyId == null) {
|
|
|
ownFamilyId = createFamilyForUser(targetUser);
|
|
|
}
|
|
|
- migrateMemberAssets(member, oldFamilyId, ownFamilyId);
|
|
|
- member.setFamilyId(ownFamilyId);
|
|
|
- member.setStatus("active");
|
|
|
- member.setUpdatedAt(new Date());
|
|
|
- familyMemberMapper.updateById(member);
|
|
|
+ if (member != null) {
|
|
|
+ migrateMemberAssets(member, oldFamilyId, ownFamilyId);
|
|
|
+ member.setFamilyId(ownFamilyId);
|
|
|
+ member.setStatus("active");
|
|
|
+ member.setUpdatedAt(new Date());
|
|
|
+ familyMemberMapper.updateById(member);
|
|
|
+ }
|
|
|
targetUser.setFamilyId(ownFamilyId);
|
|
|
targetUser.setUpdatedAt(new Date());
|
|
|
userMapper.updateById(targetUser);
|
|
|
} else {
|
|
|
- // 非微信用户:进入回收箱,资产保留在原家庭记录上
|
|
|
- member.setStatus("recycled");
|
|
|
- member.setUpdatedAt(new Date());
|
|
|
- familyMemberMapper.updateById(member);
|
|
|
+ // 非微信用户:解除家庭关联,资产保留在原家庭记录上
|
|
|
+ if (member != null) {
|
|
|
+ member.setStatus("recycled");
|
|
|
+ member.setUpdatedAt(new Date());
|
|
|
+ familyMemberMapper.updateById(member);
|
|
|
+ }
|
|
|
targetUser.setFamilyId(null);
|
|
|
targetUser.setUpdatedAt(new Date());
|
|
|
userMapper.updateById(targetUser);
|
|
|
@@ -464,7 +504,7 @@ public class FamilyMemberService {
|
|
|
familyMemberMapper.deleteById(memberId);
|
|
|
}
|
|
|
|
|
|
- logMemberAction(oldFamilyId, memberId, "kick", member.getNickname(), null, userId);
|
|
|
+ logMemberAction(oldFamilyId, memberId, "kick", memberNickname, null, userId);
|
|
|
|
|
|
// 方案B:成员被踢出后,重新核算原家庭会员身份
|
|
|
membershipService.reconcileFamilyMembership(oldFamilyId);
|
|
|
@@ -779,9 +819,13 @@ public class FamilyMemberService {
|
|
|
throw new RuntimeException("用户未加入家庭");
|
|
|
}
|
|
|
FamilyMember member = familyMemberMapper.selectById(memberId);
|
|
|
- if (member == null || !member.getFamilyId().equals(user.getFamilyId())) {
|
|
|
+ if (member != null && !member.getFamilyId().equals(user.getFamilyId())) {
|
|
|
throw new RuntimeException("成员不存在或不属于您的家庭");
|
|
|
}
|
|
|
+ // 家长账号(users 表成员)在 family_members 表无记录,走家长更新分支
|
|
|
+ if (member == null) {
|
|
|
+ return updateParentMember(user, memberId, dto);
|
|
|
+ }
|
|
|
|
|
|
// 检查编辑权限:管理员可编辑所有成员;普通用户只能编辑无关联账号的成员
|
|
|
boolean isAdmin = false;
|
|
|
@@ -829,14 +873,100 @@ public class FamilyMemberService {
|
|
|
return toFamilyMemberVO(member);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 更新家长账号(users 表成员)的基本资料。
|
|
|
+ * 家长账号只存在于 users 表(family_id + role=parent),无 family_members 记录,
|
|
|
+ * 因此编辑昵称/性别/生日/头像/手机号直接写 users 表;辈分关系仅 family_members 成员支持。
|
|
|
+ */
|
|
|
+ private FamilyMemberVO updateParentMember(User operator, Long memberId, AddFamilyMemberDTO dto) {
|
|
|
+ Family family = familyMapper.selectById(operator.getFamilyId());
|
|
|
+ boolean isAdmin = family != null && Long.valueOf(operator.getId()).equals(family.getCreatorId());
|
|
|
+ if (!isAdmin) {
|
|
|
+ throw new RuntimeException("仅家庭创建者可编辑该成员");
|
|
|
+ }
|
|
|
+ User parent = userMapper.selectById(memberId);
|
|
|
+ if (parent == null
|
|
|
+ || parent.getFamilyId() == null
|
|
|
+ || !parent.getFamilyId().equals(operator.getFamilyId())
|
|
|
+ || !"parent".equals(parent.getRole())) {
|
|
|
+ throw new RuntimeException("成员不存在或不属于您的家庭");
|
|
|
+ }
|
|
|
+ String oldNickname = parent.getNickname();
|
|
|
+ if (dto.getNickname() != null && !dto.getNickname().trim().isEmpty()) {
|
|
|
+ parent.setNickname(dto.getNickname().trim());
|
|
|
+ }
|
|
|
+ if (dto.getPhone() != null) {
|
|
|
+ parent.setPhone(dto.getPhone());
|
|
|
+ }
|
|
|
+ if (dto.getAvatar() != null) {
|
|
|
+ parent.setAvatar(dto.getAvatar());
|
|
|
+ }
|
|
|
+ if (dto.getGender() != null) {
|
|
|
+ parent.setGender(dto.getGender());
|
|
|
+ }
|
|
|
+ if (dto.getBirthday() != null) {
|
|
|
+ try {
|
|
|
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
|
|
|
+ parent.setBirthday(sdf.parse(dto.getBirthday()));
|
|
|
+ } catch (java.text.ParseException e) {
|
|
|
+ throw new IllegalArgumentException("出生日期格式错误,应为yyyy-MM-dd");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ parent.setUpdatedAt(new Date());
|
|
|
+ userMapper.updateById(parent);
|
|
|
+ logMemberAction(parent.getFamilyId(), parent.getId(), "update", oldNickname, parent.getNickname(), operator.getId());
|
|
|
+ // 返回与成员列表一致的 VO
|
|
|
+ FamilyMemberVO vo = new FamilyMemberVO();
|
|
|
+ vo.setId(parent.getId());
|
|
|
+ vo.setMemberId(parent.getId());
|
|
|
+ vo.setSource("parent");
|
|
|
+ vo.setMemberType("parent");
|
|
|
+ vo.setUserId(parent.getId());
|
|
|
+ vo.setNickname(parent.getNickname());
|
|
|
+ vo.setName(parent.getNickname());
|
|
|
+ vo.setAvatar(parent.getAvatar());
|
|
|
+ vo.setPhone(parent.getPhone());
|
|
|
+ vo.setGender(parent.getGender());
|
|
|
+ vo.setEffectiveRole("parent");
|
|
|
+ vo.setRoleLabel(parent.getFamilyRole() != null ? parent.getFamilyRole() : "家长");
|
|
|
+ if (parent.getBirthday() != null) {
|
|
|
+ vo.setAge(calculateAge(parent.getBirthday()));
|
|
|
+ }
|
|
|
+ vo.setGeneration(0);
|
|
|
+ vo.setIsAdmin(true);
|
|
|
+ vo.setIsSelf(parent.getId().equals(operator.getId()));
|
|
|
+ vo.setIsSwitchable(!parent.getId().equals(operator.getId()));
|
|
|
+ vo.setShowToFamily(parent.getShowToFamily() != null ? parent.getShowToFamily() : 1);
|
|
|
+ vo.setIsSpouse(false);
|
|
|
+ vo.setTotalPoints(parent.getTotalPoints());
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
// ===== 内部方法 =====
|
|
|
|
|
|
+ /**
|
|
|
+ * 解析成员显示名:优先 family_members.nickname,为空时回退到关联 users 表的 nickname。
|
|
|
+ * 被邀请/绑定登录账号的成员,family_members.nickname 可能为空或旧值,而 users.nickname 才是真实名字。
|
|
|
+ */
|
|
|
+ private String resolveMemberNickname(FamilyMember fm) {
|
|
|
+ if (fm.getNickname() != null && !fm.getNickname().trim().isEmpty()) {
|
|
|
+ return fm.getNickname();
|
|
|
+ }
|
|
|
+ if (fm.getUserId() != null) {
|
|
|
+ User u = userMapper.selectById(fm.getUserId());
|
|
|
+ if (u != null && u.getNickname() != null && !u.getNickname().trim().isEmpty()) {
|
|
|
+ return u.getNickname();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return fm.getNickname();
|
|
|
+ }
|
|
|
+
|
|
|
private FamilyMemberVO toFamilyMemberVO(FamilyMember member) {
|
|
|
FamilyMemberVO vo = new FamilyMemberVO();
|
|
|
vo.setId(member.getId());
|
|
|
vo.setSource("family_member");
|
|
|
vo.setUserId(member.getUserId());
|
|
|
- vo.setNickname(member.getNickname());
|
|
|
+ vo.setNickname(resolveMemberNickname(member));
|
|
|
vo.setPhone(member.getPhone());
|
|
|
vo.setAvatar(member.getAvatar());
|
|
|
vo.setGender(member.getGender());
|