feat:订单列表增加预约时间

feat:优化食堂菜品预约统计导出
feat:增加订单识别证据图片功能
This commit is contained in:
dzc committed 2026-08-23 18:49:40 +08:00
1 parent 78ce41a4de
commit d18167e084
36 files changed
+2215 -77

No files matched your search

@@ -0,0 +1,58 @@
package com.bsd.pjcloud.hard.api.constant.order;
import lombok.Getter;
import java.util.Arrays;
/**
* 订单证据图片角色,编码用于唯一位置约束和跨端展示。
*/
@Getter
public enum HardOrderEvidenceRoleEnum {
/**
* 人脸库中被命中的后台参考人脸,由服务端从受信文件中心复制为订单快照。
*/
FACE_REFERENCE("FACE_REFERENCE", "后台参考人脸"),
/**
* 终端实际通过比对的现场人脸裁剪图,由终端异步上传。
*/
FACE_LIVE("FACE_LIVE", "现场识别人脸"),
/**
* 菜谱识别的现场餐盘图片预留角色,当前版本不创建该类证据。
*/
MENU_LIVE("MENU_LIVE", "餐盘现场图");
/**
* 数据库和接口使用的稳定编码。
*/
private final String code;
/**
* PC 订单详情使用的中文名称。
*/
private final String description;
/**
* 构造订单证据图片角色。
*
* @param code 数据库与跨端协议编码
* @param description 中文展示名称
*/
HardOrderEvidenceRoleEnum(String code, String description) {
this.code = code;
this.description = description;
}
/**
* 按协议编码解析图片角色,未知编码返回 {@code null}。
*
* @param code 数据库或接口编码
* @return 对应角色;未知时返回 {@code null}
*/
public static HardOrderEvidenceRoleEnum valueOfCode(String code) {
return Arrays.stream(values()).filter(item -> item.code.equals(code)).findFirst().orElse(null);
}
}
@@ -0,0 +1,58 @@
package com.bsd.pjcloud.hard.api.constant.order;
import lombok.Getter;
import java.util.Arrays;
/**
* 服务端订单证据状态,表达证据是否仍待补传、已经可查看或已永久缺失。
*/
@Getter
public enum HardOrderEvidenceStatusEnum {
/**
* 订单已记录识别元数据,但图片仍等待终端上传或服务端复制。
*/
PENDING("PENDING", "待补传"),
/**
* 文件中心已经保存订单专属图片并可在订单详情中查看。
*/
AVAILABLE("AVAILABLE", "可查看"),
/**
* 本地文件过期、损坏或参考图片变化,已确认无法继续补齐。
*/
UNAVAILABLE("UNAVAILABLE", "永久缺失");
/**
* 数据库和接口使用的稳定编码。
*/
private final String code;
/**
* PC 订单详情使用的中文名称。
*/
private final String description;
/**
* 构造订单证据状态。
*
* @param code 数据库与跨端协议编码
* @param description 中文展示名称
*/
HardOrderEvidenceStatusEnum(String code, String description) {
this.code = code;
this.description = description;
}
/**
* 按协议编码解析证据状态,未知编码返回 {@code null}。
*
* @param code 数据库或接口编码
* @return 对应状态;未知时返回 {@code null}
*/
public static HardOrderEvidenceStatusEnum valueOfCode(String code) {
return Arrays.stream(values()).filter(item -> item.code.equals(code)).findFirst().orElse(null);
}
}
@@ -0,0 +1,53 @@
package com.bsd.pjcloud.hard.api.constant.order;
import lombok.Getter;
import java.util.Arrays;
/**
* 订单证据业务类型,编码同时用于数据库、终端 SQLite 和跨端接口,禁止随展示文案调整。
*/
@Getter
public enum HardOrderEvidenceTypeEnum {
/**
* 人脸识别证据,当前版本保存后台参考人脸和现场识别人脸。
*/
FACE_RECOGNITION("FACE_RECOGNITION", "人脸识别"),
/**
* 菜谱识别证据预留值,当前版本不执行菜谱识别或图片采集。
*/
MENU_RECOGNITION("MENU_RECOGNITION", "菜谱识别");
/**
* 数据库和接口使用的稳定编码。
*/
private final String code;
/**
* PC 订单详情使用的中文名称。
*/
private final String description;
/**
* 构造订单证据业务类型。
*
* @param code 数据库与跨端协议编码
* @param description 中文展示名称
*/
HardOrderEvidenceTypeEnum(String code, String description) {
this.code = code;
this.description = description;
}
/**
* 按协议编码解析证据类型,未知编码返回 {@code null} 供调用方拒绝非法请求。
*
* @param code 数据库或接口编码
* @return 对应类型;未知时返回 {@code null}
*/
public static HardOrderEvidenceTypeEnum valueOfCode(String code) {
return Arrays.stream(values()).filter(item -> item.code.equals(code)).findFirst().orElse(null);
}
}
@@ -4,7 +4,9 @@ import com.bsd.pjcloud.hard.api.constant.HardTypeEnum;
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.order.HardCreateCarDto;
import com.bsd.pjcloud.hard.api.dto.order.HardOrderEvidenceMetadataDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
@@ -75,4 +77,17 @@ public class HardCreateOrderDto {
@Schema(description = "离线支付是否成功")
private Boolean offlinePaySuccess;
/**
* 当前终端应用版本,仅用于订单证据问题追溯,不参与订单计价和支付。
*/
@Schema(description = "终端应用版本")
private String appVersion;
/**
* 人脸支付成功后需要挂接的证据元数据;旧终端不传时保持订单创建协议兼容。
*/
@Schema(description = "订单识别证据元数据")
@Valid
private List<HardOrderEvidenceMetadataDto> evidenceMetadataList;
}
@@ -0,0 +1,127 @@
package com.bsd.pjcloud.hard.api.dto.order;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单创建和证据补传共用的识别元数据,不包含图片字节和服务端文件信息。
*/
@Data
@Schema(description = "订单识别证据元数据")
public class HardOrderEvidenceMetadataDto {
/**
* 证据业务类型,使用 HardOrderEvidenceTypeEnum 的稳定编码。
*/
@NotBlank(message = "证据类型不能为空")
@Size(max = 32, message = "证据类型不能超过32个字符")
@Schema(description = "证据类型")
private String evidenceType;
/**
* 图片角色,使用 HardOrderEvidenceRoleEnum 的稳定编码。
*/
@NotBlank(message = "证据图片角色不能为空")
@Size(max = 32, message = "证据图片角色不能超过32个字符")
@Schema(description = "证据图片角色")
private String evidenceRole;
/**
* 同类型同角色下的图片序号,从0开始;人脸证据固定为0,菜谱识别可递增。
*/
@NotNull(message = "证据图片序号不能为空")
@Min(value = 0, message = "证据图片序号不能小于0")
@Max(value = 99, message = "证据图片序号不能大于99")
@Schema(description = "证据图片序号")
private Integer sequenceNo;
/**
* 识别用户主键;未识别到平台用户时为空,不能由终端使用其他用户替代。
*/
@Schema(description = "识别用户主键")
private Long userId;
/**
* 虹软搜索样本编号;仅人脸识别角色使用,服务端据此校验后台参考人脸。
*/
@Size(max = 64, message = "人脸样本编号不能超过64个字符")
@Schema(description = "人脸样本编号")
private String faceSampleId;
/**
* 识别时终端人脸库记录的参考图片地址,仅作为服务端复制参考图的线索,不直接作为可访问地址返回。
*/
@Size(max = 512, message = "参考人脸地址不能超过512个字符")
@Schema(description = "参考人脸地址")
private String referenceImageUrl;
/**
* 识别时终端人脸快照对应的后台更新时间;旧终端允许为空,新终端用于识别同地址内容替换。
*/
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@Schema(description = "参考人脸来源版本")
private LocalDateTime referenceImageVersion;
/**
* 识别时实际相似度,取值0到1;为空表示当前证据类型不使用相似度。
*/
@DecimalMin(value = "0", message = "相似度不能小于0")
@DecimalMax(value = "1", message = "相似度不能大于1")
@Schema(description = "识别相似度")
private BigDecimal similarity;
/**
* 识别时终端生效的相似度阈值快照,取值0到1。
*/
@DecimalMin(value = "0", message = "相似度阈值不能小于0")
@DecimalMax(value = "1", message = "相似度阈值不能大于1")
@Schema(description = "相似度阈值快照")
private BigDecimal similarityThreshold;
/**
* 终端实际完成识别的本地时间;为空时服务端仅保留订单创建时间。
*/
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@Schema(description = "终端采集时间")
private LocalDateTime capturedAt;
/**
* 终端按原始证据文件计算的 SHA-256;图片尚未生成或永久缺失时允许为空。
*/
@Pattern(regexp = "(?i)^[0-9a-f]{64}$", message = "图片哈希必须为64位SHA-256")
@Schema(description = "图片SHA-256")
private String imageHash;
/**
* 终端证据文件大小,单位字节;参考图无法读取或文件尚未生成时允许为空。
*/
@Min(value = 0, message = "图片大小不能小于0")
@Schema(description = "图片大小,单位字节")
private Long imageSize;
/**
* 终端解码后的图片宽度,单位像素。
*/
@Min(value = 1, message = "图片宽度必须大于0")
@Schema(description = "图片宽度,单位像素")
private Integer imageWidth;
/**
* 终端解码后的图片高度,单位像素。
*/
@Min(value = 1, message = "图片高度必须大于0")
@Schema(description = "图片高度,单位像素")
private Integer imageHeight;
}
@@ -0,0 +1,46 @@
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;
/**
* 终端提交订单证据元数据或永久缺失结果的请求。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "订单证据元数据提交请求")
public class HardOrderEvidenceSubmitDto extends HardOrderEvidenceMetadataDto {
/**
* 终端本地稳定订单号,用于接口响应丢失后的服务端订单对账。
*/
@NotBlank(message = "终端订单号不能为空")
@Size(max = 128, message = "终端订单号不能超过128个字符")
@Schema(description = "终端订单号")
private String androidOrderCode;
/**
* 终端请求写入的目标状态,仅允许 PENDING 或 UNAVAILABLE;现场图片上传成功由服务端改为 AVAILABLE。
*/
@NotBlank(message = "证据状态不能为空")
@Size(max = 32, message = "证据状态不能超过32个字符")
@Schema(description = "证据状态")
private String status;
/**
* 永久缺失原因;状态为 UNAVAILABLE 时必填,禁止包含本地完整敏感路径。
*/
@Size(max = 500, message = "失败原因不能超过500个字符")
@Schema(description = "永久缺失原因")
private String failureReason;
/**
* 产生识别证据的终端应用版本,用于问题追溯。
*/
@Size(max = 64, message = "应用版本不能超过64个字符")
@Schema(description = "终端应用版本")
private String appVersion;
}
@@ -0,0 +1,31 @@
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;
/**
* 终端上传订单现场证据图片时随 multipart 一并提交的元数据。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "订单现场证据上传请求")
public class HardOrderEvidenceUploadDto extends HardOrderEvidenceMetadataDto {
/**
* 终端本地稳定订单号,服务端通过该编号解析真实订单主键。
*/
@NotBlank(message = "终端订单号不能为空")
@Size(max = 128, message = "终端订单号不能超过128个字符")
@Schema(description = "终端订单号")
private String androidOrderCode;
/**
* 产生识别证据的终端应用版本,用于问题追溯。
*/
@Size(max = 64, message = "应用版本不能超过64个字符")
@Schema(description = "终端应用版本")
private String appVersion;
}
@@ -0,0 +1,208 @@
package com.bsd.pjcloud.hard.api.entity.order;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.bsd.pjcloud.common.core.util.TenantTable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单识别证据图片,从属于 hard_order 并长期保存争议处理所需的文件快照和识别元数据。
*/
@Data
@TenantTable
@TableName("hard_order_evidence_image")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "订单识别证据图片")
public class HardOrderEvidenceImageEntity extends Model<HardOrderEvidenceImageEntity> {
/**
* 证据记录自增主键。
*/
@TableId(type = IdType.AUTO)
@Schema(description = "证据记录主键")
private Long id;
/**
* 服务端订单主键,来源于 hard_order.id。
*/
@Schema(description = "订单主键")
private Long orderId;
/**
* 终端本地稳定订单号快照,来源于 hard_order.android_order_code。
*/
@Schema(description = "终端订单号")
private String androidOrderCode;
/**
* 证据业务类型,保存 HardOrderEvidenceTypeEnum.code。
*/
@Schema(description = "证据类型")
private String evidenceType;
/**
* 图片角色,保存 HardOrderEvidenceRoleEnum.code。
*/
@Schema(description = "证据图片角色")
private String evidenceRole;
/**
* 同一订单、类型和角色下的图片序号,从0开始。
*/
@Schema(description = "证据图片序号")
private Integer sequenceNo;
/**
* 识别用户主键快照;无法识别到平台用户时允许为空。
*/
@Schema(description = "识别用户主键")
private Long userId;
/**
* 虹软人脸样本编号快照,来源于 sys_user_face.auto_code。
*/
@Schema(description = "人脸样本编号")
private String faceSampleId;
/**
* 识别时终端命中的后台参考图片地址快照,仅用于与 UPMS 权威记录比对,不直接作为展示地址。
*/
@Schema(description = "参考人脸来源地址快照")
private String referenceImageUrl;
/**
* 终端识别相似度快照,取值0到1。
*/
@Schema(description = "识别相似度")
private BigDecimal similarity;
/**
* 终端识别相似度阈值快照,取值0到1。
*/
@Schema(description = "识别相似度阈值")
private BigDecimal similarityThreshold;
/**
* 产生证据的服务端设备主键,来源于 hard_order.hard_id。
*/
@Schema(description = "设备主键")
private Long hardId;
/**
* 终端实际完成识别的本地时间,用于与订单时间交叉核验。
*/
@Schema(description = "终端采集时间")
private LocalDateTime capturedAt;
/**
* 文件中心 sys_file 主键;状态非 AVAILABLE 时允许为空。
*/
@Schema(description = "文件中心记录主键")
private Long sysFileId;
/**
* 文件中心对象名,仅供服务端读取或下载,不由终端提交。
*/
@Schema(description = "文件中心对象名")
private String storageFileName;
/**
* PC 可使用统一请求层访问的文件相对地址。
*/
@Schema(description = "图片访问地址")
private String imageUrl;
/**
* 实际保存文件的 SHA-256,用于幂等和防止证据被静默替换。
*/
@Schema(description = "图片SHA-256")
private String imageHash;
/**
* 实际保存文件大小,单位字节。
*/
@Schema(description = "图片大小,单位字节")
private Long imageSize;
/**
* 实际解码图片宽度,单位像素。
*/
@Schema(description = "图片宽度,单位像素")
private Integer imageWidth;
/**
* 实际解码图片高度,单位像素。
*/
@Schema(description = "图片高度,单位像素")
private Integer imageHeight;
/**
* 证据状态,保存 HardOrderEvidenceStatusEnum.code。
*/
@Schema(description = "证据状态")
private String status;
/**
* 永久缺失或服务端复制失败的摘要,不保存终端完整文件路径。
*/
@Schema(description = "失败原因")
private String failureReason;
/**
* 产生证据的终端应用版本快照。
*/
@Schema(description = "终端应用版本")
private String appVersion;
/**
* 创建人,由 MyBatis 填充器写入。
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建人")
private String createBy;
/**
* 证据占位记录创建时间,由 MyBatis 填充器写入。
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
* 修改人,由 MyBatis 填充器在状态变化时写入。
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改人")
private String updateBy;
/**
* 最近一次状态、文件或错误信息更新时间。
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改时间")
private LocalDateTime updateTime;
/**
* 逻辑删除标记;订单退款、取消和删除不会联动改变该值。
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
@Schema(description = "删除标记")
private String delFlag;
/**
* 租户主键,由当前请求租户上下文写入并由数据层自动隔离。
*/
@Schema(description = "租户主键")
private Long tenantId;
}
@@ -0,0 +1,33 @@
package com.bsd.pjcloud.hard.api.vo.order;
import com.bsd.pjcloud.hard.api.entity.order.HardOrderEvidenceImageEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* PC 订单详情使用的订单证据图片,附带稳定编码对应的中文名称。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "订单识别证据图片详情")
public class HardOrderEvidenceImageVo extends HardOrderEvidenceImageEntity {
/**
* 证据类型中文名称,未知历史编码时回退原编码。
*/
@Schema(description = "证据类型名称")
private String evidenceTypeName;
/**
* 图片角色中文名称,未知历史编码时回退原编码。
*/
@Schema(description = "证据图片角色名称")
private String evidenceRoleName;
/**
* 证据状态中文名称,未知历史编码时回退原编码。
*/
@Schema(description = "证据状态名称")
private String statusName;
}
@@ -0,0 +1,34 @@
package com.bsd.pjcloud.hard.api.vo.order;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 终端按本地订单号对账时返回的服务端订单与证据快照。
*/
@Data
@Builder
@Schema(description = "订单证据对账结果")
public class HardOrderEvidenceResolveVo {
/**
* 服务端订单主键。
*/
@Schema(description = "服务端订单主键")
private Long orderId;
/**
* 服务端订单状态,终端仅在现有支付流程确认成功后上传证据。
*/
@Schema(description = "订单状态")
private String orderStatus;
/**
* 当前服务端已登记的证据位置和状态,用于终端幂等跳过成功项。
*/
@Schema(description = "证据列表")
private List<HardOrderEvidenceImageVo> evidenceImages;
}
@@ -0,0 +1,50 @@
package com.bsd.pjcloud.hard.api.vo.order;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
/**
* 终端提交元数据、复制参考图或上传现场图后的幂等处理结果。
*/
@Data
@Builder
@Schema(description = "订单证据处理结果")
public class HardOrderEvidenceUploadResultVo {
/**
* 服务端证据记录主键。
*/
@Schema(description = "证据记录主键")
private Long evidenceId;
/**
* 服务端订单主键。
*/
@Schema(description = "订单主键")
private Long orderId;
/**
* 处理后的证据状态。
*/
@Schema(description = "证据状态")
private String status;
/**
* AVAILABLE 状态下的图片访问地址,其他状态允许为空。
*/
@Schema(description = "图片访问地址")
private String imageUrl;
/**
* 是否实际上传或复制了新文件;幂等命中时为 false。
*/
@Schema(description = "是否新写入文件")
private Boolean uploaded;
/**
* 面向终端同步日志的简短处理结果。
*/
@Schema(description = "处理结果说明")
private String message;
}
@@ -102,4 +102,9 @@ public class HardOrderVo extends HardOrderEntity implements BaseUserDeptInterfac
* 当前访问者是否可以查看预约核销码。
*/
private Boolean canViewReserveCode;
/**
* 订单详情中的长期识别证据列表,按证据类型、角色和序号排序。
*/
private List<HardOrderEvidenceImageVo> evidenceImages;
}
@@ -11,7 +11,8 @@ import java.time.LocalDate;
/**
* 食堂预约菜品统计自定义导出行。
*
* <p>一组日期和餐段按汇总行、商户部门行、商品明细行组织;不同层级只填写所属列,配合自定义合并处理器形成树形报表。</p>
* <p>一组日期和餐段按汇总行、商户部门行、商品明细行组织;商品统计在同一商户部门块内按名称合并套餐展开后的明细,
* 不同层级只填写所属列,配合自定义合并处理器形成树形报表。</p>
*/
@Data
public class HardCanteenProductApptExcelVo {
@@ -87,6 +88,19 @@ public class HardCanteenProductApptExcelVo {
@NumberFormat("#,##0.00")
private BigDecimal productTotalPrice;
/** 商品统计中的商品名称,同一商户部门内按规范化后的名称合并。 */
@ExcelProperty({"商品统计", "商品名称"})
private String statisticProductName;
/** 商品统计中的商品总数量,按退款扣减及套餐展开后的有效明细累计。 */
@ExcelProperty({"商品统计", "商品总数量"})
private Long statisticProductCount;
/** 商品统计中的商品总价,单位为元,按退款扣减及套餐展开后的有效明细累计。 */
@ExcelProperty({"商品统计", "商品总价格"})
@NumberFormat("#,##0.00")
private BigDecimal statisticProductTotalPrice;
/** 导出行层级,仅供合并处理器识别,不写入 Excel。 */
@ExcelIgnore
private String rowLevel;