From cb74d0563fdc5694da7b0609c1bcdd437be7e2da Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Thu, 27 Aug 2026 21:51:19 +0800 Subject: [PATCH] 11 --- .../HardCanteenUserApptDishStatisticsVo.java | 4 + .../order/HardOrderTotalController.java | 71 ++- .../HardCanteenApptTotalServiceImpl.java | 8 + .../impl/order/HardOrderTotalServiceImpl.java | 494 +++++++++--------- .../mapper/order/HardOrderMapper.xml | 5 +- .../wallet/api/feign/RemoteMchService.java | 130 +++++ .../controller/mch/MchManageController.java | 16 + .../wallet/service/MchManageService.java | 9 + .../impl/mch/MchManageServiceImpl.java | 27 + 9 files changed, 527 insertions(+), 237 deletions(-) diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardCanteenUserApptDishStatisticsVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardCanteenUserApptDishStatisticsVo.java index cff3371a7..581e8637d 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardCanteenUserApptDishStatisticsVo.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardCanteenUserApptDishStatisticsVo.java @@ -21,6 +21,10 @@ public class HardCanteenUserApptDishStatisticsVo { @Schema(description = "菜品名称") private String productName; + /** 当前同餐段、同名菜品统计项实际涉及的商品 ID;同名商品可能因重复建档包含多个 ID,历史空值不写入集合。 */ + @Schema(description = "商品ID集合") + private List productIds = new ArrayList<>(); + /** 下单时固化的菜品图片地址;历史订单未保存图片时为空。 */ @Schema(description = "菜品图片地址") private String productPic; diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderTotalController.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderTotalController.java index db2cebc91..6451c2a53 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderTotalController.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderTotalController.java @@ -47,8 +47,10 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; @RestController @RequiredArgsConstructor @@ -151,12 +153,70 @@ public class HardOrderTotalController { return R.ok(hardOrderTotalService.listPayType(dto)); } + /** + * 查询当前用户所属食堂商户范围内的部门交易统计。 + * + * @param dto 时间、钱包类型及部门树筛选条件;部门条件会与当前用户可管理的食堂部门取交集 + * @return 当前用户有权查看的部门交易统计,无可见食堂或部门时返回空列表 + */ @PostMapping("/page-list-dept") @Operation(summary = "部门交易记录") public R> pageListDept(@RequestBody HardDeptTotalDto dto) { + if (!applyCanteenDepartmentScope(dto)) { + return R.ok(List.of()); + } return R.ok(hardOrderTotalService.pageListDept(dto)); } + /** + * 将部门统计条件限制到当前用户可管理的食堂商户所属部门。 + * + *

钱包服务根据当前用户主键和食堂商户类型一次性返回其所属食堂覆盖的用户部门。页面传入的 + * 包含或排除条件只在该权限部门集合内生效,最终统一改写为明确的包含集合,避免空集合或排除 + * 条件导致底层统计退化为全量查询。

+ * + * @param dto 待收敛的部门统计条件,方法会写入最终允许查询的部门 ID 集合 + * @return 是否仍存在可查询部门;false 表示调用方必须直接返回空数据 + */ + private boolean applyCanteenDepartmentScope(HardDeptTotalDto dto) { + PjcloudUser user = SecurityUtils.getUser(); + if (user == null || user.getId() == null) { + return false; + } + + R> departmentResponse = mchService.getSelfMchUserDeptIdListByUserId( + user.getId(), List.of(MerchantTypeEnum.CANTEEN)); + if (departmentResponse == null || !departmentResponse.isOk() || ObjectUtil.isEmpty(departmentResponse.getData())) { + return false; + } + List allowedDeptIds = departmentResponse.getData().stream() + .filter(id -> id != null && id > 0) + .distinct() + .toList(); + if (ObjectUtil.isEmpty(allowedDeptIds)) { + return false; + } + + List requestedDeptIds = ObjectUtil.isNotEmpty(dto.getDeptIds()) + ? dto.getDeptIds() + : ObjectUtil.isNotEmpty(dto.getDeptId()) ? List.of(dto.getDeptId()) : List.of(); + List scopedDeptIds = allowedDeptIds; + if (ObjectUtil.isNotEmpty(requestedDeptIds)) { + Set requestedDeptIdSet = new HashSet<>(requestedDeptIds); + scopedDeptIds = Boolean.FALSE.equals(dto.getIsInclude()) + ? allowedDeptIds.stream().filter(id -> !requestedDeptIdSet.contains(id)).toList() + : allowedDeptIds.stream().filter(requestedDeptIdSet::contains).toList(); + } + if (ObjectUtil.isEmpty(scopedDeptIds)) { + return false; + } + + dto.setDeptId(null); + dto.setDeptIds(scopedDeptIds); + dto.setIsInclude(true); + return true; + } + @Operation(summary = "设备交易记录导出", description = "设备交易记录导出") @PostMapping("/export-sb") @SysLog("设备交易记录导出") @@ -465,12 +525,21 @@ public class HardOrderTotalController { } } + /** + * 导出当前用户所属食堂商户范围内的部门交易统计。 + * + * @param response Excel 文件响应 + * @param dto 时间、钱包类型及部门树筛选条件;部门条件会与当前用户可管理的食堂部门取交集 + * @throws IOException 写入 Excel 响应失败时抛出 + */ @Operation(summary = "部门交易记录导出", description = "部门交易记录导出") @PostMapping("/export-dept") @SysLog("部门交易记录导出") @PreAuthorize("@pms.hasPermission('hard_hardTotal_export_dept')") public void exportDept(HttpServletResponse response, @RequestBody HardDeptTotalDto dto) throws IOException { - List list = hardOrderTotalService.pageListDept(dto); + List list = applyCanteenDepartmentScope(dto) + ? hardOrderTotalService.pageListDept(dto) + : List.of(); try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(ResourceUtil.getStream("file/部门交易金额汇总.xlsx")).build()) { WriteSheet writeSheet = EasyExcel.writerSheet().build(); FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardCanteenApptTotalServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardCanteenApptTotalServiceImpl.java index f26b5763c..9bdd41165 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardCanteenApptTotalServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardCanteenApptTotalServiceImpl.java @@ -43,6 +43,7 @@ import java.time.LocalTime; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -1122,6 +1123,9 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ String dishKey = hourType + "\u0000" + productName; DishStatisticsAccumulator dish = dishes.computeIfAbsent(dishKey, key -> new DishStatisticsAccumulator(hourType, productName, car.getProductPic())); + if (car.getProductId() != null) { + dish.productIds.add(car.getProductId()); + } // 同餐段同名菜品合并时优先保留首个非空快照图片,兼容早期订单未保存图片的情况。 if (StrUtil.isBlank(dish.productPic) && StrUtil.isNotBlank(car.getProductPic())) { dish.productPic = car.getProductPic(); @@ -1243,6 +1247,9 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ /** 下单时固化的菜品名称。 */ private final String productName; + /** 同餐段、同名菜品实际涉及的商品 ID,保留首次出现顺序并去重,便于排查同名商品重复建档。 */ + private final Set productIds = new LinkedHashSet<>(); + /** 下单时固化的菜品图片地址;同名菜品聚合时保留首个非空值。 */ private String productPic; @@ -1276,6 +1283,7 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ HardCanteenUserApptDishStatisticsVo vo = new HardCanteenUserApptDishStatisticsVo(); vo.setHourType(hourType); vo.setProductName(productName); + vo.setProductIds(new ArrayList<>(productIds)); vo.setProductPic(productPic); vo.setProductCount(productCount); vo.setOrderCount((long) orderIds.size()); 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 735eaa76c..d41437794 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 @@ -9,6 +9,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; @@ -47,6 +48,8 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.temporal.TemporalAdjusters; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -194,7 +197,10 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService { } /** - * 商户交易记录 + * 汇总商户交易记录,并补全商户及商户部门展示信息。 + * + * @param dto 商户、部门、用餐时段及交易时间等统计条件 + * @return 按商户汇总的流水、退款和净消费数据;历史部门不存在时部门名为空 */ @Override public List listCs(HardOrderDto dto) { @@ -202,85 +208,88 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService { if (ObjectUtil.isEmpty(dto.getMchTypeCodes())) { CurrentUserHardTypeInfoDto hardTypeInfo = hardManagerService.getCurrentUserHardTypeInfo(user); dto.setMchTypeCodes(hardTypeInfo.getHardTypeList()); - } else { - dto.setMchTypeCodes(dto.getMchTypeCodes()); } - // 查询总流水 + + // 流水与退款的业务发生时间不同,必须分别查询后再按商户合并,避免直接关联造成一笔订单被多笔售后重复放大。 HardOrderDto totalDto = BeanUtil.toBean(dto, HardOrderDto.class); totalDto.setOrderStatusCodes(HardOrderStatusEnum.getPayPrice()); DataScope totalDataScope = DataScope.of(); totalDataScope.setScopeDeptName("merchant_dept_id"); List totalList = hardOrderMapper.listCs(totalDto, totalDataScope); - // 扣款总流水 + HardOrderDto refundDto = BeanUtil.toBean(dto, HardOrderDto.class); refundDto.setPayTimeList(null); refundDto.setRefundTimeList(dto.getPayTimeList()); - refundDto.setOrderStatusCodes(HardOrderStatusEnum.getPayErrorCodes()); - refundDto.setRefundStatusCodes(List.of(HardSaleStatusEnum.COMPLETE.getStatus())); + // refundStatusCodes 属于订单退款状态,售后完成状态必须使用 saleStatusCodes,二者混用会把有效退款全部过滤掉。 + refundDto.setRefundStatusCodes(null); + refundDto.setSaleStatusCodes(List.of(HardSaleStatusEnum.COMPLETE.getStatus())); + // 部分退款后订单仍是已支付状态,退款统计不能只查询整单退款状态的订单。 + refundDto.setOrderStatusCodes(HardOrderStatusEnum.getPayPrice()); DataScope refundDataScope = DataScope.of(); refundDataScope.setScopeDeptName("merchant_dept_id"); - List refundList = hardOrderMapper.listRefundCs(refundDto, refundDataScope); - // 有退款没有流水的情况 - if (ObjectUtil.isAllNotEmpty(totalList, refundList)) { - List totalIds = totalList.stream().map(HardOrderCsVo::getMerchantId).toList(); - for (HardOrderCsVo refund : refundList) { - if (!totalIds.contains(refund.getMerchantId())) { - HardOrderCsVo entity = BeanUtils.toBean(refund, HardOrderCsVo.class); - entity.setTotalPayPrice(BigDecimal.ZERO); - entity.setTotalPayCount(BigDecimal.ZERO); - totalList.add(entity); - } - } - } - for (HardOrderCsVo v : totalList) { - // 退款 - if (ObjectUtil.isNotEmpty(refundList)) { - refundList.stream().filter(i -> ObjectUtil.equals(i.getMerchantId(), v.getMerchantId())).findFirst().ifPresent(i -> { - v.setTotalRefundPrice(i.getTotalPayPrice()); - v.setTotalRefundCount(i.getTotalPayCount()); - }); - } - // 消费净金额 - if (ObjectUtil.isNotEmpty(v.getTotalPayPrice())) { - BigDecimal refundPrice = BigDecimal.ZERO; - if (ObjectUtil.isNotEmpty(v.getTotalRefundPrice())) { - refundPrice = v.getTotalRefundPrice(); - } - v.setTotalProfitPrice(v.getTotalPayPrice().subtract(refundPrice)); - } - // 消费净次数 - if (ObjectUtil.isNotEmpty(v.getTotalPayCount())) { - BigDecimal refundCount = BigDecimal.ZERO; - if (ObjectUtil.isNotEmpty(v.getTotalRefundCount())) { - refundCount = v.getTotalRefundCount(); - } - v.setTotalProfitCount(v.getTotalPayCount().subtract(refundCount)); - } - } - // 聚合一下数据 + + // 使用有序 Map 一次完成合并,同时保留原流水排序,并覆盖筛选期内只有退款、没有新支付的商户。 + Map statisticsMap = new LinkedHashMap<>(); if (ObjectUtil.isNotEmpty(totalList)) { + for (HardOrderCsVo total : totalList) { + statisticsMap.put(total.getMerchantId(), total); + } + } + if (ObjectUtil.isNotEmpty(refundList)) { + for (HardOrderCsVo refund : refundList) { + HardOrderCsVo statistics = statisticsMap.computeIfAbsent(refund.getMerchantId(), merchantId -> { + HardOrderCsVo refundOnly = BeanUtils.toBean(refund, HardOrderCsVo.class); + refundOnly.setTotalPayPrice(BigDecimal.ZERO); + refundOnly.setTotalPayCount(BigDecimal.ZERO); + return refundOnly; + }); + statistics.setTotalRefundPrice(ObjectUtil.defaultIfNull(refund.getTotalPayPrice(), BigDecimal.ZERO)); + statistics.setTotalRefundCount(ObjectUtil.defaultIfNull(refund.getTotalPayCount(), BigDecimal.ZERO)); + } + } + + List statisticsList = new ArrayList<>(statisticsMap.values()); + for (HardOrderCsVo statistics : statisticsList) { + BigDecimal totalPayPrice = ObjectUtil.defaultIfNull(statistics.getTotalPayPrice(), BigDecimal.ZERO); + BigDecimal totalPayCount = ObjectUtil.defaultIfNull(statistics.getTotalPayCount(), BigDecimal.ZERO); + BigDecimal totalRefundPrice = ObjectUtil.defaultIfNull(statistics.getTotalRefundPrice(), BigDecimal.ZERO); + BigDecimal totalRefundCount = ObjectUtil.defaultIfNull(statistics.getTotalRefundCount(), BigDecimal.ZERO); + statistics.setTotalPayPrice(totalPayPrice); + statistics.setTotalPayCount(totalPayCount); + statistics.setTotalRefundPrice(totalRefundPrice); + statistics.setTotalRefundCount(totalRefundCount); + statistics.setTotalProfitPrice(totalPayPrice.subtract(totalRefundPrice)); + statistics.setTotalProfitCount(totalPayCount.subtract(totalRefundCount)); + } + + if (ObjectUtil.isNotEmpty(statisticsList)) { // 商户 - List mchIds = totalList.stream().map(HardOrderCsVo::getMerchantId).distinct().toList(); + List mchIds = statisticsList.stream().map(HardOrderCsVo::getMerchantId).filter(ObjectUtil::isNotEmpty).distinct().toList(); if (ObjectUtil.isNotEmpty(mchIds)) { - List mchList = remoteMchService.getByIds(mchIds).getData(); - if (ObjectUtil.isNotEmpty(mchList)) { - WalletDataUtil.convertMchList(totalList, CollectionUtils.convertMap(mchList, MchManageVo::getId)); + R> mchResponse = remoteMchService.getByIds(mchIds); + if (ObjectUtil.isNotEmpty(mchResponse) && mchResponse.isOk() && ObjectUtil.isNotEmpty(mchResponse.getData())) { + WalletDataUtil.convertMchList(statisticsList, CollectionUtils.convertMap(mchResponse.getData(), MchManageVo::getId)); } } // 商户部门 - List mchDeptIds = totalList.stream().map(HardOrderCsVo::getMerchantDeptId).distinct().toList(); + List mchDeptIds = statisticsList.stream().map(HardOrderCsVo::getMerchantDeptId).filter(ObjectUtil::isNotEmpty).distinct().toList(); if (ObjectUtil.isNotEmpty(mchDeptIds)) { - Map deptMap = remoteDeptService.getDeptByDeptIds(mchDeptIds).getData(); - if (ObjectUtil.isNotEmpty(deptMap)) { - for (HardOrderCsVo v : totalList) { + R> deptResponse = remoteDeptService.getDeptByDeptIds(mchDeptIds); + if (ObjectUtil.isNotEmpty(deptResponse) && deptResponse.isOk() && ObjectUtil.isNotEmpty(deptResponse.getData())) { + Map deptMap = deptResponse.getData(); + for (HardOrderCsVo v : statisticsList) { if (ObjectUtil.isEmpty(v.getMerchantDeptId())) continue; - v.setMerchantDeptName(deptMap.get(v.getMerchantDeptId()).getName()); + SysDept dept = deptMap.get(v.getMerchantDeptId()); + // 历史订单关联的商户部门可能已删除或已不在当前租户可见范围,不能因名称缺失阻断整页统计。 + if (ObjectUtil.isNotEmpty(dept)) { + v.setMerchantDeptName(dept.getName()); + } } } } } - return totalList; + return statisticsList; } @Override @@ -393,11 +402,88 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService { } /** - * 部门交易记录 + * 按用户部门汇总钱包操作、硬件消费、已完成退款和净消费数据。 + * + * @param dto 当前用户权限范围内的部门、时间和钱包类型筛选条件 + * @return 部门交易统计;筛选范围内没有部门时返回空列表 + * @throws IllegalStateException 部门或钱包远程统计调用失败时抛出,避免返回不完整报表 */ @Override public List pageListDept(HardDeptTotalDto dto) { - // 查询部门 + List deptList = queryDepartmentList(dto); + if (ObjectUtil.isEmpty(deptList)) { + return List.of(); + } + + List walletBatchList = queryWalletStatistics( + buildWalletStatisticsDto(dto, WalletBizEnum.BATCH.getCode(), WalletTransactionSumAndCountDto.TYPE_INCOME)); + List walletReduceList = queryWalletStatistics( + buildWalletStatisticsDto(dto, WalletBizEnum.BATCH.getCode(), WalletTransactionSumAndCountDto.TYPE_EXPENSE)); + List walletReturnCanteenList = queryWalletStatistics( + buildWalletStatisticsDto(dto, WalletBizEnum.RETURN_CANTEEN.getCode(), null)); + List walletOnceBatchList = queryWalletStatistics( + buildWalletStatisticsDto(dto, WalletBizEnum.UPDATE.getCode(), WalletTransactionSumAndCountDto.TYPE_INCOME)); + 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)); + + Map walletBatchMap = indexWalletStatistics(walletBatchList); + Map walletReduceMap = indexWalletStatistics(walletReduceList); + Map walletReturnCanteenMap = indexWalletStatistics(walletReturnCanteenList); + Map walletOnceBatchMap = indexWalletStatistics(walletOnceBatchList); + Map walletOnceReduceMap = indexWalletStatistics(walletOnceReduceList); + Map payMap = indexOrderStatistics(payList); + Map refundMap = indexOrderStatistics(refundList); + + List 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()), refundMap.get(dept.getDeptId())); + resultList.add(statistics); + } + return resultList; + } + + /** + * 查询统计结果需要展示的部门,并校验 UPMS 远程调用结果。 + * + * @param dto 部门包含或排除筛选条件 + * @return 匹配的部门列表,远程响应数据为空时返回空列表 + * @throws IllegalStateException UPMS 部门远程调用失败时抛出 + */ + private List queryDepartmentList(HardDeptTotalDto dto) { DeptAllListDto deptDto = new DeptAllListDto(); deptDto.setIsInclude(dto.getIsInclude()); if (ObjectUtil.isNotEmpty(dto.getDeptId())) { @@ -407,186 +493,126 @@ public class HardOrderTotalServiceImpl implements HardOrderTotalService { deptDto.setDeptId(null); deptDto.setDeptIds(dto.getDeptIds()); } - List deptList = remoteDeptService.getInnerAllDept(deptDto).getData(); - if (ObjectUtil.isEmpty(deptList)) return null; - // List deptIds = deptList.stream().map(SysDept::getDeptId).filter(ObjectUtil::isNotEmpty).distinct().toList(); - List resultList = deptList.stream().map(v -> { - HardDeptTotalVo item = new HardDeptTotalVo(); - item.setDeptId(v.getDeptId()); - item.setDeptName(v.getName()); - return item; - }).toList(); - // 查询钱包批量充值流水 - CompletableFuture> walletBatchFuture = CompletableFuture.supplyAsync(() -> { - WalletTransactionSumAndCountDto walletDto = new WalletTransactionSumAndCountDto(); - walletDto.setDeptIds(dto.getDeptIds()); - walletDto.setIsInclude(dto.getIsInclude()); - walletDto.setBizTypes(List.of(WalletBizEnum.BATCH.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> walletReduceFuture = CompletableFuture.supplyAsync(() -> { - WalletTransactionSumAndCountDto walletDto = new WalletTransactionSumAndCountDto(); - walletDto.setDeptIds(dto.getDeptIds()); - walletDto.setIsInclude(dto.getIsInclude()); - 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> walletRetrunCanteenFuture = CompletableFuture.supplyAsync(() -> { - WalletTransactionSumAndCountDto walletDto = new WalletTransactionSumAndCountDto(); - walletDto.setDeptIds(dto.getDeptIds()); - walletDto.setIsInclude(dto.getIsInclude()); - 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> walletOnceBatchFuture = CompletableFuture.supplyAsync(() -> { - WalletTransactionSumAndCountDto walletDto = new WalletTransactionSumAndCountDto(); - walletDto.setDeptIds(dto.getDeptIds()); - walletDto.setIsInclude(dto.getIsInclude()); - walletDto.setBizTypes(List.of(WalletBizEnum.UPDATE.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> walletOnceReduceFuture = CompletableFuture.supplyAsync(() -> { - WalletTransactionSumAndCountDto walletDto = new WalletTransactionSumAndCountDto(); - walletDto.setDeptIds(dto.getDeptIds()); - walletDto.setIsInclude(dto.getIsInclude()); - walletDto.setBizTypes(List.of(WalletBizEnum.UPDATE.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> payFuture = CompletableFuture.supplyAsync(() -> { - HardOrderSumAndCountDto payDto = new HardOrderSumAndCountDto(); - payDto.setUserDeptIds(dto.getDeptIds()); - payDto.setIsInclude(dto.getIsInclude()); - payDto.setOrderStatusCodes(HardOrderStatusEnum.getPayPrice()); - if (ObjectUtil.isNotEmpty(dto.getCreateTimeList())) { - payDto.setCreateTimeList(dto.getCreateTimeList()); - } - if (ObjectUtil.isNotEmpty(dto.getWalletTypeId())) { - payDto.setWalletTypeId(dto.getWalletTypeId()); - } - return hardOrderMapper.sumAndCount(payDto); - }); - // 查询订单退款流水 - CompletableFuture> 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())); - if (ObjectUtil.isNotEmpty(dto.getCreateTimeList())) { - refundDto.setRefundTimeList(dto.getCreateTimeList()); - } - if (ObjectUtil.isNotEmpty(dto.getWalletTypeId())) { - refundDto.setWalletTypeId(dto.getWalletTypeId()); - } - return hardOrderMapper.refundSumAndCount(refundDto); - }); - CompletableFuture allFutures = CompletableFuture.allOf(walletBatchFuture, walletReduceFuture, payFuture, refundFuture, walletOnceBatchFuture, walletOnceReduceFuture); - allFutures.join(); - List walletBatchList = walletBatchFuture.join(); - List walletReduceList = walletReduceFuture.join(); - List walletRetrunCanteenList = walletRetrunCanteenFuture.join(); - List walletOnceBatchList = walletOnceBatchFuture.join(); - List walletOnceReduceList = walletOnceReduceFuture.join(); - List payList = payFuture.join(); - List 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()); - }); + R> response = remoteDeptService.getInnerAllDept(deptDto); + if (response == null || !response.isOk()) { + throw new IllegalStateException("查询部门交易统计的部门信息失败"); + } + return response.getData() == null ? List.of() : response.getData(); + } + + /** + * 构建钱包部门统计条件,保证五类钱包统计使用完全一致的权限、时间和钱包类型范围。 + * + * @param dto 部门统计页面筛选条件 + * @param bizType 钱包流水业务类型编码 + * @param type 收支方向;为空时不限制流水正负方向 + * @return 钱包流水汇总条件 + */ + private WalletTransactionSumAndCountDto buildWalletStatisticsDto(HardDeptTotalDto dto, String bizType, Integer type) { + WalletTransactionSumAndCountDto walletDto = new WalletTransactionSumAndCountDto(); + walletDto.setDeptIds(dto.getDeptIds()); + walletDto.setIsInclude(dto.getIsInclude()); + walletDto.setBizTypes(List.of(bizType)); + walletDto.setType(type); + walletDto.setCreateTimeList(dto.getCreateTimeList()); + walletDto.setWalletTypeId(dto.getWalletTypeId()); + return walletDto; + } + + /** + * 调用钱包服务查询部门流水统计;远程业务失败时终止本次汇总,避免把失败数据误展示为零。 + * + * @param walletDto 钱包流水汇总条件 + * @return 钱包部门统计,远程响应数据为空时返回空列表 + * @throws IllegalStateException 钱包流水远程调用失败时抛出 + */ + private List queryWalletStatistics(WalletTransactionSumAndCountDto walletDto) { + R> response = remoteWalletTransactionService.sumAndCount(walletDto); + if (response == null || !response.isOk()) { + throw new IllegalStateException("查询钱包部门交易统计失败"); + } + return response.getData() == null ? List.of() : response.getData(); + } + + /** + * 构建硬件订单部门统计条件。退款按售后完成时间统计,并覆盖支付、核销及整单退款状态, + * 因为部分退款不会把原订单状态改成整单退款。 + * + * @param dto 部门统计页面筛选条件 + * @param refund 是否查询已完成退款 + * @return 支付或退款汇总条件 + */ + private HardOrderSumAndCountDto buildOrderStatisticsDto(HardDeptTotalDto dto, boolean refund) { + HardOrderSumAndCountDto orderDto = new HardOrderSumAndCountDto(); + orderDto.setUserDeptIds(dto.getDeptIds()); + orderDto.setIsInclude(dto.getIsInclude()); + orderDto.setOrderStatusCodes(HardOrderStatusEnum.getPayPrice()); + orderDto.setWalletTypeId(dto.getWalletTypeId()); + if (refund) { + orderDto.setRefundStatusCodes(List.of(HardSaleStatusEnum.COMPLETE.getStatus())); + orderDto.setRefundTimeList(dto.getCreateTimeList()); + } else { + orderDto.setCreateTimeList(dto.getCreateTimeList()); + } + return orderDto; + } + + /** + * 将钱包统计按部门建立索引,避免每个部门重复遍历全部统计结果。 + * + * @param statisticsList 钱包部门统计列表 + * @return 以部门 ID 为键的统计索引 + */ + private Map indexWalletStatistics(List statisticsList) { + Map statisticsMap = new LinkedHashMap<>(); + if (ObjectUtil.isNotEmpty(statisticsList)) { + for (WalletTransactionSumAndCountVo statistics : statisticsList) { + if (statistics != null && statistics.getDeptId() != null) { + statisticsMap.put(statistics.getDeptId(), statistics); + } } } - // 计算净流水 - for (HardDeptTotalVo v : resultList) { - // 流水金额 - if (ObjectUtil.isNotEmpty(v.getOrderPayPrice())) { - v.setOrderProfitPrice(v.getOrderPayPrice().subtract(v.getOrderRefundPrice())); - } - // 流水次数 - if (ObjectUtil.isNotEmpty(v.getOrderPayCount())) { - v.setOrderProfitCont(v.getOrderPayCount() - v.getOrderRefundCont()); + return statisticsMap; + } + + /** + * 将订单统计按部门建立索引,供支付和退款结果按相同键合并。 + * + * @param statisticsList 订单部门统计列表 + * @return 以部门 ID 为键的统计索引 + */ + private Map indexOrderStatistics(List statisticsList) { + Map statisticsMap = new LinkedHashMap<>(); + if (ObjectUtil.isNotEmpty(statisticsList)) { + for (HardOrderSumAndCountVo statistics : statisticsList) { + if (statistics != null && statistics.getDeptId() != null) { + statisticsMap.put(statistics.getDeptId(), statistics); + } } } - return resultList; + return statisticsMap; + } + + /** + * 合并部门的订单支付与已完成退款,并按原接口口径计算净金额和净次数。 + * + * @param target 部门统计结果 + * @param pay 支付统计,允许为空 + * @param refund 已完成售后退款统计,允许为空 + */ + private void applyOrderStatistics(HardDeptTotalVo target, HardOrderSumAndCountVo pay, HardOrderSumAndCountVo 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); } /** 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 ec7fddf7c..978aed988 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 @@ -656,9 +656,10 @@ and r.audit_time between #{dto.refundTimeList[0]} and #{dto.refundTimeList[1]} - + + and r.status in - + #{v} diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteMchService.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteMchService.java index 56595767a..5fdca61a7 100644 --- a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteMchService.java +++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteMchService.java @@ -14,60 +14,190 @@ import org.springframework.web.bind.annotation.RequestParam; import java.util.List; +/** + * 钱包服务商户管理远程调用接口。 + * + *

用于跨服务查询商户详情、部门与商户映射关系,以及当前用户可管理的商户范围。 + * 调用方必须同时检查 {@link R} 的业务状态和数据内容,不能仅以 Feign 调用未抛异常作为成功依据。

+ */ @FeignClient(contextId = "remoteMchService", value = ServiceNameConstants.WALLET_SERVICE) public interface RemoteMchService { + /** + * 按商户条件查询列表,不应用当前登录用户的数据权限。 + * + * @param body 商户筛选条件,可按主键、部门、类型、父商户等字段筛选 + * @return 商户列表响应;未匹配到数据时响应成功且数据可能为空 + */ @NoToken @PostMapping("/mch-manage/list") R> list(@RequestBody MchManageDto body); + /** + * 按商户条件查询当前登录用户数据权限范围内的商户列表。 + * + * @param body 商户筛选条件,可按主键、部门、类型、父商户等字段筛选 + * @return 权限范围内的商户列表响应;未匹配到数据时响应成功且数据可能为空 + */ @PostMapping("/mch-manage/scope-list") R> scopeList(@RequestBody MchManageDto body); + /** + * 按多个商户类型编码查询商户列表。 + * + * @param types 商户类型持久化编码集合,对应 {@link MerchantTypeEnum#getType()} + * @return 匹配的商户列表响应;没有匹配商户时返回业务失败响应 + */ @NoToken @GetMapping("/mch-manage/get-by-types") R> getByTypes(@RequestParam List types); + /** + * 按商户主键查询商户完整信息。 + * + *

返回内容包含部门名称、启用的支付配置以及钱包别名等关联信息。

+ * + * @param id 商户主键 + * @return 商户详情响应;商户不存在时返回业务失败响应 + */ @NoToken @GetMapping("/mch-manage/get-by-id") R getById(@RequestParam Long id); + /** + * 查询指定父商户的直属子商户。 + * + * @param parentId 父商户主键 + * @return 直属子商户列表响应;没有子商户时返回成功的空集合 + */ @NoToken @GetMapping("/mch-manage/get-by-parent-id") R> getByParentId(@RequestParam Long parentId); + /** + * 按商户主键集合批量查询商户。 + * + * @param ids 商户主键集合 + * @return 匹配的商户列表响应;没有匹配商户时返回业务失败响应 + */ @NoToken @GetMapping("/mch-manage/get-by-ids") R> getByIds(@RequestParam List ids); + /** + * 查询直接绑定在指定部门上的指定类型商户。 + * + *

该方法只匹配商户自身绑定的部门,不沿部门父级或子级关系查找。

+ * + * @param deptId 商户直接绑定的系统部门主键 + * @param type 商户类型枚举 + * @return 匹配的商户响应;商户不存在时返回业务失败响应 + */ @NoToken @GetMapping("/mch-manage/get-by-dept-id") R getByDeptId(@RequestParam("deptId") Long deptId, @RequestParam("type") MerchantTypeEnum type); + /** + * 按商户直接绑定的部门主键集合批量查询商户。 + * + * @param deptIds 系统部门主键集合 + * @return 匹配的商户列表响应;没有匹配商户时返回业务失败响应 + */ @NoToken @GetMapping("/mch-manage/get-by-dept-ids") R> getByDeptIds(@RequestParam List deptIds); + /** + * 按传入部门顺序查询第一个直接绑定且类型匹配的商户。 + * + *

该方法不会自行查询部门层级,调用方需要传入已经按业务优先级排列的部门集合。

+ * + * @param deptIds 待匹配的系统部门主键集合,集合顺序决定商户匹配优先级 + * @param mchType 允许匹配的商户类型 + * @return 首个匹配商户的响应;没有匹配商户时返回业务失败响应 + */ @NoToken @GetMapping("/mch-manage/get-first-mch") R getFirstMch(@RequestParam List deptIds, @RequestParam MerchantTypeEnum mchType); + /** + * 沿指定部门的父级链查询第一个类型匹配的商户。 + * + *

服务端先解析部门父级链,再按部门链顺序选择直接绑定的商户;返回内容包含启用的支付配置。

+ * + * @param deptId 起始系统部门主键 + * @param mchType 允许匹配的商户类型 + * @return 首个匹配商户的响应;没有匹配商户时返回业务失败响应 + */ @NoToken @GetMapping("/mch-manage/get-first-mch-by-dept-id") R getFirstMchByDeptId(@RequestParam Long deptId, @RequestParam MerchantTypeEnum mchType); + /** + * 通过商户部门关系视图查询指定部门归属的食堂商户。 + * + *

可从商户绑定部门的下级部门反向定位食堂商户,并优先返回关系层级最近的商户; + * 返回内容包含启用的支付配置。

+ * + * @param deptId 待解析归属关系的系统部门主键 + * @return 归属食堂商户响应;不存在归属食堂时返回业务失败响应 + */ @NoToken @GetMapping("/mch-manage/get-views-canteen-mch-by-dept-id") R getViewsCanteenMchByDeptId(@RequestParam Long deptId); + /** + * 通过商户部门关系视图查询指定部门归属的指定类型商户。 + * + *

可从商户绑定部门的下级部门反向定位商户,并优先返回关系层级最近的匹配项; + * 返回内容包含启用的支付配置。

+ * + * @param deptId 待解析归属关系的系统部门主键 + * @param mchTypeList 允许匹配的商户类型集合 + * @return 归属商户响应;不存在匹配商户时返回业务失败响应 + */ @NoToken @GetMapping("/mch-manage/get-views-mch-by-dept-id") R getViewsMchByDeptId(@RequestParam Long deptId, @RequestParam List mchTypeList); + /** + * 查询指定食堂商户覆盖的用户部门主键集合。 + * + *

服务端以商户直接绑定部门及其下级部门为候选范围,再通过商户部门关系视图筛选 + * 与该商户类型关联的用户部门。商户不存在或没有关联部门时返回业务失败响应。

+ * + * @param mchId 食堂商户主键 + * @return 去重后的用户部门主键集合响应 + */ @NoToken @GetMapping("/mch-manage/get-self-canteen-user-dept-id-list") R> getSelfCanteenUserDeptIdList(@RequestParam Long mchId); + /** + * 查询指定用户所属目标类型商户覆盖的用户部门主键集合。 + * + *

钱包服务内部先根据用户所属部门和商户类型定位商户,再复用商户部门范围查询逻辑, + * 调用方只需发起一次远程调用。用户、所属部门、匹配商户或关联部门不存在时返回业务失败响应。

+ * + * @param userId 系统用户主键 + * @param mchTypeList 允许匹配的商户类型集合 + * @return 去重后的目标商户用户部门主键集合响应 + */ + @NoToken + @GetMapping("/mch-manage/get-self-mch-user-dept-id-list-by-user-id") + R> getSelfMchUserDeptIdListByUserId(@RequestParam Long userId, @RequestParam List mchTypeList); + + /** + * 查询指定用户可管理的指定类型商户主键集合。 + * + *

服务端以用户所属部门及其下级部门作为权限范围,通过商户部门关系视图匹配商户。 + * 当用户信息不存在或没有匹配商户时,当前实现返回包含 {@code -1L} 的占位集合,调用方 + * 不应将该值作为有效商户主键使用。

+ * + * @param userId 系统用户主键 + * @param mchTypeList 允许匹配的商户类型集合 + * @return 用户可管理的商户主键集合响应;无匹配数据时可能仅包含 {@code -1L} + */ @NoToken @GetMapping("/mch-manage/get-self-mch-id-list") R> getSelfMchIdList(@RequestParam Long userId, @RequestParam List mchTypeList); diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/mch/MchManageController.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/mch/MchManageController.java index 7893cfdc0..13043fec6 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/mch/MchManageController.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/mch/MchManageController.java @@ -160,6 +160,22 @@ public class MchManageController { return R.ok(userDeptIdList); } + /** + * 查询指定用户所属目标类型商户覆盖的用户部门主键集合。 + * + * @param userId 系统用户主键 + * @param mchTypeList 允许匹配的商户类型集合 + * @return 目标商户覆盖的用户部门主键集合;无法定位用户、匹配商户或关联部门时返回业务失败响应 + */ + @Inner + @GetMapping("/get-self-mch-user-dept-id-list-by-user-id") + public R> getSelfMchUserDeptIdListByUserId(@RequestParam Long userId, + @RequestParam List mchTypeList) { + List userDeptIdList = mchManageService.getSelfMchUserDeptIdListByUserId(userId, mchTypeList); + if (ObjectUtil.isEmpty(userDeptIdList)) return R.failed("商户不存在"); + return R.ok(userDeptIdList); + } + @Inner @GetMapping("/get-self-mch-id-list") public R> getSelfMchIdList(@RequestParam Long userId, @RequestParam List mchTypeList) { diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/MchManageService.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/MchManageService.java index 72add9efe..f83350da6 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/MchManageService.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/MchManageService.java @@ -72,6 +72,15 @@ public interface MchManageService extends IService { */ List getSelfCanteenUserDeptIdList(Long mchId); + /** + * 根据用户主键和商户类型查询所属商户覆盖的用户部门主键集合。 + * + * @param userId 系统用户主键 + * @param mchTypeList 允许匹配的商户类型集合 + * @return 目标商户覆盖的用户部门主键集合;用户、匹配商户或关联部门不存在时返回空集合 + */ + List getSelfMchUserDeptIdListByUserId(Long userId, List mchTypeList); + /** * 判断用户是否能查看到所有食堂 */ diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/mch/MchManageServiceImpl.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/mch/MchManageServiceImpl.java index 27fe654ad..864f398ec 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/mch/MchManageServiceImpl.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/mch/MchManageServiceImpl.java @@ -560,6 +560,33 @@ public class MchManageServiceImpl extends ServiceImpl先通过用户所属部门和允许的商户类型定位最近的匹配商户,再复用按商户查询覆盖部门的逻辑, + * 将原本由调用方完成的两段查询收敛到钱包服务内部。

+ * + * @param userId 系统用户主键 + * @param mchTypeList 允许匹配的商户类型集合 + * @return 目标商户覆盖的用户部门主键集合;用户、所属部门、匹配商户或关联部门不存在时返回空集合 + */ + @Override + public List getSelfMchUserDeptIdListByUserId(Long userId, List mchTypeList) { + if (userId == null || ObjectUtil.isEmpty(mchTypeList)) { + return List.of(); + } + R userResponse = remoteUserService.getInnerByUserId(userId); + if (userResponse == null || !userResponse.isOk() || userResponse.getData() == null + || userResponse.getData().getSysUser() == null || userResponse.getData().getSysUser().getDeptId() == null) { + return List.of(); + } + MchManageVo merchant = getViewsCanteenMch(userResponse.getData().getSysUser().getDeptId(), mchTypeList); + if (merchant == null || merchant.getId() == null) { + return List.of(); + } + return getSelfCanteenUserDeptIdList(merchant.getId()); + } + /** * 判断用户是否能查看到所有食堂 */