From e03f9a706ba46df72c1914870a0a707d6e5e676b Mon Sep 17 00:00:00 2001
From: puzhibing <393733352@qq.com>
Date: 星期三, 30 九月 2026 09:02:38 +0800
Subject: [PATCH] 修改后台功能
---
ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TChargingCardMapper.xml | 2
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TChargingCardServiceImpl.java | 165 +++++++++--
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/TAppUserWhitelistService.java | 12
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistExcelDTO.java | 58 ++++
ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImplTest.java | 84 ++++++
sql/20260929_whitelist_export_status_auth.sql | 57 ++++
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserWhitelistController.java | 18 +
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/ChargingCardExcelDTO.java | 42 ++
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TChargingCard.java | 20 +
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserWhitelistServiceImpl.java | 229 +++++++++++++---
ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TChargingCardServiceImplTest.java | 96 ++++++
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistStatusDTO.java | 23 +
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TAppUserWhitelist.java | 16 +
13 files changed, 745 insertions(+), 77 deletions(-)
diff --git a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistExcelDTO.java b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistExcelDTO.java
new file mode 100644
index 0000000..8ee8c1e
--- /dev/null
+++ b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistExcelDTO.java
@@ -0,0 +1,58 @@
+package com.ruoyi.account.api.dto;
+
+import com.ruoyi.common.core.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 白名单用户 Excel 传输对象(仅用于导出)。
+ * <p>公司/车队名称与账户信息(账户模式、可用余额、超额、账户状态)均为导出前远程回填,
+ * 其中账户模式=1共享模式时可用余额取所属公司可用余额,=2分配模式时取白名单用户自身余额;
+ * 可用余额为负时超额取其绝对值、账户状态记为欠款。</p>
+ */
+@Data
+public class AppUserWhitelistExcelDTO implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @Excel(name = "用户手机号", sort = 1, width = 16)
+ private String phone;
+
+ @Excel(name = "姓名", sort = 2, width = 16)
+ private String name;
+
+ @Excel(name = "所属公司", sort = 3, width = 24)
+ private String companyName;
+
+ @Excel(name = "所属车队", sort = 4, width = 16)
+ private String fleetName;
+
+ /** 账户模式(1=共享模式,2=分配模式) */
+ @Excel(name = "账户模式", sort = 5, width = 12, readConverterExp = "1=共享模式,2=分配模式")
+ private Integer accountMode;
+
+ /** 可用余额(共享模式=公司可用余额,分配模式=白名单用户自身余额) */
+ @Excel(name = "可用余额", sort = 6, width = 14, scale = 2)
+ private BigDecimal availableBalance;
+
+ /** 超额(可用余额为负时取其绝对值) */
+ @Excel(name = "超额", sort = 7, width = 14, scale = 2)
+ private BigDecimal overrun;
+
+ /** 账户状态(1=正常,2=欠款) */
+ @Excel(name = "账户状态", sort = 8, width = 12, readConverterExp = "1=正常,2=欠款")
+ private Integer accountStatus;
+
+ @Excel(name = "启停状态", sort = 9, width = 12, readConverterExp = "1=启用,2=停用")
+ private Integer status;
+
+ /** 所属公司ID(仅导出内部使用,不落Excel) */
+ private Integer companyId;
+
+ /** 所属车队ID(仅导出内部使用,不落Excel) */
+ private Integer fleetId;
+
+ /** 白名单用户自身余额(分配模式可用余额取值,仅导出内部使用,不落Excel) */
+ private BigDecimal balance;
+}
diff --git a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistStatusDTO.java b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistStatusDTO.java
new file mode 100644
index 0000000..f78e1a0
--- /dev/null
+++ b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserWhitelistStatusDTO.java
@@ -0,0 +1,23 @@
+package com.ruoyi.account.api.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 白名单用户状态更新参数
+ */
+@Data
+@ApiModel(value = "白名单用户状态更新参数")
+public class AppUserWhitelistStatusDTO implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty(value = "白名单用户ID", required = true)
+ private Long id;
+
+ @ApiModelProperty(value = "状态:1=启用,2=停用", required = true)
+ private Integer status;
+}
diff --git a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/ChargingCardExcelDTO.java b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/ChargingCardExcelDTO.java
index 71fcf87..002ba1e 100644
--- a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/ChargingCardExcelDTO.java
+++ b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/ChargingCardExcelDTO.java
@@ -7,18 +7,54 @@
import java.io.Serializable;
import java.math.BigDecimal;
+/**
+ * 充电卡 Excel 传输对象。
+ * <p>导入模板与导入解析只使用「导出导入」列(卡号/卡余额/启用状态);
+ * 导出额外使用 type=EXPORT 的派生列(所属公司、所属车队、账户模式、可用余额、超额、账户状态),
+ * 派生的公司/车队/余额字段仅供导出内部回填,不参与Excel读写。</p>
+ */
@Data
@ApiModel(value = "充电卡导入导出数据")
public class ChargingCardExcelDTO implements Serializable {
private static final long serialVersionUID = 1L;
- @Excel(name = "卡号", width = 22, prompt = "必填,最多64个字符")
+ @Excel(name = "卡号", sort = 1, width = 22, prompt = "必填,最多64个字符")
private String cardNo;
- @Excel(name = "卡余额", width = 16, scale = 2, prompt = "选填,必须大于等于0")
+ @Excel(name = "卡余额", sort = 2, width = 16, scale = 2, prompt = "选填,必须大于等于0")
private BigDecimal balance;
- @Excel(name = "启用状态", width = 14, readConverterExp = "1=启用,2=禁用", combo = {"启用", "禁用"})
+ /** 所属公司(仅导出,按公司ID远程回填公司名称) */
+ @Excel(name = "所属公司", sort = 3, width = 24, type = Excel.Type.EXPORT)
+ private String companyName;
+
+ /** 所属车队(仅导出,按车队ID远程回填) */
+ @Excel(name = "所属车队", sort = 4, width = 16, type = Excel.Type.EXPORT)
+ private String fleetName;
+
+ /** 账户模式(仅导出,1=共享模式,2=分配模式) */
+ @Excel(name = "账户模式", sort = 5, width = 12, type = Excel.Type.EXPORT, readConverterExp = "1=共享模式,2=分配模式")
+ private Integer accountMode;
+
+ /** 可用余额(仅导出,共享模式=公司可用余额,分配模式=卡片自身余额) */
+ @Excel(name = "可用余额", sort = 6, width = 14, type = Excel.Type.EXPORT, scale = 2)
+ private BigDecimal availableBalance;
+
+ /** 超额(仅导出,可用余额为负时取其绝对值) */
+ @Excel(name = "超额", sort = 7, width = 14, type = Excel.Type.EXPORT, scale = 2)
+ private BigDecimal overrun;
+
+ /** 账户状态(仅导出,1=正常,2=欠款) */
+ @Excel(name = "账户状态", sort = 8, width = 12, type = Excel.Type.EXPORT, readConverterExp = "1=正常,2=欠款")
+ private Integer accountStatus;
+
+ @Excel(name = "启用状态", sort = 9, width = 14, readConverterExp = "1=启用,2=禁用", combo = {"启用", "禁用"})
private String status;
+
+ /** 所属公司ID(仅导出内部使用,不落Excel) */
+ private Integer companyId;
+
+ /** 所属车队ID(仅导出内部使用,不落Excel) */
+ private Integer fleetId;
}
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 e4eaacb..db89aa8 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
@@ -75,6 +75,22 @@
@TableField(exist = false)
private String fleetName;
+ @ApiModelProperty(value = "账户模式(列表远程回填,不落库):1=共享模式,2=分配模式")
+ @TableField(exist = false)
+ private Integer accountMode;
+
+ @ApiModelProperty(value = "可用余额(列表远程回填,不落库):共享模式=公司可用余额,分配模式=白名单用户余额")
+ @TableField(exist = false)
+ private java.math.BigDecimal availableBalance;
+
+ @ApiModelProperty(value = "超额(列表远程回填,不落库):可用余额为负时取其绝对值")
+ @TableField(exist = false)
+ private java.math.BigDecimal overrun;
+
+ @ApiModelProperty(value = "账户状态(列表远程回填,不落库):1=正常,2=欠款")
+ @TableField(exist = false)
+ private Integer accountStatus;
+
@ApiModelProperty(value = "状态:1=启用,2=停用")
@TableField("status")
private Integer status;
diff --git a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TChargingCard.java b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TChargingCard.java
index ef34bf5..0b46a07 100644
--- a/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TChargingCard.java
+++ b/ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/TChargingCard.java
@@ -66,6 +66,26 @@
@ApiModelProperty(value = "所属车队名称")
private String fleetName;
+ /** 账户模式(列表远程回填,不落库):1=共享模式,2=分配模式 */
+ @TableField(exist = false)
+ @ApiModelProperty(value = "账户模式:1共享模式,2分配模式")
+ private Integer accountMode;
+
+ /** 可用余额(列表远程回填,不落库):分配模式=卡余额,共享模式=公司可用余额 */
+ @TableField(exist = false)
+ @ApiModelProperty(value = "可用余额:分配模式取卡余额,共享模式取公司可用余额")
+ private BigDecimal availableBalance;
+
+ /** 超额(列表远程回填,不落库):可用余额为负时取其绝对值 */
+ @TableField(exist = false)
+ @ApiModelProperty(value = "超额:可用余额为负时的绝对值")
+ private BigDecimal overrun;
+
+ /** 账户状态(列表远程回填,不落库):1=正常,2=欠款 */
+ @TableField(exist = false)
+ @ApiModelProperty(value = "账户状态:1正常,2欠款")
+ private Integer accountStatus;
+
@Excel(name = "启用状态", width = 14, readConverterExp = "1=启用,2=禁用")
@ApiModelProperty(value = "状态:1启用,2禁用")
private Integer status;
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 8f9b6fa..7a785f2 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
@@ -2,9 +2,11 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.api.dto.AppUserWhitelistAllocateDTO;
+import com.ruoyi.account.api.dto.AppUserWhitelistExcelDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistImportDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistQueryDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistSaveDTO;
+import com.ruoyi.account.api.dto.AppUserWhitelistStatusDTO;
import com.ruoyi.account.api.model.TAppUserWhitelist;
import com.ruoyi.account.api.vo.AppUserWhitelistAllocateVO;
import com.ruoyi.account.api.vo.AppUserWhitelistMatchVO;
@@ -120,6 +122,14 @@
return R.ok(whitelistService.unbindFleet(ids));
}
+ @ApiOperation("启用或停用白名单用户")
+ @RequiresPermissions(value = {"/appUserWhitelist/status"}, logical = Logical.OR)
+ @Log(title = "【白名单用户】启用或停用", businessType = BusinessType.UPDATE, operatorType = OperatorType.MANAGE)
+ @PutMapping("/status")
+ public R<Boolean> status(@RequestBody AppUserWhitelistStatusDTO statusDTO) {
+ return R.ok(whitelistService.updateStatus(statusDTO));
+ }
+
@ApiOperation("额度分配概览(公司总账户余额、可分配余额)")
@RequiresPermissions(value = {"/appUserWhitelist/allocate"}, logical = Logical.OR)
@GetMapping("/allocate-overview")
@@ -135,6 +145,14 @@
return R.ok(whitelistService.allocateQuota(allocateDTO));
}
+ @ApiOperation("导出白名单用户列表")
+ @RequiresPermissions(value = {"/appUserWhitelist/export"}, logical = Logical.OR)
+ @PutMapping("/export")
+ public void export(HttpServletResponse response, @RequestBody(required = false) AppUserWhitelistQueryDTO queryDTO) {
+ ExcelUtil<AppUserWhitelistExcelDTO> util = new ExcelUtil<>(AppUserWhitelistExcelDTO.class);
+ util.exportExcel(response, whitelistService.selectExportList(queryDTO), "白名单用户数据");
+ }
+
@ApiOperation("下载白名单用户导入模板")
@RequiresPermissions(value = {"/appUserWhitelist/import"}, logical = Logical.OR)
@GetMapping("/importTemplate")
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/TAppUserWhitelistService.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/TAppUserWhitelistService.java
index 01755cf..1b6b29a 100644
--- a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/TAppUserWhitelistService.java
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/TAppUserWhitelistService.java
@@ -3,8 +3,10 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.account.api.dto.AppUserWhitelistAllocateDTO;
+import com.ruoyi.account.api.dto.AppUserWhitelistExcelDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistQueryDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistSaveDTO;
+import com.ruoyi.account.api.dto.AppUserWhitelistStatusDTO;
import com.ruoyi.account.api.model.TAppUserWhitelist;
import com.ruoyi.account.api.vo.AppUserWhitelistAllocateVO;
import com.ruoyi.account.api.vo.AppUserWhitelistMatchVO;
@@ -33,6 +35,16 @@
*/
int unbindFleet(List<Long> ids);
+ /**
+ * 导出白名单用户列表:与列表查询同一筛选口径(含合作商数据范围),并回填公司与车队名称、账户信息
+ */
+ List<AppUserWhitelistExcelDTO> selectExportList(AppUserWhitelistQueryDTO queryDTO);
+
+ /**
+ * 启用/停用白名单用户:仅允许 1=启用、2=停用,合作商账号仅能操作自身可见的数据
+ */
+ boolean updateStatus(AppUserWhitelistStatusDTO statusDTO);
+
AppUserWhitelistImportResultVO importWhitelist(MultipartFile file) throws Exception;
AppUserWhitelistMatchVO matchWhitelist(Long appUserId, Integer siteId);
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 68c8bac..0e0ef69 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
@@ -3,12 +3,15 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
+import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.api.dto.AppUserWhitelistAllocateDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistAllocateItemDTO;
+import com.ruoyi.account.api.dto.AppUserWhitelistExcelDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistImportDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistQueryDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistSaveDTO;
+import com.ruoyi.account.api.dto.AppUserWhitelistStatusDTO;
import com.ruoyi.account.api.model.TAppUser;
import com.ruoyi.account.api.model.TAppUserWhitelist;
import com.ruoyi.account.api.model.TAppUserWhitelistSite;
@@ -91,6 +94,15 @@
/** 姓名最大长度(对应 varchar(64)) */
private static final int MAX_NAME_LENGTH = 64;
+ /** 共享模式(公司账户模式 1=共享模式,可用余额取公司可用余额) */
+ private static final int ACCOUNT_MODE_SHARED = 1;
+ /** 账户状态:正常 */
+ private static final int ACCOUNT_STATUS_NORMAL = 1;
+ /** 账户状态:欠款 */
+ private static final int ACCOUNT_STATUS_OVERDUE = 2;
+ /** 金额小数位(元,对应 decimal(12,2)) */
+ private static final int AMOUNT_SCALE = 2;
+
/** 公司账户模式:2=分配模式(可设置初始分配额度并扣减公司可用余额) */
private static final int ACCOUNT_MODE_ALLOCATION = 2;
/** 初始分配额度上限(对应 decimal(12,2)) */
@@ -130,17 +142,28 @@
@Override
public Page<TAppUserWhitelist> pageList(AppUserWhitelistQueryDTO queryDTO) {
- if (queryDTO == null) {
- queryDTO = new AppUserWhitelistQueryDTO();
- }
+ AppUserWhitelistQueryDTO safeQuery = queryDTO == null ? new AppUserWhitelistQueryDTO() : queryDTO;
+ Page<TAppUserWhitelist> page = buildListQuery(safeQuery)
+ .orderByDesc(BasePojo::getCreateTime)
+ .page(Page.of(safeQuery.getPageCurr(), safeQuery.getPageSize()));
+ fillDisplay(page.getRecords());
+ return page;
+ }
+
+ /**
+ * 列表与导出共用的查询条件(含合作商数据范围):条件为空则不参与过滤;
+ * 合作商账号只能看到自身合作商录入或本人创建的白名单数据
+ */
+ private LambdaQueryChainWrapper<TAppUserWhitelist> buildListQuery(AppUserWhitelistQueryDTO queryDTO) {
+ AppUserWhitelistQueryDTO safeQuery = queryDTO == null ? new AppUserWhitelistQueryDTO() : queryDTO;
Integer currentPartnerId = getCurrentPartnerId();
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())
+ .like(StringUtils.isNotBlank(safeQuery.getPhone()), TAppUserWhitelist::getPhone, safeQuery.getPhone())
+ .like(StringUtils.isNotBlank(safeQuery.getName()), TAppUserWhitelist::getName, safeQuery.getName())
+ .eq(safeQuery.getCompanyId() != null, TAppUserWhitelist::getCompanyId, safeQuery.getCompanyId())
+ .eq(safeQuery.getFleetId() != null, TAppUserWhitelist::getFleetId, safeQuery.getFleetId())
+ .eq(safeQuery.getStatus() != null, TAppUserWhitelist::getStatus, safeQuery.getStatus())
.eq(TAppUserWhitelist::getDelFlag, false);
if (currentPartnerId != null) {
if (StringUtils.isNotBlank(username)) {
@@ -151,10 +174,77 @@
query.eq(TAppUserWhitelist::getPartnerId, currentPartnerId);
}
}
- Page<TAppUserWhitelist> page = query.orderByDesc(BasePojo::getCreateTime)
- .page(Page.of(queryDTO.getPageCurr(), queryDTO.getPageSize()));
- fillDisplay(page.getRecords());
- return page;
+ return query;
+ }
+
+ @Override
+ public List<AppUserWhitelistExcelDTO> selectExportList(AppUserWhitelistQueryDTO queryDTO) {
+ List<TAppUserWhitelist> records = buildListQuery(queryDTO)
+ .orderByDesc(BasePojo::getCreateTime)
+ .list();
+ if (records == null || records.isEmpty()) {
+ return Collections.emptyList();
+ }
+ Map<Integer, String> fleetNameMap = loadFleetNameMap(records.stream()
+ .map(TAppUserWhitelist::getFleetId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+ Map<Integer, TGroupCompany> companyMap = loadCompanyMap(records.stream()
+ .map(TAppUserWhitelist::getCompanyId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+ List<AppUserWhitelistExcelDTO> items = new ArrayList<>(records.size());
+ for (TAppUserWhitelist record : records) {
+ TGroupCompany company = companyMap.get(record.getCompanyId());
+ Integer accountMode = company == null ? null : company.getAccountMode();
+ BigDecimal balance = resolveAvailableBalance(accountMode, record.getBalance(),
+ company == null ? null : company.getAvailableBalance());
+ AppUserWhitelistExcelDTO item = new AppUserWhitelistExcelDTO();
+ item.setPhone(record.getPhone());
+ item.setName(record.getName());
+ item.setCompanyName(company == null ? null : company.getName());
+ item.setFleetName(fleetNameMap.get(record.getFleetId()));
+ item.setAccountMode(accountMode);
+ item.setAvailableBalance(balance);
+ item.setOverrun(resolveOverrun(balance));
+ item.setAccountStatus(resolveAccountStatus(balance));
+ item.setStatus(record.getStatus());
+ item.setCompanyId(record.getCompanyId());
+ item.setFleetId(record.getFleetId());
+ item.setBalance(record.getBalance());
+ items.add(item);
+ }
+ return items;
+ }
+
+ @Override
+ public boolean updateStatus(AppUserWhitelistStatusDTO statusDTO) {
+ if (statusDTO == null || statusDTO.getId() == null
+ || (!Objects.equals(statusDTO.getStatus(), STATUS_ENABLED)
+ && !Objects.equals(statusDTO.getStatus(), STATUS_DISABLED))) {
+ throw new ServiceException("白名单用户状态参数错误");
+ }
+ TAppUserWhitelist existing = this.getById(statusDTO.getId());
+ if (existing == null) {
+ throw new ServiceException("白名单用户不存在或已删除");
+ }
+ assertPartnerVisible(existing);
+ if (Objects.equals(existing.getStatus(), statusDTO.getStatus())) {
+ // 状态未变化视为幂等成功,避免无意义的更新人与更新时间变更
+ return true;
+ }
+ LambdaUpdateChainWrapper<TAppUserWhitelist> updateWrapper = lambdaUpdate()
+ .eq(TAppUserWhitelist::getId, statusDTO.getId())
+ .set(TAppUserWhitelist::getStatus, statusDTO.getStatus())
+ .setSql("update_time = now()");
+ String username = getUsername();
+ if (StringUtils.isNotBlank(username)) {
+ updateWrapper.set(TAppUserWhitelist::getUpdateBy, username);
+ }
+ if (!updateWrapper.update()) {
+ throw new ServiceException("白名单用户状态更新失败,请重试");
+ }
+ return true;
}
@Override
@@ -847,48 +937,102 @@
record.setSiteList(sites);
record.setSiteIds(sites.stream().map(TAppUserWhitelistSite::getSiteId).collect(Collectors.toList()));
}
- fillCompanyNames(records);
+ fillCompanyInfo(records);
fillFleetNames(records);
}
- /** 列表回填所属公司名称(远程失败仅记录日志,不影响列表查询) */
- private void fillCompanyNames(List<TAppUserWhitelist> records) {
- Map<Integer, String> nameMap = new HashMap<>();
+ /**
+ * 列表回填所属公司名称与账户信息(账户模式、可用余额、超额、账户状态)。
+ * 按公司ID去重后一次批量远程查询,远程失败仅记录日志并留空,不影响列表查询
+ */
+ private void fillCompanyInfo(List<TAppUserWhitelist> records) {
+ Map<Integer, TGroupCompany> companyMap = loadCompanyMap(records.stream()
+ .map(TAppUserWhitelist::getCompanyId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
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);
+ TGroupCompany company = companyMap.get(record.getCompanyId());
+ Integer accountMode = company == null ? null : company.getAccountMode();
+ BigDecimal balance = resolveAvailableBalance(accountMode, record.getBalance(),
+ company == null ? null : company.getAvailableBalance());
+ record.setCompanyName(company == null ? null : company.getName());
+ record.setAccountMode(accountMode);
+ record.setAvailableBalance(balance);
+ record.setOverrun(resolveOverrun(balance));
+ record.setAccountStatus(resolveAccountStatus(balance));
}
+ }
+
+ /** 按ID批量加载公司(远程失败或服务降级时返回空Map,由调用方按未知处理) */
+ private Map<Integer, TGroupCompany> loadCompanyMap(List<Integer> companyIds) {
+ List<Integer> distinctIds = companyIds.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
+ if (distinctIds.isEmpty()) {
+ return new HashMap<>();
+ }
+ try {
+ R<List<TGroupCompany>> response = groupCompanyClient.listCompaniesByIds(distinctIds);
+ if (response == null || !R.isSuccess(response) || response.getData() == null) {
+ return new HashMap<>();
+ }
+ Map<Integer, TGroupCompany> companyMap = new HashMap<>();
+ for (TGroupCompany company : response.getData()) {
+ if (company != null && company.getId() != null) {
+ companyMap.put(company.getId(), company);
+ }
+ }
+ return companyMap;
+ } catch (Exception ex) {
+ log.warn("回填公司账户信息失败,companyIds={}", distinctIds, ex);
+ return new HashMap<>();
+ }
+ }
+
+ /** 计算可用余额:分配模式=白名单用户自身余额,共享模式=公司可用余额,账户模式未知时留空 */
+ private BigDecimal resolveAvailableBalance(Integer accountMode, BigDecimal whitelistBalance, BigDecimal companyBalance) {
+ if (Integer.valueOf(ACCOUNT_MODE_ALLOCATION).equals(accountMode)) {
+ return whitelistBalance;
+ }
+ if (Integer.valueOf(ACCOUNT_MODE_SHARED).equals(accountMode)) {
+ return companyBalance;
+ }
+ return null;
+ }
+
+ /** 超额:可用余额为负时取其绝对值,其余情况为空 */
+ private BigDecimal resolveOverrun(BigDecimal balance) {
+ if (balance == null || balance.signum() >= 0) {
+ return null;
+ }
+ return balance.abs().setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
+ }
+
+ /** 账户状态:可用余额为负=欠款,非负=正常,可用余额未知时为空 */
+ private Integer resolveAccountStatus(BigDecimal balance) {
+ if (balance == null) {
+ return null;
+ }
+ return balance.signum() < 0 ? ACCOUNT_STATUS_OVERDUE : ACCOUNT_STATUS_NORMAL;
}
/** 列表回填所属车队名称(批量查询,远程失败仅记录日志,不影响列表查询) */
private void fillFleetNames(List<TAppUserWhitelist> records) {
- List<Integer> fleetIds = records.stream()
+ Map<Integer, String> nameMap = loadFleetNameMap(records.stream()
.map(TAppUserWhitelist::getFleetId)
.filter(Objects::nonNull)
- .distinct()
- .collect(Collectors.toList());
- if (fleetIds.isEmpty()) {
- return;
+ .collect(Collectors.toList()));
+ records.forEach(record -> record.setFleetName(nameMap.get(record.getFleetId())));
+ }
+
+ /** 按ID批量加载车队名称(远程失败返回空Map,由调用方按未知处理) */
+ private Map<Integer, String> loadFleetNameMap(List<Integer> fleetIds) {
+ List<Integer> distinctIds = fleetIds.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
+ if (distinctIds.isEmpty()) {
+ return new HashMap<>();
}
try {
- R<List<TFleet>> response = groupCompanyClient.listFleetsByIds(fleetIds);
+ R<List<TFleet>> response = groupCompanyClient.listFleetsByIds(distinctIds);
if (response == null || !R.isSuccess(response) || response.getData() == null) {
- return;
+ return new HashMap<>();
}
Map<Integer, String> nameMap = new HashMap<>();
for (TFleet fleet : response.getData()) {
@@ -896,9 +1040,10 @@
nameMap.put(fleet.getId(), fleet.getFleetName());
}
}
- records.forEach(record -> record.setFleetName(nameMap.get(record.getFleetId())));
+ return nameMap;
} catch (Exception ex) {
- log.warn("回填车队名称失败,fleetIds={}", fleetIds, ex);
+ log.warn("回填车队名称失败,fleetIds={}", distinctIds, ex);
+ return new HashMap<>();
}
}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TChargingCardServiceImpl.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TChargingCardServiceImpl.java
index a33d121..dacce8c 100644
--- a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TChargingCardServiceImpl.java
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TChargingCardServiceImpl.java
@@ -71,6 +71,14 @@
private static final int PASSWORD_MAX_LENGTH = 64;
/** 公司账户模式:2=分配模式(可设置初始分配额度并扣减公司可用余额) */
private static final int ACCOUNT_MODE_ALLOCATION = 2;
+ /** 公司账户模式:1=共享模式(可用余额取公司可用余额) */
+ private static final int ACCOUNT_MODE_SHARED = 1;
+ /** 账户状态:正常 */
+ private static final int ACCOUNT_STATUS_NORMAL = 1;
+ /** 账户状态:欠款 */
+ private static final int ACCOUNT_STATUS_OVERDUE = 2;
+ /** 金额展示精度 */
+ private static final int AMOUNT_SCALE = 2;
/** 初始分配额度上限(对应 decimal(12,2)) */
private static final BigDecimal MAX_INIT_QUOTA = new BigDecimal("99999999.99");
/** 额度分配:单次最多分配的卡片数量,防止单次请求过大 */
@@ -103,7 +111,9 @@
long pageSize = safeQuery.getPageSize() == null || safeQuery.getPageSize() < 1 ? 10 : safeQuery.getPageSize();
Page<TChargingCard> page = new Page<>(pageNum, pageSize);
IPage<TChargingCard> result = baseMapper.selectCardPage(page, safeQuery);
- fillRelationNames(result == null ? null : result.getRecords());
+ List<TChargingCard> records = result == null ? null : result.getRecords();
+ fillFleetNames(records);
+ fillAccountInfo(records);
return result;
}
@@ -384,7 +394,13 @@
@Override
public List<ChargingCardExcelDTO> selectExportList(ChargingCardQueryDTO query) {
- return baseMapper.selectExportList(query == null ? new ChargingCardQueryDTO() : query);
+ List<ChargingCardExcelDTO> items = baseMapper.selectExportList(query == null ? new ChargingCardQueryDTO() : query);
+ if (items == null || items.isEmpty()) {
+ return items;
+ }
+ fillExportFleetNames(items);
+ fillExportAccountInfo(items);
+ return items;
}
@Override
@@ -870,16 +886,11 @@
}
}
- /** 列表回填公司/车队名称(远程失败不影响列表查询) */
- private void fillRelationNames(List<TChargingCard> records) {
+ /** 列表回填车队名称(远程失败不影响列表查询) */
+ private void fillFleetNames(List<TChargingCard> records) {
if (records == null || records.isEmpty()) {
return;
}
- fillFleetNames(records);
- fillCompanyNames(records);
- }
-
- private void fillFleetNames(List<TChargingCard> records) {
List<Integer> fleetIds = records.stream()
.map(TChargingCard::getFleetId)
.filter(Objects::nonNull)
@@ -905,27 +916,123 @@
}
}
- private void fillCompanyNames(List<TChargingCard> records) {
- Map<Integer, String> nameMap = new HashMap<>();
- for (TChargingCard 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 fillAccountInfo(List<TChargingCard> records) {
+ if (records == null || records.isEmpty()) {
+ return;
}
+ Map<Integer, TGroupCompany> companyMap = loadCompanyMap(records.stream()
+ .map(TChargingCard::getCompanyId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+ for (TChargingCard record : records) {
+ TGroupCompany company = companyMap.get(record.getCompanyId());
+ Integer accountMode = company == null ? null : company.getAccountMode();
+ BigDecimal balance = resolveAvailableBalance(accountMode, record.getBalance(),
+ company == null ? null : company.getAvailableBalance());
+ record.setCompanyName(company == null ? null : company.getName());
+ record.setAccountMode(accountMode);
+ record.setAvailableBalance(balance);
+ record.setOverrun(resolveOverrun(balance));
+ record.setAccountStatus(resolveAccountStatus(balance));
+ }
+ }
+
+ /** 导出回填车队名称(远程失败不影响导出) */
+ private void fillExportFleetNames(List<ChargingCardExcelDTO> items) {
+ List<Integer> fleetIds = items.stream()
+ .map(ChargingCardExcelDTO::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());
+ }
+ }
+ items.forEach(item -> item.setFleetName(nameMap.get(item.getFleetId())));
+ } catch (Exception ex) {
+ log.warn("导出回填车队名称失败,fleetIds={}", fleetIds, ex);
+ }
+ }
+
+ /** 导出回填所属公司名称与账户信息(与列表同口径) */
+ private void fillExportAccountInfo(List<ChargingCardExcelDTO> items) {
+ Map<Integer, TGroupCompany> companyMap = loadCompanyMap(items.stream()
+ .map(ChargingCardExcelDTO::getCompanyId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+ for (ChargingCardExcelDTO item : items) {
+ TGroupCompany company = companyMap.get(item.getCompanyId());
+ Integer accountMode = company == null ? null : company.getAccountMode();
+ BigDecimal balance = resolveAvailableBalance(accountMode, item.getBalance(),
+ company == null ? null : company.getAvailableBalance());
+ item.setCompanyName(company == null ? null : company.getName());
+ item.setAccountMode(accountMode);
+ item.setAvailableBalance(balance);
+ item.setOverrun(resolveOverrun(balance));
+ item.setAccountStatus(resolveAccountStatus(balance));
+ }
+ }
+
+ /** 按ID批量加载公司(远程失败或服务降级时返回空Map,由调用方按未知处理) */
+ private Map<Integer, TGroupCompany> loadCompanyMap(List<Integer> companyIds) {
+ List<Integer> distinctIds = companyIds.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
+ if (distinctIds.isEmpty()) {
+ return new HashMap<>();
+ }
+ try {
+ R<List<TGroupCompany>> response = groupCompanyClient.listCompaniesByIds(distinctIds);
+ if (response == null || !R.isSuccess(response) || response.getData() == null) {
+ return new HashMap<>();
+ }
+ Map<Integer, TGroupCompany> companyMap = new HashMap<>();
+ for (TGroupCompany company : response.getData()) {
+ if (company != null && company.getId() != null) {
+ companyMap.put(company.getId(), company);
+ }
+ }
+ return companyMap;
+ } catch (Exception ex) {
+ log.warn("回填公司账户信息失败,companyIds={}", distinctIds, ex);
+ return new HashMap<>();
+ }
+ }
+
+ /** 计算可用余额:分配模式=卡片自身余额,共享模式=公司可用余额,账户模式未知时留空 */
+ private BigDecimal resolveAvailableBalance(Integer accountMode, BigDecimal cardBalance, BigDecimal companyBalance) {
+ if (Integer.valueOf(ACCOUNT_MODE_ALLOCATION).equals(accountMode)) {
+ return cardBalance;
+ }
+ if (Integer.valueOf(ACCOUNT_MODE_SHARED).equals(accountMode)) {
+ return companyBalance;
+ }
+ return null;
+ }
+
+ /** 超额:可用余额为负时取其绝对值,其余情况为空 */
+ private BigDecimal resolveOverrun(BigDecimal balance) {
+ if (balance == null || balance.signum() >= 0) {
+ return null;
+ }
+ return balance.abs().setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
+ }
+
+ /** 账户状态:可用余额为负=欠款,非负=正常,可用余额未知时为空 */
+ private Integer resolveAccountStatus(BigDecimal balance) {
+ if (balance == null) {
+ return null;
+ }
+ return balance.signum() < 0 ? ACCOUNT_STATUS_OVERDUE : ACCOUNT_STATUS_NORMAL;
}
/**
diff --git a/ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TChargingCardMapper.xml b/ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TChargingCardMapper.xml
index ae2fe49..fcb5732 100644
--- a/ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TChargingCardMapper.xml
+++ b/ruoyi-service/ruoyi-account/src/main/resources/mapper/account/TChargingCardMapper.xml
@@ -36,6 +36,8 @@
<select id="selectExportList" resultType="com.ruoyi.account.api.dto.ChargingCardExcelDTO">
SELECT card_no,
balance,
+ company_id,
+ fleet_id,
status
FROM t_charging_card
WHERE del_flag = 0
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 0a27303..b15bec9 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
@@ -6,6 +6,7 @@
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.ruoyi.account.api.dto.AppUserWhitelistAllocateDTO;
import com.ruoyi.account.api.dto.AppUserWhitelistAllocateItemDTO;
+import com.ruoyi.account.api.dto.AppUserWhitelistStatusDTO;
import com.ruoyi.account.api.model.TAppUser;
import com.ruoyi.account.api.model.TAppUserWhitelist;
import com.ruoyi.account.api.model.TCompanyAllocateRecord;
@@ -371,6 +372,89 @@
assertEquals("分配明细中存在重复白名单用户", exception.getMessage());
}
+ // ---------------- updateStatus ----------------
+
+ @Test
+ void updateStatusRejectsInvalidParams() {
+ assertThrows(ServiceException.class, () -> service.updateStatus(null));
+ AppUserWhitelistStatusDTO noId = new AppUserWhitelistStatusDTO();
+ noId.setStatus(1);
+ assertThrows(ServiceException.class, () -> service.updateStatus(noId));
+ AppUserWhitelistStatusDTO badStatus = new AppUserWhitelistStatusDTO();
+ badStatus.setId(1L);
+ badStatus.setStatus(3);
+
+ ServiceException exception = assertThrows(ServiceException.class, () -> service.updateStatus(badStatus));
+
+ assertEquals("白名单用户状态参数错误", exception.getMessage());
+ verify(mapper, never()).update(isNull(), any(Wrapper.class));
+ }
+
+ @Test
+ void updateStatusRejectsMissingRecord() {
+ when(mapper.selectById(1L)).thenReturn(null);
+ AppUserWhitelistStatusDTO dto = new AppUserWhitelistStatusDTO();
+ dto.setId(1L);
+ dto.setStatus(2);
+
+ ServiceException exception = assertThrows(ServiceException.class, () -> service.updateStatus(dto));
+
+ assertEquals("白名单用户不存在或已删除", exception.getMessage());
+ verify(mapper, never()).update(isNull(), any(Wrapper.class));
+ }
+
+ @Test
+ void updateStatusKeepsUnchangedStatusWithoutUpdate() {
+ TAppUserWhitelist existing = new TAppUserWhitelist();
+ existing.setId(1L);
+ existing.setStatus(1);
+ when(mapper.selectById(1L)).thenReturn(existing);
+ AppUserWhitelistStatusDTO dto = new AppUserWhitelistStatusDTO();
+ dto.setId(1L);
+ dto.setStatus(1);
+
+ assertTrue(service.updateStatus(dto));
+
+ // 状态未变化时视为幂等成功,不产生更新语句,避免污染 update_time / update_by
+ verify(mapper, never()).update(isNull(), any(Wrapper.class));
+ }
+
+ @Test
+ void updateStatusDisablesWhitelist() {
+ TAppUserWhitelist existing = new TAppUserWhitelist();
+ existing.setId(1L);
+ existing.setStatus(1);
+ when(mapper.selectById(1L)).thenReturn(existing);
+ when(mapper.update(isNull(), any(Wrapper.class))).thenReturn(1);
+ AppUserWhitelistStatusDTO dto = new AppUserWhitelistStatusDTO();
+ dto.setId(1L);
+ dto.setStatus(2);
+
+ assertTrue(service.updateStatus(dto));
+
+ ArgumentCaptor<Wrapper> wrapperCaptor = ArgumentCaptor.forClass(Wrapper.class);
+ verify(mapper).update(isNull(), wrapperCaptor.capture());
+ String sqlSet = wrapperCaptor.getValue().getSqlSet();
+ assertTrue(sqlSet.contains("status"));
+ assertTrue(sqlSet.contains("update_time"));
+ }
+
+ @Test
+ void updateStatusThrowsWhenUpdateAffectsNoRow() {
+ TAppUserWhitelist existing = new TAppUserWhitelist();
+ existing.setId(1L);
+ existing.setStatus(1);
+ when(mapper.selectById(1L)).thenReturn(existing);
+ when(mapper.update(isNull(), any(Wrapper.class))).thenReturn(0);
+ AppUserWhitelistStatusDTO dto = new AppUserWhitelistStatusDTO();
+ dto.setId(1L);
+ dto.setStatus(2);
+
+ ServiceException exception = assertThrows(ServiceException.class, () -> service.updateStatus(dto));
+
+ assertEquals("白名单用户状态更新失败,请重试", exception.getMessage());
+ }
+
private TGroupCompany allocationCompany(Integer id, String name) {
TGroupCompany company = new TGroupCompany();
company.setId(id);
diff --git a/ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TChargingCardServiceImplTest.java b/ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TChargingCardServiceImplTest.java
index d573a5f..ed8a040 100644
--- a/ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TChargingCardServiceImplTest.java
+++ b/ruoyi-service/ruoyi-account/src/test/java/com/ruoyi/account/service/impl/TChargingCardServiceImplTest.java
@@ -42,10 +42,12 @@
import java.math.BigDecimal;
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.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -557,14 +559,102 @@
Page<TChargingCard> page = new Page<>(1, 10);
page.setRecords(Collections.singletonList(record));
when(mapper.selectCardPage(any(Page.class), any())).thenReturn(page);
- when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(allocationCompany(9, "测试公司")));
+ when(groupCompanyClient.listCompaniesByIds(anyList()))
+ .thenReturn(R.ok(Collections.singletonList(allocationCompany(9, "测试公司"))));
when(groupCompanyClient.listFleetsByIds(anyList()))
.thenReturn(R.ok(Collections.singletonList(fleet(3, 9, "测试车队"))));
IPage<TChargingCard> result = service.selectCardPage(null);
- assertEquals("测试公司", result.getRecords().get(0).getCompanyName());
- assertEquals("测试车队", result.getRecords().get(0).getFleetName());
+ TChargingCard filled = result.getRecords().get(0);
+ assertEquals("测试公司", filled.getCompanyName());
+ assertEquals("测试车队", filled.getFleetName());
+ // 分配模式:可用余额取卡片自身余额,非负为正常、无超额
+ assertEquals(2, filled.getAccountMode());
+ assertEquals(0, new BigDecimal("10.00").compareTo(filled.getAvailableBalance()));
+ assertNull(filled.getOverrun());
+ assertEquals(1, filled.getAccountStatus());
+ }
+
+ @Test
+ void selectCardPageUsesCompanyBalanceForSharedModeCard() {
+ TChargingCard record = cardWithQuota(2L, 9, null);
+ Page<TChargingCard> page = new Page<>(1, 10);
+ page.setRecords(Collections.singletonList(record));
+ when(mapper.selectCardPage(any(Page.class), any())).thenReturn(page);
+ TGroupCompany shared = new TGroupCompany();
+ shared.setId(9);
+ shared.setName("共享公司");
+ shared.setAccountMode(1);
+ shared.setAvailableBalance(new BigDecimal("100.00"));
+ when(groupCompanyClient.listCompaniesByIds(anyList()))
+ .thenReturn(R.ok(Collections.singletonList(shared)));
+
+ IPage<TChargingCard> result = service.selectCardPage(null);
+
+ TChargingCard filled = result.getRecords().get(0);
+ assertEquals(1, filled.getAccountMode());
+ assertEquals(0, new BigDecimal("100.00").compareTo(filled.getAvailableBalance()));
+ assertNull(filled.getOverrun());
+ assertEquals(1, filled.getAccountStatus());
+ }
+
+ @Test
+ void selectCardPageComputesOverrunForNegativeCardBalance() {
+ TChargingCard record = cardWithQuota(3L, 9, new BigDecimal("-5.50"));
+ Page<TChargingCard> page = new Page<>(1, 10);
+ page.setRecords(Collections.singletonList(record));
+ when(mapper.selectCardPage(any(Page.class), any())).thenReturn(page);
+ when(groupCompanyClient.listCompaniesByIds(anyList()))
+ .thenReturn(R.ok(Collections.singletonList(allocationCompany(9, "分配公司"))));
+
+ IPage<TChargingCard> result = service.selectCardPage(null);
+
+ TChargingCard filled = result.getRecords().get(0);
+ assertEquals(0, new BigDecimal("-5.50").compareTo(filled.getAvailableBalance()));
+ assertEquals(0, new BigDecimal("5.50").compareTo(filled.getOverrun()));
+ assertEquals(2, filled.getAccountStatus());
+ }
+
+ @Test
+ void selectCardPageLeavesAccountFieldsEmptyWhenCompanyUnavailable() {
+ TChargingCard record = cardWithQuota(4L, 9, new BigDecimal("8.00"));
+ Page<TChargingCard> page = new Page<>(1, 10);
+ page.setRecords(Collections.singletonList(record));
+ when(mapper.selectCardPage(any(Page.class), any())).thenReturn(page);
+ when(groupCompanyClient.listCompaniesByIds(anyList())).thenReturn(R.fail("远程不可用"));
+
+ IPage<TChargingCard> result = service.selectCardPage(null);
+
+ TChargingCard filled = result.getRecords().get(0);
+ assertNull(filled.getCompanyName());
+ assertNull(filled.getAccountMode());
+ assertNull(filled.getAvailableBalance());
+ assertNull(filled.getOverrun());
+ assertNull(filled.getAccountStatus());
+ }
+
+ @Test
+ void selectExportListFillsCompanyFleetAndAccountInfo() {
+ ChargingCardExcelDTO item = new ChargingCardExcelDTO();
+ item.setCardNo("CARD-9");
+ item.setCompanyId(9);
+ item.setFleetId(3);
+ item.setBalance(new BigDecimal("20.00"));
+ when(mapper.selectExportList(any())).thenReturn(Collections.singletonList(item));
+ when(groupCompanyClient.listCompaniesByIds(anyList()))
+ .thenReturn(R.ok(Collections.singletonList(allocationCompany(9, "测试公司"))));
+ when(groupCompanyClient.listFleetsByIds(anyList()))
+ .thenReturn(R.ok(Collections.singletonList(fleet(3, 9, "测试车队"))));
+
+ List<ChargingCardExcelDTO> result = service.selectExportList(null);
+
+ assertEquals("测试公司", result.get(0).getCompanyName());
+ assertEquals("测试车队", result.get(0).getFleetName());
+ assertEquals(2, result.get(0).getAccountMode());
+ assertEquals(0, new BigDecimal("20.00").compareTo(result.get(0).getAvailableBalance()));
+ assertNull(result.get(0).getOverrun());
+ assertEquals(1, result.get(0).getAccountStatus());
}
private TGroupCompany allocationCompany(Integer id, String name) {
diff --git a/sql/20260929_whitelist_export_status_auth.sql b/sql/20260929_whitelist_export_status_auth.sql
new file mode 100644
index 0000000..f64a1e2
--- /dev/null
+++ b/sql/20260929_whitelist_export_status_auth.sql
@@ -0,0 +1,57 @@
+-- 白名单用户管理-导出 / 启停状态切换 按钮权限增量脚本
+-- 说明:本功能无表结构变更(复用 t_app_user_whitelist 现有列与远程回填的公司账户信息),
+-- 仅在 charging_pile_other 逻辑库新增按钮权限。
+-- 幂等:已存在同名 perms 时自动跳过,可重复执行。
+
+USE charging_pile_other;
+
+-- 白名单用户管理-导出按钮权限;父菜单按现有白名单用户管理菜单定位(历史数据可能存在多个节点,逐一定位均已覆盖)。
+INSERT INTO `sys_menu`
+(`menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
+SELECT permissions.menu_name, parent.menu_id, permissions.order_num, '#', '', '', 1, 0, 'F', '0', '0', permissions.perms, '#', 'admin', NOW(), '', NULL, ''
+FROM `sys_menu` parent
+JOIN (
+ SELECT '导出' AS menu_name, 12 AS order_num, '/appUserWhitelist/export' AS perms
+) permissions
+WHERE parent.perms = '/appUserWhitelist/list'
+ AND NOT EXISTS (
+ SELECT 1 FROM `sys_menu` existing WHERE existing.perms = permissions.perms
+ );
+
+-- 白名单用户管理-启用/停用按钮权限(列表启停开关)
+INSERT INTO `sys_menu`
+(`menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
+SELECT permissions.menu_name, parent.menu_id, permissions.order_num, '#', '', '', 1, 0, 'F', '0', '0', permissions.perms, '#', 'admin', NOW(), '', NULL, ''
+FROM `sys_menu` parent
+JOIN (
+ SELECT '启用/停用' AS menu_name, 13 AS order_num, '/appUserWhitelist/status' AS perms
+) permissions
+WHERE parent.perms = '/appUserWhitelist/list'
+ AND NOT EXISTS (
+ SELECT 1 FROM `sys_menu` existing WHERE existing.perms = permissions.perms
+ );
+
+-- 默认仅授予管理员角色;其他角色由后台按职责单独授权。
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, child.menu_id
+FROM `sys_menu` child
+WHERE child.perms = '/appUserWhitelist/export'
+ AND child.parent_id IN (
+ SELECT parent.menu_id FROM `sys_menu` parent WHERE parent.perms = '/appUserWhitelist/list'
+ )
+ AND NOT EXISTS (
+ SELECT 1 FROM `sys_role_menu` existing
+ WHERE existing.role_id = 1 AND existing.menu_id = child.menu_id
+ );
+
+INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
+SELECT 1, child.menu_id
+FROM `sys_menu` child
+WHERE child.perms = '/appUserWhitelist/status'
+ AND child.parent_id IN (
+ SELECT parent.menu_id FROM `sys_menu` parent WHERE parent.perms = '/appUserWhitelist/list'
+ )
+ AND NOT EXISTS (
+ SELECT 1 FROM `sys_role_menu` existing
+ WHERE existing.role_id = 1 AND existing.menu_id = child.menu_id
+ );
--
Gitblit v1.7.1