From a0e03b646f2c444b0f09687d24e9995112b99017 Mon Sep 17 00:00:00 2001
From: puzhibing <393733352@qq.com>
Date: 星期六, 10 十月 2026 17:30:11 +0800
Subject: [PATCH] 修改bug

---
 ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/service/impl/TGroupCompanyServiceImplTest.java |  638 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 638 insertions(+), 0 deletions(-)

diff --git a/ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/service/impl/TGroupCompanyServiceImplTest.java b/ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/service/impl/TGroupCompanyServiceImplTest.java
index 37a8c06..c14722c 100644
--- a/ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/service/impl/TGroupCompanyServiceImplTest.java
+++ b/ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/service/impl/TGroupCompanyServiceImplTest.java
@@ -2,11 +2,25 @@
 
 import com.baomidou.mybatisplus.core.MybatisConfiguration;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import com.ruoyi.account.api.feignClient.AppUserWhitelistClient;
+import com.ruoyi.account.api.feignClient.ChargingCardClient;
+import com.ruoyi.account.api.feignClient.CompanyAllocateRecordClient;
+import com.ruoyi.account.api.feignClient.VinManagementClient;
+import com.ruoyi.account.api.model.TVinManagement;
+import com.ruoyi.account.api.vo.CompanyAllocateSummaryVO;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.exception.ServiceException;
+import com.ruoyi.common.core.web.domain.AjaxResult;
 import com.ruoyi.other.api.domain.TGroupCompany;
 import com.ruoyi.other.api.model.TCompanyRechargeRecord;
 import com.ruoyi.other.mapper.TCompanyRechargeRecordMapper;
 import com.ruoyi.other.mapper.TFleetMapper;
+import com.ruoyi.order.api.feignClient.ChargingOrderClient;
+import com.ruoyi.system.api.domain.SysUser;
+import com.ruoyi.system.api.feignClient.SysRoleClient;
+import com.ruoyi.system.api.feignClient.SysUserClient;
 import org.apache.ibatis.builder.MapperBuilderAssistant;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -15,6 +29,7 @@
 import org.springframework.test.util.ReflectionTestUtils;
 
 import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
@@ -24,12 +39,19 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
  * 公司详情(车队数量、本月充值金额)统计逻辑单测,不依赖 Spring 容器与数据库。
  */
 class TGroupCompanyServiceImplTest {
+
+    /** 编辑公司用例共享的远程账号客户端 mock,供 verify 使用 */
+    private SysUserClient editUserClient;
+
+    /** 新增公司用例共享的远程账号客户端 mock,供 verify 使用 */
+    private SysUserClient addUserClient;
 
     @BeforeAll
     static void initTableInfo() {
@@ -72,6 +94,89 @@
         assertEquals(0, detail.getFleetCount());
         assertEquals(0, new BigDecimal("0.00").compareTo(detail.getMonthRechargeAmount()));
         assertEquals(2, detail.getMonthRechargeAmount().scale());
+    }
+
+    /**
+     * 公司详情(带充电用户聚合)统计 VIN码车辆数量:与 VIN 列表接口同口径,
+     * 且聚合充电用户时复用同一远程结果,不重复调用远程接口。
+     */
+    @Test
+    void fillsVinCountAndReusesRemoteVinList() {
+        TGroupCompany company = new TGroupCompany();
+        company.setId(1);
+        company.setAccountMode(1);
+        company.setAvailableBalance(new BigDecimal("100.00"));
+
+        TVinManagement vin1 = new TVinManagement();
+        vin1.setId(11L);
+        vin1.setVin("TESTVIN0001");
+        vin1.setBalance(new BigDecimal("10.00"));
+        vin1.setStatus(1);
+        TVinManagement vin2 = new TVinManagement();
+        vin2.setId(12L);
+        vin2.setVin("TESTVIN0002");
+        vin2.setBalance(new BigDecimal("20.00"));
+        vin2.setStatus(1);
+
+        TFleetMapper fleetMapper = Mockito.mock(TFleetMapper.class);
+        Mockito.when(fleetMapper.selectCount(Mockito.any())).thenReturn(0L);
+        Mockito.when(fleetMapper.selectList(Mockito.any())).thenReturn(Collections.emptyList());
+
+        TCompanyRechargeRecordMapper rechargeMapper = Mockito.mock(TCompanyRechargeRecordMapper.class);
+        Mockito.when(rechargeMapper.selectList(Mockito.any())).thenReturn(Collections.emptyList());
+
+        VinManagementClient vinClient = Mockito.mock(VinManagementClient.class);
+        Mockito.when(vinClient.listByCompany(1)).thenReturn(R.ok(Arrays.asList(vin1, vin2)));
+        ChargingCardClient cardClient = Mockito.mock(ChargingCardClient.class);
+        Mockito.when(cardClient.listByCompany(1)).thenReturn(R.ok(Collections.emptyList()));
+        AppUserWhitelistClient whitelistClient = Mockito.mock(AppUserWhitelistClient.class);
+        Mockito.when(whitelistClient.listByCompany(1)).thenReturn(R.ok(Collections.emptyList()));
+
+        TGroupCompanyServiceImpl service = buildService(company, fleetMapper, rechargeMapper);
+        ReflectionTestUtils.setField(service, "vinManagementClient", vinClient);
+        ReflectionTestUtils.setField(service, "chargingCardClient", cardClient);
+        ReflectionTestUtils.setField(service, "appUserWhitelistClient", whitelistClient);
+
+        TGroupCompany detail = service.getDetailWithChargingUsers(1);
+
+        assertEquals(2, detail.getVinCount());
+        assertEquals(2, detail.getChargingUsers().size());
+        // VIN 远程接口仅调用一次:vinCount 统计与充电用户聚合复用同一结果
+        Mockito.verify(vinClient, Mockito.times(1)).listByCompany(1);
+    }
+
+    /**
+     * VIN 远程接口异常降级:vinCount 返回 0,详情主流程不受影响
+     */
+    @Test
+    void vinCountZeroWhenVinRemoteFails() {
+        TGroupCompany company = new TGroupCompany();
+        company.setId(1);
+        company.setAccountMode(1);
+
+        TFleetMapper fleetMapper = Mockito.mock(TFleetMapper.class);
+        Mockito.when(fleetMapper.selectCount(Mockito.any())).thenReturn(0L);
+        Mockito.when(fleetMapper.selectList(Mockito.any())).thenReturn(Collections.emptyList());
+
+        TCompanyRechargeRecordMapper rechargeMapper = Mockito.mock(TCompanyRechargeRecordMapper.class);
+        Mockito.when(rechargeMapper.selectList(Mockito.any())).thenReturn(Collections.emptyList());
+
+        VinManagementClient vinClient = Mockito.mock(VinManagementClient.class);
+        Mockito.when(vinClient.listByCompany(1)).thenThrow(new RuntimeException("remote down"));
+        ChargingCardClient cardClient = Mockito.mock(ChargingCardClient.class);
+        Mockito.when(cardClient.listByCompany(1)).thenReturn(R.ok(Collections.emptyList()));
+        AppUserWhitelistClient whitelistClient = Mockito.mock(AppUserWhitelistClient.class);
+        Mockito.when(whitelistClient.listByCompany(1)).thenReturn(R.ok(Collections.emptyList()));
+
+        TGroupCompanyServiceImpl service = buildService(company, fleetMapper, rechargeMapper);
+        ReflectionTestUtils.setField(service, "vinManagementClient", vinClient);
+        ReflectionTestUtils.setField(service, "chargingCardClient", cardClient);
+        ReflectionTestUtils.setField(service, "appUserWhitelistClient", whitelistClient);
+
+        TGroupCompany detail = service.getDetailWithChargingUsers(1);
+
+        assertEquals(0, detail.getVinCount());
+        assertTrue(detail.getChargingUsers().isEmpty());
     }
 
     @Test
@@ -144,4 +249,537 @@
         record.setArrivalAmount(arrivalAmount == null ? null : new BigDecimal(arrivalAmount));
         return record;
     }
+
+    /**
+     * 添加/编辑企业弹窗不再配置账户模式:未传账户模式时按共享模式(1)建档;
+     * 登录账号由系统自动生成(忽略前端传入值),并随返回体回显
+     */
+    @Test
+    void addCompanyDefaultsAccountModeToSharedWhenAbsent() {
+        TGroupCompany company = new TGroupCompany();
+        company.setAccount("company01");
+        company.setPassword("pwd");
+        company.setName("测试公司");
+
+        TGroupCompanyServiceImpl service = buildAddCompanyService();
+        AjaxResult result = service.addCompany(company);
+        assertEquals(200, code(result));
+
+        ArgumentCaptor<TGroupCompany> captor = ArgumentCaptor.forClass(TGroupCompany.class);
+        Mockito.verify(service).save(captor.capture());
+        assertEquals(1, captor.getValue().getAccountMode());
+        // 账号不可由前端指定:传入值被系统生成值覆盖
+        assertTrue(captor.getValue().getAccount().matches("admin\\d{4}"));
+        assertTrue(!captor.getValue().getAccount().equals("company01"));
+        assertEquals(captor.getValue().getAccount(), String.valueOf(result.get("data")));
+    }
+
+    /**
+     * 显式传入账户模式时不被兜底值覆盖(兼容后台/脚本直接建档)
+     */
+    @Test
+    void addCompanyKeepsExplicitAccountMode() {
+        TGroupCompany company = new TGroupCompany();
+        company.setAccount("company02");
+        company.setPassword("pwd");
+        company.setAccountMode(2);
+
+        TGroupCompanyServiceImpl service = buildAddCompanyService();
+        assertEquals(200, code(service.addCompany(company)));
+
+        ArgumentCaptor<TGroupCompany> captor = ArgumentCaptor.forClass(TGroupCompany.class);
+        Mockito.verify(service).save(captor.capture());
+        assertEquals(2, captor.getValue().getAccountMode());
+        // 账号仍为系统生成值,而非前端传入值
+        assertTrue(captor.getValue().getAccount().matches("admin\\d{4}"));
+        assertTrue(!captor.getValue().getAccount().equals("company02"));
+    }
+
+    /**
+     * 新增公司:登录账号自动生成 admin+4位随机数字,初始密码按登录口径 HMAC-MD5 传输,
+     * 生成账号随返回体回显
+     */
+    @Test
+    void addCompanyGeneratesAccountAndDefaultPassword() {
+        TGroupCompany company = new TGroupCompany();
+        company.setName("测试公司");
+
+        TGroupCompanyServiceImpl service = buildAddCompanyService();
+        AjaxResult result = service.addCompany(company);
+        assertEquals(200, code(result));
+
+        assertTrue(company.getAccount().matches("admin\\d{4}"));
+        assertEquals(company.getAccount(), String.valueOf(result.get("data")));
+
+        ArgumentCaptor<SysUser> userCaptor = ArgumentCaptor.forClass(SysUser.class);
+        Mockito.verify(addUserClient).addSysUser(userCaptor.capture());
+        SysUser created = userCaptor.getValue();
+        assertEquals(company.getAccount(), created.getUserName());
+        // 初始密码 123456:HMAC-MD5(明文, password),与登录/重置链路口径一致
+        assertEquals(hmacMd5Expected("123456", "password"), created.getPassword());
+    }
+
+    /**
+     * 生成候选账号被 sys_user 占用时换号重试,最终以未占用账号建户
+     */
+    @Test
+    void addCompanyRetriesWhenCandidateAccountOccupied() {
+        TGroupCompany company = new TGroupCompany();
+        company.setName("测试公司");
+
+        SysUserClient sysUserClient = Mockito.mock(SysUserClient.class);
+        SysUser occupied = new SysUser();
+        occupied.setUserId(9L);
+        // 首个候选账号被占用,第二个候选通过
+        Mockito.when(sysUserClient.queryUserByUserName(Mockito.any()))
+                .thenReturn(R.ok(occupied), R.ok());
+        Mockito.when(sysUserClient.addSysUser(Mockito.any())).thenReturn(R.ok(100L));
+        Mockito.when(sysUserClient.getSysUser(Mockito.anyLong())).thenReturn(R.ok());
+
+        SysRoleClient sysRoleClient = Mockito.mock(SysRoleClient.class);
+        Mockito.when(sysRoleClient.getRoleIdByRoleKey(Mockito.any())).thenReturn(R.ok(74L));
+
+        TGroupCompanyServiceImpl service = Mockito.spy(new TGroupCompanyServiceImpl());
+        Mockito.doReturn(0L).when(service).count(Mockito.any());
+        Mockito.doReturn(true).when(service).save(Mockito.any());
+        ReflectionTestUtils.setField(service, "sysUserClient", sysUserClient);
+        ReflectionTestUtils.setField(service, "sysRoleClient", sysRoleClient);
+
+        AjaxResult result = service.addCompany(company);
+        assertEquals(200, code(result));
+        assertEquals(company.getAccount(), String.valueOf(result.get("data")));
+
+        // 最终以通过查重的候选账号建户
+        ArgumentCaptor<SysUser> userCaptor = ArgumentCaptor.forClass(SysUser.class);
+        Mockito.verify(sysUserClient).addSysUser(userCaptor.capture());
+        assertEquals(company.getAccount(), userCaptor.getValue().getUserName());
+        assertTrue(userCaptor.getValue().getUserName().matches("admin\\d{4}"));
+    }
+
+    @Test
+    void updateAccountModeRejectsIllegalArgs() {
+        TGroupCompany company = new TGroupCompany();
+        company.setId(1);
+        TGroupCompanyServiceImpl service = buildService(company, Mockito.mock(TFleetMapper.class),
+                Mockito.mock(TCompanyRechargeRecordMapper.class));
+
+        assertEquals(500, code(service.updateAccountMode(null, 1)));
+        assertEquals(500, code(service.updateAccountMode(1, null)));
+        assertEquals(500, code(service.updateAccountMode(1, 3)));
+        Mockito.verify(service, Mockito.never()).updateById(Mockito.any());
+    }
+
+    @Test
+    void updateAccountModeRejectsUnknownCompany() {
+        TGroupCompanyServiceImpl service = buildService(null, Mockito.mock(TFleetMapper.class),
+                Mockito.mock(TCompanyRechargeRecordMapper.class));
+
+        assertEquals(500, code(service.updateAccountMode(9, 2)));
+        Mockito.verify(service, Mockito.never()).updateById(Mockito.any());
+    }
+
+    @Test
+    void updateAccountModeOnlyUpdatesAccountModeColumn() {
+        TGroupCompany company = new TGroupCompany();
+        company.setId(2);
+        ChargingOrderClient orderClient = Mockito.mock(ChargingOrderClient.class);
+        Mockito.when(orderClient.countActiveByCompany(2)).thenReturn(R.ok(0));
+        TGroupCompanyServiceImpl service = buildService(company, Mockito.mock(TFleetMapper.class),
+                Mockito.mock(TCompanyRechargeRecordMapper.class));
+        ReflectionTestUtils.setField(service, "chargingOrderClient", orderClient);
+        Mockito.doReturn(true).when(service).updateById(Mockito.any());
+
+        assertEquals(200, code(service.updateAccountMode(2, 2)));
+
+        ArgumentCaptor<TGroupCompany> captor = ArgumentCaptor.forClass(TGroupCompany.class);
+        Mockito.verify(service).updateById(captor.capture());
+        TGroupCompany update = captor.getValue();
+        assertEquals(2, update.getId());
+        assertEquals(2, update.getAccountMode());
+        // 只更新账户模式列,其余字段保持 null,避免覆盖公司其它资料
+        assertNull(update.getName());
+        assertNull(update.getAccount());
+        assertNull(update.getStatus());
+    }
+
+    /**
+     * 分配→共享切换成功:加回公司余额、切换模式、远程回收、差额一致无需调整
+     */
+    @Test
+    void switchAllocationToSharedRecyclesBalanceAndWritesRecords() {
+        TGroupCompany company = new TGroupCompany();
+        company.setId(2);
+        company.setName("测试公司");
+        company.setAccountMode(2);
+        company.setAvailableBalance(new BigDecimal("100.00"));
+        company.setLockedAmount(new BigDecimal("0.00"));
+
+        CompanyAllocateRecordClient allocateClient = Mockito.mock(CompanyAllocateRecordClient.class);
+        CompanyAllocateSummaryVO summary = new CompanyAllocateSummaryVO();
+        summary.setCompanyId(2);
+        summary.setTotalBalance(new BigDecimal("30.50"));
+        summary.setTotalLocked(new BigDecimal("0.00"));
+        summary.setBalanceUserCount(2);
+        Mockito.when(allocateClient.sumAllocatedBalance(2)).thenReturn(R.ok(summary));
+        Mockito.when(allocateClient.recycleAllocatedBalance(Mockito.eq(2), Mockito.any(), Mockito.eq("测试公司"),
+                Mockito.eq(new BigDecimal("100.00")))).thenReturn(R.ok(new BigDecimal("30.50")));
+
+        ChargingOrderClient orderClient = Mockito.mock(ChargingOrderClient.class);
+        Mockito.when(orderClient.countActiveByCompany(2)).thenReturn(R.ok(0));
+
+        TGroupCompanyServiceImpl service = buildService(company, Mockito.mock(TFleetMapper.class),
+                Mockito.mock(TCompanyRechargeRecordMapper.class));
+        ReflectionTestUtils.setField(service, "companyAllocateRecordClient", allocateClient);
+        ReflectionTestUtils.setField(service, "chargingOrderClient", orderClient);
+        stubLambdaUpdate(service, true);
+
+        assertEquals(200, code(service.updateAccountMode(2, 1)));
+
+        // 远程回收携带回退前公司余额,写入企业账户正数流水与用户负数流水由 account 侧完成
+        Mockito.verify(allocateClient).recycleAllocatedBalance(2, null, "测试公司", new BigDecimal("100.00"));
+        Mockito.verify(orderClient).countActiveByCompany(2);
+    }
+
+    /**
+     * 存在进行中充电订单:拒绝切换且不触发回收
+     */
+    @Test
+    void switchAllocationToSharedRejectsWhenChargingInProgress() {
+        TGroupCompany company = new TGroupCompany();
+        company.setId(3);
+        company.setName("充电中公司");
+        company.setAccountMode(2);
+        company.setAvailableBalance(new BigDecimal("50.00"));
+        company.setLockedAmount(new BigDecimal("0.00"));
+
+        ChargingOrderClient orderClient = Mockito.mock(ChargingOrderClient.class);
+        Mockito.when(orderClient.countActiveByCompany(3)).thenReturn(R.ok(2));
+        CompanyAllocateRecordClient allocateClient = Mockito.mock(CompanyAllocateRecordClient.class);
+
+        TGroupCompanyServiceImpl service = buildService(company, Mockito.mock(TFleetMapper.class),
+                Mockito.mock(TCompanyRechargeRecordMapper.class));
+        ReflectionTestUtils.setField(service, "companyAllocateRecordClient", allocateClient);
+        ReflectionTestUtils.setField(service, "chargingOrderClient", orderClient);
+
+        AjaxResult result = service.updateAccountMode(3, 1);
+
+        assertEquals(500, code(result));
+        assertTrue(String.valueOf(result.get("msg")).contains("正在充电"));
+        Mockito.verify(allocateClient, Mockito.never()).sumAllocatedBalance(Mockito.anyInt());
+        Mockito.verify(allocateClient, Mockito.never()).recycleAllocatedBalance(Mockito.anyInt(), Mockito.any(),
+                Mockito.any(), Mockito.any());
+        Mockito.verify(service, Mockito.never()).updateById(Mockito.any());
+    }
+
+    /**
+     * 存在未释放充电锁定额:拒绝切换且不触发回收
+     */
+    @Test
+    void switchAllocationToSharedRejectsWhenLockedAmountNotReleased() {
+        TGroupCompany company = new TGroupCompany();
+        company.setId(4);
+        company.setName("锁定残留公司");
+        company.setAccountMode(2);
+        company.setAvailableBalance(new BigDecimal("50.00"));
+        company.setLockedAmount(new BigDecimal("0.00"));
+
+        ChargingOrderClient orderClient = Mockito.mock(ChargingOrderClient.class);
+        Mockito.when(orderClient.countActiveByCompany(4)).thenReturn(R.ok(0));
+        CompanyAllocateRecordClient allocateClient = Mockito.mock(CompanyAllocateRecordClient.class);
+        CompanyAllocateSummaryVO summary = new CompanyAllocateSummaryVO();
+        summary.setCompanyId(4);
+        summary.setTotalBalance(new BigDecimal("10.00"));
+        summary.setTotalLocked(new BigDecimal("3.00"));
+        Mockito.when(allocateClient.sumAllocatedBalance(4)).thenReturn(R.ok(summary));
+
+        TGroupCompanyServiceImpl service = buildService(company, Mockito.mock(TFleetMapper.class),
+                Mockito.mock(TCompanyRechargeRecordMapper.class));
+        ReflectionTestUtils.setField(service, "companyAllocateRecordClient", allocateClient);
+        ReflectionTestUtils.setField(service, "chargingOrderClient", orderClient);
+
+        AjaxResult result = service.updateAccountMode(4, 1);
+
+        assertEquals(500, code(result));
+        assertTrue(String.valueOf(result.get("msg")).contains("锁定金额"));
+        Mockito.verify(allocateClient, Mockito.never()).recycleAllocatedBalance(Mockito.anyInt(), Mockito.any(),
+                Mockito.any(), Mockito.any());
+        Mockito.verify(service, Mockito.never()).updateById(Mockito.any());
+    }
+
+    /**
+     * 回收失败:抛业务异常并回滚公司余额与模式(避免资金总账丢失)
+     */
+    @Test
+    void switchAllocationToSharedRollsBackWhenRecycleFails() {
+        TGroupCompany company = new TGroupCompany();
+        company.setId(5);
+        company.setName("回滚公司");
+        company.setAccountMode(2);
+        company.setAvailableBalance(new BigDecimal("80.00"));
+        company.setLockedAmount(new BigDecimal("0.00"));
+
+        ChargingOrderClient orderClient = Mockito.mock(ChargingOrderClient.class);
+        Mockito.when(orderClient.countActiveByCompany(5)).thenReturn(R.ok(0));
+        CompanyAllocateRecordClient allocateClient = Mockito.mock(CompanyAllocateRecordClient.class);
+        CompanyAllocateSummaryVO summary = new CompanyAllocateSummaryVO();
+        summary.setCompanyId(5);
+        summary.setTotalBalance(new BigDecimal("20.00"));
+        summary.setTotalLocked(new BigDecimal("0.00"));
+        Mockito.when(allocateClient.sumAllocatedBalance(5)).thenReturn(R.ok(summary));
+        Mockito.when(allocateClient.recycleAllocatedBalance(Mockito.eq(5), Mockito.any(), Mockito.any(),
+                Mockito.any())).thenReturn(R.fail("回收失败"));
+
+        TGroupCompanyServiceImpl service = buildService(company, Mockito.mock(TFleetMapper.class),
+                Mockito.mock(TCompanyRechargeRecordMapper.class));
+        ReflectionTestUtils.setField(service, "companyAllocateRecordClient", allocateClient);
+        ReflectionTestUtils.setField(service, "chargingOrderClient", orderClient);
+        stubLambdaUpdate(service, true);
+
+        assertThrows(ServiceException.class, () -> service.updateAccountMode(5, 1));
+        // 加回与回滚各一次:第二次(回滚)将模式恢复为分配模式
+        Mockito.verify(service, Mockito.times(2)).lambdaUpdate();
+    }
+
+    /**
+     * 共享→分配无进行中充电订单:通过充电校验后仅切换模式,不做任何资金操作
+     */
+    @Test
+    void switchSharedToAllocationChecksChargingThenOnlyUpdatesMode() {
+        TGroupCompany company = new TGroupCompany();
+        company.setId(6);
+        company.setName("切分配公司");
+        company.setAccountMode(1);
+        company.setAvailableBalance(new BigDecimal("60.00"));
+
+        ChargingOrderClient orderClient = Mockito.mock(ChargingOrderClient.class);
+        Mockito.when(orderClient.countActiveByCompany(6)).thenReturn(R.ok(0));
+        CompanyAllocateRecordClient allocateClient = Mockito.mock(CompanyAllocateRecordClient.class);
+
+        TGroupCompanyServiceImpl service = buildService(company, Mockito.mock(TFleetMapper.class),
+                Mockito.mock(TCompanyRechargeRecordMapper.class));
+        ReflectionTestUtils.setField(service, "companyAllocateRecordClient", allocateClient);
+        ReflectionTestUtils.setField(service, "chargingOrderClient", orderClient);
+        Mockito.doReturn(true).when(service).updateById(Mockito.any());
+
+        assertEquals(200, code(service.updateAccountMode(6, 2)));
+
+        Mockito.verify(orderClient).countActiveByCompany(6);
+        Mockito.verify(allocateClient, Mockito.never()).sumAllocatedBalance(Mockito.anyInt());
+        Mockito.verify(allocateClient, Mockito.never()).recycleAllocatedBalance(Mockito.anyInt(), Mockito.any(),
+                Mockito.any(), Mockito.any());
+        Mockito.verify(service).updateById(Mockito.any(TGroupCompany.class));
+    }
+
+    /**
+     * 共享→分配存在进行中充电订单:拒绝切换且不改模式
+     */
+    @Test
+    void switchSharedToAllocationRejectsWhenChargingInProgress() {
+        TGroupCompany company = new TGroupCompany();
+        company.setId(7);
+        company.setName("充电中切分配公司");
+        company.setAccountMode(1);
+        company.setAvailableBalance(new BigDecimal("60.00"));
+
+        ChargingOrderClient orderClient = Mockito.mock(ChargingOrderClient.class);
+        Mockito.when(orderClient.countActiveByCompany(7)).thenReturn(R.ok(3));
+        CompanyAllocateRecordClient allocateClient = Mockito.mock(CompanyAllocateRecordClient.class);
+
+        TGroupCompanyServiceImpl service = buildService(company, Mockito.mock(TFleetMapper.class),
+                Mockito.mock(TCompanyRechargeRecordMapper.class));
+        ReflectionTestUtils.setField(service, "companyAllocateRecordClient", allocateClient);
+        ReflectionTestUtils.setField(service, "chargingOrderClient", orderClient);
+
+        AjaxResult result = service.updateAccountMode(7, 2);
+
+        assertEquals(500, code(result));
+        assertTrue(String.valueOf(result.get("msg")).contains("正在充电"));
+        Mockito.verify(allocateClient, Mockito.never()).sumAllocatedBalance(Mockito.anyInt());
+        Mockito.verify(service, Mockito.never()).updateById(Mockito.any());
+    }
+
+    /**
+     * 打桩 lambdaUpdate 链:eq/setSql/set 返回自身,update 返回指定结果
+     */
+    @SuppressWarnings("unchecked")
+    private void stubLambdaUpdate(TGroupCompanyServiceImpl service, boolean updateResult) {
+        LambdaUpdateChainWrapper<TGroupCompany> chain = Mockito.mock(LambdaUpdateChainWrapper.class);
+        Mockito.when(chain.eq(Mockito.any(), Mockito.any())).thenReturn(chain);
+        Mockito.when(chain.setSql(Mockito.anyString())).thenReturn(chain);
+        Mockito.when(chain.set(Mockito.any(), Mockito.any())).thenReturn(chain);
+        Mockito.when(chain.update()).thenReturn(updateResult);
+        Mockito.doReturn(chain).when(service).lambdaUpdate();
+    }
+
+    /**
+     * 构建关注建档字段的 spy:远程账号相关调用全部打桩,不依赖 Spring 容器与数据库
+     */
+    private TGroupCompanyServiceImpl buildAddCompanyService() {
+        SysUserClient sysUserClient = Mockito.mock(SysUserClient.class);
+        Mockito.when(sysUserClient.queryUserByUserName(Mockito.any())).thenReturn(R.ok());
+        Mockito.when(sysUserClient.addSysUser(Mockito.any())).thenReturn(R.ok(100L));
+        Mockito.when(sysUserClient.getSysUser(Mockito.anyLong())).thenReturn(R.ok());
+
+        SysRoleClient sysRoleClient = Mockito.mock(SysRoleClient.class);
+        Mockito.when(sysRoleClient.getRoleIdByRoleKey(Mockito.any())).thenReturn(R.ok(74L));
+
+        TGroupCompanyServiceImpl service = Mockito.spy(new TGroupCompanyServiceImpl());
+        Mockito.doReturn(0L).when(service).count(Mockito.any());
+        Mockito.doReturn(true).when(service).save(Mockito.any());
+        ReflectionTestUtils.setField(service, "sysUserClient", sysUserClient);
+        ReflectionTestUtils.setField(service, "sysRoleClient", sysRoleClient);
+        this.addUserClient = sysUserClient;
+        return service;
+    }
+
+    /**
+     * 按登录口径计算 HMAC-MD5(明文, key) 的期望值(小写 Hex),用于断言初始密码传输值
+     */
+    private String hmacMd5Expected(String text, String key) {
+        try {
+            javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacMD5");
+            mac.init(new javax.crypto.spec.SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacMD5"));
+            byte[] bytes = mac.doFinal(text.getBytes(StandardCharsets.UTF_8));
+            StringBuilder hex = new StringBuilder(bytes.length * 2);
+            for (byte b : bytes) {
+                hex.append(Character.forDigit((b >> 4) & 0xF, 16));
+                hex.append(Character.forDigit(b & 0xF, 16));
+            }
+            return hex.toString();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 编辑公司修改登录账号:按旧账号定位用户后把 userName 更新为新账号,
+     * 不误走补建分支(避免双账号残留)
+     */
+    @Test
+    void editCompanyUpdatesUserNameWhenAccountChanged() {
+        TGroupCompany old = persistedCompany("company01");
+        TGroupCompany edit = editCompanyBody();
+        edit.setAccount("company02");
+
+        TGroupCompanyServiceImpl service = buildEditCompanyService(old, "company01", owner(100L), "company02", null);
+
+        assertEquals(200, code(service.editCompany(edit)));
+
+        ArgumentCaptor<SysUser> captor = ArgumentCaptor.forClass(SysUser.class);
+        Mockito.verify(editUserClient).updateSysUser(captor.capture());
+        assertEquals("company02", captor.getValue().getUserName());
+        Mockito.verify(editUserClient, Mockito.never()).addSysUser(Mockito.any());
+        assertEquals("company02", edit.getAccount());
+    }
+
+    /**
+     * 编辑公司修改登录密码:调用 resetPassword 且仅携带 userId 与密码(前端已 HMAC-MD5)
+     */
+    @Test
+    void editCompanyResetsPasswordWhenProvided() {
+        TGroupCompany old = persistedCompany("company01");
+        TGroupCompany edit = editCompanyBody();
+        edit.setAccount("company01");
+        edit.setPassword("hmac-value");
+
+        TGroupCompanyServiceImpl service = buildEditCompanyService(old, "company01", owner(100L), null, null);
+
+        assertEquals(200, code(service.editCompany(edit)));
+
+        ArgumentCaptor<SysUser> pwdCaptor = ArgumentCaptor.forClass(SysUser.class);
+        Mockito.verify(editUserClient).resetPassword(pwdCaptor.capture());
+        assertEquals(Long.valueOf(100L), pwdCaptor.getValue().getUserId());
+        assertEquals("hmac-value", pwdCaptor.getValue().getPassword());
+
+        // 资料更新调用不携带密码,避免哈希/脱敏值写回
+        ArgumentCaptor<SysUser> userCaptor = ArgumentCaptor.forClass(SysUser.class);
+        Mockito.verify(editUserClient).updateSysUser(userCaptor.capture());
+        assertNull(userCaptor.getValue().getPassword());
+    }
+
+    /**
+     * 新账号已被其他用户占用:返回错误且不更新任何数据
+     */
+    @Test
+    void editCompanyRejectsAccountOccupiedByOtherUser() {
+        TGroupCompany old = persistedCompany("company01");
+        TGroupCompany edit = editCompanyBody();
+        edit.setAccount("company02");
+
+        TGroupCompanyServiceImpl service = buildEditCompanyService(old, "company01", owner(100L),
+                "company02", owner(999L));
+
+        assertEquals(500, code(service.editCompany(edit)));
+
+        Mockito.verify(editUserClient, Mockito.never()).updateSysUser(Mockito.any());
+        Mockito.verify(editUserClient, Mockito.never()).resetPassword(Mockito.any());
+        Mockito.verify(service, Mockito.never()).updateById(Mockito.any());
+    }
+
+    /**
+     * 编辑时密码留空:不调用 resetPassword,密码保持不变
+     */
+    @Test
+    void editCompanyKeepsPasswordWhenAbsent() {
+        TGroupCompany old = persistedCompany("company01");
+        TGroupCompany edit = editCompanyBody();
+        edit.setAccount("company01");
+
+        TGroupCompanyServiceImpl service = buildEditCompanyService(old, "company01", owner(100L), null, null);
+
+        assertEquals(200, code(service.editCompany(edit)));
+
+        Mockito.verify(editUserClient, Mockito.never()).resetPassword(Mockito.any());
+        Mockito.verify(editUserClient).updateSysUser(Mockito.any(SysUser.class));
+    }
+
+    private TGroupCompany persistedCompany(String account) {
+        TGroupCompany company = new TGroupCompany();
+        company.setId(1);
+        company.setAccount(account);
+        company.setName("旧公司名");
+        return company;
+    }
+
+    private TGroupCompany editCompanyBody() {
+        TGroupCompany company = new TGroupCompany();
+        company.setId(1);
+        company.setName("新公司名");
+        company.setContactPhone("13800000000");
+        return company;
+    }
+
+    private SysUser owner(Long userId) {
+        SysUser user = new SysUser();
+        user.setUserId(userId);
+        user.setPassword("hashed");
+        return user;
+    }
+
+    /**
+     * 构建编辑公司场景的 spy:getById 返回库里旧记录,updateById 打桩;
+     * queryUserByUserName 按账号返回不同用户(newAccountUser 非 null 时用于模拟新账号占用)
+     */
+    private TGroupCompanyServiceImpl buildEditCompanyService(TGroupCompany old, String oldAccount, SysUser oldAccountUser,
+                                                             String newAccount, SysUser newAccountUser) {
+        SysUserClient sysUserClient = Mockito.mock(SysUserClient.class);
+        Mockito.when(sysUserClient.queryUserByUserName(oldAccount)).thenReturn(R.ok(oldAccountUser));
+        if (newAccount != null) {
+            Mockito.when(sysUserClient.queryUserByUserName(newAccount)).thenReturn(R.ok(newAccountUser));
+        }
+        Mockito.when(sysUserClient.updateSysUser(Mockito.any())).thenReturn(R.ok(true));
+        Mockito.when(sysUserClient.resetPassword(Mockito.any())).thenReturn(R.ok());
+        this.editUserClient = sysUserClient;
+
+        TGroupCompanyServiceImpl service = Mockito.spy(new TGroupCompanyServiceImpl());
+        Mockito.doReturn(old).when(service).getById(Mockito.any());
+        Mockito.doReturn(true).when(service).updateById(Mockito.any());
+        Mockito.doReturn(0L).when(service).count(Mockito.any());
+        ReflectionTestUtils.setField(service, "sysUserClient", sysUserClient);
+        return service;
+    }
+
+    private int code(AjaxResult result) {
+        return ((Number) result.get("code")).intValue();
+    }
 }

--
Gitblit v1.7.1