| | |
| | | if (!Integer.valueOf(STATUS_ENABLED).equals(whitelist.getStatus())) { |
| | | throw new ServiceException("白名单用户【" + whitelist.getPhone() + "】已停用,禁止分配额度"); |
| | | } |
| | | BigDecimal balance = normalizeQuota(whitelist.getBalance()); |
| | | BigDecimal balance = normalizeBalance(whitelist.getBalance()); |
| | | if (balance.add(amount).compareTo(MAX_INIT_QUOTA) > 0) { |
| | | throw new ServiceException("白名单用户【" + whitelist.getPhone() + "】分配后余额超过上限" |
| | | + MAX_INIT_QUOTA.toPlainString()); |
| | |
| | | String operator = getUsername(); |
| | | for (TAppUserWhitelist whitelist : whitelists) { |
| | | BigDecimal amount = amounts.get(whitelist.getId()); |
| | | BigDecimal before = normalizeQuota(whitelist.getBalance()); |
| | | BigDecimal before = normalizeBalance(whitelist.getBalance()); |
| | | if (baseMapper.addBalance(whitelist.getId(), amount, operator) != 1) { |
| | | throw new ServiceException("白名单用户【" + whitelist.getPhone() + "】额度分配失败,请重试"); |
| | | } |
| | |
| | | throw new ServiceException("白名单用户不存在"); |
| | | } |
| | | assertPartnerVisible(whitelist); |
| | | BigDecimal newQuota = normalizeQuota(saveDTO.getBalance()); |
| | | Integer oldCompanyId = whitelist.getCompanyId(); |
| | | BigDecimal oldQuota = normalizeBalance(whitelist.getBalance()); |
| | | BigDecimal newQuota = normalizeQuota(saveDTO.getBalance(), oldQuota); |
| | | Integer newCompanyId = resolveRelationCompanyId(saveDTO); |
| | | if (newQuota.signum() > 0) { |
| | | requireAllocationCompany(newCompanyId); |
| | | } |
| | | Integer oldCompanyId = whitelist.getCompanyId(); |
| | | BigDecimal oldQuota = normalizeQuota(whitelist.getBalance()); |
| | | if (Objects.equals(oldCompanyId, newCompanyId)) { |
| | | // 同一公司按差额调整:调增扣差额,调减退差额 |
| | | BigDecimal diff = newQuota.subtract(oldQuota); |
| | |
| | | whitelist.setPartnerId(resolveWhitelistPartnerId(saveDTO)); |
| | | whitelist.setCompanyId(newCompanyId); |
| | | whitelist.setFleetId(saveDTO.getFleetId()); |
| | | whitelist.setBalance(newQuota.signum() > 0 ? newQuota : null); |
| | | whitelist.setBalance(newQuota.signum() == 0 ? null : newQuota); |
| | | whitelist.setStatus(saveDTO.getStatus()); |
| | | whitelist.setUpdateBy(getUsername()); |
| | | whitelist.setRemark(saveDTO.getRemark()); |
| | |
| | | return normalized; |
| | | } |
| | | |
| | | /** |
| | | * 规范化余额(读取库中余额专用):null 视为 0,允许欠款(负数),不限制上限。 |
| | | * 与入参校验的 normalizeQuota 区分,避免欠款账户被"初始分配额度不能小于0"误拦。 |
| | | */ |
| | | private BigDecimal normalizeBalance(BigDecimal balance) { |
| | | if (balance == null) { |
| | | return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | return balance.setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | /** |
| | | * 规范化编辑提交的初始分配额度,兼容欠款账户: |
| | | * null 视为不调整额度(保持当前余额);等于当前余额的负值放行(前端欠款原值回显);其余负数仍拒绝。 |
| | | */ |
| | | private BigDecimal normalizeQuota(BigDecimal quota, BigDecimal currentBalance) { |
| | | if (quota == null) { |
| | | return normalizeBalance(currentBalance); |
| | | } |
| | | if (quota.signum() < 0) { |
| | | BigDecimal current = normalizeBalance(currentBalance); |
| | | if (quota.setScale(2, RoundingMode.HALF_UP).compareTo(current) == 0) { |
| | | return current; |
| | | } |
| | | throw new ServiceException("初始分配额度不能小于0"); |
| | | } |
| | | return normalizeQuota(quota); |
| | | } |
| | | |
| | | /** 校验公司为启用且分配模式(可设置初始分配额度) */ |
| | | private void requireAllocationCompany(Integer companyId) { |
| | | if (companyId == null) { |
| | |
| | | card.setUpdateTime(LocalDateTime.now()); |
| | | card.setUpdateBy(SecurityUtils.getUserId()); |
| | | applyRelation(card, dto); |
| | | BigDecimal newQuota = normalizeQuota(dto.getBalance()); |
| | | Integer oldCompanyId = latest.getCompanyId(); |
| | | BigDecimal oldQuota = normalizeBalance(latest.getBalance()); |
| | | BigDecimal newQuota = normalizeQuota(dto.getBalance(), oldQuota); |
| | | if (newQuota.signum() > 0) { |
| | | requireAllocationCompany(card.getCompanyId()); |
| | | } |
| | | Integer oldCompanyId = latest.getCompanyId(); |
| | | BigDecimal oldQuota = normalizeQuota(latest.getBalance()); |
| | | if (Objects.equals(oldCompanyId, card.getCompanyId())) { |
| | | // 同一公司按差额调整:调增扣差额,调减退差额 |
| | | BigDecimal diff = newQuota.subtract(oldQuota); |
| | |
| | | newQuota, TCompanyAllocateRecord.SOURCE_EDIT, "编辑卡片调整所属公司")); |
| | | } |
| | | } |
| | | card.setBalance(newQuota.signum() > 0 ? newQuota : null); |
| | | card.setBalance(newQuota.signum() == 0 ? null : newQuota); |
| | | String encodedPassword = encodePasswordIfPresent(dto.getPassword()); |
| | | if (!updateCardRecord(card, encodedPassword, latest)) { |
| | | throw new ServiceException("修改卡片失败"); |
| | |
| | | return normalized; |
| | | } |
| | | |
| | | /** |
| | | * 规范化余额(读取库中余额专用):null 视为 0,允许欠款(负数),不限制上限。 |
| | | * 与入参校验的 normalizeQuota 区分,避免欠款账户被"初始分配额度不能小于0"误拦。 |
| | | */ |
| | | private BigDecimal normalizeBalance(BigDecimal balance) { |
| | | if (balance == null) { |
| | | return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | return balance.setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | /** |
| | | * 规范化编辑提交的初始分配额度,兼容欠款账户: |
| | | * null 视为不调整额度(保持当前余额);等于当前余额的负值放行(前端欠款原值回显);其余负数仍拒绝。 |
| | | */ |
| | | private BigDecimal normalizeQuota(BigDecimal quota, BigDecimal currentBalance) { |
| | | if (quota == null) { |
| | | return normalizeBalance(currentBalance); |
| | | } |
| | | if (quota.signum() < 0) { |
| | | BigDecimal current = normalizeBalance(currentBalance); |
| | | if (quota.setScale(2, RoundingMode.HALF_UP).compareTo(current) == 0) { |
| | | return current; |
| | | } |
| | | throw new ServiceException("初始分配额度不能小于0"); |
| | | } |
| | | return normalizeQuota(quota); |
| | | } |
| | | |
| | | /** 校验公司为启用且分配模式(可设置初始分配额度) */ |
| | | private void requireAllocationCompany(Integer companyId) { |
| | | if (companyId == null) { |
| | |
| | | return false; |
| | | } |
| | | BigDecimal newBalance = card.getBalance(); |
| | | boolean balanceChanged = newBalance != null && newBalance.signum() > 0 |
| | | && !quotaEquals(existing.getBalance(), newBalance); |
| | | // 余额与库中不一致即需落库(含欠款负数与清零场景:null 与 0 等价) |
| | | boolean balanceChanged = !quotaEquals(existing.getBalance(), newBalance); |
| | | boolean relationChanged = !Objects.equals(existing.getCompanyId(), card.getCompanyId()) |
| | | || !Objects.equals(existing.getFleetId(), card.getFleetId()) |
| | | || balanceChanged; |
| | |
| | | item.setUpdateTime(LocalDateTime.now()); |
| | | item.setUpdateBy(SecurityUtils.getUserId()); |
| | | applyRelation(item, dto, old); |
| | | BigDecimal newQuota = normalizeQuota(dto.getBalance()); |
| | | BigDecimal oldQuota = normalizeQuota(old.getBalance()); |
| | | BigDecimal oldQuota = normalizeBalance(old.getBalance()); |
| | | BigDecimal newQuota = normalizeQuota(dto.getBalance(), oldQuota); |
| | | if (newQuota.signum() > 0) { |
| | | requireAllocationCompany(item.getCompanyId()); |
| | | } |
| | | Integer oldCompanyId = old.getCompanyId(); |
| | | Integer newCompanyId = item.getCompanyId(); |
| | | item.setBalance(newQuota.signum() > 0 ? newQuota : null); |
| | | item.setBalance(newQuota.signum() == 0 ? null : newQuota); |
| | | List<Runnable> compensations = new ArrayList<>(); |
| | | // 分配记录延迟到 VIN 记录落库成功后写入:失败时公司余额已回补,不应留下流水 |
| | | List<Runnable> recordTasks = new ArrayList<>(); |
| | |
| | | if (!Integer.valueOf(ENABLED).equals(vehicle.getStatus())) { |
| | | throw new ServiceException("VIN码【" + vehicle.getVin() + "】已禁用,禁止分配额度"); |
| | | } |
| | | BigDecimal quota = normalizeQuota(vehicle.getBalance()); |
| | | BigDecimal quota = normalizeBalance(vehicle.getBalance()); |
| | | if (quota.add(amount).compareTo(MAX_INIT_QUOTA) > 0) { |
| | | throw new ServiceException("VIN码【" + vehicle.getVin() + "】分配后初始分配额度超过上限" |
| | | + MAX_INIT_QUOTA.toPlainString()); |
| | |
| | | Long operator = SecurityUtils.getUserId(); |
| | | for (TVinManagement vehicle : vehicles) { |
| | | BigDecimal amount = amounts.get(vehicle.getId()); |
| | | BigDecimal before = normalizeQuota(vehicle.getBalance()); |
| | | BigDecimal before = normalizeBalance(vehicle.getBalance()); |
| | | if (baseMapper.addBalance(vehicle.getId(), amount, operator) != 1) { |
| | | throw new ServiceException("VIN码【" + vehicle.getVin() + "】额度分配失败,请重试"); |
| | | } |
| | |
| | | throw new ServiceException("VIN码账户余额不足"); |
| | | } |
| | | TVinManagement latest = getById(vinId); |
| | | return latest == null ? BigDecimal.ZERO : normalizeQuota(latest.getBalance()); |
| | | return latest == null ? BigDecimal.ZERO : normalizeBalance(latest.getBalance()); |
| | | } |
| | | |
| | | @Override |
| | |
| | | if (hasOrderCode && companyAllocateRecordService.existsConsumeRecord(orderCode)) { |
| | | TVinManagement existed = getById(vinId); |
| | | log.info("[VIN结算] 订单已结算,幂等跳过:orderCode={}, vinId={}", orderCode, vinId); |
| | | return existed == null ? BigDecimal.ZERO : normalizeQuota(existed.getBalance()); |
| | | return existed == null ? BigDecimal.ZERO : normalizeBalance(existed.getBalance()); |
| | | } |
| | | boolean updated = lambdaUpdate() |
| | | .eq(TVinManagement::getId, vinId) |
| | |
| | | throw new ServiceException("VIN码账户不存在或已删除"); |
| | | } |
| | | TVinManagement latest = getById(vinId); |
| | | BigDecimal after = latest == null ? BigDecimal.ZERO : normalizeQuota(latest.getBalance()); |
| | | BigDecimal after = latest == null ? BigDecimal.ZERO : normalizeBalance(latest.getBalance()); |
| | | if (hasOrderCode) { |
| | | if (latest == null || latest.getCompanyId() == null) { |
| | | log.error("[VIN结算] 缺少公司信息,无法写入消费流水(幂等标记缺失):orderCode={}, vinId={}", |
| | |
| | | throw new ServiceException("VIN码账户不存在或已删除"); |
| | | } |
| | | TVinManagement latest = getById(vinId); |
| | | return latest == null ? BigDecimal.ZERO : normalizeQuota(latest.getBalance()); |
| | | return latest == null ? BigDecimal.ZERO : normalizeBalance(latest.getBalance()); |
| | | } |
| | | @Override @Transactional(rollbackFor = Exception.class) public String importVins(List<VinManagementExcelDTO> items) { if (items == null || items.isEmpty()) throw new ServiceException("导入数据不能为空"); Set<String> vins = new HashSet<>(); int row = 1; for (VinManagementExcelDTO item : items) { row++; if (item == null || StringUtils.isEmpty(item.getVin())) throw new ServiceException("第" + row + "行VIN码不能为空"); String vin = item.getVin().trim(); if (vin.length() > 64) throw new ServiceException("第" + row + "行VIN码不能超过64个字符"); if (!vins.add(vin) || baseMapper.countByVin(vin, null) > 0) throw new ServiceException("第" + row + "行VIN码已存在或重复"); parseStatus(item.getStatus(), row); } Long userId = SecurityUtils.getUserId(); LocalDateTime now = LocalDateTime.now(); Integer lockedCompanyId = partnerAppUserAccessService.getCurrentCompanyId(); TGroupCompany lockedCompany = lockedCompanyId == null ? null : loadCompany(lockedCompanyId); for (VinManagementExcelDTO item : items) { TVinManagement v = new TVinManagement(); v.setVin(item.getVin().trim()); v.setPlateNumber(trim(item.getPlateNumber())); v.setPhone(trim(item.getPhone())); if (lockedCompanyId != null) { v.setCompanyId(lockedCompanyId); v.setCompanyName(lockedCompany == null ? null : lockedCompany.getName()); } else { v.setCompanyName(trim(item.getCompanyName())); } v.setStatus(parseStatus(item.getStatus(), 0)); v.setDelFlag(0); v.setCreateBy(userId); v.setCreateTime(now); save(v); } return "成功导入" + items.size() + "条VIN码数据"; } |
| | | private void validate(VinManagementSaveDTO dto, boolean update) { if (dto == null || StringUtils.isEmpty(dto.getVin())) throw new ServiceException("VIN码不能为空"); if (update && dto.getId() == null) throw new ServiceException("VIN码ID不能为空"); String vin = dto.getVin().trim(); if (vin.length() > 64) throw new ServiceException("VIN码不能超过64个字符"); if (!validStatus(dto.getStatus() == null ? ENABLED : dto.getStatus())) throw new ServiceException("VIN码状态参数错误"); if (baseMapper.countByVin(vin, dto.getId()) > 0) throw new ServiceException("VIN码已存在"); } |
| | |
| | | return normalized; |
| | | } |
| | | |
| | | /** |
| | | * 规范化余额(读取库中余额专用):null 视为 0,允许欠款(负数),不限制上限。 |
| | | * 与入参校验的 normalizeQuota 区分,避免欠款账户被"初始分配额度不能小于0"误拦。 |
| | | */ |
| | | private BigDecimal normalizeBalance(BigDecimal balance) { |
| | | if (balance == null) { |
| | | return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | return balance.setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | /** |
| | | * 规范化编辑提交的初始分配额度,兼容欠款账户: |
| | | * null 视为不调整额度(保持当前余额);等于当前余额的负值放行(前端欠款原值回显);其余负数仍拒绝。 |
| | | */ |
| | | private BigDecimal normalizeQuota(BigDecimal quota, BigDecimal currentBalance) { |
| | | if (quota == null) { |
| | | return normalizeBalance(currentBalance); |
| | | } |
| | | if (quota.signum() < 0) { |
| | | BigDecimal current = normalizeBalance(currentBalance); |
| | | if (quota.setScale(2, RoundingMode.HALF_UP).compareTo(current) == 0) { |
| | | return current; |
| | | } |
| | | throw new ServiceException("初始分配额度不能小于0"); |
| | | } |
| | | return normalizeQuota(quota); |
| | | } |
| | | |
| | | /** 校验公司为启用且分配模式(可设置初始分配额度) */ |
| | | private void requireAllocationCompany(Integer companyId) { |
| | | if (companyId == null) { |
| | |
| | | assertEquals("分配明细中存在重复白名单用户", exception.getMessage()); |
| | | } |
| | | |
| | | @Test |
| | | void allocateQuotaAllowsWhitelistWithOverdueBalance() { |
| | | // 欠款账户(余额为负):分配额度按累加计算,分配前余额以负值写入分配流水 |
| | | when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(allocationCompany(9, "测试公司"))); |
| | | when(mapper.selectBatchIds(anyCollection())) |
| | | .thenReturn(Collections.singletonList(allocateWhitelist(1L, 9, "13540211236", "-30.00"))); |
| | | when(mapper.addBalance(anyLong(), any(BigDecimal.class), any())).thenReturn(1); |
| | | when(groupCompanyClient.deductBalance(9, new BigDecimal("100.00"))).thenReturn(R.ok(new BigDecimal("900.00"))); |
| | | |
| | | AppUserWhitelistAllocateVO overview = service.allocateQuota(allocateDto(9, allocateItem(1L, "100.00"))); |
| | | |
| | | assertEquals(new BigDecimal("900.00"), overview.getAvailableBalance()); |
| | | verify(mapper).addBalance(eq(1L), eq(new BigDecimal("100.00")), any()); |
| | | verify(companyAllocateRecordService).recordAllocate(TCompanyAllocateRecord.USER_TYPE_WHITELIST, |
| | | "13540211236", null, 9, TCompanyAllocateRecord.SOURCE_ALLOCATE, |
| | | new BigDecimal("-30.00"), new BigDecimal("100.00"), "额度分配(公司:测试公司)"); |
| | | } |
| | | |
| | | // ---------------- updateStatus ---------------- |
| | | |
| | | @Test |
| | |
| | | } |
| | | |
| | | @Test |
| | | void updateCardAllowsOverdueBalanceUnchanged() { |
| | | // 欠款卡片:编辑按原值回传余额(差额为 0),不触发公司余额收退 |
| | | ChargingCardSaveDTO dto = new ChargingCardSaveDTO(); |
| | | dto.setId(7L); |
| | | dto.setCardNo("CARD-7"); |
| | | dto.setBalance(new BigDecimal("-30.00")); |
| | | when(mapper.countByCardNo("CARD-7", 7L)).thenReturn(0); |
| | | TChargingCard existing = new TChargingCard(); |
| | | existing.setId(7L); |
| | | existing.setBalance(new BigDecimal("-30.00")); |
| | | when(mapper.selectById(7L)).thenReturn(existing); |
| | | when(mapper.updateById(any(TChargingCard.class))).thenReturn(1); |
| | | |
| | | assertTrue(service.updateCard(dto)); |
| | | |
| | | verify(groupCompanyClient, never()).deductBalance(any(Integer.class), any(BigDecimal.class)); |
| | | verify(groupCompanyClient, never()).refundBalance(any(Integer.class), any(BigDecimal.class)); |
| | | // 余额未变化时不产生额外余额更新 |
| | | verify(mapper, never()).update(isNull(), any(Wrapper.class)); |
| | | } |
| | | |
| | | @Test |
| | | void deleteCardsRemovesDistinctIds() { |
| | | TChargingCard first = new TChargingCard(); |
| | | first.setId(1L); |
| | |
| | | } |
| | | |
| | | @Test |
| | | void updateCardClearsOverdueBalanceWhenQuotaReset() { |
| | | // 欠款卡片:额度改为 0 时公司补足欠款,余额显式清零 |
| | | ChargingCardSaveDTO dto = quotaUpdateDto(7L, "CARD-7", 9, BigDecimal.ZERO); |
| | | when(mapper.countByCardNo("CARD-7", 7L)).thenReturn(0); |
| | | when(mapper.selectById(7L)).thenReturn(cardWithQuota(7L, 9, new BigDecimal("-30.00"))); |
| | | when(mapper.updateById(any(TChargingCard.class))).thenReturn(1); |
| | | when(mapper.update(isNull(), any(Wrapper.class))).thenReturn(1); |
| | | when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(allocationCompany(9, "测试公司"))); |
| | | when(groupCompanyClient.deductBalance(9, new BigDecimal("30.00"))) |
| | | .thenReturn(R.ok(new BigDecimal("970.00"))); |
| | | |
| | | assertTrue(service.updateCard(dto)); |
| | | |
| | | verify(groupCompanyClient).deductBalance(9, new BigDecimal("30.00")); |
| | | ArgumentCaptor<Wrapper> wrapperCaptor = ArgumentCaptor.forClass(Wrapper.class); |
| | | verify(mapper).update(isNull(), wrapperCaptor.capture()); |
| | | assertTrue(wrapperCaptor.getValue().getSqlSet().contains("balance=")); |
| | | } |
| | | |
| | | @Test |
| | | void updateCardSwitchesCompanyRefundsOldAndDeductsNew() { |
| | | ChargingCardSaveDTO dto = quotaUpdateDto(7L, "CARD-7", 11, new BigDecimal("60.00")); |
| | | when(mapper.countByCardNo("CARD-7", 7L)).thenReturn(0); |
| | |
| | | import com.ruoyi.account.api.dto.VinManagementExcelDTO; |
| | | import com.ruoyi.account.api.dto.VinManagementQueryDTO; |
| | | import com.ruoyi.account.api.dto.VinManagementSaveDTO; |
| | | import com.ruoyi.account.api.model.TCompanyAllocateRecord; |
| | | import com.ruoyi.account.api.model.TVinManagement; |
| | | import com.ruoyi.account.api.vo.VinManagementAllocateVO; |
| | | import com.ruoyi.account.mapper.TVinManagementMapper; |
| | | import com.ruoyi.account.service.PartnerAppUserAccessService; |
| | | import com.ruoyi.account.service.TCompanyAllocateRecordService; |
| | | import com.ruoyi.common.core.context.SecurityContextHolder; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.common.core.exception.ServiceException; |
| | |
| | | @Mock |
| | | private PartnerAppUserAccessService partnerAppUserAccessService; |
| | | |
| | | @Mock |
| | | private TCompanyAllocateRecordService companyAllocateRecordService; |
| | | |
| | | @Captor |
| | | private ArgumentCaptor<Page<TVinManagement>> pageCaptor; |
| | | |
| | |
| | | ReflectionTestUtils.setField(service, "groupCompanyClient", groupCompanyClient); |
| | | ReflectionTestUtils.setField(service, "redissonClient", redissonClient); |
| | | ReflectionTestUtils.setField(service, "partnerAppUserAccessService", partnerAppUserAccessService); |
| | | ReflectionTestUtils.setField(service, "companyAllocateRecordService", companyAllocateRecordService); |
| | | // 默认透传(模拟非公司账号),公司账号隔离场景由具体用例覆盖打桩 |
| | | lenient().when(partnerAppUserAccessService.resolveCompanyFilter(any())).thenAnswer(invocation -> invocation.getArgument(0)); |
| | | lenient().when(partnerAppUserAccessService.getCurrentCompanyId()).thenReturn(null); |
| | |
| | | verify(groupCompanyClient).refundBalance(9, new BigDecimal("10.00")); |
| | | } |
| | | |
| | | @Test |
| | | void allocateQuotaAllowsOverdueVinBalance() { |
| | | // 欠款账户(余额为负):分配额度按累加计算,分配前余额以负值写入分配流水 |
| | | when(groupCompanyClient.getCompanyById(9)).thenReturn(R.ok(allocationCompany(9, "测试公司"))); |
| | | doReturn(Collections.singletonList(allocateVehicle(1L, 9, "VIN-1", "-30.00"))) |
| | | .when(service).listByIds(anyCollection()); |
| | | when(groupCompanyClient.deductBalance(9, new BigDecimal("100.00"))).thenReturn(R.ok(new BigDecimal("900.00"))); |
| | | when(mapper.addBalance(1L, new BigDecimal("100.00"), CURRENT_USER)).thenReturn(1); |
| | | |
| | | VinManagementAllocateVO vo = service.allocateQuota(allocateDto(9, allocateItem(1L, "100.00"))); |
| | | |
| | | assertEquals(new BigDecimal("900.00"), vo.getAvailableBalance()); |
| | | verify(mapper).addBalance(1L, new BigDecimal("100.00"), CURRENT_USER); |
| | | verify(companyAllocateRecordService).recordAllocate(eq(TCompanyAllocateRecord.USER_TYPE_VIN), |
| | | eq("VIN-1"), isNull(), eq(9), eq(TCompanyAllocateRecord.SOURCE_ALLOCATE), |
| | | eq(new BigDecimal("-30.00")), eq(new BigDecimal("100.00")), anyString()); |
| | | } |
| | | |
| | | @Test |
| | | void updateVinKeepsOverdueBalanceUnchanged() { |
| | | // 欠款账户编辑:余额原值回传(差额为 0),不触发公司余额收退且负数原样落库 |
| | | TVinManagement existing = new TVinManagement(); |
| | | existing.setId(9L); |
| | | existing.setBalance(new BigDecimal("-30.00")); |
| | | when(mapper.selectById(9L)).thenReturn(existing); |
| | | when(mapper.countByVin(anyString(), eq(9L))).thenReturn(0); |
| | | when(mapper.update(isNull(), any(Wrapper.class))).thenReturn(1); |
| | | |
| | | VinManagementSaveDTO dto = new VinManagementSaveDTO(); |
| | | dto.setId(9L); |
| | | dto.setVin("LSVAM4187C2184842"); |
| | | dto.setStatus(1); |
| | | dto.setBalance(new BigDecimal("-30.00")); |
| | | |
| | | assertTrue(service.updateVin(dto)); |
| | | |
| | | ArgumentCaptor<Wrapper> wrapperCaptor = ArgumentCaptor.forClass(Wrapper.class); |
| | | verify(mapper).update(isNull(), wrapperCaptor.capture()); |
| | | assertTrue(wrapperCaptor.getValue().getSqlSet().contains("balance=")); |
| | | verify(groupCompanyClient, never()).deductBalance(any(Integer.class), any(BigDecimal.class)); |
| | | verify(groupCompanyClient, never()).refundBalance(any(Integer.class), any(BigDecimal.class)); |
| | | } |
| | | |
| | | @Test |
| | | void settleBalanceReturnsOverdueBalance() { |
| | | // 欠款账户结算:余额扣成负数后仍正常返回,不再因负数校验抛异常 |
| | | when(mapper.update(isNull(), any(Wrapper.class))).thenReturn(1); |
| | | TVinManagement latest = new TVinManagement(); |
| | | latest.setBalance(new BigDecimal("-12.50")); |
| | | when(mapper.selectById(9L)).thenReturn(latest); |
| | | |
| | | BigDecimal after = service.settleBalance(9L, new BigDecimal("10.00"), new BigDecimal("22.50"), |
| | | null, null, null, null); |
| | | |
| | | assertEquals(new BigDecimal("-12.50"), after); |
| | | } |
| | | |
| | | private TGroupCompany allocationCompany(Integer id, String name) { |
| | | TGroupCompany company = new TGroupCompany(); |
| | | company.setId(id); |