diff --git a/.gitignore b/.gitignore index 9e2781708..7e9076a93 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,9 @@ gen_code ### node ### node_modules + +### AI 辅助工具 ### +.video_agent +.mimosa +.v2c +.zcode diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/TerminalOrderRecordQueryDto.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/TerminalOrderRecordQueryDto.java new file mode 100644 index 000000000..8548854f2 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/dto/hard/TerminalOrderRecordQueryDto.java @@ -0,0 +1,38 @@ +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; +import java.util.List; + +/** + * 终端交易记录查询参数。 + * + * 设备 ID 是唯一的数据范围入口:scope 为空或 DEVICE 时仅查询本机订单, + * scope 为 MERCHANT 时按服务端解析的终端所属商户查询全部设备订单, + * 客户端不能通过本接口指定其他商户或用户范围。 + */ +@Data +public class TerminalOrderRecordQueryDto { + + @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 = "数据范围:DEVICE 仅本机(默认),MERCHANT 当前终端所属商户全部设备") + private String scope; + + @Schema(description = "订单状态编码集合,为空时查询全部状态") + private List orderStatusCodes; + + @Schema(description = "用餐时段编码,为空或 -1 时不区分") + private String hourType; +} 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 index d5c11e735..a333e56aa 100644 --- 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 @@ -26,4 +26,7 @@ public class VerificationRecordQueryDto { @Schema(description = "核销类型,目前仅支持 RESERVATION 预约核销;为空时同样按预约核销查询") private String verificationType; + + @Schema(description = "数据范围:DEVICE 仅本机(默认),MERCHANT 当前终端所属商户全部设备") + private String scope; } diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/TerminalOrderRecordVo.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/TerminalOrderRecordVo.java new file mode 100644 index 000000000..561b104c0 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/vo/android/TerminalOrderRecordVo.java @@ -0,0 +1,61 @@ +package com.bsd.pjcloud.hard.api.vo.android; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** 终端交易记录展示对象,仅返回交易记录列表所需字段。 */ +@Data +public class TerminalOrderRecordVo { + + /** 订单主键。 */ + private Long orderId; + + /** 业务订单号。 */ + private String orderNo; + + /** 订单类型稳定编码。 */ + private String orderType; + + /** 订单类型展示名称。 */ + private String orderTypeName; + + /** 订单状态稳定编码。 */ + private String orderStatus; + + /** 订单状态展示名称。 */ + private String orderStatusName; + + /** 支付方式稳定编码。 */ + private String payType; + + /** 支付方式展示名称。 */ + private String payTypeName; + + /** 下单用户姓名。 */ + private String username; + + /** 下单用户所属部门。 */ + private String userDeptName; + + /** 订单商品摘要。 */ + private String productDesc; + + /** 用餐时段编码。 */ + private String hourType; + + /** 商品总金额(分)。 */ + private Integer totalPrice; + + /** 额外支付(分)。 */ + private Integer extraPrice; + + /** 实际支付(分)。 */ + private Integer payPrice; + + /** 完成交易的设备名称;本机数据范围下与服务端解析的终端一致。 */ + private String hardName; + + /** 下单时间。 */ + private LocalDateTime createTime; +} 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 acf001abc..25f32d130 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 @@ -28,6 +28,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.TerminalOrderRecordVo; 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.HardAggregateOrderCodePayStatusVo; @@ -67,6 +68,7 @@ public class HardApiController { private final HardOrderTotalService hardOrderTotalService; private final ReservationMenuQueryService reservationMenuQueryService; private final VerificationRecordService verificationRecordService; + private final TerminalOrderRecordService terminalOrderRecordService; private final HardTerminalFaceService hardTerminalFaceService; private final HardTerminalPrintService hardTerminalPrintService; @@ -485,6 +487,12 @@ public class HardApiController { return R.ok(verificationRecordService.page(page, hardId, dto)); } + @PostMapping("/order-record/page") + @Operation(summary = "分页查询当前终端交易记录") + public R> orderRecordPage(@ParameterObject Page page, @RequestHeader("DEVICE-ID") Long hardId, @Valid @RequestBody TerminalOrderRecordQueryDto dto) { + return R.ok(terminalOrderRecordService.page(page, hardId, dto)); + } + @GetMapping("/chart-latitude") @Operation(summary = "统计每天、每周、每月、每年的交易人数和次数") public R chartLatitude(@RequestParam Long merchantId) { diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/TerminalOrderRecordService.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/TerminalOrderRecordService.java new file mode 100644 index 000000000..77dca9c9f --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/TerminalOrderRecordService.java @@ -0,0 +1,19 @@ +package com.bsd.pjcloud.hard.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.hard.api.dto.hard.TerminalOrderRecordQueryDto; +import com.bsd.pjcloud.hard.api.vo.android.TerminalOrderRecordVo; + +/** 终端交易记录查询服务。 */ +public interface TerminalOrderRecordService { + + /** + * 分页查询终端交易记录。 + * + * @param page 分页参数,页码最小为 1、每页最大为 50 + * @param hardId 当前请求头中的设备主键 + * @param dto 数据范围、日期、状态和时段查询条件 + * @return 已按下单时间倒序排列的终端交易记录分页 + */ + Page page(Page page, Long hardId, TerminalOrderRecordQueryDto dto); +} diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/TerminalOrderRecordServiceImpl.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/TerminalOrderRecordServiceImpl.java new file mode 100644 index 000000000..9aa1c4477 --- /dev/null +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/service/impl/TerminalOrderRecordServiceImpl.java @@ -0,0 +1,126 @@ +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.TerminalOrderRecordQueryDto; +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.TerminalOrderRecordVo; +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.TerminalOrderRecordService; +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 TerminalOrderRecordServiceImpl implements TerminalOrderRecordService { + + /** 全部数据范围:按终端所属商户查询商户下全部设备的交易记录。 */ + private static final String SCOPE_MERCHANT = "MERCHANT"; + + /** 终端不区分用餐时段的编码。 */ + private static final String HOUR_TYPE_ALL = "-1"; + + /** 设备服务,用于校验请求设备存在并解析所属商户。 */ + private final HardManagerService hardManagerService; + + /** 订单服务,用于按设备/商户、订单状态和下单时间分页查询。 */ + private final HardOrderService hardOrderService; + + @Override + public Page page(Page page, Long hardId, TerminalOrderRecordQueryDto 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("结束日期不能早于开始日期"); + } + + HardOrderDto query = new HardOrderDto(); + if (SCOPE_MERCHANT.equalsIgnoreCase(StrUtil.trimToEmpty(dto.getScope()))) { + // 全部数据:范围来自服务端解析的终端所属商户,客户端不能自行指定商户。 + query.setMerchantId(hard.getMerchantId()); + } else { + query.setHardId(hard.getId()); + } + query.setCreateTimeList(new LocalDateTime[]{startDate.atStartOfDay(), endDate.atTime(23, 59, 59)}); + query.setOrderStatusCodes(dto.getOrderStatusCodes()); + if (StrUtil.isNotBlank(dto.getHourType()) && !HOUR_TYPE_ALL.equals(dto.getHourType().trim())) { + query.setHourType(dto.getHourType().trim()); + } + + 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(TerminalOrderRecordServiceImpl::convert) + .toList(); + Page result = new Page<>(source.getCurrent(), source.getSize(), source.getTotal()); + result.setRecords(records); + return result; + } + + /** + * 将订单查询结果转换为终端交易记录展示对象。 + * + * @param order 订单分页查询结果 + * @return 终端交易记录 + */ + private static TerminalOrderRecordVo convert(HardOrderVo order) { + TerminalOrderRecordVo vo = new TerminalOrderRecordVo(); + vo.setOrderId(order.getId()); + vo.setOrderNo(StrUtil.blankToDefault(order.getOrderNo(), String.valueOf(order.getId()))); + vo.setOrderType(order.getOrderType()); + HardOrderTypeEnum orderType = HardOrderTypeEnum.valueOfByType(order.getOrderType()); + vo.setOrderTypeName(orderType == null ? "其他类型" : orderType.getDescription()); + vo.setOrderStatus(order.getOrderStatus()); + // 状态枚举查找对未知编码抛异常,外卖/商城等终端外状态需安全降级。 + vo.setOrderStatusName(resolveStatusName(order.getOrderStatus())); + vo.setPayType(order.getPayType()); + HardPayTypeEnum payType = HardPayTypeEnum.valueOfByType(order.getPayType()); + vo.setPayTypeName(payType == null ? "其他方式" : payType.getDescription()); + vo.setUsername(StrUtil.blankToDefault(order.getUsername(), "未知用户")); + vo.setUserDeptName(order.getUserDeptName()); + vo.setProductDesc(StrUtil.blankToDefault(order.getProductDesc(), "未知商品")); + vo.setHourType(order.getHourType()); + vo.setTotalPrice(ObjectUtil.defaultIfNull(order.getTotalPrice(), 0)); + vo.setExtraPrice(ObjectUtil.defaultIfNull(order.getExtraPrice(), 0)); + vo.setPayPrice(ObjectUtil.defaultIfNull(order.getPayPrice(), 0)); + vo.setHardName(order.getHardName()); + vo.setCreateTime(order.getCreateTime()); + return vo; + } + + /** 状态枚举 {@code valueOfByType} 对未知编码会抛异常,这里安全降级为通用名称。 */ + private static String resolveStatusName(String statusCode) { + if (StrUtil.isBlank(statusCode)) { + return "未知状态"; + } + for (HardOrderStatusEnum status : HardOrderStatusEnum.values()) { + if (Objects.equals(statusCode, status.getType())) { + return status.getDescription(); + } + } + return "其他状态"; + } +} 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 b9364e4de..d0631c532 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 @@ -30,6 +30,9 @@ public class VerificationRecordServiceImpl implements VerificationRecordService /** 当前接口支持的预约核销类型协议值。 */ private static final String TYPE_RESERVATION = "RESERVATION"; + /** 全部数据范围:按终端所属商户查询商户下全部设备的核销记录。 */ + private static final String SCOPE_MERCHANT = "MERCHANT"; + /** 设备服务,用于校验请求设备存在且与查询设备一致。 */ private final HardManagerService hardManagerService; @@ -64,7 +67,12 @@ public class VerificationRecordServiceImpl implements VerificationRecordService } HardOrderDto query = new HardOrderDto(); - query.setHardId(hard.getId()); + if (SCOPE_MERCHANT.equalsIgnoreCase(StrUtil.trimToEmpty(dto.getScope()))) { + // 全部数据:范围来自服务端解析的终端所属商户,客户端不能自行指定商户。 + query.setMerchantId(hard.getMerchantId()); + } else { + query.setHardId(hard.getId()); + } query.setOrderType(HardOrderTypeEnum.RESERVE.getType()); query.setApptIdOrApptRuleIdNotNull(true); query.setOrderStatusCodes(List.of(HardOrderStatusEnum.CANCEL.getType()));