From 0c19eff578357169fc456ae50432a34701494ccd Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Tue, 1 Sep 2026 19:15:22 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E6=94=B9=E9=83=A8?= =?UTF-8?q?=E9=97=A8=E7=BB=9F=E8=AE=A1=E7=9A=84=E6=B5=81=E6=B0=B4=E9=87=91?= =?UTF-8?q?=E9=A2=9D=E5=92=8C=E9=80=80=E6=AC=BE=E9=87=91=E9=A2=9D=E4=B8=8D?= =?UTF-8?q?=E5=87=86=E7=A1=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hard/mapper/order/HardOrderMapper.java | 17 +- .../HardOrderDepartmentStatisticsDetail.java | 25 +++ .../impl/order/HardOrderTotalServiceImpl.java | 118 +++++++++++- .../mapper/order/HardOrderMapper.xml | 52 ++---- .../order/HardOrderTotalServiceImplTest.java | 170 ++++++++++++++++++ ...alletTransactionDepartmentSnapshotDto.java | 32 ++++ .../feign/RemoteWalletTransactionService.java | 13 ++ ...WalletTransactionDepartmentSnapshotVo.java | 19 ++ .../WalletTransactionController.java | 15 ++ .../mapper/WalletTransactionMapper.java | 11 ++ .../mapper/WalletTransactionMapper.xml | 20 +++ 11 files changed, 449 insertions(+), 43 deletions(-) create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/model/HardOrderDepartmentStatisticsDetail.java create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/test/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderTotalServiceImplTest.java create mode 100644 pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/WalletTransactionDepartmentSnapshotDto.java create mode 100644 pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/vo/WalletTransactionDepartmentSnapshotVo.java diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/HardOrderMapper.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/HardOrderMapper.java index 6c05ef4da..0135739a0 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/HardOrderMapper.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/HardOrderMapper.java @@ -16,6 +16,7 @@ import com.bsd.pjcloud.hard.api.entity.order.HardOrderEntity; import com.bsd.pjcloud.hard.api.entity.order.HardParentCarEntity; 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.bsd.pjcloud.hard.mapper.order.model.HardOrderStatisticsAggregate; import com.github.yulichang.wrapper.MPJLambdaWrapper; @@ -212,9 +213,21 @@ public interface HardOrderMapper extends PjcloudBaseMapper { List listOrerRefundPayType(@Param("dto") HardOrderDto refundDto); - List sumAndCount(@Param("dto") HardOrderSumAndCountDto dto); + /** + * 查询符合订单状态、商户和创建时间条件的逐笔支付统计明细,不提前按订单部门过滤或汇总。 + * + * @param dto 支付订单状态、商户和创建时间条件 + * @return 每笔订单的业务号、钱包、订单部门、支付金额和固定一次计数 + */ + List listPayStatisticsDetails(@Param("dto") HardOrderSumAndCountDto dto); - List refundSumAndCount(@Param("dto") HardOrderSumAndCountDto refundDto); + /** + * 查询符合售后状态、商户和完成时间条件的逐笔退款统计明细,不提前按原订单部门过滤或汇总。 + * + * @param refundDto 退款对应的订单状态、售后状态、商户和完成时间条件 + * @return 每笔售后的业务号、原订单钱包、订单部门、退款金额和固定一次计数 + */ + List listRefundStatisticsDetails(@Param("dto") HardOrderSumAndCountDto refundDto); List sumAndCountTotalByHourType(@Param("dto") HardOrderSumAndCountDto dto); diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/model/HardOrderDepartmentStatisticsDetail.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/model/HardOrderDepartmentStatisticsDetail.java new file mode 100644 index 000000000..38d1f815a --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/mapper/order/model/HardOrderDepartmentStatisticsDetail.java @@ -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; +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderTotalServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderTotalServiceImpl.java index acc160139..ba4143f26 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderTotalServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderTotalServiceImpl.java @@ -28,14 +28,17 @@ 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.wallet.api.constant.WalletBizEnum; +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 lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -49,10 +52,13 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.temporal.TemporalAdjusters; import java.util.ArrayList; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; @Validated @Slf4j @@ -426,8 +432,10 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService { List walletOnceReduceList = queryWalletStatistics( buildWalletStatisticsDto(dto, WalletBizEnum.UPDATE.getCode(), WalletTransactionSumAndCountDto.TYPE_EXPENSE)); - List payList = hardOrderMapper.sumAndCount(buildOrderStatisticsDto(dto, false)); - List refundList = hardOrderMapper.refundSumAndCount(buildOrderStatisticsDto(dto, true)); + List payList = aggregateByTransactionDepartment( + hardOrderMapper.listPayStatisticsDetails(buildOrderStatisticsDto(dto, false)), WalletBizEnum.PAYMENT, dto); + List refundList = aggregateByTransactionDepartment( + hardOrderMapper.listRefundStatisticsDetails(buildOrderStatisticsDto(dto, true)), WalletBizEnum.REFUND, dto); Map walletBatchMap = indexWalletStatistics(walletBatchList); Map walletReduceMap = indexWalletStatistics(walletReduceList); @@ -558,6 +566,112 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService { return orderDto; } + /** + * 将订单或售后明细按钱包流水发生时保存的部门重新归集,并在重新归属后应用页面部门条件。 + * + *

现金、第三方支付或历史异常记录没有对应钱包流水时,继续使用订单创建时保存的部门快照, + * 避免为了统一钱包口径而丢失原有非钱包交易能力。

+ * + * @param details 符合订单状态、商户和时间条件的支付或售后明细 + * @param bizType 支付或退款的钱包流水业务类型 + * @param dto 页面部门包含或排除条件 + * @return 按最终历史部门归集后的金额和次数 + */ + private List aggregateByTransactionDepartment( + List details, WalletBizEnum bizType, HardDeptTotalDto dto) { + if (ObjectUtil.isEmpty(details)) { + return List.of(); + } + Map transactionDepartmentMap = queryTransactionDepartmentMap(details, bizType); + Set departmentFilter = ObjectUtil.isEmpty(dto.getDeptIds()) + ? Set.of() : new HashSet<>(dto.getDeptIds()); + Map aggregateMap = new LinkedHashMap<>(); + for (HardOrderDepartmentStatisticsDetail detail : details) { + if (detail == null) { + continue; + } + Long departmentId = transactionDepartmentMap.getOrDefault(detail.getBizId(), detail.getDeptId()); + if (departmentId == null || !matchesDepartmentFilter(departmentId, departmentFilter, dto.getIsInclude())) { + continue; + } + HardOrderSumAndCountVo aggregate = aggregateMap.computeIfAbsent(departmentId, key -> { + HardOrderSumAndCountVo value = new HardOrderSumAndCountVo(); + value.setDeptId(key); + value.setSum(0L); + value.setCount(0L); + return value; + }); + aggregate.setSum(Math.addExact(aggregate.getSum(), ObjectUtil.defaultIfNull(detail.getSum(), 0L))); + aggregate.setCount(Math.addExact(aggregate.getCount(), ObjectUtil.defaultIfNull(detail.getCount(), 0L))); + } + return new ArrayList<>(aggregateMap.values()); + } + + /** + * 分批查询订单或售后业务号对应的钱包流水历史部门;远程失败时终止统计,禁止静默回退造成混合口径。 + * + * @param details 支付订单或退款售后明细 + * @param bizType 钱包支付或退款业务类型 + * @return 以订单 ID 或售后单 ID 字符串为键的流水历史部门 + * @throws IllegalStateException 钱包历史部门查询失败时抛出 + */ + private Map queryTransactionDepartmentMap( + List details, WalletBizEnum bizType) { + List walletDetails = details.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 expectedWalletMap = walletDetails.stream().collect(Collectors.toMap( + HardOrderDepartmentStatisticsDetail::getBizId, + HardOrderDepartmentStatisticsDetail::getWalletId)); + Map resultMap = 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 batchDetails = walletDetails.subList(start, end); + WalletTransactionDepartmentSnapshotDto snapshotDto = new WalletTransactionDepartmentSnapshotDto(); + snapshotDto.setBizType(bizType.getCode()); + snapshotDto.setBizIds(batchDetails.stream().map(HardOrderDepartmentStatisticsDetail::getBizId).toList()); + snapshotDto.setWalletIds(batchDetails.stream().map(HardOrderDepartmentStatisticsDetail::getWalletId) + .distinct().toList()); + R> response = + remoteWalletTransactionService.listDepartmentSnapshots(snapshotDto); + if (response == null || !response.isOk()) { + throw new IllegalStateException("查询钱包流水历史部门失败"); + } + if (ObjectUtil.isEmpty(response.getData())) { + continue; + } + for (WalletTransactionDepartmentSnapshotVo snapshot : response.getData()) { + if (snapshot != null && ObjectUtil.isAllNotEmpty( + snapshot.getBizId(), snapshot.getWalletId(), snapshot.getDeptId()) + && ObjectUtil.equals(expectedWalletMap.get(snapshot.getBizId()), snapshot.getWalletId())) { + // 钱包服务按流水主键倒序返回,异常重复业务号保留最新一笔历史快照。 + resultMap.putIfAbsent(snapshot.getBizId(), snapshot.getDeptId()); + } + } + } + return resultMap; + } + + /** + * 判断重新归属后的历史部门是否命中页面包含或排除条件。 + * + * @param departmentId 流水历史部门或非钱包订单部门 + * @param departmentFilter 页面部门主键集合 + * @param isInclude true 表示包含,false 表示排除,空值兼容旧接口的包含语义 + * @return 当前历史部门是否参与统计 + */ + private boolean matchesDepartmentFilter(Long departmentId, Set departmentFilter, Boolean isInclude) { + if (departmentFilter.isEmpty()) { + return true; + } + boolean contains = departmentFilter.contains(departmentId); + return Boolean.FALSE.equals(isInclude) ? !contains : contains; + } + /** * 将钱包统计按部门建立索引,避免每个部门重复遍历全部统计结果。 * diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/mapper/order/HardOrderMapper.xml b/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/mapper/order/HardOrderMapper.xml index 6692a5d70..d2d935d88 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/mapper/order/HardOrderMapper.xml +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/resources/mapper/order/HardOrderMapper.xml @@ -813,11 +813,14 @@ group by a.pay_type - select + cast(a.id as char) as bizId, + a.wallet_id as walletId, a.user_dept_id as deptId, - sum(a.pay_price) as sum, - count(0) as count + a.pay_price as sum, + 1 as count from hard_order a a.del_flag = 0 @@ -830,22 +833,6 @@ #{v} - - - - and a.user_dept_id IN - - - and a.user_dept_id NOT IN - - - and a.user_dept_id IN - - - - #{v} - - and a.create_time between #{dto.createTimeList[0]} and #{dto.createTimeList[1]} @@ -856,11 +843,15 @@ - group by a.user_dept_id - + select cast(a.id as char) as bizId, + o.wallet_id as walletId, + o.user_dept_id as deptId, + ifnull(a.refund_price, 0) as sum, + 1 as count from hard_after_sale a inner join hard_order o on o.id = a.order_id and o.del_flag = '0' @@ -871,22 +862,6 @@ #{v} - - - - and o.user_dept_id IN - - - and o.user_dept_id NOT IN - - - and o.user_dept_id IN - - - - #{v} - - and o.create_time between #{dto.createTimeList[0]} and #{dto.createTimeList[1]} @@ -906,7 +881,6 @@ - group by o.user_dept_id + +