fix:设备查询菜品频繁问题

fix:退看流水对账问题
This commit is contained in:
dzc committed 2026-09-02 12:52:32 +08:00
1 parent a3c89be742
commit 2a9457e7a7
9 files changed
+222 -70

No files matched your search

@@ -409,7 +409,7 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
}
/**
* 按用户部门汇总钱包操作、硬件消费、已完成退款和净消费数据。
* 按用户部门汇总钱包操作、硬件消费、退款流水和净消费数据。
*
* @param dto 当前用户权限范围内的部门、商户、时间和钱包类型筛选条件
* @return 部门交易统计;筛选范围内没有部门时返回空列表
@@ -432,28 +432,25 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
buildWalletStatisticsDto(dto, WalletBizEnum.UPDATE.getCode(), WalletTransactionSumAndCountDto.TYPE_INCOME));
List<WalletTransactionSumAndCountVo> walletOnceReduceList = queryWalletStatistics(
buildWalletStatisticsDto(dto, WalletBizEnum.UPDATE.getCode(), WalletTransactionSumAndCountDto.TYPE_EXPENSE));
List<WalletTransactionSumAndCountVo> walletRefundList = queryWalletStatistics(
buildWalletStatisticsDto(dto, WalletBizEnum.REFUND.getCode(), null));
List<HardOrderDepartmentStatisticsDetail> payDetails =
hardOrderMapper.listPayStatisticsDetails(buildOrderStatisticsDto(dto, false));
List<HardOrderDepartmentStatisticsDetail> refundDetails =
hardOrderMapper.listRefundStatisticsDetails(buildOrderStatisticsDto(dto, true));
hardOrderMapper.listPayStatisticsDetails(buildPayStatisticsDto(dto));
Map<String, List<HardOrderDepartmentStatisticsDetail>> detailsByBizType = new LinkedHashMap<>();
detailsByBizType.put(WalletBizEnum.PAYMENT.getCode(), payDetails);
detailsByBizType.put(WalletBizEnum.REFUND.getCode(), refundDetails);
Map<String, Map<String, WalletTransactionDepartmentSnapshotVo>> transactionSnapshotMaps =
queryTransactionDepartmentSnapshotMaps(detailsByBizType);
List<HardOrderSumAndCountVo> payList = aggregateByTransactionDepartment(payDetails,
transactionSnapshotMaps.getOrDefault(WalletBizEnum.PAYMENT.getCode(), Map.of()), dto, false);
List<HardOrderSumAndCountVo> refundList = aggregateByTransactionDepartment(refundDetails,
transactionSnapshotMaps.getOrDefault(WalletBizEnum.REFUND.getCode(), Map.of()), dto, true);
List<HardOrderSumAndCountVo> payList = aggregatePaymentsByTransactionDepartment(payDetails,
transactionSnapshotMaps.getOrDefault(WalletBizEnum.PAYMENT.getCode(), Map.of()), dto);
Map<Long, WalletTransactionSumAndCountVo> walletBatchMap = indexWalletStatistics(walletBatchList);
Map<Long, WalletTransactionSumAndCountVo> walletReduceMap = indexWalletStatistics(walletReduceList);
Map<Long, WalletTransactionSumAndCountVo> walletReturnCanteenMap = indexWalletStatistics(walletReturnCanteenList);
Map<Long, WalletTransactionSumAndCountVo> walletOnceBatchMap = indexWalletStatistics(walletOnceBatchList);
Map<Long, WalletTransactionSumAndCountVo> walletOnceReduceMap = indexWalletStatistics(walletOnceReduceList);
Map<Long, WalletTransactionSumAndCountVo> walletRefundMap = indexWalletStatistics(walletRefundList);
Map<Long, HardOrderSumAndCountVo> payMap = indexOrderStatistics(payList);
Map<Long, HardOrderSumAndCountVo> refundMap = indexOrderStatistics(refundList);
List<HardDeptTotalVo> resultList = new ArrayList<>(deptList.size());
for (SysDept dept : deptList) {
@@ -488,7 +485,7 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
ObjectUtil.defaultIfNull(walletOnceReduce.getSum(), 0L))));
statistics.setWalletOnceReduceCount(ObjectUtil.defaultIfNull(walletOnceReduce.getCount(), 0L));
}
applyOrderStatistics(statistics, payMap.get(dept.getDeptId()), refundMap.get(dept.getDeptId()));
applyOrderStatistics(statistics, payMap.get(dept.getDeptId()), walletRefundMap.get(dept.getDeptId()));
resultList.add(statistics);
}
return resultList;
@@ -519,7 +516,7 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
}
/**
* 构建钱包部门统计条件,保证五类钱包统计使用完全一致的权限、时间和钱包类型范围。
* 构建钱包部门统计条件,保证六类钱包统计使用完全一致的权限、时间和钱包类型范围。
*
* @param dto 部门统计页面筛选条件
* @param bizType 钱包流水业务类型编码
@@ -553,46 +550,37 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
}
/**
* 构建硬件订单部门统计条件。退款按售后完成时间统计,并覆盖支付、核销及整单退款状态,
* 因为部分退款不会把原订单状态改成整单退款。
* 构建硬件订单支付部门统计条件。订单金额仍保留非钱包支付能力,钱包支付会在后续按流水历史部门重新归属。
*
* @param dto 部门统计页面筛选条件
* @param refund 是否查询已完成退款
* @return 支付或退款汇总条件
* @return 支付明细查询条件
*/
private HardOrderSumAndCountDto buildOrderStatisticsDto(HardDeptTotalDto dto, boolean refund) {
private HardOrderSumAndCountDto buildPayStatisticsDto(HardDeptTotalDto dto) {
HardOrderSumAndCountDto orderDto = new HardOrderSumAndCountDto();
orderDto.setUserDeptIds(dto.getDeptIds());
orderDto.setIsInclude(dto.getIsInclude());
orderDto.setOrderStatusCodes(HardOrderStatusEnum.getPayPrice());
orderDto.setWalletTypeId(dto.getWalletTypeId());
orderDto.setMchIds(dto.getMchIds());
if (refund) {
orderDto.setRefundStatusCodes(List.of(HardSaleStatusEnum.COMPLETE.getStatus()));
orderDto.setRefundTimeList(dto.getCreateTimeList());
} else {
orderDto.setCreateTimeList(dto.getCreateTimeList());
}
orderDto.setCreateTimeList(dto.getCreateTimeList());
return orderDto;
}
/**
* 将订单或售后明细按钱包流水发生时保存的部门重新归集,并在重新归属后应用页面部门条件。
* 将订单支付明细按钱包流水发生时保存的部门重新归集,并在重新归属后应用页面部门条件。
*
* <p>支付统计中,现金、第三方支付或历史异常记录没有对应钱包流水时,继续使用订单创建时保存的
* 部门快照,避免丢失原有非钱包交易能力。退款统计则与退款记录页面一致,只接受真实 REFUND
* 钱包流水,不允许把没有资金流水的已完成售后回退计入。</p>
* <p>现金、第三方支付或历史异常记录没有对应钱包流水时,继续使用订单创建时保存的部门快照,
* 避免丢失原有非钱包交易能力。退款不再进入本方法,统一由 Wallet 按 REFUND 流水直接汇总。</p>
*
* @param details 符合订单状态、商户和时间条件的支付或售后明细
* @param transactionSnapshotMap 当前业务类型以业务号为键的钱包流水快照
* @param details 符合订单状态、商户和时间条件的支付明细
* @param transactionSnapshotMap 以订单业务号为键的钱包支付流水快照
* @param dto 页面部门包含或排除条件
* @param transactionOnly 是否只统计真实存在的钱包流水,并以钱包流水金额为准
* @return 按最终历史部门归集后的金额和次数
*/
private List<HardOrderSumAndCountVo> aggregateByTransactionDepartment(
private List<HardOrderSumAndCountVo> aggregatePaymentsByTransactionDepartment(
List<HardOrderDepartmentStatisticsDetail> details,
Map<String, WalletTransactionDepartmentSnapshotVo> transactionSnapshotMap,
HardDeptTotalDto dto, boolean transactionOnly) {
HardDeptTotalDto dto) {
if (ObjectUtil.isEmpty(details)) {
return List.of();
}
@@ -604,10 +592,6 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
continue;
}
WalletTransactionDepartmentSnapshotVo snapshot = transactionSnapshotMap.get(detail.getBizId());
if (transactionOnly && (snapshot == null || snapshot.getPrice() == null)) {
// 退款记录以真实 REFUND 钱包流水为准,已完成但没有资金流水的售后单不能回退计入。
continue;
}
Long departmentId = snapshot == null ? detail.getDeptId() : snapshot.getDeptId();
if (departmentId == null || !matchesDepartmentFilter(departmentId, departmentFilter, dto.getIsInclude())) {
continue;
@@ -619,8 +603,7 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
value.setCount(0L);
return value;
});
long amount = transactionOnly
? Math.abs(snapshot.getPrice()) : ObjectUtil.defaultIfNull(detail.getSum(), 0L);
long amount = ObjectUtil.defaultIfNull(detail.getSum(), 0L);
aggregate.setSum(Math.addExact(aggregate.getSum(), amount));
aggregate.setCount(Math.addExact(aggregate.getCount(), ObjectUtil.defaultIfNull(detail.getCount(), 0L)));
}
@@ -628,14 +611,14 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
}
/**
* 将支付和退款明细整理成受控查询组,并用一次或极少数几次 Wallet 调用查询全部流水历史部门。
* 将需要历史归属的交易明细整理成受控查询组,并用一次或极少数几次 Wallet 调用查询流水历史部门。
*
* <p>单个 SQL 查询组最多一万条,单次远程请求最多十组。常规报表只产生一次远程调用,
* 极端大数据量会自动拆分远程请求,避免请求体或 SQL 参数失控。远程失败时终止统计,
* 禁止静默回退造成历史部门和订单部门混合口径。</p>
*
* @param detailsByBizType 以钱包业务类型编码为键的支付订单或退款售后明细
* @return 第一层以业务类型编码为键、第二层以订单或售后单 ID 为键的钱包流水快照
* @param detailsByBizType 以钱包业务类型编码为键的待归属业务明细;部门页当前只传入支付订单
* @return 第一层以业务类型编码为键、第二层以业务 ID 为键的钱包流水快照
* @throws IllegalStateException 钱包历史部门查询失败时抛出
*/
private Map<String, Map<String, WalletTransactionDepartmentSnapshotVo>> queryTransactionDepartmentSnapshotMaps(
@@ -740,7 +723,7 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
}
/**
* 将订单统计按部门建立索引,供支付和退款结果按相同键合并。
* 将订单支付统计按部门建立索引,供支付结果与 Wallet 退款结果按相同键合并。
*
* @param statisticsList 订单部门统计列表
* @return 以部门 ID 为键的统计索引
@@ -758,13 +741,17 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
}
/**
* 合并部门的订单支付与已完成退款,并按原接口口径计算净金额和净次数。
* 合并部门的订单支付与 Wallet 退款流水,并计算净金额和净次数。
*
* <p>退款金额保留 REFUND 流水原始正负号,退款次数按流水条数统计,因此退款到账、退回审核冲正和
* 充值退款扣回与退款记录页面保持完全相同的流水口径。</p>
*
* @param target 部门统计结果
* @param pay 支付统计,允许为空
* @param refund 已完成售后退款统计,允许为空
* @param refund Wallet REFUND 流水统计,允许为空
*/
private void applyOrderStatistics(HardDeptTotalVo target, HardOrderSumAndCountVo pay, HardOrderSumAndCountVo refund) {
private void applyOrderStatistics(HardDeptTotalVo target, HardOrderSumAndCountVo pay,
WalletTransactionSumAndCountVo refund) {
long payPrice = pay == null ? 0L : ObjectUtil.defaultIfNull(pay.getSum(), 0L);
long payCount = pay == null ? 0L : ObjectUtil.defaultIfNull(pay.getCount(), 0L);
long refundPrice = refund == null ? 0L : ObjectUtil.defaultIfNull(refund.getSum(), 0L);
@@ -13,9 +13,11 @@ import com.bsd.pjcloud.hard.service.HardManagerService;
import com.bsd.pjcloud.wallet.api.constant.WalletBizEnum;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotBatchDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionSumAndCountDto;
import com.bsd.pjcloud.wallet.api.feign.RemoteMchService;
import com.bsd.pjcloud.wallet.api.feign.RemoteWalletTransactionService;
import com.bsd.pjcloud.wallet.api.vo.WalletTransactionDepartmentSnapshotVo;
import com.bsd.pjcloud.wallet.api.vo.WalletTransactionSumAndCountVo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -28,6 +30,7 @@ import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -54,7 +57,7 @@ class HardOrderTotalServiceImplTest {
@Mock
private RemoteMchService remoteMchService;
/** 钱包流水远程服务,分别提供普通钱包汇总和交易历史部门快照。 */
/** 钱包流水远程服务,提供普通钱包汇总、REFUND 汇总和支付历史部门快照。 */
@Mock
private RemoteWalletTransactionService remoteWalletTransactionService;
@@ -66,7 +69,7 @@ class HardOrderTotalServiceImplTest {
private HardOrderTotalServiceImpl service;
/**
* 构造真实汇总服务,并让与本用例无关的五类钱包充值统计返回空集合。
* 构造真实汇总服务,并让未单独配置的 Wallet 部门统计返回空集合。
*/
@BeforeEach
void setUp() {
@@ -76,7 +79,8 @@ class HardOrderTotalServiceImplTest {
}
/**
* 验证排除测试部门时以钱包流水部门为准:支付保留非钱包订单快照,退款只统计真实 REFUND 流水。
* 验证排除测试部门时以钱包流水部门为准:支付保留非钱包订单快照,退款直接采用全部 REFUND 流水。
* Wallet 返回的退款净额与条数已经包含分批退款、退回审核冲正和充值退款扣回,Hardware 不得再次按售后状态筛选。
*/
@Test
void pageListDeptFiltersAfterApplyingTransactionDepartmentSnapshot() {
@@ -85,18 +89,12 @@ class HardOrderTotalServiceImplTest {
detail("101", 11L, 9L, 100L),
detail("102", 12L, 1L, 200L),
detail("103", null, 1L, 50L)));
when(hardOrderMapper.listRefundStatisticsDetails(any(HardOrderSumAndCountDto.class))).thenReturn(List.of(
detail("101", 21L, 9L, 35L),
// 同一订单分批退款会产生不同售后单 ID,必须各自计入金额和次数。
detail("104", 21L, 9L, 25L),
detail("102", 22L, 1L, 40L),
detail("203", null, 1L, 10L)));
when(remoteWalletTransactionService.sumAndCount(argThat(walletQuery -> walletQuery != null
&& List.of(WalletBizEnum.REFUND.getCode()).equals(walletQuery.getBizTypes()))))
.thenReturn(R.ok(List.of(walletStatistics(1L, -9L, 3L))));
when(remoteWalletTransactionService.listDepartmentSnapshotsBatch(any())).thenReturn(R.ok(List.of(
snapshot(WalletBizEnum.PAYMENT.getCode(), "101", 11L, 1L, -100L),
snapshot(WalletBizEnum.PAYMENT.getCode(), "102", 12L, 9L, -200L),
snapshot(WalletBizEnum.REFUND.getCode(), "101", 21L, 1L, 30L),
snapshot(WalletBizEnum.REFUND.getCode(), "104", 21L, 1L, 20L),
snapshot(WalletBizEnum.REFUND.getCode(), "102", 22L, 9L, 40L))));
snapshot(WalletBizEnum.PAYMENT.getCode(), "102", 12L, 9L, -200L))));
HardDeptTotalDto query = new HardDeptTotalDto();
query.setDeptIds(List.of(9L));
@@ -108,32 +106,51 @@ class HardOrderTotalServiceImplTest {
HardDeptTotalVo normalDepartment = result.get(0);
assertEquals(BigDecimal.valueOf(150L), normalDepartment.getOrderPayPrice());
assertEquals(2L, normalDepartment.getOrderPayCount());
assertEquals(BigDecimal.valueOf(50L), normalDepartment.getOrderRefundPrice());
assertEquals(2L, normalDepartment.getOrderRefundCont());
assertEquals(BigDecimal.valueOf(100L), normalDepartment.getOrderProfitPrice());
assertEquals(0L, normalDepartment.getOrderProfitCont());
assertEquals(BigDecimal.valueOf(-9L), normalDepartment.getOrderRefundPrice());
assertEquals(3L, normalDepartment.getOrderRefundCont());
assertEquals(BigDecimal.valueOf(159L), normalDepartment.getOrderProfitPrice());
assertEquals(-1L, normalDepartment.getOrderProfitCont());
ArgumentCaptor<WalletTransactionDepartmentSnapshotBatchDto> captor =
ArgumentCaptor.forClass(WalletTransactionDepartmentSnapshotBatchDto.class);
verify(remoteWalletTransactionService).listDepartmentSnapshotsBatch(captor.capture());
verify(remoteWalletTransactionService, never()).listDepartmentSnapshots(any());
verify(hardOrderMapper, never()).listRefundStatisticsDetails(any(HardOrderSumAndCountDto.class));
verify(remoteWalletTransactionService).sumAndCount(argThat(walletQuery -> walletQuery != null
&& List.of(WalletBizEnum.REFUND.getCode()).equals(walletQuery.getBizTypes())
&& walletQuery.getType() == null
&& List.of(9L).equals(walletQuery.getDeptIds())
&& Boolean.FALSE.equals(walletQuery.getIsInclude())));
List<WalletTransactionDepartmentSnapshotDto> queryGroups = captor.getValue().getQueries();
assertEquals(2, queryGroups.size());
assertEquals(1, queryGroups.size());
assertEquals(WalletBizEnum.PAYMENT.getCode(), queryGroups.get(0).getBizType());
assertEquals(List.of("101", "102"), queryGroups.get(0).getBizIds());
assertEquals(List.of(11L, 12L), queryGroups.get(0).getWalletIds());
assertEquals(WalletBizEnum.REFUND.getCode(), queryGroups.get(1).getBizType());
assertEquals(List.of("101", "104", "102"), queryGroups.get(1).getBizIds());
assertEquals(List.of(21L, 22L), queryGroups.get(1).getWalletIds());
}
/**
* 创建一条订单或售后金额明细,部门表示原订单创建时的回退快照。
* 创建 Wallet 已按流水历史部门聚合的金额和条数。
*
* @param bizId 订单 ID 或售后单 ID 字符串
* @param departmentId 流水发生时部门 ID
* @param amount REFUND 流水正负金额之和,单位分
* @param count REFUND 流水条数,包含正向退款与负向冲正或扣回
* @return Wallet 部门流水统计
*/
private WalletTransactionSumAndCountVo walletStatistics(Long departmentId, Long amount, Long count) {
WalletTransactionSumAndCountVo statistics = new WalletTransactionSumAndCountVo();
statistics.setDeptId(departmentId);
statistics.setSum(amount);
statistics.setCount(count);
return statistics;
}
/**
* 创建一条订单支付金额明细,部门表示原订单创建时的回退快照。
*
* @param bizId 订单 ID 字符串
* @param walletId 原订单绑定的钱包 ID;非钱包交易为空
* @param departmentId 原订单部门 ID
* @param amount 支付或退款金额,单位分
* @param amount 支付金额,单位分
* @return 单笔待归属统计明细
*/
private HardOrderDepartmentStatisticsDetail detail(String bizId, Long walletId, Long departmentId, Long amount) {
@@ -150,7 +167,7 @@ class HardOrderTotalServiceImplTest {
* 创建钱包流水发生时固化的部门快照。
*
* @param bizType 钱包流水业务类型持久化编码
* @param bizId 订单 ID 或售后单 ID 字符串
* @param bizId 订单 ID 字符串
* @param walletId 产生流水的钱包 ID
* @param departmentId 流水创建时部门 ID
* @param price 钱包流水实际发生金额,单位分