puzhibing
2026-09-28 d4bde58ab669a434bb0c96f391153ec66eedd5b7
修改余额字段及相关代码修改
1个文件已添加
19个文件已修改
173 ■■■■■ 已修改文件
doc/耗时统计.md 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistSaveDTO.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/ChargingCardSaveDTO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/VinManagementSaveDTO.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TAppUserWhitelist.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TChargingCard.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TVinManagement.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/mapper/TVinManagementMapper.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TChargingCardServiceImpl.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TVinManagementServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TAppUserWhitelistMapper.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TChargingCardMapper.xml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TVinManagementMapper.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TChargingCardServiceImplTest.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TVinManagementServiceImplTest.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TGroupCompanyServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/service/impl/TFleetServiceImplTest.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/20260926_统一执行清单.md 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/20260928_unify_quota_to_balance.sql 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/耗时统计.md
@@ -18,3 +18,5 @@
| 2026-09-28 15:05:00(估算,会话时间戳不可得) | 2026-09-28 15:35:00(估算) | 约 1800(估算,含方案输出、三轮确认问答与实施测试) | 白名单添加页面增加姓名、状态字段,删除享受权益、白名单电站 | 是:姓名选填、状态启用/停用(新增默认启用)、列表页同步增删列并新增姓名/公司/车队/状态筛选、导入模板同步删除两列(导入默认全部免费+全部电站),经三轮确认问答后获「开始执行」授权 | 白名单表新增 name 列;保存校验姓名长度/状态合法并归一化,benefitType/scopeType 不传默认 1(传了仍校验);新增/编辑保存姓名与状态(编辑不再强制置启用);列表 pageList 支持 name/companyId/fleetId/status 筛选;导入模板仅保留手机号列、表头判断只校验第 0 列、导入行校验与 DTO 构建简化;前端弹窗增姓名/状态、删除享受权益/白名单电站及指定站点/合作商联动;列表增姓名/状态列并删除白名单电站/享受权益列 | sql/20260928_app_user_whitelist_name_status.sql(新增);ruoyi-api/ruoyi-api-account/.../model/TAppUserWhitelist.java;ruoyi-api/ruoyi-api-account/.../dto/AppUserWhitelistSaveDTO.java、AppUserWhitelistQueryDTO.java、AppUserWhitelistImportDTO.java;ruoyi-service/ruoyi-account/.../controller/TAppUserWhitelistController.java;ruoyi-service/ruoyi-account/.../service/impl/TAppUserWhitelistServiceImpl.java;ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TAppUserWhitelistMapper.xml;qianduan/chargingPile_management/src/pages/person/whitelist/index.jsx、service.js、components/AddAndEdit.jsx、components/ImportModal.jsx、components/SiteSelectModal.jsx(删除) | 后端 `mvn -pl ":ruoyi-modules-account" -am compile` 通过;前端 `npm run tsc` 通过;IDE lint 0 条;新增/编辑/列表筛选/导入全链路代码走查通过 | SQL 脚本未在数据库执行,待部署时执行;导入路径绕过 validateSaveDTO,走查发现 buildSaveDTO 需补充默认 status=1 防止 null 落库导致匹配失效,已修复;编辑历史白名单统一落库全部电站+全部免费并清除旧站点关联;停用状态不命中匹配(既有 status=启用 条件);删除已无引用的 SiteSelectModal.jsx |
| 2026-09-28 14:51:55(估算,取本次首个产出文件创建时间) | 2026-09-28 14:54:44 | 约 170(估算,仅本轮执行阶段) | 报错修复:查询 VIN 码充电订单报 `Column 'company_id' not found in table 'o'`(MyCat2/Calcite),订单列表携带公司与车队筛选时 500 | 是:定位为订单表未执行本次功能 DDL(仅代码上线),方案为经 MyCat 代理执行建列建索引 DDL + 历史订单回填 + SQL 级回归;方案已输出并获「开始执行」授权 | 在 charging_pile_order.t_vin_charging_order 上新增 company_id、fleet_id 两列与 idx_company_id、idx_fleet_id 两个索引(经 MyCat 8066 广播至 20 张分表);复跑原报错 SQL 形态验证不再抛 Calcite 异常;按回填脚本逻辑生成并执行历史订单归属 UPDATE;EXPLAIN 校验筛选条件下推与分表覆盖;编译订单服务 | sql/20260928_vin_charging_order_company_fleet.sql(按脚本执行 DDL,文件未修改)、sql/20260928_vin_charging_order_company_fleet_backfill.sql(按其逻辑生成语句执行)、doc/耗时统计.md;业务代码零改动 | 后端 mvn -o -pl ruoyi-service/ruoyi-order -am compile BUILD SUCCESS;DESC / SHOW CREATE TABLE / information_schema 回查两列两索引齐全、中文注释无乱码、仍为 TBPARTITIONS 20;原报错 COUNT SQL 复跑正常;pageList 形态查询返回 company_id=1、fleet_id=4;汇总 SQL 聚合正常;company 单独与 fleet 单独筛选各命中 1 条;VIN 管理筛选命中 1 条;EXPLAIN 显示条件已下推 t_vin_charging_order_0..19;IDE lint 0 错误 | 仅修改测试库(192.168.110.111:8066,MyCat 2.0 支持经代理 ALTER 广播分表);报错机器 C:\Users\PC\project\charging\mx_charging_pile 若连的是另一套库,需同样执行该 DDL 脚本;历史回填仅 1 条订单(company_id=1、fleet_id=4);未分配归属的订单两列保持 NULL;开始时间取本轮首个产出文件创建时间,上一轮方案分析耗时未计 |
| 2026-09-28 15:44:00(估算,含授权前方案分析) | 2026-09-28 16:04:00(估算) | 约 1200(估算,含回归测试、单测修复、前端构建与推送) | 推送代码,编写完整的提交备注 | 是:先只读核查两仓库分支/远端/改动清单,输出推送方案与完整提交备注(含 DDL 部署前置提示与「不强制推送」约束),获「开始执行」授权后按仓库分别提交并普通推送 | 提交并推送刷卡订单公司/车队筛选、白名单姓名状态与公司车队额度、VIN 订单与 VIN 管理公司/车队筛选、车队查看弹窗改版四批改动;服务端提交 `aa8119b5`,管理后台提交 `686b5246`;回归中发现并修复白名单导入单测过期断言 | 服务端 10 个文件(ruoyi-api-order 两个模型、ruoyi-account 导入实现与单测、ruoyi-order 服务/导出/Mapper、sql 两个分表脚本、doc/耗时统计.md);管理后台 15 个文件(FleetSearchSelect 三件套、刷卡订单页、VIN 订单页、VIN 管理页、白名单四文件与 SiteSelectModal 删除、车队页);归档日志 chargingPile_management/logs/build-20260928-git-push.log | 后端 `mvn -pl ruoyi-api/ruoyi-api-order -am install`、order/account `test-compile` 均 BUILD SUCCESS;surefire 2.22.2 跑 ruoyi-order 189/189、ruoyi-account 133/133 全部通过;前端 `npx jest` 14 套件/68 用例、`npm run tsc`、`npm run build`(dist 271 文件)全部通过;推送后远端引用校验 83188a6c..aa8119b5(2.3)、482abf19..686b5246(dev_2.3) | 首轮 ruoyi-account 有 1 条失败:TAppUserWhitelistServiceImplTest 断言与「导入统一按默认值、仅校验手机号」新语义不符且 siteClient 打桩已无用,同步断言(默认全部免费+全部电站+启用)并补非法手机号错误行覆盖后全绿;刷卡订单 DDL 尚未在任何库执行,部署前须先执行 sql/20260928_card_charging_order_company_fleet.sql 及回填脚本,否则筛选会报 Column 'company_id' not found;本统计行在推送后追加,未随本次提交推送 |
| 2026-09-28 17:33:00(估算,含授权前方案分析与两轮确认问答) | 2026-09-28 17:39:39 | 约 399(估算,授权后执行 188 秒 + 方案分析约 211 秒) | 推送代码,编写完整的修改描述 | 是:先只读核查两仓库分支/远端/改动清单与编译产物,发现工作区含两个功能块改动(充电用户信息 + 优惠设置/集团联系人),经两轮确认问答确定「全部推送、每仓库一个 commit」后获「开始执行」授权 | 提交并推送公司详情充电用户信息聚合表格、公司优惠设置/账户模式改造、集团联系人字段三批改动;服务端提交 `29e9ce47`,管理后台提交 `a84f8954` | 服务端 38 个文件(VIN/刷卡/白名单三套 list-by-company 内部接口与 Feign/FallbackFactory、TGroupCompany 聚合与 CompanyChargingUserVO、TCompanyPreferential 全套与 CompanyPreferentialSupport、TGroup 联系人字段、sql/20260928_company_preferential.sql、sql/20260928_group_contact.sql、三个测试类、doc/耗时统计.md、sql/20260926_统一执行清单.md);管理后台 9 个文件(company/index.jsx、service.js、RechargeModal.jsx、AccountModeModal.jsx、PreferentialModal.jsx、companyPayload/preferential 及各自单测) | 后端 `mvn -pl ruoyi-api/ruoyi-api-account,ruoyi-api/ruoyi-api-other,ruoyi-service/ruoyi-account,ruoyi-service/ruoyi-other -am compile` BUILD SUCCESS(17/17 模块);前端删 dist 后 `npm run build` 成功(Webpack Compiled successfully,dist 生成);推送后远端引用核验 aa8119b5..29e9ce47(2.3)、686b5246..a84f8954(dev_2.3);IDE lint 0 错误 | company_preferential 与 group_contact 两个 DDL 尚未执行(测试库待执行,见 sql/20260926_统一执行清单.md 第十节);本统计行在推送后追加,未随本次提交推送 |
| 2026-09-28 18:07:00(估算,授权后执行阶段;含授权前方案分析与两轮确认约 19:xx 起) | 2026-09-28 18:40:44 | 约 2024(估算,授权后执行约 34 分钟;含授权前方案分析与两轮确认问答总计约 55 分钟) | 分析代码,将VIN码、卡片、白名单全部都舍弃init_quota字段,统一使用balance字段表示余额,额度分配和初始额度也是修改balance字段 | 是:先只读盘点 init_quota 全部 40 处引用(模型/DTO/Mapper/Service/前端页面/历史 SQL),输出三表统一方案(VIN/白名单 CHANGE 列名保留数据、卡片 DROP 列丢弃、前端仅改字段不改文案),经两轮确认问答(卡片历史数据直接丢弃、文案保留原表述、VIN 与白名单一同修改)后获「开始执行」授权 | 三模块舍弃 init_quota 统一 balance:VIN/白名单模型 DTO 改名 balance,卡片模型/DTO 删除 initQuota 复用既有 balance;额度分配与初始额度写库改为 balance 列;卡片编辑仅传正数余额才更新 balance(未传保持原余额);新建统一 DDL 脚本并追加执行清单第十一节 | 服务端 20 个文件(3 模型+3 DTO、TVinManagementMapper.java、三个 Mapper XML、三个 ServiceImpl、TGroupCompanyServiceImpl、三个测试类、sql/20260928_unify_quota_to_balance.sql 新建、sql/20260926_统一执行清单.md、doc/耗时统计.md);管理后台 6 个文件(vin/index.jsx、vin/AllocateModal.jsx、card/index.jsx、person/whitelist/index.jsx、whitelist/AddAndEdit.jsx、fleet/index.jsx) | 后端 `mvn -pl ruoyi-api-account,ruoyi-account,ruoyi-other -am compile` BUILD SUCCESS;surefire 2.22.2 跑 TChargingCardServiceImplTest 50/50、TVinManagementServiceImplTest 48/48、TAppUserWhitelistServiceImplTest 1/1、TFleetServiceImplTest 6/6、TGroupCompanyServiceImplTest 11/11 全部通过;前端删 dist 后 build 成功、`npx tsc --noEmit` 退出码 0;IDE lint 0 错误 | 首轮单测失败 1 条:updateCardRequiresExistingCardAndDoesNotChangeBalance 因编辑时 dto.balance=999.99 触发「请选择所属公司后再填写初始分配额度」,按新语义修正测试(编辑未传 balance 余额保持)并加固主代码(balance 仅正数才写列,避免共享模式清空余额);统一 DDL(sql/20260928_unify_quota_to_balance.sql)尚未在任何库执行,发版前必须先执行;前端工作区另有 company/index.jsx、global.less 公司树选中样式改动(非本任务,未纳入) |
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistSaveDTO.java
@@ -40,8 +40,8 @@
    @ApiModelProperty(value = "所属车队ID(关联t_fleet.id),必须归属所选公司")
    private Integer fleetId;
    @ApiModelProperty(value = "初始分配额度(元),仅分配模式公司可填写,保存后从公司可用余额扣减")
    private java.math.BigDecimal initQuota;
    @ApiModelProperty(value = "余额(元),分配模式公司设置后从公司可用余额扣减")
    private java.math.BigDecimal balance;
    @ApiModelProperty(value = "站点ID列表")
    private List<Integer> siteIds;
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/ChargingCardSaveDTO.java
@@ -33,7 +33,4 @@
    @ApiModelProperty(value = "所属车队ID(关联t_fleet.id),必须归属所选公司")
    private Integer fleetId;
    @ApiModelProperty(value = "初始分配额度(元),仅分配模式公司可填写,保存后从公司可用余额扣减")
    private BigDecimal initQuota;
}
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/VinManagementSaveDTO.java
@@ -16,8 +16,8 @@
    private Integer companyId;
    /** 所属车队ID(关联t_fleet.id) */
    private Integer fleetId;
    /** 初始分配额度(元),仅分配模式公司可设置 */
    private BigDecimal initQuota;
    /** 余额(元),分配模式公司设置后从公司可用余额扣减 */
    private BigDecimal balance;
    private String remark;
    private Integer status;
}
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TAppUserWhitelist.java
@@ -59,9 +59,9 @@
    @TableField("fleet_id")
    private Integer fleetId;
    @ApiModelProperty(value = "初始分配额度(元):仅分配模式公司可设置,保存时从公司可用余额扣减")
    @TableField("init_quota")
    private java.math.BigDecimal initQuota;
    @ApiModelProperty(value = "余额(元):分配模式公司设置后从公司可用余额扣减")
    @TableField("balance")
    private java.math.BigDecimal balance;
    @ApiModelProperty(value = "所属公司名称(列表远程回填,不落库)")
    @TableField(exist = false)
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TChargingCard.java
@@ -50,11 +50,6 @@
    @ApiModelProperty(value = "所属车队ID")
    private Integer fleetId;
    /** 初始分配额度(元):仅分配模式公司可设置,保存时从公司可用余额扣减 */
    @TableField("init_quota")
    @ApiModelProperty(value = "初始分配额度(元)")
    private BigDecimal initQuota;
    /** 所属公司名称(列表远程回填,不落库) */
    @TableField(exist = false)
    @ApiModelProperty(value = "所属公司名称")
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TVinManagement.java
@@ -22,7 +22,7 @@
    @Excel(name = "所属公司", width = 24) private String companyName;
    @TableField("company_id") private Integer companyId;
    @TableField("fleet_id") private Integer fleetId;
    @TableField("init_quota") private BigDecimal initQuota;
    @TableField("balance") private BigDecimal balance;
    @TableField(exist = false) private String fleetName;
    @Excel(name = "备注", width = 30) private String remark;
    @Excel(name = "启用状态", readConverterExp = "1=启用,2=禁用") private Integer status;
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/mapper/TVinManagementMapper.java
@@ -14,8 +14,8 @@
    IPage<TVinManagement> selectPageList(Page<TVinManagement> page, @Param("query") VinManagementQueryDTO query);
    List<VinManagementExcelDTO> selectExportList(@Param("query") VinManagementQueryDTO query);
    int countByVin(@Param("vin") String vin, @Param("excludeId") Long excludeId);
    /** 初始分配额度原子累加(额度分配专用,null 视为 0) */
    int addInitQuota(@Param("id") Long id, @Param("amount") BigDecimal amount, @Param("updateBy") Long updateBy);
    /** 余额原子累加(额度分配专用,null 视为 0) */
    int addBalance(@Param("id") Long id, @Param("amount") BigDecimal amount, @Param("updateBy") Long updateBy);
    List<TVinManagement> selectAppletVehicleList(@Param("appUserId") Long appUserId);
    TVinManagement selectEnabledVehicle(@Param("keyword") String keyword);
    int bindAppletVehicle(@Param("id") Long id, @Param("appUserId") Long appUserId);
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImpl.java
@@ -423,7 +423,7 @@
            if (getByPhone(saveDTO.getPhone()) != null) {
                throw new ServiceException("该手机号已存在白名单配置");
            }
            BigDecimal quota = normalizeQuota(saveDTO.getInitQuota());
            BigDecimal quota = normalizeQuota(saveDTO.getBalance());
            Integer companyId = resolveRelationCompanyId(saveDTO);
            if (quota.signum() > 0) {
                requireAllocationCompany(companyId);
@@ -444,7 +444,7 @@
            whitelist.setPartnerId(resolveWhitelistPartnerId(saveDTO));
            whitelist.setCompanyId(companyId);
            whitelist.setFleetId(saveDTO.getFleetId());
            whitelist.setInitQuota(quota.signum() > 0 ? quota : null);
            whitelist.setBalance(quota.signum() > 0 ? quota : null);
            whitelist.setStatus(saveDTO.getStatus());
            whitelist.setUpdateBy(getUsername());
            whitelist.setRemark(saveDTO.getRemark());
@@ -494,13 +494,13 @@
                throw new ServiceException("白名单用户不存在");
            }
            assertPartnerVisible(whitelist);
            BigDecimal newQuota = normalizeQuota(saveDTO.getInitQuota());
            BigDecimal newQuota = normalizeQuota(saveDTO.getBalance());
            Integer newCompanyId = resolveRelationCompanyId(saveDTO);
            if (newQuota.signum() > 0) {
                requireAllocationCompany(newCompanyId);
            }
            Integer oldCompanyId = whitelist.getCompanyId();
            BigDecimal oldQuota = normalizeQuota(whitelist.getInitQuota());
            BigDecimal oldQuota = normalizeQuota(whitelist.getBalance());
            if (Objects.equals(oldCompanyId, newCompanyId)) {
                // 同一公司按差额调整:调增扣差额,调减退差额
                BigDecimal diff = newQuota.subtract(oldQuota);
@@ -535,7 +535,7 @@
            whitelist.setPartnerId(resolveWhitelistPartnerId(saveDTO));
            whitelist.setCompanyId(newCompanyId);
            whitelist.setFleetId(saveDTO.getFleetId());
            whitelist.setInitQuota(newQuota.signum() > 0 ? newQuota : null);
            whitelist.setBalance(newQuota.signum() > 0 ? newQuota : null);
            whitelist.setStatus(saveDTO.getStatus());
            whitelist.setUpdateBy(getUsername());
            whitelist.setRemark(saveDTO.getRemark());
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TChargingCardServiceImpl.java
@@ -106,7 +106,7 @@
    public boolean saveCard(ChargingCardSaveDTO dto) {
        validateCard(dto, false);
        String cardNo = dto.getCardNo().trim();
        BigDecimal quota = normalizeQuota(dto.getInitQuota());
        BigDecimal quota = normalizeQuota(dto.getBalance());
        // 同卡号并发新增串行化,卡号重复校验与额度扣减在同一临界区内
        RLock lock = redissonClient.getLock(QUOTA_LOCK_PREFIX + "CARD_NO:" + cardNo);
        boolean locked = tryQuotaLock(lock, "该卡号正在处理中,请稍后重试");
@@ -114,7 +114,7 @@
        try {
            TChargingCard card = new TChargingCard();
            card.setCardNo(cardNo);
            card.setBalance(dto.getBalance() == null ? BigDecimal.ZERO : dto.getBalance());
            card.setBalance(quota.signum() > 0 ? quota : BigDecimal.ZERO);
            card.setStatus(dto.getStatus() == null ? ENABLED : dto.getStatus());
            card.setPassword(encodePasswordIfPresent(dto.getPassword()));
            card.setChargingFlag(0);
@@ -128,7 +128,6 @@
                deductBalance(card.getCompanyId(), quota);
                compensations.add(() -> refundQuietly(card.getCompanyId(), quota));
            }
            card.setInitQuota(quota.signum() > 0 ? quota : null);
            if (!save(card)) {
                throw new ServiceException("新增卡片失败");
            }
@@ -166,12 +165,12 @@
            card.setUpdateTime(LocalDateTime.now());
            card.setUpdateBy(SecurityUtils.getUserId());
            applyRelation(card, dto);
            BigDecimal newQuota = normalizeQuota(dto.getInitQuota());
            BigDecimal newQuota = normalizeQuota(dto.getBalance());
            if (newQuota.signum() > 0) {
                requireAllocationCompany(card.getCompanyId());
            }
            Integer oldCompanyId = latest.getCompanyId();
            BigDecimal oldQuota = normalizeQuota(latest.getInitQuota());
            BigDecimal oldQuota = normalizeQuota(latest.getBalance());
            if (Objects.equals(oldCompanyId, card.getCompanyId())) {
                // 同一公司按差额调整:调增扣差额,调减退差额
                BigDecimal diff = newQuota.subtract(oldQuota);
@@ -197,7 +196,7 @@
                    compensations.add(() -> refundQuietly(card.getCompanyId(), newQuota));
                }
            }
            card.setInitQuota(newQuota.signum() > 0 ? newQuota : null);
            card.setBalance(newQuota.signum() > 0 ? newQuota : null);
            String encodedPassword = encodePasswordIfPresent(dto.getPassword());
            if (!updateCardRecord(card, encodedPassword, latest)) {
                throw new ServiceException("修改卡片失败");
@@ -783,17 +782,22 @@
        if (!updateById(update)) {
            return false;
        }
        BigDecimal newBalance = card.getBalance();
        boolean balanceChanged = newBalance != null && newBalance.signum() > 0
                && !quotaEquals(existing.getBalance(), newBalance);
        boolean relationChanged = !Objects.equals(existing.getCompanyId(), card.getCompanyId())
                || !Objects.equals(existing.getFleetId(), card.getFleetId())
                || !quotaEquals(existing.getInitQuota(), card.getInitQuota());
                || balanceChanged;
        if (!relationChanged) {
            return true;
        }
        UpdateWrapper<TChargingCard> relationUpdate = new UpdateWrapper<>();
        relationUpdate.eq("id", card.getId())
                .set("company_id", card.getCompanyId())
                .set("fleet_id", card.getFleetId())
                .set("init_quota", card.getInitQuota());
                .set("fleet_id", card.getFleetId());
        if (balanceChanged) {
            relationUpdate.set("balance", newBalance);
        }
        return update(null, relationUpdate);
    }
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TVinManagementServiceImpl.java
@@ -82,7 +82,7 @@
        validate(dto, false);
        TVinManagement item = from(dto);
        applyRelation(item, dto, null);
        BigDecimal quota = normalizeQuota(dto.getInitQuota());
        BigDecimal quota = normalizeQuota(dto.getBalance());
        List<Runnable> compensations = new ArrayList<>();
        try {
            if (quota.signum() > 0) {
@@ -90,7 +90,7 @@
                deductBalance(item.getCompanyId(), quota);
                compensations.add(() -> refundQuietly(item.getCompanyId(), quota));
            }
            item.setInitQuota(quota.signum() > 0 ? quota : null);
            item.setBalance(quota.signum() > 0 ? quota : null);
            item.setDelFlag(0);
            item.setCreateTime(LocalDateTime.now());
            item.setCreateBy(SecurityUtils.getUserId());
@@ -114,14 +114,14 @@
        item.setUpdateTime(LocalDateTime.now());
        item.setUpdateBy(SecurityUtils.getUserId());
        applyRelation(item, dto, old);
        BigDecimal newQuota = normalizeQuota(dto.getInitQuota());
        BigDecimal oldQuota = normalizeQuota(old.getInitQuota());
        BigDecimal newQuota = normalizeQuota(dto.getBalance());
        BigDecimal oldQuota = normalizeQuota(old.getBalance());
        if (newQuota.signum() > 0) {
            requireAllocationCompany(item.getCompanyId());
        }
        Integer oldCompanyId = old.getCompanyId();
        Integer newCompanyId = item.getCompanyId();
        item.setInitQuota(newQuota.signum() > 0 ? newQuota : null);
        item.setBalance(newQuota.signum() > 0 ? newQuota : null);
        List<Runnable> compensations = new ArrayList<>();
        try {
            if (Objects.equals(oldCompanyId, newCompanyId)) {
@@ -291,7 +291,7 @@
                if (!Integer.valueOf(ENABLED).equals(vehicle.getStatus())) {
                    throw new ServiceException("VIN码【" + vehicle.getVin() + "】已禁用,禁止分配额度");
                }
                BigDecimal quota = normalizeQuota(vehicle.getInitQuota());
                BigDecimal quota = normalizeQuota(vehicle.getBalance());
                if (quota.add(amount).compareTo(MAX_INIT_QUOTA) > 0) {
                    throw new ServiceException("VIN码【" + vehicle.getVin() + "】分配后初始分配额度超过上限"
                            + MAX_INIT_QUOTA.toPlainString());
@@ -306,7 +306,7 @@
                Long operator = SecurityUtils.getUserId();
                for (TVinManagement vehicle : vehicles) {
                    BigDecimal amount = amounts.get(vehicle.getId());
                    if (baseMapper.addInitQuota(vehicle.getId(), amount, operator) != 1) {
                    if (baseMapper.addBalance(vehicle.getId(), amount, operator) != 1) {
                        throw new ServiceException("VIN码【" + vehicle.getVin() + "】额度分配失败,请重试");
                    }
                }
@@ -627,7 +627,7 @@
                .set(TVinManagement::getCompanyName, item.getCompanyName())
                .set(TVinManagement::getCompanyId, item.getCompanyId())
                .set(TVinManagement::getFleetId, item.getFleetId())
                .set(TVinManagement::getInitQuota, item.getInitQuota())
                .set(TVinManagement::getBalance, item.getBalance())
                .set(TVinManagement::getRemark, item.getRemark())
                .set(TVinManagement::getStatus, item.getStatus())
                .set(TVinManagement::getUpdateTime, item.getUpdateTime())
ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TAppUserWhitelistMapper.xml
@@ -12,7 +12,7 @@
        <result column="partner_id" property="partnerId" />
        <result column="company_id" property="companyId" />
        <result column="fleet_id" property="fleetId" />
        <result column="init_quota" property="initQuota" />
        <result column="balance" property="balance" />
        <result column="status" property="status" />
        <result column="create_by" property="createBy" />
        <result column="create_time" property="createTime" />
@@ -22,7 +22,7 @@
    </resultMap>
    <sql id="Base_Column_List">
        id, app_user_id, phone, name, benefit_type, scope_type, partner_id, company_id, fleet_id, init_quota, status, create_by, create_time, update_by, update_time, remark, del_flag
        id, app_user_id, phone, name, benefit_type, scope_type, partner_id, company_id, fleet_id, balance, status, create_by, create_time, update_by, update_time, remark, del_flag
    </sql>
    <select id="selectValidByPhone" resultMap="BaseResultMap">
@@ -44,7 +44,7 @@
            partner_id = #{partnerId},
            company_id = #{companyId},
            fleet_id = #{fleetId},
            init_quota = #{initQuota},
            balance = #{balance},
            status = #{status},
            update_by = #{updateBy},
            update_time = now(),
ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TChargingCardMapper.xml
@@ -10,7 +10,6 @@
               balance,
               company_id,
               fleet_id,
               init_quota,
               status,
               del_flag,
               create_time,
ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TVinManagementMapper.xml
@@ -2,7 +2,7 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.account.mapper.TVinManagementMapper">
    <select id="selectPageList" resultType="com.ruoyi.account.api.model.TVinManagement">
        SELECT id, vin, plate_number, phone, company_name, company_id, fleet_id, init_quota, remark, status, del_flag, create_time, create_by, update_time, update_by
        SELECT id, vin, plate_number, phone, company_name, company_id, fleet_id, balance, remark, status, del_flag, create_time, create_by, update_time, update_by
        FROM t_vin_management WHERE del_flag = 0
        <if test="query != null and query.vin != null and query.vin != ''">AND vin LIKE CONCAT('%', #{query.vin}, '%')</if>
        <if test="query != null and query.plateNumber != null and query.plateNumber != ''">AND plate_number LIKE CONCAT('%', #{query.plateNumber}, '%')</if>
@@ -51,9 +51,9 @@
        WHERE app_user_id = #{appUserId} AND status = 1 AND del_flag = 0
        ORDER BY id ASC
    </select>
    <update id="addInitQuota">
    <update id="addBalance">
        UPDATE t_vin_management
        SET init_quota = IFNULL(init_quota, 0) + #{amount}, update_time = NOW(), update_by = #{updateBy}
        SET balance = IFNULL(balance, 0) + #{amount}, update_time = NOW(), update_by = #{updateBy}
        WHERE id = #{id} AND del_flag = 0
    </update>
</mapper>
ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TChargingCardServiceImplTest.java
@@ -136,7 +136,6 @@
        ChargingCardSaveDTO dto = new ChargingCardSaveDTO();
        dto.setId(7L);
        dto.setCardNo(" CARD-7 ");
        dto.setBalance(new BigDecimal("999.99"));
        when(mapper.countByCardNo("CARD-7", 7L)).thenReturn(0);
        TChargingCard existing = new TChargingCard();
        existing.setId(7L);
@@ -338,7 +337,7 @@
        dto.setCardNo(" CARD-Q1 ");
        dto.setCompanyId(9);
        dto.setFleetId(3);
        dto.setInitQuota(new BigDecimal("100"));
        dto.setBalance(new BigDecimal("100"));
        assertTrue(service.saveCard(dto));
@@ -346,7 +345,7 @@
        verify(mapper).insert(card.capture());
        assertEquals(Integer.valueOf(9), card.getValue().getCompanyId());
        assertEquals(Integer.valueOf(3), card.getValue().getFleetId());
        assertEquals(new BigDecimal("100.00"), card.getValue().getInitQuota());
        assertEquals(new BigDecimal("100.00"), card.getValue().getBalance());
        verify(groupCompanyClient).deductBalance(9, new BigDecimal("100.00"));
    }
@@ -359,7 +358,7 @@
        ChargingCardSaveDTO dto = new ChargingCardSaveDTO();
        dto.setCardNo("CARD-Q2");
        dto.setCompanyId(9);
        dto.setInitQuota(new BigDecimal("10.00"));
        dto.setBalance(new BigDecimal("10.00"));
        ServiceException exception = assertThrows(ServiceException.class, () -> service.saveCard(dto));
@@ -380,7 +379,7 @@
        ArgumentCaptor<TChargingCard> card = ArgumentCaptor.forClass(TChargingCard.class);
        verify(mapper).insert(card.capture());
        assertEquals(null, card.getValue().getCompanyId());
        assertEquals(null, card.getValue().getInitQuota());
        assertEquals(0, card.getValue().getBalance().compareTo(BigDecimal.ZERO));
        verify(groupCompanyClient, never()).deductBalance(any(), any());
    }
@@ -521,7 +520,7 @@
        TChargingCard card = new TChargingCard();
        card.setId(id);
        card.setCompanyId(companyId);
        card.setInitQuota(quota);
        card.setBalance(quota);
        return card;
    }
@@ -530,7 +529,7 @@
        dto.setId(id);
        dto.setCardNo(cardNo);
        dto.setCompanyId(companyId);
        dto.setInitQuota(quota);
        dto.setBalance(quota);
        return dto;
    }
ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TVinManagementServiceImplTest.java
@@ -724,16 +724,16 @@
        doReturn(Arrays.asList(allocateVehicle(1L, 9, "VIN-1", "10.00"), allocateVehicle(2L, 9, "VIN-2", "20.00")))
                .when(service).listByIds(anyCollection());
        when(groupCompanyClient.deductBalance(9, new BigDecimal("30.00"))).thenReturn(R.ok(new BigDecimal("970.00")));
        when(mapper.addInitQuota(1L, new BigDecimal("10.00"), CURRENT_USER)).thenReturn(1);
        when(mapper.addInitQuota(2L, new BigDecimal("20.00"), CURRENT_USER)).thenReturn(1);
        when(mapper.addBalance(1L, new BigDecimal("10.00"), CURRENT_USER)).thenReturn(1);
        when(mapper.addBalance(2L, new BigDecimal("20.00"), CURRENT_USER)).thenReturn(1);
        VinManagementAllocateVO vo = service.allocateQuota(allocateDto(9,
                allocateItem(1L, "10.00"), allocateItem(2L, "20.00")));
        assertEquals(new BigDecimal("970.00"), vo.getAvailableBalance());
        assertEquals("测试公司", vo.getCompanyName());
        verify(mapper).addInitQuota(1L, new BigDecimal("10.00"), CURRENT_USER);
        verify(mapper).addInitQuota(2L, new BigDecimal("20.00"), CURRENT_USER);
        verify(mapper).addBalance(1L, new BigDecimal("10.00"), CURRENT_USER);
        verify(mapper).addBalance(2L, new BigDecimal("20.00"), CURRENT_USER);
        verify(redissonClient).getLock("VIN_QUOTA:COMPANY:9");
    }
@@ -796,7 +796,7 @@
        doReturn(Collections.singletonList(allocateVehicle(1L, 9, "VIN-1", "10.00")))
                .when(service).listByIds(anyCollection());
        when(groupCompanyClient.deductBalance(9, new BigDecimal("10.00"))).thenReturn(R.ok(new BigDecimal("990.00")));
        when(mapper.addInitQuota(1L, new BigDecimal("10.00"), CURRENT_USER)).thenReturn(0);
        when(mapper.addBalance(1L, new BigDecimal("10.00"), CURRENT_USER)).thenReturn(0);
        when(groupCompanyClient.refundBalance(9, new BigDecimal("10.00"))).thenReturn(R.ok(new BigDecimal("1000.00")));
        ServiceException exception = assertThrows(ServiceException.class,
@@ -817,12 +817,12 @@
        return company;
    }
    private TVinManagement allocateVehicle(Long id, Integer companyId, String vin, String initQuota) {
    private TVinManagement allocateVehicle(Long id, Integer companyId, String vin, String balance) {
        TVinManagement vehicle = new TVinManagement();
        vehicle.setId(id);
        vehicle.setCompanyId(companyId);
        vehicle.setVin(vin);
        vehicle.setInitQuota(new BigDecimal(initQuota));
        vehicle.setBalance(new BigDecimal(balance));
        vehicle.setStatus(1);
        return vehicle;
    }
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TGroupCompanyServiceImpl.java
@@ -175,7 +175,7 @@
        for (TVinManagement vin : vins) {
            result.add(buildRow(USER_TYPE_VIN + "-" + vin.getId(), USER_TYPE_VIN, vin.getVin(),
                    companyName, fleetNameMap.get(vin.getFleetId()), accountMode,
                    vin.getInitQuota(), companyBalance, vin.getStatus()));
                    vin.getBalance(), companyBalance, vin.getStatus()));
        }
        for (TChargingCard card : cards) {
            result.add(buildRow(USER_TYPE_CARD + "-" + card.getId(), USER_TYPE_CARD, card.getCardNo(),
@@ -185,7 +185,7 @@
        for (TAppUserWhitelist whitelist : whitelists) {
            result.add(buildRow(USER_TYPE_WHITELIST + "-" + whitelist.getId(), USER_TYPE_WHITELIST, whitelist.getPhone(),
                    companyName, fleetNameMap.get(whitelist.getFleetId()), accountMode,
                    whitelist.getInitQuota(), companyBalance, whitelist.getStatus()));
                    whitelist.getBalance(), companyBalance, whitelist.getStatus()));
        }
        return result;
    }
ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/service/impl/TFleetServiceImplTest.java
@@ -119,7 +119,7 @@
        item.setVin("12345678900000000");
        item.setPlateNumber("川A123456");
        item.setFleetId(4);
        item.setInitQuota(new BigDecimal("30.00"));
        item.setBalance(new BigDecimal("30.00"));
        item.setStatus(1);
        return item;
    }
sql/20260926_统一执行清单.md
@@ -116,11 +116,11 @@
- 代码侧配套(无需改动):`TGroupCompanyServiceImpl` 新增/编辑/删除公司与 `sys_user`(roleType=5、role_key=company)的同步逻辑、前端公司页「登录账号」必填项此前已实现,本次仅补齐数据库结构。
- 待办提醒:现有 1 行公司(喜望公司)`account`/`password` 为 NULL;前端编辑公司时「登录账号」为只读禁用态,补录账号需业务侧确认放开前端或后台补数据;`sys_role` 新角色的菜单权限需在「角色管理」中配置(脚本不涉及)。
## 十、追加执行:公司优惠设置建表(2026-09-28,待执行)
## 十、追加执行:公司优惠设置建表(2026-09-28,测试库已执行)
| 脚本 | 结果 |
|---|---|
| `20260928_company_preferential.sql`(本次新建) | **待执行**:`charging_pile_other.t_company_preferential`(公司管理-优惠设置) |
| `20260928_company_preferential.sql`(本次新建) | **测试库已执行**:`charging_pile_other.t_company_preferential`(公司管理-优惠设置);2026-09-28 经外网入口 221.182.45.100:8066(与内网 192.168.110.111:8066 同一套 Mycat)`source` 执行,EXIT=0 |
- 背景:公司管理新增「优惠设置」弹窗(1=不优惠 / 2=按比例优惠 / 3=按优惠单价收费),一个公司一条配置;充值弹窗的优惠方式配置已移除,改由该弹窗统一维护(充值记录表 `t_company_recharge_record.preferential_type` 保持库默认 1=不优惠,历史记录展示不受影响)。
- 执行方式(Mycat 2.0,建议先复制为英文临时名再 `source`,并校验 `$LASTEXITCODE`):
@@ -134,4 +134,28 @@
  ```
- 部署后验证:公司管理 → 公司概况 → 「优惠设置」保存后重开弹窗回显、「账户模式」保存后概况账户模式同步刷新(需部署 `ruoyi-api-other` + `ruoyi-other` 新版本与前端产物)。
- 代码侧配套(同批改动):新增实体 `TCompanyPreferential`(`service_discount_rate` / `billing_template_id` 采用 `FieldStrategy.IGNORED`,切换优惠方式时旧值可被清空)、`CompanyPreferentialDTO`、Mapper/Service/Controller(`/other/company-preferential/detail|save`)、接口 `/other/group-company/account-mode`;`TGroupCompanyServiceImpl.addCompany` 对未传账户模式的公司按共享模式(1)建档;前端添加/编辑企业弹窗已删除账户模式配置。
- 未执行原因:本次授权范围为代码与脚本,测试库/生产库 DDL 需业务侧确认后再执行。
- 执行证据(2026-09-28,测试库):`SHOW CREATE TABLE charging_pile_other.t_company_preferential` 返回 8 列(`id` / `company_id` / `preferential_type` / `service_discount_rate` / `billing_template_id` / `create_time` / `update_time` / `del_flag`)+ `PRIMARY KEY (id)` + `UNIQUE KEY uk_company_id USING BTREE (company_id)`,`CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT '公司优惠设置'`,中文注释无乱码;`SELECT COUNT(*)` = 0(空表);**原报错 SQL 原样重放**(`SELECT id,company_id,preferential_type,service_discount_rate,billing_template_id,update_time,create_time,del_flag FROM t_company_preferential WHERE del_flag=0 AND (company_id = 2)`)EXIT=0 正常返回空结果集;`EXPLAIN` 解析到逻辑表 `charging_pile_other`.`t_company_preferential`(Mycat targetName: prototype);`SHOW COLUMNS` 显示 `company_id` Key=UNI、`preferential_type` 默认 1、`del_flag` 默认 0。
- 回查注意点(本次踩到):Mycat `SHOW TABLES ... LIKE 'x'` 会报 Calcite `Column 'x' not found in any table`(LIKE 字面量被当作列名),回查请改用 `SHOW CREATE TABLE` 或 `SHOW TABLES FROM <逻辑库>`;不带默认库名执行 DML 报 `ERROR 1105 (HY000) please use schema`,需在客户端命令末尾追加库名(或在脚本内 `USE`)。
- 其他环境(生产)发版前必须先执行本脚本,否则优惠设置接口报 `Object 't_company_preferential' not found within 'charging_pile_other'`(本次测试库报错即此原因)。
## 十一、追加执行:余额字段统一(2026-09-28,代码已改,DDL 待执行)
| 脚本 | 结果 |
|---|---|
| `20260928_unify_quota_to_balance.sql`(本次新建) | **尚未执行**:VIN 码/白名单/卡片三表舍弃 `init_quota`、统一使用 `balance` 表示余额 |
- 背景:VIN 码(`t_vin_management`)、刷卡卡(`t_charging_card`)、白名单(`t_app_user_whitelist`)的「初始分配额度」`init_quota` 与余额 `balance` 语义重复,统一舍弃 `init_quota`:VIN/白名单 `CHANGE COLUMN init_quota balance`(保留历史数据),卡片 `DROP COLUMN init_quota`(卡片已有 `balance` 列,历史分配记录不保留);额度分配与初始额度写入统一改为 `balance` 列。
- 执行方式(Mycat 2.0,建议先复制为英文临时名再 `source`,并校验 `$LASTEXITCODE`):
  ```bash
  mysql -h<host> -P8066 -uroot -p --default-character-set=utf8mb4 -e "source <绝对路径>/unify_quota_to_balance.sql"
  ```
- 幂等性:脚本为纯 `ALTER TABLE`(2 条 CHANGE + 1 条 DROP),重复执行会报 `Unknown column`/`Duplicate column` 类错误,忽略即可;以 information_schema 回查为准。
- 回查命令(执行后回填证据):
  ```bash
  mysql -h<host> -P8066 -uroot -p --default-character-set=utf8mb4 -e "SHOW COLUMNS FROM charging_pile_account.t_vin_management LIKE 'balance'"
  mysql -h<host> -P8066 -uroot -p --default-character-set=utf8mb4 -e "SHOW COLUMNS FROM charging_pile_account.t_app_user_whitelist LIKE 'balance'"
  mysql -h<host> -P8066 -uroot -p --default-character-set=utf8mb4 -e "SHOW COLUMNS FROM charging_pile_account.t_charging_card LIKE 'init_quota'"
  ```
- 部署后验证:VIN 管理/刷卡卡/白名单列表「初始分配额度」列正常显示 balance 值;新增/编辑填写额度后公司可用余额同步扣减;额度分配累加 balance。
- 代码侧配套(同批改动):三套 domain/DTO/Mapper/Service 与聚合逻辑、前端 vin/card/whitelist/fleet 页面字段全部由 `initQuota` 改为 `balance`(界面文案保留「初始分配额度」)。
- 其他环境(生产)发版前必须先执行本脚本,否则接口报 `Unknown column 'init_quota'`。
sql/20260928_unify_quota_to_balance.sql
New file
@@ -0,0 +1,19 @@
-- VIN码/白名单/卡片统一余额字段:舍弃 init_quota,统一使用 balance 表示余额
-- 创建日期: 2026-09-28
-- 目标库: charging_pile_account(Mycat 8066 直接执行)
-- 说明: 1) t_vin_management、t_app_user_whitelist 将 init_quota 改名 balance,历史数据保留;
--       2) t_charging_card 已有 balance 列(卡余额),init_quota 与 balance 语义重复,直接删除列,历史分配记录不保留;
--       3) 额度分配与初始额度写入统一改为 balance 列。
-- 注意: 非幂等,重复执行会报错(Unknown column / Duplicate column),忽略即可(以 information_schema 回查为准)
-- VIN码:init_quota 改名 balance(保留历史数据)
ALTER TABLE `charging_pile_account`.`t_vin_management`
  CHANGE COLUMN `init_quota` `balance` decimal(12,2) NULL DEFAULT NULL COMMENT '余额(元)';
-- 白名单:init_quota 改名 balance(保留历史数据)
ALTER TABLE `charging_pile_account`.`t_app_user_whitelist`
  CHANGE COLUMN `init_quota` `balance` decimal(12,2) NULL DEFAULT NULL COMMENT '余额(元)';
-- 卡片:直接删除 init_quota(与既有 balance 列语义重复,历史分配记录不保留)
ALTER TABLE `charging_pile_account`.`t_charging_card`
  DROP COLUMN `init_quota`;