From e429c291995f3f20184079e480031533ae5c03e6 Mon Sep 17 00:00:00 2001
From: zjk <852185829@qq.com>
Date: 星期五, 18 九月 2026 17:24:28 +0800
Subject: [PATCH] refactor(全项目): 综合优化全端业务与修复各类问题

---
 tcm-training-admin-backend/src/test/java/com/tcm/training/admin/member/service/UserAppServiceTest.java |  195 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 195 insertions(+), 0 deletions(-)

diff --git a/tcm-training-admin-backend/src/test/java/com/tcm/training/admin/member/service/UserAppServiceTest.java b/tcm-training-admin-backend/src/test/java/com/tcm/training/admin/member/service/UserAppServiceTest.java
index 21d910b..2c96067 100644
--- a/tcm-training-admin-backend/src/test/java/com/tcm/training/admin/member/service/UserAppServiceTest.java
+++ b/tcm-training-admin-backend/src/test/java/com/tcm/training/admin/member/service/UserAppServiceTest.java
@@ -6,7 +6,10 @@
 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.tcm.training.admin.common.PageResponse;
+import com.tcm.training.admin.live.entity.Clinic;
 import com.tcm.training.admin.live.mapper.ClinicMapper;
+import com.tcm.training.admin.member.dto.UserBatchClinicUpdateRequest;
+import com.tcm.training.admin.member.dto.UserUpdateRequest;
 import com.tcm.training.admin.member.entity.AppUser;
 import com.tcm.training.admin.member.mapper.UserMapper;
 import com.tcm.training.admin.member.row.UserDetailRow;
@@ -17,6 +20,9 @@
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
 import org.mockito.ArgumentCaptor;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
@@ -24,7 +30,9 @@
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 
 import java.time.LocalDateTime;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -133,4 +141,191 @@
         assertTrue(wrapperCaptor.getValue().getParamNameValuePairs().values().stream().allMatch(value -> value == null));
         assertEquals(Long.valueOf(12L), result.getId());
     }
+
+    @ParameterizedTest
+    @CsvSource({"10, 100", "20, 100", "20, "})
+    void updateShouldSynchronizeClinicBindingIncludingStaleRelations(Long oldClinicId, Long oldDoctorId) {
+        AppUser user = prepareUserUpdate(0);
+        user.setClinicId(oldClinicId);
+        user.setSuperiorClinicUserId(oldDoctorId);
+        prepareClinicAccounts(1);
+
+        userAppService.update(12L, updateRequest(20L));
+
+        verify(userMapper).updateById(user);
+        assertClinicBinding(Collections.singletonList(12L), 20L, 200L);
+    }
+
+    @Test
+    void updateShouldExplicitlyClearBothClinicFields() {
+        AppUser user = prepareUserUpdate(0);
+        user.setClinicId(10L);
+        user.setSuperiorClinicUserId(100L);
+
+        userAppService.update(12L, updateRequest(null));
+
+        assertClinicBinding(Collections.singletonList(12L), null, null);
+        verify(clinicMapper, never()).selectById(any());
+    }
+
+    @Test
+    void updateShouldPreserveDoctorAccountClinic() {
+        AppUser user = prepareUserUpdate(1);
+        user.setClinicId(10L);
+
+        userAppService.update(12L, updateRequest(null));
+
+        assertEquals(Long.valueOf(10L), user.getClinicId());
+        verify(userMapper).updateById(user);
+        verify(userMapper, never()).update(any(), any());
+    }
+
+    @Test
+    void batchUpdateShouldSynchronizeBothClinicFields() {
+        prepareClinicAccounts(1);
+        when(userMapper.selectCount(any())).thenReturn(2L);
+
+        userAppService.batchUpdateClinic(batchRequest());
+
+        assertClinicBinding(Arrays.asList(12L, 13L), 20L, 200L);
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = {0, 2})
+    void updateShouldRejectMissingOrAmbiguousClinicAccount(int accountCount) {
+        AppUser user = new AppUser();
+        user.setId(12L);
+        user.setType(0);
+        when(userMapper.selectOne(any())).thenReturn(user);
+        prepareClinicAccounts(accountCount);
+
+        IllegalArgumentException error = assertThrows(IllegalArgumentException.class,
+                () -> userAppService.update(12L, updateRequest(20L)));
+
+        assertEquals(accountCount == 0 ? "目标诊所尚未关联诊所账号,无法绑定患者"
+                : "目标诊所关联多个账号,请先确认患者归属", error.getMessage());
+        assertNoUserWrite();
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = {0, 2})
+    void batchUpdateShouldRejectMissingOrAmbiguousClinicAccount(int accountCount) {
+        prepareClinicAccounts(accountCount);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> userAppService.batchUpdateClinic(batchRequest()));
+
+        assertNoUserWrite();
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    void batchUpdateShouldRejectMissingOrDeletedClinic(boolean deleted) {
+        if (deleted) {
+            Clinic clinic = new Clinic();
+            clinic.setId(20L);
+            clinic.setDeleted(1);
+            when(clinicMapper.selectById(20L)).thenReturn(clinic);
+        }
+
+        IllegalArgumentException error = assertThrows(IllegalArgumentException.class,
+                () -> userAppService.batchUpdateClinic(batchRequest()));
+
+        assertEquals("绑定诊所不存在", error.getMessage());
+        assertNoUserWrite();
+    }
+
+    @Test
+    void batchUpdateShouldRejectUsersOutsidePersonalUserScope() {
+        prepareClinicAccounts(1);
+        when(userMapper.selectCount(any())).thenAnswer(invocation -> {
+            com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<AppUser> query =
+                    invocation.getArgument(0);
+            assertTrue(query.getSqlSegment().contains("type ="));
+            assertTrue(query.getParamNameValuePairs().containsValue(0));
+            return 1L;
+        });
+
+        IllegalArgumentException error = assertThrows(IllegalArgumentException.class,
+                () -> userAppService.batchUpdateClinic(batchRequest()));
+
+        assertEquals("部分用户不存在或不是普通用户", error.getMessage());
+        assertNoUserWrite();
+    }
+
+    private AppUser prepareUserUpdate(int type) {
+        AppUser user = new AppUser();
+        user.setId(12L);
+        user.setType(type);
+        user.setDeleted(0);
+        // 第一次查询用户,第二次检查手机号冲突。
+        when(userMapper.selectOne(any())).thenReturn(user, null);
+        UserDetailRow detail = new UserDetailRow();
+        detail.setId(12L);
+        when(userMapper.selectUserDetail(12L)).thenReturn(detail);
+        return user;
+    }
+
+    private void prepareClinicAccounts(int count) {
+        Clinic clinic = new Clinic();
+        clinic.setId(20L);
+        clinic.setDeleted(0);
+        when(clinicMapper.selectById(20L)).thenReturn(clinic);
+        AppUser doctor = new AppUser();
+        doctor.setId(200L);
+        doctor.setType(1);
+        doctor.setClinicId(20L);
+        when(userMapper.selectList(any())).thenAnswer(invocation -> {
+            com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<AppUser> query =
+                    invocation.getArgument(0);
+            String sql = query.getSqlSegment();
+            assertTrue(sql.contains("clinic_id =") && sql.contains("type =") && sql.contains("deleted ="));
+            assertTrue(query.getParamNameValuePairs().values().containsAll(Arrays.asList(20L, 1, 0)));
+            return Collections.nCopies(count, doctor);
+        });
+    }
+
+    private UserUpdateRequest updateRequest(Long clinicId) {
+        UserUpdateRequest request = new UserUpdateRequest();
+        request.setNickname("测试用户");
+        request.setPhone("13800000000");
+        request.setStatus(1);
+        request.setClinicId(clinicId);
+        return request;
+    }
+
+    private UserBatchClinicUpdateRequest batchRequest() {
+        UserBatchClinicUpdateRequest request = new UserBatchClinicUpdateRequest();
+        request.setUserIds(Arrays.asList(12L, 13L));
+        request.setClinicId(20L);
+        return request;
+    }
+
+    private void assertNoUserWrite() {
+        verify(userMapper, never()).updateById(any(AppUser.class));
+        verify(userMapper, never()).update(any(), any());
+    }
+
+    private void assertClinicBinding(List<Long> userIds, Long clinicId, Long doctorId) {
+        ArgumentCaptor<LambdaUpdateWrapper<AppUser>> captor = ArgumentCaptor.forClass(LambdaUpdateWrapper.class);
+        verify(userMapper).update(isNull(), captor.capture());
+        LambdaUpdateWrapper<AppUser> update = captor.getValue();
+        // 按列名核对绑定参数,避免诊所 ID 和诊所账号 ID 被误用。
+        String[] assignments = update.getSqlSet().split(",");
+        assertEquals(2, assignments.length);
+        assertAssignment(update, assignments[0], "clinic_id", clinicId);
+        assertAssignment(update, assignments[1], "superior_clinic_user_id", doctorId);
+        String sql = update.getSqlSegment();
+        assertTrue(sql.contains("id IN") && sql.contains("type =") && sql.contains("deleted ="));
+        assertTrue(update.getParamNameValuePairs().values().containsAll(userIds));
+        assertTrue(update.getParamNameValuePairs().containsValue(0));
+    }
+
+    private void assertAssignment(LambdaUpdateWrapper<AppUser> update, String assignment,
+                                  String column, Long expectedValue) {
+        assertTrue(assignment.startsWith(column + "="));
+        String parameter = assignment.substring(assignment.lastIndexOf('.') + 1, assignment.length() - 1);
+        assertTrue(update.getParamNameValuePairs().containsKey(parameter));
+        assertEquals(expectedValue, update.getParamNameValuePairs().get(parameter));
+    }
 }

--
Gitblit v1.7.1