diff --git a/db/20260823_add_hard_order_verification_time.sql b/db/20260823_add_hard_order_verification_time.sql new file mode 100644 index 00000000..344c3c96 --- /dev/null +++ b/db/20260823_add_hard_order_verification_time.sql @@ -0,0 +1,16 @@ +-- 硬件订单新增独立核销时间。 +-- 历史二维码核销订单只能使用最后更新时间近似回填;外卖等非核销完成状态不参与回填。 +-- 新核销订单由业务事务写入准确时间,后续订单更新不再改变该值。 + +ALTER TABLE `pjcloudx_hardware`.`hard_order` + ADD COLUMN `verification_time` datetime DEFAULT NULL + COMMENT '订单实际核销完成时间,后续订单更新不得覆盖' AFTER `refund_status`, + ADD KEY `idx_hard_order_verification_time` + (`tenant_id`, `verification_time`, `del_flag`); + +UPDATE `pjcloudx_hardware`.`hard_order` +SET `verification_time` = `update_time` +WHERE ((`order_status` = '20' AND `order_type` IN ('1', '2', '3', '4')) + OR `pay_type` IN ('5', '6', '50')) + AND `verification_time` IS NULL + AND `update_time` IS NOT NULL; 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 e7ba33ec..80487abf 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,9 +38,12 @@ public class HardOrderDto extends HardOrderEntity implements BaseIsIncludeTreeIn private LocalDateTime[] createTimeList; - /** 更新时间范围采用左闭右开区间,供按订单实际状态变更时间查询。 */ + /** 更新时间范围采用左闭右开区间,供兼容旧调用方按订单状态变更时间查询。 */ private LocalDateTime[] updateTimeList; + /** 订单核销时间闭区间,仅按订单实际核销完成时间查询。 */ + private LocalDateTime[] verificationTimeList; + private LocalDateTime[] payTimeList; private LocalDateTime[] refundTimeList; diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderEvidenceOrderRefDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderEvidenceOrderRefDto.java new file mode 100644 index 00000000..510a7e4e --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderEvidenceOrderRefDto.java @@ -0,0 +1,45 @@ +package com.bsd.pjcloud.hard.api.dto.order; + +import cn.hutool.core.util.StrUtil; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 终端订单识别证据的订单定位参数。 + * + *

消费机现场下单继续使用终端订单号对账;预约核销订单不是当前终端创建的,核销成功后改用 + * 服务端订单 ID 定位。两种方式都必须结合请求头中的设备 ID 校验订单归属。

+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@Schema(description = "订单识别证据的订单定位参数") +public abstract class HardOrderEvidenceOrderRefDto extends HardOrderEvidenceMetadataDto { + + /** + * 终端本地稳定订单号,用于消费机现场下单后对账;预约核销证据允许为空。 + */ + @Size(max = 128, message = "终端订单号不能超过128个字符") + @Schema(description = "终端订单号,与服务端订单ID二选一") + private String androidOrderCode; + + /** + * 已存在的服务端订单主键,仅用于当前设备已经成功核销的预约订单;现场下单证据允许为空。 + */ + @Positive(message = "服务端订单ID必须大于0") + @Schema(description = "服务端订单ID,与终端订单号二选一") + private Long orderId; + + /** + * 校验至少提供一种订单定位信息,防止无条件查询或写入订单证据。 + * + * @return 提供终端订单号或服务端订单 ID 时返回 {@code true} + */ + @AssertTrue(message = "终端订单号和服务端订单ID不能同时为空") + public boolean isOrderReferencePresent() { + return StrUtil.isNotBlank(androidOrderCode) || orderId != null; + } +} diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderEvidenceSubmitDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderEvidenceSubmitDto.java index 99cd0168..343b0978 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderEvidenceSubmitDto.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderEvidenceSubmitDto.java @@ -12,15 +12,7 @@ import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = true) @Schema(description = "订单证据元数据提交请求") -public class HardOrderEvidenceSubmitDto extends HardOrderEvidenceMetadataDto { - - /** - * 终端本地稳定订单号,用于接口响应丢失后的服务端订单对账。 - */ - @NotBlank(message = "终端订单号不能为空") - @Size(max = 128, message = "终端订单号不能超过128个字符") - @Schema(description = "终端订单号") - private String androidOrderCode; +public class HardOrderEvidenceSubmitDto extends HardOrderEvidenceOrderRefDto { /** * 终端请求写入的目标状态,仅允许 PENDING 或 UNAVAILABLE;现场图片上传成功由服务端改为 AVAILABLE。 diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderEvidenceUploadDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderEvidenceUploadDto.java index 1b02f844..4b8f3363 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderEvidenceUploadDto.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/order/HardOrderEvidenceUploadDto.java @@ -1,7 +1,6 @@ package com.bsd.pjcloud.hard.api.dto.order; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Data; import lombok.EqualsAndHashCode; @@ -12,15 +11,7 @@ import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = true) @Schema(description = "订单现场证据上传请求") -public class HardOrderEvidenceUploadDto extends HardOrderEvidenceMetadataDto { - - /** - * 终端本地稳定订单号,服务端通过该编号解析真实订单主键。 - */ - @NotBlank(message = "终端订单号不能为空") - @Size(max = 128, message = "终端订单号不能超过128个字符") - @Schema(description = "终端订单号") - private String androidOrderCode; +public class HardOrderEvidenceUploadDto extends HardOrderEvidenceOrderRefDto { /** * 产生识别证据的终端应用版本,用于问题追溯。 diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/entity/order/HardOrderEntity.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/entity/order/HardOrderEntity.java index 7b73513d..d124723d 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/entity/order/HardOrderEntity.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/entity/order/HardOrderEntity.java @@ -235,6 +235,12 @@ public class HardOrderEntity extends Model { @Schema(description = "退款状态") private String refundStatus; + /** + * 订单核销完成时间;仅在订单实际完成核销时写入,避免被后续退款等订单更新覆盖。 + */ + @Schema(description = "订单核销时间") + private LocalDateTime verificationTime; + /** * 订单取消时间(退款订单) */ diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/entity/order/HardOrderEvidenceImageEntity.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/entity/order/HardOrderEvidenceImageEntity.java index 77bd85aa..e7e6c59b 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/entity/order/HardOrderEvidenceImageEntity.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/entity/order/HardOrderEvidenceImageEntity.java @@ -39,9 +39,9 @@ public class HardOrderEvidenceImageEntity extends Model resolve(@RequestHeader("DEVICE-ID") Long hardId, - @RequestParam String androidOrderCode) { - return R.ok(hardOrderEvidenceImageService.resolve(hardId, androidOrderCode)); + @RequestParam(required = false) String androidOrderCode, + @RequestParam(required = false) Long orderId) { + return R.ok(hardOrderEvidenceImageService.resolve(hardId, androidOrderCode, orderId)); } /** diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardOrderEvidenceImageService.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardOrderEvidenceImageService.java index f46577a8..2312cabb 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardOrderEvidenceImageService.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/HardOrderEvidenceImageService.java @@ -19,13 +19,14 @@ import java.util.List; public interface HardOrderEvidenceImageService extends IService { /** - * 按终端订单号对账服务端订单和已登记证据。 + * 按终端订单号或服务端订单 ID 对账订单和已登记证据。 * * @param hardId 请求头中的当前设备主键 - * @param androidOrderCode 终端本地稳定订单号 + * @param androidOrderCode 终端本地稳定订单号,预约核销证据允许为空 + * @param orderId 服务端订单主键,现场下单证据允许为空 * @return 服务端订单及证据状态 */ - HardOrderEvidenceResolveVo resolve(Long hardId, String androidOrderCode); + HardOrderEvidenceResolveVo resolve(Long hardId, String androidOrderCode, Long orderId); /** * 提交证据元数据、触发参考图复制或报告永久缺失。 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 index c4a17493..b9364e4d 100644 --- 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 @@ -22,15 +22,28 @@ 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; + /** + * 分页查询指定设备在日期范围内完成的预约核销记录。 + * + * @param page 分页参数,页码最小为 1、每页最大为 50 + * @param hardId 当前请求头中的设备主键 + * @param dto 核销类型、设备和日期范围查询条件 + * @return 已按核销时间倒序排列的终端核销记录分页 + */ @Override public Page page(Page page, Long hardId, VerificationRecordQueryDto dto) { if (!Objects.equals(hardId, dto.getHardId())) { @@ -57,7 +70,7 @@ public class VerificationRecordServiceImpl implements VerificationRecordService 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()}); + query.setVerificationTimeList(new LocalDateTime[]{startDate.atStartOfDay(), endDate.atTime(23, 59, 59)}); long current = Math.max(1, page.getCurrent()); long size = Math.min(50, Math.max(1, page.getSize())); @@ -71,6 +84,12 @@ public class VerificationRecordServiceImpl implements VerificationRecordService return result; } + /** + * 将订单查询结果转换为终端核销记录展示对象。 + * + * @param order 已完成核销的预约订单 + * @return 终端核销记录 + */ private static VerificationRecordVo convert(HardOrderVo order) { VerificationRecordVo vo = new VerificationRecordVo(); vo.setOrderId(order.getId()); @@ -87,7 +106,15 @@ public class VerificationRecordServiceImpl implements VerificationRecordService return vo; } + /** + * 解析核销记录的展示时间;新数据使用独立核销时间,旧数据按更新时间、支付时间、创建时间依次降级。 + * + * @param order 已核销订单 + * @return 可供终端展示的核销时间,历史数据全部缺失时返回 {@code null} + */ private static LocalDateTime resolveVerificationTime(HardOrderVo order) { + if (order.getVerificationTime() != null) return order.getVerificationTime(); + // 历史订单在新增独立字段前没有核销时间,兼容返回最近更新时间,避免旧记录在终端列表中变为空。 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/HardCanteenApptTotalExportServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardCanteenApptTotalExportServiceImpl.java index 8a8698af..8a09f42f 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardCanteenApptTotalExportServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardCanteenApptTotalExportServiceImpl.java @@ -885,7 +885,7 @@ public class HardCanteenApptTotalExportServiceImpl implements HardCanteenApptTot detail.setMerchantName(merchantNameMap.get(order.getMerchantId())); detail.setTotalPrice(order.getTotalPrice()); detail.setOrderStatus(order.getOrderStatus()); - detail.setUpdateTime(order.getUpdateTime()); + detail.setUpdateTime(order.getVerificationTime() != null ? order.getVerificationTime() : order.getUpdateTime()); detail.setUpdateBy(order.getUpdateBy()); return detail; } 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 414e6cf0..86004770 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 @@ -773,7 +773,7 @@ public class HardCanteenApptTotalServiceImpl implements HardCanteenApptTotalServ detail.setMerchantName(merchantNameMap.get(order.getMerchantId())); detail.setTotalPrice(order.getTotalPrice()); detail.setOrderStatus(order.getOrderStatus()); - detail.setUpdateTime(order.getUpdateTime()); + detail.setUpdateTime(order.getVerificationTime() != null ? order.getVerificationTime() : order.getUpdateTime()); detail.setUpdateBy(order.getUpdateBy()); return detail; } diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderEvidenceImageServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderEvidenceImageServiceImpl.java index dd1f3583..f54f3ba1 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderEvidenceImageServiceImpl.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/order/HardOrderEvidenceImageServiceImpl.java @@ -101,6 +101,11 @@ public class HardOrderEvidenceImageServiceImpl extends ServiceImpl预约订单只有在核销成功后才会把 {@code hard_id} 更新为当前设备,因此终端即使提前知道 + * 服务端订单 ID,也不能在核销前或其他设备上写入证据。

+ * + * @param hardId 当前设备主键 + * @param androidOrderCode 终端本地订单号,允许为空 + * @param orderId 服务端订单主键,允许为空 + * @param requirePaid 是否要求订单处于已支付、已退款或已核销状态 + * @return 属于当前设备的订单 */ - private HardOrderEntity requireOrder(Long hardId, String androidOrderCode, boolean requirePaid) { + private HardOrderEntity requireOrder(Long hardId, String androidOrderCode, Long orderId, boolean requirePaid) { if (hardId == null) throw new RuntimeException("DEVICE-ID不能为空"); - if (StrUtil.isBlank(androidOrderCode)) throw new RuntimeException("终端订单号不能为空"); + if (StrUtil.isBlank(androidOrderCode) && orderId == null) { + throw new RuntimeException("终端订单号和服务端订单ID不能同时为空"); + } HardManagerEntity hard = hardManagerMapper.selectById(hardId); if (hard == null) throw new RuntimeException("当前租户下设备不存在"); - HardOrderEntity order = hardOrderMapper.selectOne(new LambdaQueryWrapper() + LambdaQueryWrapper query = new LambdaQueryWrapper() .eq(HardOrderEntity::getHardId, hardId) - .eq(HardOrderEntity::getAndroidOrderCode, androidOrderCode)); + .eq(orderId != null, HardOrderEntity::getId, orderId) + .eq(StrUtil.isNotBlank(androidOrderCode), HardOrderEntity::getAndroidOrderCode, androidOrderCode); + HardOrderEntity order = hardOrderMapper.selectOne(query); if (order == null) throw new RuntimeException("订单不存在或不属于当前设备"); if (requirePaid && !HardOrderStatusEnum.getPayPrice().contains(order.getOrderStatus())) { throw new RuntimeException("订单尚未确认支付成功,暂不能保存识别证据"); @@ -332,13 +355,29 @@ public class HardOrderEvidenceImageServiceImpl extends ServiceImpl + + and a.verification_time between #{dto.verificationTimeList[0]} and #{dto.verificationTimeList[1]} + and a.appt_use_date between #{dto.apptUseDateTimeList[0]} and #{dto.apptUseDateTimeList[1]} @@ -315,6 +319,9 @@ + + order by a.verification_time desc, a.id desc + order by a.update_time desc, a.id desc @@ -338,6 +345,9 @@ + + order by a.verification_time desc, a.id desc + order by a.update_time desc, a.id desc