From d61c4b4a7fc618506896db58ed867f6eb767432b Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Fri, 18 Sep 2026 15:18:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=8F=E5=A6=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/device/DeviceController.java | 4 +- .../trade/enums/order/TradeBizSceneEnum.java | 34 +++ .../admin/aftersale/AfterSaleController.java | 70 ++++- .../aftersale/vo/AfterSalePageReqVO.java | 15 + .../aftersale/vo/AfterSaleRespPageItemVO.java | 6 + .../admin/order/TradeOrderController.java | 21 +- .../admin/order/vo/TradeOrderPageReqVO.java | 15 + .../vo/TradeOrderSalesStatisticsReqVO.java | 12 + .../vo/TradeOrderStatisticsDetailReqVO.java | 13 + .../dal/mysql/aftersale/AfterSaleMapper.java | 33 ++- .../dal/mysql/order/TradeOrderItemMapper.java | 28 +- .../dal/mysql/order/TradeOrderMapper.java | 90 +++++- .../aftersale/AfterSaleServiceImpl.java | 3 + .../service/order/TradeOrderQueryService.java | 26 ++ .../order/TradeOrderQueryServiceImpl.java | 267 +++++++++++++++++- .../order/TradeOrderUpdateServiceImpl.java | 8 + .../order/TradeWxaOrderShippingService.java | 151 ++++++++++ .../handler/TradeDeviceOrderHandler.java | 7 + .../pay/api/order/dto/PayOrderRespDTO.java | 10 + .../system/api/social/SocialClientApi.java | 7 + .../SocialWxOrderShippingUploadReqDTO.java | 54 ++++ .../api/social/SocialClientApiImpl.java | 5 + .../admin/user/vo/user/UserPageReqVO.java | 3 + .../service/social/SocialClientService.java | 10 +- .../social/SocialClientServiceImpl.java | 42 ++- .../service/user/AdminUserServiceImpl.java | 30 ++ 26 files changed, 934 insertions(+), 30 deletions(-) create mode 100644 yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeBizSceneEnum.java create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeWxaOrderShippingService.java create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxOrderShippingUploadReqDTO.java diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/DeviceController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/DeviceController.java index 94ee9f2..d7240d6 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/DeviceController.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/device/DeviceController.java @@ -91,7 +91,7 @@ public class DeviceController { @GetMapping("/list-all-simple") @Operation(summary = "获得贩卖机信息精简列表(用于下拉选择)") - @PreAuthorize("@ss.hasPermission('product:device:query')") + @PreAuthorize("@ss.hasAnyPermissions('product:device:query','system:user:query','system:user:create','system:user:update')") public CommonResult> getDeviceSimpleList() { List list = deviceService.getDeviceSimpleList(); return success(BeanUtils.toBean(list, DeviceSimpleRespVO.class)); @@ -99,7 +99,7 @@ public class DeviceController { @GetMapping("/list-simple-by-user") @Operation(summary = "获得贩卖机信息精简列表(根据用户绑定过滤,超级管理员返回全部)") - @PreAuthorize("@ss.hasPermission('product:device:query')") + @PreAuthorize("@ss.hasAnyPermissions('product:device:query','trade:machine-order:query','trade:machine-order:statistics','trade:machine-after-sale:query')") public CommonResult> getDeviceSimpleListByUser() { List list = deviceService.getDeviceSimpleListByUser(); return success(BeanUtils.toBean(list, DeviceSimpleRespVO.class)); diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeBizSceneEnum.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeBizSceneEnum.java new file mode 100644 index 0000000..8b3ce95 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/order/TradeBizSceneEnum.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.trade.enums.order; + +import cn.hutool.core.util.StrUtil; + +/** + * 管理端交易业务场景:商城 / 售货机 + */ +public final class TradeBizSceneEnum { + + public static final String MALL = "mall"; + public static final String MACHINE = "machine"; + + private TradeBizSceneEnum() { + } + + /** + * 未传或非法时默认商城,避免老前端误拉出售货机数据 + */ + public static String normalize(String bizScene) { + if (StrUtil.equalsIgnoreCase(bizScene, MACHINE)) { + return MACHINE; + } + return MALL; + } + + public static boolean isMachine(String bizScene) { + return MACHINE.equals(normalize(bizScene)); + } + + public static boolean isMall(String bizScene) { + return MALL.equals(normalize(bizScene)); + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/AfterSaleController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/AfterSaleController.java index b89db05..aa0afd0 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/AfterSaleController.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/AfterSaleController.java @@ -1,11 +1,15 @@ package cn.iocoder.yudao.module.trade.controller.admin.aftersale; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.member.api.user.MemberUserApi; import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO; +import cn.iocoder.yudao.module.product.api.device.DeviceApi; +import cn.iocoder.yudao.module.product.api.device.dto.DeviceRespDto; import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.*; import cn.iocoder.yudao.module.trade.convert.aftersale.AfterSaleConvert; import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO; @@ -26,8 +30,13 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.annotation.security.PermitAll; import javax.validation.Valid; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @@ -49,10 +58,12 @@ public class AfterSaleController { private AfterSaleLogService afterSaleLogService; @Resource private MemberUserApi memberUserApi; + @Resource + private DeviceApi deviceApi; @GetMapping("/page") @Operation(summary = "获得售后订单分页") - @PreAuthorize("@ss.hasPermission('trade:after-sale:query')") + @PreAuthorize("@ss.hasAnyPermissions('trade:after-sale:query','trade:machine-after-sale:query')") public CommonResult> getAfterSalePage(@Valid AfterSalePageReqVO pageVO) { // 查询售后 PageResult pageResult = afterSaleService.getAfterSalePage(pageVO); @@ -63,19 +74,25 @@ public class AfterSaleController { // 查询会员 Map memberUsers = memberUserApi.getUserMap( convertSet(pageResult.getList(), AfterSaleDO::getUserId)); - return success(AfterSaleConvert.INSTANCE.convertPage(pageResult, memberUsers)); + PageResult voPage = AfterSaleConvert.INSTANCE.convertPage(pageResult, memberUsers); + fillAfterSaleDeviceInfo(voPage.getList(), pageResult.getList()); + return success(voPage); } @GetMapping("/get-detail") @Operation(summary = "获得售后订单详情") @Parameter(name = "id", description = "售后编号", required = true, example = "1") - @PreAuthorize("@ss.hasPermission('trade:after-sale:query')") - public CommonResult getOrderDetail(@RequestParam("id") Long id) { + @PreAuthorize("@ss.hasAnyPermissions('trade:after-sale:query','trade:machine-after-sale:query')") + public CommonResult getOrderDetail(@RequestParam("id") Long id, + @RequestParam(value = "bizScene", required = false) String bizScene) { // 查询订单 AfterSaleDO afterSale = afterSaleService.getAfterSale(id); if (afterSale == null) { return success(null); } + if (!tradeOrderQueryService.hasAdminOrderDevicePermission(afterSale.getOrderId(), bizScene)) { + return success(null); + } // 查询订单 TradeOrderDO order = tradeOrderQueryService.getOrder(afterSale.getOrderId()); @@ -141,4 +158,49 @@ public class AfterSaleController { return success(true); } + /** + * 填充售后关联订单项的设备 IMEI / 名称 + */ + private void fillAfterSaleDeviceInfo(List voList, List doList) { + if (CollUtil.isEmpty(voList) || CollUtil.isEmpty(doList)) { + return; + } + Set orderIds = doList.stream() + .map(AfterSaleDO::getOrderId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (CollUtil.isEmpty(orderIds)) { + return; + } + List orderItems = tradeOrderQueryService.getOrderItemListByOrderId(orderIds); + Map itemDeviceMap = orderItems.stream() + .filter(item -> StrUtil.isNotEmpty(item.getDeviceId())) + .collect(Collectors.toMap(TradeOrderItemDO::getId, TradeOrderItemDO::getDeviceId, (a, b) -> a)); + if (itemDeviceMap.isEmpty()) { + return; + } + Map afterSaleItemIdMap = doList.stream() + .filter(d -> d.getId() != null && d.getOrderItemId() != null) + .collect(Collectors.toMap(AfterSaleDO::getId, AfterSaleDO::getOrderItemId, (a, b) -> a)); + Set imeis = new java.util.HashSet<>(itemDeviceMap.values()); + Map deviceMap = CollectionUtils.convertMap( + imeis.isEmpty() ? Collections.emptyList() : deviceApi.getDeviceByDeviceIds(new ArrayList<>(imeis)), + DeviceRespDto::getDeviceId); + for (AfterSaleRespPageItemVO vo : voList) { + Long orderItemId = afterSaleItemIdMap.get(vo.getId()); + if (orderItemId == null) { + continue; + } + String imei = itemDeviceMap.get(orderItemId); + if (StrUtil.isEmpty(imei)) { + continue; + } + vo.setDeviceId(imei); + DeviceRespDto device = deviceMap.get(imei); + if (device != null) { + vo.setDeviceName(device.getName()); + } + } + } + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java index 4b8756c..ea1ea0a 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java @@ -49,4 +49,19 @@ public class AfterSalePageReqVO extends PageParam { @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime[] createTime; + /** + * 业务场景:mall=商城,machine=售货机;未传默认 mall + */ + @Schema(description = "业务场景:mall / machine", example = "mall") + private String bizScene; + + @Schema(description = "设备 IMEI(售货机筛选)", example = "860000000000001") + private String deviceId; + + /** + * 数据权限:当前用户绑定的设备 IMEI 集合 + */ + @Schema(description = "数据权限用:当前用户绑定的设备 IMEI 集合", hidden = true) + private java.util.List bindDeviceImeis; + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSaleRespPageItemVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSaleRespPageItemVO.java index 3e76405..f806bfb 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSaleRespPageItemVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/vo/AfterSaleRespPageItemVO.java @@ -32,4 +32,10 @@ public class AfterSaleRespPageItemVO extends AfterSaleBaseVO { */ private MemberUserRespVO user; + @Schema(description = "设备IMEI", example = "860000000000001") + private String deviceId; + + @Schema(description = "设备名称", example = "一号机") + private String deviceName; + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/TradeOrderController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/TradeOrderController.java index c8f01d4..409aebd 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/TradeOrderController.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/TradeOrderController.java @@ -73,7 +73,7 @@ public class TradeOrderController { @GetMapping("/page") @Operation(summary = "获得交易订单分页") - @PreAuthorize("@ss.hasPermission('trade:order:query')") + @PreAuthorize("@ss.hasAnyPermissions('trade:order:query','trade:machine-order:query')") public CommonResult> getOrderPage(TradeOrderPageReqVO reqVO) { // 查询订单 PageResult pageResult = tradeOrderQueryService.getOrderPage(reqVO); @@ -120,7 +120,7 @@ public class TradeOrderController { @GetMapping("/summary") @Operation(summary = "获得交易订单统计") - @PreAuthorize("@ss.hasPermission('trade:order:query')") + @PreAuthorize("@ss.hasAnyPermissions('trade:order:query','trade:machine-order:query')") public CommonResult getOrderSummary(TradeOrderPageReqVO reqVO) { return success(tradeOrderQueryService.getOrderSummary(reqVO)); } @@ -128,11 +128,12 @@ public class TradeOrderController { @GetMapping("/get-detail") @Operation(summary = "获得交易订单详情") @Parameter(name = "id", description = "订单编号", required = true, example = "1") - @PreAuthorize("@ss.hasPermission('trade:order:query')") - public CommonResult getOrderDetail(@RequestParam("id") Long id) { + @PreAuthorize("@ss.hasAnyPermissions('trade:order:query','trade:machine-order:query')") + public CommonResult getOrderDetail(@RequestParam("id") Long id, + @RequestParam(value = "bizScene", required = false) String bizScene) { // 查询订单 TradeOrderDO order = tradeOrderQueryService.getOrder(id); - if (order == null) { + if (order == null || !tradeOrderQueryService.hasAdminOrderDevicePermission(id, bizScene)) { return success(null); } // 查询订单项 @@ -149,7 +150,7 @@ public class TradeOrderController { @GetMapping("/get-express-track-list") @Operation(summary = "获得交易订单的物流轨迹") @Parameter(name = "id", description = "交易订单编号") - @PreAuthorize("@ss.hasPermission('trade:order:query')") + @PreAuthorize("@ss.hasAnyPermissions('trade:order:query','trade:machine-order:query')") public CommonResult> getOrderExpressTrackList(@RequestParam("id") Long id) { return success(TradeOrderConvert.INSTANCE.convertList02( tradeOrderQueryService.getExpressTrackList(id))); @@ -208,7 +209,7 @@ public class TradeOrderController { @GetMapping("/get-by-pick-up-verify-code") @Operation(summary = "查询核销码对应的订单") @Parameter(name = "pickUpVerifyCode", description = "自提核销码") - @PreAuthorize("@ss.hasPermission('trade:order:query')") + @PreAuthorize("@ss.hasAnyPermissions('trade:order:query','trade:machine-order:query')") public CommonResult getByPickUpVerifyCode(@RequestParam("pickUpVerifyCode") String pickUpVerifyCode) { TradeOrderDO tradeOrder = tradeOrderUpdateService.getByPickUpVerifyCode(pickUpVerifyCode); return success(TradeOrderConvert.INSTANCE.convert2(tradeOrder, null)); @@ -216,7 +217,7 @@ public class TradeOrderController { @GetMapping("/sales-statistics") @Operation(summary = "获取订单销售统计数据") - @PreAuthorize("@ss.hasPermission('trade:order:query')") + @PreAuthorize("@ss.hasAnyPermissions('trade:order:query','trade:machine-order:query','trade:machine-order:statistics')") public CommonResult> getSalesStatistics(TradeOrderSalesStatisticsReqVO salesStatisticsReqVO) { PageResult orderItemStatisticsRespVOList = tradeOrderQueryService.getSalesStatistics(salesStatisticsReqVO); @@ -267,14 +268,14 @@ public class TradeOrderController { @GetMapping("/statistics-detail") @Operation(summary = "获取订单销售统计详情") - @PreAuthorize("@ss.hasPermission('trade:order:query')") + @PreAuthorize("@ss.hasAnyPermissions('trade:order:query','trade:machine-order:query','trade:machine-order:statistics')") public CommonResult> getStatisticsDetail(TradeOrderStatisticsDetailReqVO salesStatisticsReqVO) { return success(tradeOrderQueryService.getOrderItemListByBarCode(salesStatisticsReqVO)); } @GetMapping("/export-excel") @Operation(summary = "导出订单销售统计详情 Excel") - @PreAuthorize("@ss.hasPermission('trade:order:export')") + @PreAuthorize("@ss.hasAnyPermissions('trade:order:export','trade:machine-order:statistics')") @ApiAccessLog(operateType = EXPORT) public void exportStatisticsDetailExcel(@Valid TradeOrderStatisticsDetailReqVO salesStatisticsReqVO, HttpServletResponse response) throws IOException { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderPageReqVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderPageReqVO.java index 074a1e5..04f273b 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderPageReqVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderPageReqVO.java @@ -61,4 +61,19 @@ public class TradeOrderPageReqVO extends PageParam { @InEnum(value = TerminalEnum.class, message = "订单来源 {value}") private Integer terminal; + /** + * 业务场景:mall=商城,machine=售货机;未传默认 mall + */ + @Schema(description = "业务场景:mall / machine", example = "mall") + private String bizScene; + + @Schema(description = "设备 IMEI(售货机筛选)", example = "860000000000001") + private String deviceId; + + /** + * 数据权限:当前用户绑定的设备 IMEI 集合(非空时仅查这些设备相关订单) + */ + @Schema(description = "数据权限用:当前用户绑定的设备 IMEI 集合", hidden = true) + private List bindDeviceImeis; + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderSalesStatisticsReqVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderSalesStatisticsReqVO.java index d8e9757..69b484a 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderSalesStatisticsReqVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderSalesStatisticsReqVO.java @@ -39,4 +39,16 @@ public class TradeOrderSalesStatisticsReqVO extends PageParam { @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime[] createTime; + /** + * 业务场景:mall=商城,machine=售货机;未传默认 mall + */ + @Schema(description = "业务场景:mall / machine", example = "mall") + private String bizScene; + + /** + * 数据权限:当前用户绑定的设备 IMEI 集合 + */ + @Schema(description = "数据权限用:当前用户绑定的设备 IMEI 集合", hidden = true) + private List bindDeviceImeis; + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderStatisticsDetailReqVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderStatisticsDetailReqVO.java index f343640..600f355 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderStatisticsDetailReqVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/vo/TradeOrderStatisticsDetailReqVO.java @@ -6,6 +6,7 @@ import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; +import java.util.List; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; @@ -25,4 +26,16 @@ public class TradeOrderStatisticsDetailReqVO extends PageParam { @Schema(description = "创建时间") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime[] createTime; + + /** + * 业务场景:mall=商城,machine=售货机;未传默认 mall + */ + @Schema(description = "业务场景:mall / machine", example = "mall") + private String bizScene; + + /** + * 数据权限:当前用户绑定的设备 IMEI 集合 + */ + @Schema(description = "数据权限用:当前用户绑定的设备 IMEI 集合", hidden = true) + private List bindDeviceImeis; } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java index 341dabc..24d1722 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java @@ -1,21 +1,26 @@ package cn.iocoder.yudao.module.trade.dal.mysql.aftersale; +import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO; import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO; +import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; +import cn.iocoder.yudao.module.trade.enums.order.TradeBizSceneEnum; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; @Mapper public interface AfterSaleMapper extends BaseMapperX { default PageResult selectPage(AfterSalePageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() .eqIfPresent(AfterSaleDO::getUserId, reqVO.getUserId()) .likeIfPresent(AfterSaleDO::getNo, reqVO.getNo()) .eqIfPresent(AfterSaleDO::getStatus, reqVO.getStatus()) @@ -24,7 +29,31 @@ public interface AfterSaleMapper extends BaseMapperX { .likeIfPresent(AfterSaleDO::getOrderNo, reqVO.getOrderNo()) .likeIfPresent(AfterSaleDO::getSpuName, reqVO.getSpuName()) .betweenIfPresent(AfterSaleDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(AfterSaleDO::getId)); + .orderByDesc(AfterSaleDO::getId); + appendBizSceneAndDeviceFilter(wrapper, reqVO.getBizScene(), reqVO.getBindDeviceImeis()); + return selectPage(reqVO, wrapper); + } + + /** + * 售后按关联订单做商城/售货机拆分 + 设备绑定过滤 + */ + static void appendBizSceneAndDeviceFilter(LambdaQueryWrapperX wrapper, + String bizScene, + List bindDeviceImeis) { + if (CollUtil.isNotEmpty(bindDeviceImeis)) { + String inValues = bindDeviceImeis.stream() + .map(imei -> "'" + imei.replace("'", "") + "'") + .collect(Collectors.joining(",")); + wrapper.inSql(AfterSaleDO::getOrderId, + "SELECT order_id FROM trade_order_item WHERE deleted = 0 AND device_id IN (" + inValues + ")"); + return; + } + String machineOrderSql = TradeOrderMapper.buildMachineOrderIdSql(); + if (TradeBizSceneEnum.isMachine(bizScene)) { + wrapper.inSql(AfterSaleDO::getOrderId, machineOrderSql); + } else { + wrapper.notInSql(AfterSaleDO::getOrderId, machineOrderSql); + } } default PageResult selectPage(Long userId, PageParam pageParam) { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java index 5304729..ce030f2 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderItemMapper.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.dal.mysql.order; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; @@ -13,6 +14,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemCountDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemOrderNoDO; +import cn.iocoder.yudao.module.trade.enums.order.TradeBizSceneEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; @@ -71,10 +73,13 @@ public interface TradeOrderItemMapper extends BaseMapperX { .selectAll(TradeOrderItemDO.class) .selectSum(TradeOrderItemDO::getCount, TradeOrderItemCountDO::getOrderCount) .eqIfPresent(TradeOrderItemDO::getDeviceId, reqVO.getDeviceId()) + .inIfPresent(TradeOrderItemDO::getDeviceId, reqVO.getBindDeviceImeis()) .eqIfPresent(TradeOrderItemDO::getBarCode, reqVO.getBarCode()) .likeIfPresent(TradeOrderItemDO::getSpuName, reqVO.getSpuName()) .betweenIfPresent(TradeOrderItemDO::getCreateTime, reqVO.getCreateTime()) .groupBy(TradeOrderItemDO::getBarCode); + appendSalesBizSceneFilter(query, reqVO.getBizScene(), CollUtil.isNotEmpty(reqVO.getBindDeviceImeis()) + || StrUtil.isNotEmpty(reqVO.getDeviceId())); query.leftJoin(TradeOrderDO.class,TradeOrderDO::getId, TradeOrderItemDO::getOrderId) .eq(TradeOrderDO::getStatus, TradeOrderStatusEnum.COMPLETED.getStatus()); return selectJoinPage(reqVO,TradeOrderItemCountDO.class,query); @@ -85,15 +90,34 @@ public interface TradeOrderItemMapper extends BaseMapperX { .selectAll(TradeOrderItemDO.class) .selectAs(TradeOrderDO::getNo, TradeOrderItemOrderNoDO::getNo) .eqIfPresent(TradeOrderItemDO::getDeviceId, salesStatisticsReqVO.getDeviceId()) + .inIfPresent(TradeOrderItemDO::getDeviceId, salesStatisticsReqVO.getBindDeviceImeis()) .eqIfPresent(TradeOrderItemDO::getBarCode, salesStatisticsReqVO.getBarCode()) .likeIfPresent(TradeOrderItemDO::getSpuName, salesStatisticsReqVO.getSpuName()) .betweenIfPresent(TradeOrderItemDO::getCreateTime, salesStatisticsReqVO.getCreateTime()); + appendSalesBizSceneFilter(query, salesStatisticsReqVO.getBizScene(), + CollUtil.isNotEmpty(salesStatisticsReqVO.getBindDeviceImeis()) + || StrUtil.isNotEmpty(salesStatisticsReqVO.getDeviceId())); query.leftJoin(TradeOrderDO.class,TradeOrderDO::getId, TradeOrderItemDO::getOrderId); -// return selectPage(salesStatisticsReqVO,new MPJLambdaWrapperX() -// .eq(TradeOrderItemDO::getBarCode, salesStatisticsReqVO.getBarCode())); return selectJoinPage(salesStatisticsReqVO,TradeOrderItemOrderNoDO.class, query); } + /** + * 销售统计业务场景:machine 仅有 device_id;mall 仅无 device_id。 + * 若已按 bindDeviceImeis / deviceId 过滤,则不再叠加场景条件。 + */ + static void appendSalesBizSceneFilter(MPJLambdaWrapper query, + String bizScene, + boolean alreadyDeviceFiltered) { + if (alreadyDeviceFiltered) { + return; + } + if (TradeBizSceneEnum.isMachine(bizScene)) { + query.isNotNull(TradeOrderItemDO::getDeviceId).ne(TradeOrderItemDO::getDeviceId, ""); + } else { + query.and(w -> w.isNull(TradeOrderItemDO::getDeviceId).or().eq(TradeOrderItemDO::getDeviceId, "")); + } + } + /** * 查询用户在指定时间范围内购买某个 SPU 的总数量(统计未取消订单,含待支付) * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java index 9ea87e8..c797255 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/order/TradeOrderMapper.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.trade.dal.mysql.order; +import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; @@ -7,6 +8,8 @@ import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderPageReqVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; +import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; +import cn.iocoder.yudao.module.trade.enums.order.TradeBizSceneEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; @@ -15,6 +18,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; @Mapper public interface TradeOrderMapper extends BaseMapperX { @@ -29,7 +33,7 @@ public interface TradeOrderMapper extends BaseMapperX { } default PageResult selectPage(TradeOrderPageReqVO reqVO, Set userIds) { - return selectPage(reqVO, new LambdaQueryWrapperX() + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() .likeIfPresent(TradeOrderDO::getNo, reqVO.getNo()) .eqIfPresent(TradeOrderDO::getUserId, reqVO.getUserId()) .eqIfPresent(TradeOrderDO::getDeliveryType, reqVO.getDeliveryType()) @@ -42,12 +46,14 @@ public interface TradeOrderMapper extends BaseMapperX { .inIfPresent(TradeOrderDO::getPickUpStoreId, reqVO.getPickUpStoreIds()) .likeIfPresent(TradeOrderDO::getPickUpVerifyCode, reqVO.getPickUpVerifyCode()) .betweenIfPresent(TradeOrderDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(TradeOrderDO::getId)); + .orderByDesc(TradeOrderDO::getId); + appendBizSceneAndDeviceFilter(wrapper, reqVO.getBizScene(), reqVO.getBindDeviceImeis()); + return selectPage(reqVO, wrapper); } // TODO @疯狂:如果用 map 返回,要不这里直接用 TradeOrderSummaryRespVO 返回?也算合理,就当 sql 查询出这么个玩意~~ default List> selectOrderSummaryGroupByRefundStatus(TradeOrderPageReqVO reqVO, Set userIds) { - return selectMaps(new MPJLambdaWrapperX() + MPJLambdaWrapperX wrapper = new MPJLambdaWrapperX() .selectAs(TradeOrderDO::getRefundStatus, TradeOrderDO::getRefundStatus) // 售后状态 .selectCount(TradeOrderDO::getId, "count") // 售后状态对应的数量 .selectSum(TradeOrderDO::getPayPrice, "price") // 售后状态对应的支付金额 @@ -62,8 +68,82 @@ public interface TradeOrderMapper extends BaseMapperX { .eqIfPresent(TradeOrderDO::getLogisticsId, reqVO.getLogisticsId()) .inIfPresent(TradeOrderDO::getPickUpStoreId, reqVO.getPickUpStoreIds()) .likeIfPresent(TradeOrderDO::getPickUpVerifyCode, reqVO.getPickUpVerifyCode()) - .betweenIfPresent(TradeOrderDO::getCreateTime, reqVO.getCreateTime()) - .groupBy(TradeOrderDO::getRefundStatus)); // 按售后状态分组 + .betweenIfPresent(TradeOrderDO::getCreateTime, reqVO.getCreateTime()); + appendBizSceneAndDeviceFilter(wrapper, reqVO.getBizScene(), reqVO.getBindDeviceImeis()); + wrapper.groupBy(TradeOrderDO::getRefundStatus); // 按售后状态分组 + return selectMaps(wrapper); + } + + /** + * 业务场景 + 设备数据权限 + *
    + *
  • bindDeviceImeis 非空:仅这些 IMEI 相关订单(售货机侧)
  • + *
  • machine:售货机订单(type/delivery/item.device_id)
  • + *
  • mall(默认):排除售货机订单
  • + *
+ */ + static void appendBizSceneAndDeviceFilter(LambdaQueryWrapperX wrapper, + String bizScene, + List bindDeviceImeis) { + if (CollUtil.isNotEmpty(bindDeviceImeis)) { + wrapper.inSql(TradeOrderDO::getId, buildOrderIdInSqlByDeviceImeis(bindDeviceImeis)); + return; + } + if (TradeBizSceneEnum.isMachine(bizScene)) { + wrapper.inSql(TradeOrderDO::getId, buildMachineOrderIdSql()); + } else { + wrapper.notInSql(TradeOrderDO::getId, buildMachineOrderIdSql()); + } + } + + static void appendBizSceneAndDeviceFilter(MPJLambdaWrapperX wrapper, + String bizScene, + List bindDeviceImeis) { + if (CollUtil.isNotEmpty(bindDeviceImeis)) { + wrapper.inSql(TradeOrderDO::getId, buildOrderIdInSqlByDeviceImeis(bindDeviceImeis)); + return; + } + if (TradeBizSceneEnum.isMachine(bizScene)) { + wrapper.inSql(TradeOrderDO::getId, buildMachineOrderIdSql()); + } else { + wrapper.notInSql(TradeOrderDO::getId, buildMachineOrderIdSql()); + } + } + + static String buildOrderIdInSqlByDeviceImeis(List bindDeviceImeis) { + String inValues = bindDeviceImeis.stream() + .map(imei -> "'" + imei.replace("'", "") + "'") + .collect(Collectors.joining(",")); + return "SELECT order_id FROM trade_order_item WHERE deleted = 0 AND device_id IN (" + inValues + ")"; + } + + /** + * 售货机订单:type=MACHINE 或 delivery_type=DOOR 或订单项有 device_id + */ + static String buildMachineOrderIdSql() { + int machineType = TradeOrderTypeEnum.MACHINE.getType(); + int doorType = DeliveryTypeEnum.DOOR.getType(); + return "SELECT DISTINCT o.id FROM trade_order o" + + " LEFT JOIN trade_order_item i ON i.order_id = o.id AND i.deleted = 0" + + " WHERE o.deleted = 0 AND (" + + " o.type = " + machineType + + " OR o.delivery_type = " + doorType + + " OR (i.device_id IS NOT NULL AND i.device_id != '')" + + ")"; + } + + /** 判断订单是否为售货机订单(详情权限等复用) */ + static boolean isMachineOrder(TradeOrderDO order, boolean hasDeviceItem) { + if (order == null) { + return false; + } + if (TradeOrderTypeEnum.MACHINE.getType().equals(order.getType())) { + return true; + } + if (DeliveryTypeEnum.DOOR.getType().equals(order.getDeliveryType())) { + return true; + } + return hasDeviceItem; } default PageResult selectPage(AppTradeOrderPageReqVO reqVO, Long userId) { diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java index 8a38d44..2958532 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java @@ -83,6 +83,9 @@ public class AfterSaleServiceImpl implements AfterSaleService { @Override public PageResult getAfterSalePage(AfterSalePageReqVO pageReqVO) { + if (!tradeOrderQueryService.prepareAfterSalePagePermission(pageReqVO)) { + return PageResult.empty(); + } return tradeAfterSaleMapper.selectPage(pageReqVO); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java index 5ee2aa8..f2ca3fc 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryService.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.trade.service.order; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.*; import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; @@ -73,6 +74,31 @@ public interface TradeOrderQueryService { */ TradeOrderSummaryRespVO getOrderSummary(TradeOrderPageReqVO reqVO); + /** + * 管理端是否可访问该订单(按绑定设备数据权限) + * + * @param orderId 订单编号 + * @return 是否可访问 + */ + boolean hasAdminOrderDevicePermission(Long orderId); + + /** + * 管理端是否可访问该订单(按绑定设备 + 业务场景) + * + * @param orderId 订单编号 + * @param bizScene mall / machine,可空(默认按设备权限) + * @return 是否可访问 + */ + boolean hasAdminOrderDevicePermission(Long orderId, String bizScene); + + /** + * 售后分页:写入 bizScene / 绑定设备过滤条件 + * + * @param reqVO 售后分页请求 + * @return false 表示无权限应返回空 + */ + boolean prepareAfterSalePagePermission(AfterSalePageReqVO reqVO); + /** * 【会员】获得交易订单分页 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java index 6c67422..5395b36 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderQueryServiceImpl.java @@ -9,7 +9,11 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.member.api.user.MemberUserApi; import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import cn.iocoder.yudao.module.product.api.device.DeviceApi; +import cn.iocoder.yudao.module.product.api.device.dto.DeviceRespDto; +import cn.iocoder.yudao.module.system.api.permission.PermissionApi; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.*; import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageReqVO; import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO; @@ -20,6 +24,7 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemOrderNoD import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; import cn.iocoder.yudao.module.trade.dal.redis.RedisKeyConstants; +import cn.iocoder.yudao.module.trade.enums.order.TradeBizSceneEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; @@ -32,9 +37,11 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.*; +import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.EXPRESS_NOT_EXISTS; import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_NOT_FOUND; @@ -60,6 +67,15 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService { @Resource private MemberUserApi memberUserApi; + @Resource + private AdminUserApi adminUserApi; + + @Resource + private PermissionApi permissionApi; + + @Resource + private DeviceApi deviceApi; + // =================== Order =================== @Override @@ -97,6 +113,10 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService { if (userIds == null) { // 没查询到用户,说明肯定也没他的订单 return PageResult.empty(); } + // 设备数据权限 + if (!applyOrderDeviceDataPermission(reqVO)) { + return PageResult.empty(); + } // 分页查询 return tradeOrderMapper.selectPage(reqVO, userIds); @@ -129,6 +149,9 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService { if (userIds == null) { // 没查询到用户,说明肯定也没他的订单 return new TradeOrderSummaryRespVO(); } + if (!applyOrderDeviceDataPermission(reqVO)) { + return new TradeOrderSummaryRespVO(); + } // 查询每个售后状态对应的数量、金额 List> list = tradeOrderMapper.selectOrderSummaryGroupByRefundStatus(reqVO, userIds); @@ -174,6 +197,10 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService { if (order == null) { throw exception(ORDER_NOT_FOUND); } + // 管理端设备数据权限 + if (!hasAdminOrderDevicePermission(id)) { + throw exception(ORDER_NOT_FOUND); + } // 查询物流 return getExpressTrackList(order); } @@ -253,16 +280,254 @@ public class TradeOrderQueryServiceImpl implements TradeOrderQueryService { @Override public PageResult getSalesStatistics(TradeOrderSalesStatisticsReqVO salesStatisticsReqVO) { + salesStatisticsReqVO.setBizScene(TradeBizSceneEnum.normalize(salesStatisticsReqVO.getBizScene())); + if (!applyStatisticsDeviceDataPermission(salesStatisticsReqVO)) { + return PageResult.empty(); + } PageResult tradeOrderItemDOS = tradeOrderItemMapper.selectSalesStatistics(salesStatisticsReqVO); return BeanUtils.toBean(tradeOrderItemDOS, TradeOrderItemStatisticsRespVO.class); } @Override public PageResult getOrderItemListByBarCode(TradeOrderStatisticsDetailReqVO salesStatisticsReqVO) { + salesStatisticsReqVO.setBizScene(TradeBizSceneEnum.normalize(salesStatisticsReqVO.getBizScene())); + if (!applyStatisticsDeviceDataPermission(salesStatisticsReqVO)) { + return PageResult.empty(); + } PageResult tradeOrderItemDOS = tradeOrderItemMapper.getOrderItemListByBarCode(salesStatisticsReqVO); return BeanUtils.toBean(tradeOrderItemDOS, TradeOrderItemStatisticsDetailRespVO.class); } + @Override + public boolean hasAdminOrderDevicePermission(Long orderId) { + return hasAdminOrderDevicePermission(orderId, null); + } + + @Override + public boolean hasAdminOrderDevicePermission(Long orderId, String bizScene) { + TradeOrderDO order = tradeOrderMapper.selectById(orderId); + if (order == null) { + return false; + } + List items = tradeOrderItemMapper.selectListByOrderId(orderId); + boolean hasDeviceItem = CollUtil.isNotEmpty(items) && items.stream() + .anyMatch(i -> StrUtil.isNotEmpty(i.getDeviceId())); + boolean isMachine = TradeOrderMapper.isMachineOrder(order, hasDeviceItem); + // 显式传入场景时做商城/售货机互斥;未传则仅按设备绑定范围 + String scene = StrUtil.isEmpty(bizScene) ? null : TradeBizSceneEnum.normalize(bizScene); + if (scene != null) { + if (TradeBizSceneEnum.isMachine(scene) && !isMachine) { + return false; + } + if (TradeBizSceneEnum.isMall(scene) && isMachine) { + return false; + } + } + + AdminOrderDeviceScope scope = resolveAdminOrderDeviceScope(); + if (scope.mode == AdminOrderDeviceScope.Mode.ALL) { + return true; + } + if (scope.mode == AdminOrderDeviceScope.Mode.NONE) { + return false; + } + List itemImeis = CollUtil.isEmpty(items) ? Collections.emptyList() : items.stream() + .map(TradeOrderItemDO::getDeviceId) + .filter(StrUtil::isNotEmpty) + .collect(Collectors.toList()); + if (scope.mode == AdminOrderDeviceScope.Mode.MALL_ONLY) { + if (scene != null && TradeBizSceneEnum.isMachine(scene)) { + return false; + } + return !isMachine; + } + // BOUND + if (scene == null) { + return CollUtil.isEmpty(itemImeis) || itemImeis.stream().anyMatch(scope.imeis::contains); + } + if (TradeBizSceneEnum.isMachine(scene)) { + return itemImeis.stream().anyMatch(scope.imeis::contains); + } + return CollUtil.isEmpty(itemImeis); + } + + @Override + public boolean prepareAfterSalePagePermission(AfterSalePageReqVO reqVO) { + String scene = TradeBizSceneEnum.normalize(reqVO.getBizScene()); + reqVO.setBizScene(scene); + AdminOrderDeviceScope scope = resolveAdminOrderDeviceScope(); + String requestDeviceId = reqVO.getDeviceId(); + if (TradeBizSceneEnum.isMachine(scene)) { + if (scope.mode == AdminOrderDeviceScope.Mode.ALL) { + if (StrUtil.isNotEmpty(requestDeviceId)) { + reqVO.setBindDeviceImeis(Collections.singletonList(requestDeviceId)); + } + return true; + } + if (scope.mode == AdminOrderDeviceScope.Mode.BOUND) { + if (StrUtil.isNotEmpty(requestDeviceId)) { + if (!scope.imeis.contains(requestDeviceId)) { + return false; + } + reqVO.setBindDeviceImeis(Collections.singletonList(requestDeviceId)); + } else { + reqVO.setBindDeviceImeis(scope.imeis); + } + return true; + } + return false; + } + return scope.mode != AdminOrderDeviceScope.Mode.NONE; + } + + private boolean applyOrderDeviceDataPermission(TradeOrderPageReqVO reqVO) { + String scene = TradeBizSceneEnum.normalize(reqVO.getBizScene()); + reqVO.setBizScene(scene); + AdminOrderDeviceScope scope = resolveAdminOrderDeviceScope(); + String requestDeviceId = reqVO.getDeviceId(); + if (TradeBizSceneEnum.isMachine(scene)) { + if (scope.mode == AdminOrderDeviceScope.Mode.ALL) { + if (StrUtil.isNotEmpty(requestDeviceId)) { + reqVO.setBindDeviceImeis(Collections.singletonList(requestDeviceId)); + } + return true; + } + if (scope.mode == AdminOrderDeviceScope.Mode.BOUND) { + if (StrUtil.isNotEmpty(requestDeviceId)) { + if (!scope.imeis.contains(requestDeviceId)) { + return false; + } + reqVO.setBindDeviceImeis(Collections.singletonList(requestDeviceId)); + } else { + reqVO.setBindDeviceImeis(scope.imeis); + } + return true; + } + // 未绑定设备 / 无权限:不能进售货机订单 + return false; + } + // mall + if (scope.mode == AdminOrderDeviceScope.Mode.NONE) { + return false; + } + // 超管 / 未绑定 / 已绑定:商城页一律只看商城(不按 IMEI 掺售货机) + return true; + } + + private boolean applyStatisticsDeviceDataPermission(TradeOrderSalesStatisticsReqVO reqVO) { + return applyStatisticsDeviceDataPermission(reqVO.getBizScene(), reqVO.getDeviceId(), + reqVO::setDeviceId, reqVO::setBindDeviceImeis); + } + + private boolean applyStatisticsDeviceDataPermission(TradeOrderStatisticsDetailReqVO reqVO) { + return applyStatisticsDeviceDataPermission(reqVO.getBizScene(), reqVO.getDeviceId(), + reqVO::setDeviceId, reqVO::setBindDeviceImeis); + } + + /** + * 销售统计设备过滤 + */ + private boolean applyStatisticsDeviceDataPermission(String bizScene, + String requestDeviceId, + java.util.function.Consumer deviceIdSetter, + java.util.function.Consumer> bindSetter) { + String scene = TradeBizSceneEnum.normalize(bizScene); + AdminOrderDeviceScope scope = resolveAdminOrderDeviceScope(); + if (TradeBizSceneEnum.isMachine(scene)) { + if (scope.mode == AdminOrderDeviceScope.Mode.ALL) { + return true; + } + if (scope.mode == AdminOrderDeviceScope.Mode.BOUND) { + if (StrUtil.isNotEmpty(requestDeviceId)) { + if (!scope.imeis.contains(requestDeviceId)) { + return false; + } + deviceIdSetter.accept(requestDeviceId); + return true; + } + bindSetter.accept(scope.imeis); + return true; + } + return false; + } + // mall:不允许按设备 IMEI 查 + if (StrUtil.isNotEmpty(requestDeviceId)) { + return false; + } + return scope.mode != AdminOrderDeviceScope.Mode.NONE; + } + + /** + * 解析当前管理端用户的订单设备数据权限范围 + */ + private AdminOrderDeviceScope resolveAdminOrderDeviceScope() { + Long userId = getLoginUserId(); + if (userId == null) { + return AdminOrderDeviceScope.none(); + } + if (permissionApi.hasAnyRoles(userId, "super_admin")) { + return AdminOrderDeviceScope.all(); + } + List userDeviceIds = adminUserApi.getUserDeviceIds(userId); + if (CollUtil.isEmpty(userDeviceIds)) { + // 未绑定设备:只能看商城订单,不能看任何售货机订单 + return AdminOrderDeviceScope.mallOnly(); + } + List devices = deviceApi.getDeviceListByIds(userDeviceIds); + if (CollUtil.isEmpty(devices)) { + return AdminOrderDeviceScope.none(); + } + List imeis = devices.stream() + .map(DeviceRespDto::getDeviceId) + .filter(StrUtil::isNotEmpty) + .distinct() + .collect(Collectors.toList()); + if (CollUtil.isEmpty(imeis)) { + return AdminOrderDeviceScope.none(); + } + return AdminOrderDeviceScope.bound(imeis); + } + + /** + * 管理端订单设备数据权限范围 + */ + private static final class AdminOrderDeviceScope { + private enum Mode { + /** 超管:不限制 */ + ALL, + /** 已绑定设备:仅这些 IMEI */ + BOUND, + /** 未绑定设备:仅无 IMEI 的商城订单 */ + MALL_ONLY, + /** 无权限 */ + NONE + } + + private final Mode mode; + private final List imeis; + + private AdminOrderDeviceScope(Mode mode, List imeis) { + this.mode = mode; + this.imeis = imeis; + } + + static AdminOrderDeviceScope all() { + return new AdminOrderDeviceScope(Mode.ALL, null); + } + + static AdminOrderDeviceScope bound(List imeis) { + return new AdminOrderDeviceScope(Mode.BOUND, imeis); + } + + static AdminOrderDeviceScope mallOnly() { + return new AdminOrderDeviceScope(Mode.MALL_ONLY, null); + } + + static AdminOrderDeviceScope none() { + return new AdminOrderDeviceScope(Mode.NONE, null); + } + } + /** * 获得自身的代理对象,解决 AOP 生效问题 * diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index 019c0bf..b789c9a 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -63,6 +63,7 @@ import cn.iocoder.yudao.module.trade.service.message.TradeMessageService; import cn.iocoder.yudao.module.trade.service.message.bo.TradeOrderMessageWhenDeliveryOrderReqBO; import cn.iocoder.yudao.module.trade.service.order.bo.GiftVoucherCalculateRespBO; import cn.iocoder.yudao.module.trade.service.order.handler.TradeOrderHandler; +import cn.iocoder.yudao.module.trade.service.order.TradeWxaOrderShippingService; import cn.iocoder.yudao.module.trade.service.price.TradePriceService; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; @@ -142,6 +143,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @Resource private GiftVoucherApi giftVoucherApi; + @Resource + private TradeWxaOrderShippingService tradeWxaOrderShippingService; + @Resource private AdminUserApi adminUserApi; @@ -481,6 +485,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { .setOrderId(order.getId()).setUserId(order.getUserId()).setMessage(null)); // 4.2 发送订阅消息 getSelf().sendDeliveryOrderMessage(order, deliveryReqVO); + // 4.3 上传微信小程序发货信息 + List orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId()); + tradeWxaOrderShippingService.uploadExpressShipping(order, orderItems, + deliveryReqVO.getLogisticsId(), deliveryReqVO.getLogisticsNo()); } @Async diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeWxaOrderShippingService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeWxaOrderShippingService.java new file mode 100644 index 0000000..36a6db2 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeWxaOrderShippingService.java @@ -0,0 +1,151 @@ +package cn.iocoder.yudao.module.trade.service.order; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.enums.TerminalEnum; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; +import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.system.api.social.SocialClientApi; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxOrderShippingUploadReqDTO; +import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; +import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 微信小程序发货信息录入(交易结算管理) + */ +@Service +@Slf4j +public class TradeWxaOrderShippingService { + + /** 物流模式:1-快递 2-同城 3-虚拟 4-用户自提 */ + private static final int LOGISTICS_TYPE_EXPRESS = 1; + private static final int LOGISTICS_TYPE_VIRTUAL = 3; + private static final int LOGISTICS_TYPE_PICKUP = 4; + + @Resource + private PayOrderApi payOrderApi; + @Resource + private SocialClientApi socialClientApi; + @Resource + private DeliveryExpressService deliveryExpressService; + + /** + * 快递发货后,上传微信发货信息 + */ + @Async + public void uploadExpressShipping(TradeOrderDO order, List orderItems, + Long logisticsId, String logisticsNo) { + uploadShipping(order, orderItems, DeliveryTypeEnum.EXPRESS.getType(), logisticsId, logisticsNo); + } + + /** + * 售货机出货成功后,上传微信发货信息(用户自提) + */ + @Async + public void uploadMachineShipping(TradeOrderDO order, List orderItems) { + uploadShipping(order, orderItems, DeliveryTypeEnum.DOOR.getType(), null, null); + } + + private void uploadShipping(TradeOrderDO order, List orderItems, + Integer deliveryType, Long logisticsId, String logisticsNo) { + try { + if (order == null || CollUtil.isEmpty(orderItems)) { + return; + } + // 仅微信小程序订单 + if (!Objects.equals(order.getTerminal(), TerminalEnum.WECHAT_MINI_PROGRAM.getTerminal())) { + log.debug("[uploadShipping][order({}) 非微信小程序订单,跳过]", order.getId()); + return; + } + if (order.getPayOrderId() == null) { + log.debug("[uploadShipping][order({}) 无支付单,跳过]", order.getId()); + return; + } + PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId()); + if (payOrder == null || !PayOrderStatusEnum.isSuccess(payOrder.getStatus())) { + log.debug("[uploadShipping][order({}) 支付单未成功,跳过]", order.getId()); + return; + } + // 仅微信小程序支付 + if (!StrUtil.equals(payOrder.getChannelCode(), "wx_lite")) { + log.debug("[uploadShipping][order({}) 非 wx_lite 支付(channel={}),跳过]", + order.getId(), payOrder.getChannelCode()); + return; + } + if (StrUtil.isBlank(payOrder.getChannelOrderNo())) { + log.warn("[uploadShipping][order({}) 缺少微信 transaction_id,跳过]", order.getId()); + return; + } + String openid = payOrder.getChannelUserId(); + if (StrUtil.isBlank(openid)) { + log.warn("[uploadShipping][order({}) 缺少 payer openid,跳过]", order.getId()); + return; + } + + int logisticsType; + String trackingNo = null; + String expressCompany = null; + if (Objects.equals(deliveryType, DeliveryTypeEnum.EXPRESS.getType())) { + if (logisticsId != null && logisticsId > 0 && StrUtil.isNotBlank(logisticsNo)) { + logisticsType = LOGISTICS_TYPE_EXPRESS; + trackingNo = logisticsNo; + DeliveryExpressDO express = deliveryExpressService.getDeliveryExpress(logisticsId); + if (express != null && StrUtil.isNotBlank(express.getCode())) { + expressCompany = express.getCode(); + } + } else { + logisticsType = LOGISTICS_TYPE_VIRTUAL; + } + } else if (Objects.equals(deliveryType, DeliveryTypeEnum.DOOR.getType()) + || Objects.equals(deliveryType, DeliveryTypeEnum.PICK_UP.getType())) { + logisticsType = LOGISTICS_TYPE_PICKUP; + } else { + logisticsType = LOGISTICS_TYPE_VIRTUAL; + } + + String itemDesc = buildItemDesc(orderItems); + SocialWxOrderShippingUploadReqDTO reqDTO = new SocialWxOrderShippingUploadReqDTO(); + reqDTO.setUserType(UserTypeEnum.MEMBER.getValue()); + reqDTO.setTransactionId(payOrder.getChannelOrderNo()); + reqDTO.setOpenid(openid); + reqDTO.setLogisticsType(logisticsType); + reqDTO.setTrackingNo(trackingNo); + reqDTO.setExpressCompany(expressCompany); + reqDTO.setItemDesc(itemDesc); + + socialClientApi.uploadWxaOrderShipping(reqDTO); + log.info("[uploadShipping][order({}) transactionId={} logisticsType={} 微信发货信息已上传]", + order.getId(), payOrder.getChannelOrderNo(), logisticsType); + } catch (Exception e) { + log.error("[uploadShipping][order({}) 上传微信发货信息失败]", order.getId(), e); + } + } + + private String buildItemDesc(List orderItems) { + String desc = orderItems.stream() + .map(item -> { + String name = StrUtil.blankToDefault(item.getSpuName(), "商品"); + Integer count = item.getCount() != null ? item.getCount() : 1; + return name + "*" + count + "个"; + }) + .collect(Collectors.joining(",")); + if (desc.length() > 120) { + desc = desc.substring(0, 117) + "..."; + } + return desc; + } + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java index e26518b..7070f32 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeDeviceOrderHandler.java @@ -19,6 +19,7 @@ import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; import cn.iocoder.yudao.module.trade.service.devicelog.DeviceLogService; import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; +import cn.iocoder.yudao.module.trade.service.order.TradeWxaOrderShippingService; import cn.iocoder.yudao.module.trade.service.order.bo.DispenseRequestBo; import cn.iocoder.yudao.module.trade.service.order.bo.DispenseResponseBo; import cn.iocoder.yudao.module.trade.service.order.bo.DispenseResultStatusEnum; @@ -79,6 +80,9 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler { @Resource private DeviceLogService deviceLogService; + @Resource + private TradeWxaOrderShippingService tradeWxaOrderShippingService; + // TOKEN @Value("${xr.cabinet.token}") private String token; @@ -230,6 +234,9 @@ public class TradeDeviceOrderHandler implements TradeOrderHandler { } catch (Exception e) { log.error("[售货机出货]订单:{} 设备出货失败后回补库存异常: {}", order.getNo(), e.getMessage(), e); } + } else { + // 出货成功:上传微信小程序发货信息(用户自提) + tradeWxaOrderShippingService.uploadMachineShipping(order, orderItems); } // 6. 无论出货成功/失败,尝试将订单状态更新为已完成(保持原有语义) diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java index a1c9c32..c9516c9 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/order/dto/PayOrderRespDTO.java @@ -50,4 +50,14 @@ public class PayOrderRespDTO { // ========== 渠道相关字段 ========== + /** + * 渠道用户编号(如微信 openid) + */ + private String channelUserId; + + /** + * 渠道订单号(如微信 transaction_id) + */ + private String channelOrderNo; + } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java index ef02dd1..4bfb15d 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java @@ -66,4 +66,11 @@ public interface SocialClientApi { */ void sendWxaSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO); + /** + * 上传微信小程序发货信息(交易结算管理) + * + * @param reqDTO 请求 + */ + void uploadWxaOrderShipping(SocialWxOrderShippingUploadReqDTO reqDTO); + } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxOrderShippingUploadReqDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxOrderShippingUploadReqDTO.java new file mode 100644 index 0000000..a309dcd --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxOrderShippingUploadReqDTO.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.system.api.social.dto; + +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** + * 微信小程序 - 发货信息录入 Request DTO + */ +@Data +public class SocialWxOrderShippingUploadReqDTO { + + /** + * 用户类型 + */ + @NotNull(message = "用户类型不能为空") + private Integer userType; + + /** + * 微信支付单号(transaction_id) + */ + @NotEmpty(message = "微信支付单号不能为空") + private String transactionId; + + /** + * 支付者 openid + */ + @NotEmpty(message = "openid 不能为空") + private String openid; + + /** + * 物流模式:1-快递 2-同城 3-虚拟 4-用户自提 + */ + @NotNull(message = "物流模式不能为空") + private Integer logisticsType; + + /** + * 物流单号(快递发货时必填) + */ + private String trackingNo; + + /** + * 物流公司编码(快递发货时必填,参见微信文档) + */ + private String expressCompany; + + /** + * 商品描述,如:微信红包*1个 + */ + @NotEmpty(message = "商品描述不能为空") + private String itemDesc; + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java index d0e6040..4fb4bab 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java @@ -94,4 +94,9 @@ public class SocialClientApiImpl implements SocialClientApi { socialClientService.sendSubscribeMessage(reqDTO, template.getPriTmplId(), socialUser.getOpenid()); } + @Override + public void uploadWxaOrderShipping(SocialWxOrderShippingUploadReqDTO reqDTO) { + socialClientService.uploadWxaOrderShipping(reqDTO); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserPageReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserPageReqVO.java index 7dadb51..b3cea93 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserPageReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserPageReqVO.java @@ -38,4 +38,7 @@ public class UserPageReqVO extends PageParam { @Schema(description = "角色编号", example = "1024") private Long roleId; + @Schema(description = "设备IMEI", example = "860000000000001") + private String deviceId; + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java index 71c716c..02390c6 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java @@ -2,8 +2,7 @@ package cn.iocoder.yudao.module.system.service.social; import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; +import cn.iocoder.yudao.module.system.api.social.dto.*; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO; @@ -93,6 +92,13 @@ public interface SocialClientService { */ void sendSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO, String templateId, String openId); + /** + * 上传微信小程序发货信息 + * + * @param reqDTO 请求 + */ + void uploadWxaOrderShipping(SocialWxOrderShippingUploadReqDTO reqDTO); + // =================== 客户端管理 =================== /** diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java index d61f800..9175f6e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java @@ -3,6 +3,10 @@ package cn.iocoder.yudao.module.system.service.social; import cn.binarywang.wx.miniapp.api.WxMaService; import cn.binarywang.wx.miniapp.api.WxMaSubscribeService; import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; +import cn.binarywang.wx.miniapp.bean.shop.request.shipping.OrderKeyBean; +import cn.binarywang.wx.miniapp.bean.shop.request.shipping.PayerBean; +import cn.binarywang.wx.miniapp.bean.shop.request.shipping.ShippingListBean; +import cn.binarywang.wx.miniapp.bean.shop.request.shipping.WxMaOrderShippingInfoUploadRequest; import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage; import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl; @@ -18,8 +22,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.cache.CacheUtils; import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; +import cn.iocoder.yudao.module.system.api.social.dto.*; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO; @@ -54,6 +57,10 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.Duration; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -305,6 +312,37 @@ public class SocialClientServiceImpl implements SocialClientService { } } + @Override + public void uploadWxaOrderShipping(SocialWxOrderShippingUploadReqDTO reqDTO) { + WxMaService service = getWxMaService(reqDTO.getUserType()); + ShippingListBean shipping = ShippingListBean.builder() + .itemDesc(reqDTO.getItemDesc()) + .trackingNo(reqDTO.getTrackingNo()) + .expressCompany(reqDTO.getExpressCompany()) + .build(); + WxMaOrderShippingInfoUploadRequest request = WxMaOrderShippingInfoUploadRequest.builder() + .orderKey(OrderKeyBean.builder() + .orderNumberType(2) // 使用微信支付单号 + .transactionId(reqDTO.getTransactionId()) + .build()) + .logisticsType(reqDTO.getLogisticsType()) + .deliveryMode(1) // 统一发货 + .shippingList(Collections.singletonList(shipping)) + .uploadTime(OffsetDateTime.now(ZoneId.of("Asia/Shanghai")) + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"))) + .payer(PayerBean.builder().openid(reqDTO.getOpenid()).build()) + .build(); + try { + service.getWxMaOrderShippingService().upload(request); + log.info("[uploadWxaOrderShipping][transactionId({}) logisticsType({}) 上传成功]", + reqDTO.getTransactionId(), reqDTO.getLogisticsType()); + } catch (WxErrorException e) { + log.error("[uploadWxaOrderShipping][reqDTO({}) 上传发货信息失败 errCode={} errMsg={}]", + reqDTO, e.getError().getErrorCode(), e.getError().getErrorMsg(), e); + throw new RuntimeException(e); + } + } + /** * 构建发送消息请求参数 * diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index dd16edd..18b14de 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java @@ -11,8 +11,11 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.infra.api.config.ConfigApi; import cn.iocoder.yudao.module.infra.api.file.FileApi; +import cn.iocoder.yudao.module.product.api.device.DeviceApi; +import cn.iocoder.yudao.module.product.api.device.dto.DeviceRespDto; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthRegisterReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO; @@ -88,6 +91,8 @@ public class AdminUserServiceImpl implements AdminUserService { private FileApi fileApi; @Resource private ConfigApi configApi; + @Resource + private DeviceApi deviceApi; @Override @Transactional(rollbackFor = Exception.class) @@ -282,6 +287,31 @@ public class AdminUserServiceImpl implements AdminUserService { Set userIds = reqVO.getRoleId() != null ? permissionService.getUserRoleIdListByRoleId(singleton(reqVO.getRoleId())) : null; + // 按设备 IMEI 筛选:用户表 device_id 或绑定关系 + if (StrUtil.isNotEmpty(reqVO.getDeviceId())) { + Set deviceUserIds = new HashSet<>(); + List legacyUsers = userMapper.selectList( + new LambdaQueryWrapperX() + .eq(AdminUserDO::getDeviceId, reqVO.getDeviceId())); + deviceUserIds.addAll(convertSet(legacyUsers, AdminUserDO::getId)); + DeviceRespDto device = deviceApi.getDeviceByDeviceId(reqVO.getDeviceId()); + if (device != null && device.getId() != null) { + deviceUserIds.addAll(convertSet( + userDeviceMapper.selectListByDeviceId(device.getId()), UserDeviceDO::getUserId)); + } + if (CollUtil.isEmpty(deviceUserIds)) { + return PageResult.empty(); + } + if (userIds != null) { + userIds.retainAll(deviceUserIds); + if (CollUtil.isEmpty(userIds)) { + return PageResult.empty(); + } + } else { + userIds = deviceUserIds; + } + } + // 分页查询 return userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()), userIds); }