实现 M002 CP5 任务确认与 OPERA 模拟骨架
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 审计日志快照。用于后端审计列表接口,不暴露数据库 Entity。
|
||||
*/
|
||||
public record ReservationAuditLogSnapshot(
|
||||
Long id,
|
||||
String hotelId,
|
||||
Long orderId,
|
||||
Long taskId,
|
||||
Long operationId,
|
||||
String actorType,
|
||||
String actorId,
|
||||
String action,
|
||||
String reason,
|
||||
String beforeSnapshotJson,
|
||||
String afterSnapshotJson,
|
||||
LocalDateTime occurredAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* OPERA 模拟操作 attempt 入库草稿。每次执行或重试生成一条。
|
||||
*/
|
||||
public record ReservationOperaOperationAttemptDraft(
|
||||
String hotelId,
|
||||
Long orderId,
|
||||
Long taskId,
|
||||
Long operationId,
|
||||
Integer attemptNumber,
|
||||
String attemptStatus,
|
||||
String requestPayloadJson,
|
||||
String responsePayloadJson,
|
||||
String errorMessage,
|
||||
LocalDateTime startedAt,
|
||||
LocalDateTime finishedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* OPERA 模拟操作 attempt 快照。用于执行结果和重试历史展示。
|
||||
*/
|
||||
public record ReservationOperaOperationAttemptSnapshot(
|
||||
Long id,
|
||||
String hotelId,
|
||||
Long orderId,
|
||||
Long taskId,
|
||||
Long operationId,
|
||||
Integer attemptNumber,
|
||||
String attemptStatus,
|
||||
String requestPayloadJson,
|
||||
String responsePayloadJson,
|
||||
String errorMessage,
|
||||
LocalDateTime startedAt,
|
||||
LocalDateTime finishedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* OPERA 模拟操作入库草稿。任务确认后由系统固定生成两条。
|
||||
*/
|
||||
public record ReservationOperaOperationDraft(
|
||||
String hotelId,
|
||||
Long orderId,
|
||||
Long taskId,
|
||||
Integer operationSequence,
|
||||
String operationCode,
|
||||
String operationName,
|
||||
String operationStatus,
|
||||
String requestPayloadJson,
|
||||
LocalDateTime now
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* OPERA 模拟操作快照。Service 使用该对象展示操作状态,不暴露 Entity。
|
||||
*/
|
||||
public record ReservationOperaOperationSnapshot(
|
||||
Long id,
|
||||
String hotelId,
|
||||
Long orderId,
|
||||
Long taskId,
|
||||
Integer operationSequence,
|
||||
String operationCode,
|
||||
String operationName,
|
||||
String operationStatus,
|
||||
String requestPayloadJson,
|
||||
Integer attemptCount,
|
||||
Long lastAttemptId,
|
||||
String lastErrorMessage,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 任务卡快照。保存字段契约版本和 AI 原始快照,供详情接口按矩阵组装展示字段。
|
||||
* 任务卡快照。保存字段契约版本、AI 原始快照、用户草稿和最终确认 payload。
|
||||
*/
|
||||
public record ReservationTaskCardSnapshot(
|
||||
Long id,
|
||||
@@ -9,6 +11,10 @@ public record ReservationTaskCardSnapshot(
|
||||
Long taskId,
|
||||
String taskCardType,
|
||||
String fieldContractVersion,
|
||||
String aiPayloadJson
|
||||
String aiPayloadJson,
|
||||
String draftPayloadJson,
|
||||
String confirmedPayloadJson,
|
||||
String confirmedBy,
|
||||
LocalDateTime confirmedAt
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 任务快照。用于任务详情、队列可处理状态和人工复核转换,不向上暴露 Entity。
|
||||
* 任务快照。用于任务详情、队列可处理状态、人工复核转换和任务确认,不向上暴露 Entity。
|
||||
*/
|
||||
public record ReservationTaskSnapshot(
|
||||
Long id,
|
||||
@@ -20,6 +22,7 @@ public record ReservationTaskSnapshot(
|
||||
Long parentTaskId,
|
||||
Integer parentSourceEventIndex,
|
||||
String linkedTaskGroupId,
|
||||
Boolean blockedUntilParentCompleted
|
||||
Boolean blockedUntilParentCompleted,
|
||||
LocalDateTime confirmedAt
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.enums;
|
||||
|
||||
/**
|
||||
* OPERA 模拟 attempt 状态。每次执行或重试只能成功或失败。
|
||||
*/
|
||||
public enum ReservationOperaAttemptStatus {
|
||||
SUCCEEDED,
|
||||
FAILED
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.enums;
|
||||
|
||||
/**
|
||||
* OPERA 模拟操作状态。第一版只表达待执行、成功和失败。
|
||||
*/
|
||||
public enum ReservationOperaOperationStatus {
|
||||
PENDING,
|
||||
SUCCEEDED,
|
||||
FAILED
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* OPERA 模拟执行请求。第一版只用开关控制模拟成功或失败,不接真实 OPERA。
|
||||
*/
|
||||
public record ReservationOperaSimulationRequest(
|
||||
@JsonProperty("simulate_success")
|
||||
Boolean simulateSuccess,
|
||||
@JsonProperty("failure_message")
|
||||
String failureMessage
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 任务卡草稿保存和最终确认请求。第一版 field_values 使用矩阵 field_path 作为 key,不使用 write_path 生成 OPERA 参数。
|
||||
*/
|
||||
public record ReservationTaskPayloadMutationRequest(
|
||||
@JsonProperty("field_values")
|
||||
Map<String, Object> fieldValues
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* OPERA 模拟操作 attempt 响应。用于展示每次执行或重试的结果。
|
||||
*/
|
||||
public record ReservationOperaOperationAttemptResult(
|
||||
@JsonProperty("attempt_id")
|
||||
String attemptId,
|
||||
@JsonProperty("attempt_number")
|
||||
Integer attemptNumber,
|
||||
@JsonProperty("attempt_status")
|
||||
String attemptStatus,
|
||||
@JsonProperty("response_payload")
|
||||
Object responsePayload,
|
||||
@JsonProperty("error_message")
|
||||
String errorMessage,
|
||||
@JsonProperty("started_at")
|
||||
String startedAt,
|
||||
@JsonProperty("finished_at")
|
||||
String finishedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* OPERA 模拟操作响应。包含当前操作状态和 attempt 历史。
|
||||
*/
|
||||
public record ReservationOperaOperationResult(
|
||||
@JsonProperty("operation_id")
|
||||
String operationId,
|
||||
@JsonProperty("task_id")
|
||||
String taskId,
|
||||
@JsonProperty("operation_sequence")
|
||||
Integer operationSequence,
|
||||
@JsonProperty("operation_code")
|
||||
String operationCode,
|
||||
@JsonProperty("operation_name")
|
||||
String operationName,
|
||||
@JsonProperty("operation_status")
|
||||
String operationStatus,
|
||||
@JsonProperty("attempt_count")
|
||||
Integer attemptCount,
|
||||
@JsonProperty("last_error_message")
|
||||
String lastErrorMessage,
|
||||
List<ReservationOperaOperationAttemptResult> attempts
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 任务审计列表响应。第一版按任务维度返回审计流水。
|
||||
*/
|
||||
public record ReservationTaskAuditListResult(
|
||||
@JsonProperty("task_id")
|
||||
String taskId,
|
||||
List<ReservationTaskAuditLogResult> items
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* 任务审计列表单条响应。只返回审计摘要,不返回完整客户原始报文。
|
||||
*/
|
||||
public record ReservationTaskAuditLogResult(
|
||||
@JsonProperty("audit_id")
|
||||
String auditId,
|
||||
@JsonProperty("order_id")
|
||||
String orderId,
|
||||
@JsonProperty("task_id")
|
||||
String taskId,
|
||||
@JsonProperty("operation_id")
|
||||
String operationId,
|
||||
@JsonProperty("actor_type")
|
||||
String actorType,
|
||||
@JsonProperty("actor_id")
|
||||
String actorId,
|
||||
String action,
|
||||
String reason,
|
||||
@JsonProperty("before_snapshot")
|
||||
Object beforeSnapshot,
|
||||
@JsonProperty("after_snapshot")
|
||||
Object afterSnapshot,
|
||||
@JsonProperty("occurred_at")
|
||||
String occurredAt
|
||||
) {
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 任务详情结果。第一版提供队列可处理状态和按矩阵生成的字段列表。
|
||||
* 任务详情结果。提供队列可处理状态、任务卡 payload 和按矩阵生成的字段列表。
|
||||
*/
|
||||
public record ReservationTaskDetailResult(
|
||||
@JsonProperty("task_id")
|
||||
@@ -21,7 +21,13 @@ public record ReservationTaskDetailResult(
|
||||
String taskStatus,
|
||||
@JsonProperty("field_contract_version")
|
||||
String fieldContractVersion,
|
||||
@JsonProperty("draft_payload")
|
||||
Object draftPayload,
|
||||
@JsonProperty("confirmed_payload")
|
||||
Object confirmedPayload,
|
||||
ReservationTaskAvailabilityResult availability,
|
||||
List<ReservationTaskFieldResult> fields
|
||||
List<ReservationTaskFieldResult> fields,
|
||||
@JsonProperty("opera_operations")
|
||||
List<ReservationOperaOperationResult> operaOperations
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 任务卡草稿保存或最终确认结果。payload 为第一版 field_path 简单结构,供前端回显和后续 OPERA 转换使用。
|
||||
*/
|
||||
public record ReservationTaskPayloadMutationResult(
|
||||
@JsonProperty("task_id")
|
||||
String taskId,
|
||||
@JsonProperty("order_id")
|
||||
String orderId,
|
||||
@JsonProperty("task_status")
|
||||
String taskStatus,
|
||||
@JsonProperty("draft_payload")
|
||||
Object draftPayload,
|
||||
@JsonProperty("confirmed_payload")
|
||||
Object confirmedPayload,
|
||||
@JsonProperty("opera_operations")
|
||||
List<ReservationOperaOperationResult> operaOperations
|
||||
) {
|
||||
}
|
||||
@@ -1,13 +1,19 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.control;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ManualReviewConversionRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationOperaSimulationRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationTaskPayloadMutationRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ManualReviewConversionResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOperaOperationResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskAuditListResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskDetailResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskPayloadMutationResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.service.ReservationTaskWorkflowService;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@@ -48,4 +54,66 @@ public class ReservationTaskController {
|
||||
@RequestBody(required = false) ManualReviewConversionRequest request) {
|
||||
return taskWorkflowService.convertManualReviewTask(taskId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存任务卡草稿。第一版 field_values 使用矩阵 field_path,不直接生成 OPERA 参数。
|
||||
*/
|
||||
@PutMapping(
|
||||
value = "/{taskId}/draft",
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReservationTaskPayloadMutationResult saveDraft(
|
||||
@PathVariable Long taskId,
|
||||
@RequestBody(required = false) ReservationTaskPayloadMutationRequest request) {
|
||||
return taskWorkflowService.saveTaskDraft(taskId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 最终确认任务卡字段;校验通过后任务进入 READY,等待后续 OPERA 阶段处理。
|
||||
*/
|
||||
@PostMapping(
|
||||
value = "/{taskId}/confirm",
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReservationTaskPayloadMutationResult confirm(
|
||||
@PathVariable Long taskId,
|
||||
@RequestBody(required = false) ReservationTaskPayloadMutationRequest request) {
|
||||
return taskWorkflowService.confirmTask(taskId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一条 OPERA 模拟操作;第一版只写入模拟 attempt,不调用真实 OPERA。
|
||||
*/
|
||||
@PostMapping(
|
||||
value = "/{taskId}/opera-operations/{operationId}/execute",
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReservationOperaOperationResult executeOperaOperation(
|
||||
@PathVariable Long taskId,
|
||||
@PathVariable Long operationId,
|
||||
@RequestBody(required = false) ReservationOperaSimulationRequest request) {
|
||||
return taskWorkflowService.executeOperaOperation(taskId, operationId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试失败的 OPERA 模拟操作;重试会追加 attempt 历史。
|
||||
*/
|
||||
@PostMapping(
|
||||
value = "/{taskId}/opera-operations/{operationId}/retry",
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReservationOperaOperationResult retryOperaOperation(
|
||||
@PathVariable Long taskId,
|
||||
@PathVariable Long operationId,
|
||||
@RequestBody(required = false) ReservationOperaSimulationRequest request) {
|
||||
return taskWorkflowService.retryOperaOperation(taskId, operationId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询任务审计流水,供前端后续展示人工确认和 OPERA 模拟操作轨迹。
|
||||
*/
|
||||
@GetMapping(value = "/{taskId}/audits", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReservationTaskAuditListResult listAudits(@PathVariable Long taskId) {
|
||||
return taskWorkflowService.listTaskAudits(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package cn.nianxx.thhotel.workflows.reservation.control;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationWorkflowErrorResponse;
|
||||
import cn.nianxx.thhotel.workflows.reservation.service.impl.ReservationTaskWorkflowException;
|
||||
import java.util.List;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
@@ -20,6 +19,9 @@ public class ReservationTaskControllerAdvice {
|
||||
public ResponseEntity<ReservationWorkflowErrorResponse> handleTaskWorkflowException(
|
||||
ReservationTaskWorkflowException exception) {
|
||||
return ResponseEntity.status(exception.getStatus())
|
||||
.body(new ReservationWorkflowErrorResponse(exception.getErrorCode(), exception.getMessage(), List.of()));
|
||||
.body(new ReservationWorkflowErrorResponse(
|
||||
exception.getErrorCode(),
|
||||
exception.getMessage(),
|
||||
exception.getDetails()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
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 OPERA 模拟操作 attempt 实体。记录每次执行或重试的请求、响应和结果。
|
||||
*/
|
||||
@TableName("workflow_reservation_opera_operation_attempt")
|
||||
public class ReservationOperaOperationAttemptEntity {
|
||||
|
||||
/** OPERA 模拟操作 attempt ID。 */
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
/** 酒店或业务上下文 ID。 */
|
||||
private String hotelId;
|
||||
/** 关联订单 ID。 */
|
||||
private Long orderId;
|
||||
/** 关联任务 ID。 */
|
||||
private Long taskId;
|
||||
/** 关联 OPERA 模拟操作 ID。 */
|
||||
private Long operationId;
|
||||
/** 同一操作内第几次 attempt,从 1 开始。 */
|
||||
private Integer attemptNumber;
|
||||
/** attempt 状态:SUCCEEDED、FAILED。 */
|
||||
private String attemptStatus;
|
||||
/** 本次模拟请求 JSON。 */
|
||||
private String requestPayloadJson;
|
||||
/** 本次模拟响应 JSON。 */
|
||||
private String responsePayloadJson;
|
||||
/** 失败原因摘要。 */
|
||||
private String errorMessage;
|
||||
/** attempt 开始 UTC 时间。 */
|
||||
private LocalDateTime startedAt;
|
||||
/** attempt 结束 UTC 时间。 */
|
||||
private LocalDateTime finishedAt;
|
||||
/** 记录创建 UTC 时间。 */
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
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 getOrderId() { return orderId; }
|
||||
public void setOrderId(Long orderId) { this.orderId = orderId; }
|
||||
public Long getTaskId() { return taskId; }
|
||||
public void setTaskId(Long taskId) { this.taskId = taskId; }
|
||||
public Long getOperationId() { return operationId; }
|
||||
public void setOperationId(Long operationId) { this.operationId = operationId; }
|
||||
public Integer getAttemptNumber() { return attemptNumber; }
|
||||
public void setAttemptNumber(Integer attemptNumber) { this.attemptNumber = attemptNumber; }
|
||||
public String getAttemptStatus() { return attemptStatus; }
|
||||
public void setAttemptStatus(String attemptStatus) { this.attemptStatus = attemptStatus; }
|
||||
public String getRequestPayloadJson() { return requestPayloadJson; }
|
||||
public void setRequestPayloadJson(String requestPayloadJson) { this.requestPayloadJson = requestPayloadJson; }
|
||||
public String getResponsePayloadJson() { return responsePayloadJson; }
|
||||
public void setResponsePayloadJson(String responsePayloadJson) { this.responsePayloadJson = responsePayloadJson; }
|
||||
public String getErrorMessage() { return errorMessage; }
|
||||
public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }
|
||||
public LocalDateTime getStartedAt() { return startedAt; }
|
||||
public void setStartedAt(LocalDateTime startedAt) { this.startedAt = startedAt; }
|
||||
public LocalDateTime getFinishedAt() { return finishedAt; }
|
||||
public void setFinishedAt(LocalDateTime finishedAt) { this.finishedAt = finishedAt; }
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
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 OPERA 模拟操作实体。保存任务确认后生成的固定模拟操作及当前状态。
|
||||
*/
|
||||
@TableName("workflow_reservation_opera_operation")
|
||||
public class ReservationOperaOperationEntity {
|
||||
|
||||
/** OPERA 模拟操作 ID。 */
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
/** 酒店或业务上下文 ID。 */
|
||||
private String hotelId;
|
||||
/** 关联订单 ID。 */
|
||||
private Long orderId;
|
||||
/** 关联任务 ID。 */
|
||||
private Long taskId;
|
||||
/** 任务内模拟操作顺序,从 1 开始。 */
|
||||
private Integer operationSequence;
|
||||
/** 模拟操作代码,第一版固定两类。 */
|
||||
private String operationCode;
|
||||
/** 模拟操作展示名称。 */
|
||||
private String operationName;
|
||||
/** 操作状态:PENDING、SUCCEEDED、FAILED。 */
|
||||
private String operationStatus;
|
||||
/** 模拟请求 JSON,来源于确认 payload 的低耦合摘要。 */
|
||||
private String requestPayloadJson;
|
||||
/** 已执行 attempt 次数。 */
|
||||
private Integer attemptCount;
|
||||
/** 最近一次 attempt ID。 */
|
||||
private Long lastAttemptId;
|
||||
/** 最近一次失败原因摘要。 */
|
||||
private String lastErrorMessage;
|
||||
/** 记录创建 UTC 时间。 */
|
||||
private LocalDateTime createdAt;
|
||||
/** 记录更新 UTC 时间。 */
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
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 getOrderId() { return orderId; }
|
||||
public void setOrderId(Long orderId) { this.orderId = orderId; }
|
||||
public Long getTaskId() { return taskId; }
|
||||
public void setTaskId(Long taskId) { this.taskId = taskId; }
|
||||
public Integer getOperationSequence() { return operationSequence; }
|
||||
public void setOperationSequence(Integer operationSequence) { this.operationSequence = operationSequence; }
|
||||
public String getOperationCode() { return operationCode; }
|
||||
public void setOperationCode(String operationCode) { this.operationCode = operationCode; }
|
||||
public String getOperationName() { return operationName; }
|
||||
public void setOperationName(String operationName) { this.operationName = operationName; }
|
||||
public String getOperationStatus() { return operationStatus; }
|
||||
public void setOperationStatus(String operationStatus) { this.operationStatus = operationStatus; }
|
||||
public String getRequestPayloadJson() { return requestPayloadJson; }
|
||||
public void setRequestPayloadJson(String requestPayloadJson) { this.requestPayloadJson = requestPayloadJson; }
|
||||
public Integer getAttemptCount() { return attemptCount; }
|
||||
public void setAttemptCount(Integer attemptCount) { this.attemptCount = attemptCount; }
|
||||
public Long getLastAttemptId() { return lastAttemptId; }
|
||||
public void setLastAttemptId(Long lastAttemptId) { this.lastAttemptId = lastAttemptId; }
|
||||
public String getLastErrorMessage() { return lastErrorMessage; }
|
||||
public void setLastErrorMessage(String lastErrorMessage) { this.lastErrorMessage = lastErrorMessage; }
|
||||
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; }
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Reservation 任务卡实体。CP1-3 只保存 AI 原始快照,不承载用户编辑确认 payload。
|
||||
* Reservation 任务卡实体。保存 AI 原始快照、用户草稿和最终确认 payload。
|
||||
*/
|
||||
@TableName("workflow_reservation_task_card")
|
||||
public class ReservationTaskCardEntity {
|
||||
@@ -24,6 +24,14 @@ public class ReservationTaskCardEntity {
|
||||
private String fieldContractVersion;
|
||||
/** 任务卡使用的 AI 原始 JSON 快照。 */
|
||||
private String aiPayloadJson;
|
||||
/** 用户保存的任务卡草稿 JSON,第一版按矩阵 field_path 保存。 */
|
||||
private String draftPayloadJson;
|
||||
/** 用户最终确认后的任务卡 JSON,第一版按矩阵 field_path 合并保存。 */
|
||||
private String confirmedPayloadJson;
|
||||
/** 确认人标识,用户身份权限接入前使用本地占位。 */
|
||||
private String confirmedBy;
|
||||
/** 任务卡最终确认 UTC 时间。 */
|
||||
private LocalDateTime confirmedAt;
|
||||
/** 记录创建 UTC 时间。 */
|
||||
private LocalDateTime createdAt;
|
||||
/** 记录更新 UTC 时间。 */
|
||||
@@ -41,6 +49,14 @@ public class ReservationTaskCardEntity {
|
||||
public void setFieldContractVersion(String fieldContractVersion) { this.fieldContractVersion = fieldContractVersion; }
|
||||
public String getAiPayloadJson() { return aiPayloadJson; }
|
||||
public void setAiPayloadJson(String aiPayloadJson) { this.aiPayloadJson = aiPayloadJson; }
|
||||
public String getDraftPayloadJson() { return draftPayloadJson; }
|
||||
public void setDraftPayloadJson(String draftPayloadJson) { this.draftPayloadJson = draftPayloadJson; }
|
||||
public String getConfirmedPayloadJson() { return confirmedPayloadJson; }
|
||||
public void setConfirmedPayloadJson(String confirmedPayloadJson) { this.confirmedPayloadJson = confirmedPayloadJson; }
|
||||
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 LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
|
||||
@@ -48,6 +48,8 @@ public class ReservationTaskEntity {
|
||||
private Boolean blockedUntilParentCompleted;
|
||||
/** 最近失败原因摘要。 */
|
||||
private String lastFailureReason;
|
||||
/** 用户最终确认任务字段和订单归属的 UTC 时间。 */
|
||||
private LocalDateTime confirmedAt;
|
||||
/** 任务完成 UTC 时间。 */
|
||||
private LocalDateTime completedAt;
|
||||
/** 乐观锁版本。 */
|
||||
@@ -93,6 +95,8 @@ public class ReservationTaskEntity {
|
||||
public void setBlockedUntilParentCompleted(Boolean blockedUntilParentCompleted) { this.blockedUntilParentCompleted = blockedUntilParentCompleted; }
|
||||
public String getLastFailureReason() { return lastFailureReason; }
|
||||
public void setLastFailureReason(String lastFailureReason) { this.lastFailureReason = lastFailureReason; }
|
||||
public LocalDateTime getConfirmedAt() { return confirmedAt; }
|
||||
public void setConfirmedAt(LocalDateTime confirmedAt) { this.confirmedAt = confirmedAt; }
|
||||
public LocalDateTime getCompletedAt() { return completedAt; }
|
||||
public void setCompletedAt(LocalDateTime completedAt) { this.completedAt = completedAt; }
|
||||
public Long getVersion() { return version; }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.mapper;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationOperaOperationAttemptEntity;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* Reservation OPERA 模拟操作 attempt Mapper,只负责 attempt 表持久化访问。
|
||||
*/
|
||||
@Mapper
|
||||
public interface ReservationOperaOperationAttemptMapper extends BaseMapper<ReservationOperaOperationAttemptEntity> {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.mapper;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationOperaOperationEntity;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* Reservation OPERA 模拟操作 Mapper,只负责操作表持久化访问。
|
||||
*/
|
||||
@Mapper
|
||||
public interface ReservationOperaOperationMapper extends BaseMapper<ReservationOperaOperationEntity> {
|
||||
}
|
||||
@@ -4,6 +4,11 @@ import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiBatchDraf
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiBatchSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiTransitionDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAuditLogDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAuditLogSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOperaOperationAttemptDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOperaOperationAttemptSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOperaOperationDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOperaOperationSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOrderDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOrderSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskCardDraft;
|
||||
@@ -11,15 +16,20 @@ import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskCardSna
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOrderStatus;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationTaskStatus;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationAiBatchEntity;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationAiTransitionEntity;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationAuditLogEntity;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationOperaOperationAttemptEntity;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationOperaOperationEntity;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationOrderEntity;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationTaskCardEntity;
|
||||
import cn.nianxx.thhotel.workflows.reservation.domain.ReservationTaskEntity;
|
||||
import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationAiBatchMapper;
|
||||
import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationAiTransitionMapper;
|
||||
import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationAuditLogMapper;
|
||||
import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationOperaOperationAttemptMapper;
|
||||
import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationOperaOperationMapper;
|
||||
import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationOrderMapper;
|
||||
import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationTaskCardMapper;
|
||||
import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationTaskMapper;
|
||||
@@ -40,6 +50,8 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
private final ReservationOrderMapper orderMapper;
|
||||
private final ReservationTaskMapper taskMapper;
|
||||
private final ReservationTaskCardMapper taskCardMapper;
|
||||
private final ReservationOperaOperationMapper operaOperationMapper;
|
||||
private final ReservationOperaOperationAttemptMapper operaOperationAttemptMapper;
|
||||
private final ReservationAuditLogMapper auditLogMapper;
|
||||
|
||||
/**
|
||||
@@ -51,12 +63,16 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
ReservationOrderMapper orderMapper,
|
||||
ReservationTaskMapper taskMapper,
|
||||
ReservationTaskCardMapper taskCardMapper,
|
||||
ReservationOperaOperationMapper operaOperationMapper,
|
||||
ReservationOperaOperationAttemptMapper operaOperationAttemptMapper,
|
||||
ReservationAuditLogMapper auditLogMapper) {
|
||||
this.batchMapper = batchMapper;
|
||||
this.transitionMapper = transitionMapper;
|
||||
this.orderMapper = orderMapper;
|
||||
this.taskMapper = taskMapper;
|
||||
this.taskCardMapper = taskCardMapper;
|
||||
this.operaOperationMapper = operaOperationMapper;
|
||||
this.operaOperationAttemptMapper = operaOperationAttemptMapper;
|
||||
this.auditLogMapper = auditLogMapper;
|
||||
}
|
||||
|
||||
@@ -339,6 +355,169 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
.eq(ReservationTaskCardEntity::getTaskId, taskId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存任务卡草稿 payload。
|
||||
*/
|
||||
@Override
|
||||
public boolean updateTaskDraftPayload(String hotelId, Long taskId, String draftPayloadJson, LocalDateTime now) {
|
||||
return taskCardMapper.update(null, Wrappers.<ReservationTaskCardEntity>lambdaUpdate()
|
||||
.set(ReservationTaskCardEntity::getDraftPayloadJson, draftPayloadJson)
|
||||
.set(ReservationTaskCardEntity::getUpdatedAt, now)
|
||||
.eq(ReservationTaskCardEntity::getHotelId, hotelId)
|
||||
.eq(ReservationTaskCardEntity::getTaskId, taskId)) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存最终确认 payload,并将任务状态从 PENDING_CONFIRM 流转为 READY。
|
||||
*/
|
||||
@Override
|
||||
public boolean confirmTaskPayload(
|
||||
String hotelId,
|
||||
Long taskId,
|
||||
String confirmedPayloadJson,
|
||||
String confirmedBy,
|
||||
LocalDateTime confirmedAt) {
|
||||
int updatedTaskCount = taskMapper.update(null, Wrappers.<ReservationTaskEntity>lambdaUpdate()
|
||||
.set(ReservationTaskEntity::getTaskStatus, ReservationTaskStatus.READY.name())
|
||||
.set(ReservationTaskEntity::getConfirmedAt, confirmedAt)
|
||||
.set(ReservationTaskEntity::getUpdatedAt, confirmedAt)
|
||||
.setSql("version = version + 1")
|
||||
.eq(ReservationTaskEntity::getHotelId, hotelId)
|
||||
.eq(ReservationTaskEntity::getId, taskId)
|
||||
.eq(ReservationTaskEntity::getTaskStatus, ReservationTaskStatus.PENDING_CONFIRM.name()));
|
||||
if (updatedTaskCount == 0) {
|
||||
return false;
|
||||
}
|
||||
int updatedCardCount = taskCardMapper.update(null, Wrappers.<ReservationTaskCardEntity>lambdaUpdate()
|
||||
.set(ReservationTaskCardEntity::getConfirmedPayloadJson, confirmedPayloadJson)
|
||||
.set(ReservationTaskCardEntity::getConfirmedBy, confirmedBy)
|
||||
.set(ReservationTaskCardEntity::getConfirmedAt, confirmedAt)
|
||||
.set(ReservationTaskCardEntity::getUpdatedAt, confirmedAt)
|
||||
.eq(ReservationTaskCardEntity::getHotelId, hotelId)
|
||||
.eq(ReservationTaskCardEntity::getTaskId, taskId));
|
||||
return updatedCardCount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入 OPERA 模拟操作。
|
||||
*/
|
||||
@Override
|
||||
public Long insertOperaOperation(ReservationOperaOperationDraft draft) {
|
||||
ReservationOperaOperationEntity entity = new ReservationOperaOperationEntity();
|
||||
entity.setHotelId(draft.hotelId());
|
||||
entity.setOrderId(draft.orderId());
|
||||
entity.setTaskId(draft.taskId());
|
||||
entity.setOperationSequence(draft.operationSequence());
|
||||
entity.setOperationCode(draft.operationCode());
|
||||
entity.setOperationName(draft.operationName());
|
||||
entity.setOperationStatus(draft.operationStatus());
|
||||
entity.setRequestPayloadJson(draft.requestPayloadJson());
|
||||
entity.setAttemptCount(0);
|
||||
entity.setCreatedAt(draft.now());
|
||||
entity.setUpdatedAt(draft.now());
|
||||
operaOperationMapper.insert(entity);
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询任务下 OPERA 模拟操作,按任务内顺序返回。
|
||||
*/
|
||||
@Override
|
||||
public List<ReservationOperaOperationSnapshot> findOperaOperationsByTaskId(String hotelId, Long taskId) {
|
||||
return operaOperationMapper.selectList(Wrappers.<ReservationOperaOperationEntity>lambdaQuery()
|
||||
.eq(ReservationOperaOperationEntity::getHotelId, hotelId)
|
||||
.eq(ReservationOperaOperationEntity::getTaskId, taskId)
|
||||
.orderByAsc(ReservationOperaOperationEntity::getOperationSequence))
|
||||
.stream()
|
||||
.map(this::toOperaOperationSnapshot)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按操作 ID 查询 OPERA 模拟操作。
|
||||
*/
|
||||
@Override
|
||||
public Optional<ReservationOperaOperationSnapshot> findOperaOperationById(String hotelId, Long operationId) {
|
||||
ReservationOperaOperationEntity entity = operaOperationMapper.selectOne(
|
||||
Wrappers.<ReservationOperaOperationEntity>lambdaQuery()
|
||||
.eq(ReservationOperaOperationEntity::getHotelId, hotelId)
|
||||
.eq(ReservationOperaOperationEntity::getId, operationId)
|
||||
.last("LIMIT 1"));
|
||||
return Optional.ofNullable(entity).map(this::toOperaOperationSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 OPERA 模拟操作 attempt 历史。
|
||||
*/
|
||||
@Override
|
||||
public List<ReservationOperaOperationAttemptSnapshot> findOperaOperationAttempts(String hotelId, Long operationId) {
|
||||
return operaOperationAttemptMapper.selectList(Wrappers.<ReservationOperaOperationAttemptEntity>lambdaQuery()
|
||||
.eq(ReservationOperaOperationAttemptEntity::getHotelId, hotelId)
|
||||
.eq(ReservationOperaOperationAttemptEntity::getOperationId, operationId)
|
||||
.orderByAsc(ReservationOperaOperationAttemptEntity::getAttemptNumber))
|
||||
.stream()
|
||||
.map(this::toOperaOperationAttemptSnapshot)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入 OPERA 模拟操作 attempt。
|
||||
*/
|
||||
@Override
|
||||
public Long insertOperaOperationAttempt(ReservationOperaOperationAttemptDraft draft) {
|
||||
ReservationOperaOperationAttemptEntity entity = new ReservationOperaOperationAttemptEntity();
|
||||
entity.setHotelId(draft.hotelId());
|
||||
entity.setOrderId(draft.orderId());
|
||||
entity.setTaskId(draft.taskId());
|
||||
entity.setOperationId(draft.operationId());
|
||||
entity.setAttemptNumber(draft.attemptNumber());
|
||||
entity.setAttemptStatus(draft.attemptStatus());
|
||||
entity.setRequestPayloadJson(draft.requestPayloadJson());
|
||||
entity.setResponsePayloadJson(draft.responsePayloadJson());
|
||||
entity.setErrorMessage(draft.errorMessage());
|
||||
entity.setStartedAt(draft.startedAt());
|
||||
entity.setFinishedAt(draft.finishedAt());
|
||||
entity.setCreatedAt(draft.startedAt());
|
||||
operaOperationAttemptMapper.insert(entity);
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 OPERA 模拟操作当前状态和最近 attempt 摘要。
|
||||
*/
|
||||
@Override
|
||||
public boolean updateOperaOperationAfterAttempt(
|
||||
String hotelId,
|
||||
Long operationId,
|
||||
String operationStatus,
|
||||
Long lastAttemptId,
|
||||
String lastErrorMessage,
|
||||
LocalDateTime now) {
|
||||
return operaOperationMapper.update(null, Wrappers.<ReservationOperaOperationEntity>lambdaUpdate()
|
||||
.set(ReservationOperaOperationEntity::getOperationStatus, operationStatus)
|
||||
.set(ReservationOperaOperationEntity::getLastAttemptId, lastAttemptId)
|
||||
.set(ReservationOperaOperationEntity::getLastErrorMessage, lastErrorMessage)
|
||||
.set(ReservationOperaOperationEntity::getUpdatedAt, now)
|
||||
.setSql("attempt_count = attempt_count + 1")
|
||||
.eq(ReservationOperaOperationEntity::getHotelId, hotelId)
|
||||
.eq(ReservationOperaOperationEntity::getId, operationId)) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务状态。
|
||||
*/
|
||||
@Override
|
||||
public boolean updateTaskStatus(String hotelId, Long taskId, String taskStatus, LocalDateTime now) {
|
||||
return taskMapper.update(null, Wrappers.<ReservationTaskEntity>lambdaUpdate()
|
||||
.set(ReservationTaskEntity::getTaskStatus, taskStatus)
|
||||
.set(ReservationTaskEntity::getCompletedAt,
|
||||
ReservationTaskStatus.COMPLETED.name().equals(taskStatus) ? now : null)
|
||||
.set(ReservationTaskEntity::getUpdatedAt, now)
|
||||
.setSql("version = version + 1")
|
||||
.eq(ReservationTaskEntity::getHotelId, hotelId)
|
||||
.eq(ReservationTaskEntity::getId, taskId)) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计订单下全部任务数量。
|
||||
*/
|
||||
@@ -387,6 +566,20 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询任务审计流水,按发生时间正序返回。
|
||||
*/
|
||||
@Override
|
||||
public List<ReservationAuditLogSnapshot> findAuditLogsByTaskId(String hotelId, Long taskId) {
|
||||
return auditLogMapper.selectList(Wrappers.<ReservationAuditLogEntity>lambdaQuery()
|
||||
.eq(ReservationAuditLogEntity::getHotelId, hotelId)
|
||||
.eq(ReservationAuditLogEntity::getTaskId, taskId)
|
||||
.orderByAsc(ReservationAuditLogEntity::getOccurredAt))
|
||||
.stream()
|
||||
.map(this::toAuditLogSnapshot)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换批次实体为快照。
|
||||
*/
|
||||
@@ -435,7 +628,8 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
entity.getParentTaskId(),
|
||||
entity.getParentSourceEventIndex(),
|
||||
entity.getLinkedTaskGroupId(),
|
||||
entity.getBlockedUntilParentCompleted());
|
||||
entity.getBlockedUntilParentCompleted(),
|
||||
entity.getConfirmedAt());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -448,6 +642,70 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
entity.getTaskId(),
|
||||
entity.getTaskCardType(),
|
||||
entity.getFieldContractVersion(),
|
||||
entity.getAiPayloadJson());
|
||||
entity.getAiPayloadJson(),
|
||||
entity.getDraftPayloadJson(),
|
||||
entity.getConfirmedPayloadJson(),
|
||||
entity.getConfirmedBy(),
|
||||
entity.getConfirmedAt());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 OPERA 模拟操作实体为快照。
|
||||
*/
|
||||
private ReservationOperaOperationSnapshot toOperaOperationSnapshot(ReservationOperaOperationEntity entity) {
|
||||
return new ReservationOperaOperationSnapshot(
|
||||
entity.getId(),
|
||||
entity.getHotelId(),
|
||||
entity.getOrderId(),
|
||||
entity.getTaskId(),
|
||||
entity.getOperationSequence(),
|
||||
entity.getOperationCode(),
|
||||
entity.getOperationName(),
|
||||
entity.getOperationStatus(),
|
||||
entity.getRequestPayloadJson(),
|
||||
entity.getAttemptCount(),
|
||||
entity.getLastAttemptId(),
|
||||
entity.getLastErrorMessage(),
|
||||
entity.getCreatedAt(),
|
||||
entity.getUpdatedAt());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 OPERA 模拟操作 attempt 实体为快照。
|
||||
*/
|
||||
private ReservationOperaOperationAttemptSnapshot toOperaOperationAttemptSnapshot(
|
||||
ReservationOperaOperationAttemptEntity entity) {
|
||||
return new ReservationOperaOperationAttemptSnapshot(
|
||||
entity.getId(),
|
||||
entity.getHotelId(),
|
||||
entity.getOrderId(),
|
||||
entity.getTaskId(),
|
||||
entity.getOperationId(),
|
||||
entity.getAttemptNumber(),
|
||||
entity.getAttemptStatus(),
|
||||
entity.getRequestPayloadJson(),
|
||||
entity.getResponsePayloadJson(),
|
||||
entity.getErrorMessage(),
|
||||
entity.getStartedAt(),
|
||||
entity.getFinishedAt());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换审计实体为快照。
|
||||
*/
|
||||
private ReservationAuditLogSnapshot toAuditLogSnapshot(ReservationAuditLogEntity entity) {
|
||||
return new ReservationAuditLogSnapshot(
|
||||
entity.getId(),
|
||||
entity.getHotelId(),
|
||||
entity.getOrderId(),
|
||||
entity.getTaskId(),
|
||||
entity.getOperationId(),
|
||||
entity.getActorType(),
|
||||
entity.getActorId(),
|
||||
entity.getAction(),
|
||||
entity.getReason(),
|
||||
entity.getBeforeSnapshotJson(),
|
||||
entity.getAfterSnapshotJson(),
|
||||
entity.getOccurredAt());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@ import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiBatchDraf
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiBatchSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiTransitionDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAuditLogDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAuditLogSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOperaOperationAttemptDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOperaOperationAttemptSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOperaOperationDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOperaOperationSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOrderDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOrderSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskCardDraft;
|
||||
@@ -106,6 +111,62 @@ public interface ReservationAiWorkflowRepository {
|
||||
Integer executionOrder,
|
||||
LocalDateTime now);
|
||||
|
||||
/**
|
||||
* 保存任务卡草稿 payload。
|
||||
*/
|
||||
boolean updateTaskDraftPayload(String hotelId, Long taskId, String draftPayloadJson, LocalDateTime now);
|
||||
|
||||
/**
|
||||
* 保存最终确认 payload,并将任务状态从 PENDING_CONFIRM 流转为 READY。
|
||||
*/
|
||||
boolean confirmTaskPayload(
|
||||
String hotelId,
|
||||
Long taskId,
|
||||
String confirmedPayloadJson,
|
||||
String confirmedBy,
|
||||
LocalDateTime confirmedAt);
|
||||
|
||||
/**
|
||||
* 新增 OPERA 模拟操作,并返回操作 ID。
|
||||
*/
|
||||
Long insertOperaOperation(ReservationOperaOperationDraft draft);
|
||||
|
||||
/**
|
||||
* 查询任务下全部 OPERA 模拟操作。
|
||||
*/
|
||||
List<ReservationOperaOperationSnapshot> findOperaOperationsByTaskId(String hotelId, Long taskId);
|
||||
|
||||
/**
|
||||
* 按操作 ID 查询 OPERA 模拟操作。
|
||||
*/
|
||||
Optional<ReservationOperaOperationSnapshot> findOperaOperationById(String hotelId, Long operationId);
|
||||
|
||||
/**
|
||||
* 查询 OPERA 模拟操作的 attempt 历史。
|
||||
*/
|
||||
List<ReservationOperaOperationAttemptSnapshot> findOperaOperationAttempts(String hotelId, Long operationId);
|
||||
|
||||
/**
|
||||
* 新增 OPERA 模拟操作 attempt,并返回 attempt ID。
|
||||
*/
|
||||
Long insertOperaOperationAttempt(ReservationOperaOperationAttemptDraft draft);
|
||||
|
||||
/**
|
||||
* 更新 OPERA 模拟操作的当前状态和最近 attempt 摘要。
|
||||
*/
|
||||
boolean updateOperaOperationAfterAttempt(
|
||||
String hotelId,
|
||||
Long operationId,
|
||||
String operationStatus,
|
||||
Long lastAttemptId,
|
||||
String lastErrorMessage,
|
||||
LocalDateTime now);
|
||||
|
||||
/**
|
||||
* 更新任务状态,用于 OPERA 模拟操作全部成功后完成任务。
|
||||
*/
|
||||
boolean updateTaskStatus(String hotelId, Long taskId, String taskStatus, LocalDateTime now);
|
||||
|
||||
/**
|
||||
* 统计订单当前剩余任务数量,用于判断临时订单能否逻辑删除。
|
||||
*/
|
||||
@@ -120,4 +181,9 @@ public interface ReservationAiWorkflowRepository {
|
||||
* 新增订单任务审计记录。
|
||||
*/
|
||||
Long insertAuditLog(ReservationAuditLogDraft draft);
|
||||
|
||||
/**
|
||||
* 查询任务审计流水。
|
||||
*/
|
||||
List<ReservationAuditLogSnapshot> findAuditLogsByTaskId(String hotelId, Long taskId);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.service;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ManualReviewConversionRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationOperaSimulationRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationTaskPayloadMutationRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ManualReviewConversionResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOperaOperationResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskAuditListResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskDetailResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskPayloadMutationResult;
|
||||
|
||||
/**
|
||||
* Reservation 任务工作流服务。提供任务详情、队列可处理状态和人工复核转换能力。
|
||||
* Reservation 任务工作流服务。提供任务详情、队列可处理状态、人工复核转换和任务确认能力。
|
||||
*/
|
||||
public interface ReservationTaskWorkflowService {
|
||||
|
||||
@@ -18,4 +23,35 @@ public interface ReservationTaskWorkflowService {
|
||||
* 将 Fallback / manual_review 任务人工转换为 New、Update 或 Cancel。
|
||||
*/
|
||||
ManualReviewConversionResult convertManualReviewTask(Long taskId, ManualReviewConversionRequest request);
|
||||
|
||||
/**
|
||||
* 保存任务卡草稿;只允许当前可编辑的 PENDING_CONFIRM 任务调用。
|
||||
*/
|
||||
ReservationTaskPayloadMutationResult saveTaskDraft(Long taskId, ReservationTaskPayloadMutationRequest request);
|
||||
|
||||
/**
|
||||
* 最终确认任务卡字段;校验通过后任务从 PENDING_CONFIRM 流转为 READY。
|
||||
*/
|
||||
ReservationTaskPayloadMutationResult confirmTask(Long taskId, ReservationTaskPayloadMutationRequest request);
|
||||
|
||||
/**
|
||||
* 执行一条 OPERA 模拟操作;只允许前置模拟操作已成功的操作执行。
|
||||
*/
|
||||
ReservationOperaOperationResult executeOperaOperation(
|
||||
Long taskId,
|
||||
Long operationId,
|
||||
ReservationOperaSimulationRequest request);
|
||||
|
||||
/**
|
||||
* 重试失败的 OPERA 模拟操作;成功后继续保留 attempt 历史。
|
||||
*/
|
||||
ReservationOperaOperationResult retryOperaOperation(
|
||||
Long taskId,
|
||||
Long operationId,
|
||||
ReservationOperaSimulationRequest request);
|
||||
|
||||
/**
|
||||
* 查询任务审计流水。
|
||||
*/
|
||||
ReservationTaskAuditListResult listTaskAudits(Long taskId);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* Reservation 任务工作流受控异常。用于详情、队列状态和人工转换接口。
|
||||
* Reservation 任务工作流受控异常。用于详情、队列状态、人工转换和任务确认接口。
|
||||
*/
|
||||
public class ReservationTaskWorkflowException extends RuntimeException {
|
||||
|
||||
private final HttpStatus status;
|
||||
private final String errorCode;
|
||||
private final List<String> details;
|
||||
|
||||
public ReservationTaskWorkflowException(HttpStatus status, String errorCode, String message) {
|
||||
this(status, errorCode, message, List.of());
|
||||
}
|
||||
|
||||
public ReservationTaskWorkflowException(
|
||||
HttpStatus status,
|
||||
String errorCode,
|
||||
String message,
|
||||
List<String> details) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
this.errorCode = errorCode;
|
||||
this.details = details == null ? List.of() : List.copyOf(details);
|
||||
}
|
||||
|
||||
public HttpStatus getStatus() {
|
||||
@@ -23,4 +34,8 @@ public class ReservationTaskWorkflowException extends RuntimeException {
|
||||
public String getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public List<String> getDetails() {
|
||||
return details;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
-- M002 CP5:任务草稿和最终确认 payload。第一版按矩阵 field_path 保存,不直接生成 OPERA 参数。
|
||||
ALTER TABLE workflow_reservation_task_card
|
||||
ADD COLUMN draft_payload_json LONGTEXT NULL COMMENT '用户保存的任务卡草稿 JSON,第一版按矩阵 field_path 作为 key 保存';
|
||||
|
||||
ALTER TABLE workflow_reservation_task_card
|
||||
ADD COLUMN confirmed_payload_json LONGTEXT NULL COMMENT '用户最终确认后的任务卡 JSON,第一版按矩阵 field_path 合并保存,后续 OPERA 参数组装时再转换';
|
||||
|
||||
ALTER TABLE workflow_reservation_task_card
|
||||
ADD COLUMN confirmed_by VARCHAR(128) NULL COMMENT '确认人标识,用户身份权限接入前使用本地占位';
|
||||
|
||||
ALTER TABLE workflow_reservation_task_card
|
||||
ADD COLUMN confirmed_at DATETIME(6) NULL COMMENT '任务卡最终确认 UTC 时间';
|
||||
|
||||
ALTER TABLE workflow_reservation_task
|
||||
ADD COLUMN confirmed_at DATETIME(6) NULL COMMENT '任务最终确认 UTC 时间';
|
||||
@@ -0,0 +1,42 @@
|
||||
-- M002 OPERA 模拟骨架:每个已确认任务固定生成两条模拟操作。
|
||||
CREATE TABLE workflow_reservation_opera_operation (
|
||||
id BIGINT NOT NULL COMMENT 'OPERA 模拟操作 ID',
|
||||
hotel_id VARCHAR(64) NOT NULL COMMENT '酒店或业务上下文 ID',
|
||||
order_id BIGINT NOT NULL COMMENT '关联订单 ID',
|
||||
task_id BIGINT NOT NULL COMMENT '关联任务 ID',
|
||||
operation_sequence INT NOT NULL COMMENT '任务内模拟操作顺序,从 1 开始',
|
||||
operation_code VARCHAR(64) NOT NULL COMMENT '模拟操作代码,第一版固定两类',
|
||||
operation_name VARCHAR(128) NOT NULL COMMENT '模拟操作展示名称',
|
||||
operation_status VARCHAR(32) NOT NULL COMMENT '操作状态:PENDING、SUCCEEDED、FAILED',
|
||||
request_payload_json LONGTEXT NULL COMMENT '模拟请求 JSON,来源于 confirmed_payload_json 的低耦合摘要',
|
||||
attempt_count INT NOT NULL DEFAULT 0 COMMENT '已执行 attempt 次数',
|
||||
last_attempt_id BIGINT NULL COMMENT '最近一次 attempt ID',
|
||||
last_error_message VARCHAR(512) NULL COMMENT '最近一次失败原因摘要',
|
||||
created_at DATETIME(6) NOT NULL COMMENT '记录创建 UTC 时间',
|
||||
updated_at DATETIME(6) NOT NULL COMMENT '记录更新 UTC 时间',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_reservation_opera_operation_sequence (hotel_id, task_id, operation_sequence),
|
||||
KEY idx_reservation_opera_operation_task_status (hotel_id, task_id, operation_status),
|
||||
KEY idx_reservation_opera_operation_order (hotel_id, order_id, operation_sequence)
|
||||
) COMMENT='Reservation OPERA 模拟操作表,第一版每个任务固定两条操作';
|
||||
|
||||
-- M002 OPERA 模拟骨架:每次执行或重试都会生成一条 attempt。
|
||||
CREATE TABLE workflow_reservation_opera_operation_attempt (
|
||||
id BIGINT NOT NULL COMMENT 'OPERA 模拟操作 attempt ID',
|
||||
hotel_id VARCHAR(64) NOT NULL COMMENT '酒店或业务上下文 ID',
|
||||
order_id BIGINT NOT NULL COMMENT '关联订单 ID',
|
||||
task_id BIGINT NOT NULL COMMENT '关联任务 ID',
|
||||
operation_id BIGINT NOT NULL COMMENT '关联 OPERA 模拟操作 ID',
|
||||
attempt_number INT NOT NULL COMMENT '同一操作内第几次 attempt,从 1 开始',
|
||||
attempt_status VARCHAR(32) NOT NULL COMMENT 'attempt 状态:SUCCEEDED、FAILED',
|
||||
request_payload_json LONGTEXT NULL COMMENT '本次模拟请求 JSON',
|
||||
response_payload_json LONGTEXT NULL COMMENT '本次模拟响应 JSON',
|
||||
error_message VARCHAR(512) NULL COMMENT '失败原因摘要',
|
||||
started_at DATETIME(6) NOT NULL COMMENT 'attempt 开始 UTC 时间',
|
||||
finished_at DATETIME(6) NOT NULL COMMENT 'attempt 结束 UTC 时间',
|
||||
created_at DATETIME(6) NOT NULL COMMENT '记录创建 UTC 时间',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_reservation_opera_attempt_number (hotel_id, operation_id, attempt_number),
|
||||
KEY idx_reservation_opera_attempt_task (hotel_id, task_id, created_at),
|
||||
KEY idx_reservation_opera_attempt_operation (hotel_id, operation_id, attempt_number)
|
||||
) COMMENT='Reservation OPERA 模拟操作 attempt 表,记录执行和重试结果';
|
||||
Reference in New Issue
Block a user