package com.ruoyi.order.service.impl;
|
|
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
|
import com.ruoyi.account.api.dto.ChargingCardAuthDTO;
|
import com.ruoyi.account.api.dto.ChargingCardDeductDTO;
|
import com.ruoyi.account.api.feignClient.ChargingCardClient;
|
import com.ruoyi.account.api.model.TChargingCard;
|
import com.ruoyi.account.api.vo.ChargingCardDeductVO;
|
import com.ruoyi.chargingPile.api.feignClient.ChargingGunClient;
|
import com.ruoyi.chargingPile.api.model.TChargingGun;
|
import com.ruoyi.chargingPile.api.vo.GetChargingGunByCode;
|
import com.ruoyi.common.core.domain.R;
|
import com.ruoyi.order.api.dto.DeviceStartChargingDTO;
|
import com.ruoyi.order.api.model.TCardAccountingStrategyDetailOrder;
|
import com.ruoyi.order.api.model.TCardChargingOrder;
|
import com.ruoyi.order.api.model.TCardChargingOrderAccountingStrategy;
|
import com.ruoyi.order.api.query.UploadRealTimeMonitoringDataQuery;
|
import com.ruoyi.order.api.vo.DeviceStartChargingVO;
|
import com.ruoyi.order.api.vo.TransactionRecordMessageVO;
|
import com.ruoyi.order.mapper.TCardChargingOrderMapper;
|
import com.ruoyi.order.service.TCardAccountingStrategyDetailOrderService;
|
import com.ruoyi.order.service.TCardChargingOrderAccountingStrategyService;
|
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.Test;
|
import org.mockito.ArgumentCaptor;
|
import org.redisson.api.RLock;
|
import org.redisson.api.RedissonClient;
|
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.transaction.TransactionStatus;
|
import org.springframework.transaction.support.TransactionCallback;
|
import org.springframework.transaction.support.TransactionTemplate;
|
|
import java.math.BigDecimal;
|
import java.util.Collection;
|
import java.util.Collections;
|
import java.util.concurrent.TimeUnit;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.anyLong;
|
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.when;
|
|
/**
|
* 刷卡订单金额字段单测(charge_amount / residual_amount / balance_pay_amount):
|
* 1) 建单写入可用金额基数与启动剩余金额(与返回设备的余额同源);
|
* 2) 监控按"可用金额基数 - 设备已充金额"刷新剩余金额;
|
* 3) 结算写入剩余金额(基数 - 本单金额)与余额支付金额,并同步回写内存订单对象。
|
*/
|
class TCardChargingOrderAmountFieldsTest {
|
|
/**
|
* 纯单测无 Spring 容器,MyBatis-Plus 的 lambda 列缓存需手工初始化,
|
* 否则服务实现里的 LambdaQueryWrapper/LambdaUpdateWrapper 解析列名时抛 "can not find lambda cache"
|
*/
|
@BeforeAll
|
static void initMybatisPlusTableInfo() {
|
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
|
TableInfoHelper.initTableInfo(assistant, TCardChargingOrder.class);
|
}
|
|
private RLock mockedLock(boolean tryLockResult) throws InterruptedException {
|
RLock lock = mock(RLock.class);
|
when(lock.tryLock(anyLong(), anyLong(), any(TimeUnit.class))).thenReturn(tryLockResult);
|
when(lock.isHeldByCurrentThread()).thenReturn(false);
|
return lock;
|
}
|
|
private RedissonClient mockedRedisson(RLock lock) {
|
RedissonClient redisson = mock(RedissonClient.class);
|
when(redisson.getLock(anyString())).thenReturn(lock);
|
return redisson;
|
}
|
|
@SuppressWarnings("unchecked")
|
private <T> R<T> okWithData(T data) {
|
R<T> result = mock(R.class);
|
when(result.getCode()).thenReturn(R.SUCCESS);
|
when(result.getData()).thenReturn(data);
|
return result;
|
}
|
|
// ===== 建单:charge_amount / residual_amount = 卡余额 =====
|
|
@Test
|
void writesChargeAndResidualAmountOnCardOrderCreation() throws InterruptedException {
|
TCardChargingOrderServiceImpl service = new TCardChargingOrderServiceImpl();
|
TCardChargingOrderMapper mapper = mock(TCardChargingOrderMapper.class);
|
ChargingCardClient cardClient = mock(ChargingCardClient.class);
|
ChargingGunClient gunClient = mock(ChargingGunClient.class);
|
ReflectionTestUtils.setField(service, "baseMapper", mapper);
|
ReflectionTestUtils.setField(service, "chargingCardClient", cardClient);
|
ReflectionTestUtils.setField(service, "chargingGunClient", gunClient);
|
ReflectionTestUtils.setField(service, "redissonClient", mockedRedisson(mockedLock(true)));
|
when(mapper.insert(any(TCardChargingOrder.class))).thenReturn(1);
|
|
TChargingGun gun = new TChargingGun();
|
gun.setId(9);
|
gun.setStatus(2);
|
R<TChargingGun> gunResult = okWithData(gun);
|
when(gunClient.getChargingGunByCode(any(GetChargingGunByCode.class))).thenReturn(gunResult);
|
|
TChargingCard card = new TChargingCard();
|
card.setId(5L);
|
card.setCardNo("C001");
|
card.setBalance(new BigDecimal("100.00"));
|
R<TChargingCard> verifyResult = okWithData(card);
|
when(cardClient.verifyCharging(any(ChargingCardAuthDTO.class))).thenReturn(verifyResult);
|
R<Boolean> lockResult = okWithData(true);
|
when(cardClient.lockForCharging(anyLong(), anyString())).thenReturn(lockResult);
|
|
DeviceStartChargingDTO dto = new DeviceStartChargingDTO();
|
dto.setChargingPileCode("P1");
|
dto.setChargingGunCode("G1");
|
dto.setAccount("C001");
|
DeviceStartChargingVO result = service.startCardCharging(dto);
|
|
assertEquals(1, result.getAuthentication());
|
assertEquals(new BigDecimal("100.00"), result.getBalance());
|
ArgumentCaptor<TCardChargingOrder> captor = ArgumentCaptor.forClass(TCardChargingOrder.class);
|
verify(mapper).insert(captor.capture());
|
assertEquals(new BigDecimal("100.00"), captor.getValue().getChargeAmount());
|
assertEquals(new BigDecimal("100.00"), captor.getValue().getResidualAmount());
|
}
|
|
// ===== 监控:residual_amount = charge_amount - paid_amount =====
|
|
@Test
|
void updatesResidualAmountFromPaidAmountWhileMonitoring() {
|
TCardChargingOrderServiceImpl service = new TCardChargingOrderServiceImpl();
|
TCardChargingOrderMapper mapper = mock(TCardChargingOrderMapper.class);
|
ReflectionTestUtils.setField(service, "baseMapper", mapper);
|
|
TCardChargingOrder charging = new TCardChargingOrder();
|
charging.setId(1L);
|
charging.setCode("SD1");
|
charging.setStatus(3);
|
charging.setChargeAmount(new BigDecimal("100.00"));
|
when(mapper.selectOne(any())).thenReturn(charging);
|
when(mapper.update(any(TCardChargingOrder.class), any())).thenReturn(1);
|
|
UploadRealTimeMonitoringDataQuery query = new UploadRealTimeMonitoringDataQuery();
|
query.setTransaction_serial_number("SD1");
|
query.setPaid_amount(new BigDecimal("12.34"));
|
service.cardChargeMonitoring(query);
|
|
ArgumentCaptor<TCardChargingOrder> captor = ArgumentCaptor.forClass(TCardChargingOrder.class);
|
verify(mapper).update(captor.capture(), any());
|
assertEquals(new BigDecimal("87.66"), captor.getValue().getResidualAmount());
|
}
|
|
// ===== 结算:residual_amount = charge_amount - orderAmount;balance_pay_amount = orderAmount =====
|
|
@Test
|
@SuppressWarnings("unchecked")
|
void writesResidualChargeAndBalancePayAmountOnSettlement() throws InterruptedException {
|
TCardChargingOrderServiceImpl service = new TCardChargingOrderServiceImpl();
|
TCardChargingOrderMapper mapper = mock(TCardChargingOrderMapper.class);
|
ChargingCardClient cardClient = mock(ChargingCardClient.class);
|
ChargingGunClient gunClient = mock(ChargingGunClient.class);
|
TCardAccountingStrategyDetailOrderService detailOrderService = mock(TCardAccountingStrategyDetailOrderService.class);
|
TCardChargingOrderAccountingStrategyService strategyService = mock(TCardChargingOrderAccountingStrategyService.class);
|
TransactionTemplate txTemplate = mock(TransactionTemplate.class);
|
ReflectionTestUtils.setField(service, "baseMapper", mapper);
|
ReflectionTestUtils.setField(service, "chargingCardClient", cardClient);
|
ReflectionTestUtils.setField(service, "chargingGunClient", gunClient);
|
ReflectionTestUtils.setField(service, "cardAccountingStrategyDetailOrderService", detailOrderService);
|
ReflectionTestUtils.setField(service, "cardChargingOrderAccountingStrategyService", strategyService);
|
ReflectionTestUtils.setField(service, "transactionTemplate", txTemplate);
|
ReflectionTestUtils.setField(service, "redissonClient", mockedRedisson(mockedLock(true)));
|
|
TCardChargingOrder charging = new TCardChargingOrder();
|
charging.setId(1L);
|
charging.setCode("SD1");
|
charging.setStatus(4);
|
charging.setCardId(5L);
|
charging.setChargingGunId(9);
|
charging.setChargeAmount(new BigDecimal("100.00"));
|
when(mapper.selectOne(any())).thenReturn(charging);
|
when(mapper.updateById(any(TCardChargingOrder.class))).thenReturn(1);
|
|
TChargingGun gun = new TChargingGun();
|
gun.setId(9);
|
gun.setStatus(4);
|
R<TChargingGun> gunResult = okWithData(gun);
|
when(gunClient.getChargingGunById(any())).thenReturn(gunResult);
|
R<String> gunUpdateResult = mock(R.class);
|
when(gunUpdateResult.getCode()).thenReturn(R.SUCCESS);
|
when(gunClient.updateChargingGunById(any(TChargingGun.class))).thenReturn(gunUpdateResult);
|
R<Boolean> unlockResult = okWithData(true);
|
when(cardClient.unlockForCharging(anyLong(), anyString())).thenReturn(unlockResult);
|
ChargingCardDeductVO deductVO = new ChargingCardDeductVO();
|
deductVO.setDuplicated(false);
|
R<ChargingCardDeductVO> deductResult = okWithData(deductVO);
|
when(cardClient.deductForCharging(any(ChargingCardDeductDTO.class))).thenReturn(deductResult);
|
|
// 计费策略快照:尖段电费 1.0 元/度、服务费 0.5 元/度
|
TCardAccountingStrategyDetailOrder snapshot = new TCardAccountingStrategyDetailOrder();
|
snapshot.setType(1);
|
snapshot.setElectrovalence(new BigDecimal("1.0000"));
|
snapshot.setServiceCharge(new BigDecimal("0.5000"));
|
snapshot.setCostServiceCharge(new BigDecimal("0.5000"));
|
LambdaQueryChainWrapper<TCardAccountingStrategyDetailOrder> queryWrapper = mock(LambdaQueryChainWrapper.class);
|
when(detailOrderService.lambdaQuery()).thenReturn(queryWrapper);
|
when(queryWrapper.eq(any(), any())).thenReturn(queryWrapper);
|
when(queryWrapper.list()).thenReturn(Collections.singletonList(snapshot));
|
|
LambdaUpdateChainWrapper<TCardChargingOrderAccountingStrategy> updateWrapper = mock(LambdaUpdateChainWrapper.class);
|
when(strategyService.lambdaUpdate()).thenReturn(updateWrapper);
|
when(updateWrapper.eq(any(), any())).thenReturn(updateWrapper);
|
when(updateWrapper.remove()).thenReturn(true);
|
when(strategyService.saveBatch(any(Collection.class))).thenReturn(true);
|
|
when(txTemplate.execute(any())).thenAnswer(invocation -> {
|
TransactionCallback<?> callback = invocation.getArgument(0);
|
return callback.doInTransaction(mock(TransactionStatus.class));
|
});
|
|
TransactionRecordMessageVO vo = new TransactionRecordMessageVO();
|
vo.setTransaction_serial_number("SD1");
|
vo.setSpike_charge1(new BigDecimal("10"));
|
R result = service.cardEndChargeBillingCharge(vo);
|
|
assertEquals(R.SUCCESS, result.getCode());
|
ArgumentCaptor<TCardChargingOrder> captor = ArgumentCaptor.forClass(TCardChargingOrder.class);
|
verify(mapper).updateById(captor.capture());
|
TCardChargingOrder update = captor.getValue();
|
// 尖段 10 度 * (1.0 + 0.5) = 15.00 元
|
assertEquals(new BigDecimal("15.00"), update.getOrderAmount());
|
assertEquals(new BigDecimal("15.00"), update.getBalancePayAmount());
|
assertEquals(new BigDecimal("100.00"), update.getChargeAmount());
|
assertEquals(new BigDecimal("85.00"), update.getResidualAmount());
|
// 内存订单对象同步回写,保证结算后监管推送字段完整
|
assertEquals(new BigDecimal("85.00"), charging.getResidualAmount());
|
assertEquals(new BigDecimal("15.00"), charging.getBalancePayAmount());
|
// 余额扣款金额与余额支付金额同源
|
ArgumentCaptor<ChargingCardDeductDTO> deductCaptor = ArgumentCaptor.forClass(ChargingCardDeductDTO.class);
|
verify(cardClient).deductForCharging(deductCaptor.capture());
|
assertEquals(new BigDecimal("15.00"), deductCaptor.getValue().getAmount());
|
}
|
}
|