| | |
| | | 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; |
| | | import com.ruoyi.account.api.model.TCompanyAllocateRecord; |
| | | import com.ruoyi.account.api.vo.AppUserWhitelistAllocateVO; |
| | | import com.ruoyi.account.api.vo.AppUserWhitelistMatchVO; |
| | | import com.ruoyi.account.api.vo.AppUserWhitelistImportErrorVO; |
| | | import com.ruoyi.account.api.vo.AppUserWhitelistImportResultVO; |
| | | import com.ruoyi.account.mapper.TAppUserWhitelistMapper; |
| | | import com.ruoyi.account.service.PartnerAppUserAccessService; |
| | | import com.ruoyi.account.service.TAppUserService; |
| | | import com.ruoyi.account.service.TAppUserWhitelistService; |
| | | import com.ruoyi.account.service.TAppUserWhitelistSiteService; |
| | | import com.ruoyi.account.service.TCompanyAllocateRecordService; |
| | | import com.ruoyi.chargingPile.api.domain.SiteMenu; |
| | | import com.ruoyi.chargingPile.api.feignClient.PartnerClient; |
| | | import com.ruoyi.chargingPile.api.feignClient.SiteClient; |
| | |
| | | |
| | | private static final Pattern PHONE_PATTERN = Pattern.compile("^1\\d{10}$"); |
| | | private static final String IMPORT_HEADER_PHONE = "用户手机号"; |
| | | private static final int BENEFIT_ALL_FREE = 1; |
| | | private static final int BENEFIT_ELECTRIC_FREE = 2; |
| | | private static final int BENEFIT_SERVICE_FREE = 3; |
| | | private static final int SCOPE_ALL_SITE = 1; |
| | | private static final int SCOPE_SELECTED_SITE = 2; |
| | | private static final int SCOPE_PARTNER = 3; |
| | |
| | | /** 姓名最大长度(对应 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)) */ |
| | | 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 int MAX_ALLOCATE_WHITELIST_COUNT = 200; |
| | | /** 白名单额度分配并发锁前缀:按公司串行,防止同公司并发分配重复占用可分配余额 */ |
| | | private static final String ALLOCATE_LOCK_PREFIX = "APP_USER_WHITELIST_QUOTA:COMPANY:"; |
| | | /** 获取锁最长等待时间(秒) */ |
| | | private static final long LOCK_WAIT_SECONDS = 5L; |
| | | /** 锁自动释放时间(秒),兜住异常未解锁的场景 */ |
| | |
| | | @Autowired |
| | | private RedissonClient redissonClient; |
| | | |
| | | @Resource |
| | | private TCompanyAllocateRecordService companyAllocateRecordService; |
| | | |
| | | @Resource |
| | | private PartnerAppUserAccessService partnerAppUserAccessService; |
| | | |
| | | @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; |
| | | // 公司账号强制按所属公司过滤,覆盖前端传入的公司筛选 |
| | | safeQuery.setCompanyId(partnerAppUserAccessService.resolveCompanyFilter(safeQuery.getCompanyId())); |
| | | 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); |
| | | // 公司账号越权校验:只能操作本公司数据 |
| | | partnerAppUserAccessService.assertCompanyVisible(existing.getCompanyId()); |
| | | 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 |
| | |
| | | throw new ServiceException("白名单用户不存在"); |
| | | } |
| | | assertPartnerVisible(whitelist); |
| | | // 公司账号越权校验:只能查看本公司数据 |
| | | partnerAppUserAccessService.assertCompanyVisible(whitelist.getCompanyId()); |
| | | fillDisplay(Collections.singletonList(whitelist)); |
| | | return whitelist; |
| | | } |
| | |
| | | continue; |
| | | } |
| | | assertPartnerVisible(whitelist); |
| | | // 公司账号越权校验:只能删除本公司数据 |
| | | partnerAppUserAccessService.assertCompanyVisible(whitelist.getCompanyId()); |
| | | this.removeById(whitelistId); |
| | | whitelistSiteService.remove(Wrappers.lambdaQuery(TAppUserWhitelistSite.class) |
| | | .eq(TAppUserWhitelistSite::getWhitelistId, whitelistId)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 批量解绑车队:仅清空车队归属(fleet_id 置空),公司、余额、状态等不受影响; |
| | | * 未绑定车队的选中记录忽略不计 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public int unbindFleet(List<Long> ids) { |
| | | if (ids == null || ids.isEmpty()) { |
| | | throw new ServiceException("请选择需要解绑车队的白名单用户"); |
| | | } |
| | | List<Long> distinct = ids.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList()); |
| | | if (distinct.isEmpty()) { |
| | | throw new ServiceException("请选择需要解绑车队的白名单用户"); |
| | | } |
| | | List<TAppUserWhitelist> records = listByIds(distinct); |
| | | if (records.size() != distinct.size()) { |
| | | throw new ServiceException("部分白名单用户不存在或已删除"); |
| | | } |
| | | for (TAppUserWhitelist record : records) { |
| | | assertPartnerVisible(record); |
| | | // 公司账号越权校验:只能解绑本公司数据 |
| | | partnerAppUserAccessService.assertCompanyVisible(record.getCompanyId()); |
| | | } |
| | | List<Long> boundIds = records.stream() |
| | | .filter(record -> record.getFleetId() != null) |
| | | .map(TAppUserWhitelist::getId) |
| | | .collect(Collectors.toList()); |
| | | if (boundIds.isEmpty()) { |
| | | return 0; |
| | | } |
| | | // 显式 set null 清空车队归属(MyBatis-Plus 字段策略会忽略实体 null,需走 UpdateWrapper) |
| | | boolean updated = lambdaUpdate() |
| | | .in(TAppUserWhitelist::getId, boundIds) |
| | | .set(TAppUserWhitelist::getFleetId, null) |
| | | .set(TAppUserWhitelist::getUpdateBy, getUsername()) |
| | | .setSql("update_time = now()") |
| | | .update(); |
| | | if (!updated) { |
| | | throw new ServiceException("白名单用户车队解绑失败,请重试"); |
| | | } |
| | | return boundIds.size(); |
| | | } |
| | | |
| | | @Override |
| | |
| | | AppUserWhitelistMatchVO result = new AppUserWhitelistMatchVO(); |
| | | result.setMatched(true); |
| | | result.setWhitelistId(whitelist.getId()); |
| | | result.setBenefitType(whitelist.getBenefitType()); |
| | | // 白名单权益已下线(充电完全按企业优惠结算),命中结果不再返回权益类型与名称 |
| | | result.setScopeType(whitelist.getScopeType()); |
| | | result.setBenefitName(getBenefitName(whitelist.getBenefitType())); |
| | | result.setCompanyId(whitelist.getCompanyId()); |
| | | result.setFleetId(whitelist.getFleetId()); |
| | | //企业账户分配模式:返回白名单账户可用余额,供建单侧计算下发金额与锁定额度 |
| | | result.setBalance(whitelist.getBalance()); |
| | | return result; |
| | | } |
| | | |
| | | @Override |
| | | public List<TAppUserWhitelist> listByCompany(Integer companyId) { |
| | | if (companyId == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | return lambdaQuery() |
| | | .eq(TAppUserWhitelist::getCompanyId, companyId) |
| | | .eq(TAppUserWhitelist::getDelFlag, false) |
| | | .orderByDesc(BasePojo::getCreateTime) |
| | | .list(); |
| | | } |
| | | |
| | | /** |
| | | * 锁定白名单账户余额(企业账户流程-分配模式,实扣冻结): |
| | | * 余额立即扣减并计入 locked_amount,余额不足时条件更新命中 0 行直接失败; |
| | | * 与 VIN/刷卡账户锁定口径一致(T VinManagementServiceImpl/TChargingCardServiceImpl)。 |
| | | */ |
| | | @Override |
| | | public BigDecimal lockBalance(Long whitelistId, BigDecimal amount) { |
| | | if (whitelistId == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new ServiceException("锁定金额参数错误"); |
| | | } |
| | | boolean updated = lambdaUpdate() |
| | | .eq(TAppUserWhitelist::getId, whitelistId) |
| | | .eq(TAppUserWhitelist::getDelFlag, false) |
| | | .ge(TAppUserWhitelist::getBalance, amount) |
| | | .setSql("balance = IFNULL(balance, 0) - " + amount.toPlainString() |
| | | + ", locked_amount = IFNULL(locked_amount, 0) + " + amount.toPlainString()) |
| | | .update(); |
| | | if (!updated) { |
| | | throw new ServiceException("白名单账户余额不足"); |
| | | } |
| | | TAppUserWhitelist latest = getById(whitelistId); |
| | | return latest == null || latest.getBalance() == null ? BigDecimal.ZERO : latest.getBalance(); |
| | | } |
| | | |
| | | /** |
| | | * 结算白名单账户余额:先释放锁定额,再按实际消费扣减,允许扣成负余额 |
| | | * (与 VIN 结算口径一致:balance = balance + locked - actual,locked_amount 归零下限保护)。 |
| | | */ |
| | | @Override |
| | | public BigDecimal settleBalance(Long whitelistId, BigDecimal lockedAmount, BigDecimal actualAmount) { |
| | | BigDecimal lock = lockedAmount == null ? BigDecimal.ZERO : lockedAmount; |
| | | BigDecimal actual = actualAmount == null ? BigDecimal.ZERO : actualAmount; |
| | | if (whitelistId == null || lock.compareTo(BigDecimal.ZERO) < 0 || actual.compareTo(BigDecimal.ZERO) < 0) { |
| | | throw new ServiceException("结算金额参数错误"); |
| | | } |
| | | boolean updated = lambdaUpdate() |
| | | .eq(TAppUserWhitelist::getId, whitelistId) |
| | | .setSql("balance = IFNULL(balance, 0) + " + lock.toPlainString() + " - " + actual.toPlainString() |
| | | + ", locked_amount = GREATEST(IFNULL(locked_amount, 0) - " + lock.toPlainString() + ", 0)") |
| | | .update(); |
| | | if (!updated) { |
| | | throw new ServiceException("白名单用户不存在或已删除"); |
| | | } |
| | | TAppUserWhitelist latest = getById(whitelistId); |
| | | return latest == null || latest.getBalance() == null ? BigDecimal.ZERO : latest.getBalance(); |
| | | } |
| | | |
| | | /** |
| | | * 释放白名单账户锁定金额:余额回补、锁定额度扣减(未产生消费/订单回滚时使用)。 |
| | | */ |
| | | @Override |
| | | public BigDecimal unlockBalance(Long whitelistId, BigDecimal amount) { |
| | | if (whitelistId == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new ServiceException("释放金额参数错误"); |
| | | } |
| | | boolean updated = lambdaUpdate() |
| | | .eq(TAppUserWhitelist::getId, whitelistId) |
| | | .setSql("balance = IFNULL(balance, 0) + " + amount.toPlainString() |
| | | + ", locked_amount = GREATEST(IFNULL(locked_amount, 0) - " + amount.toPlainString() + ", 0)") |
| | | .update(); |
| | | if (!updated) { |
| | | throw new ServiceException("白名单用户不存在或已删除"); |
| | | } |
| | | TAppUserWhitelist latest = getById(whitelistId); |
| | | return latest == null || latest.getBalance() == null ? BigDecimal.ZERO : latest.getBalance(); |
| | | } |
| | | |
| | | @Override |
| | | public AppUserWhitelistAllocateVO getAllocateOverview(Integer companyId) { |
| | | // 公司账号只能查看本公司额度概览,覆盖前端传入的公司 |
| | | companyId = partnerAppUserAccessService.resolveCompanyFilter(companyId); |
| | | if (companyId == null) { |
| | | throw new ServiceException("请选择公司"); |
| | | } |
| | | TGroupCompany company = loadCompany(companyId); |
| | | if (company == null) { |
| | | throw new ServiceException("公司不存在或已删除"); |
| | | } |
| | | return buildAllocateOverview(company); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public AppUserWhitelistAllocateVO allocateQuota(AppUserWhitelistAllocateDTO allocateDTO) { |
| | | // 公司账号只能给本公司分配额度,覆盖前端传入的公司 |
| | | allocateDTO.setCompanyId(partnerAppUserAccessService.resolveCompanyFilter(allocateDTO.getCompanyId())); |
| | | Map<Long, BigDecimal> amounts = validateAllocateRequest(allocateDTO); |
| | | Integer companyId = allocateDTO.getCompanyId(); |
| | | // 公司必须存在、启用且为分配模式(共享模式不支持额度分配) |
| | | TGroupCompany company = loadAllocationCompany(companyId); |
| | | BigDecimal total = amounts.values().stream() |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add) |
| | | .setScale(2, RoundingMode.HALF_UP); |
| | | // 同公司分配串行化,避免并发下重复占用可分配余额 |
| | | RLock lock = redissonClient.getLock(ALLOCATE_LOCK_PREFIX + companyId); |
| | | boolean locked = tryQuotaLock(lock, "该公司额度分配正在处理中,请稍后重试"); |
| | | try { |
| | | List<TAppUserWhitelist> whitelists = listByIds(amounts.keySet()); |
| | | if (whitelists == null || whitelists.size() != amounts.size()) { |
| | | throw new ServiceException("部分白名单用户不存在或已删除"); |
| | | } |
| | | for (TAppUserWhitelist whitelist : whitelists) { |
| | | // 合作商账号仅能分配自身可见的白名单记录 |
| | | assertPartnerVisible(whitelist); |
| | | BigDecimal amount = amounts.get(whitelist.getId()); |
| | | if (!companyId.equals(whitelist.getCompanyId())) { |
| | | throw new ServiceException("白名单用户【" + whitelist.getPhone() + "】不属于所选公司,无法分配额度"); |
| | | } |
| | | if (!Integer.valueOf(STATUS_ENABLED).equals(whitelist.getStatus())) { |
| | | throw new ServiceException("白名单用户【" + whitelist.getPhone() + "】已停用,禁止分配额度"); |
| | | } |
| | | BigDecimal balance = normalizeQuota(whitelist.getBalance()); |
| | | if (balance.add(amount).compareTo(MAX_INIT_QUOTA) > 0) { |
| | | throw new ServiceException("白名单用户【" + whitelist.getPhone() + "】分配后余额超过上限" |
| | | + MAX_INIT_QUOTA.toPlainString()); |
| | | } |
| | | } |
| | | // 先扣公司可分配余额(条件更新,余额不足直接失败),失败无需补偿 |
| | | R<BigDecimal> deductResponse = groupCompanyClient.deductBalance(companyId, total); |
| | | if (deductResponse == null || !R.isSuccess(deductResponse)) { |
| | | throw new ServiceException(buildDeductFailureMessage(deductResponse)); |
| | | } |
| | | try { |
| | | String operator = getUsername(); |
| | | for (TAppUserWhitelist whitelist : whitelists) { |
| | | BigDecimal amount = amounts.get(whitelist.getId()); |
| | | BigDecimal before = normalizeQuota(whitelist.getBalance()); |
| | | if (baseMapper.addBalance(whitelist.getId(), amount, operator) != 1) { |
| | | throw new ServiceException("白名单用户【" + whitelist.getPhone() + "】额度分配失败,请重试"); |
| | | } |
| | | recordWhitelistAllocate(whitelist.getPhone(), whitelist.getName(), companyId, before, amount, |
| | | TCompanyAllocateRecord.SOURCE_ALLOCATE, buildWhitelistAllocateRemark(company)); |
| | | } |
| | | } catch (RuntimeException ex) { |
| | | // 白名单余额变更随事务回滚,公司余额为远程扣减需显式退还 |
| | | refundQuietly(companyId, total); |
| | | throw ex; |
| | | } |
| | | AppUserWhitelistAllocateVO vo = buildAllocateOverview(company); |
| | | vo.setAvailableBalance(normalizeMoney(deductResponse.getData())); |
| | | return vo; |
| | | } finally { |
| | | unlockQuota(lock, locked); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | throw new ServiceException("请选择正确的状态"); |
| | | } |
| | | saveDTO.setStatus(status); |
| | | // 享受权益不传默认全部免费,传了仍按原规则校验 |
| | | Integer benefitType = saveDTO.getBenefitType() == null ? BENEFIT_ALL_FREE : saveDTO.getBenefitType(); |
| | | if (!isValidBenefit(benefitType)) { |
| | | throw new ServiceException("请选择正确的享受权益"); |
| | | } |
| | | saveDTO.setBenefitType(benefitType); |
| | | // 白名单权益已下线:白名单用户充电完全按企业优惠结算,忽略入参中的享受权益 |
| | | saveDTO.setBenefitType(null); |
| | | // 白名单电站不传默认全部电站,传了仍按原规则校验 |
| | | Integer scopeType = saveDTO.getScopeType() == null ? SCOPE_ALL_SITE : saveDTO.getScopeType(); |
| | | if (!isValidScope(scopeType)) { |
| | |
| | | } |
| | | if (Objects.equals(scopeType, SCOPE_PARTNER) && saveDTO.getPartnerId() == null) { |
| | | throw new ServiceException("请选择加盟商"); |
| | | } |
| | | // 所属公司与所属车队必填:白名单用户归属企业,充电完全按企业优惠结算 |
| | | if (saveDTO.getCompanyId() == null) { |
| | | throw new ServiceException("请选择所属公司"); |
| | | } |
| | | if (saveDTO.getFleetId() == null) { |
| | | throw new ServiceException("请选择所属车队"); |
| | | } |
| | | validatePartnerDataScope(saveDTO); |
| | | TAppUserWhitelist exists = getByPhone(saveDTO.getPhone()); |
| | |
| | | if (getByPhone(saveDTO.getPhone()) != null) { |
| | | throw new ServiceException("该手机号已存在白名单配置"); |
| | | } |
| | | BigDecimal quota = normalizeQuota(saveDTO.getInitQuota()); |
| | | BigDecimal quota = normalizeQuota(saveDTO.getBalance()); |
| | | Integer companyId = resolveRelationCompanyId(saveDTO); |
| | | if (quota.signum() > 0) { |
| | | requireAllocationCompany(companyId); |
| | |
| | | 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.setBalance(quota.signum() > 0 ? quota : null); |
| | | whitelist.setStatus(saveDTO.getStatus()); |
| | | whitelist.setUpdateBy(getUsername()); |
| | | whitelist.setRemark(saveDTO.getRemark()); |
| | |
| | | whitelistSiteService.saveBatch(sites); |
| | | } |
| | | } |
| | | recordWhitelistAllocate(whitelist.getPhone(), whitelist.getName(), companyId, BigDecimal.ZERO, quota, |
| | | TCompanyAllocateRecord.SOURCE_CREATE, "新增白名单初始分配额度"); |
| | | } catch (RuntimeException ex) { |
| | | compensate(compensations); |
| | | throw ex; |
| | |
| | | throw new ServiceException("白名单用户不存在"); |
| | | } |
| | | assertPartnerVisible(existing); |
| | | // 公司账号越权校验:只能编辑本公司数据 |
| | | partnerAppUserAccessService.assertCompanyVisible(existing.getCompanyId()); |
| | | // 同白名单并发编辑串行化:额度差额计算、公司余额扣减/退还必须在临界区内,防止重复扣减或重复退还 |
| | | RLock lock = redissonClient.getLock(QUOTA_LOCK_PREFIX + saveDTO.getId()); |
| | | boolean locked = tryQuotaLock(lock, "该白名单额度正在处理中,请稍后重试"); |
| | | List<Runnable> compensations = new ArrayList<>(); |
| | | // 分配记录延迟到白名单落库成功后写入:失败时公司余额已回补,不应留下流水 |
| | | List<Runnable> recordTasks = new ArrayList<>(); |
| | | try { |
| | | TAppUserWhitelist whitelist = this.getById(saveDTO.getId()); |
| | | if (whitelist == null) { |
| | | throw new ServiceException("白名单用户不存在"); |
| | | } |
| | | assertPartnerVisible(whitelist); |
| | | BigDecimal newQuota = normalizeQuota(saveDTO.getInitQuota()); |
| | | BigDecimal newQuota = normalizeQuota(saveDTO.getBalance()); |
| | | Integer newCompanyId = resolveRelationCompanyId(saveDTO); |
| | | if (newQuota.signum() > 0) { |
| | | requireAllocationCompany(newCompanyId); |
| | | } |
| | | Integer oldCompanyId = whitelist.getCompanyId(); |
| | | BigDecimal oldQuota = normalizeQuota(whitelist.getInitQuota()); |
| | | BigDecimal oldQuota = normalizeQuota(whitelist.getBalance()); |
| | | if (Objects.equals(oldCompanyId, newCompanyId)) { |
| | | // 同一公司按差额调整:调增扣差额,调减退差额 |
| | | BigDecimal diff = newQuota.subtract(oldQuota); |
| | | if (diff.signum() > 0 && newCompanyId != null) { |
| | | deductBalance(newCompanyId, diff); |
| | | compensations.add(() -> refundQuietly(newCompanyId, diff)); |
| | | recordTasks.add(() -> recordWhitelistAllocate(saveDTO.getPhone(), saveDTO.getName(), newCompanyId, |
| | | oldQuota, diff, TCompanyAllocateRecord.SOURCE_EDIT, "编辑白名单调整初始分配额度")); |
| | | } else if (diff.signum() < 0 && newCompanyId != null) { |
| | | BigDecimal refund = diff.negate(); |
| | | refundBalance(newCompanyId, refund); |
| | | compensations.add(() -> deductQuietly(newCompanyId, refund)); |
| | | recordTasks.add(() -> recordWhitelistAllocate(saveDTO.getPhone(), saveDTO.getName(), newCompanyId, |
| | | oldQuota, diff, TCompanyAllocateRecord.SOURCE_EDIT, "编辑白名单调减初始分配额度")); |
| | | } |
| | | } else { |
| | | // 跨公司:旧公司全额退还,新公司全额扣减 |
| | | if (oldCompanyId != null && oldQuota.signum() > 0) { |
| | | refundBalance(oldCompanyId, oldQuota); |
| | | compensations.add(() -> deductQuietly(oldCompanyId, oldQuota)); |
| | | recordTasks.add(() -> recordWhitelistAllocate(saveDTO.getPhone(), saveDTO.getName(), oldCompanyId, |
| | | oldQuota, oldQuota.negate(), TCompanyAllocateRecord.SOURCE_EDIT, |
| | | "编辑白名单调整所属公司(原公司退还)")); |
| | | } |
| | | if (newCompanyId != null && newQuota.signum() > 0) { |
| | | deductBalance(newCompanyId, newQuota); |
| | | compensations.add(() -> refundQuietly(newCompanyId, newQuota)); |
| | | recordTasks.add(() -> recordWhitelistAllocate(saveDTO.getPhone(), saveDTO.getName(), newCompanyId, |
| | | BigDecimal.ZERO, newQuota, TCompanyAllocateRecord.SOURCE_EDIT, "编辑白名单调整所属公司")); |
| | | } |
| | | } |
| | | TAppUser appUser = appUserService.lambdaQuery() |
| | |
| | | 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.setBalance(newQuota.signum() > 0 ? newQuota : null); |
| | | whitelist.setStatus(saveDTO.getStatus()); |
| | | whitelist.setUpdateBy(getUsername()); |
| | | whitelist.setRemark(saveDTO.getRemark()); |
| | |
| | | whitelistSiteService.saveBatch(sites); |
| | | } |
| | | } |
| | | recordTasks.forEach(Runnable::run); |
| | | } catch (RuntimeException ex) { |
| | | compensate(compensations); |
| | | throw ex; |
| | |
| | | return this.baseMapper.selectValidByPhone(phone); |
| | | } |
| | | |
| | | /** 写入白名单分配流水(分配为正数、回收为负数;公司为空或金额为0时自动忽略) */ |
| | | private void recordWhitelistAllocate(String phone, String name, Integer companyId, BigDecimal beforeBalance, |
| | | BigDecimal amount, int changeSource, String remark) { |
| | | try { |
| | | companyAllocateRecordService.recordAllocate(TCompanyAllocateRecord.USER_TYPE_WHITELIST, phone, name, |
| | | companyId, changeSource, beforeBalance, amount, remark); |
| | | } catch (RuntimeException ex) { |
| | | // 流水为审计数据,写入失败不阻断白名单额度变更主流程,仅记录日志便于排查 |
| | | log.error("白名单{}分配流水写入失败:{}", phone, ex.getMessage(), ex); |
| | | } |
| | | } |
| | | |
| | | private List<String> validateImportRow(AppUserWhitelistImportDTO row) { |
| | | List<String> errors = new ArrayList<>(); |
| | | if (row == null) { |
| | |
| | | return errors; |
| | | } |
| | | |
| | | /** 导入数据统一按默认值处理:全部免费 + 全部电站 + 启用 */ |
| | | /** 导入数据统一按默认值处理:全部电站 + 启用(权益已下线,不再写入享受权益) */ |
| | | private AppUserWhitelistSaveDTO buildSaveDTO(AppUserWhitelistImportDTO row) { |
| | | AppUserWhitelistSaveDTO dto = new AppUserWhitelistSaveDTO(); |
| | | dto.setPhone(normalize(row.getPhone())); |
| | | dto.setBenefitType(BENEFIT_ALL_FREE); |
| | | dto.setScopeType(SCOPE_ALL_SITE); |
| | | dto.setSiteIds(Collections.emptyList()); |
| | | dto.setStatus(STATUS_ENABLED); |
| | |
| | | Map<Integer, Partner> partnerMap = getPartnerMap(records, siteMap); |
| | | for (TAppUserWhitelist record : records) { |
| | | record.setUid(record.getId() == null ? null : record.getId().toString()); |
| | | record.setBenefitName(getBenefitName(record.getBenefitType())); |
| | | // 白名单权益已下线,列表不再填充享受权益名称 |
| | | record.setScopeName(getScopeName(record.getScopeType())); |
| | | if (record.getPartnerId() != null) { |
| | | Partner partner = partnerMap.get(record.getPartnerId()); |
| | |
| | | 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 Integer resolveRelationCompanyId(AppUserWhitelistSaveDTO saveDTO) { |
| | | // 公司账号新增/编辑白名单用户时强制归属本公司,覆盖前端传入的公司 |
| | | saveDTO.setCompanyId(partnerAppUserAccessService.resolveCompanyFilter(saveDTO.getCompanyId())); |
| | | Integer companyId = saveDTO.getCompanyId(); |
| | | if (companyId != null && loadCompany(companyId) == null) { |
| | | throw new ServiceException("所属公司不存在或已删除"); |
| | |
| | | throw new ServiceException("公司信息查询失败:" + (response == null ? "服务无响应" : response.getMsg())); |
| | | } |
| | | return response.getData(); |
| | | } |
| | | |
| | | /** |
| | | * 额度分配:加载公司并校验为启用且分配模式。 |
| | | * 与初始分配额度校验分开,便于按场景返回更准确的提示,且不影响既有链路。 |
| | | */ |
| | | private TGroupCompany loadAllocationCompany(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("公司已停用,无法分配额度"); |
| | | } |
| | | return company; |
| | | } |
| | | |
| | | /** 组装额度分配概览(可分配余额、总账户余额) */ |
| | | private AppUserWhitelistAllocateVO buildAllocateOverview(TGroupCompany company) { |
| | | AppUserWhitelistAllocateVO vo = new AppUserWhitelistAllocateVO(); |
| | | vo.setCompanyId(company.getId()); |
| | | vo.setCompanyName(company.getName()); |
| | | vo.setAccountMode(company.getAccountMode()); |
| | | vo.setAvailableBalance(normalizeMoney(company.getAvailableBalance())); |
| | | vo.setTotalRechargeAmount(normalizeMoney(company.getTotalRechargeAmount())); |
| | | return vo; |
| | | } |
| | | |
| | | /** 金额归一化:null 视为 0,统一保留2位小数 */ |
| | | private BigDecimal normalizeMoney(BigDecimal amount) { |
| | | return (amount == null ? BigDecimal.ZERO : amount).setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | /** 校验额度分配请求并返回 白名单记录ID → 本次分配金额(保持提交顺序) */ |
| | | private Map<Long, BigDecimal> validateAllocateRequest(AppUserWhitelistAllocateDTO dto) { |
| | | if (dto == null) { |
| | | throw new ServiceException("分配参数不能为空"); |
| | | } |
| | | if (dto.getCompanyId() == null) { |
| | | throw new ServiceException("请选择公司"); |
| | | } |
| | | if (dto.getItems() == null || dto.getItems().isEmpty()) { |
| | | throw new ServiceException("请至少为一条白名单用户填写本次分配金额"); |
| | | } |
| | | if (dto.getItems().size() > MAX_ALLOCATE_WHITELIST_COUNT) { |
| | | throw new ServiceException("单次最多分配" + MAX_ALLOCATE_WHITELIST_COUNT + "条白名单用户"); |
| | | } |
| | | Map<Long, BigDecimal> amounts = new LinkedHashMap<>(); |
| | | for (AppUserWhitelistAllocateItemDTO item : dto.getItems()) { |
| | | if (item == null || item.getWhitelistId() == null) { |
| | | throw new ServiceException("分配明细中存在无效白名单用户"); |
| | | } |
| | | if (amounts.containsKey(item.getWhitelistId())) { |
| | | throw new ServiceException("分配明细中存在重复白名单用户"); |
| | | } |
| | | validateAmount(item.getAmount(), "分配金额"); |
| | | if (item.getAmount().compareTo(MAX_INIT_QUOTA) > 0) { |
| | | throw new ServiceException("单条白名单用户分配金额不能超过" + MAX_INIT_QUOTA.toPlainString()); |
| | | } |
| | | amounts.put(item.getWhitelistId(), item.getAmount().setScale(2, RoundingMode.HALF_UP)); |
| | | } |
| | | return amounts; |
| | | } |
| | | |
| | | /** 校验金额:必填、大于0、最多2位小数 */ |
| | | private void validateAmount(BigDecimal amount, String fieldName) { |
| | | if (amount == null) { |
| | | throw new ServiceException(fieldName + "不能为空"); |
| | | } |
| | | if (amount.signum() <= 0) { |
| | | throw new ServiceException(fieldName + "必须大于0"); |
| | | } |
| | | if (amount.stripTrailingZeros().scale() > 2) { |
| | | throw new ServiceException(fieldName + "最多保留2位小数"); |
| | | } |
| | | } |
| | | |
| | | /** 公司余额扣减失败提示:余额不足场景给出可读文案 */ |
| | | private String buildDeductFailureMessage(R<BigDecimal> response) { |
| | | if (response == null) { |
| | | return "公司可用余额扣减失败:服务无响应"; |
| | | } |
| | | String msg = response.getMsg(); |
| | | if (msg != null && msg.contains("余额不足")) { |
| | | return "公司可分配余额不足,无法完成本次分配"; |
| | | } |
| | | return "公司可用余额扣减失败:" + msg; |
| | | } |
| | | |
| | | /** 组装额度分配流水备注(含来源公司) */ |
| | | private String buildWhitelistAllocateRemark(TGroupCompany company) { |
| | | String companyName = company == null || company.getName() == null ? "" : company.getName(); |
| | | String remark = "额度分配(公司:" + companyName + ")"; |
| | | return remark.length() > 255 ? remark.substring(0, 255) : remark; |
| | | } |
| | | |
| | | private TFleet loadFleet(Integer fleetId) { |
| | |
| | | return StringUtils.isNotBlank(phone) && PHONE_PATTERN.matcher(phone).matches(); |
| | | } |
| | | |
| | | private boolean isValidBenefit(Integer benefitType) { |
| | | return Objects.equals(benefitType, BENEFIT_ALL_FREE) |
| | | || Objects.equals(benefitType, BENEFIT_ELECTRIC_FREE) |
| | | || Objects.equals(benefitType, BENEFIT_SERVICE_FREE); |
| | | } |
| | | |
| | | private boolean isValidScope(Integer scopeType) { |
| | | return Objects.equals(scopeType, SCOPE_ALL_SITE) |
| | | || Objects.equals(scopeType, SCOPE_SELECTED_SITE) |
| | |
| | | |
| | | private String normalize(String value) { |
| | | return value == null ? null : value.trim(); |
| | | } |
| | | |
| | | private String getBenefitName(Integer benefitType) { |
| | | if (Objects.equals(benefitType, BENEFIT_ALL_FREE)) { |
| | | return "全部免费"; |
| | | } |
| | | if (Objects.equals(benefitType, BENEFIT_ELECTRIC_FREE)) { |
| | | return "免电费"; |
| | | } |
| | | if (Objects.equals(benefitType, BENEFIT_SERVICE_FREE)) { |
| | | return "免服务费"; |
| | | } |
| | | return ""; |
| | | } |
| | | |
| | | private String getScopeName(Integer scopeType) { |