| New file |
| | |
| | | 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; |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| | |
| | | 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; |
| | | } |
| | |
| | | @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; |
| | |
| | | @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; |
| | |
| | | |
| | | 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; |
| | |
| | | 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") |
| | |
| | | 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") |
| | |
| | | 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; |
| | |
| | | */ |
| | | 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); |
| | |
| | | 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; |
| | |
| | | /** 姓名最大长度(对应 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)) */ |
| | |
| | | |
| | | @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)) { |
| | |
| | | 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 |
| | |
| | | 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()) { |
| | |
| | | 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<>(); |
| | | } |
| | | } |
| | | |
| | |
| | | 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"); |
| | | /** 额度分配:单次最多分配的卡片数量,防止单次请求过大 */ |
| | |
| | | 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; |
| | | } |
| | | |
| | |
| | | |
| | | @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 |
| | |
| | | } |
| | | } |
| | | |
| | | /** 列表回填公司/车队名称(远程失败不影响列表查询) */ |
| | | 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) |
| | |
| | | } |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | |
| | | /** |
| | |
| | | <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 |
| | |
| | | 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; |
| | |
| | | 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); |
| | |
| | | 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; |
| | |
| | | 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) { |
| New file |
| | |
| | | -- 白名单用户管理-导出 / 启停状态切换 按钮权限增量脚本 |
| | | -- 说明:本功能无表结构变更(复用 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 |
| | | ); |