| doc/耗时统计.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/service/impl/TFleetServiceImplTest.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
doc/耗时统计.md
@@ -23,3 +23,4 @@ | 2026-09-29(开始时间未记录,估算) | 2026-09-29 10:52:50 | 未记录(估算约 5400 秒,跨会话续作) | 充电时间超过授权时间主动停止充电的判断最好提前1分钟校验,避免数据传输延迟导致的导出实现(续作:VIN 码充电 5 项需求落码) | 是:先只读分析并输出修改方案(含提前量常量、边界保护口径),用户确认「开始执行」后落码 | 完成 VIN 码充电 5 项需求:①余额按企业账户模式(共享锁企业/分配锁 VIN)实扣冻结;②授权站点与时段(含指定电价阶段)校验 + 授权到期提前 60 秒主动停充;③按企业优惠设置(服务费比例/计费模板单价)优惠计费并落优惠金额;④结算解锁扣费退回 + 分配记录消费流水(按订单幂等);⑤补偿/关单路径统一释放锁定;前端分配记录新增「类型」列;同步删除 IoT 下发硬编码余额 100 | 服务端 22 个:ruoyi-api-order/TVinChargingOrder(5 新列)、TVinChargingOrderAccountingStrategy(3 新列)、ruoyi-api-account/TCompanyAllocateRecord(change_type/charging_order_code/USER_TYPE_COMPANY)、TGroupCompany、TVinManagement、4 个新 Feign 客户端与 fallback、3 个 inner 接口控制器,ruoyi-service/ruoyi-order/TVinChargingOrderServiceImpl(授权校验/锁定/停充/优惠结算/流水)、ruoyi-service/ruoyi-integration/IotMessageListener、sql/20260929_vin_charge_lock_{other,account,order}.sql;测试类 TVinChargingOrderCompanyFleetTest;管理后台 2 个:allocateRecord.js、AllocateRecordModal.jsx 与其单测 | 后端 order 192/192、account 159/159、integration 2/2 通过(surefire 2.22.2);other 128 条中 4 条、chargingPile 40 条中 1 条为历史失败(Emind/EnergyReport/导出列顺序,工作区未改相关实现,非本次引入);前端 Jest 13/13;三方测试库执行 3 个 DDL 脚本并回查 12 项列全部存在、中文注释无乱码、分片表下推查询通过 | 开始时间因跨会话续作未记录,耗时按估算标注;测试库(内网 111:8066 / 外网 221.182.45.100:8066 同库)已执行 DDL,其他环境发版前须先执行三个 SQL 脚本 | | 2026-09-29 19:22:00(估算) | 2026-09-29 19:47:00(估算) | 约 1500(估算) | VIN码管理页面字段与筛选改造(前一会话续作:补充「导入模板也更新,添加和编辑去掉备注字段」) | 是:先只读复核 DTO/Mapper/Service/前端与三个测试类,输出含字段矩阵、影响面、回归方案的完整方案,用户确认「开始执行」后落码 | ①列表新增账户模式/可用余额/超额/账户状态,移除初始分配额度与备注列,手机号改「联系人手机号」且取消筛选,启用状态改「启停状态」;②新增创建时间区间筛选(beginTime/endTime,闭区间);③行内新增解绑、工具栏顺序调整为添加车辆/批量解绑/额度分配/导入/导出/删除;④导出改 10 列并回填车队名与账户信息;⑤导入模板改 5 列(去掉备注);⑥添加/编辑弹窗去掉备注,后端不再接收/写入 remark(编辑不再清空历史备注),DB 列保留无 DDL | 服务端 7 个:ruoyi-api-account/VinManagementQueryDTO、VinManagementExcelDTO、VinManagementSaveDTO、TVinManagement;ruoyi-account/TVinManagementMapper.xml、TVinManagementServiceImpl;测试 3 个:TVinManagementServiceImplTest、VinManagementMapperStructureTest、TVinManagementControllerTest;管理后台 4 个:pages/vin/index.jsx、queryParams.js(新增)、queryParams.test.js(新增)、components/ImportModal.jsx | 后端 surefire 2.22.2:VIN 三个测试类 70/70(controller 15、mapper 2、service 53)、account 模块全量 172/172 通过;依赖模块 ruoyi-other、ruoyi-order test-compile BUILD SUCCESS;前端 Jest 7/7、`npm run tsc` 退出码 0、@babel/parser 语法校验 5 个改动文件全部通过;IDE 诊断 0 错误 | 开始时间未精确记录,按估算标注;未执行真实数据库与页面联调,功能验证为代码走查+编译;旧模板(含手机号/备注/启用状态列头)只能识别 VIN码/车牌号/所属公司,需用新模板;导入仍按历史逻辑只写公司名称文本、不关联公司ID,账户类列为空;接口 phone 查询条件保留兼容 | 2026-09-30 09:35:00(估算) | 2026-09-30 10:05:00(估算) | 约 1800(估算) | 刷卡充电 4 项期望改造:点2 账户结算与锁定额度释放幂等、点5 无计费快照禁止建单、点7 授权停充提前量固定 60 秒、点8 变动流水归位(口径A) | 是:先只读定位 4 项精确落点并完成三项决策确认,输出含改动清单、DDL、风险与回归方案的完整方案,用户确认「开始执行」后落码 | ①共享模式结算传订单号启用账户侧幂等(账户侧按订单号+变更类型写企业消费流水),订单侧不再代写共享流水,分配模式与历史卡扣款仍记卡账户;②补偿扫描改卡侧+企业侧流水双判据,任一侧查询失败即不回退(避免重复扣款);③订单表新增 lock_released,释放锁定额度先条件更新抢占标记,失败回滚标记供重试;④建单先写计费策略快照,无策略/快照异常回滚删单并回告启动失败(fail(null));⑤授权停充提前量由 min(60, 授权时长/2) 改为 min(60, 授权时长),刷卡/VIN/白名单三链路统一 | 服务端 4 个:ruoyi-api-order/TCardChargingOrder(新增 lock_released)、ruoyi-order/TCardChargingOrderServiceImpl(建单顺序、解锁幂等、结算流水、补偿双判据)、ruoyi-order/ChargeAccountSupport(提前量口径、已结算订单号查询)、ruoyi-order/TVinChargingOrderServiceImpl(注释口径同步);SQL 1 个:sql/20260930_card_order_lock_released.sql(分片表 DDL,未执行);测试 2 个:TCardChargingOrderAmountFieldsTest(新增 6 用例并补建单快照 mock)、TVinChargingOrderAuthorizeStopTest(提前量新口径) | 后端 surefire 2.22.2(先 test-compile 再 surefire):受影响 5 个测试类 68/68 通过;ruoyi-order 模块全量 231/231 通过;ruoyi-api-order + ruoyi-order 编译通过;IDE 诊断 0 错误 | DDL 未在测试库执行(待授权,脚本已备好);白名单链路结算流水仍由订单侧写入、未纳入本次口径A范围;建议项8(公司余额更新与流水写入强一致)未纳入;开始与结束时间未精确记录,按估算标注 | | 2026-09-30 10:50:00(估算,本次收尾会话) | 2026-09-30 11:15:04 | 约 1500(估算,仅本次收尾会话) | 继续(公司账号体系改造续作收尾:company_admin 内置角色替代 company、四模块公司级数据隔离的回归测试补齐与耗时归档) | 是:前序会话已输出完整改造方案并获「开始执行」授权,本次为同任务续作收尾,直接执行剩余回归测试项 | ①TFleetServiceImplTest 追加 2 条公司隔离用例(公司账号导入车队强制归属本公司、平台账号无公司锁定时所属公司必填),并补 @BeforeAll 初始化 TFleet 表元数据支撑 lambdaQuery;②复核确认 TChargingCardServiceImplTest 3 条卡片隔离用例、TVinManagementServiceImplTest 3 条 VIN 隔离用例、TAppUserWhitelistServiceImplTest 隔离用例均已就位;③重跑 ruoyi-account / ruoyi-other 全量单测与前端生产构建 | 测试 1 个:ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/service/impl/TFleetServiceImplTest.java(imports + @BeforeAll + 2 用例);doc/耗时统计.md | 后端 surefire 2.22.2(先 test-compile 再 surefire):ruoyi-account 全量 189/189 通过 BUILD SUCCESS(TChargingCardServiceImplTest 61、TVinManagementServiceImplTest 56、TAppUserWhitelistServiceImplTest 23、PartnerAppUserAccessServiceTest 7);ruoyi-other 全量 142 条中 TFleetServiceImplTest 20/20(含新增 2 条),仅 4 条历史失败(Emind 3 + EnergyReport 1,与既有登记一致非本次引入);前端 npm run build 成功(Webpack Compiled successfully in 1.94m,dist 123 个 js,中文 \u 转义与既有构建行为一致);IDE lint 0 错误 | 公司账号体系改造主体(角色/绑定/四模块隔离/SQL 测试库验证/前端改造及三个测试类依赖注入修复、TAppUserWhitelistServiceImpl 补字段、TFleetServiceImpl 补 import)在前序会话完成、耗时未记录;sql/20260930_company_admin_role_menu.sql 仅测试库已执行,其他环境发版前须先跑;开始时间为估算(会话首条命令时间不可得) | ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/service/impl/TFleetServiceImplTest.java
@@ -1,5 +1,7 @@ package com.ruoyi.other.service.impl; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.account.api.dto.VinManagementQueryDTO; import com.ruoyi.account.api.feignClient.FleetUnbindClient; @@ -10,10 +12,15 @@ import com.ruoyi.other.api.domain.TChargeAuthorization; import com.ruoyi.other.api.domain.TFleet; import com.ruoyi.other.api.domain.TGroupCompany; import com.ruoyi.other.api.dto.FleetImportDTO; import com.ruoyi.other.api.feignClient.FleetOrderAggregateClient; import com.ruoyi.other.api.vo.FleetOrderAggregateVO; import com.ruoyi.other.mapper.TChargeAuthorizationMapper; import com.ruoyi.other.mapper.TFleetMapper; import com.ruoyi.other.service.CompanyAccountAccessService; import com.ruoyi.other.service.TGroupCompanyService; import org.apache.ibatis.builder.MapperBuilderAssistant; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; @@ -36,6 +43,13 @@ * 远程客户端和 Mapper 以 Mock 替代,不依赖 Spring 容器与数据库。 */ class TFleetServiceImplTest { @BeforeAll static void initTableInfo() { // MP 的 lambdaQuery 会通过 TableInfoHelper 取表元数据解析 lambda 列名, // 单元测试不启动 Spring,需要手动初始化 TableInfo,否则抛 MybatisPlusException TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), TFleet.class); } @Test void passesFleetIdAndPagingToVinService() { @@ -379,6 +393,52 @@ Mockito.verify(service, Mockito.never()).removeByIds(Mockito.anyList()); } @Test void importFleetsForcesCompanyAccountOwnership() { // 公司账号:导入车队强制归属本公司,Excel 中的所属公司仅作展示信息,不做公司名校验 TFleetMapper fleetMapper = Mockito.mock(TFleetMapper.class); Mockito.when(fleetMapper.selectCount(Mockito.any())).thenReturn(0L); Mockito.when(fleetMapper.insert(Mockito.any(TFleet.class))).thenReturn(1); CompanyAccountAccessService access = Mockito.mock(CompanyAccountAccessService.class); Mockito.when(access.getCurrentCompanyId()).thenReturn(2); TFleetServiceImpl service = Mockito.spy(new TFleetServiceImpl()); ReflectionTestUtils.setField(service, "baseMapper", fleetMapper); ReflectionTestUtils.setField(service, "companyAccountAccessService", access); FleetImportDTO item = new FleetImportDTO(); item.setFleetName("公司导入车队"); item.setCompanyName("随意填写的公司"); String result = service.importFleets(Collections.singletonList(item)); assertTrue(result.contains("成功导入")); ArgumentCaptor<TFleet> captor = ArgumentCaptor.forClass(TFleet.class); Mockito.verify(fleetMapper).insert(captor.capture()); assertEquals(Integer.valueOf(2), captor.getValue().getCompanyId()); } @Test void importFleetsRequiresCompanyNameForNonCompanyAccount() { // 平台/合作商账号:无公司锁定时所属公司必填 TFleetMapper fleetMapper = Mockito.mock(TFleetMapper.class); CompanyAccountAccessService access = Mockito.mock(CompanyAccountAccessService.class); Mockito.when(access.getCurrentCompanyId()).thenReturn(null); TFleetServiceImpl service = Mockito.spy(new TFleetServiceImpl()); ReflectionTestUtils.setField(service, "baseMapper", fleetMapper); ReflectionTestUtils.setField(service, "companyAccountAccessService", access); FleetImportDTO item = new FleetImportDTO(); item.setFleetName("无公司车队"); ServiceException ex = assertThrows(ServiceException.class, () -> service.importFleets(Collections.singletonList(item))); assertTrue(ex.getMessage().contains("所属公司不能为空"), ex.getMessage()); Mockito.verify(fleetMapper, Mockito.never()).insert(Mockito.any(TFleet.class)); } private TFleet fleet(Integer id) { TFleet fleet = new TFleet(); fleet.setId(id);