实现前端P0订单任务查询接口
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
package cn.nianxx.thhotel.platform.message.repository;
|
||||
|
||||
import cn.nianxx.thhotel.platform.message.common.request.CaptureSourceMessageMedia;
|
||||
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageInboxDraft;
|
||||
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageInboxSnapshot;
|
||||
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageOriginalAccessAuditDraft;
|
||||
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageOriginalContent;
|
||||
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageOriginalMediaItem;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessagePageResult;
|
||||
import cn.nianxx.thhotel.platform.message.common.request.SourceMessageQueryRequest;
|
||||
import cn.nianxx.thhotel.platform.message.common.enums.SourceMessageBodyContentType;
|
||||
import cn.nianxx.thhotel.platform.message.common.enums.SourceMessageCaptureStatus;
|
||||
import cn.nianxx.thhotel.platform.message.common.request.CaptureSourceMessageMedia;
|
||||
import cn.nianxx.thhotel.platform.message.common.request.SourceMessageQueryRequest;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessagePageResult;
|
||||
import cn.nianxx.thhotel.platform.message.domain.SourceMessageBodyEntity;
|
||||
import cn.nianxx.thhotel.platform.message.domain.SourceMessageInboxEntity;
|
||||
import cn.nianxx.thhotel.platform.message.domain.SourceMessageMediaEntity;
|
||||
@@ -112,6 +112,34 @@ public class MybatisSourceMessageInboxRepository implements SourceMessageInboxRe
|
||||
return new SourceMessagePageResult<>(items, page.getTotal(), pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按安全摘要关键词查询 SourceMessage ID。这里只查询列表可展示字段,不触碰正文、HTML、附件 URL 或 payload。
|
||||
*/
|
||||
@Override
|
||||
public List<Long> findIdsBySafeKeyword(String hotelId, String keyword, int limit) {
|
||||
if (!hasText(keyword) || limit < 1) {
|
||||
return List.of();
|
||||
}
|
||||
String trimmedKeyword = trim(keyword);
|
||||
Page<SourceMessageInboxEntity> page = inboxMapper.selectPage(Page.of(1, limit),
|
||||
Wrappers.<SourceMessageInboxEntity>lambdaQuery()
|
||||
.eq(hasText(hotelId), SourceMessageInboxEntity::getHotelId, trim(hotelId))
|
||||
.and(wrapper -> wrapper
|
||||
.like(SourceMessageInboxEntity::getExternalMessageId, trimmedKeyword)
|
||||
.or()
|
||||
.like(SourceMessageInboxEntity::getExternalConversationId, trimmedKeyword)
|
||||
.or()
|
||||
.like(SourceMessageInboxEntity::getSenderSummary, trimmedKeyword)
|
||||
.or()
|
||||
.like(SourceMessageInboxEntity::getSubject, trimmedKeyword)
|
||||
.or()
|
||||
.like(SourceMessageInboxEntity::getSafeSnippet, trimmedKeyword))
|
||||
.orderByDesc(SourceMessageInboxEntity::getReceivedAt));
|
||||
return page.getRecords().stream()
|
||||
.map(SourceMessageInboxEntity::getId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入 Inbox 主记录及原始 payload;RECEIVED 状态额外保存正文和媒体引用。
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageOriginalConten
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessagePageResult;
|
||||
import cn.nianxx.thhotel.platform.message.common.request.SourceMessageQueryRequest;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -36,6 +37,11 @@ public interface SourceMessageInboxRepository {
|
||||
int pageNum,
|
||||
int pageSize);
|
||||
|
||||
/**
|
||||
* 按来源消息安全摘要关键词查询内部 SourceMessage ID,只匹配普通列表可展示字段。
|
||||
*/
|
||||
List<Long> findIdsBySafeKeyword(String hotelId, String keyword, int limit);
|
||||
|
||||
/**
|
||||
* 插入 Inbox 及其 payload/body/media 子记录,返回内部 SourceMessage ID。
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.nianxx.thhotel.platform.message.service;
|
||||
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageSummaryResponse;
|
||||
import cn.nianxx.thhotel.platform.message.common.request.SourceMessageQueryRequest;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessagePageResult;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -18,6 +19,15 @@ public interface SourceMessageQueryService {
|
||||
*/
|
||||
SourceMessagePageResult<SourceMessageSummaryResponse> query(SourceMessageQueryRequest request);
|
||||
|
||||
/**
|
||||
* 按来源消息安全摘要关键词查询内部 SourceMessage ID,供业务列表做安全关联过滤。
|
||||
*
|
||||
* @param hotelId 酒店上下文 ID
|
||||
* @param keyword 来源消息安全摘要关键词
|
||||
* @return 匹配的内部 SourceMessage ID 列表
|
||||
*/
|
||||
List<Long> findIdsBySafeKeyword(String hotelId, String keyword);
|
||||
|
||||
/**
|
||||
* 按内部 SourceMessage ID 读取单条安全摘要。
|
||||
*
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package cn.nianxx.thhotel.platform.message.service.impl;
|
||||
|
||||
import cn.nianxx.thhotel.platform.message.repository.SourceMessageInboxRepository;
|
||||
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageInboxSnapshot;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessagePageResult;
|
||||
import cn.nianxx.thhotel.platform.message.common.request.SourceMessageQueryRequest;
|
||||
import cn.nianxx.thhotel.platform.message.service.SourceMessageQueryService;
|
||||
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageSummaryResponse;
|
||||
import cn.nianxx.thhotel.platform.message.common.request.SourceMessageQueryRequest;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessagePageResult;
|
||||
import cn.nianxx.thhotel.platform.message.repository.SourceMessageInboxRepository;
|
||||
import cn.nianxx.thhotel.platform.message.service.SourceMessageQueryService;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
@@ -22,6 +22,7 @@ public class SourceMessageQueryServiceImpl implements SourceMessageQueryService
|
||||
private static final int DEFAULT_PAGE_NUM = 1;
|
||||
private static final int DEFAULT_PAGE_SIZE = 20;
|
||||
private static final int MAX_PAGE_SIZE = 100;
|
||||
private static final int MAX_SAFE_KEYWORD_MATCHES = 500;
|
||||
|
||||
private final SourceMessageInboxRepository inboxRepository;
|
||||
|
||||
@@ -44,6 +45,14 @@ public class SourceMessageQueryServiceImpl implements SourceMessageQueryService
|
||||
return new SourceMessagePageResult<>(items, page.total(), pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询匹配来源消息安全摘要关键词的内部 SourceMessage ID,供业务查询做关联过滤。
|
||||
*/
|
||||
@Override
|
||||
public List<Long> findIdsBySafeKeyword(String hotelId, String keyword) {
|
||||
return inboxRepository.findIdsBySafeKeyword(hotelId, keyword, MAX_SAFE_KEYWORD_MATCHES);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取单条 SourceMessage 安全摘要,不返回正文、HTML、媒体 URL 或原始 payload。
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.time.LocalDateTime;
|
||||
* @param blockedUntilParentCompleted 是否等待父任务完成
|
||||
* @param lastFailureReason 最近失败原因
|
||||
* @param completedAt 任务完成时间
|
||||
* @param createdAt 创建时间
|
||||
* @param updatedAt 最近更新时间
|
||||
* @param transitionSourceEventIndex AI transition 来源事件序号
|
||||
* @param catalogCode Skill 目录代码
|
||||
@@ -49,6 +50,7 @@ public record ReservationAiQueryTaskSnapshot(
|
||||
Boolean blockedUntilParentCompleted,
|
||||
String lastFailureReason,
|
||||
LocalDateTime completedAt,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt,
|
||||
Integer transitionSourceEventIndex,
|
||||
String catalogCode,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Reservation 模块内部分页快照。Repository 返回该对象,Service 再转换为前端响应结构。
|
||||
*
|
||||
* @param items 当前页记录
|
||||
* @param total 符合条件的总记录数
|
||||
* @param pageNum 当前页码,从 1 开始
|
||||
* @param pageSize 每页数量
|
||||
* @param <T> 分页记录类型
|
||||
*/
|
||||
public record ReservationPageSnapshot<T>(
|
||||
List<T> items,
|
||||
long total,
|
||||
int pageNum,
|
||||
int pageSize
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.request;
|
||||
|
||||
/**
|
||||
* 前端任务列表 / 工作台查询条件。第一版只做只读查询,不改变任务或订单状态。
|
||||
*
|
||||
* @param hotelId 酒店上下文 ID
|
||||
* @param orderId 订单 ID 过滤
|
||||
* @param taskType 系统主任务类型过滤
|
||||
* @param taskStatus 任务状态过滤
|
||||
* @param taskSubtype 任务 subtype 过滤
|
||||
* @param queueParticipation 是否参与订单执行队列
|
||||
* @param keyword 业务号、临时订单号、任务字段或来源消息安全摘要关键词
|
||||
* @param pageNum 页码,从 1 开始
|
||||
* @param pageSize 每页数量
|
||||
*/
|
||||
public record ReservationTaskWorkbenchQueryRequest(
|
||||
String hotelId,
|
||||
Long orderId,
|
||||
String taskType,
|
||||
String taskStatus,
|
||||
String taskSubtype,
|
||||
Boolean queueParticipation,
|
||||
String keyword,
|
||||
Integer pageNum,
|
||||
Integer pageSize
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.result;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 前端订单详情响应,包含订单摘要、任务时间线和非阻塞警告。
|
||||
*
|
||||
* @param order 订单摘要
|
||||
* @param tasks 同订单任务时间线
|
||||
* @param warnings 当前无法提供的扩展信息或非阻塞提醒
|
||||
*/
|
||||
public record ReservationOrderDetailResult(
|
||||
ReservationOrderSummaryResult order,
|
||||
List<ReservationOrderTaskTimelineItemResult> tasks,
|
||||
List<String> warnings
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* 前端订单详情页的订单摘要。只包含本系统已有订单快照,不伪造 OPERA 投影字段。
|
||||
*
|
||||
* @param orderId 订单 ID
|
||||
* @param hotelId 酒店上下文 ID
|
||||
* @param orderStatus 订单状态
|
||||
* @param temporaryOrderNo 临时订单号
|
||||
* @param confirmationNumber Confirmation No.
|
||||
* @param groupCode Group Code
|
||||
* @param blockCode Block Code,当前无可靠来源时为空
|
||||
* @param allotmentCode Allotment Code,当前无可靠来源时为空
|
||||
* @param displayName 前端展示名称
|
||||
* @param createdAt 创建时间
|
||||
* @param updatedAt 最近更新时间
|
||||
*/
|
||||
public record ReservationOrderSummaryResult(
|
||||
@JsonProperty("order_id")
|
||||
String orderId,
|
||||
@JsonProperty("hotel_id")
|
||||
String hotelId,
|
||||
@JsonProperty("order_status")
|
||||
String orderStatus,
|
||||
@JsonProperty("temporary_order_no")
|
||||
String temporaryOrderNo,
|
||||
@JsonProperty("confirmation_number")
|
||||
String confirmationNumber,
|
||||
@JsonProperty("group_code")
|
||||
String groupCode,
|
||||
@JsonProperty("block_code")
|
||||
String blockCode,
|
||||
@JsonProperty("allotment_code")
|
||||
String allotmentCode,
|
||||
@JsonProperty("display_name")
|
||||
String displayName,
|
||||
@JsonProperty("created_at")
|
||||
String createdAt,
|
||||
@JsonProperty("updated_at")
|
||||
String updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* 订单详情页任务时间线单项。用于前端按同订单任务顺序展示处理状态。
|
||||
*
|
||||
* @param taskId 任务 ID
|
||||
* @param taskType 系统主任务类型
|
||||
* @param taskSubtype 任务 subtype
|
||||
* @param taskStatus 任务状态
|
||||
* @param cardName 任务卡展示名称
|
||||
* @param queueSequence 同订单队列顺序
|
||||
* @param queueParticipation 是否参与执行队列
|
||||
* @param canProcess 当前是否可处理
|
||||
* @param readonlyReasonCode 只读原因代码
|
||||
* @param createdAt 任务创建时间
|
||||
*/
|
||||
public record ReservationOrderTaskTimelineItemResult(
|
||||
@JsonProperty("task_id")
|
||||
String taskId,
|
||||
@JsonProperty("task_type")
|
||||
String taskType,
|
||||
@JsonProperty("task_subtype")
|
||||
String taskSubtype,
|
||||
@JsonProperty("task_status")
|
||||
String taskStatus,
|
||||
@JsonProperty("card_name")
|
||||
String cardName,
|
||||
@JsonProperty("queue_sequence")
|
||||
Integer queueSequence,
|
||||
@JsonProperty("queue_participation")
|
||||
Boolean queueParticipation,
|
||||
@JsonProperty("can_process")
|
||||
Boolean canProcess,
|
||||
@JsonProperty("readonly_reason_code")
|
||||
String readonlyReasonCode,
|
||||
@JsonProperty("created_at")
|
||||
String createdAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* 前端分页元数据。字段名使用 snake_case,避免前端自行转换。
|
||||
*
|
||||
* @param pageNum 当前页码,从 1 开始
|
||||
* @param pageSize 每页数量
|
||||
* @param total 符合条件的总记录数
|
||||
*/
|
||||
public record ReservationPaginationResult(
|
||||
@JsonProperty("page_num")
|
||||
int pageNum,
|
||||
@JsonProperty("page_size")
|
||||
int pageSize,
|
||||
long total
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* 前端任务列表 / 工作台单行结果。只返回摘要和可处理状态,不返回 AI 原始 payload。
|
||||
*
|
||||
* @param taskId 任务 ID,按字符串返回避免前端长整型精度问题
|
||||
* @param orderId 订单 ID
|
||||
* @param hotelId 酒店上下文 ID
|
||||
* @param displayOrderKey 前端优先展示的订单业务号或临时订单号
|
||||
* @param temporaryOrderNo 临时订单号
|
||||
* @param taskType 系统主任务类型
|
||||
* @param taskSubtype 任务 subtype
|
||||
* @param taskStatus 任务状态
|
||||
* @param cardName 任务卡展示名称,第一版使用任务卡类型
|
||||
* @param queueSequence 同订单队列顺序
|
||||
* @param queueParticipation 是否参与订单执行队列
|
||||
* @param canProcess 当前是否可处理
|
||||
* @param readonlyReasonCode 只读原因代码
|
||||
* @param sourceMessageId 来源 SourceMessage Inbox ID
|
||||
* @param sourceSubject 来源消息主题摘要
|
||||
* @param createdAt 任务创建时间
|
||||
* @param updatedAt 任务更新时间
|
||||
*/
|
||||
public record ReservationTaskWorkbenchItemResult(
|
||||
@JsonProperty("task_id")
|
||||
String taskId,
|
||||
@JsonProperty("order_id")
|
||||
String orderId,
|
||||
@JsonProperty("hotel_id")
|
||||
String hotelId,
|
||||
@JsonProperty("display_order_key")
|
||||
String displayOrderKey,
|
||||
@JsonProperty("temporary_order_no")
|
||||
String temporaryOrderNo,
|
||||
@JsonProperty("task_type")
|
||||
String taskType,
|
||||
@JsonProperty("task_subtype")
|
||||
String taskSubtype,
|
||||
@JsonProperty("task_status")
|
||||
String taskStatus,
|
||||
@JsonProperty("card_name")
|
||||
String cardName,
|
||||
@JsonProperty("queue_sequence")
|
||||
Integer queueSequence,
|
||||
@JsonProperty("queue_participation")
|
||||
Boolean queueParticipation,
|
||||
@JsonProperty("can_process")
|
||||
Boolean canProcess,
|
||||
@JsonProperty("readonly_reason_code")
|
||||
String readonlyReasonCode,
|
||||
@JsonProperty("source_message_id")
|
||||
String sourceMessageId,
|
||||
@JsonProperty("source_subject")
|
||||
String sourceSubject,
|
||||
@JsonProperty("created_at")
|
||||
String createdAt,
|
||||
@JsonProperty("updated_at")
|
||||
String updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.result;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 前端任务列表 / 工作台分页响应。
|
||||
*
|
||||
* @param items 当前页任务摘要
|
||||
* @param page 分页元数据
|
||||
*/
|
||||
public record ReservationTaskWorkbenchListResult(
|
||||
List<ReservationTaskWorkbenchItemResult> items,
|
||||
ReservationPaginationResult page
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.control;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationTaskWorkbenchQueryRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOrderDetailResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskWorkbenchListResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.service.ReservationFrontendQueryService;
|
||||
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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Reservation 前端查询 Controller。提供页面读取接口,不做业务写操作。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/reservation")
|
||||
public class ReservationFrontendQueryController {
|
||||
|
||||
private final ReservationFrontendQueryService frontendQueryService;
|
||||
|
||||
/**
|
||||
* 注入前端查询服务,Controller 只负责 HTTP 参数到查询对象的转换。
|
||||
*/
|
||||
public ReservationFrontendQueryController(ReservationFrontendQueryService frontendQueryService) {
|
||||
this.frontendQueryService = frontendQueryService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询任务列表 / 工作台摘要,供前端按任务处理状态展示入口。
|
||||
*/
|
||||
@GetMapping(value = "/tasks", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReservationTaskWorkbenchListResult listTasks(
|
||||
@RequestParam(name = "hotel_id", required = false) String hotelId,
|
||||
@RequestParam(name = "order_id", required = false) Long orderId,
|
||||
@RequestParam(name = "task_type", required = false) String taskType,
|
||||
@RequestParam(name = "task_status", required = false) String taskStatus,
|
||||
@RequestParam(name = "task_subtype", required = false) String taskSubtype,
|
||||
@RequestParam(name = "queue_participation", required = false) Boolean queueParticipation,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(name = "page_num", required = false) Integer pageNum,
|
||||
@RequestParam(name = "page_size", required = false) Integer pageSize) {
|
||||
return frontendQueryService.queryTaskWorkbench(new ReservationTaskWorkbenchQueryRequest(
|
||||
hotelId,
|
||||
orderId,
|
||||
taskType,
|
||||
taskStatus,
|
||||
taskSubtype,
|
||||
queueParticipation,
|
||||
keyword,
|
||||
pageNum,
|
||||
pageSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单详情和任务时间线,供订单详情页展示当前订单处理脉络。
|
||||
*/
|
||||
@GetMapping(value = "/orders/{orderId}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ReservationOrderDetailResult orderDetail(
|
||||
@PathVariable Long orderId,
|
||||
@RequestParam(name = "hotel_id", required = false) String hotelId,
|
||||
@RequestParam(name = "include_tasks", required = false) Boolean includeTasks,
|
||||
@RequestParam(name = "include_source_summary", required = false) Boolean includeSourceSummary) {
|
||||
return frontendQueryService.getOrderDetail(hotelId, orderId, includeTasks, includeSourceSummary);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,10 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
/**
|
||||
* Reservation 任务接口统一异常处理。错误响应不暴露内部堆栈或原始 AI payload。
|
||||
*/
|
||||
@RestControllerAdvice(assignableTypes = ReservationTaskController.class)
|
||||
@RestControllerAdvice(assignableTypes = {
|
||||
ReservationTaskController.class,
|
||||
ReservationFrontendQueryController.class
|
||||
})
|
||||
public class ReservationTaskControllerAdvice {
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,10 +13,12 @@ import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOperaOperat
|
||||
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.ReservationPageSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskCardDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskCardSnapshot;
|
||||
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.request.ReservationTaskWorkbenchQueryRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOrderKeyType;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOrderStatus;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationTaskStatus;
|
||||
@@ -37,6 +39,7 @@ import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationOrderMapper;
|
||||
import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationTaskCardMapper;
|
||||
import cn.nianxx.thhotel.workflows.reservation.mapper.ReservationTaskMapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -352,6 +355,63 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
return Optional.ofNullable(entity).map(this::toAiQueryOrderSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按订单 ID 批量查询订单快照,供前端任务列表补充订单展示键。
|
||||
*/
|
||||
@Override
|
||||
public List<ReservationAiQueryOrderSnapshot> findAiQueryOrdersByIds(String hotelId, List<Long> orderIds) {
|
||||
if (orderIds == null || orderIds.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return orderMapper.selectList(Wrappers.<ReservationOrderEntity>lambdaQuery()
|
||||
.eq(ReservationOrderEntity::getHotelId, hotelId)
|
||||
.in(ReservationOrderEntity::getId, orderIds))
|
||||
.stream()
|
||||
.map(this::toAiQueryOrderSnapshot)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端任务列表分页查询。只读取任务摘要字段,完整任务卡 payload 仍走任务详情接口。
|
||||
*/
|
||||
@Override
|
||||
public ReservationPageSnapshot<ReservationAiQueryTaskSnapshot> queryFrontendTasks(
|
||||
ReservationTaskWorkbenchQueryRequest request,
|
||||
List<Long> keywordSourceMessageIds,
|
||||
int pageNum,
|
||||
int pageSize) {
|
||||
List<Long> keywordOrderIds = findOrderIdsByKeyword(request.hotelId(), request.keyword());
|
||||
List<Long> sourceMessageIds = keywordSourceMessageIds == null ? List.of() : keywordSourceMessageIds;
|
||||
Page<ReservationTaskEntity> page = taskMapper.selectPage(Page.of(pageNum, pageSize),
|
||||
Wrappers.<ReservationTaskEntity>lambdaQuery()
|
||||
.eq(ReservationTaskEntity::getHotelId, request.hotelId())
|
||||
.eq(request.orderId() != null, ReservationTaskEntity::getOrderId, request.orderId())
|
||||
.eq(hasText(request.taskType()), ReservationTaskEntity::getSystemTaskType, trim(request.taskType()))
|
||||
.eq(hasText(request.taskStatus()), ReservationTaskEntity::getTaskStatus, trim(request.taskStatus()))
|
||||
.eq(hasText(request.taskSubtype()), ReservationTaskEntity::getTaskSubtype, trim(request.taskSubtype()))
|
||||
.eq(request.queueParticipation() != null,
|
||||
ReservationTaskEntity::getQueueParticipation,
|
||||
request.queueParticipation())
|
||||
.and(hasText(request.keyword()), wrapper -> {
|
||||
wrapper.like(ReservationTaskEntity::getSystemTaskType, trim(request.keyword()))
|
||||
.or()
|
||||
.like(ReservationTaskEntity::getTaskSubtype, trim(request.keyword()));
|
||||
if (!keywordOrderIds.isEmpty()) {
|
||||
wrapper.or().in(ReservationTaskEntity::getOrderId, keywordOrderIds);
|
||||
}
|
||||
if (!sourceMessageIds.isEmpty()) {
|
||||
wrapper.or().in(ReservationTaskEntity::getSourceMessageId, sourceMessageIds);
|
||||
}
|
||||
})
|
||||
.orderByAsc(ReservationTaskEntity::getOrderId)
|
||||
.orderByAsc(ReservationTaskEntity::getExecutionOrder));
|
||||
return new ReservationPageSnapshot<>(
|
||||
toAiQueryTaskSnapshots(request.hotelId(), page.getRecords()),
|
||||
page.getTotal(),
|
||||
pageNum,
|
||||
pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定订单下全部任务,并补充对应 AI transition 的事件序号和 Skill 信息。
|
||||
*/
|
||||
@@ -738,6 +798,36 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
return value != null && !value.isBlank();
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除查询文本首尾空白,避免空格参与等值查询。
|
||||
*/
|
||||
private String trim(String value) {
|
||||
return value == null ? null : value.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按订单业务号、临时订单号和展示名查询订单 ID,用于任务列表关键词过滤。
|
||||
*/
|
||||
private List<Long> findOrderIdsByKeyword(String hotelId, String keyword) {
|
||||
if (!hasText(keyword)) {
|
||||
return List.of();
|
||||
}
|
||||
String trimmedKeyword = trim(keyword);
|
||||
return orderMapper.selectList(Wrappers.<ReservationOrderEntity>lambdaQuery()
|
||||
.eq(ReservationOrderEntity::getHotelId, hotelId)
|
||||
.and(wrapper -> wrapper
|
||||
.like(ReservationOrderEntity::getOrderBusinessKey, trimmedKeyword)
|
||||
.or()
|
||||
.like(ReservationOrderEntity::getActiveBusinessKey, trimmedKeyword)
|
||||
.or()
|
||||
.like(ReservationOrderEntity::getTemporaryOrderCode, trimmedKeyword)
|
||||
.or()
|
||||
.like(ReservationOrderEntity::getDisplayName, trimmedKeyword)))
|
||||
.stream()
|
||||
.map(ReservationOrderEntity::getId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换批次实体为快照。
|
||||
*/
|
||||
@@ -839,6 +929,7 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
entity.getBlockedUntilParentCompleted(),
|
||||
entity.getLastFailureReason(),
|
||||
entity.getCompletedAt(),
|
||||
entity.getCreatedAt(),
|
||||
entity.getUpdatedAt(),
|
||||
transition == null ? null : transition.getSourceEventIndex(),
|
||||
transition == null ? null : transition.getCatalogCode(),
|
||||
|
||||
@@ -13,10 +13,12 @@ import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOperaOperat
|
||||
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.ReservationPageSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskCardDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskCardSnapshot;
|
||||
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.request.ReservationTaskWorkbenchQueryRequest;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -102,6 +104,20 @@ public interface ReservationAiWorkflowRepository {
|
||||
*/
|
||||
Optional<ReservationAiQueryOrderSnapshot> findAiQueryOrderById(String hotelId, Long orderId);
|
||||
|
||||
/**
|
||||
* 按订单 ID 批量查询订单快照,供前端列表补充展示字段。
|
||||
*/
|
||||
List<ReservationAiQueryOrderSnapshot> findAiQueryOrdersByIds(String hotelId, List<Long> orderIds);
|
||||
|
||||
/**
|
||||
* 分页查询前端任务列表 / 工作台摘要。
|
||||
*/
|
||||
ReservationPageSnapshot<ReservationAiQueryTaskSnapshot> queryFrontendTasks(
|
||||
ReservationTaskWorkbenchQueryRequest request,
|
||||
List<Long> keywordSourceMessageIds,
|
||||
int pageNum,
|
||||
int pageSize);
|
||||
|
||||
/**
|
||||
* 查询指定订单下全部任务,并带上对应 AI transition 的路由字段。
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.service;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationTaskWorkbenchQueryRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOrderDetailResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskWorkbenchListResult;
|
||||
|
||||
/**
|
||||
* Reservation 前端页面查询服务。提供任务工作台和订单详情时间线,不执行任何业务写操作。
|
||||
*/
|
||||
public interface ReservationFrontendQueryService {
|
||||
|
||||
/**
|
||||
* 查询任务列表 / 工作台摘要,并实时计算每条任务当前是否可处理。
|
||||
*/
|
||||
ReservationTaskWorkbenchListResult queryTaskWorkbench(ReservationTaskWorkbenchQueryRequest request);
|
||||
|
||||
/**
|
||||
* 查询订单详情和同订单任务时间线,供前端订单详情页展示。
|
||||
*/
|
||||
ReservationOrderDetailResult getOrderDetail(
|
||||
String hotelId,
|
||||
Long orderId,
|
||||
Boolean includeTasks,
|
||||
Boolean includeSourceSummary);
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.service.impl;
|
||||
|
||||
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageSummaryResponse;
|
||||
import cn.nianxx.thhotel.platform.message.service.SourceMessageQueryService;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiQueryOrderSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiQueryTaskSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationPageSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOrderKeyType;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationTaskStatus;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationTaskWorkbenchQueryRequest;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOrderDetailResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOrderSummaryResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOrderTaskTimelineItemResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationPaginationResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskAvailabilityResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskWorkbenchItemResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskWorkbenchListResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.repository.ReservationAiWorkflowRepository;
|
||||
import cn.nianxx.thhotel.workflows.reservation.service.ReservationFrontendQueryService;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Reservation 前端查询服务实现。只组装安全摘要和实时可处理状态,不返回 AI 原始 payload。
|
||||
*/
|
||||
@Service
|
||||
public class ReservationFrontendQueryServiceImpl implements ReservationFrontendQueryService {
|
||||
|
||||
private static final String DEFAULT_HOTEL_ID = "HOTEL-TEST";
|
||||
private static final int DEFAULT_PAGE_NUM = 1;
|
||||
private static final int DEFAULT_PAGE_SIZE = 20;
|
||||
private static final int MAX_PAGE_SIZE = 100;
|
||||
private static final String REASON_PROCESSABLE = "PROCESSABLE";
|
||||
private static final String REASON_PREVIOUS_TASK_NOT_FINISHED = "PREVIOUS_TASK_NOT_FINISHED";
|
||||
private static final String REASON_TASK_FINISHED = "TASK_FINISHED";
|
||||
private static final String REASON_READ_ONLY = "READ_ONLY";
|
||||
|
||||
private final ReservationAiWorkflowRepository workflowRepository;
|
||||
private final SourceMessageQueryService sourceMessageQueryService;
|
||||
private final ReservationTaskAvailabilityResolver availabilityResolver;
|
||||
|
||||
/**
|
||||
* 注入持久化边界、SourceMessage 安全摘要服务和可处理状态解析器。
|
||||
*/
|
||||
public ReservationFrontendQueryServiceImpl(
|
||||
ReservationAiWorkflowRepository workflowRepository,
|
||||
SourceMessageQueryService sourceMessageQueryService,
|
||||
ReservationTaskAvailabilityResolver availabilityResolver) {
|
||||
this.workflowRepository = workflowRepository;
|
||||
this.sourceMessageQueryService = sourceMessageQueryService;
|
||||
this.availabilityResolver = availabilityResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询前端任务列表,并补充订单展示键和来源消息安全主题摘要。
|
||||
*/
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ReservationTaskWorkbenchListResult queryTaskWorkbench(ReservationTaskWorkbenchQueryRequest request) {
|
||||
ReservationTaskWorkbenchQueryRequest normalizedRequest = normalizeRequest(request);
|
||||
int pageNum = normalizePageNum(normalizedRequest.pageNum());
|
||||
int pageSize = normalizePageSize(normalizedRequest.pageSize());
|
||||
List<Long> keywordSourceMessageIds = findSourceMessageIdsByKeyword(
|
||||
normalizedRequest.hotelId(),
|
||||
normalizedRequest.keyword());
|
||||
ReservationPageSnapshot<ReservationAiQueryTaskSnapshot> page =
|
||||
workflowRepository.queryFrontendTasks(normalizedRequest, keywordSourceMessageIds, pageNum, pageSize);
|
||||
Map<Long, ReservationAiQueryOrderSnapshot> ordersById = findOrdersById(
|
||||
normalizedRequest.hotelId(),
|
||||
page.items().stream().map(ReservationAiQueryTaskSnapshot::orderId).toList());
|
||||
List<ReservationTaskWorkbenchItemResult> items = page.items().stream()
|
||||
.map(task -> toWorkbenchItem(task, ordersById.get(task.orderId())))
|
||||
.toList();
|
||||
return new ReservationTaskWorkbenchListResult(
|
||||
items,
|
||||
new ReservationPaginationResult(page.pageNum(), page.pageSize(), page.total()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单详情。includeTasks=false 时只返回订单摘要,便于后续前端轻量使用。
|
||||
*/
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ReservationOrderDetailResult getOrderDetail(
|
||||
String hotelId,
|
||||
Long orderId,
|
||||
Boolean includeTasks,
|
||||
Boolean includeSourceSummary) {
|
||||
String normalizedHotelId = normalizeHotelId(hotelId);
|
||||
ReservationAiQueryOrderSnapshot order = workflowRepository
|
||||
.findAiQueryOrderById(normalizedHotelId, orderId)
|
||||
.orElseThrow(() -> new ReservationTaskWorkflowException(
|
||||
HttpStatus.NOT_FOUND,
|
||||
"ORDER_NOT_FOUND",
|
||||
"订单不存在。"));
|
||||
List<ReservationOrderTaskTimelineItemResult> tasks = Boolean.FALSE.equals(includeTasks)
|
||||
? List.of()
|
||||
: workflowRepository.findAiQueryTasksByOrderIds(normalizedHotelId, List.of(order.id()))
|
||||
.stream()
|
||||
.map(this::toTimelineItem)
|
||||
.toList();
|
||||
return new ReservationOrderDetailResult(toOrderSummary(order), tasks, List.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化任务列表查询条件,避免 Controller 层散落默认值规则。
|
||||
*/
|
||||
private ReservationTaskWorkbenchQueryRequest normalizeRequest(ReservationTaskWorkbenchQueryRequest request) {
|
||||
if (request == null) {
|
||||
return new ReservationTaskWorkbenchQueryRequest(
|
||||
DEFAULT_HOTEL_ID,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
DEFAULT_PAGE_NUM,
|
||||
DEFAULT_PAGE_SIZE);
|
||||
}
|
||||
return new ReservationTaskWorkbenchQueryRequest(
|
||||
normalizeHotelId(request.hotelId()),
|
||||
request.orderId(),
|
||||
trimToNull(request.taskType()),
|
||||
trimToNull(request.taskStatus()),
|
||||
trimToNull(request.taskSubtype()),
|
||||
request.queueParticipation(),
|
||||
trimToNull(request.keyword()),
|
||||
request.pageNum(),
|
||||
request.pageSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量读取订单快照并按订单 ID 建立索引。
|
||||
*/
|
||||
private Map<Long, ReservationAiQueryOrderSnapshot> findOrdersById(String hotelId, List<Long> orderIds) {
|
||||
Map<Long, ReservationAiQueryOrderSnapshot> result = new LinkedHashMap<>();
|
||||
workflowRepository.findAiQueryOrdersByIds(hotelId, orderIds)
|
||||
.forEach(order -> result.put(order.id(), order));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按来源消息安全摘要关键词查找 SourceMessage ID,避免任务列表直接读取来源消息正文。
|
||||
*/
|
||||
private List<Long> findSourceMessageIdsByKeyword(String hotelId, String keyword) {
|
||||
if (trimToNull(keyword) == null) {
|
||||
return List.of();
|
||||
}
|
||||
return sourceMessageQueryService.findIdsBySafeKeyword(hotelId, keyword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换任务快照为前端工作台单行结果。
|
||||
*/
|
||||
private ReservationTaskWorkbenchItemResult toWorkbenchItem(
|
||||
ReservationAiQueryTaskSnapshot task,
|
||||
ReservationAiQueryOrderSnapshot order) {
|
||||
ReservationTaskAvailabilityResult availability = availabilityResolver.calculateAvailability(toTaskSnapshot(task));
|
||||
return new ReservationTaskWorkbenchItemResult(
|
||||
task.id().toString(),
|
||||
task.orderId().toString(),
|
||||
task.hotelId(),
|
||||
displayOrderKey(order),
|
||||
order == null ? null : order.temporaryOrderCode(),
|
||||
task.systemTaskType(),
|
||||
task.taskSubtype(),
|
||||
task.taskStatus(),
|
||||
task.taskCardType(),
|
||||
task.executionOrder(),
|
||||
task.queueParticipation(),
|
||||
canProcess(availability),
|
||||
readonlyReasonCode(task, availability),
|
||||
task.sourceMessageId().toString(),
|
||||
sourceSubject(task.sourceMessageId()),
|
||||
toIsoString(task.createdAt()),
|
||||
toIsoString(task.updatedAt()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换任务快照为订单详情任务时间线结果。
|
||||
*/
|
||||
private ReservationOrderTaskTimelineItemResult toTimelineItem(ReservationAiQueryTaskSnapshot task) {
|
||||
ReservationTaskAvailabilityResult availability = availabilityResolver.calculateAvailability(toTaskSnapshot(task));
|
||||
return new ReservationOrderTaskTimelineItemResult(
|
||||
task.id().toString(),
|
||||
task.systemTaskType(),
|
||||
task.taskSubtype(),
|
||||
task.taskStatus(),
|
||||
task.taskCardType(),
|
||||
task.executionOrder(),
|
||||
task.queueParticipation(),
|
||||
canProcess(availability),
|
||||
readonlyReasonCode(task, availability),
|
||||
toIsoString(task.createdAt()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换订单快照为前端订单摘要。
|
||||
*/
|
||||
private ReservationOrderSummaryResult toOrderSummary(ReservationAiQueryOrderSnapshot order) {
|
||||
return new ReservationOrderSummaryResult(
|
||||
order.id().toString(),
|
||||
order.hotelId(),
|
||||
order.orderStatus(),
|
||||
order.temporaryOrderCode(),
|
||||
confirmationNumber(order),
|
||||
groupCode(order),
|
||||
null,
|
||||
null,
|
||||
order.displayName(),
|
||||
toIsoString(order.createdAt()),
|
||||
toIsoString(order.updatedAt()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 AI 查询任务快照为可处理状态解析器使用的通用任务快照。
|
||||
*/
|
||||
private ReservationTaskSnapshot toTaskSnapshot(ReservationAiQueryTaskSnapshot task) {
|
||||
return new ReservationTaskSnapshot(
|
||||
task.id(),
|
||||
task.hotelId(),
|
||||
task.orderId(),
|
||||
task.sourceMessageId(),
|
||||
task.aiTransitionId(),
|
||||
task.resultType(),
|
||||
task.aiTaskType(),
|
||||
task.systemTaskType(),
|
||||
task.taskCardType(),
|
||||
task.taskSubtype(),
|
||||
task.taskStatus(),
|
||||
task.queueParticipation(),
|
||||
task.executionOrder(),
|
||||
task.parentTaskId(),
|
||||
task.parentSourceEventIndex(),
|
||||
task.linkedTaskGroupId(),
|
||||
task.blockedUntilParentCompleted(),
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前任务是否可由前端呈现为可处理状态。
|
||||
*/
|
||||
private boolean canProcess(ReservationTaskAvailabilityResult availability) {
|
||||
return availability.editable() || availability.confirmable() || availability.executable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将实时可处理状态转换为前端稳定原因代码。
|
||||
*/
|
||||
private String readonlyReasonCode(
|
||||
ReservationAiQueryTaskSnapshot task,
|
||||
ReservationTaskAvailabilityResult availability) {
|
||||
if (availability.blocked()) {
|
||||
return REASON_PREVIOUS_TASK_NOT_FINISHED;
|
||||
}
|
||||
if (ReservationTaskStatus.COMPLETED.name().equals(task.taskStatus())
|
||||
|| ReservationTaskStatus.FAILED.name().equals(task.taskStatus())) {
|
||||
return REASON_TASK_FINISHED;
|
||||
}
|
||||
if (canProcess(availability)) {
|
||||
return REASON_PROCESSABLE;
|
||||
}
|
||||
return REASON_READ_ONLY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成订单展示键,优先真实业务号,其次临时订单号。
|
||||
*/
|
||||
private String displayOrderKey(ReservationAiQueryOrderSnapshot order) {
|
||||
if (order == null) {
|
||||
return null;
|
||||
}
|
||||
String activeBusinessKey = trimToNull(order.activeBusinessKey());
|
||||
if (activeBusinessKey != null) {
|
||||
return activeBusinessKey;
|
||||
}
|
||||
String orderBusinessKey = trimToNull(order.orderBusinessKey());
|
||||
return orderBusinessKey == null ? order.temporaryOrderCode() : orderBusinessKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取来源消息安全主题摘要,不返回邮件正文或附件 URL。
|
||||
*/
|
||||
private String sourceSubject(Long sourceMessageId) {
|
||||
if (sourceMessageId == null) {
|
||||
return null;
|
||||
}
|
||||
Optional<SourceMessageSummaryResponse> summary = sourceMessageQueryService.getSummary(sourceMessageId);
|
||||
return summary.map(SourceMessageSummaryResponse::subject).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从订单快照中提取 Group Code。
|
||||
*/
|
||||
private String groupCode(ReservationAiQueryOrderSnapshot order) {
|
||||
if (ReservationOrderKeyType.GROUP_CODE.name().equals(order.orderKeyType())) {
|
||||
return displayOrderKey(order);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从订单快照中提取 Confirmation No.。
|
||||
*/
|
||||
private String confirmationNumber(ReservationAiQueryOrderSnapshot order) {
|
||||
if (ReservationOrderKeyType.CONFIRMATION_NUMBER.name().equals(order.orderKeyType())) {
|
||||
return displayOrderKey(order);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化酒店 ID。第一版未接用户酒店上下文时使用本地默认酒店。
|
||||
*/
|
||||
private String normalizeHotelId(String hotelId) {
|
||||
String trimmedHotelId = trimToNull(hotelId);
|
||||
return trimmedHotelId == null ? DEFAULT_HOTEL_ID : trimmedHotelId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化页码,避免非法参数导致全表读取。
|
||||
*/
|
||||
private int normalizePageNum(Integer pageNum) {
|
||||
return pageNum == null || pageNum < 1 ? DEFAULT_PAGE_NUM : pageNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化页大小,并限制最大页大小。
|
||||
*/
|
||||
private int normalizePageSize(Integer pageSize) {
|
||||
if (pageSize == null || pageSize < 1) {
|
||||
return DEFAULT_PAGE_SIZE;
|
||||
}
|
||||
return Math.min(pageSize, MAX_PAGE_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除空白字符串,空字符串按 null 处理。
|
||||
*/
|
||||
private String trimToNull(String value) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据库 UTC 时间转换为 ISO 字符串。
|
||||
*/
|
||||
private String toIsoString(LocalDateTime value) {
|
||||
return value == null ? null : value.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.service.impl;
|
||||
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationTaskStatus;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskAvailabilityResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.repository.ReservationAiWorkflowRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Reservation 任务实时可处理状态解析器。BLOCKED 不落库,统一按同订单前置任务实时计算。
|
||||
*/
|
||||
@Component
|
||||
public class ReservationTaskAvailabilityResolver {
|
||||
|
||||
private final ReservationAiWorkflowRepository workflowRepository;
|
||||
|
||||
/**
|
||||
* 注入工作流持久化边界,用于读取同订单前置队列任务。
|
||||
*/
|
||||
public ReservationTaskAvailabilityResolver(ReservationAiWorkflowRepository workflowRepository) {
|
||||
this.workflowRepository = workflowRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算任务可处理状态。FAILED 和 COMPLETED 视为前置任务结束,不阻塞后续任务。
|
||||
*/
|
||||
public ReservationTaskAvailabilityResult calculateAvailability(ReservationTaskSnapshot task) {
|
||||
if (!Boolean.TRUE.equals(task.queueParticipation())) {
|
||||
return new ReservationTaskAvailabilityResult(false, true, false, false, false, null, null);
|
||||
}
|
||||
ReservationTaskSnapshot blockingTask = workflowRepository
|
||||
.findQueueTasksBefore(task.hotelId(), task.orderId(), task.executionOrder())
|
||||
.stream()
|
||||
.filter(this::isBlockingTask)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (blockingTask != null) {
|
||||
return new ReservationTaskAvailabilityResult(
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
blockingTask.id().toString(),
|
||||
"同订单前置任务未完成。");
|
||||
}
|
||||
boolean pendingConfirm = ReservationTaskStatus.PENDING_CONFIRM.name().equals(task.taskStatus());
|
||||
boolean ready = ReservationTaskStatus.READY.name().equals(task.taskStatus());
|
||||
return new ReservationTaskAvailabilityResult(false, false, pendingConfirm, pendingConfirm, ready, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断前置任务是否仍阻塞后续任务。
|
||||
*/
|
||||
private boolean isBlockingTask(ReservationTaskSnapshot task) {
|
||||
return !ReservationTaskStatus.COMPLETED.name().equals(task.taskStatus())
|
||||
&& !ReservationTaskStatus.FAILED.name().equals(task.taskStatus());
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,7 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
|
||||
private final ReservationAiWorkflowRepository workflowRepository;
|
||||
private final ReservationTaskCardFieldDefinitionProvider fieldDefinitionProvider;
|
||||
private final ReservationTaskAvailabilityResolver availabilityResolver;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
@@ -81,9 +82,11 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
public ReservationTaskWorkflowServiceImpl(
|
||||
ReservationAiWorkflowRepository workflowRepository,
|
||||
ReservationTaskCardFieldDefinitionProvider fieldDefinitionProvider,
|
||||
ReservationTaskAvailabilityResolver availabilityResolver,
|
||||
ObjectMapper objectMapper) {
|
||||
this.workflowRepository = workflowRepository;
|
||||
this.fieldDefinitionProvider = fieldDefinitionProvider;
|
||||
this.availabilityResolver = availabilityResolver;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@@ -97,7 +100,7 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
ReservationTaskCardSnapshot taskCard = workflowRepository
|
||||
.findTaskCardByTaskId(task.hotelId(), task.id())
|
||||
.orElseThrow(() -> error(HttpStatus.NOT_FOUND, "TASK_CARD_NOT_FOUND", "任务卡不存在。"));
|
||||
ReservationTaskAvailabilityResult availability = calculateAvailability(task);
|
||||
ReservationTaskAvailabilityResult availability = availabilityResolver.calculateAvailability(task);
|
||||
List<ReservationTaskFieldResult> fields = buildFieldResults(task, taskCard);
|
||||
List<ReservationOperaOperationResult> operaOperations = findOperaOperationResults(task);
|
||||
return new ReservationTaskDetailResult(
|
||||
@@ -380,7 +383,7 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
if (!ReservationTaskStatus.READY.name().equals(task.taskStatus())) {
|
||||
throw error(HttpStatus.CONFLICT, "TASK_STATUS_NOT_EXECUTABLE", "只有 READY 任务允许执行 OPERA 模拟操作。");
|
||||
}
|
||||
ReservationTaskAvailabilityResult availability = calculateAvailability(task);
|
||||
ReservationTaskAvailabilityResult availability = availabilityResolver.calculateAvailability(task);
|
||||
if (availability.readOnly() || !availability.executable()) {
|
||||
throw error(HttpStatus.CONFLICT, "TASK_READ_ONLY", "任务当前只读,不能执行 OPERA 模拟操作。");
|
||||
}
|
||||
@@ -622,7 +625,7 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
if (!ReservationTaskStatus.PENDING_CONFIRM.name().equals(task.taskStatus())) {
|
||||
throw error(HttpStatus.CONFLICT, "TASK_STATUS_NOT_EDITABLE", "只有 PENDING_CONFIRM 任务允许编辑或确认。");
|
||||
}
|
||||
ReservationTaskAvailabilityResult availability = calculateAvailability(task);
|
||||
ReservationTaskAvailabilityResult availability = availabilityResolver.calculateAvailability(task);
|
||||
if (availability.readOnly() || !availability.editable() || !availability.confirmable()) {
|
||||
throw error(HttpStatus.CONFLICT, "TASK_READ_ONLY", "任务当前只读,不能编辑或确认。");
|
||||
}
|
||||
@@ -1214,42 +1217,6 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
return text != null && text.contains(keyword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算任务可处理状态。FAILED 和 COMPLETED 视为前置任务结束,不阻塞后续任务。
|
||||
*/
|
||||
private ReservationTaskAvailabilityResult calculateAvailability(ReservationTaskSnapshot task) {
|
||||
if (!Boolean.TRUE.equals(task.queueParticipation())) {
|
||||
return new ReservationTaskAvailabilityResult(false, true, false, false, false, null, null);
|
||||
}
|
||||
ReservationTaskSnapshot blockingTask = workflowRepository
|
||||
.findQueueTasksBefore(task.hotelId(), task.orderId(), task.executionOrder())
|
||||
.stream()
|
||||
.filter(this::isBlockingTask)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (blockingTask != null) {
|
||||
return new ReservationTaskAvailabilityResult(
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
blockingTask.id().toString(),
|
||||
"同订单前置任务未完成。");
|
||||
}
|
||||
boolean pendingConfirm = ReservationTaskStatus.PENDING_CONFIRM.name().equals(task.taskStatus());
|
||||
boolean ready = ReservationTaskStatus.READY.name().equals(task.taskStatus());
|
||||
return new ReservationTaskAvailabilityResult(false, false, pendingConfirm, pendingConfirm, ready, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断前置任务是否仍阻塞后续任务。
|
||||
*/
|
||||
private boolean isBlockingTask(ReservationTaskSnapshot task) {
|
||||
return !ReservationTaskStatus.COMPLETED.name().equals(task.taskStatus())
|
||||
&& !ReservationTaskStatus.FAILED.name().equals(task.taskStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据任务卡矩阵和 AI 原始快照组装详情字段。
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.control;
|
||||
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import cn.nianxx.thhotel.ThHotelApplication;
|
||||
import cn.nianxx.thhotel.platform.message.common.request.CaptureSourceMessageCommand;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageCaptureResult;
|
||||
import cn.nianxx.thhotel.platform.message.service.SourceMessageCaptureService;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@SpringBootTest(classes = ThHotelApplication.class)
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles("test")
|
||||
class ReservationFrontendQueryControllerTest {
|
||||
|
||||
private static final String HOTEL_ID = "HOTEL-TEST";
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private SourceMessageCaptureService captureService;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Test
|
||||
void shouldReturnTaskWorkbenchListWithRealtimeAvailability() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage(
|
||||
"mail-frontend-task-list-001",
|
||||
"Frontend Query List Smoke");
|
||||
Long orderId = 930000000000000101L;
|
||||
Long firstTaskId = 930000000000000301L;
|
||||
Long secondTaskId = 930000000000000302L;
|
||||
insertActiveGroupOrder(orderId, source.inboxId(), "GRP-FRONTEND-LIST-001");
|
||||
insertTransition(930000000000000201L, source.inboxId(), 1, "GRP-FRONTEND-LIST-001",
|
||||
"New Booking", "NEW_BOOKING", "NEW_BOOKING");
|
||||
insertTransition(930000000000000202L, source.inboxId(), 2, "GRP-FRONTEND-LIST-001",
|
||||
"Update Booking", "UPDATE_BOOKING", "UPDATE_BOOKING");
|
||||
insertTask(firstTaskId, orderId, source.inboxId(), 930000000000000201L, "New Booking",
|
||||
"NEW_BOOKING", "NEW_BOOKING", "PENDING_CONFIRM", 1);
|
||||
insertTask(secondTaskId, orderId, source.inboxId(), 930000000000000202L, "Update Booking",
|
||||
"UPDATE_BOOKING", "UPDATE_BOOKING", "PENDING_CONFIRM", 2);
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks")
|
||||
.param("hotel_id", HOTEL_ID)
|
||||
.param("order_id", orderId.toString())
|
||||
.param("page_num", "1")
|
||||
.param("page_size", "20"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.items[0].task_id").value(firstTaskId.toString()))
|
||||
.andExpect(jsonPath("$.items[0].order_id").value(orderId.toString()))
|
||||
.andExpect(jsonPath("$.items[0].hotel_id").value(HOTEL_ID))
|
||||
.andExpect(jsonPath("$.items[0].display_order_key").value("GRP-FRONTEND-LIST-001"))
|
||||
.andExpect(jsonPath("$.items[0].task_type").value("NEW_BOOKING"))
|
||||
.andExpect(jsonPath("$.items[0].task_status").value("PENDING_CONFIRM"))
|
||||
.andExpect(jsonPath("$.items[0].queue_sequence").value(1))
|
||||
.andExpect(jsonPath("$.items[0].queue_participation").value(true))
|
||||
.andExpect(jsonPath("$.items[0].can_process").value(true))
|
||||
.andExpect(jsonPath("$.items[0].readonly_reason_code").value("PROCESSABLE"))
|
||||
.andExpect(jsonPath("$.items[0].source_message_id").value(source.inboxId().toString()))
|
||||
.andExpect(jsonPath("$.items[0].source_subject").value("Frontend Query List Smoke"))
|
||||
.andExpect(jsonPath("$.items[1].task_id").value(secondTaskId.toString()))
|
||||
.andExpect(jsonPath("$.items[1].task_type").value("UPDATE_BOOKING"))
|
||||
.andExpect(jsonPath("$.items[1].can_process").value(false))
|
||||
.andExpect(jsonPath("$.items[1].readonly_reason_code").value("PREVIOUS_TASK_NOT_FINISHED"))
|
||||
.andExpect(jsonPath("$.page.page_num").value(1))
|
||||
.andExpect(jsonPath("$.page.page_size").value(20))
|
||||
.andExpect(jsonPath("$.page.total").value(2));
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks")
|
||||
.param("hotel_id", HOTEL_ID)
|
||||
.param("keyword", "Frontend Query List Smoke")
|
||||
.param("page_num", "1")
|
||||
.param("page_size", "20"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.items[0].source_subject").value("Frontend Query List Smoke"))
|
||||
.andExpect(jsonPath("$.items[1].source_subject").value("Frontend Query List Smoke"))
|
||||
.andExpect(jsonPath("$.page.total").value(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnOrderDetailWithTaskTimeline() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage(
|
||||
"mail-frontend-order-detail-001",
|
||||
"Frontend Query Detail Smoke");
|
||||
Long orderId = 930000000000000401L;
|
||||
Long firstTaskId = 930000000000000601L;
|
||||
Long secondTaskId = 930000000000000602L;
|
||||
insertActiveGroupOrder(orderId, source.inboxId(), "GRP-FRONTEND-DETAIL-001");
|
||||
insertTransition(930000000000000501L, source.inboxId(), 1, "GRP-FRONTEND-DETAIL-001",
|
||||
"New Booking", "NEW_BOOKING", "NEW_BOOKING");
|
||||
insertTransition(930000000000000502L, source.inboxId(), 2, "GRP-FRONTEND-DETAIL-001",
|
||||
"Update Booking", "UPDATE_BOOKING", "UPDATE_BOOKING");
|
||||
insertTask(firstTaskId, orderId, source.inboxId(), 930000000000000501L, "New Booking",
|
||||
"NEW_BOOKING", "NEW_BOOKING", "COMPLETED", 1);
|
||||
insertTask(secondTaskId, orderId, source.inboxId(), 930000000000000502L, "Update Booking",
|
||||
"UPDATE_BOOKING", "UPDATE_BOOKING", "PENDING_CONFIRM", 2);
|
||||
|
||||
mockMvc.perform(get("/api/reservation/orders/{orderId}", orderId)
|
||||
.param("hotel_id", HOTEL_ID)
|
||||
.param("include_tasks", "true")
|
||||
.param("include_source_summary", "true"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.order.order_id").value(orderId.toString()))
|
||||
.andExpect(jsonPath("$.order.hotel_id").value(HOTEL_ID))
|
||||
.andExpect(jsonPath("$.order.order_status").value("ACTIVE"))
|
||||
.andExpect(jsonPath("$.order.temporary_order_no").value("TMP-" + orderId))
|
||||
.andExpect(jsonPath("$.order.group_code").value("GRP-FRONTEND-DETAIL-001"))
|
||||
.andExpect(jsonPath("$.order.confirmation_number").value(nullValue()))
|
||||
.andExpect(jsonPath("$.order.display_name").value("GRP-FRONTEND-DETAIL-001"))
|
||||
.andExpect(jsonPath("$.tasks[0].task_id").value(firstTaskId.toString()))
|
||||
.andExpect(jsonPath("$.tasks[0].task_status").value("COMPLETED"))
|
||||
.andExpect(jsonPath("$.tasks[0].readonly_reason_code").value("TASK_FINISHED"))
|
||||
.andExpect(jsonPath("$.tasks[1].task_id").value(secondTaskId.toString()))
|
||||
.andExpect(jsonPath("$.tasks[1].task_status").value("PENDING_CONFIRM"))
|
||||
.andExpect(jsonPath("$.tasks[1].can_process").value(true))
|
||||
.andExpect(jsonPath("$.tasks[1].readonly_reason_code").value("PROCESSABLE"))
|
||||
.andExpect(jsonPath("$.warnings.length()").value(0));
|
||||
|
||||
mockMvc.perform(get("/api/reservation/orders/{orderId}", orderId)
|
||||
.param("hotel_id", HOTEL_ID)
|
||||
.param("include_tasks", "false"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.order.order_id").value(orderId.toString()))
|
||||
.andExpect(jsonPath("$.tasks.length()").value(0));
|
||||
}
|
||||
|
||||
private SourceMessageCaptureResult captureSourceMessage(String externalMessageId, String subject) {
|
||||
return captureService.capture(new CaptureSourceMessageCommand(
|
||||
HOTEL_ID,
|
||||
"AGENTBUS",
|
||||
"EMAIL",
|
||||
externalMessageId,
|
||||
"thread-" + externalMessageId,
|
||||
"frame-" + externalMessageId,
|
||||
"session-frontend-query",
|
||||
Instant.parse("2026-07-08T08:00:00Z"),
|
||||
"guest@example.test",
|
||||
subject,
|
||||
"Please handle booking message.",
|
||||
"<html><body>Please handle booking message.</body></html>",
|
||||
"{\"source\":{\"external_message_id\":\"" + externalMessageId + "\"}}",
|
||||
"agentbus-outlook-v1",
|
||||
List.of()
|
||||
));
|
||||
}
|
||||
|
||||
private void insertActiveGroupOrder(Long orderId, Long sourceMessageId, String groupCode) {
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO workflow_reservation_order (
|
||||
id, hotel_id, order_key_type, order_business_key, active_business_key,
|
||||
temporary_order_code, order_status, business_key_source, display_name,
|
||||
source_message_id, version, created_at, updated_at
|
||||
)
|
||||
VALUES (?, ?, 'GROUP_CODE', ?, ?, ?, 'ACTIVE', 'AI_CANDIDATE', ?, ?, 0,
|
||||
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
""", orderId, HOTEL_ID, groupCode, groupCode, "TMP-" + orderId, groupCode, sourceMessageId);
|
||||
}
|
||||
|
||||
private void insertTransition(
|
||||
Long transitionId,
|
||||
Long sourceMessageId,
|
||||
Integer sourceEventIndex,
|
||||
String groupCode,
|
||||
String aiTaskType,
|
||||
String systemTaskType,
|
||||
String taskCardType) {
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO workflow_reservation_ai_transition (
|
||||
id, hotel_id, batch_id, source_message_id, source_event_index, array_index,
|
||||
execution_order, catalog_code, skill_id, result_type, ai_task_type,
|
||||
system_task_type, task_card_type, task_subtype, current_or_history,
|
||||
group_code, item_payload_sha256, item_idempotency_key, blocked_until_parent_completed,
|
||||
ai_payload_json, case_keys_json, extracted_fields_json, created_at, updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 'S02', 'frontend_query_skill',
|
||||
'normal_task', ?, ?, ?, 'frontend_query', 'current', ?, ?, ?, 0,
|
||||
'{}', '{}', '{}', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
""", transitionId, HOTEL_ID, transitionId - 1, sourceMessageId, sourceEventIndex,
|
||||
sourceEventIndex, sourceEventIndex, aiTaskType, systemTaskType, taskCardType, groupCode,
|
||||
"0".repeat(64), transitionId.toString());
|
||||
}
|
||||
|
||||
private void insertTask(
|
||||
Long taskId,
|
||||
Long orderId,
|
||||
Long sourceMessageId,
|
||||
Long transitionId,
|
||||
String aiTaskType,
|
||||
String systemTaskType,
|
||||
String taskCardType,
|
||||
String taskStatus,
|
||||
Integer executionOrder) {
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO workflow_reservation_task (
|
||||
id, hotel_id, order_id, source_message_id, ai_transition_id,
|
||||
result_type, ai_task_type, system_task_type, task_card_type, task_subtype,
|
||||
task_status, queue_participation, execution_order, blocked_until_parent_completed,
|
||||
version, created_at, updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, 'normal_task', ?, ?, ?, 'frontend_query',
|
||||
?, 1, ?, 0, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
""", taskId, HOTEL_ID, orderId, sourceMessageId, transitionId, aiTaskType,
|
||||
systemTaskType, taskCardType, taskStatus, executionOrder);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user