feat:对账以wallet_transaction表为准

This commit is contained in:
dzc committed 2026-09-30 16:35:46 +08:00
1 parent b0b16ae58f
commit c34b249d7f
13 files changed
+611 -188

No files matched your search

@@ -16,6 +16,7 @@ import com.bsd.pjcloud.hard.api.entity.manager.HardManagerEntity;
import com.bsd.pjcloud.hard.api.entity.order.HardOrderEntity;
import com.bsd.pjcloud.hard.api.vo.hard.PanelPorductVo;
import com.bsd.pjcloud.hard.api.vo.order.*;
import com.bsd.pjcloud.hard.mapper.order.model.HardOrderDepartmentStatisticsDetail;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.apache.ibatis.annotations.Mapper;
@@ -169,6 +170,14 @@ public interface HardOrderMapper extends PjcloudBaseMapper<HardOrderEntity> {
List<HardOrderSumAndCountVo> refundSumAndCount(@Param("dto") HardOrderSumAndCountDto refundDto);
/**
* 查询部门交易统计的逐笔订单支付明细;钱包支付会按流水历史部门重新归集,SQL 不做部门过滤。
*
* @param dto 订单状态、商户、时间和钱包类型筛选条件
* @return 逐笔支付明细,bizId 为订单 ID 字符串
*/
List<HardOrderDepartmentStatisticsDetail> listPayStatisticsDetails(@Param("dto") HardOrderSumAndCountDto dto);
List<HardOrderSumAndCountVo> sumAndCountTotalByHourType(@Param("dto") HardOrderSumAndCountDto dto);
HardZyCardSyncVo totalYearMonthDay(@Param("dto") HardOrderDto hardOrderDto);
@@ -0,0 +1,25 @@
package com.bsd.pjcloud.hard.mapper.order.model;
import lombok.Data;
/**
* 部门交易统计使用的逐笔订单或售后明细,仅在 Hardware 服务内部参与历史部门重新归集。
*/
@Data
public class HardOrderDepartmentStatisticsDetail {
/** 钱包流水使用的稳定业务号;支付对应订单 ID,退款对应售后单 ID。 */
private String bizId;
/** 原订单绑定的钱包 ID;非钱包交易和部分历史异常订单允许为空。 */
private Long walletId;
/** 原订单创建时保存的部门 ID,在没有对应钱包流水时作为历史归属回退值。 */
private Long deptId;
/** 单笔支付或退款金额,单位分;历史退款金额为空时由 SQL 转为零。 */
private Long sum;
/** 当前明细对应的统计次数,支付订单和售后记录均固定为一。 */
private Long count;
}
@@ -19,6 +19,7 @@ import com.bsd.pjcloud.admin.api.entity.SysDept;
import com.bsd.pjcloud.admin.api.feign.RemoteDeptService;
import com.bsd.pjcloud.common.core.util.BeanUtils;
import com.bsd.pjcloud.common.core.util.CollectionUtils;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.common.data.datascope.DataScopeFuncEnum;
import com.bsd.pjcloud.common.security.service.PjcloudUser;
@@ -35,15 +36,19 @@ import com.bsd.pjcloud.hard.api.utils.HardUtil;
import com.bsd.pjcloud.hard.api.vo.order.*;
import com.bsd.pjcloud.hard.mapper.manager.HardManagerMapper;
import com.bsd.pjcloud.hard.mapper.order.HardOrderMapper;
import com.bsd.pjcloud.hard.mapper.order.model.HardOrderDepartmentStatisticsDetail;
import com.bsd.pjcloud.hard.service.HardManagerService;
import com.bsd.pjcloud.hard.service.HardOrderTotalService;
import com.bsd.pjcloud.hard.utils.RemoteBatchQueryUtil;
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.utils.WalletDataUtil;
import com.bsd.pjcloud.wallet.api.vo.MchManageVo;
import com.bsd.pjcloud.wallet.api.vo.WalletTransactionDepartmentSnapshotVo;
import com.bsd.pjcloud.wallet.api.vo.WalletTransactionSumAndCountVo;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
@@ -62,6 +67,7 @@ import java.time.*;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Validated
@Slf4j
@@ -220,7 +226,7 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
} else {
dto.setMchTypeCodes(dto.getMchTypeCodes());
}
// 查询总流水
// 流水与退款的业务发生时间不同,必须分别查询后再按商户合并,避免直接关联造成一笔订单被多笔售后重复放大。
HardOrderDto totalDto = BeanUtil.toBean(dto, HardOrderDto.class);
totalDto.setOrderStatusCodes(HardOrderStatusEnum.getPayPrice());
DataScope totalDataScope = DataScope.of();
@@ -230,7 +236,9 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
HardOrderDto refundDto = BeanUtil.toBean(dto, HardOrderDto.class);
refundDto.setPayTimeList(null);
refundDto.setRefundTimeList(dto.getPayTimeList());
refundDto.setOrderStatusCodes(HardOrderStatusEnum.getPayErrorCodes());
// refundStatusCodes 属于订单退款状态,售后完成状态必须使用 saleStatusCodes,二者混用会把有效退款全部过滤掉。
// 部分退款后订单仍是已支付状态,退款统计不能只查询整单退款状态的订单。
refundDto.setOrderStatusCodes(HardOrderStatusEnum.getPayPrice());
refundDto.setRefundStatusCodes(List.of(HardSaleStatusEnum.COMPLETE.getStatus()));
DataScope refundDataScope = DataScope.of();
refundDataScope.setScopeDeptName("merchant_dept_id");
@@ -327,7 +335,8 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
refundDto.setRefundTimeList(dto.getPayTimeList());
refundDto.setAuditTimeYearStr(dto.getPayTimeYearStr());
refundDto.setPayTimeYearStr(null);
refundDto.setOrderStatusCodes(HardOrderStatusEnum.getPayErrorCodes());
// 部分退款后订单仍是已支付状态,退款统计不能只查询整单退款状态的订单;审核中的售后单未完成退款,不计入统计。
refundDto.setOrderStatusCodes(HardOrderStatusEnum.getPayPrice());
refundDto.setRefundStatusCodes(List.of(HardSaleStatusEnum.COMPLETE.getStatus()));
if (ObjectUtil.isNotEmpty(dto.getMchIds())) {
refundDto.setMchIds(dto.getMchIds());
@@ -514,10 +523,99 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
/**
* 部门交易记录
*
* <p>钱包操作与退款金额直接按 wallet_transaction 流水汇总;订单支付先取逐笔明细,
* 再按支付流水创建时固化的部门重新归集,现金、第三方支付回退订单部门快照。
* 退款金额保留 REFUND 流水原始正负号,审核中的退款尚未生成流水,不会计入任何金额。</p>
*
* @param dto 当前用户权限范围内的部门、时间和钱包类型筛选条件
* @return 部门交易统计;筛选范围内没有部门时返回空列表
* @throws IllegalStateException 部门或钱包远程统计调用失败时抛出,避免返回不完整报表
*/
@Override
public List<HardDeptTotalVo> pageListDept(HardDeptTotalDto dto) {
// 查询部门
List<SysDept> deptList = queryDepartmentList(dto);
if (ObjectUtil.isEmpty(deptList)) {
return List.of();
}
List<WalletTransactionSumAndCountVo> walletBatchList = queryWalletStatistics(
buildWalletStatisticsDto(dto, List.of(WalletBizEnum.BATCH.getCode()), WalletTransactionSumAndCountDto.TYPE_INCOME));
List<WalletTransactionSumAndCountVo> walletReduceList = queryWalletStatistics(
buildWalletStatisticsDto(dto, List.of(WalletBizEnum.BATCH.getCode()), WalletTransactionSumAndCountDto.TYPE_EXPENSE));
List<WalletTransactionSumAndCountVo> walletReturnCanteenList = queryWalletStatistics(
buildWalletStatisticsDto(dto, List.of(WalletBizEnum.RETURN_CANTEEN.getCode()), null));
List<WalletTransactionSumAndCountVo> walletOnceBatchList = queryWalletStatistics(
buildWalletStatisticsDto(dto, List.of(WalletBizEnum.UPDATE.getCode(), WalletBizEnum.RECHARGE.getCode()), WalletTransactionSumAndCountDto.TYPE_INCOME));
List<WalletTransactionSumAndCountVo> walletOnceReduceList = queryWalletStatistics(
buildWalletStatisticsDto(dto, List.of(WalletBizEnum.UPDATE.getCode(), WalletBizEnum.RECHARGE.getCode()), WalletTransactionSumAndCountDto.TYPE_EXPENSE));
List<WalletTransactionSumAndCountVo> walletRefundList = queryWalletStatistics(
buildWalletStatisticsDto(dto, List.of(WalletBizEnum.REFUND.getCode()), null));
List<HardOrderDepartmentStatisticsDetail> payDetails =
hardOrderMapper.listPayStatisticsDetails(buildPayStatisticsDto(dto));
Map<String, List<HardOrderDepartmentStatisticsDetail>> detailsByBizType = new LinkedHashMap<>();
detailsByBizType.put(WalletBizEnum.PAYMENT.getCode(), payDetails);
Map<String, Map<String, WalletTransactionDepartmentSnapshotVo>> transactionSnapshotMaps =
queryTransactionDepartmentSnapshotMaps(detailsByBizType);
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);
List<HardDeptTotalVo> resultList = new ArrayList<>(deptList.size());
for (SysDept dept : deptList) {
HardDeptTotalVo statistics = new HardDeptTotalVo();
statistics.setDeptId(dept.getDeptId());
statistics.setDeptName(dept.getName());
WalletTransactionSumAndCountVo walletBatch = walletBatchMap.get(dept.getDeptId());
if (walletBatch != null) {
statistics.setWalletPrice(BigDecimal.valueOf(ObjectUtil.defaultIfNull(walletBatch.getSum(), 0L)));
statistics.setWalletCount(ObjectUtil.defaultIfNull(walletBatch.getCount(), 0L));
}
WalletTransactionSumAndCountVo walletReduce = walletReduceMap.get(dept.getDeptId());
if (walletReduce != null) {
statistics.setWalletReducePrice(Math.abs(ObjectUtil.defaultIfNull(walletReduce.getSum(), 0L)));
statistics.setWalletReduceCount(ObjectUtil.defaultIfNull(walletReduce.getCount(), 0L));
}
WalletTransactionSumAndCountVo walletReturnCanteen = walletReturnCanteenMap.get(dept.getDeptId());
if (walletReturnCanteen != null) {
statistics.setWalletReturnCanteenPrice(BigDecimal.valueOf(Math.abs(
ObjectUtil.defaultIfNull(walletReturnCanteen.getSum(), 0L))));
statistics.setWalletReturnCanteenCount(ObjectUtil.defaultIfNull(walletReturnCanteen.getCount(), 0L));
}
WalletTransactionSumAndCountVo walletOnceBatch = walletOnceBatchMap.get(dept.getDeptId());
if (walletOnceBatch != null) {
statistics.setWalletOncePrice(BigDecimal.valueOf(ObjectUtil.defaultIfNull(walletOnceBatch.getSum(), 0L)));
statistics.setWalletOnceCount(ObjectUtil.defaultIfNull(walletOnceBatch.getCount(), 0L));
}
WalletTransactionSumAndCountVo walletOnceReduce = walletOnceReduceMap.get(dept.getDeptId());
if (walletOnceReduce != null) {
statistics.setWalletOnceReducePrice(BigDecimal.valueOf(Math.abs(
ObjectUtil.defaultIfNull(walletOnceReduce.getSum(), 0L))));
statistics.setWalletOnceReduceCount(ObjectUtil.defaultIfNull(walletOnceReduce.getCount(), 0L));
}
applyOrderStatistics(statistics, payMap.get(dept.getDeptId()), walletRefundMap.get(dept.getDeptId()));
resultList.add(statistics);
}
return resultList;
}
/**
* 查询统计结果需要展示的部门,并校验 UPMS 远程调用结果。
*
* @param dto 部门包含或排除筛选条件
* @return 匹配的部门列表,远程响应数据为空时返回空列表
* @throws IllegalStateException UPMS 部门远程调用失败时抛出
*/
private List<SysDept> queryDepartmentList(HardDeptTotalDto dto) {
DeptAllListDto deptDto = new DeptAllListDto();
deptDto.setIsInclude(dto.getIsInclude());
if (ObjectUtil.isNotEmpty(dto.getDeptId())) {
@@ -527,193 +625,264 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService {
deptDto.setDeptId(null);
deptDto.setDeptIds(dto.getDeptIds());
}
List<SysDept> deptList = remoteDeptService.getInnerAllDept(deptDto).getData();
if (ObjectUtil.isEmpty(deptList)) return null;
// List<Long> deptIds = deptList.stream().map(SysDept::getDeptId).filter(ObjectUtil::isNotEmpty).distinct().toList();
List<HardDeptTotalVo> resultList = deptList.stream().map(v -> {
HardDeptTotalVo item = new HardDeptTotalVo();
item.setDeptId(v.getDeptId());
item.setDeptName(v.getName());
return item;
}).toList();
// 查询钱包批量充值流水
CompletableFuture<List<WalletTransactionSumAndCountVo>> walletBatchFuture = CompletableFuture.supplyAsync(() -> {
WalletTransactionSumAndCountDto walletDto = new WalletTransactionSumAndCountDto();
walletDto.setDeptIds(dto.getDeptIds());
walletDto.setIsInclude(dto.getIsInclude());
walletDto.setWalletTypeIdList(dto.getWalletTypeIdList());
walletDto.setBizTypes(List.of(WalletBizEnum.BATCH.getCode()));
walletDto.setType(WalletTransactionSumAndCountDto.TYPE_INCOME);
if (ObjectUtil.isNotEmpty(dto.getCreateTimeList())) {
walletDto.setCreateTimeList(dto.getCreateTimeList());
R<List<SysDept>> response = remoteDeptService.getInnerAllDept(deptDto);
if (response == null || !response.isOk()) {
throw new IllegalStateException("查询部门交易统计的部门信息失败");
}
return response.getData() == null ? List.of() : response.getData();
}
/**
* 构建钱包部门统计条件,保证六类钱包统计使用完全一致的权限、时间和钱包类型范围。
*
* @param dto 部门统计页面筛选条件
* @param bizTypes 钱包流水业务类型编码集合
* @param type 收支方向;为空时不限制流水正负方向
* @return 钱包流水汇总条件
*/
private WalletTransactionSumAndCountDto buildWalletStatisticsDto(HardDeptTotalDto dto, List<String> bizTypes, Integer type) {
WalletTransactionSumAndCountDto walletDto = new WalletTransactionSumAndCountDto();
walletDto.setDeptIds(dto.getDeptIds());
walletDto.setIsInclude(dto.getIsInclude());
walletDto.setBizTypes(bizTypes);
walletDto.setType(type);
walletDto.setCreateTimeList(dto.getCreateTimeList());
walletDto.setWalletTypeId(dto.getWalletTypeId());
walletDto.setWalletTypeIdList(dto.getWalletTypeIdList());
return walletDto;
}
/**
* 调用钱包服务查询部门流水统计;远程业务失败时终止本次汇总,避免把失败数据误展示为零。
*
* @param walletDto 钱包流水汇总条件
* @return 钱包部门统计,远程响应数据为空时返回空列表
* @throws IllegalStateException 钱包流水远程调用失败时抛出
*/
private List<WalletTransactionSumAndCountVo> queryWalletStatistics(WalletTransactionSumAndCountDto walletDto) {
R<List<WalletTransactionSumAndCountVo>> response = remoteWalletTransactionService.sumAndCount(walletDto);
if (response == null || !response.isOk()) {
throw new IllegalStateException("查询钱包部门交易统计失败");
}
return response.getData() == null ? List.of() : response.getData();
}
/**
* 构建硬件订单支付部门统计条件。订单金额仍保留非钱包支付能力,钱包支付会在后续按流水历史部门重新归属;
* 明细查询不做部门过滤,部门包含/排除条件在重归集后统一应用。
*
* @param dto 部门统计页面筛选条件
* @return 支付明细查询条件
*/
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.setWalletTypeIdList(dto.getWalletTypeIdList());
orderDto.setCreateTimeList(dto.getCreateTimeList());
return orderDto;
}
/**
* 将订单支付明细按钱包流水发生时保存的部门重新归集,并在重新归属后应用页面部门条件。
*
* <p>现金、第三方支付或历史异常记录没有对应钱包流水时,继续使用订单创建时保存的部门快照,
* 避免丢失原有非钱包交易能力。退款不再进入本方法,统一由 Wallet 按 REFUND 流水直接汇总。</p>
*
* @param details 符合订单状态、商户和时间条件的支付明细
* @param transactionSnapshotMap 以订单业务号为键的钱包支付流水快照
* @param dto 页面部门包含或排除条件
* @return 按最终历史部门归集后的金额和次数
*/
private List<HardOrderSumAndCountVo> aggregatePaymentsByTransactionDepartment(
List<HardOrderDepartmentStatisticsDetail> details,
Map<String, WalletTransactionDepartmentSnapshotVo> transactionSnapshotMap,
HardDeptTotalDto dto) {
if (ObjectUtil.isEmpty(details)) {
return List.of();
}
Set<Long> departmentFilter = ObjectUtil.isEmpty(dto.getDeptIds())
? Set.of() : new HashSet<>(dto.getDeptIds());
Map<Long, HardOrderSumAndCountVo> aggregateMap = new LinkedHashMap<>();
for (HardOrderDepartmentStatisticsDetail detail : details) {
if (detail == null) {
continue;
}
if (ObjectUtil.isNotEmpty(dto.getWalletTypeId())) {
walletDto.setWalletTypeId(dto.getWalletTypeId());
WalletTransactionDepartmentSnapshotVo snapshot = transactionSnapshotMap.get(detail.getBizId());
Long departmentId = snapshot == null ? detail.getDeptId() : snapshot.getDeptId();
if (departmentId == null || !matchesDepartmentFilter(departmentId, departmentFilter, dto.getIsInclude())) {
continue;
}
return remoteWalletTransactionService.sumAndCount(walletDto).getData();
});
// 查询钱包批量扣减流水
CompletableFuture<List<WalletTransactionSumAndCountVo>> walletReduceFuture = CompletableFuture.supplyAsync(() -> {
WalletTransactionSumAndCountDto walletDto = new WalletTransactionSumAndCountDto();
walletDto.setDeptIds(dto.getDeptIds());
walletDto.setIsInclude(dto.getIsInclude());
walletDto.setWalletTypeIdList(dto.getWalletTypeIdList());
walletDto.setBizTypes(List.of(WalletBizEnum.BATCH.getCode()));
walletDto.setType(WalletTransactionSumAndCountDto.TYPE_EXPENSE);
if (ObjectUtil.isNotEmpty(dto.getCreateTimeList())) {
walletDto.setCreateTimeList(dto.getCreateTimeList());
}
if (ObjectUtil.isNotEmpty(dto.getWalletTypeId())) {
walletDto.setWalletTypeId(dto.getWalletTypeId());
}
return remoteWalletTransactionService.sumAndCount(walletDto).getData();
});
// 查询钱包退还各自食堂
CompletableFuture<List<WalletTransactionSumAndCountVo>> walletRetrunCanteenFuture = CompletableFuture.supplyAsync(() -> {
WalletTransactionSumAndCountDto walletDto = new WalletTransactionSumAndCountDto();
walletDto.setDeptIds(dto.getDeptIds());
walletDto.setIsInclude(dto.getIsInclude());
walletDto.setWalletTypeIdList(dto.getWalletTypeIdList());
walletDto.setBizTypes(List.of(WalletBizEnum.RETURN_CANTEEN.getCode()));
// walletDto.setType(WalletTransactionSumAndCountDto.TYPE_EXPENSE);
if (ObjectUtil.isNotEmpty(dto.getCreateTimeList())) {
walletDto.setCreateTimeList(dto.getCreateTimeList());
}
if (ObjectUtil.isNotEmpty(dto.getWalletTypeId())) {
walletDto.setWalletTypeId(dto.getWalletTypeId());
}
return remoteWalletTransactionService.sumAndCount(walletDto).getData();
});
// 查询钱包单次充值流水
CompletableFuture<List<WalletTransactionSumAndCountVo>> walletOnceBatchFuture = CompletableFuture.supplyAsync(() -> {
WalletTransactionSumAndCountDto walletDto = new WalletTransactionSumAndCountDto();
walletDto.setDeptIds(dto.getDeptIds());
walletDto.setIsInclude(dto.getIsInclude());
walletDto.setWalletTypeIdList(dto.getWalletTypeIdList());
walletDto.setBizTypes(List.of(WalletBizEnum.UPDATE.getCode(), WalletBizEnum.RECHARGE.getCode()));
walletDto.setType(WalletTransactionSumAndCountDto.TYPE_INCOME);
if (ObjectUtil.isNotEmpty(dto.getCreateTimeList())) {
walletDto.setCreateTimeList(dto.getCreateTimeList());
}
if (ObjectUtil.isNotEmpty(dto.getWalletTypeId())) {
walletDto.setWalletTypeId(dto.getWalletTypeId());
}
return remoteWalletTransactionService.sumAndCount(walletDto).getData();
});
// 查询钱包单次扣减流水
CompletableFuture<List<WalletTransactionSumAndCountVo>> walletOnceReduceFuture = CompletableFuture.supplyAsync(() -> {
WalletTransactionSumAndCountDto walletDto = new WalletTransactionSumAndCountDto();
walletDto.setDeptIds(dto.getDeptIds());
walletDto.setIsInclude(dto.getIsInclude());
walletDto.setWalletTypeIdList(dto.getWalletTypeIdList());
walletDto.setBizTypes(List.of(WalletBizEnum.UPDATE.getCode(), WalletBizEnum.RECHARGE.getCode()));
walletDto.setType(WalletTransactionSumAndCountDto.TYPE_EXPENSE);
if (ObjectUtil.isNotEmpty(dto.getCreateTimeList())) {
walletDto.setCreateTimeList(dto.getCreateTimeList());
}
if (ObjectUtil.isNotEmpty(dto.getWalletTypeId())) {
walletDto.setWalletTypeId(dto.getWalletTypeId());
}
return remoteWalletTransactionService.sumAndCount(walletDto).getData();
});
// 查询订单支付流水
CompletableFuture<List<HardOrderSumAndCountVo>> payFuture = CompletableFuture.supplyAsync(() -> {
HardOrderSumAndCountDto payDto = new HardOrderSumAndCountDto();
payDto.setUserDeptIds(dto.getDeptIds());
payDto.setIsInclude(dto.getIsInclude());
payDto.setOrderStatusCodes(HardOrderStatusEnum.getPayPrice());
payDto.setWalletTypeIdList(dto.getWalletTypeIdList());
if (ObjectUtil.isNotEmpty(dto.getCreateTimeList())) {
payDto.setCreateTimeList(dto.getCreateTimeList());
}
if (ObjectUtil.isNotEmpty(dto.getWalletTypeId())) {
payDto.setWalletTypeId(dto.getWalletTypeId());
}
return hardOrderMapper.sumAndCount(payDto);
});
// 查询订单退款流水
CompletableFuture<List<HardOrderSumAndCountVo>> refundFuture = CompletableFuture.supplyAsync(() -> {
HardOrderSumAndCountDto refundDto = new HardOrderSumAndCountDto();
refundDto.setUserDeptIds(dto.getDeptIds());
refundDto.setIsInclude(dto.getIsInclude());
refundDto.setOrderStatusCodes(HardOrderStatusEnum.getPayErrorCodes());
refundDto.setRefundStatusCodes(List.of(HardSaleStatusEnum.COMPLETE.getStatus()));
refundDto.setWalletTypeIdList(dto.getWalletTypeIdList());
if (ObjectUtil.isNotEmpty(dto.getCreateTimeList())) {
refundDto.setRefundTimeList(dto.getCreateTimeList());
}
if (ObjectUtil.isNotEmpty(dto.getWalletTypeId())) {
refundDto.setWalletTypeId(dto.getWalletTypeId());
}
return hardOrderMapper.refundSumAndCount(refundDto);
});
CompletableFuture<Void> allFutures = CompletableFuture.allOf(walletBatchFuture, walletReduceFuture, payFuture, refundFuture, walletOnceBatchFuture, walletOnceReduceFuture);
allFutures.join();
List<WalletTransactionSumAndCountVo> walletBatchList = walletBatchFuture.join();
List<WalletTransactionSumAndCountVo> walletReduceList = walletReduceFuture.join();
List<WalletTransactionSumAndCountVo> walletRetrunCanteenList = walletRetrunCanteenFuture.join();
List<WalletTransactionSumAndCountVo> walletOnceBatchList = walletOnceBatchFuture.join();
List<WalletTransactionSumAndCountVo> walletOnceReduceList = walletOnceReduceFuture.join();
List<HardOrderSumAndCountVo> payList = payFuture.join();
List<HardOrderSumAndCountVo> refundList = refundFuture.join();
// 聚合一下数据
for (HardDeptTotalVo v : resultList) {
if (ObjectUtil.isNotEmpty(walletBatchList)) {
walletBatchList.stream().filter(i -> ObjectUtil.equals(i.getDeptId(), v.getDeptId())).findFirst().ifPresent(i -> {
v.setWalletPrice(BigDecimal.valueOf(i.getSum()));
v.setWalletCount(i.getCount());
});
}
if (ObjectUtil.isNotEmpty(walletReduceList)) {
walletReduceList.stream().filter(i -> ObjectUtil.equals(i.getDeptId(), v.getDeptId())).findFirst().ifPresent(i -> {
v.setWalletReducePrice(Math.abs(i.getSum()));
v.setWalletReduceCount(i.getCount());
});
}
// 退还各自食堂
if (ObjectUtil.isNotEmpty(walletRetrunCanteenList)) {
walletRetrunCanteenList.stream().filter(i -> ObjectUtil.equals(i.getDeptId(), v.getDeptId())).findFirst().ifPresent(i -> {
v.setWalletReturnCanteenPrice(BigDecimal.valueOf(Math.abs(i.getSum())));
v.setWalletReturnCanteenCount(i.getCount());
});
}
if (ObjectUtil.isNotEmpty(walletOnceBatchList)) {
walletOnceBatchList.stream().filter(i -> ObjectUtil.equals(i.getDeptId(), v.getDeptId())).findFirst().ifPresent(i -> {
v.setWalletOncePrice(BigDecimal.valueOf(i.getSum()));
v.setWalletOnceCount(i.getCount());
});
}
if (ObjectUtil.isNotEmpty(walletOnceReduceList)) {
walletOnceReduceList.stream().filter(i -> ObjectUtil.equals(i.getDeptId(), v.getDeptId())).findFirst().ifPresent(i -> {
v.setWalletOnceReducePrice(BigDecimal.valueOf(Math.abs(i.getSum())));
v.setWalletOnceReduceCount(i.getCount());
});
}
if (ObjectUtil.isNotEmpty(payList)) {
payList.stream().filter(i -> ObjectUtil.equals(i.getDeptId(), v.getDeptId())).findFirst().ifPresent(i -> {
v.setOrderPayPrice(BigDecimal.valueOf(i.getSum()));
v.setOrderPayCount(i.getCount());
});
}
if (ObjectUtil.isNotEmpty(refundList)) {
refundList.stream().filter(i -> ObjectUtil.equals(i.getDeptId(), v.getDeptId())).findFirst().ifPresent(i -> {
v.setOrderRefundPrice(BigDecimal.valueOf(i.getSum()));
v.setOrderRefundCont(i.getCount());
});
HardOrderSumAndCountVo aggregate = aggregateMap.computeIfAbsent(departmentId, key -> {
HardOrderSumAndCountVo value = new HardOrderSumAndCountVo();
value.setDeptId(key);
value.setSum(0L);
value.setCount(0L);
return value;
});
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)));
}
return new ArrayList<>(aggregateMap.values());
}
/**
* 将需要历史归属的交易明细整理成受控查询组,并用一次或极少数几次 Wallet 调用查询流水历史部门。
*
* <p>单个 SQL 查询组最多一万条,单次远程请求最多十组。常规报表只产生一次远程调用,
* 极端大数据量会自动拆分远程请求,避免请求体或 SQL 参数失控。远程失败时终止统计,
* 禁止静默回退造成历史部门和订单部门混合口径。</p>
*
* @param detailsByBizType 以钱包业务类型编码为键的待归属业务明细;部门页当前只传入支付订单
* @return 第一层以业务类型编码为键、第二层以业务 ID 为键的钱包流水快照
* @throws IllegalStateException 钱包历史部门查询失败时抛出
*/
private Map<String, Map<String, WalletTransactionDepartmentSnapshotVo>> queryTransactionDepartmentSnapshotMaps(
Map<String, List<HardOrderDepartmentStatisticsDetail>> detailsByBizType) {
Map<String, Map<String, Long>> expectedWalletMaps = new LinkedHashMap<>();
Map<String, Map<String, WalletTransactionDepartmentSnapshotVo>> resultMaps = new LinkedHashMap<>();
List<WalletTransactionDepartmentSnapshotDto> queryGroups = new ArrayList<>();
for (Map.Entry<String, List<HardOrderDepartmentStatisticsDetail>> entry : detailsByBizType.entrySet()) {
List<HardOrderDepartmentStatisticsDetail> sourceDetails =
entry.getValue() == null ? List.of() : entry.getValue();
List<HardOrderDepartmentStatisticsDetail> walletDetails = sourceDetails.stream()
.filter(ObjectUtil::isNotEmpty)
.filter(detail -> ObjectUtil.isAllNotEmpty(detail.getBizId(), detail.getWalletId()))
.collect(Collectors.toMap(HardOrderDepartmentStatisticsDetail::getBizId,
detail -> detail, (first, ignored) -> first, LinkedHashMap::new))
.values().stream()
.toList();
Map<String, Long> expectedWalletMap = walletDetails.stream().collect(Collectors.toMap(
HardOrderDepartmentStatisticsDetail::getBizId,
HardOrderDepartmentStatisticsDetail::getWalletId,
(first, ignored) -> first, LinkedHashMap::new));
expectedWalletMaps.put(entry.getKey(), expectedWalletMap);
resultMaps.put(entry.getKey(), new LinkedHashMap<>());
for (int start = 0; start < walletDetails.size();
start += WalletTransactionDepartmentSnapshotDto.MAX_QUERY_BATCH_SIZE) {
int end = Math.min(start + WalletTransactionDepartmentSnapshotDto.MAX_QUERY_BATCH_SIZE,
walletDetails.size());
List<HardOrderDepartmentStatisticsDetail> batchDetails = walletDetails.subList(start, end);
WalletTransactionDepartmentSnapshotDto snapshotDto = new WalletTransactionDepartmentSnapshotDto();
snapshotDto.setBizType(entry.getKey());
snapshotDto.setBizIds(batchDetails.stream()
.map(HardOrderDepartmentStatisticsDetail::getBizId).toList());
snapshotDto.setWalletIds(batchDetails.stream()
.map(HardOrderDepartmentStatisticsDetail::getWalletId).distinct().toList());
queryGroups.add(snapshotDto);
}
}
// 计算净流水
for (HardDeptTotalVo v : resultList) {
// 流水金额
if (ObjectUtil.isNotEmpty(v.getOrderPayPrice())) {
v.setOrderProfitPrice(v.getOrderPayPrice().subtract(v.getOrderRefundPrice()));
for (int start = 0; start < queryGroups.size();
start += WalletTransactionDepartmentSnapshotBatchDto.MAX_QUERY_GROUP_SIZE) {
int end = Math.min(start + WalletTransactionDepartmentSnapshotBatchDto.MAX_QUERY_GROUP_SIZE,
queryGroups.size());
WalletTransactionDepartmentSnapshotBatchDto batchDto =
new WalletTransactionDepartmentSnapshotBatchDto();
batchDto.setQueries(new ArrayList<>(queryGroups.subList(start, end)));
R<List<WalletTransactionDepartmentSnapshotVo>> response =
remoteWalletTransactionService.listDepartmentSnapshotsBatch(batchDto);
if (response == null || !response.isOk()) {
throw new IllegalStateException("查询钱包流水历史部门失败");
}
// 流水次数
if (ObjectUtil.isNotEmpty(v.getOrderPayCount())) {
v.setOrderProfitCont(v.getOrderPayCount() - v.getOrderRefundCont());
if (ObjectUtil.isEmpty(response.getData())) {
continue;
}
for (WalletTransactionDepartmentSnapshotVo snapshot : response.getData()) {
if (snapshot != null && ObjectUtil.isAllNotEmpty(
snapshot.getBizType(), snapshot.getBizId(), snapshot.getWalletId(), snapshot.getDeptId())) {
Map<String, Long> expectedWalletMap = expectedWalletMaps.get(snapshot.getBizType());
Map<String, WalletTransactionDepartmentSnapshotVo> resultMap = resultMaps.get(snapshot.getBizType());
if (expectedWalletMap == null || resultMap == null
|| !ObjectUtil.equals(expectedWalletMap.get(snapshot.getBizId()), snapshot.getWalletId())) {
continue;
}
// 钱包服务按流水主键倒序返回,异常重复业务号保留最新一笔历史快照。
resultMap.putIfAbsent(snapshot.getBizId(), snapshot);
}
}
}
return resultList;
return resultMaps;
}
/**
* 判断重新归属后的历史部门是否命中页面包含或排除条件。
*
* @param departmentId 流水历史部门或非钱包订单部门
* @param departmentFilter 页面部门主键集合
* @param isInclude true 表示包含,false 表示排除,空值兼容旧接口的包含语义
* @return 当前历史部门是否参与统计
*/
private boolean matchesDepartmentFilter(Long departmentId, Set<Long> departmentFilter, Boolean isInclude) {
if (departmentFilter.isEmpty()) {
return true;
}
boolean contains = departmentFilter.contains(departmentId);
return Boolean.FALSE.equals(isInclude) ? !contains : contains;
}
/**
* 将钱包统计按部门建立索引,避免每个部门重复遍历全部统计结果。
*
* @param statisticsList 钱包部门统计列表
* @return 以部门 ID 为键的统计索引
*/
private Map<Long, WalletTransactionSumAndCountVo> indexWalletStatistics(List<WalletTransactionSumAndCountVo> statisticsList) {
Map<Long, WalletTransactionSumAndCountVo> statisticsMap = new LinkedHashMap<>();
if (ObjectUtil.isNotEmpty(statisticsList)) {
for (WalletTransactionSumAndCountVo statistics : statisticsList) {
if (statistics != null && statistics.getDeptId() != null) {
statisticsMap.put(statistics.getDeptId(), statistics);
}
}
}
return statisticsMap;
}
/**
* 将订单支付统计按部门建立索引,供支付结果与 Wallet 退款结果按相同键合并。
*
* @param statisticsList 订单部门统计列表
* @return 以部门 ID 为键的统计索引
*/
private Map<Long, HardOrderSumAndCountVo> indexOrderStatistics(List<HardOrderSumAndCountVo> statisticsList) {
Map<Long, HardOrderSumAndCountVo> statisticsMap = new LinkedHashMap<>();
if (ObjectUtil.isNotEmpty(statisticsList)) {
for (HardOrderSumAndCountVo statistics : statisticsList) {
if (statistics != null && statistics.getDeptId() != null) {
statisticsMap.put(statistics.getDeptId(), statistics);
}
}
}
return statisticsMap;
}
/**
* 合并部门的订单支付与 Wallet 退款流水,并计算净金额和净次数。
*
* <p>退款金额保留 REFUND 流水原始正负号,退款次数按流水条数统计,因此退款到账、充值退款扣回
* 与退款记录页面保持完全相同的流水口径;退款审核中的售后单尚未生成流水,不会计入。</p>
*
* @param target 部门统计结果
* @param pay 支付统计,允许为空
* @param refund Wallet 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);
long refundCount = refund == null ? 0L : ObjectUtil.defaultIfNull(refund.getCount(), 0L);
BigDecimal orderPayPrice = BigDecimal.valueOf(payPrice);
BigDecimal orderRefundPrice = BigDecimal.valueOf(refundPrice);
target.setOrderPayPrice(orderPayPrice);
target.setOrderPayCount(payCount);
target.setOrderRefundPrice(orderRefundPrice);
target.setOrderRefundCont(refundCount);
target.setOrderProfitPrice(orderPayPrice.subtract(orderRefundPrice));
target.setOrderProfitCont(payCount - refundCount);
}
/**
@@ -775,6 +775,50 @@
group by a.user_dept_id
</select>
<!-- 部门交易统计的逐笔支付明细:钱包支付随后按 wallet_transaction 流水历史部门重新归集,
因此这里不做 user_dept_id 过滤,部门包含/排除条件在重归集后应用。 -->
<select id="listPayStatisticsDetails"
resultType="com.bsd.pjcloud.hard.mapper.order.model.HardOrderDepartmentStatisticsDetail">
select
cast(a.id as char) as bizId,
a.wallet_id as walletId,
a.user_dept_id as deptId,
a.pay_price as sum,
1 as count
from hard_order a
left join pjcloudx_wallet.wallet_manage w_m ON w_m.id = a.wallet_id
<where>
a.del_flag = 0
<if test="dto.walletTypeId != null">
and w_m.wallet_type_id = #{dto.walletTypeId}
</if>
<if test="dto.walletTypeIdList != null and dto.walletTypeIdList.size() != 0">
and w_m.wallet_type_id in
<foreach collection="dto.walletTypeIdList" item="v" open="(" close=")" separator=",">
#{v}
</foreach>
</if>
<if test="dto.isRefundSuccess != null and dto.isRefundSuccess == true ">
and (a.refund_status is null or a.refund_status not in ('20'))
</if>
<if test="dto.orderStatusCodes != null and dto.orderStatusCodes.size() != 0">
and a.order_status in
<foreach collection="dto.orderStatusCodes" item="v" open="(" close=")" separator=",">
#{v}
</foreach>
</if>
<if test="dto.createTimeList != null and dto.createTimeList.length == 2">
and a.create_time between #{dto.createTimeList[0]} and #{dto.createTimeList[1]}
</if>
<if test="dto.mchIds != null and dto.mchIds.size() != 0">
and a.merchant_id in
<foreach collection="dto.mchIds" item="v" open="(" close=")" separator=",">
#{v}
</foreach>
</if>
</where>
</select>
<select id="refundSumAndCount" resultType="com.bsd.pjcloud.hard.api.vo.order.HardOrderSumAndCountVo">
select o.user_dept_id as deptId,sum(ifnull(a.refund_price, 0)) as sum, count(0) as count
from hard_after_sale a
@@ -0,0 +1,28 @@
package com.bsd.pjcloud.wallet.api.dto;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
/**
* 钱包流水历史部门快照批量查询条件,用一次跨服务请求承载多个受控 SQL 查询组。
*/
@Data
public class WalletTransactionDepartmentSnapshotBatchDto {
/**
* 单次远程请求允许携带的最大查询组数量,限制请求体和钱包服务单线程数据库占用时长。
*/
public static final int MAX_QUERY_GROUP_SIZE = 10;
/**
* 按业务类型和 SQL 参数容量拆分后的查询组,组内条件继续沿用单组校验规则。
*/
@Valid
@NotEmpty(message = "钱包流水历史部门查询组不能为空")
@Size(max = MAX_QUERY_GROUP_SIZE, message = "单次查询的钱包流水历史部门查询组不能超过10个")
private List<WalletTransactionDepartmentSnapshotDto> queries;
}
@@ -0,0 +1,34 @@
package com.bsd.pjcloud.wallet.api.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
/**
* 钱包流水历史部门快照查询条件,供业务服务按稳定业务号批量还原流水发生时的部门。
*/
@Data
public class WalletTransactionDepartmentSnapshotDto {
/**
* 单个 SQL 查询组允许携带的最大业务号或钱包数量;两组 IN 参数合计仍低于 MySQL 预编译参数上限。
*/
public static final int MAX_QUERY_BATCH_SIZE = 10_000;
/** 钱包流水业务类型持久化编码,例如消费 PAYMENT 或退款 REFUND。 */
@NotBlank(message = "钱包流水业务类型不能为空")
private String bizType;
/** 待查询的钱包流水业务号集合;调用方应按单组上限拆分,避免超过数据库参数容量。 */
@NotEmpty(message = "钱包流水业务号不能为空")
@Size(max = MAX_QUERY_BATCH_SIZE, message = "单组查询的钱包流水业务号不能超过10000个")
private List<String> bizIds;
/** 业务号对应的候选钱包 ID 集合,用于命中钱包流水现有索引并缩小扫描范围。 */
@NotEmpty(message = "钱包ID不能为空")
@Size(max = MAX_QUERY_BATCH_SIZE, message = "单组查询的钱包ID不能超过10000个")
private List<Long> walletIds;
}
@@ -4,8 +4,10 @@ import com.bsd.pjcloud.common.core.constant.ServiceNameConstants;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.feign.annotation.NoToken;
import com.bsd.pjcloud.wallet.api.dto.WalletBalanceTransactionDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotBatchDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionSumAndCountDto;
import com.bsd.pjcloud.wallet.api.entity.WalletTransactionEntity;
import com.bsd.pjcloud.wallet.api.vo.WalletTransactionDepartmentSnapshotVo;
import com.bsd.pjcloud.wallet.api.vo.WalletTransactionSumAndCountVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
@@ -28,4 +30,12 @@ public interface RemoteWalletTransactionService {
@PostMapping("/wallet-transaction/sum-and-count")
R<List<WalletTransactionSumAndCountVo>> sumAndCount(@RequestBody WalletTransactionSumAndCountDto dto);
/**
* 按业务类型和数据库参数容量拆分后的查询组,批量还原流水创建时固化的历史部门。
*/
@NoToken
@PostMapping("/wallet-transaction/department-snapshots-batch")
R<List<WalletTransactionDepartmentSnapshotVo>> listDepartmentSnapshotsBatch(
@RequestBody WalletTransactionDepartmentSnapshotBatchDto dto);
}
@@ -0,0 +1,25 @@
package com.bsd.pjcloud.wallet.api.vo;
import lombok.Data;
/**
* 钱包流水历史部门快照,部门取自流水创建时固化的 {@code wallet_transaction.dept_id}。
*/
@Data
public class WalletTransactionDepartmentSnapshotVo {
/** 钱包流水业务类型持久化编码,用于批量响应区分支付与退款业务号。 */
private String bizType;
/** 钱包流水关联的稳定业务号;消费对应订单 ID,订单退款对应售后单 ID。 */
private String bizId;
/** 产生该笔流水的钱包 ID,用于调用方核对业务号与订单钱包的一致性。 */
private Long walletId;
/** 流水创建时的钱包所属部门 ID;历史异常流水可能为空。 */
private Long deptId;
/** 钱包流水实际发生金额,单位分;退款统计以该值为准,避免混入没有资金流水的售后记录。 */
private Long price;
}
@@ -12,12 +12,14 @@ import com.bsd.pjcloud.common.security.service.PjcloudUser;
import com.bsd.pjcloud.common.security.util.SecurityUtils;
import com.bsd.pjcloud.wallet.api.constant.WalletBizEnum;
import com.bsd.pjcloud.wallet.api.dto.WalletBalanceTransactionDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotBatchDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionRemarkDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionSumAndCountDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionUpdateInfoDto;
import com.bsd.pjcloud.wallet.api.entity.WalletTransactionEntity;
import com.bsd.pjcloud.wallet.api.vo.WalletCzTotalVo;
import com.bsd.pjcloud.wallet.api.vo.WalletTransactionDepartmentSnapshotVo;
import com.bsd.pjcloud.wallet.api.vo.WalletTransactionSumAndCountVo;
import com.bsd.pjcloud.wallet.api.vo.WalletTypeTotalVo;
import com.bsd.pjcloud.wallet.api.vo.transaction.WalletTransactionVo;
@@ -73,6 +75,16 @@ public class WalletTransactionController {
public R<List<WalletTransactionSumAndCountVo>> sumAndCount(@RequestBody WalletTransactionSumAndCountDto dto) {
return R.ok(walletTransactionMapper.sumAndCount(dto));
}
/**
* 按业务类型和数据库参数容量拆分后的查询组,批量还原流水创建时固化的历史部门。
*/
@Inner
@PostMapping("/department-snapshots-batch")
public R<List<WalletTransactionDepartmentSnapshotVo>> listDepartmentSnapshotsBatch(
@Valid @RequestBody WalletTransactionDepartmentSnapshotBatchDto dto) {
return R.ok(walletTransactionService.listDepartmentSnapshotsBatch(dto));
}
/* ************************************* end 远程调用 end ************************************* */
/* ************************************* start app start ************************************* */
@@ -6,10 +6,12 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionSumAndCountDto;
import com.bsd.pjcloud.wallet.api.entity.WalletTransactionEntity;
import com.bsd.pjcloud.wallet.api.vo.WalletExportCzTotalPerson;
import com.bsd.pjcloud.wallet.api.vo.WalletTransactionDepartmentSnapshotVo;
import com.bsd.pjcloud.wallet.api.vo.WalletTransactionSumAndCountVo;
import com.bsd.pjcloud.wallet.api.vo.WalletTypeCountVo;
import com.bsd.pjcloud.wallet.api.vo.recharge.WalletRechargeApplySelectCompareVo;
@@ -83,5 +85,14 @@ public interface WalletTransactionMapper extends PjcloudBaseMapper<WalletTransac
List<WalletTransactionSumAndCountVo> sumAndCount(@Param("dto") WalletTransactionSumAndCountDto dto);
/**
* 查询指定业务流水创建时固化的历史部门,结果按流水主键倒序以便调用方兼容异常重复数据。
*
* @param dto 单一业务类型及分批业务号集合
* @return 对应流水的业务号和历史部门;未生成钱包流水的业务号不会返回
*/
List<WalletTransactionDepartmentSnapshotVo> listDepartmentSnapshots(
@Param("dto") WalletTransactionDepartmentSnapshotDto dto);
List<WalletExportCzTotalPerson> exportCzTotalPerson();
}
@@ -4,11 +4,13 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.wallet.api.constant.WalletBizEnum;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotBatchDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionRemarkDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionUpdateInfoDto;
import com.bsd.pjcloud.wallet.api.entity.WalletTransactionEntity;
import com.bsd.pjcloud.wallet.api.vo.WalletCzTotalVo;
import com.bsd.pjcloud.wallet.api.vo.WalletTransactionDepartmentSnapshotVo;
import com.bsd.pjcloud.wallet.api.vo.WalletTypeTotalVo;
import com.bsd.pjcloud.wallet.api.vo.transaction.WalletTransactionVo;
import jakarta.servlet.http.HttpServletResponse;
@@ -79,4 +81,12 @@ public interface WalletTransactionService extends IService<WalletTransactionEnti
* 后台充值统计按照个人(导出)
*/
void exportCzTotalPerson(HttpServletResponse response, WalletTransactionDto dto, Object o);
/**
* 在一次钱包服务调用中依次执行受控历史部门查询组,并合并支付、退款快照结果。
*
* @param dto 按业务类型和数据库参数容量拆分后的查询组
* @return 所有查询组命中的流水历史部门快照
*/
List<WalletTransactionDepartmentSnapshotVo> listDepartmentSnapshotsBatch(WalletTransactionDepartmentSnapshotBatchDto dto);
}
@@ -31,6 +31,8 @@ import com.bsd.pjcloud.wallet.api.constant.WalletTypeEnum;
import com.bsd.pjcloud.wallet.api.constant.mch.WalletMchSupType;
import com.bsd.pjcloud.wallet.api.dto.MchManageDto;
import com.bsd.pjcloud.wallet.api.dto.WalletManageDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotBatchDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDepartmentSnapshotDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionRemarkDto;
import com.bsd.pjcloud.wallet.api.dto.WalletTransactionUpdateInfoDto;
@@ -107,6 +109,28 @@ public class WalletTransactionServiceImpl extends ServiceImpl<WalletTransactionM
return baseMapper.insert(entity) > 0;
}
/**
* 在一次钱包服务调用中依次执行受控历史部门查询组,并合并支付、退款快照结果。
*
* <p>分组边界留在钱包服务内部,Hardware 不再为每一万条明细承担一次串行网络往返;
* 每组仍使用现有 wallet_id 索引缩小扫描范围,且不会生成超过数据库参数容量的 SQL。</p>
*
* @param dto 按业务类型和数据库参数容量拆分后的查询组
* @return 所有查询组命中的流水历史部门快照
*/
@Override
public List<WalletTransactionDepartmentSnapshotVo> listDepartmentSnapshotsBatch(
WalletTransactionDepartmentSnapshotBatchDto dto) {
List<WalletTransactionDepartmentSnapshotVo> result = new ArrayList<>();
for (WalletTransactionDepartmentSnapshotDto query : dto.getQueries()) {
List<WalletTransactionDepartmentSnapshotVo> snapshots = baseMapper.listDepartmentSnapshots(query);
if (ObjectUtil.isNotEmpty(snapshots)) {
result.addAll(snapshots);
}
}
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
// @CacheEvict(value = CacheConstants.WALLET_DETAIL, key = "#walletId")
@@ -164,7 +164,7 @@
from wallet_transaction a
left join wallet_manage m on m.id = a.wallet_id
<where>
a.del_flag = 0 and m.dept_id is not null
a.del_flag = 0 and a.dept_id is not null
<if test="dto.walletId != null">
a.wallet_id = ${dto.walletId}
</if>
@@ -194,13 +194,13 @@
<if test="dto.deptIds != null and dto.deptIds.size() != 0">
<choose>
<when test="dto.isInclude != null and dto.isInclude == true">
and m.dept_id IN
and a.dept_id IN
</when>
<when test="dto.isInclude != null and dto.isInclude == false">
and m.dept_id NOT IN
and a.dept_id NOT IN
</when>
<otherwise>
and m.dept_id IN
and a.dept_id IN
</otherwise>
</choose>
<foreach collection="dto.deptIds" item="v" open="(" close=")" separator=",">
@@ -214,6 +214,28 @@
group by a.dept_id
</select>
<select id="listDepartmentSnapshots"
resultType="com.bsd.pjcloud.wallet.api.vo.WalletTransactionDepartmentSnapshotVo">
select a.biz_type as bizType,
a.biz_id as bizId,
a.wallet_id as walletId,
a.dept_id as deptId,
a.price as price
from wallet_transaction a
where a.del_flag = '0'
and a.dept_id is not null
and a.biz_type = #{dto.bizType}
and a.wallet_id in
<foreach collection="dto.walletIds" item="walletId" open="(" close=")" separator=",">
#{walletId}
</foreach>
and a.biz_id in
<foreach collection="dto.bizIds" item="bizId" open="(" close=")" separator=",">
#{bizId}
</foreach>
order by a.id desc
</select>
<select id="sumAndCountUser" resultType="com.bsd.pjcloud.wallet.api.vo.WalletTransactionSumAndCountVo">
select a.dept_id,
sum(a.price) as sum,