完成M002 V3 CP4列表详情展示
This commit is contained in:
@@ -29,6 +29,10 @@ import java.time.LocalDateTime;
|
||||
* @param transitionSourceEventIndex AI transition 来源事件序号
|
||||
* @param catalogCode Skill 目录代码
|
||||
* @param skillId Skill 标识
|
||||
* @param routeCode M002 V3 路由码
|
||||
* @param systemProcessCategory 系统处理分类
|
||||
* @param adapterErrorCode Adapter 契约错误代码
|
||||
* @param adapterErrorMessage Adapter 契约错误说明
|
||||
*/
|
||||
public record ReservationAiQueryTaskSnapshot(
|
||||
Long id,
|
||||
@@ -54,6 +58,10 @@ public record ReservationAiQueryTaskSnapshot(
|
||||
LocalDateTime updatedAt,
|
||||
Integer transitionSourceEventIndex,
|
||||
String catalogCode,
|
||||
String skillId
|
||||
String skillId,
|
||||
String routeCode,
|
||||
String systemProcessCategory,
|
||||
String adapterErrorCode,
|
||||
String adapterErrorMessage
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* AI transition 展示快照。用于任务详情展示同一批次的 V3 路由、契约错误和未处理意图,不直接暴露 Entity。
|
||||
*
|
||||
* @param id AI transition 主键
|
||||
* @param hotelId 酒店上下文 ID
|
||||
* @param batchId AI 入站批次 ID
|
||||
* @param sourceMessageId 来源 SourceMessage Inbox ID
|
||||
* @param sourceEventIndex AI current 事件序号
|
||||
* @param arrayIndex AI 返回数组顺序,从 1 开始
|
||||
* @param executionOrder 初始执行顺序
|
||||
* @param catalogCode Skill 目录代码
|
||||
* @param skillId Skill 标识
|
||||
* @param resultType AI 结果类型
|
||||
* @param aiTaskType AI 原始任务类型
|
||||
* @param routeCode M002 V3 路由码
|
||||
* @param systemProcessCategory 系统处理分类
|
||||
* @param systemTaskType 系统主任务类型
|
||||
* @param taskCardType 任务卡类型
|
||||
* @param taskSubtype 业务动作 subtype
|
||||
* @param currentOrHistory 当前或历史标识
|
||||
* @param adapterErrorCode Adapter 契约错误代码
|
||||
* @param adapterErrorMessage Adapter 契约错误说明
|
||||
* @param aiPayloadJson AI 原始 item JSON
|
||||
* @param createdAt 创建 UTC 时间
|
||||
* @param updatedAt 最近更新 UTC 时间
|
||||
*/
|
||||
public record ReservationAiTransitionSnapshot(
|
||||
Long id,
|
||||
String hotelId,
|
||||
Long batchId,
|
||||
Long sourceMessageId,
|
||||
Integer sourceEventIndex,
|
||||
Integer arrayIndex,
|
||||
Integer executionOrder,
|
||||
String catalogCode,
|
||||
String skillId,
|
||||
String resultType,
|
||||
String aiTaskType,
|
||||
String routeCode,
|
||||
String systemProcessCategory,
|
||||
String systemTaskType,
|
||||
String taskCardType,
|
||||
String taskSubtype,
|
||||
String currentOrHistory,
|
||||
String adapterErrorCode,
|
||||
String adapterErrorMessage,
|
||||
String aiPayloadJson,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* 任务详情中的 AI transition 展示块。用于展示未建任务的关联 V3 诊断信息。
|
||||
*
|
||||
* @param transitionId AI transition ID,按字符串返回避免前端长整型精度问题
|
||||
* @param sourceEventIndex AI current 事件序号
|
||||
* @param arrayIndex AI 返回数组顺序
|
||||
* @param resultType AI 结果类型
|
||||
* @param aiTaskType AI 原始任务类型
|
||||
* @param taskSubtype 业务动作 subtype
|
||||
* @param routeCode M002 V3 路由码
|
||||
* @param systemProcessCategory 系统处理分类
|
||||
* @param adapterErrorCode Adapter 契约错误代码
|
||||
* @param adapterErrorMessage Adapter 契约错误说明
|
||||
* @param payloadFragment 对前端展示安全的 AI item 片段,当前不包含邮件原文
|
||||
*/
|
||||
public record ReservationAiTransitionDisplayResult(
|
||||
@JsonProperty("transition_id")
|
||||
String transitionId,
|
||||
@JsonProperty("source_event_index")
|
||||
Integer sourceEventIndex,
|
||||
@JsonProperty("array_index")
|
||||
Integer arrayIndex,
|
||||
@JsonProperty("result_type")
|
||||
String resultType,
|
||||
@JsonProperty("ai_task_type")
|
||||
String aiTaskType,
|
||||
@JsonProperty("task_subtype")
|
||||
String taskSubtype,
|
||||
@JsonProperty("route_code")
|
||||
String routeCode,
|
||||
@JsonProperty("system_process_category")
|
||||
String systemProcessCategory,
|
||||
@JsonProperty("adapter_error_code")
|
||||
String adapterErrorCode,
|
||||
@JsonProperty("adapter_error_message")
|
||||
String adapterErrorMessage,
|
||||
@JsonProperty("payload_fragment")
|
||||
Object payloadFragment
|
||||
) {
|
||||
}
|
||||
@@ -8,7 +8,11 @@ import java.time.OffsetDateTime;
|
||||
*
|
||||
* @param taskId 任务 ID
|
||||
* @param taskType 系统主任务类型
|
||||
* @param resultType AI 结果类型
|
||||
* @param aiTaskType AI 原始任务类型
|
||||
* @param taskSubtype 任务 subtype
|
||||
* @param routeCode M002 V3 路由码
|
||||
* @param systemProcessCategory 系统处理分类
|
||||
* @param taskStatus 任务状态
|
||||
* @param cardName 任务卡展示名称
|
||||
* @param queueSequence 同订单队列顺序
|
||||
@@ -28,8 +32,16 @@ public record ReservationOrderTaskTimelineItemResult(
|
||||
String taskId,
|
||||
@JsonProperty("task_type")
|
||||
String taskType,
|
||||
@JsonProperty("result_type")
|
||||
String resultType,
|
||||
@JsonProperty("ai_task_type")
|
||||
String aiTaskType,
|
||||
@JsonProperty("task_subtype")
|
||||
String taskSubtype,
|
||||
@JsonProperty("route_code")
|
||||
String routeCode,
|
||||
@JsonProperty("system_process_category")
|
||||
String systemProcessCategory,
|
||||
@JsonProperty("task_status")
|
||||
String taskStatus,
|
||||
@JsonProperty("card_name")
|
||||
|
||||
@@ -9,6 +9,11 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
* @param entryResultMeaning 入口结果语义
|
||||
* @param entryResultDescription 入口结果中文说明
|
||||
* @param entryResultSourceMessageId SuperAgent 回传的外部来源消息 ID
|
||||
* @param resultType AI 结果类型
|
||||
* @param routeCode M002 V3 路由码
|
||||
* @param agentAssessment S10/S99 入口评估对象
|
||||
* @param notification S10/S99 入口通知对象
|
||||
* @param manualReview S99 人工复核对象;S10 通常为空
|
||||
* @param rawAnswer SuperAgent 原始文本返回
|
||||
*/
|
||||
public record ReservationSourceMessageOnlyResult(
|
||||
@@ -20,6 +25,16 @@ public record ReservationSourceMessageOnlyResult(
|
||||
String entryResultDescription,
|
||||
@JsonProperty("entry_result_source_message_id")
|
||||
String entryResultSourceMessageId,
|
||||
@JsonProperty("result_type")
|
||||
String resultType,
|
||||
@JsonProperty("route_code")
|
||||
String routeCode,
|
||||
@JsonProperty("agent_assessment")
|
||||
Object agentAssessment,
|
||||
@JsonProperty("notification")
|
||||
Object notification,
|
||||
@JsonProperty("manual_review")
|
||||
Object manualReview,
|
||||
@JsonProperty("raw_answer")
|
||||
String rawAnswer
|
||||
) {
|
||||
|
||||
@@ -17,6 +17,11 @@ import java.util.List;
|
||||
* @param conversationMessageCount 同一外部邮件会话下的消息数量
|
||||
* @param systemTaskType 系统主任务类型
|
||||
* @param taskCardType 任务卡类型
|
||||
* @param resultType AI 结果类型
|
||||
* @param aiTaskType AI 原始任务类型
|
||||
* @param taskSubtype 业务动作 subtype
|
||||
* @param routeCode M002 V3 路由码
|
||||
* @param systemProcessCategory 系统处理分类
|
||||
* @param taskStatus 任务状态
|
||||
* @param fieldContractVersion 字段矩阵契约版本
|
||||
* @param draftPayload 草稿 payload
|
||||
@@ -25,6 +30,8 @@ import java.util.List;
|
||||
* @param availability 当前可处理状态
|
||||
* @param fields 按字段矩阵生成的字段列表
|
||||
* @param operaOperations OPERA 模拟操作列表
|
||||
* @param adapterContractErrors 同批次未建任务的 Adapter 契约错误展示块
|
||||
* @param unhandledIntents 同批次未建任务的未处理意图展示块
|
||||
*/
|
||||
public record ReservationTaskDetailResult(
|
||||
@JsonProperty("task_id")
|
||||
@@ -47,6 +54,16 @@ public record ReservationTaskDetailResult(
|
||||
String systemTaskType,
|
||||
@JsonProperty("task_card_type")
|
||||
String taskCardType,
|
||||
@JsonProperty("result_type")
|
||||
String resultType,
|
||||
@JsonProperty("ai_task_type")
|
||||
String aiTaskType,
|
||||
@JsonProperty("task_subtype")
|
||||
String taskSubtype,
|
||||
@JsonProperty("route_code")
|
||||
String routeCode,
|
||||
@JsonProperty("system_process_category")
|
||||
String systemProcessCategory,
|
||||
@JsonProperty("task_status")
|
||||
String taskStatus,
|
||||
@JsonProperty("field_contract_version")
|
||||
@@ -60,6 +77,10 @@ public record ReservationTaskDetailResult(
|
||||
ReservationTaskAvailabilityResult availability,
|
||||
List<ReservationTaskFieldResult> fields,
|
||||
@JsonProperty("opera_operations")
|
||||
List<ReservationOperaOperationResult> operaOperations
|
||||
List<ReservationOperaOperationResult> operaOperations,
|
||||
@JsonProperty("adapter_contract_errors")
|
||||
List<ReservationAiTransitionDisplayResult> adapterContractErrors,
|
||||
@JsonProperty("unhandled_intents")
|
||||
List<ReservationAiTransitionDisplayResult> unhandledIntents
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -12,7 +12,11 @@ import java.time.OffsetDateTime;
|
||||
* @param displayOrderKey 前端优先展示的订单业务号或临时订单号
|
||||
* @param temporaryOrderNo 临时订单号
|
||||
* @param taskType 系统主任务类型
|
||||
* @param resultType AI 结果类型
|
||||
* @param aiTaskType AI 原始任务类型
|
||||
* @param taskSubtype 任务 subtype
|
||||
* @param routeCode M002 V3 路由码
|
||||
* @param systemProcessCategory 系统处理分类
|
||||
* @param taskStatus 任务状态
|
||||
* @param cardName 任务卡展示名称,第一版使用任务卡类型
|
||||
* @param queueSequence 同订单队列顺序
|
||||
@@ -41,8 +45,16 @@ public record ReservationTaskWorkbenchItemResult(
|
||||
String temporaryOrderNo,
|
||||
@JsonProperty("task_type")
|
||||
String taskType,
|
||||
@JsonProperty("result_type")
|
||||
String resultType,
|
||||
@JsonProperty("ai_task_type")
|
||||
String aiTaskType,
|
||||
@JsonProperty("task_subtype")
|
||||
String taskSubtype,
|
||||
@JsonProperty("route_code")
|
||||
String routeCode,
|
||||
@JsonProperty("system_process_category")
|
||||
String systemProcessCategory,
|
||||
@JsonProperty("task_status")
|
||||
String taskStatus,
|
||||
@JsonProperty("card_name")
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiBatchSnap
|
||||
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.ReservationAiTransitionDraft;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiTransitionSnapshot;
|
||||
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;
|
||||
@@ -288,6 +289,40 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
return Optional.ofNullable(entity).map(this::toTaskCardSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 AI transition ID 查询展示快照,用于任务详情补充 V3 入站路由上下文。
|
||||
*/
|
||||
@Override
|
||||
public Optional<ReservationAiTransitionSnapshot> findTransitionById(String hotelId, Long transitionId) {
|
||||
if (transitionId == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
ReservationAiTransitionEntity entity = transitionMapper.selectOne(
|
||||
Wrappers.<ReservationAiTransitionEntity>lambdaQuery()
|
||||
.eq(ReservationAiTransitionEntity::getHotelId, hotelId)
|
||||
.eq(ReservationAiTransitionEntity::getId, transitionId)
|
||||
.last("LIMIT 1"));
|
||||
return Optional.ofNullable(entity).map(this::toAiTransitionSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 AI 批次查询全部 transition 展示快照,按 AI 返回顺序稳定排序。
|
||||
*/
|
||||
@Override
|
||||
public List<ReservationAiTransitionSnapshot> findTransitionsByBatchId(String hotelId, Long batchId) {
|
||||
if (batchId == null) {
|
||||
return List.of();
|
||||
}
|
||||
return transitionMapper.selectList(Wrappers.<ReservationAiTransitionEntity>lambdaQuery()
|
||||
.eq(ReservationAiTransitionEntity::getHotelId, hotelId)
|
||||
.eq(ReservationAiTransitionEntity::getBatchId, batchId)
|
||||
.orderByAsc(ReservationAiTransitionEntity::getArrayIndex)
|
||||
.orderByAsc(ReservationAiTransitionEntity::getId))
|
||||
.stream()
|
||||
.map(this::toAiTransitionSnapshot)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询同订单前置队列任务,Message Notification 等非队列任务不会进入结果。
|
||||
*/
|
||||
@@ -1021,7 +1056,40 @@ public class MybatisReservationAiWorkflowRepository implements ReservationAiWork
|
||||
entity.getUpdatedAt(),
|
||||
transition == null ? null : transition.getSourceEventIndex(),
|
||||
transition == null ? null : transition.getCatalogCode(),
|
||||
transition == null ? null : transition.getSkillId());
|
||||
transition == null ? null : transition.getSkillId(),
|
||||
transition == null ? null : transition.getRouteCode(),
|
||||
transition == null ? null : transition.getSystemProcessCategory(),
|
||||
transition == null ? null : transition.getAdapterErrorCode(),
|
||||
transition == null ? null : transition.getAdapterErrorMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 AI transition 实体为展示快照。
|
||||
*/
|
||||
private ReservationAiTransitionSnapshot toAiTransitionSnapshot(ReservationAiTransitionEntity entity) {
|
||||
return new ReservationAiTransitionSnapshot(
|
||||
entity.getId(),
|
||||
entity.getHotelId(),
|
||||
entity.getBatchId(),
|
||||
entity.getSourceMessageId(),
|
||||
entity.getSourceEventIndex(),
|
||||
entity.getArrayIndex(),
|
||||
entity.getExecutionOrder(),
|
||||
entity.getCatalogCode(),
|
||||
entity.getSkillId(),
|
||||
entity.getResultType(),
|
||||
entity.getAiTaskType(),
|
||||
entity.getRouteCode(),
|
||||
entity.getSystemProcessCategory(),
|
||||
entity.getSystemTaskType(),
|
||||
entity.getTaskCardType(),
|
||||
entity.getTaskSubtype(),
|
||||
entity.getCurrentOrHistory(),
|
||||
entity.getAdapterErrorCode(),
|
||||
entity.getAdapterErrorMessage(),
|
||||
entity.getAiPayloadJson(),
|
||||
entity.getCreatedAt(),
|
||||
entity.getUpdatedAt());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ 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.ReservationAiQueryOrderSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiQueryTaskSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiTransitionSnapshot;
|
||||
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;
|
||||
@@ -82,6 +83,16 @@ public interface ReservationAiWorkflowRepository {
|
||||
*/
|
||||
Optional<ReservationTaskCardSnapshot> findTaskCardByTaskId(String hotelId, Long taskId);
|
||||
|
||||
/**
|
||||
* 按 AI transition ID 查询展示快照,用于任务详情补充 V3 路由和批次上下文。
|
||||
*/
|
||||
Optional<ReservationAiTransitionSnapshot> findTransitionById(String hotelId, Long transitionId);
|
||||
|
||||
/**
|
||||
* 查询同一 AI 批次下的 transition 展示快照,用于任务详情展示未建任务的契约错误和未处理意图。
|
||||
*/
|
||||
List<ReservationAiTransitionSnapshot> findTransitionsByBatchId(String hotelId, Long batchId);
|
||||
|
||||
/**
|
||||
* 查询同订单中排在当前任务之前且参与队列的任务。
|
||||
*/
|
||||
|
||||
@@ -329,7 +329,11 @@ public class ReservationFrontendQueryServiceImpl implements ReservationFrontendQ
|
||||
displayOrderKey(order),
|
||||
order == null || isHiddenSystemOrder(order) ? null : order.temporaryOrderCode(),
|
||||
task.systemTaskType(),
|
||||
task.resultType(),
|
||||
task.aiTaskType(),
|
||||
task.taskSubtype(),
|
||||
task.routeCode(),
|
||||
task.systemProcessCategory(),
|
||||
task.taskStatus(),
|
||||
task.taskCardType(),
|
||||
task.executionOrder(),
|
||||
@@ -356,7 +360,11 @@ public class ReservationFrontendQueryServiceImpl implements ReservationFrontendQ
|
||||
return new ReservationOrderTaskTimelineItemResult(
|
||||
task.id().toString(),
|
||||
task.systemTaskType(),
|
||||
task.resultType(),
|
||||
task.aiTaskType(),
|
||||
task.taskSubtype(),
|
||||
task.routeCode(),
|
||||
task.systemProcessCategory(),
|
||||
task.taskStatus(),
|
||||
task.taskCardType(),
|
||||
task.executionOrder(),
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageSummaryRespons
|
||||
import cn.nianxx.thhotel.platform.message.service.SourceMessageQueryService;
|
||||
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.ReservationAiTransitionSnapshot;
|
||||
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;
|
||||
@@ -12,6 +13,7 @@ import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationOrderSnapsh
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskCardFieldDefinition;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskCardSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskSnapshot;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationAiSystemProcessCategory;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationBusinessKeySource;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOperaAttemptStatus;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOperaOperationStatus;
|
||||
@@ -24,6 +26,7 @@ import cn.nianxx.thhotel.workflows.reservation.common.request.ManualReviewConver
|
||||
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.ReservationAiTransitionDisplayResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOperaOperationAttemptResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOperaOperationResult;
|
||||
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationSourceMessageOnlyResult;
|
||||
@@ -112,6 +115,8 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
List<ReservationOperaOperationResult> operaOperations = findOperaOperationResults(task);
|
||||
ReservationSourceMessageOnlyResult sourceMessageOnlyResult = buildSourceMessageOnlyResult(task, taskCard);
|
||||
SourceMessageDetailContext sourceContext = findSourceMessageDetailContext(task);
|
||||
ReservationAiTransitionSnapshot transition = findTaskTransition(task);
|
||||
List<ReservationAiTransitionSnapshot> relatedTransitions = findRelatedTransitions(task, transition);
|
||||
return new ReservationTaskDetailResult(
|
||||
task.id().toString(),
|
||||
task.orderId().toString(),
|
||||
@@ -123,6 +128,11 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
sourceContext.conversationMessageCount(),
|
||||
task.systemTaskType(),
|
||||
task.taskCardType(),
|
||||
task.resultType(),
|
||||
task.aiTaskType(),
|
||||
task.taskSubtype(),
|
||||
transition == null ? null : transition.routeCode(),
|
||||
transition == null ? null : transition.systemProcessCategory(),
|
||||
task.taskStatus(),
|
||||
taskCard.fieldContractVersion(),
|
||||
jsonPayloadToObject(taskCard.draftPayloadJson()),
|
||||
@@ -130,7 +140,13 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
sourceMessageOnlyResult,
|
||||
availability,
|
||||
fields,
|
||||
operaOperations);
|
||||
operaOperations,
|
||||
buildTransitionDisplayResults(
|
||||
relatedTransitions,
|
||||
ReservationAiSystemProcessCategory.ADAPTER_CONTRACT_ERROR),
|
||||
buildTransitionDisplayResults(
|
||||
relatedTransitions,
|
||||
ReservationAiSystemProcessCategory.UNHANDLED_CURRENT_INTENT));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -854,9 +870,86 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
textValue(payload, "entry_result_meaning"),
|
||||
textValue(payload, "entry_result_description"),
|
||||
textValue(payload, "source_message_id"),
|
||||
textValue(payload, "result_type"),
|
||||
textValue(payload, "route_code"),
|
||||
rawAnswerField(payload, "agent_assessment"),
|
||||
rawAnswerField(payload, "notification"),
|
||||
rawAnswerField(payload, "manual_review"),
|
||||
textValue(payload, "raw_answer"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前任务对应的 AI transition,用于详情展示 V3 route 和系统处理分类。
|
||||
*/
|
||||
private ReservationAiTransitionSnapshot findTaskTransition(ReservationTaskSnapshot task) {
|
||||
return workflowRepository.findTransitionById(task.hotelId(), task.aiTransitionId()).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询同一 AI 批次下的关联 transition,用于展示没有生成任务的诊断类结果。
|
||||
*/
|
||||
private List<ReservationAiTransitionSnapshot> findRelatedTransitions(
|
||||
ReservationTaskSnapshot task,
|
||||
ReservationAiTransitionSnapshot transition) {
|
||||
if (transition == null) {
|
||||
return List.of();
|
||||
}
|
||||
return workflowRepository.findTransitionsByBatchId(task.hotelId(), transition.batchId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 按系统处理分类构造详情页展示块。
|
||||
*/
|
||||
private List<ReservationAiTransitionDisplayResult> buildTransitionDisplayResults(
|
||||
List<ReservationAiTransitionSnapshot> transitions,
|
||||
ReservationAiSystemProcessCategory category) {
|
||||
if (transitions == null || transitions.isEmpty() || category == null) {
|
||||
return List.of();
|
||||
}
|
||||
return transitions.stream()
|
||||
.filter(transition -> category.name().equals(transition.systemProcessCategory()))
|
||||
.map(this::toTransitionDisplayResult)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 AI transition 快照为前端详情展示块。
|
||||
*/
|
||||
private ReservationAiTransitionDisplayResult toTransitionDisplayResult(ReservationAiTransitionSnapshot transition) {
|
||||
return new ReservationAiTransitionDisplayResult(
|
||||
transition.id().toString(),
|
||||
transition.sourceEventIndex(),
|
||||
transition.arrayIndex(),
|
||||
transition.resultType(),
|
||||
transition.aiTaskType(),
|
||||
transition.taskSubtype(),
|
||||
transition.routeCode(),
|
||||
transition.systemProcessCategory(),
|
||||
transition.adapterErrorCode(),
|
||||
transition.adapterErrorMessage(),
|
||||
jsonPayloadToObject(transition.aiPayloadJson()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 S10/S99 原始回答中提取指定对象字段;旧 S000/S999 文本无法解析时返回 null。
|
||||
*/
|
||||
private Object rawAnswerField(JsonNode payload, String fieldName) {
|
||||
String rawAnswer = textValue(payload, "raw_answer");
|
||||
if (trimToNull(rawAnswer) == null || trimToNull(fieldName) == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JsonNode root = objectMapper.readTree(rawAnswer);
|
||||
JsonNode fieldNode = root.path(fieldName);
|
||||
if (fieldNode.isMissingNode() || fieldNode.isNull()) {
|
||||
return null;
|
||||
}
|
||||
return objectMapper.convertValue(fieldNode, Object.class);
|
||||
} catch (Exception exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入草稿保存或最终确认审计。审计只记录 payload 摘要,不保存完整客户字段。
|
||||
*/
|
||||
|
||||
@@ -745,6 +745,78 @@ class SuperAgentTaskResultControllerTest {
|
||||
assertThat(taskCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposeStructuredS99NotificationInTaskListAndDetail() throws Exception {
|
||||
captureSourceMessage("mail-v3-s99-frontend-visible-001");
|
||||
String body = """
|
||||
{
|
||||
"source_message": {
|
||||
"source_message_id": "mail-v3-s99-frontend-visible-001",
|
||||
"subject": null,
|
||||
"from": null,
|
||||
"cc": [],
|
||||
"received_at": null,
|
||||
"source_channel": "Email"
|
||||
},
|
||||
"route_code": "S99",
|
||||
"handler_type": "main_agent_outcome",
|
||||
"result_type": "source_message_review_notification",
|
||||
"current_or_history": "current",
|
||||
"agent_assessment": {
|
||||
"status": "insufficient_business_material",
|
||||
"reason_code": "cannot_form_business_material_package",
|
||||
"automation_action": "none"
|
||||
},
|
||||
"notification": {
|
||||
"required": true,
|
||||
"notification_type": "source_message_review",
|
||||
"show_source_message": true,
|
||||
"requires_user_decision": true
|
||||
},
|
||||
"manual_review": {
|
||||
"reason_code": "cannot_form_business_material_package",
|
||||
"review_notes": "需要人工查看原邮件。"
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
MvcResult result = mockMvc.perform(signedPost(body, "nonce-v3-s99-frontend-visible-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn();
|
||||
String taskId = com.jayway.jsonpath.JsonPath.read(
|
||||
result.getResponse().getContentAsString(),
|
||||
"$.items[0].task_id"
|
||||
);
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks")
|
||||
.param("hotel_id", "HOTEL-TEST")
|
||||
.param("task_type", "SOURCE_MESSAGE_ONLY")
|
||||
.param("keyword", "mail-v3-s99-frontend-visible-001"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.items[0].task_id").value(taskId))
|
||||
.andExpect(jsonPath("$.items[0].result_type").value("source_message_review_notification"))
|
||||
.andExpect(jsonPath("$.items[0].ai_task_type").value("S99"))
|
||||
.andExpect(jsonPath("$.items[0].route_code").value("S99"))
|
||||
.andExpect(jsonPath("$.items[0].system_process_category").value("SOURCE_MESSAGE_NOTIFICATION"));
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks/{taskId}", taskId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.result_type").value("source_message_review_notification"))
|
||||
.andExpect(jsonPath("$.ai_task_type").value("S99"))
|
||||
.andExpect(jsonPath("$.task_subtype").value("S99"))
|
||||
.andExpect(jsonPath("$.route_code").value("S99"))
|
||||
.andExpect(jsonPath("$.system_process_category").value("SOURCE_MESSAGE_NOTIFICATION"))
|
||||
.andExpect(jsonPath("$.source_message_only_result.result_type")
|
||||
.value("source_message_review_notification"))
|
||||
.andExpect(jsonPath("$.source_message_only_result.route_code").value("S99"))
|
||||
.andExpect(jsonPath("$.source_message_only_result.agent_assessment.status")
|
||||
.value("insufficient_business_material"))
|
||||
.andExpect(jsonPath("$.source_message_only_result.notification.notification_type")
|
||||
.value("source_message_review"))
|
||||
.andExpect(jsonPath("$.source_message_only_result.manual_review.reason_code")
|
||||
.value("cannot_form_business_material_package"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateBusinessTaskFromV3BusinessRootMessageEvent() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-v3-business-root-001");
|
||||
@@ -968,6 +1040,116 @@ class SuperAgentTaskResultControllerTest {
|
||||
assertThat(taskCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposeV3RouteAndRelatedTransitionBlocksInFrontendQueries() throws Exception {
|
||||
captureSourceMessage("mail-v3-frontend-route-blocks-001");
|
||||
String body = """
|
||||
{
|
||||
"source_message": {
|
||||
"source_message_id": "mail-v3-frontend-route-blocks-001",
|
||||
"subject": "Mixed events",
|
||||
"from": null,
|
||||
"cc": [],
|
||||
"received_at": null,
|
||||
"source_channel": "Email"
|
||||
},
|
||||
"message_events": [
|
||||
{
|
||||
"event_type": "Trace",
|
||||
"source_event_index": "E1",
|
||||
"current_or_history": "current",
|
||||
"case_keys": {
|
||||
"group_code": "GRP-V3-FRONTEND-BLOCK-001",
|
||||
"confirmation_number": null
|
||||
},
|
||||
"extracted_fields": {},
|
||||
"contract_errors": [
|
||||
{
|
||||
"path": "/extracted_fields",
|
||||
"code": "unsupported_contract"
|
||||
}
|
||||
],
|
||||
"manual_review": null
|
||||
},
|
||||
{
|
||||
"event_type": "New Booking",
|
||||
"source_event_index": "E2",
|
||||
"current_or_history": "current",
|
||||
"case_keys": {
|
||||
"group_code": null,
|
||||
"confirmation_number": "CNF-V3-FRONTEND-BLOCK-001"
|
||||
},
|
||||
"extracted_fields": {
|
||||
"booking_object_type": "FIT Reservation"
|
||||
},
|
||||
"manual_review": null
|
||||
}
|
||||
],
|
||||
"case_candidates": [],
|
||||
"extraction_warnings": [],
|
||||
"unhandled_current_intents": [
|
||||
{
|
||||
"intent_type": "unsupported_active_request",
|
||||
"reason_code": "no_task_card_available"
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
MvcResult result = mockMvc.perform(signedPost(body, "nonce-v3-frontend-route-blocks-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.accepted_count").value(3))
|
||||
.andReturn();
|
||||
String orderId = com.jayway.jsonpath.JsonPath.read(
|
||||
result.getResponse().getContentAsString(),
|
||||
"$.items[1].order_id"
|
||||
);
|
||||
String taskId = com.jayway.jsonpath.JsonPath.read(
|
||||
result.getResponse().getContentAsString(),
|
||||
"$.items[1].task_id"
|
||||
);
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks")
|
||||
.param("hotel_id", "HOTEL-TEST")
|
||||
.param("keyword", "mail-v3-frontend-route-blocks-001"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.items[0].task_id").value(taskId))
|
||||
.andExpect(jsonPath("$.items[0].result_type").value("normal_task"))
|
||||
.andExpect(jsonPath("$.items[0].ai_task_type").value("New Booking"))
|
||||
.andExpect(jsonPath("$.items[0].route_code").value("R01_NEW_FIT_RESERVATION_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[0].system_process_category").value("BUSINESS_TASK"));
|
||||
|
||||
mockMvc.perform(get("/api/reservation/orders/{orderId}", orderId)
|
||||
.param("hotel_id", "HOTEL-TEST")
|
||||
.param("include_tasks", "true"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.tasks[0].task_id").value(taskId))
|
||||
.andExpect(jsonPath("$.tasks[0].result_type").value("normal_task"))
|
||||
.andExpect(jsonPath("$.tasks[0].ai_task_type").value("New Booking"))
|
||||
.andExpect(jsonPath("$.tasks[0].route_code").value("R01_NEW_FIT_RESERVATION_NORMAL"))
|
||||
.andExpect(jsonPath("$.tasks[0].system_process_category").value("BUSINESS_TASK"));
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks/{taskId}", taskId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.result_type").value("normal_task"))
|
||||
.andExpect(jsonPath("$.ai_task_type").value("New Booking"))
|
||||
.andExpect(jsonPath("$.task_subtype").value("new_fit_reservation"))
|
||||
.andExpect(jsonPath("$.route_code").value("R01_NEW_FIT_RESERVATION_NORMAL"))
|
||||
.andExpect(jsonPath("$.system_process_category").value("BUSINESS_TASK"))
|
||||
.andExpect(jsonPath("$.adapter_contract_errors.length()").value(1))
|
||||
.andExpect(jsonPath("$.adapter_contract_errors[0].adapter_error_code")
|
||||
.value("EVENT_CONTRACT_ERROR"))
|
||||
.andExpect(jsonPath("$.adapter_contract_errors[0].payload_fragment.contract_errors[0].code")
|
||||
.value("unsupported_contract"))
|
||||
.andExpect(jsonPath("$.unhandled_intents.length()").value(1))
|
||||
.andExpect(jsonPath("$.unhandled_intents[0].route_code")
|
||||
.value("R42_UNHANDLED_CURRENT_INTENT"))
|
||||
.andExpect(jsonPath("$.unhandled_intents[0].system_process_category")
|
||||
.value("UNHANDLED_CURRENT_INTENT"))
|
||||
.andExpect(jsonPath("$.unhandled_intents[0].payload_fragment.intent_type")
|
||||
.value("unsupported_active_request"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPersistAdapterContractErrorForIncompleteV3ManualReview() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-v3-manual-review-incomplete-001");
|
||||
|
||||
Reference in New Issue
Block a user