From 83188a6c8ea2f123519378da736382a9136a4331 Mon Sep 17 00:00:00 2001
From: puzhibing <393733352@qq.com>
Date: 星期一, 28 九月 2026 14:47:09 +0800
Subject: [PATCH] 新增加功能和修改已有功能

---
 ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/mapper/VinManagementMapperStructureTest.java       |   44 ++
 ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/query/VinChargingOrderQuery.java                   |    6 
 ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/feignClient/GroupCompanyClient.java                |    6 
 ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/VinChargingOrderMapperStructureTest.java        |    4 
 sql/20260928_app_user_whitelist_name_status.sql                                                                |    8 
 ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistQueryDTO.java              |   12 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImpl.java     |  561 +++++++++++++++++++-------
 ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TAppUserWhitelist.java                   |   24 +
 ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/model/TVinChargingOrder.java                       |    8 
 ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TVinChargingOrderCompanyFleetTest.java    |  192 +++++++++
 ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistSaveDTO.java               |   17 
 ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/factory/GroupCompanyFallbackFactory.java           |    5 
 ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistImportDTO.java             |    8 
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TVinChargingOrderServiceImpl.java         |   76 +++
 ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TVinChargingOrderMapper.xml                          |    2 
 sql/20260928_vin_charging_order_company_fleet.sql                                                              |  128 ++++++
 ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImplTest.java |   16 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserWhitelistController.java        |    2 
 sql/20260928_vin_charging_order_company_fleet_backfill.sql                                                     |   56 ++
 doc/耗时统计.md                                                                                                    |    1 
 ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TVinManagementMapper.xml                         |    2 
 ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TAppUserWhitelistMapper.xml                      |   10 
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/VinChargingOrderExport.java                     |    4 
 sql/20260928_app_user_whitelist_company_fleet_quota.sql                                                        |   19 
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TGroupCompanyController.java                |   20 
 ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/vo/ChargingOrderVO.java                            |    8 
 26 files changed, 1,075 insertions(+), 164 deletions(-)

diff --git "a/doc/\350\200\227\346\227\266\347\273\237\350\256\241.md" "b/doc/\350\200\227\346\227\266\347\273\237\350\256\241.md"
index cbe4fa2..d76787e 100644
--- "a/doc/\350\200\227\346\227\266\347\273\237\350\256\241.md"
+++ "b/doc/\350\200\227\346\227\266\347\273\237\350\256\241.md"
@@ -14,3 +14,4 @@
 | 2026-09-15 17:39:31 | 2026-09-15 17:52:02 | 751 | 请继续执行未完成的任务。(续作:SD 已结束未扣款补偿) | 是:账户侧新增批量扣款流水查询接口,订单侧扫描状态5且应扣款的刷卡订单,逐单复核无扣款流水后条件回退为停止中交由既有补偿结算;本轮补齐“已有流水不回退”“账户查询失败不回退”“无候选不远程调用”“未抢到锁不回退”“定时任务异常隔离”5 个回归用例 | 完成 SD 已结束(状态5)订单无充电扣款流水的定时兜底回退,并补齐测试与全量回归;默认 surefire 版本无法执行 JUnit 5,改用本地仓库 surefire 2.22.2 执行 | `ruoyi-api/ruoyi-api-account/.../feignClient/ChargingCardClient.java`;`ruoyi-api/ruoyi-api-account/.../factory/ChargingCardFallbackFactory.java`;`ruoyi-service/ruoyi-account/.../controller/TChargingCardController.java`;`.../mapper/TChargingCardMapper.java`;`.../service/TChargingCardService.java`;`.../service/impl/TChargingCardServiceImpl.java`;`.../resources/mapper/account/TChargingCardMapper.xml`;`.../test/.../TChargingCardControllerTest.java`;`.../test/.../TChargingCardServiceImplTest.java`;`ruoyi-service/ruoyi-order/.../service/TCardChargingOrderService.java`;`.../service/impl/TCardChargingOrderServiceImpl.java`;`.../service/impl/TVinChargingOrderServiceImpl.java`;`.../util/task/TaskUtil.java`;`.../test/.../TCardChargingOrderAmountFieldsTest.java`;`.../test/.../TaskUtilCompensationTest.java`;`doc/耗时统计.md` | `mvn -o -pl ruoyi-api/ruoyi-api-account,ruoyi-service/ruoyi-account,ruoyi-service/ruoyi-order -am test-compile -DskipTests` BUILD SUCCESS;surefire 2.22.2 定向 14/14、ruoyi-order 全量 185/185、ruoyi-account 全量 103/103 全部通过;IDE lint 0 条;`git diff --check` 通过;代码走查通过 | 开始时间取本次续作会话首个命令时间,耗时仅统计续作会话(751 秒);实现主体在当天更早会话完成、时间未记录,故整体任务耗时未合并统计;新增用例首轮因在 thenReturn 参数内调用 Mockito 桩方法触发 UnfinishedStubbing,提取局部变量后修复 |
 | 2026-09-21 15:19:30(估算) | 2026-09-21 15:23:29 | 约 240(估算) | 分析代码,汇总下已完成三方对接的品牌:光伏:科士达、华为、隆基、晶科、通威;储能:利普芯储能、华为、宁德时代、阳光电源;监控:海康、萤石、大华;道闸:吉莲、顺捷 | 否(只读分析任务,未涉及代码修改,无需授权口令) | 全项目盘点光伏/储能/视频监控/道闸四类三方对接实现,逐品牌核对真实完成情况,区分「已完成对接 / 仅枚举占位 / 代码中不存在」,并核对数据表品牌字段与同步任务 | `.codebuddy/memory/2026-09-21.md`(新建);`.codebuddy/memory/MEMORY.md`;`doc/耗时统计.md`(未修改任何业务代码) | 只读分析,未执行构建与测试;通过全库关键词检索、类与 SQL 文件走查、调用链核对交叉验证(含 2 个子代理并行探索) | 开始时间因本会话首条命令时间戳不可得而估算;结论:已真实完成对接为科士达、华为(仅光伏)、利普芯(eMind2000)、萤石、通用 HTTP 道闸平台,以及清单外的优旦储能;阳光电源/古瑞瓦特/海康/大华/顺捷、吉联、舜杰、捷联均为枚举占位;隆基、晶科、通威、宁德时代、吉莲在代码中不存在 |
 | 2026-09-28(估算,会话首条命令时间不可得) | 2026-09-28(估算) | 约 2400(估算) | 公司管理添加/编辑公司增加登录账号密码字段;定义 roleType=5 为公司账号(先定 3 后改 5) | 是:t_group_company 增加 account/password;内置角色 role_key=company 供公司账号共享;sys_user.roleType 语义统一为 1=平台 2=合作商 3=门店 4=修理厂 5=公司,遗留公司接口 roleType 2→5;公司增/改/删同步创建/更新/停用 sys_user 账号(前端密码 HMAC-MD5) | 公司管理账号密码字段与公司登录账号全链路 | `ruoyi-api/ruoyi-api-other/.../domain/TGroupCompany.java`;`ruoyi-api/ruoyi-api-system/.../feignClient/SysRoleClient.java`;`ruoyi-api/ruoyi-api-system/.../factory/SysRoleFallbackFactory.java`;`ruoyi-modules/ruoyi-system/.../controller/SysRoleController.java`;`ruoyi-modules/ruoyi-system/.../controller/SysUserController.java`;`ruoyi-modules/ruoyi-system/src/test/.../PartnerTypeRoleServiceTest.java`;`ruoyi-service/ruoyi-other/.../service/TGroupCompanyService.java`;`ruoyi-service/ruoyi-other/.../service/impl/TGroupCompanyServiceImpl.java`;`ruoyi-service/ruoyi-other/.../controller/TGroupCompanyController.java`;`ruoyi-service/ruoyi-other/src/test/.../TGroupCompanyServiceImplTest.java`;`ruoyi-service/ruoyi-other/src/test/.../EmindEnergyChainManagementServiceTest.java`;`sql/20260928_company_account_role.sql`;`qianduan/chargingPile_management/src/pages/company/index.jsx`;`doc/耗时统计.md` | 后端 4 模块 `mvn -am compile` 通过;surefire 2.22.2 跑 TGroupCompanyServiceImplTest 6/6、PartnerTypeRoleServiceTest 7/7 全部通过;前端 `tsc --noEmit` 通过;前端生产构建成功(dist 生成);IDE lint 0 条 | SQL 脚本未在数据库执行,待部署时执行;修复了 2 处历史遗留测试(EmindEnergyChainManagementServiceTest 构造参数不同步、TGroupCompanyServiceImplTest 断言与 getDetail 双查询不一致);前端构建需在脚本内设置 CODEBUDDY_SAFE_DELETE_ENABLED=0 且勿删除被占用日志文件 |
+| 2026-09-28 14:27:21(估算,取授权后首个产出文件创建时间) | 2026-09-28 14:45:50 | 约 1109(估算,含测试、构建与收尾归档) | VIN码充电订单增加所属公司、所属车队:需公司与车队联动、按方案B实现、VIN 管理页也增加筛选条件 | 是:先输出方案A(按 VIN 集合筛选)与方案B(订单表冗余归属列、SQL 直接筛选)对比,用户确认方案B与联动后输出修订版方案并获「开始执行」授权 | 订单表新增 company_id、fleet_id 快照列(建单时取 VIN 管理记录归属);列表/汇总/导出共用 companyId、fleetId 筛选;按 ID 批量远程取名回填所属公司/车队;VIN 管理页导出条件与列表对齐;两页搜索区公司与车队联动下拉 | ruoyi-api/ruoyi-api-order/.../api/model/TVinChargingOrder.java、ruoyi-api/ruoyi-api-order/.../api/query/VinChargingOrderQuery.java、ruoyi-api/ruoyi-api-order/.../api/vo/ChargingOrderVO.java、ruoyi-api/ruoyi-api-other/.../api/feignClient/GroupCompanyClient.java、ruoyi-api/ruoyi-api-other/.../api/factory/GroupCompanyFallbackFactory.java、ruoyi-service/ruoyi-order/.../service/impl/TVinChargingOrderServiceImpl.java、ruoyi-service/ruoyi-order/.../export/VinChargingOrderExport.java、ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TVinChargingOrderMapper.xml、ruoyi-service/ruoyi-order/src/test/.../mapper/VinChargingOrderMapperStructureTest.java、ruoyi-service/ruoyi-order/src/test/.../impl/TVinChargingOrderCompanyFleetTest.java(新增)、ruoyi-service/ruoyi-other/.../controller/TGroupCompanyController.java、ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TVinManagementMapper.xml、ruoyi-service/ruoyi-account/src/test/.../mapper/VinManagementMapperStructureTest.java(新增)、sql/20260928_vin_charging_order_company_fleet.sql(新增)、sql/20260928_vin_charging_order_company_fleet_backfill.sql(新增)、qianduan/chargingPile_management/src/components/FleetSearchSelect/(新增 index.jsx、fleetOptions.js、fleetOptions.test.js)、qianduan/chargingPile_management/src/pages/order/vinChargingOrder/index.jsx、service.js、service.test.js、qianduan/chargingPile_management/src/pages/vin/index.jsx | 后端 surefire 2.22.2 定向执行 ruoyi-order 四个测试类(TVinChargingOrderCompanyFleetTest 4、VinChargingOrderMapperStructureTest 1、TVinChargingOrderVinBackfillTest 5、TVinChargingOrderHandshakeTest 7)与 ruoyi-account VinManagementMapperStructureTest 1,全部通过;前端 npx jest 2 套件 5 用例通过;前端 npm run build 成功(BUILD_EXIT=0,dist 生成);IDE lint 0 错误 | SQL 未在数据库执行:分片表 DDL 需经 MyCat 代理或直连后端 MySQL 逐分表执行,历史订单回填脚本需人工生成语句并执行;建单快照语义为「建单当时归属」,车辆改归属不影响历史订单;导出 Excel 新增所属公司、所属车队两列;订单页列缓存 persistenceKey 需重置列设置;方案修订阶段(授权前)耗时未记录,开始时间取授权后首个产出文件创建时间;构建日志归档 logs/build-20260928-vinOrderCompanyFleet.log |
diff --git a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistImportDTO.java b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistImportDTO.java
index 4e9535f..0679dba 100644
--- a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistImportDTO.java
+++ b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistImportDTO.java
@@ -15,12 +15,4 @@
     @Excel(name = "用户手机号", width = 18, prompt = "填写11位手机号,如:13800138000")
     @ApiModelProperty(value = "用户手机号")
     private String phone;
-
-    @Excel(name = "享受权益", width = 18, prompt = "1=全部免费,2=免电费,3=免服务费", combo = {"1", "2", "3"})
-    @ApiModelProperty(value = "享受权益")
-    private String benefitType;
-
-    @Excel(name = "白名单电站", width = 40, prompt = "填写all表示全部电站;指定电站填写平台站点编号,多个用英文逗号分隔,如:ST001,ST002")
-    @ApiModelProperty(value = "白名单电站")
-    private String whitelistSite;
 }
diff --git a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistQueryDTO.java b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistQueryDTO.java
index 0935bdf..9cc34dc 100644
--- a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistQueryDTO.java
+++ b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistQueryDTO.java
@@ -16,4 +16,16 @@
 
     @ApiModelProperty(value = "用户手机号")
     private String phone;
+
+    @ApiModelProperty(value = "用户姓名")
+    private String name;
+
+    @ApiModelProperty(value = "所属公司ID")
+    private Integer companyId;
+
+    @ApiModelProperty(value = "所属车队ID")
+    private Integer fleetId;
+
+    @ApiModelProperty(value = "状态:1=启用,2=停用")
+    private Integer status;
 }
diff --git a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistSaveDTO.java b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistSaveDTO.java
index f5f8677..467c705 100644
--- a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistSaveDTO.java
+++ b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistSaveDTO.java
@@ -19,7 +19,13 @@
     @ApiModelProperty(value = "用户手机号")
     private String phone;
 
-    @ApiModelProperty(value = "享受权益:1=全部免费,2=免电费,3=免服务费")
+    @ApiModelProperty(value = "用户姓名(选填)")
+    private String name;
+
+    @ApiModelProperty(value = "状态:1=启用,2=停用(不传默认启用)")
+    private Integer status;
+
+    @ApiModelProperty(value = "享受权益:1=全部免费,2=免电费,3=免服务费(不传默认全部免费)")
     private Integer benefitType;
 
     @ApiModelProperty(value = "白名单电站范围:1=全部电站,2=指定站点,3=指定合作商")
@@ -28,6 +34,15 @@
     @ApiModelProperty(value = "合作商ID")
     private Integer partnerId;
 
+    @ApiModelProperty(value = "所属公司ID(关联t_group_company.id)")
+    private Integer companyId;
+
+    @ApiModelProperty(value = "所属车队ID(关联t_fleet.id),必须归属所选公司")
+    private Integer fleetId;
+
+    @ApiModelProperty(value = "初始分配额度(元),仅分配模式公司可填写,保存后从公司可用余额扣减")
+    private java.math.BigDecimal initQuota;
+
     @ApiModelProperty(value = "站点ID列表")
     private List<Integer> siteIds;
 
diff --git a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TAppUserWhitelist.java b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TAppUserWhitelist.java
index 42a2d5a..9168598 100644
--- a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TAppUserWhitelist.java
+++ b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TAppUserWhitelist.java
@@ -35,6 +35,10 @@
     @TableField("phone")
     private String phone;
 
+    @ApiModelProperty(value = "用户姓名(选填)")
+    @TableField("name")
+    private String name;
+
     @ApiModelProperty(value = "享受权益:1=全部免费,2=免电费,3=免服务费")
     @TableField("benefit_type")
     private Integer benefitType;
@@ -47,6 +51,26 @@
     @TableField("partner_id")
     private Integer partnerId;
 
+    @ApiModelProperty(value = "所属公司ID(关联t_group_company.id,跨库仅存ID)")
+    @TableField("company_id")
+    private Integer companyId;
+
+    @ApiModelProperty(value = "所属车队ID(关联t_fleet.id,跨库仅存ID)")
+    @TableField("fleet_id")
+    private Integer fleetId;
+
+    @ApiModelProperty(value = "初始分配额度(元):仅分配模式公司可设置,保存时从公司可用余额扣减")
+    @TableField("init_quota")
+    private java.math.BigDecimal initQuota;
+
+    @ApiModelProperty(value = "所属公司名称(列表远程回填,不落库)")
+    @TableField(exist = false)
+    private String companyName;
+
+    @ApiModelProperty(value = "所属车队名称(列表远程回填,不落库)")
+    @TableField(exist = false)
+    private String fleetName;
+
     @ApiModelProperty(value = "状态:1=启用,2=停用")
     @TableField("status")
     private Integer status;
diff --git a/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/model/TVinChargingOrder.java b/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/model/TVinChargingOrder.java
index cbc210f..bca2012 100644
--- a/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/model/TVinChargingOrder.java
+++ b/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/model/TVinChargingOrder.java
@@ -17,4 +17,12 @@
     @TableField("vin_id")
     private Long vinId;
 
+    @ApiModelProperty("所属公司ID(建单时VIN记录快照,关联t_group_company.id)")
+    @TableField("company_id")
+    private Integer companyId;
+
+    @ApiModelProperty("所属车队ID(建单时VIN记录快照,关联t_fleet.id)")
+    @TableField("fleet_id")
+    private Integer fleetId;
+
 }
diff --git a/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/query/VinChargingOrderQuery.java b/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/query/VinChargingOrderQuery.java
index 09d21c1..8b20c2f 100644
--- a/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/query/VinChargingOrderQuery.java
+++ b/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/query/VinChargingOrderQuery.java
@@ -40,6 +40,12 @@
     @ApiModelProperty("充电枪ID")
     private Integer gunId;
 
+    @ApiModelProperty("所属公司ID")
+    private Integer companyId;
+
+    @ApiModelProperty("所属车队ID")
+    private Integer fleetId;
+
     @ApiModelProperty("充电开始时间范围的开始时间")
     private String startTime1;
 
diff --git a/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/vo/ChargingOrderVO.java b/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/vo/ChargingOrderVO.java
index 0836079..5deb00e 100644
--- a/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/vo/ChargingOrderVO.java
+++ b/ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/vo/ChargingOrderVO.java
@@ -60,6 +60,14 @@
     private String vin;
     @ApiModelProperty(value = "VIN管理记录ID(VIN码充电订单)")
     private Long vinId;
+    @ApiModelProperty(value = "所属公司ID(VIN码充电订单建单快照)")
+    private Integer companyId;
+    @ApiModelProperty(value = "所属公司名称(按公司ID远程回填)")
+    private String companyName;
+    @ApiModelProperty(value = "所属车队ID(VIN码充电订单建单快照)")
+    private Integer fleetId;
+    @ApiModelProperty(value = "所属车队名称(按车队ID远程回填)")
+    private String fleetName;
     @ApiModelProperty(value = "时段数")
     private Integer count;
     @ApiModelProperty(value = "充电结束原因")
diff --git a/ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/factory/GroupCompanyFallbackFactory.java b/ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/factory/GroupCompanyFallbackFactory.java
index cf93c56..4a8f163 100644
--- a/ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/factory/GroupCompanyFallbackFactory.java
+++ b/ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/factory/GroupCompanyFallbackFactory.java
@@ -33,6 +33,11 @@
             }
 
             @Override
+            public R<List<TGroupCompany>> listCompaniesByIds(List<Integer> ids) {
+                return R.fail("查询公司列表失败:" + throwable.getMessage());
+            }
+
+            @Override
             public R<BigDecimal> deductBalance(Integer companyId, BigDecimal amount) {
                 return R.fail("扣减公司可用余额失败:" + throwable.getMessage());
             }
diff --git a/ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/feignClient/GroupCompanyClient.java b/ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/feignClient/GroupCompanyClient.java
index 794a393..360cbbb 100644
--- a/ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/feignClient/GroupCompanyClient.java
+++ b/ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/feignClient/GroupCompanyClient.java
@@ -29,6 +29,12 @@
     R<TGroupCompany> getCompanyById(@PathVariable("id") Integer id);
 
     /**
+     * 按ID批量查询公司(VIN 码充电订单列表回填公司名称使用)
+     */
+    @PostMapping("/group-company/remote/list-by-ids")
+    R<List<TGroupCompany>> listCompaniesByIds(@RequestBody List<Integer> ids);
+
+    /**
      * 扣减公司可用余额(条件更新,余额不足返回失败,返回扣减后余额)
      */
     @PostMapping("/group-company/remote/deduct-balance")
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserWhitelistController.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserWhitelistController.java
index 1bcdfc2..05585ce 100644
--- a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserWhitelistController.java
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserWhitelistController.java
@@ -85,7 +85,7 @@
     public void importTemplate(HttpServletResponse response) {
         ExcelUtil<AppUserWhitelistImportDTO> util = new ExcelUtil<>(AppUserWhitelistImportDTO.class);
         util.importTemplateExcel(response, "白名单用户导入模板",
-                "填写说明:用户手机号填写11位手机号;享受权益填写 1=全部免费、2=免电费、3=免服务费;白名单电站填写 all 表示全部电站,指定电站填写平台站点编号,多个用英文逗号分隔,如 ST001,ST002。");
+                "填写说明:用户手机号填写11位手机号,如:13800138000。导入后默认享受全部免费权益、适用于全部电站。");
     }
 
     @ApiOperation("导入白名单用户")
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImpl.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImpl.java
index db281a9..5d32587 100644
--- a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImpl.java
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImpl.java
@@ -29,6 +29,9 @@
 import com.ruoyi.common.core.web.domain.BasePojo;
 import com.ruoyi.common.security.service.TokenService;
 import com.ruoyi.common.security.utils.SecurityUtils;
+import com.ruoyi.other.api.domain.TFleet;
+import com.ruoyi.other.api.domain.TGroupCompany;
+import com.ruoyi.other.api.feignClient.GroupCompanyClient;
 import com.ruoyi.system.api.domain.SysUser;
 import com.ruoyi.system.api.feignClient.SysUserClient;
 import com.ruoyi.system.api.model.LoginUser;
@@ -36,6 +39,11 @@
 import org.apache.poi.ss.usermodel.Sheet;
 import org.apache.poi.ss.usermodel.Workbook;
 import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.PlatformTransactionManager;
 import org.springframework.transaction.TransactionDefinition;
@@ -45,6 +53,8 @@
 
 import javax.annotation.Resource;
 import java.io.InputStream;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -54,6 +64,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
@@ -63,10 +74,10 @@
 @Service
 public class TAppUserWhitelistServiceImpl extends ServiceImpl<TAppUserWhitelistMapper, TAppUserWhitelist> implements TAppUserWhitelistService {
 
+    private static final Logger log = LoggerFactory.getLogger(TAppUserWhitelistServiceImpl.class);
+
     private static final Pattern PHONE_PATTERN = Pattern.compile("^1\\d{10}$");
     private static final String IMPORT_HEADER_PHONE = "用户手机号";
-    private static final String IMPORT_HEADER_BENEFIT_TYPE = "享受权益";
-    private static final String IMPORT_HEADER_WHITELIST_SITE = "白名单电站";
     private static final int BENEFIT_ALL_FREE = 1;
     private static final int BENEFIT_ELECTRIC_FREE = 2;
     private static final int BENEFIT_SERVICE_FREE = 3;
@@ -74,6 +85,20 @@
     private static final int SCOPE_SELECTED_SITE = 2;
     private static final int SCOPE_PARTNER = 3;
     private static final int STATUS_ENABLED = 1;
+    private static final int STATUS_DISABLED = 2;
+    /** 姓名最大长度(对应 varchar(64)) */
+    private static final int MAX_NAME_LENGTH = 64;
+
+    /** 公司账户模式:2=分配模式(可设置初始分配额度并扣减公司可用余额) */
+    private static final int ACCOUNT_MODE_ALLOCATION = 2;
+    /** 初始分配额度上限(对应 decimal(12,2)) */
+    private static final BigDecimal MAX_INIT_QUOTA = new BigDecimal("99999999.99");
+    /** 初始分配额度并发锁前缀:新增按手机号、编辑按ID串行,防止并发下重复扣减或退还公司余额 */
+    private static final String QUOTA_LOCK_PREFIX = "APP_USER_WHITELIST_QUOTA:";
+    /** 获取锁最长等待时间(秒) */
+    private static final long LOCK_WAIT_SECONDS = 5L;
+    /** 锁自动释放时间(秒),兜住异常未解锁的场景 */
+    private static final long LOCK_LEASE_SECONDS = 30L;
 
     @Resource
     private TAppUserWhitelistSiteService whitelistSiteService;
@@ -89,6 +114,10 @@
     private SysUserClient sysUserClient;
     @Resource
     private PlatformTransactionManager transactionManager;
+    @Autowired
+    private GroupCompanyClient groupCompanyClient;
+    @Autowired
+    private RedissonClient redissonClient;
 
     @Override
     public Page<TAppUserWhitelist> pageList(AppUserWhitelistQueryDTO queryDTO) {
@@ -99,6 +128,10 @@
         String username = getUsername();
         LambdaQueryChainWrapper<TAppUserWhitelist> query = this.lambdaQuery()
                 .like(StringUtils.isNotBlank(queryDTO.getPhone()), TAppUserWhitelist::getPhone, queryDTO.getPhone())
+                .like(StringUtils.isNotBlank(queryDTO.getName()), TAppUserWhitelist::getName, queryDTO.getName())
+                .eq(queryDTO.getCompanyId() != null, TAppUserWhitelist::getCompanyId, queryDTO.getCompanyId())
+                .eq(queryDTO.getFleetId() != null, TAppUserWhitelist::getFleetId, queryDTO.getFleetId())
+                .eq(queryDTO.getStatus() != null, TAppUserWhitelist::getStatus, queryDTO.getStatus())
                 .eq(TAppUserWhitelist::getDelFlag, false);
         if (currentPartnerId != null) {
             if (StringUtils.isNotBlank(username)) {
@@ -171,7 +204,6 @@
             throw new ServiceException("导入数据不能为空");
         }
 
-        Map<String, Site> siteCodeMap = getSiteCodeMap();
         Map<String, AppUserWhitelistSaveDTO> validRows = new LinkedHashMap<>();
         Map<String, Integer> validRowNumbers = new HashMap<>();
         List<AppUserWhitelistImportErrorVO> errors = new ArrayList<>();
@@ -179,12 +211,12 @@
         for (int i = 0; i < rows.size(); i++) {
             AppUserWhitelistImportDTO row = rows.get(i);
             int rowNum = titleNum + i + 2;
-            List<String> rowErrors = validateImportRow(row, siteCodeMap);
+            List<String> rowErrors = validateImportRow(row);
             if (!rowErrors.isEmpty()) {
                 errors.add(new AppUserWhitelistImportErrorVO(rowNum, normalize(row.getPhone()), String.join(";", rowErrors)));
                 continue;
             }
-            AppUserWhitelistSaveDTO dto = buildSaveDTO(row, siteCodeMap);
+            AppUserWhitelistSaveDTO dto = buildSaveDTO(row);
             if (!importPhones.add(dto.getPhone())) {
                 errors.add(new AppUserWhitelistImportErrorVO(rowNum, dto.getPhone(), "导入文件中手机号重复"));
                 continue;
@@ -314,9 +346,7 @@
             return false;
         }
         ExcelUtil<AppUserWhitelistImportDTO> util = new ExcelUtil<>(AppUserWhitelistImportDTO.class);
-        return IMPORT_HEADER_PHONE.equals(normalize(String.valueOf(util.getCellValue(row, 0))))
-                && IMPORT_HEADER_BENEFIT_TYPE.equals(normalize(String.valueOf(util.getCellValue(row, 1))))
-                && IMPORT_HEADER_WHITELIST_SITE.equals(normalize(String.valueOf(util.getCellValue(row, 2))));
+        return IMPORT_HEADER_PHONE.equals(normalize(String.valueOf(util.getCellValue(row, 0))));
     }
 
     private void validateSaveDTO(AppUserWhitelistSaveDTO saveDTO) {
@@ -327,17 +357,36 @@
         if (!isValidPhone(saveDTO.getPhone())) {
             throw new ServiceException("请输入正确的用户手机号");
         }
-        if (!isValidBenefit(saveDTO.getBenefitType())) {
+        saveDTO.setName(normalize(saveDTO.getName()));
+        if (saveDTO.getName() != null && saveDTO.getName().isEmpty()) {
+            saveDTO.setName(null);
+        }
+        if (StringUtils.isNotBlank(saveDTO.getName()) && saveDTO.getName().length() > MAX_NAME_LENGTH) {
+            throw new ServiceException("姓名长度不能超过" + MAX_NAME_LENGTH + "个字符");
+        }
+        // 状态不传默认启用,仅允许1=启用、2=停用
+        Integer status = saveDTO.getStatus() == null ? STATUS_ENABLED : saveDTO.getStatus();
+        if (!Objects.equals(status, STATUS_ENABLED) && !Objects.equals(status, STATUS_DISABLED)) {
+            throw new ServiceException("请选择正确的状态");
+        }
+        saveDTO.setStatus(status);
+        // 享受权益不传默认全部免费,传了仍按原规则校验
+        Integer benefitType = saveDTO.getBenefitType() == null ? BENEFIT_ALL_FREE : saveDTO.getBenefitType();
+        if (!isValidBenefit(benefitType)) {
             throw new ServiceException("请选择正确的享受权益");
         }
-        if (!isValidScope(saveDTO.getScopeType())) {
+        saveDTO.setBenefitType(benefitType);
+        // 白名单电站不传默认全部电站,传了仍按原规则校验
+        Integer scopeType = saveDTO.getScopeType() == null ? SCOPE_ALL_SITE : saveDTO.getScopeType();
+        if (!isValidScope(scopeType)) {
             throw new ServiceException("请选择正确的白名单电站范围");
         }
-        if (Objects.equals(saveDTO.getScopeType(), SCOPE_SELECTED_SITE)
+        saveDTO.setScopeType(scopeType);
+        if (Objects.equals(scopeType, SCOPE_SELECTED_SITE)
                 && (saveDTO.getSiteIds() == null || saveDTO.getSiteIds().isEmpty())) {
             throw new ServiceException("请选择白名单电站");
         }
-        if (Objects.equals(saveDTO.getScopeType(), SCOPE_PARTNER) && saveDTO.getPartnerId() == null) {
+        if (Objects.equals(scopeType, SCOPE_PARTNER) && saveDTO.getPartnerId() == null) {
             throw new ServiceException("请选择加盟商");
         }
         validatePartnerDataScope(saveDTO);
@@ -354,81 +403,155 @@
         if (getByPhone(saveDTO.getPhone()) != null) {
             throw new ServiceException("该手机号已存在白名单配置");
         }
-        TAppUserWhitelist whitelist = new TAppUserWhitelist();
-        TAppUser appUser = appUserService.lambdaQuery()
-                .eq(TAppUser::getPhone, saveDTO.getPhone())
-                .last("LIMIT 1")
-                .one();
-        whitelist.setCreateBy(getUsername());
-        whitelist.setAppUserId(appUser == null ? null : appUser.getId());
-        whitelist.setPhone(saveDTO.getPhone());
-        whitelist.setBenefitType(saveDTO.getBenefitType());
-        whitelist.setScopeType(saveDTO.getScopeType());
-        whitelist.setPartnerId(resolveWhitelistPartnerId(saveDTO));
-        whitelist.setStatus(STATUS_ENABLED);
-        whitelist.setUpdateBy(getUsername());
-        whitelist.setRemark(saveDTO.getRemark());
-        whitelist.setDelFlag(false);
-        if (whitelist.getId() == null) {
-            this.save(whitelist);
-        }
-
-        whitelistSiteService.deleteByWhitelistId(whitelist.getId());
-        if (Objects.equals(saveDTO.getScopeType(), SCOPE_SELECTED_SITE)) {
-            Long whitelistId = whitelist.getId();
-            List<TAppUserWhitelistSite> sites = saveDTO.getSiteIds().stream()
-                    .filter(Objects::nonNull)
-                    .distinct()
-                    .map(siteId -> {
-                        TAppUserWhitelistSite site = new TAppUserWhitelistSite();
-                        site.setWhitelistId(whitelistId);
-                        site.setSiteId(siteId);
-                        site.setDelFlag(false);
-                        return site;
-                    }).collect(Collectors.toList());
-            if (!sites.isEmpty()) {
-                whitelistSiteService.saveBatch(sites);
+        // 同手机号并发新增串行化:手机号重复校验与额度扣减在同一临界区内
+        RLock lock = redissonClient.getLock(QUOTA_LOCK_PREFIX + "PHONE:" + saveDTO.getPhone());
+        boolean locked = tryQuotaLock(lock, "该手机号正在处理中,请稍后重试");
+        List<Runnable> compensations = new ArrayList<>();
+        try {
+            if (getByPhone(saveDTO.getPhone()) != null) {
+                throw new ServiceException("该手机号已存在白名单配置");
             }
+            BigDecimal quota = normalizeQuota(saveDTO.getInitQuota());
+            Integer companyId = resolveRelationCompanyId(saveDTO);
+            if (quota.signum() > 0) {
+                requireAllocationCompany(companyId);
+                deductBalance(companyId, quota);
+                compensations.add(() -> refundQuietly(companyId, quota));
+            }
+            TAppUserWhitelist whitelist = new TAppUserWhitelist();
+            TAppUser appUser = appUserService.lambdaQuery()
+                    .eq(TAppUser::getPhone, saveDTO.getPhone())
+                    .last("LIMIT 1")
+                    .one();
+            whitelist.setCreateBy(getUsername());
+            whitelist.setAppUserId(appUser == null ? null : appUser.getId());
+            whitelist.setPhone(saveDTO.getPhone());
+            whitelist.setName(saveDTO.getName());
+            whitelist.setBenefitType(saveDTO.getBenefitType());
+            whitelist.setScopeType(saveDTO.getScopeType());
+            whitelist.setPartnerId(resolveWhitelistPartnerId(saveDTO));
+            whitelist.setCompanyId(companyId);
+            whitelist.setFleetId(saveDTO.getFleetId());
+            whitelist.setInitQuota(quota.signum() > 0 ? quota : null);
+            whitelist.setStatus(saveDTO.getStatus());
+            whitelist.setUpdateBy(getUsername());
+            whitelist.setRemark(saveDTO.getRemark());
+            whitelist.setDelFlag(false);
+            if (whitelist.getId() == null) {
+                this.save(whitelist);
+            }
+
+            whitelistSiteService.deleteByWhitelistId(whitelist.getId());
+            if (Objects.equals(saveDTO.getScopeType(), SCOPE_SELECTED_SITE)) {
+                Long whitelistId = whitelist.getId();
+                List<TAppUserWhitelistSite> sites = saveDTO.getSiteIds().stream()
+                        .filter(Objects::nonNull)
+                        .distinct()
+                        .map(siteId -> {
+                            TAppUserWhitelistSite site = new TAppUserWhitelistSite();
+                            site.setWhitelistId(whitelistId);
+                            site.setSiteId(siteId);
+                            site.setDelFlag(false);
+                            return site;
+                        }).collect(Collectors.toList());
+                if (!sites.isEmpty()) {
+                    whitelistSiteService.saveBatch(sites);
+                }
+            }
+        } catch (RuntimeException ex) {
+            compensate(compensations);
+            throw ex;
+        } finally {
+            unlockQuota(lock, locked);
         }
     }
 
     private void updateWhitelist(AppUserWhitelistSaveDTO saveDTO) {
-        TAppUserWhitelist whitelist = this.getById(saveDTO.getId());
-        if (whitelist == null) {
+        TAppUserWhitelist existing = this.getById(saveDTO.getId());
+        if (existing == null) {
             throw new ServiceException("白名单用户不存在");
         }
-        assertPartnerVisible(whitelist);
-        TAppUser appUser = appUserService.lambdaQuery()
-                .eq(TAppUser::getPhone, saveDTO.getPhone())
-                .last("LIMIT 1")
-                .one();
-        whitelist.setAppUserId(appUser == null ? null : appUser.getId());
-        whitelist.setPhone(saveDTO.getPhone());
-        whitelist.setBenefitType(saveDTO.getBenefitType());
-        whitelist.setScopeType(saveDTO.getScopeType());
-        whitelist.setPartnerId(resolveWhitelistPartnerId(saveDTO));
-        whitelist.setStatus(STATUS_ENABLED);
-        whitelist.setUpdateBy(getUsername());
-        whitelist.setRemark(saveDTO.getRemark());
-        whitelist.setDelFlag(false);
-        this.baseMapper.updateByIdIgnoreLogic(whitelist);
-
-        whitelistSiteService.deleteByWhitelistId(whitelist.getId());
-        if (Objects.equals(saveDTO.getScopeType(), SCOPE_SELECTED_SITE)) {
-            Long whitelistId = whitelist.getId();
-            List<TAppUserWhitelistSite> sites = saveDTO.getSiteIds().stream()
-                    .filter(Objects::nonNull)
-                    .distinct()
-                    .map(siteId -> {
-                        TAppUserWhitelistSite site = new TAppUserWhitelistSite();
-                        site.setWhitelistId(whitelistId);
-                        site.setSiteId(siteId);
-                        site.setDelFlag(false);
-                        return site;
-                    }).collect(Collectors.toList());
-            if (!sites.isEmpty()) {
-                whitelistSiteService.saveBatch(sites);
+        assertPartnerVisible(existing);
+        // 同白名单并发编辑串行化:额度差额计算、公司余额扣减/退还必须在临界区内,防止重复扣减或重复退还
+        RLock lock = redissonClient.getLock(QUOTA_LOCK_PREFIX + saveDTO.getId());
+        boolean locked = tryQuotaLock(lock, "该白名单额度正在处理中,请稍后重试");
+        List<Runnable> compensations = new ArrayList<>();
+        try {
+            TAppUserWhitelist whitelist = this.getById(saveDTO.getId());
+            if (whitelist == null) {
+                throw new ServiceException("白名单用户不存在");
             }
+            assertPartnerVisible(whitelist);
+            BigDecimal newQuota = normalizeQuota(saveDTO.getInitQuota());
+            Integer newCompanyId = resolveRelationCompanyId(saveDTO);
+            if (newQuota.signum() > 0) {
+                requireAllocationCompany(newCompanyId);
+            }
+            Integer oldCompanyId = whitelist.getCompanyId();
+            BigDecimal oldQuota = normalizeQuota(whitelist.getInitQuota());
+            if (Objects.equals(oldCompanyId, newCompanyId)) {
+                // 同一公司按差额调整:调增扣差额,调减退差额
+                BigDecimal diff = newQuota.subtract(oldQuota);
+                if (diff.signum() > 0 && newCompanyId != null) {
+                    deductBalance(newCompanyId, diff);
+                    compensations.add(() -> refundQuietly(newCompanyId, diff));
+                } else if (diff.signum() < 0 && newCompanyId != null) {
+                    BigDecimal refund = diff.negate();
+                    refundBalance(newCompanyId, refund);
+                    compensations.add(() -> deductQuietly(newCompanyId, refund));
+                }
+            } else {
+                // 跨公司:旧公司全额退还,新公司全额扣减
+                if (oldCompanyId != null && oldQuota.signum() > 0) {
+                    refundBalance(oldCompanyId, oldQuota);
+                    compensations.add(() -> deductQuietly(oldCompanyId, oldQuota));
+                }
+                if (newCompanyId != null && newQuota.signum() > 0) {
+                    deductBalance(newCompanyId, newQuota);
+                    compensations.add(() -> refundQuietly(newCompanyId, newQuota));
+                }
+            }
+            TAppUser appUser = appUserService.lambdaQuery()
+                    .eq(TAppUser::getPhone, saveDTO.getPhone())
+                    .last("LIMIT 1")
+                    .one();
+            whitelist.setAppUserId(appUser == null ? null : appUser.getId());
+            whitelist.setPhone(saveDTO.getPhone());
+            whitelist.setName(saveDTO.getName());
+            whitelist.setBenefitType(saveDTO.getBenefitType());
+            whitelist.setScopeType(saveDTO.getScopeType());
+            whitelist.setPartnerId(resolveWhitelistPartnerId(saveDTO));
+            whitelist.setCompanyId(newCompanyId);
+            whitelist.setFleetId(saveDTO.getFleetId());
+            whitelist.setInitQuota(newQuota.signum() > 0 ? newQuota : null);
+            whitelist.setStatus(saveDTO.getStatus());
+            whitelist.setUpdateBy(getUsername());
+            whitelist.setRemark(saveDTO.getRemark());
+            whitelist.setDelFlag(false);
+            this.baseMapper.updateByIdIgnoreLogic(whitelist);
+
+            whitelistSiteService.deleteByWhitelistId(whitelist.getId());
+            if (Objects.equals(saveDTO.getScopeType(), SCOPE_SELECTED_SITE)) {
+                Long whitelistId = whitelist.getId();
+                List<TAppUserWhitelistSite> sites = saveDTO.getSiteIds().stream()
+                        .filter(Objects::nonNull)
+                        .distinct()
+                        .map(siteId -> {
+                            TAppUserWhitelistSite site = new TAppUserWhitelistSite();
+                            site.setWhitelistId(whitelistId);
+                            site.setSiteId(siteId);
+                            site.setDelFlag(false);
+                            return site;
+                        }).collect(Collectors.toList());
+                if (!sites.isEmpty()) {
+                    whitelistSiteService.saveBatch(sites);
+                }
+            }
+        } catch (RuntimeException ex) {
+            compensate(compensations);
+            throw ex;
+        } finally {
+            unlockQuota(lock, locked);
         }
     }
 
@@ -442,7 +565,7 @@
         return this.baseMapper.selectValidByPhone(phone);
     }
 
-    private List<String> validateImportRow(AppUserWhitelistImportDTO row, Map<String, Site> siteCodeMap) {
+    private List<String> validateImportRow(AppUserWhitelistImportDTO row) {
         List<String> errors = new ArrayList<>();
         if (row == null) {
             errors.add("空行");
@@ -452,57 +575,17 @@
         if (!isValidPhone(phone)) {
             errors.add("用户手机号格式错误");
         }
-        Integer benefitType = parseInteger(row.getBenefitType());
-        if (!isValidBenefit(benefitType)) {
-            errors.add("享受权益只能填写1、2、3");
-        }
-        String whitelistSite = normalize(row.getWhitelistSite());
-        if (StringUtils.isBlank(whitelistSite)) {
-            errors.add("白名单电站不能为空");
-        } else if (!"all".equalsIgnoreCase(whitelistSite)) {
-            for (String code : whitelistSite.split(",")) {
-                String siteCode = normalize(code);
-                if (StringUtils.isBlank(siteCode)) {
-                    errors.add("白名单电站存在空编号");
-                } else if (!siteCodeMap.containsKey(siteCode)) {
-                    errors.add("站点编号不存在:" + siteCode);
-                }
-            }
-        }
         return errors;
     }
 
-    private AppUserWhitelistSaveDTO buildSaveDTO(AppUserWhitelistImportDTO row, Map<String, Site> siteCodeMap) {
+    /** 导入数据统一按默认值处理:全部免费 + 全部电站 */
+    private AppUserWhitelistSaveDTO buildSaveDTO(AppUserWhitelistImportDTO row) {
         AppUserWhitelistSaveDTO dto = new AppUserWhitelistSaveDTO();
         dto.setPhone(normalize(row.getPhone()));
-        dto.setBenefitType(parseInteger(row.getBenefitType()));
-        String whitelistSite = normalize(row.getWhitelistSite());
-        if ("all".equalsIgnoreCase(whitelistSite)) {
-            dto.setScopeType(SCOPE_ALL_SITE);
-            dto.setSiteIds(Collections.emptyList());
-        } else {
-            dto.setScopeType(SCOPE_SELECTED_SITE);
-            List<Integer> siteIds = new ArrayList<>();
-            for (String code : whitelistSite.split(",")) {
-                Site site = siteCodeMap.get(normalize(code));
-                if (site != null) {
-                    siteIds.add(site.getId());
-                }
-            }
-            dto.setSiteIds(siteIds.stream().distinct().collect(Collectors.toList()));
-        }
+        dto.setBenefitType(BENEFIT_ALL_FREE);
+        dto.setScopeType(SCOPE_ALL_SITE);
+        dto.setSiteIds(Collections.emptyList());
         return dto;
-    }
-
-    private Map<String, Site> getSiteCodeMap() {
-        List<Site> sites = getCurrentSelectableSites();
-        Map<String, Site> siteCodeMap = new HashMap<>();
-        for (Site site : sites) {
-            if (site != null && StringUtils.isNotBlank(site.getCode())) {
-                siteCodeMap.put(site.getCode(), site);
-            }
-        }
-        return siteCodeMap;
     }
 
     private void fillDisplay(List<TAppUserWhitelist> records) {
@@ -536,6 +619,59 @@
             }
             record.setSiteList(sites);
             record.setSiteIds(sites.stream().map(TAppUserWhitelistSite::getSiteId).collect(Collectors.toList()));
+        }
+        fillCompanyNames(records);
+        fillFleetNames(records);
+    }
+
+    /** 列表回填所属公司名称(远程失败仅记录日志,不影响列表查询) */
+    private void fillCompanyNames(List<TAppUserWhitelist> records) {
+        Map<Integer, String> nameMap = new HashMap<>();
+        for (TAppUserWhitelist record : records) {
+            Integer companyId = record.getCompanyId();
+            if (companyId == null) {
+                continue;
+            }
+            if (nameMap.containsKey(companyId)) {
+                record.setCompanyName(nameMap.get(companyId));
+                continue;
+            }
+            String companyName = null;
+            try {
+                TGroupCompany company = loadCompany(companyId);
+                companyName = company == null ? null : company.getName();
+            } catch (Exception ex) {
+                log.warn("回填公司名称失败,companyId={}", companyId, ex);
+            }
+            nameMap.put(companyId, companyName);
+            record.setCompanyName(companyName);
+        }
+    }
+
+    /** 列表回填所属车队名称(批量查询,远程失败仅记录日志,不影响列表查询) */
+    private void fillFleetNames(List<TAppUserWhitelist> records) {
+        List<Integer> fleetIds = records.stream()
+                .map(TAppUserWhitelist::getFleetId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        if (fleetIds.isEmpty()) {
+            return;
+        }
+        try {
+            R<List<TFleet>> response = groupCompanyClient.listFleetsByIds(fleetIds);
+            if (response == null || !R.isSuccess(response) || response.getData() == null) {
+                return;
+            }
+            Map<Integer, String> nameMap = new HashMap<>();
+            for (TFleet fleet : response.getData()) {
+                if (fleet != null && fleet.getId() != null) {
+                    nameMap.put(fleet.getId(), fleet.getFleetName());
+                }
+            }
+            records.forEach(record -> record.setFleetName(nameMap.get(record.getFleetId())));
+        } catch (Exception ex) {
+            log.warn("回填车队名称失败,fleetIds={}", fleetIds, ex);
         }
     }
 
@@ -623,21 +759,155 @@
         return Objects.equals(saveDTO.getScopeType(), SCOPE_PARTNER) ? saveDTO.getPartnerId() : null;
     }
 
-    private List<Site> getCurrentSelectableSites() {
-        Integer currentPartnerId = getCurrentPartnerId();
-        if (currentPartnerId == null) {
-            R<List<Site>> siteResult = siteClient.getSiteAll();
-            return siteResult == null || siteResult.getData() == null ? Collections.emptyList() : siteResult.getData();
+    /** 校验并回填所属公司与车队:车队必须归属所选公司;未选公司时按车队自动带出公司 */
+    private Integer resolveRelationCompanyId(AppUserWhitelistSaveDTO saveDTO) {
+        Integer companyId = saveDTO.getCompanyId();
+        if (companyId != null && loadCompany(companyId) == null) {
+            throw new ServiceException("所属公司不存在或已删除");
         }
-        Set<Integer> siteIds = getCurrentPartnerSiteIds(currentPartnerId);
-        if (siteIds.isEmpty()) {
-            return Collections.emptyList();
+        Integer fleetId = saveDTO.getFleetId();
+        if (fleetId != null) {
+            TFleet fleet = loadFleet(fleetId);
+            if (fleet == null) {
+                throw new ServiceException("所属车队不存在或已删除");
+            }
+            if (companyId == null) {
+                companyId = fleet.getCompanyId();
+            } else if (!companyId.equals(fleet.getCompanyId())) {
+                throw new ServiceException("所属车队不属于所选公司");
+            }
         }
-        R<List<Site>> siteResult = siteClient.getSiteByIds(new ArrayList<>(siteIds));
-        List<Site> sites = siteResult == null || siteResult.getData() == null ? Collections.emptyList() : siteResult.getData();
-        return sites.stream()
-                .filter(site -> site != null && Objects.equals(site.getPartnerId(), currentPartnerId))
-                .collect(Collectors.toList());
+        return companyId;
+    }
+
+    /** 规范化初始分配额度:null 视为 0,不允许负数、超过2位小数或超出上限 */
+    private BigDecimal normalizeQuota(BigDecimal quota) {
+        if (quota == null) {
+            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+        }
+        if (quota.signum() < 0) {
+            throw new ServiceException("初始分配额度不能小于0");
+        }
+        if (quota.stripTrailingZeros().scale() > 2) {
+            throw new ServiceException("初始分配额度最多保留2位小数");
+        }
+        BigDecimal normalized = quota.setScale(2, RoundingMode.HALF_UP);
+        if (normalized.compareTo(MAX_INIT_QUOTA) > 0) {
+            throw new ServiceException("初始分配额度不能超过" + MAX_INIT_QUOTA.toPlainString());
+        }
+        return normalized;
+    }
+
+    /** 校验公司为启用且分配模式(可设置初始分配额度) */
+    private void requireAllocationCompany(Integer companyId) {
+        if (companyId == null) {
+            throw new ServiceException("请选择所属公司后再填写初始分配额度");
+        }
+        TGroupCompany company = loadCompany(companyId);
+        if (company == null) {
+            throw new ServiceException("所属公司不存在或已删除");
+        }
+        if (!Integer.valueOf(ACCOUNT_MODE_ALLOCATION).equals(company.getAccountMode())) {
+            throw new ServiceException("仅分配模式的公司可填写初始分配额度");
+        }
+        if (!Integer.valueOf(1).equals(company.getStatus())) {
+            throw new ServiceException("所属公司已停用,无法填写初始分配额度");
+        }
+    }
+
+    private TGroupCompany loadCompany(Integer companyId) {
+        R<TGroupCompany> response = groupCompanyClient.getCompanyById(companyId);
+        if (response == null || !R.isSuccess(response)) {
+            throw new ServiceException("公司信息查询失败:" + (response == null ? "服务无响应" : response.getMsg()));
+        }
+        return response.getData();
+    }
+
+    private TFleet loadFleet(Integer fleetId) {
+        R<TFleet> response = groupCompanyClient.getFleetById(fleetId);
+        if (response == null || !R.isSuccess(response)) {
+            throw new ServiceException("车队信息查询失败:" + (response == null ? "服务无响应" : response.getMsg()));
+        }
+        return response.getData();
+    }
+
+    /** 扣减公司可用余额(余额不足或服务异常时抛出业务异常) */
+    private void deductBalance(Integer companyId, BigDecimal amount) {
+        R<BigDecimal> response = groupCompanyClient.deductBalance(companyId, amount);
+        if (response == null || !R.isSuccess(response)) {
+            throw new ServiceException("公司可用余额扣减失败:" + (response == null ? "服务无响应" : response.getMsg()));
+        }
+    }
+
+    /** 退还公司可用余额(失败时抛出业务异常) */
+    private void refundBalance(Integer companyId, BigDecimal amount) {
+        R<BigDecimal> response = groupCompanyClient.refundBalance(companyId, amount);
+        if (response == null || !R.isSuccess(response)) {
+            throw new ServiceException("公司可用余额退还失败:" + (response == null ? "服务无响应" : response.getMsg()));
+        }
+    }
+
+    /** 静默退还(补偿场景使用,失败仅记录日志) */
+    private void refundQuietly(Integer companyId, BigDecimal amount) {
+        try {
+            R<BigDecimal> response = groupCompanyClient.refundBalance(companyId, amount);
+            if (response == null || !R.isSuccess(response)) {
+                log.error("公司可用余额退还失败,需人工核对,companyId={}, amount={}, msg={}",
+                        companyId, amount, response == null ? "服务无响应" : response.getMsg());
+            }
+        } catch (Exception ex) {
+            log.error("公司可用余额退还异常,需人工核对,companyId={}, amount={}", companyId, amount, ex);
+        }
+    }
+
+    /** 静默扣减(补偿场景使用,失败仅记录日志) */
+    private void deductQuietly(Integer companyId, BigDecimal amount) {
+        try {
+            R<BigDecimal> response = groupCompanyClient.deductBalance(companyId, amount);
+            if (response == null || !R.isSuccess(response)) {
+                log.error("公司可用余额回滚扣减失败,需人工核对,companyId={}, amount={}, msg={}",
+                        companyId, amount, response == null ? "服务无响应" : response.getMsg());
+            }
+        } catch (Exception ex) {
+            log.error("公司可用余额回滚扣减异常,需人工核对,companyId={}, amount={}", companyId, amount, ex);
+        }
+    }
+
+    /** 保存/更新失败时反向补偿已执行的余额操作(倒序执行) */
+    private void compensate(List<Runnable> compensations) {
+        for (int i = compensations.size() - 1; i >= 0; i--) {
+            try {
+                compensations.get(i).run();
+            } catch (Exception ex) {
+                log.error("公司可用余额补偿失败,需人工核对", ex);
+            }
+        }
+    }
+
+    /** 抢占并发锁,抢占失败抛出业务异常 */
+    private boolean tryQuotaLock(RLock lock, String busyMessage) {
+        try {
+            if (!lock.tryLock(LOCK_WAIT_SECONDS, LOCK_LEASE_SECONDS, TimeUnit.SECONDS)) {
+                throw new ServiceException(busyMessage);
+            }
+            return true;
+        } catch (InterruptedException ex) {
+            Thread.currentThread().interrupt();
+            throw new ServiceException(busyMessage);
+        }
+    }
+
+    private void unlockQuota(RLock lock, boolean locked) {
+        if (!locked) {
+            return;
+        }
+        try {
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        } catch (Exception ex) {
+            log.warn("释放白名单额度并发锁失败", ex);
+        }
     }
 
     private Set<Integer> getCurrentPartnerSiteIds(Integer partnerId) {
@@ -706,21 +976,6 @@
         return Objects.equals(scopeType, SCOPE_ALL_SITE)
                 || Objects.equals(scopeType, SCOPE_SELECTED_SITE)
                 || Objects.equals(scopeType, SCOPE_PARTNER);
-    }
-
-    private Integer parseInteger(String value) {
-        try {
-            String normalizedValue = normalize(value);
-            if (StringUtils.isBlank(normalizedValue)) {
-                return null;
-            }
-            if (normalizedValue.matches("^\\d+\\.0+$")) {
-                normalizedValue = normalizedValue.substring(0, normalizedValue.indexOf("."));
-            }
-            return Integer.valueOf(normalizedValue);
-        } catch (Exception e) {
-            return null;
-        }
     }
 
     private String normalize(String value) {
diff --git a/ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TAppUserWhitelistMapper.xml b/ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TAppUserWhitelistMapper.xml
index df52329..8acea72 100644
--- a/ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TAppUserWhitelistMapper.xml
+++ b/ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TAppUserWhitelistMapper.xml
@@ -6,9 +6,13 @@
         <id column="id" property="id" />
         <result column="app_user_id" property="appUserId" />
         <result column="phone" property="phone" />
+        <result column="name" property="name" />
         <result column="benefit_type" property="benefitType" />
         <result column="scope_type" property="scopeType" />
         <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="status" property="status" />
         <result column="create_by" property="createBy" />
         <result column="create_time" property="createTime" />
@@ -18,7 +22,7 @@
     </resultMap>
 
     <sql id="Base_Column_List">
-        id, app_user_id, phone, benefit_type, scope_type, partner_id, 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, init_quota, status, create_by, create_time, update_by, update_time, remark, del_flag
     </sql>
 
     <select id="selectValidByPhone" resultMap="BaseResultMap">
@@ -34,9 +38,13 @@
         <set>
             app_user_id = #{appUserId},
             phone = #{phone},
+            name = #{name},
             benefit_type = #{benefitType},
             scope_type = #{scopeType},
             partner_id = #{partnerId},
+            company_id = #{companyId},
+            fleet_id = #{fleetId},
+            init_quota = #{initQuota},
             status = #{status},
             update_by = #{updateBy},
             update_time = now(),
diff --git a/ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TVinManagementMapper.xml b/ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TVinManagementMapper.xml
index 2e9cf10..5856f50 100644
--- a/ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TVinManagementMapper.xml
+++ b/ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TVinManagementMapper.xml
@@ -19,6 +19,8 @@
         <if test="query != null and query.plateNumber != null and query.plateNumber != ''">AND plate_number LIKE CONCAT('%', #{query.plateNumber}, '%')</if>
         <if test="query != null and query.phone != null and query.phone != ''">AND phone LIKE CONCAT('%', #{query.phone}, '%')</if>
         <if test="query != null and query.status != null">AND status = #{query.status}</if>
+        <if test="query != null and query.companyId != null">AND company_id = #{query.companyId}</if>
+        <if test="query != null and query.fleetId != null">AND fleet_id = #{query.fleetId}</if>
         ORDER BY create_time DESC, id DESC
     </select>
     <select id="countByVin" resultType="int">SELECT COUNT(1) FROM t_vin_management WHERE vin = #{vin}<if test="excludeId != null"> AND id != #{excludeId}</if></select>
diff --git a/ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/mapper/VinManagementMapperStructureTest.java b/ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/mapper/VinManagementMapperStructureTest.java
new file mode 100644
index 0000000..4d1d241
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/mapper/VinManagementMapperStructureTest.java
@@ -0,0 +1,44 @@
+package com.ruoyi.account.mapper;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * VIN 管理 Mapper 结构断言:列表与导出必须共用同一套筛选条件(含所属公司/车队),避免导出结果与列表不一致。
+ */
+class VinManagementMapperStructureTest {
+
+    @Test
+    void pageListAndExportShareCompanyAndFleetFilters() throws IOException {
+        InputStream input = getClass().getClassLoader().getResourceAsStream("mapper/account/TVinManagementMapper.xml");
+        assertNotNull(input);
+        String mapper;
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
+            mapper = reader.lines().collect(Collectors.joining("\n")).toLowerCase();
+        }
+        assertTrue(mapper.contains("select id=\"selectpagelist\""));
+        assertTrue(mapper.contains("select id=\"selectexportlist\""));
+        assertEquals(2, occurrences(mapper, "and company_id = #{query.companyid}"));
+        assertEquals(2, occurrences(mapper, "and fleet_id = #{query.fleetid}"));
+    }
+
+    private int occurrences(String content, String keyword) {
+        int count = 0;
+        int index = content.indexOf(keyword);
+        while (index >= 0) {
+            count++;
+            index = content.indexOf(keyword, index + keyword.length());
+        }
+        return count;
+    }
+}
diff --git a/ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImplTest.java b/ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImplTest.java
index f7d525a..b5b55f6 100644
--- a/ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImplTest.java
+++ b/ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImplTest.java
@@ -11,6 +11,7 @@
 import com.ruoyi.chargingPile.api.feignClient.SiteClient;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.security.service.TokenService;
+import com.ruoyi.other.api.feignClient.GroupCompanyClient;
 import com.ruoyi.system.api.feignClient.SysUserClient;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.junit.jupiter.api.BeforeEach;
@@ -18,6 +19,8 @@
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.springframework.mock.web.MockMultipartFile;
 import org.springframework.test.util.ReflectionTestUtils;
 import org.springframework.transaction.PlatformTransactionManager;
@@ -25,10 +28,12 @@
 
 import java.io.ByteArrayOutputStream;
 import java.util.Collections;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -44,6 +49,8 @@
     @Mock private TokenService tokenService;
     @Mock private SysUserClient sysUserClient;
     @Mock private PlatformTransactionManager transactionManager;
+    @Mock private GroupCompanyClient groupCompanyClient;
+    @Mock private RedissonClient redissonClient;
 
     private TAppUserWhitelistServiceImpl service;
 
@@ -58,6 +65,15 @@
         ReflectionTestUtils.setField(service, "tokenService", tokenService);
         ReflectionTestUtils.setField(service, "sysUserClient", sysUserClient);
         ReflectionTestUtils.setField(service, "transactionManager", transactionManager);
+        ReflectionTestUtils.setField(service, "groupCompanyClient", groupCompanyClient);
+        ReflectionTestUtils.setField(service, "redissonClient", redissonClient);
+        RLock lock = mock(RLock.class);
+        when(redissonClient.getLock(any())).thenReturn(lock);
+        try {
+            when(lock.tryLock(anyLong(), anyLong(), any(TimeUnit.class))).thenReturn(true);
+        } catch (InterruptedException exception) {
+            throw new IllegalStateException(exception);
+        }
     }
 
     @Test
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/VinChargingOrderExport.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/VinChargingOrderExport.java
index 26bbafe..dbb0906 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/VinChargingOrderExport.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/export/VinChargingOrderExport.java
@@ -16,6 +16,10 @@
     private String code;
     @Excel(name = "VIN码", width = 24)
     private String vin;
+    @Excel(name = "所属公司", width = 24)
+    private String companyName;
+    @Excel(name = "所属车队", width = 20)
+    private String fleetName;
     @Excel(name = "站点名称", width = 24)
     private String siteName;
     @Excel(name = "充电终端名称", width = 24)
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TVinChargingOrderServiceImpl.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TVinChargingOrderServiceImpl.java
index 1216e35..9ac1315 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TVinChargingOrderServiceImpl.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TVinChargingOrderServiceImpl.java
@@ -52,6 +52,9 @@
 import com.ruoyi.order.service.TVinChargingOrderService;
 import com.ruoyi.order.util.TransactionStageParser;
 import com.ruoyi.order.util.mongodb.service.UploadRealTimeMonitoringDataService;
+import com.ruoyi.other.api.domain.TFleet;
+import com.ruoyi.other.api.domain.TGroupCompany;
+import com.ruoyi.other.api.feignClient.GroupCompanyClient;
 import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 import org.slf4j.Logger;
@@ -135,6 +138,8 @@
     private ChargingPileClient chargingPileClient;
     @Resource
     private SendMessageClient sendMessageClient;
+    @Resource
+    private GroupCompanyClient groupCompanyClient;
     @Resource
     private UploadRealTimeMonitoringDataService monitoringDataService;
     @Resource
@@ -538,6 +543,7 @@
             item.setPartnerIncome(BigDecimal.ZERO);
         }
         fillVinInfoAndPhone(list);
+        fillCompanyFleetNames(list);
     }
 
     /**
@@ -581,6 +587,73 @@
                 }
             }
         }
+    }
+
+    /**
+     * 回填所属公司/车队名称:按订单快照 companyId/fleetId 批量远程取名(公司/车队改名后列表即时生效),
+     * 远程调用失败仅跳过回填,不影响列表其他字段。
+     */
+    private void fillCompanyFleetNames(List<ChargingOrderVO> list) {
+        if (list == null || list.isEmpty()) {
+            return;
+        }
+        List<Integer> companyIds = list.stream().map(ChargingOrderVO::getCompanyId)
+                .filter(Objects::nonNull).distinct().collect(Collectors.toList());
+        if (!companyIds.isEmpty()) {
+            Map<Integer, String> companyNames = loadCompanyNames(companyIds);
+            for (ChargingOrderVO item : list) {
+                String name = item.getCompanyId() == null ? null : companyNames.get(item.getCompanyId());
+                if (StringUtils.isNotEmpty(name)) {
+                    item.setCompanyName(name);
+                }
+            }
+        }
+        List<Integer> fleetIds = list.stream().map(ChargingOrderVO::getFleetId)
+                .filter(Objects::nonNull).distinct().collect(Collectors.toList());
+        if (fleetIds.isEmpty()) {
+            return;
+        }
+        Map<Integer, String> fleetNames = loadFleetNames(fleetIds);
+        for (ChargingOrderVO item : list) {
+            String name = item.getFleetId() == null ? null : fleetNames.get(item.getFleetId());
+            if (StringUtils.isNotEmpty(name)) {
+                item.setFleetName(name);
+            }
+        }
+    }
+
+    /** 批量查询公司名称,失败返回已取到的数据 */
+    private Map<Integer, String> loadCompanyNames(List<Integer> companyIds) {
+        Map<Integer, String> result = new HashMap<>();
+        try {
+            List<TGroupCompany> companies = Optional.ofNullable(groupCompanyClient.listCompaniesByIds(companyIds).getData())
+                    .orElse(Collections.emptyList());
+            for (TGroupCompany company : companies) {
+                if (company != null && company.getId() != null && StringUtils.isNotEmpty(company.getName())) {
+                    result.put(company.getId(), company.getName());
+                }
+            }
+        } catch (Exception e) {
+            log.warn("[VD回填] 批量查询公司名称失败 ids={}", companyIds, e);
+        }
+        return result;
+    }
+
+    /** 批量查询车队名称,失败返回已取到的数据 */
+    private Map<Integer, String> loadFleetNames(List<Integer> fleetIds) {
+        Map<Integer, String> result = new HashMap<>();
+        try {
+            List<TFleet> fleets = Optional.ofNullable(groupCompanyClient.listFleetsByIds(fleetIds).getData())
+                    .orElse(Collections.emptyList());
+            for (TFleet fleet : fleets) {
+                if (fleet != null && fleet.getId() != null && StringUtils.isNotEmpty(fleet.getFleetName())) {
+                    result.put(fleet.getId(), fleet.getFleetName());
+                }
+            }
+        } catch (Exception e) {
+            log.warn("[VD回填] 批量查询车队名称失败 ids={}", fleetIds, e);
+        }
+        return result;
     }
 
     /** 手机号回填优先级:订单值 → VIN 记录手机号 → App 用户当前手机号 */
@@ -733,6 +806,9 @@
                 order.setExpectedVin(vinRecord.getVin());
                 order.setActualVin(vin);
                 order.setPlateNum(vinRecord.getPlateNumber());
+                // 所属公司/车队快照落库(VIN 管理记录当前归属),供订单列表/汇总/导出按公司、车队直接筛选
+                order.setCompanyId(vinRecord.getCompanyId());
+                order.setFleetId(vinRecord.getFleetId());
                 order.setCreateTime(LocalDateTime.now());
                 save(order);
 
diff --git a/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TVinChargingOrderMapper.xml b/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TVinChargingOrderMapper.xml
index 46e4146..07b27c8 100644
--- a/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TVinChargingOrderMapper.xml
+++ b/ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TVinChargingOrderMapper.xml
@@ -15,6 +15,8 @@
             </if>
             <if test="query.plateNum != null and query.plateNum != ''">AND o.plate_num LIKE CONCAT('%', #{query.plateNum}, '%')</if>
             <if test="query.phone != null and query.phone != ''">AND o.phone LIKE CONCAT('%', #{query.phone}, '%')</if>
+            <if test="query.companyId != null">AND o.company_id = #{query.companyId}</if>
+            <if test="query.fleetId != null">AND o.fleet_id = #{query.fleetId}</if>
             <if test="query.status != null">AND o.status = #{query.status}</if>
             <if test="query.siteId != null">AND o.site_id = #{query.siteId}</if>
             <if test="query.pileId != null">AND o.charging_pile_id = #{query.pileId}</if>
diff --git a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/VinChargingOrderMapperStructureTest.java b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/VinChargingOrderMapperStructureTest.java
index 601627c..9133b7d 100644
--- a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/VinChargingOrderMapperStructureTest.java
+++ b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/mapper/VinChargingOrderMapperStructureTest.java
@@ -29,6 +29,10 @@
         assertTrue(mapper.contains("query.platenum"));
         assertTrue(mapper.contains("query.phone"));
         assertTrue(mapper.contains("query.siteids"));
+        assertTrue(mapper.contains("query.companyid"));
+        assertTrue(mapper.contains("query.fleetid"));
+        assertTrue(mapper.contains("o.company_id = #{query.companyid}"));
+        assertTrue(mapper.contains("o.fleet_id = #{query.fleetid}"));
         assertTrue(mapper.contains("countaccountingstrategies"));
         assertTrue(mapper.contains("group by charging_order_id"));
         assertTrue(mapper.contains("pageappletorders"));
diff --git a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TVinChargingOrderCompanyFleetTest.java b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TVinChargingOrderCompanyFleetTest.java
new file mode 100644
index 0000000..faac3b7
--- /dev/null
+++ b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/TVinChargingOrderCompanyFleetTest.java
@@ -0,0 +1,192 @@
+package com.ruoyi.order.service.impl;
+
+import com.ruoyi.account.api.feignClient.VinManagementClient;
+import com.ruoyi.account.api.model.TVinManagement;
+import com.ruoyi.chargingPile.api.feignClient.ChargingGunClient;
+import com.ruoyi.chargingPile.api.model.TChargingGun;
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.order.api.dto.DeviceStartChargingDTO;
+import com.ruoyi.order.api.model.TVinChargingOrder;
+import com.ruoyi.order.api.vo.ChargingOrderVO;
+import com.ruoyi.order.api.vo.DeviceStartChargingVO;
+import com.ruoyi.order.mapper.TVinChargingOrderMapper;
+import com.ruoyi.other.api.domain.TFleet;
+import com.ruoyi.other.api.domain.TGroupCompany;
+import com.ruoyi.other.api.feignClient.GroupCompanyClient;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * VIN码充电订单「所属公司/所属车队」单测:
+ * 1) 建单时把 VIN 管理记录的 companyId/fleetId 作为快照写入订单;
+ * 2) 列表/导出按订单快照 companyId/fleetId 批量远程取名,失败或缺失时保持为空且不抛异常。
+ */
+@ExtendWith(MockitoExtension.class)
+class TVinChargingOrderCompanyFleetTest {
+
+    private static final String VIN = "LFV3A23C8K3123456";
+
+    @InjectMocks
+    private TVinChargingOrderServiceImpl service;
+
+    @Mock
+    private TVinChargingOrderMapper mapper;
+    @Mock
+    private ChargingGunClient chargingGunClient;
+    @Mock
+    private VinManagementClient vinManagementClient;
+    @Mock
+    private GroupCompanyClient groupCompanyClient;
+    @Mock
+    private org.redisson.api.RedissonClient redissonClient;
+    @Mock
+    private org.redisson.api.RLock lock;
+
+    private void fillCompanyFleetNames(List<ChargingOrderVO> list) {
+        ReflectionTestUtils.invokeMethod(service, "fillCompanyFleetNames", list);
+    }
+
+    @Test
+    void writesCompanyAndFleetSnapshotWhenBuildingOrder() throws Exception {
+        ReflectionTestUtils.setField(service, "baseMapper", mapper);
+        when(redissonClient.getLock(anyString())).thenReturn(lock);
+        when(lock.tryLock(anyLong(), anyLong(), any(TimeUnit.class))).thenReturn(true);
+        lenient().when(lock.isHeldByCurrentThread()).thenReturn(true);
+        when(mapper.insert(any(TVinChargingOrder.class))).thenReturn(1);
+
+        TChargingGun gun = new TChargingGun();
+        gun.setId(6);
+        when(chargingGunClient.getChargingGunByCode(any())).thenReturn(R.ok(gun));
+
+        TVinManagement vinRecord = new TVinManagement();
+        vinRecord.setId(1L);
+        vinRecord.setVin(VIN);
+        vinRecord.setPlateNumber("川A123456");
+        vinRecord.setStatus(1);
+        vinRecord.setCompanyId(12);
+        vinRecord.setFleetId(3);
+        when(vinManagementClient.getVinByCode(VIN)).thenReturn(R.ok(vinRecord));
+
+        DeviceStartChargingDTO dto = new DeviceStartChargingDTO();
+        dto.setChargingPileCode("P001");
+        dto.setChargingGunCode("G001");
+        dto.setAccount(new StringBuilder(VIN).reverse().toString());
+
+        DeviceStartChargingVO vo = service.startVinCharging(dto);
+
+        assertEquals(Integer.valueOf(1), vo.getAuthentication());
+        ArgumentCaptor<TVinChargingOrder> captor = ArgumentCaptor.forClass(TVinChargingOrder.class);
+        verify(mapper).insert(captor.capture());
+        TVinChargingOrder saved = captor.getValue();
+        assertEquals(Integer.valueOf(12), saved.getCompanyId());
+        assertEquals(Integer.valueOf(3), saved.getFleetId());
+        assertEquals(Long.valueOf(1L), saved.getVinId());
+        assertEquals(VIN, saved.getActualVin());
+        assertEquals(VIN, saved.getExpectedVin());
+        assertEquals("川A123456", saved.getPlateNum());
+    }
+
+    @Test
+    void fillsCompanyAndFleetNamesFromOrderSnapshot() {
+        when(groupCompanyClient.listCompaniesByIds(anyList()))
+                .thenReturn(R.ok(Collections.singletonList(company(12, "甲公司"))));
+        when(groupCompanyClient.listFleetsByIds(anyList()))
+                .thenReturn(R.ok(Collections.singletonList(fleet(3, "一队"))));
+
+        ChargingOrderVO withFleet = order(12, 3);
+        ChargingOrderVO companyOnly = order(12, null);
+        ChargingOrderVO unmatched = order(99, 88);
+        List<ChargingOrderVO> list = new ArrayList<>(Arrays.asList(withFleet, companyOnly, unmatched));
+
+        fillCompanyFleetNames(list);
+
+        assertEquals("甲公司", withFleet.getCompanyName());
+        assertEquals("一队", withFleet.getFleetName());
+        assertEquals("甲公司", companyOnly.getCompanyName());
+        assertNull(companyOnly.getFleetName());
+        assertNull(unmatched.getCompanyName());
+        assertNull(unmatched.getFleetName());
+
+        ArgumentCaptor<List<Integer>> companyIds = ArgumentCaptor.forClass(List.class);
+        verify(groupCompanyClient).listCompaniesByIds(companyIds.capture());
+        assertTrue(companyIds.getValue().containsAll(Arrays.asList(12, 99)));
+        assertEquals(2, companyIds.getValue().size());
+        ArgumentCaptor<List<Integer>> fleetIds = ArgumentCaptor.forClass(List.class);
+        verify(groupCompanyClient).listFleetsByIds(fleetIds.capture());
+        assertTrue(fleetIds.getValue().containsAll(Arrays.asList(3, 88)));
+        assertEquals(2, fleetIds.getValue().size());
+    }
+
+    @Test
+    void keepsNamesBlankWhenRemoteFails() {
+        when(groupCompanyClient.listCompaniesByIds(anyList())).thenReturn(R.fail("公司服务调用失败"));
+        when(groupCompanyClient.listFleetsByIds(anyList())).thenThrow(new RuntimeException("车队服务不可用"));
+
+        ChargingOrderVO item = order(12, 3);
+        List<ChargingOrderVO> list = new ArrayList<>(Collections.singletonList(item));
+
+        fillCompanyFleetNames(list);
+
+        assertNull(item.getCompanyName());
+        assertNull(item.getFleetName());
+    }
+
+    @Test
+    void skipsRemoteCallsWhenOrdersHaveNoOwnership() {
+        ChargingOrderVO item = new ChargingOrderVO();
+        item.setCode("VD202609280001");
+        List<ChargingOrderVO> list = new ArrayList<>(Collections.singletonList(item));
+
+        fillCompanyFleetNames(list);
+
+        verify(groupCompanyClient, never()).listCompaniesByIds(anyList());
+        verify(groupCompanyClient, never()).listFleetsByIds(anyList());
+        assertNull(item.getCompanyName());
+        assertNull(item.getFleetName());
+    }
+
+    private ChargingOrderVO order(Integer companyId, Integer fleetId) {
+        ChargingOrderVO item = new ChargingOrderVO();
+        item.setCode("VD202609280002");
+        item.setCompanyId(companyId);
+        item.setFleetId(fleetId);
+        return item;
+    }
+
+    private TGroupCompany company(Integer id, String name) {
+        TGroupCompany company = new TGroupCompany();
+        company.setId(id);
+        company.setName(name);
+        return company;
+    }
+
+    private TFleet fleet(Integer id, String fleetName) {
+        TFleet fleet = new TFleet();
+        fleet.setId(id);
+        fleet.setFleetName(fleetName);
+        return fleet;
+    }
+}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TGroupCompanyController.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TGroupCompanyController.java
index f7eaf9e..04cad2a 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TGroupCompanyController.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TGroupCompanyController.java
@@ -22,6 +22,10 @@
 
 import javax.annotation.Resource;
 import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * <p>
@@ -222,4 +226,20 @@
         return R.ok(latest == null ? null : latest.getAvailableBalance());
     }
 
+    /**
+     * 远程接口:按ID批量查询公司(VIN 码充电订单列表回填公司名称使用,内部调用)
+     */
+    @PostMapping("/remote/list-by-ids")
+    @ApiOperation(value = "远程-按ID批量查询公司", tags = {"内部调用"})
+    public R<List<TGroupCompany>> remoteListByIds(@RequestBody List<Integer> ids) {
+        if (ids == null || ids.isEmpty()) {
+            return R.ok(new ArrayList<>());
+        }
+        List<Integer> distinct = ids.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
+        if (distinct.isEmpty()) {
+            return R.ok(new ArrayList<>());
+        }
+        return R.ok(groupCompanyService.listByIds(distinct));
+    }
+
 }
diff --git a/sql/20260928_app_user_whitelist_company_fleet_quota.sql b/sql/20260928_app_user_whitelist_company_fleet_quota.sql
new file mode 100644
index 0000000..81575d3
--- /dev/null
+++ b/sql/20260928_app_user_whitelist_company_fleet_quota.sql
@@ -0,0 +1,19 @@
+-- 白名单管理增加:所属公司、所属车队、初始分配额度
+-- 创建日期: 2026-09-28
+-- 目标库: charging_pile_account(Mycat 8066 直接执行)
+-- 说明: company_id 关联 t_group_company.id(charging_pile_other 库);fleet_id 关联 t_fleet.id(charging_pile_other 库);跨库仅存ID,不建外键
+--       init_quota 为白名单初始分配额度(元),仅"分配模式"公司可填写并扣减公司可用余额;删除白名单不退还额度(对齐卡片管理)
+-- 注意: 非幂等,列已存在时执行会报 Duplicate column,忽略即可(以 information_schema 回查为准)
+
+ALTER TABLE `charging_pile_account`.`t_app_user_whitelist`
+  ADD COLUMN `company_id` int NULL DEFAULT NULL COMMENT '所属公司ID(关联t_group_company.id)' AFTER `partner_id`;
+ALTER TABLE `charging_pile_account`.`t_app_user_whitelist`
+  ADD COLUMN `fleet_id` int NULL DEFAULT NULL COMMENT '所属车队ID(关联t_fleet.id)' AFTER `company_id`;
+ALTER TABLE `charging_pile_account`.`t_app_user_whitelist`
+  ADD COLUMN `init_quota` decimal(12,2) NULL DEFAULT NULL COMMENT '初始分配额度(元)' AFTER `fleet_id`;
+
+-- 辅助索引(可选,代理不支持时可跳过,不影响功能)
+ALTER TABLE `charging_pile_account`.`t_app_user_whitelist`
+  ADD KEY `idx_company_id` (`company_id`);
+ALTER TABLE `charging_pile_account`.`t_app_user_whitelist`
+  ADD KEY `idx_fleet_id` (`fleet_id`);
diff --git a/sql/20260928_app_user_whitelist_name_status.sql b/sql/20260928_app_user_whitelist_name_status.sql
new file mode 100644
index 0000000..4f69126
--- /dev/null
+++ b/sql/20260928_app_user_whitelist_name_status.sql
@@ -0,0 +1,8 @@
+-- 白名单管理:新增姓名列(status 列已存在,无需重复添加)
+-- 创建日期: 2026-09-28
+-- 目标库: charging_pile_account(Mycat 8066 直接执行)
+-- 说明: name 为用户姓名,选填,仅作展示用途
+-- 注意: 非幂等,列已存在时执行会报 Duplicate column,忽略即可(以 information_schema 回查为准)
+
+ALTER TABLE `charging_pile_account`.`t_app_user_whitelist`
+  ADD COLUMN `name` varchar(64) NULL DEFAULT NULL COMMENT '用户姓名' AFTER `phone`;
diff --git a/sql/20260928_vin_charging_order_company_fleet.sql b/sql/20260928_vin_charging_order_company_fleet.sql
new file mode 100644
index 0000000..757abd8
--- /dev/null
+++ b/sql/20260928_vin_charging_order_company_fleet.sql
@@ -0,0 +1,128 @@
+-- VIN码充电订单新增「所属公司 / 所属车队」快照列
+-- 目的:VIN 码充电订单列表/汇总/导出支持按公司、车队筛选与展示(方案B:订单表冗余归属列 + SQL 直接筛选)
+-- 目标库:charging_pile_order(表 t_vin_charging_order,TBPARTITION BY MOD_HASH(vin_id) TBPARTITIONS 20)
+-- 数据来源:建单时取 t_vin_management(VIN 管理记录)的 company_id / fleet_id 快照;
+--           历史订单回填见同目录 20260928_vin_charging_order_company_fleet_backfill.sql
+-- 执行方式:
+--   1) 首选「小节 A」经数据库代理(MyCat 8066)执行,代理会把 DDL 广播到全部物理分表;
+--   2) 代理不支持分片表 DDL 时,改用「小节 B」直连后端 MySQL 实例执行(存储过程,逐物理分表,幂等);
+--   3) 最后执行「小节 C」回查校验。
+-- 幂等说明:小节 A 为裸 ALTER,重复执行会报 Duplicate column,忽略即可(以小节 C 回查为准);
+--           小节 B 先查 information_schema 再变更,可重复执行。
+
+-- ============ 小节 A:经代理执行(推荐) ============
+USE `charging_pile_order`;
+
+ALTER TABLE `t_vin_charging_order`
+  ADD COLUMN `company_id` int DEFAULT NULL COMMENT '所属公司ID(建单时VIN记录快照,关联t_group_company.id)';
+
+ALTER TABLE `t_vin_charging_order`
+  ADD COLUMN `fleet_id` int DEFAULT NULL COMMENT '所属车队ID(建单时VIN记录快照,关联t_fleet.id)';
+
+ALTER TABLE `t_vin_charging_order` ADD KEY `idx_company_id` (`company_id`);
+ALTER TABLE `t_vin_charging_order` ADD KEY `idx_fleet_id` (`fleet_id`);
+
+-- ============ 小节 B:直连后端 MySQL 实例执行(代理不可用时分表执行,幂等) ============
+USE `charging_pile_order`;
+
+DROP PROCEDURE IF EXISTS `add_vin_charging_order_company_fleet`;
+DELIMITER $$
+CREATE PROCEDURE `add_vin_charging_order_company_fleet`()
+BEGIN
+    DECLARE done INT DEFAULT 0;
+    DECLARE target_table VARCHAR(64);
+    DECLARE column_exists INT DEFAULT 0;
+    DECLARE index_exists INT DEFAULT 0;
+
+    -- 覆盖逻辑表(若物理存在)与 TBPARTITION 物理分表 t_vin_charging_order_0..N
+    DECLARE table_cursor CURSOR FOR
+        SELECT t.table_name
+        FROM information_schema.tables t
+        WHERE t.table_schema = DATABASE()
+          AND t.table_name REGEXP '^t_vin_charging_order(_[0-9]+)?$';
+
+    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
+
+    OPEN table_cursor;
+    table_loop: LOOP
+        FETCH table_cursor INTO target_table;
+        IF done = 1 THEN
+            LEAVE table_loop;
+        END IF;
+
+        SELECT COUNT(*) INTO column_exists
+        FROM information_schema.columns
+        WHERE table_schema = DATABASE()
+          AND table_name = target_table
+          AND column_name = 'company_id';
+        IF column_exists = 0 THEN
+            SET @ddl = CONCAT('ALTER TABLE `', target_table, '` ADD COLUMN `company_id` int DEFAULT NULL COMMENT ''所属公司ID(建单时VIN记录快照,关联t_group_company.id)''');
+            PREPARE stmt FROM @ddl;
+            EXECUTE stmt;
+            DEALLOCATE PREPARE stmt;
+        END IF;
+
+        SELECT COUNT(*) INTO column_exists
+        FROM information_schema.columns
+        WHERE table_schema = DATABASE()
+          AND table_name = target_table
+          AND column_name = 'fleet_id';
+        IF column_exists = 0 THEN
+            SET @ddl = CONCAT('ALTER TABLE `', target_table, '` ADD COLUMN `fleet_id` int DEFAULT NULL COMMENT ''所属车队ID(建单时VIN记录快照,关联t_fleet.id)''');
+            PREPARE stmt FROM @ddl;
+            EXECUTE stmt;
+            DEALLOCATE PREPARE stmt;
+        END IF;
+
+        SELECT COUNT(*) INTO index_exists
+        FROM information_schema.statistics
+        WHERE table_schema = DATABASE()
+          AND table_name = target_table
+          AND index_name = 'idx_company_id';
+        IF index_exists = 0 THEN
+            SET @ddl = CONCAT('ALTER TABLE `', target_table, '` ADD KEY `idx_company_id` (`company_id`)');
+            PREPARE stmt FROM @ddl;
+            EXECUTE stmt;
+            DEALLOCATE PREPARE stmt;
+        END IF;
+
+        SELECT COUNT(*) INTO index_exists
+        FROM information_schema.statistics
+        WHERE table_schema = DATABASE()
+          AND table_name = target_table
+          AND index_name = 'idx_fleet_id';
+        IF index_exists = 0 THEN
+            SET @ddl = CONCAT('ALTER TABLE `', target_table, '` ADD KEY `idx_fleet_id` (`fleet_id`)');
+            PREPARE stmt FROM @ddl;
+            EXECUTE stmt;
+            DEALLOCATE PREPARE stmt;
+        END IF;
+    END LOOP;
+    CLOSE table_cursor;
+END$$
+DELIMITER ;
+
+CALL `add_vin_charging_order_company_fleet`();
+DROP PROCEDURE `add_vin_charging_order_company_fleet`;
+
+-- ============ 小节 C:回查校验(应看到每个物理分表都存在 company_id / fleet_id 两列) ============
+SELECT t.table_name, c.column_name, c.column_type, c.column_comment
+FROM information_schema.tables t
+JOIN information_schema.columns c
+  ON c.table_schema = t.table_schema
+ AND c.table_name = t.table_name
+WHERE t.table_schema = DATABASE()
+  AND t.table_name REGEXP '^t_vin_charging_order(_[0-9]+)?$'
+  AND c.column_name IN ('company_id', 'fleet_id')
+ORDER BY t.table_name, c.column_name;
+
+SELECT COUNT(DISTINCT t.table_name) AS shard_count,
+       SUM(CASE WHEN c.column_name = 'company_id' THEN 1 ELSE 0 END) AS company_id_columns,
+       SUM(CASE WHEN c.column_name = 'fleet_id' THEN 1 ELSE 0 END) AS fleet_id_columns
+FROM information_schema.tables t
+JOIN information_schema.columns c
+  ON c.table_schema = t.table_schema
+ AND c.table_name = t.table_name
+WHERE t.table_schema = DATABASE()
+  AND t.table_name REGEXP '^t_vin_charging_order(_[0-9]+)?$'
+  AND c.column_name IN ('company_id', 'fleet_id');
diff --git a/sql/20260928_vin_charging_order_company_fleet_backfill.sql b/sql/20260928_vin_charging_order_company_fleet_backfill.sql
new file mode 100644
index 0000000..74a2558
--- /dev/null
+++ b/sql/20260928_vin_charging_order_company_fleet_backfill.sql
@@ -0,0 +1,56 @@
+-- VIN码充电订单「所属公司 / 所属车队」历史数据回填脚本
+-- 前置:已执行 20260928_vin_charging_order_company_fleet.sql(两列已存在)
+-- 数据来源:charging_pile_account.t_vin_management(含 company_id / fleet_id / del_flag)
+-- 执行方式:MyCat 不支持跨逻辑库 UPDATE JOIN,故分三步人工执行:
+--   步骤1 在 charging_pile_account 生成 UPDATE 语句(单库只读查询);
+--   步骤2 把步骤1 结果集逐行复制到 charging_pile_order 执行(vin_id 为分片键,可路由单分片);
+--   步骤3 回查校验。
+-- 语义与幂等:
+--   * 仅回填 company_id IS NULL 的历史订单行,不覆盖上线后新建订单的建单时快照;
+--   * 同一 VIN 重复执行结果一致(写入 VIN 记录当前归属);
+--   * 车辆未分配公司且未分配车队的 VIN 不生成语句,其历史订单两列保持 NULL。
+
+-- ============ 步骤 0:前置检查(charging_pile_account,只读) ============
+USE `charging_pile_account`;
+
+SELECT COUNT(1) AS assignable_vehicle_count
+FROM `t_vin_management`
+WHERE `del_flag` = 0 AND (`company_id` IS NOT NULL OR `fleet_id` IS NOT NULL);
+
+SELECT COUNT(1) AS unassigned_vehicle_count
+FROM `t_vin_management`
+WHERE `del_flag` = 0 AND `company_id` IS NULL AND `fleet_id` IS NULL;
+
+-- ============ 步骤 1:生成回填语句(charging_pile_account,只读) ============
+-- 复制结果集 backfill_sql 列全部内容,粘贴到 charging_pile_order 逐行执行
+SELECT CONCAT('UPDATE `t_vin_charging_order` SET `company_id` = ', IFNULL(v.`company_id`, 'NULL'),
+              ', `fleet_id` = ', IFNULL(v.`fleet_id`, 'NULL'),
+              ' WHERE `vin_id` = ', v.`id`, ' AND `company_id` IS NULL;') AS backfill_sql
+FROM `t_vin_management` v
+WHERE v.`del_flag` = 0
+  AND (v.`company_id` IS NOT NULL OR v.`fleet_id` IS NOT NULL)
+ORDER BY v.`id`;
+
+-- ============ 步骤 2:在 charging_pile_order 执行步骤1 生成的语句 ============
+-- 示例(vin_id = 1 的车辆归属公司 12、车队 3):
+-- USE `charging_pile_order`;
+-- UPDATE `t_vin_charging_order` SET `company_id` = 12, `fleet_id` = 3 WHERE `vin_id` = 1 AND `company_id` IS NULL;
+
+-- ============ 步骤 3:回查校验(charging_pile_order) ============
+USE `charging_pile_order`;
+
+-- 全量盘点:总数 / 已回填公司 / 已回填车队 / 两者皆空
+SELECT COUNT(1) AS order_total,
+       SUM(CASE WHEN `company_id` IS NOT NULL THEN 1 ELSE 0 END) AS company_filled,
+       SUM(CASE WHEN `fleet_id` IS NOT NULL THEN 1 ELSE 0 END) AS fleet_filled,
+       SUM(CASE WHEN `company_id` IS NULL AND `fleet_id` IS NULL THEN 1 ELSE 0 END) AS unassigned
+FROM `t_vin_charging_order`;
+
+-- 以下跨逻辑库比对请【直连后端 MySQL 实例】执行(代理不支持跨库 JOIN):
+-- 归属公司与 VIN 记录不一致的订单(回填后应为 0;上线后新建订单因车辆改归属而不为 0 属预期)
+-- SELECT COUNT(1) AS snapshot_diff_count
+-- FROM `charging_pile_order`.`t_vin_charging_order` o
+-- JOIN `charging_pile_account`.`t_vin_management` v ON v.`id` = o.`vin_id`
+-- WHERE o.`del_flag` = 0
+--   AND o.`company_id` IS NOT NULL
+--   AND v.`company_id` <> o.`company_id`;

--
Gitblit v1.7.1