diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardAfterSaleVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardAfterSaleVo.java index c4a1faac..b67f2060 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardAfterSaleVo.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardAfterSaleVo.java @@ -6,6 +6,7 @@ import com.bsd.pjcloud.admin.api.vo.base.BaseDeptInterface; import com.bsd.pjcloud.admin.api.vo.base.BaseUserDeptInterface; import com.bsd.pjcloud.admin.api.vo.base.BaseUserIdentityInterface; import com.bsd.pjcloud.admin.api.vo.base.BaseUserInterface; +import com.bsd.pjcloud.hard.api.entity.order.HardAfterSaleCarEntity; import com.bsd.pjcloud.hard.api.entity.order.HardAfterSaleEntity; import com.bsd.pjcloud.hard.api.vo.base.BaseOrderInterface; import lombok.Data; @@ -99,4 +100,9 @@ public class HardAfterSaleVo extends HardAfterSaleEntity implements BaseUserInte private Integer orderPayPrice; private LocalDateTime orderPayTime; + + /** + * 当前售后单申请时保存的商品快照,用于审批详情展示本次退款商品及份数;历史未保存明细时为空列表。 + */ + private List carList; } diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenApptTotalController.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenApptTotalController.java index b89696d7..2ca224f6 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenApptTotalController.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCanteenApptTotalController.java @@ -8,11 +8,14 @@ import com.bsd.pjcloud.common.core.constant.enums.MerchantTypeEnum; import com.bsd.pjcloud.common.core.util.R; import com.bsd.pjcloud.common.excel.annotation.ResponseExcel; import com.bsd.pjcloud.common.log.annotation.SysLog; +import com.bsd.pjcloud.common.security.service.PjcloudUser; +import com.bsd.pjcloud.common.security.util.SecurityUtils; import com.bsd.pjcloud.hard.api.constant.order.HardOrderTypeEnum; import com.bsd.pjcloud.hard.api.dto.order.HardCanteenUserApptQueryDto; import com.bsd.pjcloud.hard.api.dto.order.HardOrderDto; import com.bsd.pjcloud.hard.api.vo.order.HardCanteenProductTotalVo; import com.bsd.pjcloud.hard.api.vo.order.HardCanteenUserApptOrderVo; +import com.bsd.pjcloud.hard.api.vo.order.HardCanteenUserApptStatisticsSummaryVo; import com.bsd.pjcloud.hard.api.vo.order.HardCanteenUserApptVo; import com.bsd.pjcloud.hard.api.vo.order.HardCanteenUserApptStatisticsVo; import com.bsd.pjcloud.hard.api.vo.order.excel.HardCanteenUserApptExcelVo; @@ -91,6 +94,37 @@ public class HardCanteenApptTotalController { return R.ok(canteenApptTotalService.productReservTotal(page, dto, null)); } + /** + * 查询 App 当前可管理食堂范围内的预约菜品及点餐人员统计。 + * + *

食堂主键不信任客户端输入,而是根据当前商户管理员重新计算;服务层只统计有效预约订单, + * 并按已完成退款扣减后的净菜品份数聚合。

+ * + * @param query 预约用餐日期范围和餐段筛选条件 + * @return 当前账号可管理食堂内的菜品和点餐人员汇总;没有可管理食堂时返回空汇总 + */ + @Operation(summary = "App预约菜品人员统计", description = "按当前账号可管理食堂统计预约菜品和点餐人员净份数") + @ApiCryptoAes + @PostMapping("/app/user-reserv-statistics") + public R appUserReservationStatistics( + @Valid @RequestBody HardCanteenUserApptQueryDto query) { + PjcloudUser user = SecurityUtils.getUser(); + if (user == null || user.getId() == null) { + throw new IllegalStateException("未获取到当前登录用户"); + } + R> merchantResponse = remoteMchService + .getSelfMchIdList(user.getId(), List.of(MerchantTypeEnum.CANTEEN)); + if (merchantResponse == null || !merchantResponse.isOk()) { + String message = merchantResponse == null ? null : merchantResponse.getMsg(); + throw new IllegalStateException(ObjectUtil.isEmpty(message) ? "查询可管理食堂范围失败" : message); + } + List merchantIds = merchantResponse.getData(); + if (ObjectUtil.isEmpty(merchantIds)) { + return R.ok(new HardCanteenUserApptStatisticsSummaryVo()); + } + return R.ok(canteenApptTotalService.appUserReservationStatistics(query, merchantIds)); + } + /* ************************************* end app end ************************************* */ /** diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCarController.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCarController.java index bb84f2d4..03c6fdea 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCarController.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardCarController.java @@ -136,6 +136,13 @@ public class HardCarController { } /* ************************************* end app end ************************************* */ + /** + * 分页查询订单原购物车商品,并补充已完成退款、申请中退款和当前剩余份数。 + * + * @param page 分页参数 + * @param orderId 原订单主键 + * @return 原购物车商品分页;订单不存在或未关联购物车时返回空数据 + */ @GetMapping("/order-id") @Operation(summary = "根据订单id查询购物车") public R> orderId(@ParameterObject Page page, @RequestParam Long orderId) { @@ -144,7 +151,9 @@ public class HardCarController { if (ObjectUtil.isEmpty(order) || ObjectUtil.isEmpty(order.getParentCarId())) return R.ok(null); HardCarDto dto = new HardCarDto(); dto.setParentCarId(order.getParentCarId()); - return R.ok(hardCarService.pageJoin(page, dto)); + Page carPage = hardCarService.pageJoin(page, dto); + hardAfterSaleCarService.fillRefundQuantityStats(order.getParentCarId(), carPage.getRecords()); + return R.ok(carPage); } @GetMapping("/refund-car") diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardCanteenApptTotalService.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardCanteenApptTotalService.java index 61eba838..adee4d5a 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardCanteenApptTotalService.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardCanteenApptTotalService.java @@ -6,6 +6,7 @@ import com.bsd.pjcloud.hard.api.dto.order.HardCanteenUserApptQueryDto; import com.bsd.pjcloud.hard.api.dto.order.HardOrderDto; import com.bsd.pjcloud.hard.api.vo.order.HardCanteenProductTotalVo; import com.bsd.pjcloud.hard.api.vo.order.HardCanteenUserApptOrderVo; +import com.bsd.pjcloud.hard.api.vo.order.HardCanteenUserApptStatisticsSummaryVo; import com.bsd.pjcloud.hard.api.vo.order.HardCanteenUserApptVo; import com.bsd.pjcloud.hard.api.vo.order.HardCanteenUserApptStatisticsVo; @@ -53,4 +54,14 @@ public interface HardCanteenApptTotalService { */ HardCanteenUserApptStatisticsVo userReservationStatistics(HardCanteenUserApptQueryDto query); + /** + * 按 App 当前账号可管理的食堂范围统计有效预约菜品及点餐人员。 + * + * @param query 预约用餐日期范围和餐段筛选条件 + * @param merchantIds 服务端根据当前账号解析出的可管理食堂主键集合 + * @return 已完成退款扣减后的菜品和点餐人员汇总;食堂范围为空时返回空汇总 + */ + HardCanteenUserApptStatisticsSummaryVo appUserReservationStatistics( + HardCanteenUserApptQueryDto query, List merchantIds); + } diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardAfterSaleServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardAfterSaleServiceImpl.java index f811bc7e..e1c1136b 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardAfterSaleServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardAfterSaleServiceImpl.java @@ -466,6 +466,12 @@ public class HardAfterSaleServiceImpl extends ServiceImpl list = baseMapper.selectList(new LambdaQueryWrapper() @@ -475,6 +481,8 @@ public class HardAfterSaleServiceImpl extends ServiceImpl user = userService.getInnerByUserId(detail.getUserId()); 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 68f75b1b..91ee55f9 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 @@ -299,6 +299,85 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ return result; } + /** + * 按 App 当前账号可管理食堂统计预约菜品及点餐人员。 + * + *

与 PC 人员预约统计不同,App 商户视角以可管理食堂中的有效订单为主数据源, + * 再按订单用户主键批量补齐姓名和部门,避免商户账号的组织数据权限漏掉实际点餐人员。

+ * + * @param query 预约用餐日期范围和餐段筛选条件 + * @param merchantIds 服务端解析出的可管理食堂主键集合,禁止使用客户端自行声明的范围 + * @return 完成退款扣减后的菜品及点餐人员汇总 + */ + @Override + public HardCanteenUserApptStatisticsSummaryVo appUserReservationStatistics( + HardCanteenUserApptQueryDto query, List merchantIds) { + validateQuery(query); + List scopedMerchantIds = merchantIds == null + ? List.of() : merchantIds.stream().filter(Objects::nonNull).distinct().toList(); + if (scopedMerchantIds.isEmpty()) { + return new HardCanteenUserApptStatisticsSummaryVo(); + } + + HardOrderDto orderQuery = buildReservationOrderQuery(query, null); + orderQuery.setMerchantId(null); + orderQuery.setMchIds(scopedMerchantIds); + List orders = retainEffectiveReservationOrders( + hardOrderMapper.scopeList(orderQuery, null), query); + if (orders.isEmpty()) { + return new HardCanteenUserApptStatisticsSummaryVo(); + } + + List userIds = orders.stream().map(HardOrderVo::getUserId) + .filter(Objects::nonNull).distinct().toList(); + List users = fetchUsersByIds(userIds); + List rows = buildRows(users, query, getQueryDates(query), orders).stream() + .filter(row -> row.getOrders() != null && !row.getOrders().isEmpty()).toList(); + Map orderMap = orders.stream().filter(order -> order.getId() != null) + .collect(Collectors.toMap(HardOrderVo::getId, Function.identity(), (left, right) -> left)); + Map> carMap = fetchReservationCars(orders, query); + StatisticsBucket bucket = new StatisticsBucket(); + rows.forEach(row -> bucket.addRow(row, orderMap, carMap)); + return bucket.toSummary(); + } + + /** + * 按订单用户主键批量读取不受商户账号组织数据权限裁剪的用户展示摘要。 + * + *

订单范围已先由可管理食堂限定,因此这里只补齐这些订单关联人员的姓名、账号和部门; + * 若历史用户已删除,则保留一个未命名占位,确保对应订单和菜品份数不会从统计中消失。

+ * + * @param userIds 可管理食堂有效预约订单中的去重用户主键 + * @return 与输入主键顺序一致的用户摘要列表 + */ + private List fetchUsersByIds(List userIds) { + if (userIds.isEmpty()) { + return List.of(); + } + Map userMap = new LinkedHashMap<>(); + for (int start = 0; start < userIds.size(); start += ORDER_USER_BATCH_SIZE) { + int end = Math.min(start + ORDER_USER_BATCH_SIZE, userIds.size()); + List batchUserIds = new ArrayList<>(userIds.subList(start, end)); + R> response = remoteUserService.getUserDeptByIds(batchUserIds); + if (response == null || !response.isOk() || response.getData() == null) { + String message = response == null ? null : response.getMsg(); + throw new IllegalStateException(StrUtil.isBlank(message) ? "查询点餐人员信息失败" : message); + } + response.getData().stream().filter(user -> user.getUserId() != null) + .forEach(user -> userMap.putIfAbsent(user.getUserId(), user)); + } + return userIds.stream().map(userId -> { + UserVO user = userMap.get(userId); + if (user != null) { + return user; + } + UserVO fallback = new UserVO(); + fallback.setUserId(userId); + fallback.setName("未命名用户"); + return fallback; + }).toList(); + } + /** * 校验必填条件和前后端状态协议值,防止未知值被静默当成“全部”。 *