实现M002 V4持久化基线
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Reservation V4 订单任务创建草稿。用于按 source_message_id + order_ref 幂等创建订单任务聚合。
|
||||
*/
|
||||
public record ReservationV4OrderTaskDraft(
|
||||
/** 酒店 ID。 */
|
||||
String hotelId,
|
||||
/** SourceMessage Inbox 内部 ID。 */
|
||||
Long sourceMessageId,
|
||||
/** AI 回调批次 ID。 */
|
||||
Long aiBatchId,
|
||||
/** V4 包内订单引用。 */
|
||||
String orderRef,
|
||||
/** order_contexts[] 中的一基序号。 */
|
||||
Integer orderContextIndex,
|
||||
/** 绑定的本地订单投影 ID,归属未解决时可为空。 */
|
||||
Long orderId,
|
||||
/** 目标订单业务类型,例如 GROUP、FIT。 */
|
||||
String targetBookingType,
|
||||
/** 目标订单定位类型。 */
|
||||
String targetLocatorType,
|
||||
/** 目标订单定位值。 */
|
||||
String targetLocatorValue,
|
||||
/** 订单归属解析状态。 */
|
||||
String targetResolutionStatus,
|
||||
/** 订单任务状态快照。 */
|
||||
String orderTaskStatus,
|
||||
/** 来源邮件接收 UTC 时间。 */
|
||||
LocalDateTime sourceReceivedAt,
|
||||
/** 当前写入 UTC 时间。 */
|
||||
LocalDateTime now
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Reservation V4 订单任务快照。屏蔽 Entity 细节供 Service 和后续查询接口使用。
|
||||
*/
|
||||
public record ReservationV4OrderTaskSnapshot(
|
||||
/** V4 订单任务 ID。 */
|
||||
Long id,
|
||||
/** 酒店 ID。 */
|
||||
String hotelId,
|
||||
/** SourceMessage Inbox 内部 ID。 */
|
||||
Long sourceMessageId,
|
||||
/** AI 回调批次 ID。 */
|
||||
Long aiBatchId,
|
||||
/** V4 包内订单引用。 */
|
||||
String orderRef,
|
||||
/** order_contexts[] 中的一基序号。 */
|
||||
Integer orderContextIndex,
|
||||
/** 绑定的本地订单投影 ID,归属未解决时可为空。 */
|
||||
Long orderId,
|
||||
/** 目标订单业务类型。 */
|
||||
String targetBookingType,
|
||||
/** 目标订单定位类型。 */
|
||||
String targetLocatorType,
|
||||
/** 目标订单定位值。 */
|
||||
String targetLocatorValue,
|
||||
/** 订单归属解析状态。 */
|
||||
String targetResolutionStatus,
|
||||
/** 订单任务状态快照。 */
|
||||
String orderTaskStatus,
|
||||
/** 来源邮件接收 UTC 时间。 */
|
||||
LocalDateTime sourceReceivedAt,
|
||||
/** 乐观锁版本。 */
|
||||
Long version,
|
||||
/** 记录创建 UTC 时间。 */
|
||||
LocalDateTime createdAt,
|
||||
/** 记录更新 UTC 时间。 */
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Reservation V4 来源通知创建草稿。第一版用于 S10 纯通知入库。
|
||||
*/
|
||||
public record ReservationV4SourceNotificationDraft(
|
||||
/** 酒店 ID。 */
|
||||
String hotelId,
|
||||
/** SourceMessage Inbox 内部 ID。 */
|
||||
Long sourceMessageId,
|
||||
/** AI 回调批次 ID。 */
|
||||
Long aiBatchId,
|
||||
/** AI transition ID,S10 第一版可为空。 */
|
||||
Long aiTransitionId,
|
||||
/** 包级路由码,第一版用于 S10。 */
|
||||
String routeCode,
|
||||
/** 通知状态。 */
|
||||
String notificationStatus,
|
||||
/** 来源通知原始 AI 片段或包级摘要 JSON。 */
|
||||
String rawPayloadJson,
|
||||
/** 来源邮件接收 UTC 时间。 */
|
||||
LocalDateTime sourceReceivedAt,
|
||||
/** 当前写入 UTC 时间。 */
|
||||
LocalDateTime now
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Reservation V4 来源通知快照。用于后续 S10 通知详情和确认接口。
|
||||
*/
|
||||
public record ReservationV4SourceNotificationSnapshot(
|
||||
/** V4 来源通知 ID。 */
|
||||
Long id,
|
||||
/** 酒店 ID。 */
|
||||
String hotelId,
|
||||
/** SourceMessage Inbox 内部 ID。 */
|
||||
Long sourceMessageId,
|
||||
/** AI 回调批次 ID。 */
|
||||
Long aiBatchId,
|
||||
/** AI transition ID,S10 第一版可为空。 */
|
||||
Long aiTransitionId,
|
||||
/** 包级路由码。 */
|
||||
String routeCode,
|
||||
/** 通知状态。 */
|
||||
String notificationStatus,
|
||||
/** 来源通知原始 AI 片段或包级摘要 JSON。 */
|
||||
String rawPayloadJson,
|
||||
/** 确认处理人标识。 */
|
||||
String ackBy,
|
||||
/** 确认处理 UTC 时间。 */
|
||||
LocalDateTime ackAt,
|
||||
/** 来源邮件接收 UTC 时间。 */
|
||||
LocalDateTime sourceReceivedAt,
|
||||
/** 乐观锁版本。 */
|
||||
Long version,
|
||||
/** 记录创建 UTC 时间。 */
|
||||
LocalDateTime createdAt,
|
||||
/** 记录更新 UTC 时间。 */
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Reservation V4 任务卡创建草稿。非 event 卡 source_event_index 允许为空,由 Repository 固定写入 0。
|
||||
*/
|
||||
public record ReservationV4TaskCardDraft(
|
||||
/** 酒店 ID。 */
|
||||
String hotelId,
|
||||
/** V4 订单任务 ID。 */
|
||||
Long v4OrderTaskId,
|
||||
/** SourceMessage Inbox 内部 ID。 */
|
||||
Long sourceMessageId,
|
||||
/** AI transition ID,非 event 卡可为空。 */
|
||||
Long aiTransitionId,
|
||||
/** 卡片类型。 */
|
||||
String cardType,
|
||||
/** 业务事件类型,非 event 卡为空。 */
|
||||
String eventType,
|
||||
/** message_events[] 中的一基序号;非 event 卡为空并写入 0。 */
|
||||
Integer sourceEventIndex,
|
||||
/** 同一订单任务内卡片排序。 */
|
||||
Integer cardSortOrder,
|
||||
/** 卡片状态。 */
|
||||
String cardStatus,
|
||||
/** 人工复核状态,非复核卡为空。 */
|
||||
String reviewStatus,
|
||||
/** AI 原始卡片 payload JSON 快照。 */
|
||||
String aiPayloadJson,
|
||||
/** 前端展示用 payload JSON 快照。 */
|
||||
String displayPayloadJson,
|
||||
/** 第一版后端校验错误 JSON。 */
|
||||
String validationErrorsJson,
|
||||
/** 当前写入 UTC 时间。 */
|
||||
LocalDateTime now
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Reservation V4 任务卡快照。保存 AI 原始卡、展示卡、确认卡和复核结果的当前数据库视图。
|
||||
*/
|
||||
public record ReservationV4TaskCardSnapshot(
|
||||
/** V4 任务卡 ID。 */
|
||||
Long id,
|
||||
/** 酒店 ID。 */
|
||||
String hotelId,
|
||||
/** V4 订单任务 ID。 */
|
||||
Long v4OrderTaskId,
|
||||
/** SourceMessage Inbox 内部 ID。 */
|
||||
Long sourceMessageId,
|
||||
/** AI transition ID,非 event 卡可为空。 */
|
||||
Long aiTransitionId,
|
||||
/** 卡片类型。 */
|
||||
String cardType,
|
||||
/** 业务事件类型,非 event 卡为空。 */
|
||||
String eventType,
|
||||
/** message_events[] 中的一基序号;非 event 卡固定为 0。 */
|
||||
Integer sourceEventIndex,
|
||||
/** 同一订单任务内卡片排序。 */
|
||||
Integer cardSortOrder,
|
||||
/** 卡片状态。 */
|
||||
String cardStatus,
|
||||
/** 人工复核状态,非复核卡为空。 */
|
||||
String reviewStatus,
|
||||
/** AI 原始卡片 payload JSON 快照。 */
|
||||
String aiPayloadJson,
|
||||
/** 前端展示用 payload JSON 快照。 */
|
||||
String displayPayloadJson,
|
||||
/** 用户最终确认 payload JSON。 */
|
||||
String confirmedPayloadJson,
|
||||
/** 人工复核解阻结果 JSON。 */
|
||||
String reviewResolutionJson,
|
||||
/** 第一版后端校验错误 JSON。 */
|
||||
String validationErrorsJson,
|
||||
/** 最终确认人标识。 */
|
||||
String confirmedBy,
|
||||
/** 最终确认 UTC 时间。 */
|
||||
LocalDateTime confirmedAt,
|
||||
/** 乐观锁版本。 */
|
||||
Long version,
|
||||
/** 记录创建 UTC 时间。 */
|
||||
LocalDateTime createdAt,
|
||||
/** 记录更新 UTC 时间。 */
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.enums;
|
||||
|
||||
/**
|
||||
* Reservation V4 任务卡状态。
|
||||
*/
|
||||
public enum ReservationV4CardStatus {
|
||||
/** 只读展示卡,不允许用户编辑或确认。 */
|
||||
READONLY,
|
||||
/** 待用户确认的业务卡。 */
|
||||
PENDING_CONFIRM,
|
||||
/** 待人工复核解阻的业务卡。 */
|
||||
REVIEW_REQUIRED,
|
||||
/** 已由用户确认完成的业务卡。 */
|
||||
CONFIRMED
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.enums;
|
||||
|
||||
/**
|
||||
* Reservation V4 任务卡类型。
|
||||
*/
|
||||
public enum ReservationV4CardType {
|
||||
/** 来源邮件展示卡。 */
|
||||
SOURCE_MESSAGE_DISPLAY,
|
||||
/** 订单级 Basic Information 卡。 */
|
||||
BASIC_INFORMATION,
|
||||
/** 房型、房晚和房数等客房信息卡。 */
|
||||
ROOM_INFORMATION,
|
||||
/** 预订备注追踪卡。 */
|
||||
TRACE_RESERVATION_NOTES,
|
||||
/** Rooming List 业务卡。 */
|
||||
ROOMING_LIST,
|
||||
/** Payment 业务卡。 */
|
||||
PAYMENT
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.enums;
|
||||
|
||||
/**
|
||||
* Reservation V4 来源通知状态。
|
||||
*/
|
||||
public enum ReservationV4NotificationStatus {
|
||||
/** 需要用户确认已读或已处理。 */
|
||||
ACK_REQUIRED,
|
||||
/** 用户已经确认处理。 */
|
||||
ACKED
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.enums;
|
||||
|
||||
/**
|
||||
* Reservation V4 订单任务状态快照。
|
||||
*/
|
||||
public enum ReservationV4OrderTaskStatus {
|
||||
/** 订单任务仍有待处理卡片。 */
|
||||
OPEN,
|
||||
/** 订单任务下所有需要处理的卡片均已完成。 */
|
||||
COMPLETED
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.enums;
|
||||
|
||||
/**
|
||||
* Reservation V4 订单归属解析状态。
|
||||
*/
|
||||
public enum ReservationV4TargetResolutionStatus {
|
||||
/** 已明确匹配到目标订单或可创建的新订单上下文。 */
|
||||
RESOLVED,
|
||||
/** 缺少足够信息,等待人工确认订单归属。 */
|
||||
UNRESOLVED,
|
||||
/** 匹配到多个候选订单,需要人工处理冲突。 */
|
||||
CONFLICT
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Reservation V4 订单任务实体。承载同一 SourceMessage + order_ref 下的订单任务聚合。
|
||||
*/
|
||||
@TableName("workflow_reservation_v4_order_task")
|
||||
public class ReservationV4OrderTaskEntity {
|
||||
|
||||
/** V4 订单任务 ID。 */
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
/** 酒店 ID。 */
|
||||
private String hotelId;
|
||||
/** SourceMessage Inbox 内部 ID。 */
|
||||
private Long sourceMessageId;
|
||||
/** AI 回调批次 ID。 */
|
||||
private Long aiBatchId;
|
||||
/** V4 包内订单引用。 */
|
||||
private String orderRef;
|
||||
/** order_contexts[] 中的一基序号。 */
|
||||
private Integer orderContextIndex;
|
||||
/** 绑定的本地订单投影 ID,归属未解决时可为空。 */
|
||||
private Long orderId;
|
||||
/** 目标订单业务类型,例如 GROUP、FIT。 */
|
||||
private String targetBookingType;
|
||||
/** 目标订单定位类型。 */
|
||||
private String targetLocatorType;
|
||||
/** 目标订单定位值。 */
|
||||
private String targetLocatorValue;
|
||||
/** 订单归属解析状态。 */
|
||||
private String targetResolutionStatus;
|
||||
/** 订单任务状态快照。 */
|
||||
private String orderTaskStatus;
|
||||
/** 来源邮件接收 UTC 时间。 */
|
||||
private LocalDateTime sourceReceivedAt;
|
||||
/** 乐观锁版本。 */
|
||||
private Long version;
|
||||
/** 记录创建 UTC 时间。 */
|
||||
private LocalDateTime createdAt;
|
||||
/** 记录更新 UTC 时间。 */
|
||||
private LocalDateTime updatedAt;
|
||||
/** 逻辑删除 UTC 时间。 */
|
||||
private LocalDateTime logicDeletedAt;
|
||||
/** 逻辑删除原因。 */
|
||||
private String logicDeletedReason;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public String getHotelId() { return hotelId; }
|
||||
public void setHotelId(String hotelId) { this.hotelId = hotelId; }
|
||||
public Long getSourceMessageId() { return sourceMessageId; }
|
||||
public void setSourceMessageId(Long sourceMessageId) { this.sourceMessageId = sourceMessageId; }
|
||||
public Long getAiBatchId() { return aiBatchId; }
|
||||
public void setAiBatchId(Long aiBatchId) { this.aiBatchId = aiBatchId; }
|
||||
public String getOrderRef() { return orderRef; }
|
||||
public void setOrderRef(String orderRef) { this.orderRef = orderRef; }
|
||||
public Integer getOrderContextIndex() { return orderContextIndex; }
|
||||
public void setOrderContextIndex(Integer orderContextIndex) { this.orderContextIndex = orderContextIndex; }
|
||||
public Long getOrderId() { return orderId; }
|
||||
public void setOrderId(Long orderId) { this.orderId = orderId; }
|
||||
public String getTargetBookingType() { return targetBookingType; }
|
||||
public void setTargetBookingType(String targetBookingType) { this.targetBookingType = targetBookingType; }
|
||||
public String getTargetLocatorType() { return targetLocatorType; }
|
||||
public void setTargetLocatorType(String targetLocatorType) { this.targetLocatorType = targetLocatorType; }
|
||||
public String getTargetLocatorValue() { return targetLocatorValue; }
|
||||
public void setTargetLocatorValue(String targetLocatorValue) { this.targetLocatorValue = targetLocatorValue; }
|
||||
public String getTargetResolutionStatus() { return targetResolutionStatus; }
|
||||
public void setTargetResolutionStatus(String targetResolutionStatus) { this.targetResolutionStatus = targetResolutionStatus; }
|
||||
public String getOrderTaskStatus() { return orderTaskStatus; }
|
||||
public void setOrderTaskStatus(String orderTaskStatus) { this.orderTaskStatus = orderTaskStatus; }
|
||||
public LocalDateTime getSourceReceivedAt() { return sourceReceivedAt; }
|
||||
public void setSourceReceivedAt(LocalDateTime sourceReceivedAt) { this.sourceReceivedAt = sourceReceivedAt; }
|
||||
public Long getVersion() { return version; }
|
||||
public void setVersion(Long version) { this.version = version; }
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||
public LocalDateTime getLogicDeletedAt() { return logicDeletedAt; }
|
||||
public void setLogicDeletedAt(LocalDateTime logicDeletedAt) { this.logicDeletedAt = logicDeletedAt; }
|
||||
public String getLogicDeletedReason() { return logicDeletedReason; }
|
||||
public void setLogicDeletedReason(String logicDeletedReason) { this.logicDeletedReason = logicDeletedReason; }
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Reservation V4 来源通知实体。第一版用于保存 S10 纯通知,不挂订单任务。
|
||||
*/
|
||||
@TableName("workflow_reservation_v4_source_notification")
|
||||
public class ReservationV4SourceNotificationEntity {
|
||||
|
||||
/** V4 来源通知 ID。 */
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
/** 酒店 ID。 */
|
||||
private String hotelId;
|
||||
/** SourceMessage Inbox 内部 ID。 */
|
||||
private Long sourceMessageId;
|
||||
/** AI 回调批次 ID。 */
|
||||
private Long aiBatchId;
|
||||
/** AI transition ID,S10 第一版可为空。 */
|
||||
private Long aiTransitionId;
|
||||
/** 包级路由码。 */
|
||||
private String routeCode;
|
||||
/** 通知状态。 */
|
||||
private String notificationStatus;
|
||||
/** 来源通知原始 AI 片段或包级摘要 JSON。 */
|
||||
private String rawPayloadJson;
|
||||
/** 确认处理人标识。 */
|
||||
private String ackBy;
|
||||
/** 确认处理 UTC 时间。 */
|
||||
private LocalDateTime ackAt;
|
||||
/** 来源邮件接收 UTC 时间。 */
|
||||
private LocalDateTime sourceReceivedAt;
|
||||
/** 乐观锁版本。 */
|
||||
private Long version;
|
||||
/** 记录创建 UTC 时间。 */
|
||||
private LocalDateTime createdAt;
|
||||
/** 记录更新 UTC 时间。 */
|
||||
private LocalDateTime updatedAt;
|
||||
/** 逻辑删除 UTC 时间。 */
|
||||
private LocalDateTime logicDeletedAt;
|
||||
/** 逻辑删除原因。 */
|
||||
private String logicDeletedReason;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public String getHotelId() { return hotelId; }
|
||||
public void setHotelId(String hotelId) { this.hotelId = hotelId; }
|
||||
public Long getSourceMessageId() { return sourceMessageId; }
|
||||
public void setSourceMessageId(Long sourceMessageId) { this.sourceMessageId = sourceMessageId; }
|
||||
public Long getAiBatchId() { return aiBatchId; }
|
||||
public void setAiBatchId(Long aiBatchId) { this.aiBatchId = aiBatchId; }
|
||||
public Long getAiTransitionId() { return aiTransitionId; }
|
||||
public void setAiTransitionId(Long aiTransitionId) { this.aiTransitionId = aiTransitionId; }
|
||||
public String getRouteCode() { return routeCode; }
|
||||
public void setRouteCode(String routeCode) { this.routeCode = routeCode; }
|
||||
public String getNotificationStatus() { return notificationStatus; }
|
||||
public void setNotificationStatus(String notificationStatus) { this.notificationStatus = notificationStatus; }
|
||||
public String getRawPayloadJson() { return rawPayloadJson; }
|
||||
public void setRawPayloadJson(String rawPayloadJson) { this.rawPayloadJson = rawPayloadJson; }
|
||||
public String getAckBy() { return ackBy; }
|
||||
public void setAckBy(String ackBy) { this.ackBy = ackBy; }
|
||||
public LocalDateTime getAckAt() { return ackAt; }
|
||||
public void setAckAt(LocalDateTime ackAt) { this.ackAt = ackAt; }
|
||||
public LocalDateTime getSourceReceivedAt() { return sourceReceivedAt; }
|
||||
public void setSourceReceivedAt(LocalDateTime sourceReceivedAt) { this.sourceReceivedAt = sourceReceivedAt; }
|
||||
public Long getVersion() { return version; }
|
||||
public void setVersion(Long version) { this.version = version; }
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||
public LocalDateTime getLogicDeletedAt() { return logicDeletedAt; }
|
||||
public void setLogicDeletedAt(LocalDateTime logicDeletedAt) { this.logicDeletedAt = logicDeletedAt; }
|
||||
public String getLogicDeletedReason() { return logicDeletedReason; }
|
||||
public void setLogicDeletedReason(String logicDeletedReason) { this.logicDeletedReason = logicDeletedReason; }
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Reservation V4 任务卡实体。承载来源邮件展示卡、Basic Information 卡和具体业务卡。
|
||||
*/
|
||||
@TableName("workflow_reservation_v4_task_card")
|
||||
public class ReservationV4TaskCardEntity {
|
||||
|
||||
/** V4 任务卡 ID。 */
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
/** 酒店 ID。 */
|
||||
private String hotelId;
|
||||
/** V4 订单任务 ID。 */
|
||||
private Long v4OrderTaskId;
|
||||
/** SourceMessage Inbox 内部 ID。 */
|
||||
private Long sourceMessageId;
|
||||
/** AI transition ID,非 event 卡可为空。 */
|
||||
private Long aiTransitionId;
|
||||
/** 卡片类型。 */
|
||||
private String cardType;
|
||||
/** 业务事件类型,非 event 卡为空。 */
|
||||
private String eventType;
|
||||
/** message_events[] 中的一基序号;非 event 卡固定为 0。 */
|
||||
private Integer sourceEventIndex;
|
||||
/** 同一订单任务内卡片排序。 */
|
||||
private Integer cardSortOrder;
|
||||
/** 卡片状态。 */
|
||||
private String cardStatus;
|
||||
/** 人工复核状态,非复核卡为空。 */
|
||||
private String reviewStatus;
|
||||
/** AI 原始卡片 payload JSON 快照。 */
|
||||
private String aiPayloadJson;
|
||||
/** 前端展示用 payload JSON 快照。 */
|
||||
private String displayPayloadJson;
|
||||
/** 用户最终确认 payload JSON。 */
|
||||
private String confirmedPayloadJson;
|
||||
/** 人工复核解阻结果 JSON。 */
|
||||
private String reviewResolutionJson;
|
||||
/** 第一版后端校验错误 JSON。 */
|
||||
private String validationErrorsJson;
|
||||
/** 最终确认人标识。 */
|
||||
private String confirmedBy;
|
||||
/** 最终确认 UTC 时间。 */
|
||||
private LocalDateTime confirmedAt;
|
||||
/** 乐观锁版本。 */
|
||||
private Long version;
|
||||
/** 记录创建 UTC 时间。 */
|
||||
private LocalDateTime createdAt;
|
||||
/** 记录更新 UTC 时间。 */
|
||||
private LocalDateTime updatedAt;
|
||||
/** 逻辑删除 UTC 时间。 */
|
||||
private LocalDateTime logicDeletedAt;
|
||||
/** 逻辑删除原因。 */
|
||||
private String logicDeletedReason;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public String getHotelId() { return hotelId; }
|
||||
public void setHotelId(String hotelId) { this.hotelId = hotelId; }
|
||||
public Long getV4OrderTaskId() { return v4OrderTaskId; }
|
||||
public void setV4OrderTaskId(Long v4OrderTaskId) { this.v4OrderTaskId = v4OrderTaskId; }
|
||||
public Long getSourceMessageId() { return sourceMessageId; }
|
||||
public void setSourceMessageId(Long sourceMessageId) { this.sourceMessageId = sourceMessageId; }
|
||||
public Long getAiTransitionId() { return aiTransitionId; }
|
||||
public void setAiTransitionId(Long aiTransitionId) { this.aiTransitionId = aiTransitionId; }
|
||||
public String getCardType() { return cardType; }
|
||||
public void setCardType(String cardType) { this.cardType = cardType; }
|
||||
public String getEventType() { return eventType; }
|
||||
public void setEventType(String eventType) { this.eventType = eventType; }
|
||||
public Integer getSourceEventIndex() { return sourceEventIndex; }
|
||||
public void setSourceEventIndex(Integer sourceEventIndex) { this.sourceEventIndex = sourceEventIndex; }
|
||||
public Integer getCardSortOrder() { return cardSortOrder; }
|
||||
public void setCardSortOrder(Integer cardSortOrder) { this.cardSortOrder = cardSortOrder; }
|
||||
public String getCardStatus() { return cardStatus; }
|
||||
public void setCardStatus(String cardStatus) { this.cardStatus = cardStatus; }
|
||||
public String getReviewStatus() { return reviewStatus; }
|
||||
public void setReviewStatus(String reviewStatus) { this.reviewStatus = reviewStatus; }
|
||||
public String getAiPayloadJson() { return aiPayloadJson; }
|
||||
public void setAiPayloadJson(String aiPayloadJson) { this.aiPayloadJson = aiPayloadJson; }
|
||||
public String getDisplayPayloadJson() { return displayPayloadJson; }
|
||||
public void setDisplayPayloadJson(String displayPayloadJson) { this.displayPayloadJson = displayPayloadJson; }
|
||||
public String getConfirmedPayloadJson() { return confirmedPayloadJson; }
|
||||
public void setConfirmedPayloadJson(String confirmedPayloadJson) { this.confirmedPayloadJson = confirmedPayloadJson; }
|
||||
public String getReviewResolutionJson() { return reviewResolutionJson; }
|
||||
public void setReviewResolutionJson(String reviewResolutionJson) { this.reviewResolutionJson = reviewResolutionJson; }
|
||||
public String getValidationErrorsJson() { return validationErrorsJson; }
|
||||
public void setValidationErrorsJson(String validationErrorsJson) { this.validationErrorsJson = validationErrorsJson; }
|
||||
public String getConfirmedBy() { return confirmedBy; }
|
||||
public void setConfirmedBy(String confirmedBy) { this.confirmedBy = confirmedBy; }
|
||||
public LocalDateTime getConfirmedAt() { return confirmedAt; }
|
||||
public void setConfirmedAt(LocalDateTime confirmedAt) { this.confirmedAt = confirmedAt; }
|
||||
public Long getVersion() { return version; }
|
||||
public void setVersion(Long version) { this.version = version; }
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||
public LocalDateTime getLogicDeletedAt() { return logicDeletedAt; }
|
||||
public void setLogicDeletedAt(LocalDateTime logicDeletedAt) { this.logicDeletedAt = logicDeletedAt; }
|
||||
public String getLogicDeletedReason() { return logicDeletedReason; }
|
||||
public void setLogicDeletedReason(String logicDeletedReason) { this.logicDeletedReason = logicDeletedReason; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.mapper;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationV4OrderTaskEntity;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* Reservation V4 订单任务 Mapper。自带 CRUD 由 MyBatis-Plus 提供。
|
||||
*/
|
||||
@Mapper
|
||||
public interface ReservationV4OrderTaskMapper extends BaseMapper<ReservationV4OrderTaskEntity> {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.mapper;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationV4SourceNotificationEntity;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* Reservation V4 来源通知 Mapper。自带 CRUD 由 MyBatis-Plus 提供。
|
||||
*/
|
||||
@Mapper
|
||||
public interface ReservationV4SourceNotificationMapper extends BaseMapper<ReservationV4SourceNotificationEntity> {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.mapper;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationV4TaskCardEntity;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* Reservation V4 任务卡 Mapper。自带 CRUD 由 MyBatis-Plus 提供。
|
||||
*/
|
||||
@Mapper
|
||||
public interface ReservationV4TaskCardMapper extends BaseMapper<ReservationV4TaskCardEntity> {
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.repository;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4SourceNotificationDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4SourceNotificationSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationV4SourceNotificationEntity;
|
||||
import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationV4SourceNotificationMapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* Reservation V4 来源通知 MyBatis-Plus 持久化实现。
|
||||
*/
|
||||
@Repository
|
||||
public class MybatisReservationV4SourceNotificationRepository implements ReservationV4SourceNotificationRepository {
|
||||
|
||||
private final ReservationV4SourceNotificationMapper sourceNotificationMapper;
|
||||
|
||||
/**
|
||||
* 注入来源通知 Mapper,Repository 负责幂等和快照转换。
|
||||
*/
|
||||
public MybatisReservationV4SourceNotificationRepository(
|
||||
ReservationV4SourceNotificationMapper sourceNotificationMapper) {
|
||||
this.sourceNotificationMapper = sourceNotificationMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按来源通知 ID 查询当前快照。
|
||||
*/
|
||||
@Override
|
||||
public Optional<ReservationV4SourceNotificationSnapshot> findSourceNotificationById(
|
||||
String hotelId,
|
||||
Long notificationId) {
|
||||
ReservationV4SourceNotificationEntity entity = sourceNotificationMapper.selectOne(
|
||||
Wrappers.<ReservationV4SourceNotificationEntity>lambdaQuery()
|
||||
.eq(ReservationV4SourceNotificationEntity::getHotelId, hotelId)
|
||||
.eq(ReservationV4SourceNotificationEntity::getId, notificationId)
|
||||
.isNull(ReservationV4SourceNotificationEntity::getLogicDeletedAt)
|
||||
.last("LIMIT 1"));
|
||||
return Optional.ofNullable(entity).map(this::toSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 SourceMessage 和 AI 批次查询来源通知,用于幂等判断。
|
||||
*/
|
||||
@Override
|
||||
public Optional<ReservationV4SourceNotificationSnapshot> findSourceNotificationBySourceMessageAndBatch(
|
||||
String hotelId,
|
||||
Long sourceMessageId,
|
||||
Long aiBatchId) {
|
||||
ReservationV4SourceNotificationEntity entity = sourceNotificationMapper.selectOne(
|
||||
Wrappers.<ReservationV4SourceNotificationEntity>lambdaQuery()
|
||||
.eq(ReservationV4SourceNotificationEntity::getHotelId, hotelId)
|
||||
.eq(ReservationV4SourceNotificationEntity::getSourceMessageId, sourceMessageId)
|
||||
.eq(ReservationV4SourceNotificationEntity::getAiBatchId, aiBatchId)
|
||||
.isNull(ReservationV4SourceNotificationEntity::getLogicDeletedAt)
|
||||
.last("LIMIT 1"));
|
||||
return Optional.ofNullable(entity).map(this::toSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 SourceMessage 查询来源通知列表,用于后续邮件通知详情和工作台展示。
|
||||
*/
|
||||
@Override
|
||||
public List<ReservationV4SourceNotificationSnapshot> findSourceNotificationsBySourceMessageId(
|
||||
String hotelId,
|
||||
Long sourceMessageId) {
|
||||
return sourceNotificationMapper.selectList(Wrappers.<ReservationV4SourceNotificationEntity>lambdaQuery()
|
||||
.eq(ReservationV4SourceNotificationEntity::getHotelId, hotelId)
|
||||
.eq(ReservationV4SourceNotificationEntity::getSourceMessageId, sourceMessageId)
|
||||
.isNull(ReservationV4SourceNotificationEntity::getLogicDeletedAt)
|
||||
.orderByAsc(ReservationV4SourceNotificationEntity::getSourceReceivedAt)
|
||||
.orderByAsc(ReservationV4SourceNotificationEntity::getId))
|
||||
.stream()
|
||||
.map(this::toSnapshot)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 幂等创建 S10 来源通知;并发重复插入时返回已存在记录。
|
||||
*/
|
||||
@Override
|
||||
public ReservationV4SourceNotificationSnapshot findOrCreateSourceNotification(
|
||||
ReservationV4SourceNotificationDraft draft) {
|
||||
Optional<ReservationV4SourceNotificationSnapshot> existing =
|
||||
findSourceNotificationBySourceMessageAndBatch(draft.hotelId(), draft.sourceMessageId(), draft.aiBatchId());
|
||||
if (existing.isPresent()) {
|
||||
return existing.get();
|
||||
}
|
||||
ReservationV4SourceNotificationEntity entity = new ReservationV4SourceNotificationEntity();
|
||||
entity.setHotelId(draft.hotelId());
|
||||
entity.setSourceMessageId(draft.sourceMessageId());
|
||||
entity.setAiBatchId(draft.aiBatchId());
|
||||
entity.setAiTransitionId(draft.aiTransitionId());
|
||||
entity.setRouteCode(draft.routeCode());
|
||||
entity.setNotificationStatus(draft.notificationStatus());
|
||||
entity.setRawPayloadJson(draft.rawPayloadJson());
|
||||
entity.setSourceReceivedAt(draft.sourceReceivedAt());
|
||||
entity.setVersion(0L);
|
||||
entity.setCreatedAt(draft.now());
|
||||
entity.setUpdatedAt(draft.now());
|
||||
try {
|
||||
sourceNotificationMapper.insert(entity);
|
||||
return toSnapshot(entity);
|
||||
} catch (DuplicateKeyException ex) {
|
||||
return findSourceNotificationBySourceMessageAndBatch(
|
||||
draft.hotelId(),
|
||||
draft.sourceMessageId(),
|
||||
draft.aiBatchId()).orElseThrow(() -> ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 version 乐观锁更新来源通知状态,常用于 S10 确认已读 / 已处理。
|
||||
*/
|
||||
@Override
|
||||
public boolean updateNotificationStatusWithVersion(
|
||||
String hotelId,
|
||||
Long notificationId,
|
||||
Long expectedVersion,
|
||||
String notificationStatus,
|
||||
String ackBy,
|
||||
LocalDateTime ackAt) {
|
||||
int updated = sourceNotificationMapper.update(Wrappers.<ReservationV4SourceNotificationEntity>lambdaUpdate()
|
||||
.set(ReservationV4SourceNotificationEntity::getNotificationStatus, notificationStatus)
|
||||
.set(ReservationV4SourceNotificationEntity::getAckBy, ackBy)
|
||||
.set(ReservationV4SourceNotificationEntity::getAckAt, ackAt)
|
||||
.set(ReservationV4SourceNotificationEntity::getUpdatedAt, ackAt)
|
||||
.setSql("version = version + 1")
|
||||
.eq(ReservationV4SourceNotificationEntity::getHotelId, hotelId)
|
||||
.eq(ReservationV4SourceNotificationEntity::getId, notificationId)
|
||||
.eq(ReservationV4SourceNotificationEntity::getVersion, expectedVersion)
|
||||
.isNull(ReservationV4SourceNotificationEntity::getLogicDeletedAt));
|
||||
return updated == 1;
|
||||
}
|
||||
|
||||
private ReservationV4SourceNotificationSnapshot toSnapshot(ReservationV4SourceNotificationEntity entity) {
|
||||
return new ReservationV4SourceNotificationSnapshot(
|
||||
entity.getId(),
|
||||
entity.getHotelId(),
|
||||
entity.getSourceMessageId(),
|
||||
entity.getAiBatchId(),
|
||||
entity.getAiTransitionId(),
|
||||
entity.getRouteCode(),
|
||||
entity.getNotificationStatus(),
|
||||
entity.getRawPayloadJson(),
|
||||
entity.getAckBy(),
|
||||
entity.getAckAt(),
|
||||
entity.getSourceReceivedAt(),
|
||||
entity.getVersion(),
|
||||
entity.getCreatedAt(),
|
||||
entity.getUpdatedAt());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.repository;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4OrderTaskDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4OrderTaskSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4TaskCardDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4TaskCardSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationV4OrderTaskEntity;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationV4TaskCardEntity;
|
||||
import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationV4OrderTaskMapper;
|
||||
import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationV4TaskCardMapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* Reservation V4 订单任务和多卡 MyBatis-Plus 持久化实现。
|
||||
*/
|
||||
@Repository
|
||||
public class MybatisReservationV4WorkflowRepository implements ReservationV4WorkflowRepository {
|
||||
|
||||
private static final int NON_EVENT_SOURCE_EVENT_INDEX = 0;
|
||||
|
||||
private final ReservationV4OrderTaskMapper orderTaskMapper;
|
||||
private final ReservationV4TaskCardMapper taskCardMapper;
|
||||
|
||||
/**
|
||||
* 注入 V4 Mapper,Repository 负责幂等、乐观锁和快照转换。
|
||||
*/
|
||||
public MybatisReservationV4WorkflowRepository(
|
||||
ReservationV4OrderTaskMapper orderTaskMapper,
|
||||
ReservationV4TaskCardMapper taskCardMapper) {
|
||||
this.orderTaskMapper = orderTaskMapper;
|
||||
this.taskCardMapper = taskCardMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 SourceMessage 和 order_ref 查询 V4 订单任务,用于幂等创建和回调重放判断。
|
||||
*/
|
||||
@Override
|
||||
public Optional<ReservationV4OrderTaskSnapshot> findOrderTaskBySourceMessageAndOrderRef(
|
||||
String hotelId,
|
||||
Long sourceMessageId,
|
||||
String orderRef) {
|
||||
ReservationV4OrderTaskEntity entity = orderTaskMapper.selectOne(
|
||||
Wrappers.<ReservationV4OrderTaskEntity>lambdaQuery()
|
||||
.eq(ReservationV4OrderTaskEntity::getHotelId, hotelId)
|
||||
.eq(ReservationV4OrderTaskEntity::getSourceMessageId, sourceMessageId)
|
||||
.eq(ReservationV4OrderTaskEntity::getOrderRef, orderRef)
|
||||
.isNull(ReservationV4OrderTaskEntity::getLogicDeletedAt)
|
||||
.last("LIMIT 1"));
|
||||
return Optional.ofNullable(entity).map(this::toOrderTaskSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按订单任务 ID 查询 V4 订单任务。
|
||||
*/
|
||||
@Override
|
||||
public Optional<ReservationV4OrderTaskSnapshot> findOrderTaskById(String hotelId, Long orderTaskId) {
|
||||
ReservationV4OrderTaskEntity entity = orderTaskMapper.selectOne(
|
||||
Wrappers.<ReservationV4OrderTaskEntity>lambdaQuery()
|
||||
.eq(ReservationV4OrderTaskEntity::getHotelId, hotelId)
|
||||
.eq(ReservationV4OrderTaskEntity::getId, orderTaskId)
|
||||
.isNull(ReservationV4OrderTaskEntity::getLogicDeletedAt)
|
||||
.last("LIMIT 1"));
|
||||
return Optional.ofNullable(entity).map(this::toOrderTaskSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 SourceMessage 查询全部 V4 订单任务,保留同一邮件内多个 order_context 的顺序。
|
||||
*/
|
||||
@Override
|
||||
public List<ReservationV4OrderTaskSnapshot> findOrderTasksBySourceMessageId(String hotelId, Long sourceMessageId) {
|
||||
return orderTaskMapper.selectList(Wrappers.<ReservationV4OrderTaskEntity>lambdaQuery()
|
||||
.eq(ReservationV4OrderTaskEntity::getHotelId, hotelId)
|
||||
.eq(ReservationV4OrderTaskEntity::getSourceMessageId, sourceMessageId)
|
||||
.isNull(ReservationV4OrderTaskEntity::getLogicDeletedAt)
|
||||
.orderByAsc(ReservationV4OrderTaskEntity::getOrderContextIndex)
|
||||
.orderByAsc(ReservationV4OrderTaskEntity::getId))
|
||||
.stream()
|
||||
.map(this::toOrderTaskSnapshot)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 幂等创建 V4 订单任务;唯一键冲突时返回已存在记录。
|
||||
*/
|
||||
@Override
|
||||
public ReservationV4OrderTaskSnapshot findOrCreateOrderTask(ReservationV4OrderTaskDraft draft) {
|
||||
Optional<ReservationV4OrderTaskSnapshot> existing = findOrderTaskBySourceMessageAndOrderRef(
|
||||
draft.hotelId(),
|
||||
draft.sourceMessageId(),
|
||||
draft.orderRef());
|
||||
if (existing.isPresent()) {
|
||||
return existing.get();
|
||||
}
|
||||
ReservationV4OrderTaskEntity entity = new ReservationV4OrderTaskEntity();
|
||||
entity.setHotelId(draft.hotelId());
|
||||
entity.setSourceMessageId(draft.sourceMessageId());
|
||||
entity.setAiBatchId(draft.aiBatchId());
|
||||
entity.setOrderRef(draft.orderRef());
|
||||
entity.setOrderContextIndex(requirePositiveOrderContextIndex(draft.orderContextIndex()));
|
||||
entity.setOrderId(draft.orderId());
|
||||
entity.setTargetBookingType(draft.targetBookingType());
|
||||
entity.setTargetLocatorType(draft.targetLocatorType());
|
||||
entity.setTargetLocatorValue(draft.targetLocatorValue());
|
||||
entity.setTargetResolutionStatus(draft.targetResolutionStatus());
|
||||
entity.setOrderTaskStatus(draft.orderTaskStatus());
|
||||
entity.setSourceReceivedAt(draft.sourceReceivedAt());
|
||||
entity.setVersion(0L);
|
||||
entity.setCreatedAt(draft.now());
|
||||
entity.setUpdatedAt(draft.now());
|
||||
try {
|
||||
orderTaskMapper.insert(entity);
|
||||
return toOrderTaskSnapshot(entity);
|
||||
} catch (DuplicateKeyException ex) {
|
||||
return findOrderTaskBySourceMessageAndOrderRef(
|
||||
draft.hotelId(),
|
||||
draft.sourceMessageId(),
|
||||
draft.orderRef()).orElseThrow(() -> ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 V4 任务卡;非 event 卡 source_event_index 固定写入 0。
|
||||
*/
|
||||
@Override
|
||||
public ReservationV4TaskCardSnapshot insertTaskCard(ReservationV4TaskCardDraft draft) {
|
||||
ReservationV4TaskCardEntity entity = new ReservationV4TaskCardEntity();
|
||||
entity.setHotelId(draft.hotelId());
|
||||
entity.setV4OrderTaskId(draft.v4OrderTaskId());
|
||||
entity.setSourceMessageId(draft.sourceMessageId());
|
||||
entity.setAiTransitionId(draft.aiTransitionId());
|
||||
entity.setCardType(draft.cardType());
|
||||
entity.setEventType(draft.eventType());
|
||||
entity.setSourceEventIndex(normalizeSourceEventIndex(draft));
|
||||
entity.setCardSortOrder(draft.cardSortOrder());
|
||||
entity.setCardStatus(draft.cardStatus());
|
||||
entity.setReviewStatus(draft.reviewStatus());
|
||||
entity.setAiPayloadJson(draft.aiPayloadJson());
|
||||
entity.setDisplayPayloadJson(draft.displayPayloadJson());
|
||||
entity.setValidationErrorsJson(draft.validationErrorsJson());
|
||||
entity.setVersion(0L);
|
||||
entity.setCreatedAt(draft.now());
|
||||
entity.setUpdatedAt(draft.now());
|
||||
taskCardMapper.insert(entity);
|
||||
return toTaskCardSnapshot(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按任务卡 ID 查询 V4 任务卡。
|
||||
*/
|
||||
@Override
|
||||
public Optional<ReservationV4TaskCardSnapshot> findTaskCardById(String hotelId, Long taskCardId) {
|
||||
ReservationV4TaskCardEntity entity = taskCardMapper.selectOne(
|
||||
Wrappers.<ReservationV4TaskCardEntity>lambdaQuery()
|
||||
.eq(ReservationV4TaskCardEntity::getHotelId, hotelId)
|
||||
.eq(ReservationV4TaskCardEntity::getId, taskCardId)
|
||||
.isNull(ReservationV4TaskCardEntity::getLogicDeletedAt)
|
||||
.last("LIMIT 1"));
|
||||
return Optional.ofNullable(entity).map(this::toTaskCardSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单任务下全部 V4 任务卡,并按卡片排序和 event index 返回。
|
||||
*/
|
||||
@Override
|
||||
public List<ReservationV4TaskCardSnapshot> findTaskCardsByOrderTaskId(String hotelId, Long orderTaskId) {
|
||||
return taskCardMapper.selectList(Wrappers.<ReservationV4TaskCardEntity>lambdaQuery()
|
||||
.eq(ReservationV4TaskCardEntity::getHotelId, hotelId)
|
||||
.eq(ReservationV4TaskCardEntity::getV4OrderTaskId, orderTaskId)
|
||||
.isNull(ReservationV4TaskCardEntity::getLogicDeletedAt)
|
||||
.orderByAsc(ReservationV4TaskCardEntity::getCardSortOrder)
|
||||
.orderByAsc(ReservationV4TaskCardEntity::getSourceEventIndex)
|
||||
.orderByAsc(ReservationV4TaskCardEntity::getId))
|
||||
.stream()
|
||||
.map(this::toTaskCardSnapshot)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 version 乐观锁更新 V4 任务卡状态。
|
||||
*/
|
||||
@Override
|
||||
public boolean updateTaskCardStatusWithVersion(
|
||||
String hotelId,
|
||||
Long taskCardId,
|
||||
Long expectedVersion,
|
||||
String cardStatus,
|
||||
LocalDateTime now) {
|
||||
int updated = taskCardMapper.update(Wrappers.<ReservationV4TaskCardEntity>lambdaUpdate()
|
||||
.set(ReservationV4TaskCardEntity::getCardStatus, cardStatus)
|
||||
.set(ReservationV4TaskCardEntity::getUpdatedAt, now)
|
||||
.setSql("version = version + 1")
|
||||
.eq(ReservationV4TaskCardEntity::getHotelId, hotelId)
|
||||
.eq(ReservationV4TaskCardEntity::getId, taskCardId)
|
||||
.eq(ReservationV4TaskCardEntity::getVersion, expectedVersion)
|
||||
.isNull(ReservationV4TaskCardEntity::getLogicDeletedAt));
|
||||
return updated == 1;
|
||||
}
|
||||
|
||||
private ReservationV4OrderTaskSnapshot toOrderTaskSnapshot(ReservationV4OrderTaskEntity entity) {
|
||||
return new ReservationV4OrderTaskSnapshot(
|
||||
entity.getId(),
|
||||
entity.getHotelId(),
|
||||
entity.getSourceMessageId(),
|
||||
entity.getAiBatchId(),
|
||||
entity.getOrderRef(),
|
||||
entity.getOrderContextIndex(),
|
||||
entity.getOrderId(),
|
||||
entity.getTargetBookingType(),
|
||||
entity.getTargetLocatorType(),
|
||||
entity.getTargetLocatorValue(),
|
||||
entity.getTargetResolutionStatus(),
|
||||
entity.getOrderTaskStatus(),
|
||||
entity.getSourceReceivedAt(),
|
||||
entity.getVersion(),
|
||||
entity.getCreatedAt(),
|
||||
entity.getUpdatedAt());
|
||||
}
|
||||
|
||||
private Integer requirePositiveOrderContextIndex(Integer orderContextIndex) {
|
||||
if (orderContextIndex == null || orderContextIndex <= 0) {
|
||||
throw new IllegalArgumentException("V4 order_context_index 必须是一基正整数。");
|
||||
}
|
||||
return orderContextIndex;
|
||||
}
|
||||
|
||||
private Integer normalizeSourceEventIndex(ReservationV4TaskCardDraft draft) {
|
||||
boolean eventCard = draft.aiTransitionId() != null || hasText(draft.eventType());
|
||||
Integer sourceEventIndex = draft.sourceEventIndex();
|
||||
if (eventCard) {
|
||||
if (sourceEventIndex == null || sourceEventIndex <= 0) {
|
||||
throw new IllegalArgumentException("V4 event 卡 source_event_index 必须是一基正整数。");
|
||||
}
|
||||
return sourceEventIndex;
|
||||
}
|
||||
if (sourceEventIndex != null && !sourceEventIndex.equals(NON_EVENT_SOURCE_EVENT_INDEX)) {
|
||||
throw new IllegalArgumentException("V4 非 event 卡 source_event_index 必须固定为 0。");
|
||||
}
|
||||
return NON_EVENT_SOURCE_EVENT_INDEX;
|
||||
}
|
||||
|
||||
private boolean hasText(String value) {
|
||||
return value != null && !value.isBlank();
|
||||
}
|
||||
|
||||
private ReservationV4TaskCardSnapshot toTaskCardSnapshot(ReservationV4TaskCardEntity entity) {
|
||||
return new ReservationV4TaskCardSnapshot(
|
||||
entity.getId(),
|
||||
entity.getHotelId(),
|
||||
entity.getV4OrderTaskId(),
|
||||
entity.getSourceMessageId(),
|
||||
entity.getAiTransitionId(),
|
||||
entity.getCardType(),
|
||||
entity.getEventType(),
|
||||
entity.getSourceEventIndex(),
|
||||
entity.getCardSortOrder(),
|
||||
entity.getCardStatus(),
|
||||
entity.getReviewStatus(),
|
||||
entity.getAiPayloadJson(),
|
||||
entity.getDisplayPayloadJson(),
|
||||
entity.getConfirmedPayloadJson(),
|
||||
entity.getReviewResolutionJson(),
|
||||
entity.getValidationErrorsJson(),
|
||||
entity.getConfirmedBy(),
|
||||
entity.getConfirmedAt(),
|
||||
entity.getVersion(),
|
||||
entity.getCreatedAt(),
|
||||
entity.getUpdatedAt());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.repository;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4SourceNotificationDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4SourceNotificationSnapshot;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Reservation V4 来源通知持久化边界。第一版用于 S10 纯通知的幂等创建、查询和确认。
|
||||
*/
|
||||
public interface ReservationV4SourceNotificationRepository {
|
||||
|
||||
/**
|
||||
* 按来源通知 ID 查询当前快照。
|
||||
*/
|
||||
Optional<ReservationV4SourceNotificationSnapshot> findSourceNotificationById(
|
||||
String hotelId,
|
||||
Long notificationId);
|
||||
|
||||
/**
|
||||
* 按 SourceMessage 和 AI 批次查询来源通知,用于幂等判断。
|
||||
*/
|
||||
Optional<ReservationV4SourceNotificationSnapshot> findSourceNotificationBySourceMessageAndBatch(
|
||||
String hotelId,
|
||||
Long sourceMessageId,
|
||||
Long aiBatchId);
|
||||
|
||||
/**
|
||||
* 按 SourceMessage 查询来源通知列表,用于后续邮件通知详情和工作台展示。
|
||||
*/
|
||||
List<ReservationV4SourceNotificationSnapshot> findSourceNotificationsBySourceMessageId(
|
||||
String hotelId,
|
||||
Long sourceMessageId);
|
||||
|
||||
/**
|
||||
* 幂等创建 S10 来源通知;并发重复插入时返回已存在记录。
|
||||
*/
|
||||
ReservationV4SourceNotificationSnapshot findOrCreateSourceNotification(ReservationV4SourceNotificationDraft draft);
|
||||
|
||||
/**
|
||||
* 按 version 乐观锁更新来源通知状态,常用于 S10 确认已读 / 已处理。
|
||||
*/
|
||||
boolean updateNotificationStatusWithVersion(
|
||||
String hotelId,
|
||||
Long notificationId,
|
||||
Long expectedVersion,
|
||||
String notificationStatus,
|
||||
String ackBy,
|
||||
LocalDateTime ackAt);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.repository;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4OrderTaskDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4OrderTaskSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4TaskCardDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4TaskCardSnapshot;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Reservation V4 订单任务和多卡持久化边界。Service 不直接依赖 Mapper 或 Entity。
|
||||
*/
|
||||
public interface ReservationV4WorkflowRepository {
|
||||
|
||||
/**
|
||||
* 按 SourceMessage 和 order_ref 查询 V4 订单任务,用于幂等创建和回调重放判断。
|
||||
*/
|
||||
Optional<ReservationV4OrderTaskSnapshot> findOrderTaskBySourceMessageAndOrderRef(
|
||||
String hotelId,
|
||||
Long sourceMessageId,
|
||||
String orderRef);
|
||||
|
||||
/**
|
||||
* 按订单任务 ID 查询 V4 订单任务。
|
||||
*/
|
||||
Optional<ReservationV4OrderTaskSnapshot> findOrderTaskById(String hotelId, Long orderTaskId);
|
||||
|
||||
/**
|
||||
* 按 SourceMessage 查询全部 V4 订单任务,保留同一邮件内多个 order_context 的顺序。
|
||||
*/
|
||||
List<ReservationV4OrderTaskSnapshot> findOrderTasksBySourceMessageId(String hotelId, Long sourceMessageId);
|
||||
|
||||
/**
|
||||
* 幂等创建 V4 订单任务;唯一键冲突时返回已存在记录。
|
||||
*/
|
||||
ReservationV4OrderTaskSnapshot findOrCreateOrderTask(ReservationV4OrderTaskDraft draft);
|
||||
|
||||
/**
|
||||
* 创建 V4 任务卡;非 event 卡 source_event_index 固定写入 0。
|
||||
*/
|
||||
ReservationV4TaskCardSnapshot insertTaskCard(ReservationV4TaskCardDraft draft);
|
||||
|
||||
/**
|
||||
* 按任务卡 ID 查询 V4 任务卡。
|
||||
*/
|
||||
Optional<ReservationV4TaskCardSnapshot> findTaskCardById(String hotelId, Long taskCardId);
|
||||
|
||||
/**
|
||||
* 查询订单任务下全部 V4 任务卡,并按卡片排序和 event index 返回。
|
||||
*/
|
||||
List<ReservationV4TaskCardSnapshot> findTaskCardsByOrderTaskId(String hotelId, Long orderTaskId);
|
||||
|
||||
/**
|
||||
* 按 version 乐观锁更新 V4 任务卡状态。
|
||||
*/
|
||||
boolean updateTaskCardStatusWithVersion(
|
||||
String hotelId,
|
||||
Long taskCardId,
|
||||
Long expectedVersion,
|
||||
String cardStatus,
|
||||
LocalDateTime now);
|
||||
}
|
||||
Reference in New Issue
Block a user