From 21e5739e943699ef31707f7353d6984aaa33156b Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Tue, 28 Jul 2026 19:28:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=85=85=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CanteenCategoryController.java | 9 +- .../api/dto/hard/HandCancelActCodeDto.java | 8 + .../api/dto/hard/ReservationMenuQueryDto.java | 8 + .../dto/hard/VerificationRecordQueryDto.java | 29 ++ .../hard/api/dto/order/HardOrderDto.java | 6 + .../api/feign/RemoteHardOrderService.java | 11 + .../vo/android/ReservationMenuQueryVo.java | 6 +- .../api/vo/android/VerificationRecordVo.java | 40 ++ .../order/HardPendingReservationCountVo.java | 20 + .../controller/hard/HardApiController.java | 16 +- .../controller/order/HardOrderController.java | 11 + .../hard/service/HardOrderService.java | 6 + .../service/ReservationMenuQueryService.java | 3 + .../service/VerificationRecordService.java | 12 + .../impl/ReservationMenuQueryServiceImpl.java | 29 +- .../impl/VerificationRecordServiceImpl.java | 94 +++++ .../impl/order/HardOrderServiceImpl.java | 36 ++ .../mapper/order/HardOrderMapper.xml | 20 +- .../ReservationMenuQueryServiceImplTest.java | 31 ++ .../pay/ThirdPayOrderBizTypeEnum.java | 1 + .../impl/appplus/ThirdAppPlusServiceImpl.java | 97 ++++- .../api/feign/RemoteUserFaceService.java | 28 ++ .../api/vo/SysUserFaceStatusCountVo.java | 25 ++ ...mework.cloud.openfeign.FeignClient.imports | 1 + .../controller/SysUserFaceController.java | 13 + .../admin/mapper/SysUserFaceMapper.java | 11 + .../admin/service/SysUserFaceService.java | 6 + .../admin/service/SysUserIdentityService.java | 9 + .../admin/service/SysUserRelationService.java | 7 + .../service/impl/SysUserFaceServiceImpl.java | 14 + .../service/impl/SysUserServiceImpl.java | 28 +- .../impl/user/SysUserIdentityServiceImpl.java | 10 + .../impl/user/SysUserRelationServiceImpl.java | 16 +- .../resources/mapper/SysUserFaceMapper.xml | 14 + .../app/GuardianWardRechargeCreateDto.java | 23 ++ .../feign/RemoteWalletRechargeService.java | 13 + .../app/GuardianWardRechargeContextVo.java | 27 ++ .../app/GuardianWardRechargeCreateVo.java | 25 ++ .../app/GuardianWardRechargeItemVo.java | 75 ++++ .../app/GuardianWardRechargePackageVo.java | 35 ++ .../app/GuardianWardRechargeResultVo.java | 42 +++ ...mework.cloud.openfeign.FeignClient.imports | 2 +- .../recharge/WalletRechargeController.java | 50 ++- .../mapper/recharge/WalletRechargeMapper.java | 14 +- .../wallet/service/MchPaySettingService.java | 5 + .../wallet/service/WalletRechargeService.java | 45 ++- .../WalletRechargePackageServiceImpl.java | 124 +++++- .../recharge/WalletRechargeServiceImpl.java | 355 +++++++++++++++++- 48 files changed, 1444 insertions(+), 66 deletions(-) create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/VerificationRecordQueryDto.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/VerificationRecordVo.java create mode 100644 pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardPendingReservationCountVo.java create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/VerificationRecordService.java create mode 100644 pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/VerificationRecordServiceImpl.java create mode 100644 pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteUserFaceService.java create mode 100644 pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/SysUserFaceStatusCountVo.java create mode 100644 pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/dto/recharge/app/GuardianWardRechargeCreateDto.java create mode 100644 pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/vo/recharge/app/GuardianWardRechargeContextVo.java create mode 100644 pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/vo/recharge/app/GuardianWardRechargeCreateVo.java create mode 100644 pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/vo/recharge/app/GuardianWardRechargeItemVo.java create mode 100644 pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/vo/recharge/app/GuardianWardRechargePackageVo.java create mode 100644 pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/vo/recharge/app/GuardianWardRechargeResultVo.java diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenCategoryController.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenCategoryController.java index 7950861e2..2b0f25316 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenCategoryController.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenCategoryController.java @@ -1,5 +1,6 @@ package com.bsd.pjcloud.canteen.controller; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.collection.CollUtil; @@ -139,10 +140,14 @@ public class CanteenCategoryController { @Operation(summary = "通过id查询", description = "通过id查询") @GetMapping("/getAllSimple") // @PreAuthorize("@pms.hasPermission('canteen_canteenCategory_view')") - public R> getAllSimple(@RequestParam(value = "categoryType", required = false) String categoryType, - @RequestParam(value = "name", required = false) String name) { + public R> getAllSimple( + @RequestParam(value = "categoryType", required = false) String categoryType, + @RequestParam(value = "name", required = false) String name, + @RequestParam(value = "merchantId", required = false) String merchantId + ) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.like(StringUtils.isNotBlank(name), CanteenCategoryEntity::getName, name); + wrapper.eq(ObjectUtil.isNotEmpty(merchantId), CanteenCategoryEntity::getMerchantId, merchantId); wrapper.orderByAsc(CanteenCategoryEntity::getSort); wrapper.apply(StringUtils.isNotEmpty(categoryType), "FIND_IN_SET({0}, your_column_name)", categoryType); List list = canteenCategoryService.list(wrapper); diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/HandCancelActCodeDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/HandCancelActCodeDto.java index 6c641aaf4..3b60e9ff7 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/HandCancelActCodeDto.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/HandCancelActCodeDto.java @@ -15,4 +15,12 @@ public class HandCancelActCodeDto { @NotNull(message = "用户id不能为空") private Long userId; + /** + * 发起活动核销的终端设备 ID。 + * + * 旧终端未传时保持兼容;新终端传入后复用订单现有 hard_id 字段记录设备归属。 + */ + @Schema(description = "发起核销的设备ID") + private Long hardwareId; + } diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/ReservationMenuQueryDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/ReservationMenuQueryDto.java index 175191a38..a859e936c 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/ReservationMenuQueryDto.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/ReservationMenuQueryDto.java @@ -11,17 +11,25 @@ import lombok.Data; @Data public class ReservationMenuQueryDto { + /** 当前终端设备 ID。 */ @NotNull(message = "设备ID不能为空") @Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED) private Long hardId; + /** 本人身份识别方式。 */ @NotNull(message = "识别方式不能为空") @Schema(description = "识别方式:CARD饭卡、FACE人脸", requiredMode = Schema.RequiredMode.REQUIRED) private ReservationRecognitionTypeEnum recognitionType; + /** 饭卡卡号,仅 CARD 识别时传递。 */ @Schema(description = "饭卡卡号,CARD识别时必填") private String cardNumber; + /** 人脸插件识别出的用户 ID,仅 FACE 识别时传递。 */ @Schema(description = "人脸插件识别出的用户ID,FACE识别时必填") private Long faceUserId; + + /** 是否对返回的用户姓名进行脱敏;旧终端未传时默认脱敏。 */ + @Schema(description = "是否脱敏用户姓名,未传时默认true") + private Boolean maskName; } diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/VerificationRecordQueryDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/VerificationRecordQueryDto.java new file mode 100644 index 000000000..d5c11e735 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/VerificationRecordQueryDto.java @@ -0,0 +1,29 @@ +package com.bsd.pjcloud.hard.api.dto.hard; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDate; + +/** + * 终端核销记录查询参数。 + * + * 设备 ID 是唯一的数据范围入口,客户端不能通过本接口指定商户或用户范围。 + */ +@Data +public class VerificationRecordQueryDto { + + @NotNull(message = "设备ID不能为空") + @Schema(description = "当前终端设备ID", requiredMode = Schema.RequiredMode.REQUIRED) + private Long hardId; + + @Schema(description = "开始日期,格式 yyyy-MM-dd") + private LocalDate startDate; + + @Schema(description = "结束日期,格式 yyyy-MM-dd") + private LocalDate endDate; + + @Schema(description = "核销类型,目前仅支持 RESERVATION 预约核销;为空时同样按预约核销查询") + private String verificationType; +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderDto.java index b26744ea4..6ad8c89e8 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderDto.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderDto.java @@ -38,6 +38,9 @@ public class HardOrderDto extends HardOrderEntity implements BaseIsIncludeTreeIn private LocalDateTime[] createTimeList; + /** 更新时间范围采用左闭右开区间,供按订单实际状态变更时间查询。 */ + private LocalDateTime[] updateTimeList; + private LocalDateTime[] payTimeList; private LocalDateTime[] refundTimeList; @@ -51,6 +54,9 @@ public class HardOrderDto extends HardOrderEntity implements BaseIsIncludeTreeIn private List orderPayTypeCodes; + /** 排除的支付方式编码,与包含条件分开表达,避免调用方枚举所有允许值。 */ + private List excludeOrderPayTypeCodes; + private List hardTypeCodes; private List refundStatusCodes; diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/feign/RemoteHardOrderService.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/feign/RemoteHardOrderService.java index 86168cb53..c0def7721 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/feign/RemoteHardOrderService.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/feign/RemoteHardOrderService.java @@ -8,6 +8,7 @@ import com.bsd.pjcloud.hard.api.dto.order.HardOrderPreferencesDto; import com.bsd.pjcloud.hard.api.dto.order.remote.HardUpdateShopStatusDto; import com.bsd.pjcloud.hard.api.dto.order.remote.HardUpdateTakeOutStatusDto; import com.bsd.pjcloud.hard.api.entity.order.HardOrderEntity; +import com.bsd.pjcloud.hard.api.vo.order.HardPendingReservationCountVo; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -38,4 +39,14 @@ public interface RemoteHardOrderService { @PostMapping("/hard-order/update-shop-status") R updateShopStatus(@RequestBody HardUpdateShopStatusDto dto); + /** + * 批量查询用户今天及以后的待核销预约订单数量。 + * + * @param userIds 实际用餐人用户ID列表 + * @return 各用户待核销预约订单数量 + */ + @NoToken + @PostMapping("/hard-order/inner/pending-reservation-count") + R> listPendingReservationCounts(@RequestBody List userIds); + } diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/ReservationMenuQueryVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/ReservationMenuQueryVo.java index f0fd0e7e6..8a90e45ae 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/ReservationMenuQueryVo.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/ReservationMenuQueryVo.java @@ -6,15 +6,15 @@ import java.time.LocalDate; import java.util.List; /** - * 终端本人预约菜单查询结果,仅包含主副屏展示所需的脱敏信息。 + * 终端本人预约菜单查询结果,仅包含主副屏展示所需的信息。 */ @Data public class ReservationMenuQueryVo { - /** 脱敏后的用餐人姓名。 */ + /** 按终端配置返回的用户姓名;字段名保留用于兼容既有终端。 */ private String maskedName; - /** 用餐人所属部门或班级名称。 */ + /** 用户所属部门名称。 */ private String deptName; /** 当前终端绑定的食堂名称。 */ diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/VerificationRecordVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/VerificationRecordVo.java new file mode 100644 index 000000000..69c713638 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/VerificationRecordVo.java @@ -0,0 +1,40 @@ +package com.bsd.pjcloud.hard.api.vo.android; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** 终端核销记录展示对象,仅返回列表所需字段。 */ +@Data +public class VerificationRecordVo { + + /** 订单主键。 */ + private Long orderId; + + /** 业务订单号。 */ + private String orderNo; + + /** 核销类型稳定编码。 */ + private String verificationType; + + /** 核销类型展示名称。 */ + private String verificationTypeName; + + /** 核销用户姓名。 */ + private String username; + + /** 核销用户所属部门。 */ + private String userDeptName; + + /** 订单商品或活动内容摘要。 */ + private String productDesc; + + /** 原订单支付或核销方式。 */ + private String payTypeName; + + /** 订单总金额,单位为分。 */ + private Integer totalPrice; + + /** 设备完成核销的时间。 */ + private LocalDateTime verificationTime; +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardPendingReservationCountVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardPendingReservationCountVo.java new file mode 100644 index 000000000..e68f116c5 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/order/HardPendingReservationCountVo.java @@ -0,0 +1,20 @@ +package com.bsd.pjcloud.hard.api.vo.order; + +import lombok.Data; + +/** + * 用户待核销预约订单数量。 + */ +@Data +public class HardPendingReservationCountVo { + + /** + * 实际用餐人用户ID。 + */ + private Long userId; + + /** + * 今天及以后已支付但尚未核销的预约订单数量。 + */ + private Long pendingCount; +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/hard/HardApiController.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/hard/HardApiController.java index 618678b23..0953af7ad 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/hard/HardApiController.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/hard/HardApiController.java @@ -1,7 +1,9 @@ package com.bsd.pjcloud.hard.controller.hard; + import com.bsd.pjcloud.common.api.encrypt.annotation.crypto.ApiCryptoAes; import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.bsd.pjcloud.common.core.util.R; import com.bsd.pjcloud.common.idempotent.annotation.Idempotent; import com.bsd.pjcloud.common.security.service.PjcloudUser; @@ -17,6 +19,7 @@ import com.bsd.pjcloud.hard.api.vo.HardInvoiceVo; import com.bsd.pjcloud.hard.api.vo.manager.HardManagerVo; import com.bsd.pjcloud.hard.api.vo.android.HardAdUserVo; import com.bsd.pjcloud.hard.api.vo.android.ReservationMenuQueryVo; +import com.bsd.pjcloud.hard.api.vo.android.VerificationRecordVo; import com.bsd.pjcloud.hard.api.vo.order.HardCarVo; import com.bsd.pjcloud.hard.api.vo.order.HardCreateHardQrCodeScanOrderVo; import com.bsd.pjcloud.hard.api.vo.order.HardOrderVo; @@ -27,6 +30,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; +import org.springdoc.core.annotations.ParameterObject; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -49,6 +53,7 @@ public class HardApiController { private final HardApiService hardApiService; private final HardOrderTotalService hardOrderTotalService; private final ReservationMenuQueryService reservationMenuQueryService; + private final VerificationRecordService verificationRecordService; @PostMapping("/bind") @Operation(summary = "绑定服务端设备") @@ -181,8 +186,15 @@ public class HardApiController { @PostMapping("/reservation-menu/cancel") @Operation(summary = "按订单ID核销预约订单") @Idempotent(expireTime = 1, key = "'/hard-api/ad/reservation-menu/cancel:' + #dto.getOrderId()", info = "正在核销中,请勿重复操作") - public R cancelReservationMenu(@Valid @RequestBody ReservationMenuCancelDto dto) { - return R.ok(reservationMenuQueryService.cancel(dto)); + public R cancelReservationMenu(@Valid @RequestBody ReservationMenuCancelDto dto, + @RequestHeader(value = "DEVICE-ID", required = false) Long hardId) { + return R.ok(reservationMenuQueryService.cancel(dto, hardId)); + } + + @PostMapping("/verification-record/page") + @Operation(summary = "分页查询当前终端核销记录") + public R> verificationRecordPage(@ParameterObject Page page, @RequestHeader("DEVICE-ID") Long hardId, @Valid @RequestBody VerificationRecordQueryDto dto) { + return R.ok(verificationRecordService.page(page, hardId, dto)); } @GetMapping("/chart-latitude") diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderController.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderController.java index c840084b2..cda689eaa 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderController.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderController.java @@ -47,6 +47,7 @@ import com.bsd.pjcloud.hard.api.dto.order.remote.HardUpdateShopStatusDto; import com.bsd.pjcloud.hard.api.dto.order.remote.HardUpdateTakeOutStatusDto; import com.bsd.pjcloud.hard.api.entity.order.HardOrderEntity; import com.bsd.pjcloud.hard.api.vo.order.HardOrderVo; +import com.bsd.pjcloud.hard.api.vo.order.HardPendingReservationCountVo; import com.bsd.pjcloud.hard.api.vo.order.app.HardAppCreateReserveOrdersVo; import com.bsd.pjcloud.hard.api.vo.order.app.HardAppScanPayOrderVo; import com.bsd.pjcloud.hard.api.vo.order.app.HardAppCreateShopOrderVo; @@ -137,6 +138,16 @@ public class HardOrderController { public R updateShopStatus(@RequestBody HardUpdateShopStatusDto dto) { return R.ok(hardwareOrderService.updateShopStatus(dto)); } + + /** + * 批量查询待核销预约数量,调用方必须先完成可访问用户范围校验。 + */ + @Inner + @Operation(summary = "批量查询待核销预约数量", description = "按实际用餐人返回今天及以后已支付预约订单数量") + @PostMapping("/inner/pending-reservation-count") + public R> listPendingReservationCounts(@RequestBody List userIds) { + return R.ok(hardwareOrderService.listPendingReservationCounts(userIds)); + } /* ************************************* end 远程调用 ************************************* */ /* ************************************* start app start ************************************* */ diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardOrderService.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardOrderService.java index 95b58cc1f..c12575784 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardOrderService.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardOrderService.java @@ -29,6 +29,7 @@ import com.bsd.pjcloud.hard.api.entity.order.HardCarEntity; import com.bsd.pjcloud.hard.api.entity.order.HardOrderEntity; import com.bsd.pjcloud.hard.api.vo.order.HardCreateHardQrCodeScanOrderVo; import com.bsd.pjcloud.hard.api.vo.order.HardOrderVo; +import com.bsd.pjcloud.hard.api.vo.order.HardPendingReservationCountVo; import com.bsd.pjcloud.hard.api.vo.order.app.HardAppCreateShopOrderVo; import com.bsd.pjcloud.hard.api.vo.order.app.HardAppCreateReserveOrdersVo; import com.bsd.pjcloud.hard.api.vo.order.app.HardAppScanPayOrderVo; @@ -56,6 +57,11 @@ public interface HardOrderService extends IService { List scopeList(HardOrderDto dto, DataScope dataScope); + /** + * 批量统计今天及以后已支付但尚未核销的预约订单数量。 + */ + List listPendingReservationCounts(List userIds); + HardOrderVo join(Long id); /** diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/ReservationMenuQueryService.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/ReservationMenuQueryService.java index d2c2834b4..b0a15cc33 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/ReservationMenuQueryService.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/ReservationMenuQueryService.java @@ -15,4 +15,7 @@ public interface ReservationMenuQueryService { * 仅按订单 ID 核销一笔预约订单。 */ Boolean cancel(ReservationMenuCancelDto dto); + + /** 设备 ID 只用于记录核销归属,订单选择仍严格由请求体中的 orderId 决定。 */ + Boolean cancel(ReservationMenuCancelDto dto, Long hardId); } diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/VerificationRecordService.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/VerificationRecordService.java new file mode 100644 index 000000000..fe37348a8 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/VerificationRecordService.java @@ -0,0 +1,12 @@ +package com.bsd.pjcloud.hard.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.hard.api.dto.hard.VerificationRecordQueryDto; +import com.bsd.pjcloud.hard.api.vo.android.VerificationRecordVo; + +/** 终端核销记录查询服务。 */ +public interface VerificationRecordService { + + /** 按当前设备和日期分页查询已完成的预约核销订单。 */ + Page page(Page page, Long hardId, VerificationRecordQueryDto dto); +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/ReservationMenuQueryServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/ReservationMenuQueryServiceImpl.java index 15c2b9502..29c5aa422 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/ReservationMenuQueryServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/ReservationMenuQueryServiceImpl.java @@ -73,11 +73,16 @@ public class ReservationMenuQueryServiceImpl implements ReservationMenuQueryServ // 身份与商户条件只在服务端生成,终端无法借此接口查询其他用户或其他食堂。 List orders = hardOrderService.scopeList(query, null); - return aggregate(recognizedUser, terminalMerchant, orders); + return aggregate(recognizedUser, terminalMerchant, orders, dto.getMaskName()); } @Override public Boolean cancel(ReservationMenuCancelDto dto) { + return cancel(dto, null); + } + + @Override + public Boolean cancel(ReservationMenuCancelDto dto, Long hardId) { HardOrderEntity order = hardOrderService.getById(dto.getOrderId()); if (order == null) throw new RuntimeException("预约订单不存在"); if (!Objects.equals(order.getOrderType(), HardOrderTypeEnum.RESERVE.getType()) || !Objects.equals(order.getOrderStatus(), HardOrderStatusEnum.PAY.getType())) { @@ -92,11 +97,20 @@ public class ReservationMenuQueryServiceImpl implements ReservationMenuQueryServ HandCancelCodeDto cancelDto = new HandCancelCodeDto(); cancelDto.setOrderId(order.getId()); - // 当前接口契约只接收订单 ID;-1 沿用商户端和后台手动核销的非终端设备标识。 - cancelDto.setHardwareId(-1L); + // 请求体仍只接收 orderId;设备请求头只记录核销归属,不能参与订单选择。 + cancelDto.setHardwareId(resolveVerificationHardId(order, hardId)); return hardOrderService.handCancelCode(cancelDto); } + private Long resolveVerificationHardId(HardOrderEntity order, Long hardId) { + if (hardId == null) return -1L; + HardManagerEntity hard = hardManagerService.getById(hardId); + if (hard == null || !Objects.equals(hard.getMerchantId(), order.getMerchantId())) { + throw new RuntimeException("当前设备不能核销该预约订单"); + } + return hard.getId(); + } + private static boolean isCancelableRefundStatus(String refundStatus) { return StrUtil.isBlank(refundStatus) || Objects.equals(refundStatus, HardRefundStatusEnum.NONE.getStatus()) @@ -142,7 +156,7 @@ public class ReservationMenuQueryServiceImpl implements ReservationMenuQueryServ return new RecognizedUser(user.getUserId(), user.getName(), user.getDeptId()); } - private ReservationMenuQueryVo aggregate(RecognizedUser user, TerminalMerchant terminalMerchant, List sourceOrders) { + private ReservationMenuQueryVo aggregate(RecognizedUser user, TerminalMerchant terminalMerchant, List sourceOrders, Boolean maskNameEnabled) { List orders = (sourceOrders == null ? List.of() : sourceOrders).stream() .filter(order -> order != null && order.getApptUseDate() != null && StrUtil.isNotBlank(resolveHourType(order))) .sorted(Comparator.comparing(HardOrderVo::getApptUseDate) @@ -185,7 +199,8 @@ public class ReservationMenuQueryServiceImpl implements ReservationMenuQueryServ } ReservationMenuQueryVo result = new ReservationMenuQueryVo(); - result.setMaskedName(maskName(user.name())); + // 旧终端没有 maskName 字段,缺失时必须继续脱敏,避免升级后意外泄露完整姓名。 + result.setMaskedName(Boolean.FALSE.equals(maskNameEnabled) ? normalizedName(user.name()) : maskName(user.name())); result.setDeptName(resolveDeptName(user.deptId())); result.setMerchantName(terminalMerchant.merchant().getName()); result.setTotalDayCount(days.size()); @@ -231,6 +246,10 @@ public class ReservationMenuQueryServiceImpl implements ReservationMenuQueryServ return normalized.length() == 1 ? "*" : normalized.substring(0, 1) + "*"; } + private static String normalizedName(String name) { + return StrUtil.isBlank(name) ? "" : name.trim(); + } + private static T requireData(R result, String message) { if (result == null || !result.isOk() || result.getData() == null) { throw new RuntimeException(message); diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/VerificationRecordServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/VerificationRecordServiceImpl.java new file mode 100644 index 000000000..83e37e25a --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/VerificationRecordServiceImpl.java @@ -0,0 +1,94 @@ +package com.bsd.pjcloud.hard.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.hard.api.constant.order.HardOrderStatusEnum; +import com.bsd.pjcloud.hard.api.constant.order.HardOrderTypeEnum; +import com.bsd.pjcloud.hard.api.constant.order.HardPayTypeEnum; +import com.bsd.pjcloud.hard.api.dto.hard.VerificationRecordQueryDto; +import com.bsd.pjcloud.hard.api.dto.order.HardOrderDto; +import com.bsd.pjcloud.hard.api.entity.manager.HardManagerEntity; +import com.bsd.pjcloud.hard.api.vo.android.VerificationRecordVo; +import com.bsd.pjcloud.hard.api.vo.order.HardOrderVo; +import com.bsd.pjcloud.hard.service.HardManagerService; +import com.bsd.pjcloud.hard.service.HardOrderService; +import com.bsd.pjcloud.hard.service.VerificationRecordService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +@Service +@RequiredArgsConstructor +public class VerificationRecordServiceImpl implements VerificationRecordService { + + private static final String TYPE_RESERVATION = "RESERVATION"; + + private final HardManagerService hardManagerService; + private final HardOrderService hardOrderService; + + @Override + public Page page(Page page, Long hardId, VerificationRecordQueryDto dto) { + if (!Objects.equals(hardId, dto.getHardId())) { + throw new RuntimeException("请求设备与查询设备不一致"); + } + HardManagerEntity hard = hardManagerService.getById(hardId); + if (hard == null) { + throw new RuntimeException("设备不存在或无权访问"); + } + + LocalDate startDate = ObjectUtil.defaultIfNull(dto.getStartDate(), LocalDate.now()); + LocalDate endDate = ObjectUtil.defaultIfNull(dto.getEndDate(), startDate); + if (endDate.isBefore(startDate)) { + throw new RuntimeException("结束日期不能早于开始日期"); + } + if (StrUtil.isNotBlank(dto.getVerificationType()) && !TYPE_RESERVATION.equalsIgnoreCase(dto.getVerificationType().trim())) { + throw new RuntimeException("目前仅支持查询预约核销记录"); + } + + HardOrderDto query = new HardOrderDto(); + query.setHardId(hard.getId()); + query.setOrderType(HardOrderTypeEnum.RESERVE.getType()); + query.setOrderStatusCodes(List.of(HardOrderStatusEnum.CANCEL.getType())); + // 活动核销历史上复用了预约订单类型,支付方式范围需明确排除 ACT_CANCEL。 + query.setExcludeOrderPayTypeCodes(List.of(HardPayTypeEnum.ACT_CANCEL.getType())); + query.setUpdateTimeList(new LocalDateTime[]{startDate.atStartOfDay(), endDate.plusDays(1).atStartOfDay()}); + + long current = Math.max(1, page.getCurrent()); + long size = Math.min(50, Math.max(1, page.getSize())); + // 订单范围、分页、用户和部门信息统一由订单服务处理,核销记录只负责转换展示字段。 + Page source = hardOrderService.scopePageList(new Page<>(current, size), query, null); + List records = source.getRecords().stream() + .map(VerificationRecordServiceImpl::convert) + .toList(); + Page result = new Page<>(source.getCurrent(), source.getSize(), source.getTotal()); + result.setRecords(records); + return result; + } + + private static VerificationRecordVo convert(HardOrderVo order) { + VerificationRecordVo vo = new VerificationRecordVo(); + vo.setOrderId(order.getId()); + vo.setOrderNo(StrUtil.blankToDefault(order.getOrderNo(), String.valueOf(order.getId()))); + vo.setVerificationType(TYPE_RESERVATION); + vo.setVerificationTypeName("预约核销"); + vo.setUsername(StrUtil.blankToDefault(order.getUsername(), "未知用户")); + vo.setUserDeptName(order.getUserDeptName()); + vo.setProductDesc(StrUtil.blankToDefault(order.getProductDesc(), "核销订单")); + HardPayTypeEnum payType = HardPayTypeEnum.valueOfByType(order.getPayType()); + vo.setPayTypeName(payType == null ? "其他方式" : payType.getDescription()); + vo.setTotalPrice(ObjectUtil.defaultIfNull(order.getTotalPrice(), 0)); + vo.setVerificationTime(resolveVerificationTime(order)); + return vo; + } + + private static LocalDateTime resolveVerificationTime(HardOrderVo order) { + if (order.getUpdateTime() != null) return order.getUpdateTime(); + if (order.getPayTime() != null) return order.getPayTime(); + return order.getCreateTime(); + } +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java index d77961640..ec262e66d 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderServiceImpl.java @@ -76,6 +76,7 @@ import com.bsd.pjcloud.hard.api.vo.manager.HardManagerVo; import com.bsd.pjcloud.hard.api.vo.order.HardCarVo; import com.bsd.pjcloud.hard.api.vo.order.HardCreateHardQrCodeScanOrderVo; import com.bsd.pjcloud.hard.api.vo.order.HardOrderVo; +import com.bsd.pjcloud.hard.api.vo.order.HardPendingReservationCountVo; import com.bsd.pjcloud.hard.api.vo.order.app.HardAppCreateShopOrderVo; import com.bsd.pjcloud.hard.api.vo.order.app.HardAppCreateReserveOrdersVo; import com.bsd.pjcloud.hard.api.vo.order.app.HardAppScanPayOrderVo; @@ -135,7 +136,9 @@ import org.springframework.web.context.request.RequestContextHolder; import java.math.BigDecimal; import java.math.RoundingMode; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -212,6 +215,32 @@ public class HardOrderServiceImpl extends ServiceImpl listPendingReservationCounts(List userIds) { + List distinctUserIds = userIds == null ? List.of() : userIds.stream().filter(Objects::nonNull).distinct().toList(); + if (distinctUserIds.isEmpty()) { + return List.of(); + } + LocalDate today = LocalDate.now(ZoneId.of("Asia/Shanghai")); + Map countMap = baseMapper.selectList(new LambdaQueryWrapper() + .select(HardOrderEntity::getUserId) + .in(HardOrderEntity::getUserId, distinctUserIds) + .eq(HardOrderEntity::getOrderType, HardOrderTypeEnum.RESERVE.getType()) + .eq(HardOrderEntity::getOrderStatus, HardOrderStatusEnum.PAY.getType()) + .ge(HardOrderEntity::getApptUseDate, today)) + .stream() + .collect(Collectors.groupingBy(HardOrderEntity::getUserId, LinkedHashMap::new, Collectors.counting())); + return distinctUserIds.stream().map(userId -> { + HardPendingReservationCountVo result = new HardPendingReservationCountVo(); + result.setUserId(userId); + result.setPendingCount(countMap.getOrDefault(userId, 0L)); + return result; + }).toList(); + } + /** * 商户查询订单列表 */ @@ -2421,6 +2450,12 @@ public class HardOrderServiceImpl extends ServiceImpl + + and (a.pay_type is null or a.pay_type not in + + #{v} + + ) + and a.merchant_type in @@ -169,6 +176,10 @@ and a.create_time between #{dto.createTimeList[0]} and #{dto.createTimeList[1]} + + and a.update_time >= #{dto.updateTimeList[0]} + and a.update_time < #{dto.updateTimeList[1]} + and a.appt_use_date between #{dto.apptUseDateTimeList[0]} and #{dto.apptUseDateTimeList[1]} @@ -234,7 +245,14 @@ group by a.id - order by a.create_time desc, a.user_id desc + + + order by a.update_time desc, a.id desc + + + order by a.create_time desc, a.user_id desc + + + select user_id as userId, + sum(case when process_status = #{availableStatus} then 1 else 0 end) as availableCount, + sum(case when process_status = #{pendingStatus} then 1 else 0 end) as pendingCount + from sys_user_face + where del_flag = '0' + and user_id in + + #{userId} + + and process_status in (#{availableStatus}, #{pendingStatus}) + group by user_id + +