补齐订单列表V4继续处理入口

This commit is contained in:
andy
2026-07-20 01:54:38 +07:00
parent d1955f5097
commit 4b05cabd13
11 changed files with 459 additions and 17 deletions

View File

@@ -0,0 +1,13 @@
package cn.nianxx.thhotel.workflows.reservation.common.enums;
/**
* Reservation V4 订单列表下一步处理动作类型。
*/
public enum ReservationV4NextActionType {
/** 下一张卡片可走普通确认接口。 */
CONFIRM,
/** 下一张卡片需要走人工复核解阻接口。 */
REVIEW,
/** 当前订单没有 V4 待处理卡片。 */
NONE
}

View File

@@ -16,6 +16,11 @@ import java.time.OffsetDateTime;
* @param displayName 订单展示名
* @param openTaskCount 未关闭任务数,排除 COMPLETED 和 FAILED
* @param nextProcessableTaskId 下一条当前可处理任务 ID
* @param nextV4OrderTaskId 当前订单下第一条仍需用户处理的 V4 订单任务 ID
* @param nextV4ActionCardId 当前 V4 订单任务下第一张仍需确认或复核的卡片 ID
* @param nextV4ActionType V4 下一步动作类型CONFIRM / REVIEW / NONE
* @param nextV4ActionStatus V4 下一步卡片状态PENDING_CONFIRM / REVIEW_REQUIRED没有待处理卡时为空
* @param v4OpenOrderTaskCount 当前订单下未完成的 V4 订单任务数COMPLETED 不计入
* @param createdAt 订单创建时间UTC
* @param updatedAt 订单更新时间UTC
*/
@@ -40,6 +45,16 @@ public record ReservationOrderListItemResult(
Integer openTaskCount,
@JsonProperty("next_processable_task_id")
String nextProcessableTaskId,
@JsonProperty("next_v4_order_task_id")
String nextV4OrderTaskId,
@JsonProperty("next_v4_action_card_id")
String nextV4ActionCardId,
@JsonProperty("next_v4_action_type")
String nextV4ActionType,
@JsonProperty("next_v4_action_status")
String nextV4ActionStatus,
@JsonProperty("v4_open_order_task_count")
Integer v4OpenOrderTaskCount,
@JsonProperty("created_at")
OffsetDateTime createdAt,
@JsonProperty("updated_at")

View File

@@ -15,6 +15,9 @@ import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOrderKeyT
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationOrderVisibility;
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationTaskStatus;
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationV4CardStatus;
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationV4CardType;
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationV4NextActionType;
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationV4OrderTaskStatus;
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationOrderListQueryRequest;
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationTaskWorkbenchQueryRequest;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationOrderDetailResult;
@@ -134,11 +137,15 @@ public class ReservationFrontendQueryServiceImpl implements ReservationFrontendQ
Map<Long, List<ReservationAiQueryTaskSnapshot>> tasksByOrderId = groupTasksByOrderId(orderTasks);
Map<Long, ReservationTaskAvailabilityResult> availabilityByTaskId =
calculateAvailabilityByTaskId(orderTasks, orderTasks);
Map<Long, V4OrderListNextAction> v4NextActionsByOrderId = findV4NextActionsByOrderId(
normalizedRequest.hotelId(),
orderIds);
List<ReservationOrderListItemResult> items = page.items().stream()
.map(order -> toOrderListItem(
order,
tasksByOrderId.getOrDefault(order.id(), List.of()),
availabilityByTaskId))
availabilityByTaskId,
v4NextActionsByOrderId.getOrDefault(order.id(), V4OrderListNextAction.none())))
.toList();
return new ReservationOrderListResult(
items,
@@ -555,7 +562,8 @@ public class ReservationFrontendQueryServiceImpl implements ReservationFrontendQ
private ReservationOrderListItemResult toOrderListItem(
ReservationAiQueryOrderSnapshot order,
List<ReservationAiQueryTaskSnapshot> tasks,
Map<Long, ReservationTaskAvailabilityResult> availabilityByTaskId) {
Map<Long, ReservationTaskAvailabilityResult> availabilityByTaskId,
V4OrderListNextAction v4NextAction) {
return new ReservationOrderListItemResult(
order.id().toString(),
order.hotelId(),
@@ -567,6 +575,11 @@ public class ReservationFrontendQueryServiceImpl implements ReservationFrontendQ
order.displayName(),
openTaskCount(tasks),
nextProcessableTaskId(tasks, availabilityByTaskId),
v4NextAction.orderTaskId(),
v4NextAction.cardId(),
v4NextAction.actionType(),
v4NextAction.actionStatus(),
v4NextAction.openOrderTaskCount(),
UtcTimeFormatter.toUtcOffsetDateTime(order.createdAt()),
UtcTimeFormatter.toUtcOffsetDateTime(order.updatedAt()));
}
@@ -601,6 +614,155 @@ public class ReservationFrontendQueryServiceImpl implements ReservationFrontendQ
return tasksByOrderId;
}
/**
* 批量派生订单列表 V4 下一步处理入口。S10/S99 来源通知没有订单,不会进入该统计。
*/
private Map<Long, V4OrderListNextAction> findV4NextActionsByOrderId(String hotelId, List<Long> orderIds) {
if (orderIds == null || orderIds.isEmpty()) {
return Map.of();
}
List<ReservationV4OrderTaskSnapshot> orderTasks = v4WorkflowRepository.findOrderTasksByOrderIds(
hotelId,
orderIds);
if (orderTasks.isEmpty()) {
return Map.of();
}
Map<Long, List<ReservationV4OrderTaskSnapshot>> orderTasksByOrderId = groupV4OrderTasksByOrderId(orderTasks);
Map<Long, List<ReservationV4TaskCardSnapshot>> cardsByOrderTaskId = findV4TaskCardsByOrderTaskId(
hotelId,
orderTasks);
Map<Long, V4OrderListNextAction> result = new LinkedHashMap<>();
for (Long orderId : orderIds) {
result.put(orderId, toV4OrderListNextAction(
orderTasksByOrderId.getOrDefault(orderId, List.of()),
cardsByOrderTaskId));
}
return result;
}
/**
* 按本地订单 ID 对 V4 订单任务分组,并保留 Repository 已给出的同订单队列顺序。
*/
private Map<Long, List<ReservationV4OrderTaskSnapshot>> groupV4OrderTasksByOrderId(
List<ReservationV4OrderTaskSnapshot> orderTasks) {
Map<Long, List<ReservationV4OrderTaskSnapshot>> result = new LinkedHashMap<>();
List<ReservationV4OrderTaskSnapshot> safeOrderTasks =
orderTasks == null ? List.of() : orderTasks;
for (ReservationV4OrderTaskSnapshot orderTask : safeOrderTasks) {
if (orderTask.orderId() == null) {
continue;
}
result.computeIfAbsent(orderTask.orderId(), ignored -> new java.util.ArrayList<>()).add(orderTask);
}
return result;
}
/**
* 从同订单 V4 队列中派生第一条仍需处理的订单任务和卡片。
*/
private V4OrderListNextAction toV4OrderListNextAction(
List<ReservationV4OrderTaskSnapshot> orderTasks,
Map<Long, List<ReservationV4TaskCardSnapshot>> cardsByOrderTaskId) {
List<ReservationV4OrderTaskSnapshot> safeOrderTasks = orderTasks == null ? List.of() : orderTasks;
int openOrderTaskCount = Math.toIntExact(safeOrderTasks.stream()
.filter(this::isOpenV4OrderTask)
.count());
for (ReservationV4OrderTaskSnapshot orderTask : safeOrderTasks) {
if (!isOpenV4OrderTask(orderTask)) {
continue;
}
V4CardNextAction cardAction = nextV4CardAction(
cardsByOrderTaskId.getOrDefault(orderTask.id(), List.of()));
if (!ReservationV4NextActionType.NONE.name().equals(cardAction.actionType())) {
return new V4OrderListNextAction(
orderTask.id().toString(),
cardAction.cardId(),
cardAction.actionType(),
cardAction.actionStatus(),
openOrderTaskCount);
}
}
return V4OrderListNextAction.none(openOrderTaskCount);
}
/**
* 判断 V4 订单任务是否仍处于打开状态。COMPLETED 已结束,不计入订单列表 open 数。
*/
private boolean isOpenV4OrderTask(ReservationV4OrderTaskSnapshot orderTask) {
return orderTask != null
&& !ReservationV4OrderTaskStatus.COMPLETED.name().equals(orderTask.orderTaskStatus());
}
/**
* 派生单个 V4 订单任务下的下一张待处理卡Basic 优先,业务卡内 REVIEW 高于普通确认。
*/
private V4CardNextAction nextV4CardAction(List<ReservationV4TaskCardSnapshot> cards) {
List<ReservationV4TaskCardSnapshot> safeCards = cards == null ? List.of() : cards;
ReservationV4TaskCardSnapshot basicCard = safeCards.stream()
.filter(card -> ReservationV4CardType.BASIC_INFORMATION.name().equals(card.cardType()))
.findFirst()
.orElse(null);
V4CardNextAction basicAction = actionableCard(basicCard);
if (!ReservationV4NextActionType.NONE.name().equals(basicAction.actionType())) {
return basicAction;
}
ReservationV4TaskCardSnapshot reviewCard = firstBusinessCardByStatus(
safeCards,
ReservationV4CardStatus.REVIEW_REQUIRED.name());
if (reviewCard != null) {
return new V4CardNextAction(
reviewCard.id().toString(),
ReservationV4NextActionType.REVIEW.name(),
ReservationV4CardStatus.REVIEW_REQUIRED.name());
}
ReservationV4TaskCardSnapshot confirmCard = firstBusinessCardByStatus(
safeCards,
ReservationV4CardStatus.PENDING_CONFIRM.name());
if (confirmCard != null) {
return new V4CardNextAction(
confirmCard.id().toString(),
ReservationV4NextActionType.CONFIRM.name(),
ReservationV4CardStatus.PENDING_CONFIRM.name());
}
return V4CardNextAction.none();
}
/**
* 将 Basic Information 卡状态转换为订单列表可跳转动作。
*/
private V4CardNextAction actionableCard(ReservationV4TaskCardSnapshot card) {
if (card == null) {
return V4CardNextAction.none();
}
if (ReservationV4CardStatus.REVIEW_REQUIRED.name().equals(card.cardStatus())) {
return new V4CardNextAction(
card.id().toString(),
ReservationV4NextActionType.REVIEW.name(),
ReservationV4CardStatus.REVIEW_REQUIRED.name());
}
if (ReservationV4CardStatus.PENDING_CONFIRM.name().equals(card.cardStatus())) {
return new V4CardNextAction(
card.id().toString(),
ReservationV4NextActionType.CONFIRM.name(),
ReservationV4CardStatus.PENDING_CONFIRM.name());
}
return V4CardNextAction.none();
}
/**
* 按 Repository 排序找到第一张指定状态的业务卡,排除来源邮件展示卡和 Basic Information。
*/
private ReservationV4TaskCardSnapshot firstBusinessCardByStatus(
List<ReservationV4TaskCardSnapshot> cards,
String cardStatus) {
return cards.stream()
.filter(card -> !ReservationV4CardType.SOURCE_MESSAGE_DISPLAY.name().equals(card.cardType()))
.filter(card -> !ReservationV4CardType.BASIC_INFORMATION.name().equals(card.cardType()))
.filter(card -> cardStatus.equals(card.cardStatus()))
.findFirst()
.orElse(null);
}
/**
* 统计未关闭任务数量,排除 COMPLETED 和 FAILED。
*/
@@ -811,4 +973,41 @@ public class ReservationFrontendQueryServiceImpl implements ReservationFrontendQ
Long conversationMessageCount) {
}
/**
* 订单列表中一个本地订单对应的 V4 下一步处理摘要。
*/
private record V4OrderListNextAction(
String orderTaskId,
String cardId,
String actionType,
String actionStatus,
Integer openOrderTaskCount) {
private static V4OrderListNextAction none() {
return none(0);
}
private static V4OrderListNextAction none(Integer openOrderTaskCount) {
return new V4OrderListNextAction(
null,
null,
ReservationV4NextActionType.NONE.name(),
null,
openOrderTaskCount);
}
}
/**
* 单张 V4 卡片对应的下一步动作摘要。
*/
private record V4CardNextAction(
String cardId,
String actionType,
String actionStatus) {
private static V4CardNextAction none() {
return new V4CardNextAction(null, ReservationV4NextActionType.NONE.name(), null);
}
}
}