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.ChargingCardDeductDTO;
|
import com.ruoyi.account.api.feignClient.ChargingCardClient;
|
import com.ruoyi.account.api.vo.ChargingCardDeductVO;
|
import com.ruoyi.chargingPile.api.feignClient.ChargingGunClient;
|
import com.ruoyi.chargingPile.api.model.TChargingGun;
|
import com.ruoyi.common.core.domain.R;
|
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.model.TVinAccountingStrategyDetailOrder;
|
import com.ruoyi.order.api.model.TVinChargingOrder;
|
import com.ruoyi.order.api.model.TVinChargingOrderAccountingStrategy;
|
import com.ruoyi.order.api.vo.TransactionRecordMessageVO;
|
import com.ruoyi.order.mapper.TCardChargingOrderMapper;
|
import com.ruoyi.order.mapper.TVinChargingOrderMapper;
|
import com.ruoyi.order.service.TCardAccountingStrategyDetailOrderService;
|
import com.ruoyi.order.service.TCardChargingOrderAccountingStrategyService;
|
import com.ruoyi.order.service.TVinAccountingStrategyDetailOrderService;
|
import com.ruoyi.order.service.TVinChargingOrderAccountingStrategyService;
|
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.time.LocalDateTime;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collection;
|
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.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;
|
|
/**
|
* VIN/刷卡充电结算明细开始/结束时间口径对齐测试(与 CD 充电 TaskUtil.endOrder 一致):
|
* 1) 循环内:start_time/end_time 截取快照时段位 HH:mm;
|
* 2) 循环外:details 按 type 升序排序,首段 start_time 用订单实际开始时间 HH:mm 覆盖,
|
* 末段 end_time 用订单实际结束时间 HH:mm 覆盖;
|
* 3) 订单实际时间缺失时保持快照时段位兜底。
|
*
|
* 注:本测试类中所有 R 类型的 mock 都在测试体内显式分步 stubbing,
|
* 避免在 .when().thenReturn() 调用链中再嵌套调用其他会触发 Mockito 状态切换的辅助方法。
|
*/
|
class CardVinSettlementTimeAlignmentTest {
|
|
@BeforeAll
|
static void initMybatisPlusTableInfo() {
|
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
|
TableInfoHelper.initTableInfo(assistant, TCardChargingOrder.class);
|
TableInfoHelper.initTableInfo(assistant, TVinChargingOrder.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;
|
}
|
|
/** 构造三段(尖 00-08 / 平 08-16 / 谷 16-23:59)的快照 */
|
private List<TVinAccountingStrategyDetailOrder> vinThreePeriodSnapshots() {
|
TVinAccountingStrategyDetailOrder spike = new TVinAccountingStrategyDetailOrder();
|
spike.setId(1);
|
spike.setType(1);
|
spike.setStartTime("00:00");
|
spike.setEndTime("08:00");
|
spike.setElectrovalence(new BigDecimal("1.2000"));
|
spike.setServiceCharge(new BigDecimal("0.6000"));
|
spike.setCostServiceCharge(new BigDecimal("0.6000"));
|
|
TVinAccountingStrategyDetailOrder flat = new TVinAccountingStrategyDetailOrder();
|
flat.setId(3);
|
flat.setType(3);
|
flat.setStartTime("08:00");
|
flat.setEndTime("16:00");
|
flat.setElectrovalence(new BigDecimal("0.9000"));
|
flat.setServiceCharge(new BigDecimal("0.5000"));
|
flat.setCostServiceCharge(new BigDecimal("0.5000"));
|
|
TVinAccountingStrategyDetailOrder valley = new TVinAccountingStrategyDetailOrder();
|
valley.setId(4);
|
valley.setType(4);
|
valley.setStartTime("16:00");
|
valley.setEndTime("23:59");
|
valley.setElectrovalence(new BigDecimal("0.5000"));
|
valley.setServiceCharge(new BigDecimal("0.4000"));
|
valley.setCostServiceCharge(new BigDecimal("0.4000"));
|
|
return Arrays.asList(spike, flat, valley);
|
}
|
|
private List<TCardAccountingStrategyDetailOrder> cardThreePeriodSnapshots() {
|
TCardAccountingStrategyDetailOrder spike = new TCardAccountingStrategyDetailOrder();
|
spike.setId(11);
|
spike.setType(1);
|
spike.setStartTime("00:00");
|
spike.setEndTime("08:00");
|
spike.setElectrovalence(new BigDecimal("1.2000"));
|
spike.setServiceCharge(new BigDecimal("0.6000"));
|
spike.setCostServiceCharge(new BigDecimal("0.6000"));
|
|
TCardAccountingStrategyDetailOrder flat = new TCardAccountingStrategyDetailOrder();
|
flat.setId(13);
|
flat.setType(3);
|
flat.setStartTime("08:00");
|
flat.setEndTime("16:00");
|
flat.setElectrovalence(new BigDecimal("0.9000"));
|
flat.setServiceCharge(new BigDecimal("0.5000"));
|
flat.setCostServiceCharge(new BigDecimal("0.5000"));
|
|
TCardAccountingStrategyDetailOrder valley = new TCardAccountingStrategyDetailOrder();
|
valley.setId(14);
|
valley.setType(4);
|
valley.setStartTime("16:00");
|
valley.setEndTime("23:59");
|
valley.setElectrovalence(new BigDecimal("0.5000"));
|
valley.setServiceCharge(new BigDecimal("0.4000"));
|
valley.setCostServiceCharge(new BigDecimal("0.4000"));
|
|
return Arrays.asList(spike, flat, valley);
|
}
|
|
private void stubVinStrategyQuerySave(TVinAccountingStrategyDetailOrderService detailOrderService,
|
TVinChargingOrderAccountingStrategyService strategyService,
|
List<TVinAccountingStrategyDetailOrder> snapshots) {
|
LambdaQueryChainWrapper<TVinAccountingStrategyDetailOrder> queryWrapper = mock(LambdaQueryChainWrapper.class);
|
when(detailOrderService.lambdaQuery()).thenReturn(queryWrapper);
|
when(queryWrapper.eq(any(), any())).thenReturn(queryWrapper);
|
when(queryWrapper.list()).thenReturn(snapshots);
|
|
LambdaUpdateChainWrapper<TVinChargingOrderAccountingStrategy> 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);
|
}
|
|
private void stubCardStrategyQuerySave(TCardAccountingStrategyDetailOrderService detailOrderService,
|
TCardChargingOrderAccountingStrategyService strategyService,
|
List<TCardAccountingStrategyDetailOrder> snapshots) {
|
LambdaQueryChainWrapper<TCardAccountingStrategyDetailOrder> queryWrapper = mock(LambdaQueryChainWrapper.class);
|
when(detailOrderService.lambdaQuery()).thenReturn(queryWrapper);
|
when(queryWrapper.eq(any(), any())).thenReturn(queryWrapper);
|
when(queryWrapper.list()).thenReturn(snapshots);
|
|
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);
|
}
|
|
private void stubTxExecute(TransactionTemplate txTemplate) {
|
when(txTemplate.execute(any())).thenAnswer(invocation -> {
|
TransactionCallback<?> callback = invocation.getArgument(0);
|
return callback.doInTransaction(mock(TransactionStatus.class));
|
});
|
}
|
|
/** mock 复位枪桩需要的两个 R 返回:getChargingGunById / updateChargingGunById */
|
private void stubGunAndUpdate(ChargingGunClient gunClient, int gunId) {
|
TChargingGun gun = new TChargingGun();
|
gun.setId(gunId);
|
gun.setStatus(4);
|
R<TChargingGun> gunResult = mock(R.class);
|
when(gunResult.getCode()).thenReturn(R.SUCCESS);
|
when(gunResult.getData()).thenReturn(gun);
|
when(gunClient.getChargingGunById(any())).thenReturn(gunResult);
|
|
R<String> updateResult = mock(R.class);
|
when(updateResult.getCode()).thenReturn(R.SUCCESS);
|
when(gunClient.updateChargingGunById(any(TChargingGun.class))).thenReturn(updateResult);
|
}
|
|
/** mock 刷卡解锁/扣款 R 返回 */
|
private void stubCardDeductAndUnlock(ChargingCardClient cardClient) {
|
R<Boolean> unlockResult = mock(R.class);
|
when(unlockResult.getCode()).thenReturn(R.SUCCESS);
|
when(unlockResult.getData()).thenReturn(true);
|
when(cardClient.unlockForCharging(anyLong(), anyString())).thenReturn(unlockResult);
|
|
ChargingCardDeductVO deductVO = new ChargingCardDeductVO();
|
deductVO.setDuplicated(false);
|
R<ChargingCardDeductVO> deductResult = mock(R.class);
|
when(deductResult.getCode()).thenReturn(R.SUCCESS);
|
when(deductResult.getData()).thenReturn(deductVO);
|
when(cardClient.deductForCharging(any(ChargingCardDeductDTO.class))).thenReturn(deductResult);
|
}
|
|
// ===== VD:多时段首末段覆盖 =====
|
// 订单实际开始 07:30 / 实际结束 21:45;快照三段(尖/平/谷)。
|
// 期望:首段(type=1)start_time=07:30;末段(type=4)end_time=21:45;中段(type=3)保持快照时段位。
|
|
@Test
|
@SuppressWarnings("unchecked")
|
void vinThreePeriodOverlaysStartAndEndWithOrderActualTime() throws InterruptedException {
|
TVinChargingOrderServiceImpl service = new TVinChargingOrderServiceImpl();
|
TVinChargingOrderMapper mapper = mock(TVinChargingOrderMapper.class);
|
ChargingGunClient gunClient = mock(ChargingGunClient.class);
|
TVinAccountingStrategyDetailOrderService detailOrderService = mock(TVinAccountingStrategyDetailOrderService.class);
|
TVinChargingOrderAccountingStrategyService strategyService = mock(TVinChargingOrderAccountingStrategyService.class);
|
TransactionTemplate txTemplate = mock(TransactionTemplate.class);
|
ReflectionTestUtils.setField(service, "baseMapper", mapper);
|
ReflectionTestUtils.setField(service, "chargingGunClient", gunClient);
|
ReflectionTestUtils.setField(service, "vinAccountingStrategyDetailOrderService", detailOrderService);
|
ReflectionTestUtils.setField(service, "vinChargingOrderAccountingStrategyService", strategyService);
|
ReflectionTestUtils.setField(service, "transactionTemplate", txTemplate);
|
ReflectionTestUtils.setField(service, "redissonClient", mockedRedisson(mockedLock(true)));
|
|
TVinChargingOrder charging = new TVinChargingOrder();
|
charging.setId(2L);
|
charging.setCode("VD1");
|
charging.setStatus(4);
|
charging.setChargingGunId(7);
|
charging.setStartTime(LocalDateTime.of(2026, 9, 14, 7, 30, 0));
|
charging.setEndTime(LocalDateTime.of(2026, 9, 14, 21, 45, 0));
|
when(mapper.selectOne(any())).thenReturn(charging);
|
when(mapper.updateById(any())).thenReturn(1);
|
|
stubGunAndUpdate(gunClient, 7);
|
stubVinStrategyQuerySave(detailOrderService, strategyService, vinThreePeriodSnapshots());
|
stubTxExecute(txTemplate);
|
|
// VO:尖 3 / 平 5 / 谷 8 度,触发三段写入
|
TransactionRecordMessageVO vo = new TransactionRecordMessageVO();
|
vo.setTransaction_serial_number("VD1");
|
vo.setSpike_charge1(new BigDecimal("3"));
|
vo.setFlat_charge1(new BigDecimal("5"));
|
vo.setValley_charge1(new BigDecimal("8"));
|
|
R result = service.vinEndChargeBillingCharge(vo);
|
assertEquals(R.SUCCESS, result.getCode());
|
|
ArgumentCaptor<Collection<TVinChargingOrderAccountingStrategy>> captor = ArgumentCaptor.forClass(Collection.class);
|
verify(strategyService).saveBatch(captor.capture());
|
List<TVinChargingOrderAccountingStrategy> saved = new ArrayList<>(captor.getValue());
|
assertEquals(3, saved.size());
|
assertEquals(Integer.valueOf(1), saved.get(0).getType());
|
assertEquals(Integer.valueOf(3), saved.get(1).getType());
|
assertEquals(Integer.valueOf(4), saved.get(2).getType());
|
|
// 首段:start_time = 07:30(订单实际开始时间 HH:mm);end_time 保持快照 "08:00"
|
assertEquals("07:30", saved.get(0).getStartTime());
|
assertEquals("08:00", saved.get(0).getEndTime());
|
|
// 中段:保持快照时段位
|
assertEquals("08:00", saved.get(1).getStartTime());
|
assertEquals("16:00", saved.get(1).getEndTime());
|
|
// 末段:end_time = 21:45(订单实际结束时间 HH:mm);start_time 保持快照 "16:00"
|
assertEquals("16:00", saved.get(2).getStartTime());
|
assertEquals("21:45", saved.get(2).getEndTime());
|
|
saved.forEach(s -> {
|
assertNotNull(s.getCreateTime());
|
assertNotNull(s.getAccountingStrategyDetailId());
|
});
|
}
|
|
// ===== VD:订单 startTime/endTime 缺失时首末段保持快照兜底 =====
|
|
@Test
|
@SuppressWarnings("unchecked")
|
void vinThreePeriodKeepsSnapshotWhenOrderTimeMissing() throws InterruptedException {
|
TVinChargingOrderServiceImpl service = new TVinChargingOrderServiceImpl();
|
TVinChargingOrderMapper mapper = mock(TVinChargingOrderMapper.class);
|
ChargingGunClient gunClient = mock(ChargingGunClient.class);
|
TVinAccountingStrategyDetailOrderService detailOrderService = mock(TVinAccountingStrategyDetailOrderService.class);
|
TVinChargingOrderAccountingStrategyService strategyService = mock(TVinChargingOrderAccountingStrategyService.class);
|
TransactionTemplate txTemplate = mock(TransactionTemplate.class);
|
ReflectionTestUtils.setField(service, "baseMapper", mapper);
|
ReflectionTestUtils.setField(service, "chargingGunClient", gunClient);
|
ReflectionTestUtils.setField(service, "vinAccountingStrategyDetailOrderService", detailOrderService);
|
ReflectionTestUtils.setField(service, "vinChargingOrderAccountingStrategyService", strategyService);
|
ReflectionTestUtils.setField(service, "transactionTemplate", txTemplate);
|
ReflectionTestUtils.setField(service, "redissonClient", mockedRedisson(mockedLock(true)));
|
|
TVinChargingOrder charging = new TVinChargingOrder();
|
charging.setId(2L);
|
charging.setCode("VD1");
|
charging.setStatus(4);
|
charging.setChargingGunId(7);
|
// startTime/endTime 均为 null
|
when(mapper.selectOne(any())).thenReturn(charging);
|
when(mapper.updateById(any())).thenReturn(1);
|
|
stubGunAndUpdate(gunClient, 7);
|
stubVinStrategyQuerySave(detailOrderService, strategyService, vinThreePeriodSnapshots());
|
stubTxExecute(txTemplate);
|
|
TransactionRecordMessageVO vo = new TransactionRecordMessageVO();
|
vo.setTransaction_serial_number("VD1");
|
vo.setSpike_charge1(new BigDecimal("3"));
|
vo.setFlat_charge1(new BigDecimal("5"));
|
vo.setValley_charge1(new BigDecimal("8"));
|
|
R result = service.vinEndChargeBillingCharge(vo);
|
assertEquals(R.SUCCESS, result.getCode());
|
|
ArgumentCaptor<Collection<TVinChargingOrderAccountingStrategy>> captor = ArgumentCaptor.forClass(Collection.class);
|
verify(strategyService).saveBatch(captor.capture());
|
List<TVinChargingOrderAccountingStrategy> saved = new ArrayList<>(captor.getValue());
|
assertEquals(3, saved.size());
|
// 首末段均未覆盖,保持快照时段位
|
assertEquals("00:00", saved.get(0).getStartTime());
|
assertEquals("08:00", saved.get(0).getEndTime());
|
assertEquals("16:00", saved.get(2).getStartTime());
|
assertEquals("23:59", saved.get(2).getEndTime());
|
}
|
|
// ===== SD:多时段首末段覆盖 =====
|
|
@Test
|
@SuppressWarnings("unchecked")
|
void cardThreePeriodOverlaysStartAndEndWithOrderActualTime() 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"));
|
charging.setStartTime(LocalDateTime.of(2026, 9, 14, 7, 30, 0));
|
charging.setEndTime(LocalDateTime.of(2026, 9, 14, 21, 45, 0));
|
when(mapper.selectOne(any())).thenReturn(charging);
|
when(mapper.updateById(any())).thenReturn(1);
|
|
stubGunAndUpdate(gunClient, 9);
|
stubCardDeductAndUnlock(cardClient);
|
stubCardStrategyQuerySave(detailOrderService, strategyService, cardThreePeriodSnapshots());
|
stubTxExecute(txTemplate);
|
|
TransactionRecordMessageVO vo = new TransactionRecordMessageVO();
|
vo.setTransaction_serial_number("SD1");
|
vo.setSpike_charge1(new BigDecimal("3"));
|
vo.setFlat_charge1(new BigDecimal("5"));
|
vo.setValley_charge1(new BigDecimal("8"));
|
|
R result = service.cardEndChargeBillingCharge(vo);
|
assertEquals(R.SUCCESS, result.getCode());
|
|
ArgumentCaptor<Collection<TCardChargingOrderAccountingStrategy>> captor = ArgumentCaptor.forClass(Collection.class);
|
verify(strategyService).saveBatch(captor.capture());
|
List<TCardChargingOrderAccountingStrategy> saved = new ArrayList<>(captor.getValue());
|
assertEquals(3, saved.size());
|
assertEquals(Integer.valueOf(1), saved.get(0).getType());
|
assertEquals(Integer.valueOf(3), saved.get(1).getType());
|
assertEquals(Integer.valueOf(4), saved.get(2).getType());
|
|
// 首段:start_time = 07:30;end_time 保持快照 "08:00"
|
assertEquals("07:30", saved.get(0).getStartTime());
|
assertEquals("08:00", saved.get(0).getEndTime());
|
|
// 中段:保持快照时段位
|
assertEquals("08:00", saved.get(1).getStartTime());
|
assertEquals("16:00", saved.get(1).getEndTime());
|
|
// 末段:end_time = 21:45;start_time 保持快照 "16:00"
|
assertEquals("16:00", saved.get(2).getStartTime());
|
assertEquals("21:45", saved.get(2).getEndTime());
|
|
saved.forEach(s -> {
|
assertNotNull(s.getCreateTime());
|
assertNotNull(s.getAccountingStrategyDetailId());
|
});
|
}
|
|
// ===== SD:订单 startTime/endTime 缺失时首末段保持快照兜底 =====
|
|
@Test
|
@SuppressWarnings("unchecked")
|
void cardThreePeriodKeepsSnapshotWhenOrderTimeMissing() 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())).thenReturn(1);
|
|
stubGunAndUpdate(gunClient, 9);
|
stubCardDeductAndUnlock(cardClient);
|
stubCardStrategyQuerySave(detailOrderService, strategyService, cardThreePeriodSnapshots());
|
stubTxExecute(txTemplate);
|
|
TransactionRecordMessageVO vo = new TransactionRecordMessageVO();
|
vo.setTransaction_serial_number("SD1");
|
vo.setSpike_charge1(new BigDecimal("3"));
|
vo.setFlat_charge1(new BigDecimal("5"));
|
vo.setValley_charge1(new BigDecimal("8"));
|
|
R result = service.cardEndChargeBillingCharge(vo);
|
assertEquals(R.SUCCESS, result.getCode());
|
|
ArgumentCaptor<Collection<TCardChargingOrderAccountingStrategy>> captor = ArgumentCaptor.forClass(Collection.class);
|
verify(strategyService).saveBatch(captor.capture());
|
List<TCardChargingOrderAccountingStrategy> saved = new ArrayList<>(captor.getValue());
|
assertEquals(3, saved.size());
|
assertEquals("00:00", saved.get(0).getStartTime());
|
assertEquals("08:00", saved.get(0).getEndTime());
|
assertEquals("16:00", saved.get(2).getStartTime());
|
assertEquals("23:59", saved.get(2).getEndTime());
|
}
|
|
// ===== SD:单时段且订单时间缺失,保持快照时段位(兼容 V0.2 现有行为) =====
|
|
@Test
|
@SuppressWarnings("unchecked")
|
void cardSinglePeriodKeepsSnapshotWhenOrderTimeMissing() 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())).thenReturn(1);
|
|
stubGunAndUpdate(gunClient, 9);
|
stubCardDeductAndUnlock(cardClient);
|
|
// 单段快照(type=1,00:00-06:00)
|
TCardAccountingStrategyDetailOrder spike = new TCardAccountingStrategyDetailOrder();
|
spike.setId(11);
|
spike.setType(1);
|
spike.setStartTime("00:00");
|
spike.setEndTime("06:00");
|
spike.setElectrovalence(new BigDecimal("1.0000"));
|
spike.setServiceCharge(new BigDecimal("0.5000"));
|
spike.setCostServiceCharge(new BigDecimal("0.5000"));
|
stubCardStrategyQuerySave(detailOrderService, strategyService, Collections.singletonList(spike));
|
stubTxExecute(txTemplate);
|
|
TransactionRecordMessageVO vo = new TransactionRecordMessageVO();
|
vo.setTransaction_serial_number("SD1");
|
vo.setSpike_charge1(new BigDecimal("5"));
|
|
R result = service.cardEndChargeBillingCharge(vo);
|
assertEquals(R.SUCCESS, result.getCode());
|
|
ArgumentCaptor<Collection<TCardChargingOrderAccountingStrategy>> captor = ArgumentCaptor.forClass(Collection.class);
|
verify(strategyService).saveBatch(captor.capture());
|
List<TCardChargingOrderAccountingStrategy> saved = new ArrayList<>(captor.getValue());
|
assertEquals(1, saved.size());
|
// 单段:start/end 都是快照时段位(订单时间为 null 时不覆盖)
|
assertEquals("00:00", saved.get(0).getStartTime());
|
assertEquals("06:00", saved.get(0).getEndTime());
|
}
|
}
|