修复 V4 确认接口边界

This commit is contained in:
andy
2026-07-19 10:58:58 +07:00
parent fdb16cc4c1
commit 080266c633
7 changed files with 216 additions and 17 deletions

View File

@@ -7,6 +7,7 @@ import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAuditLogDra
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4OrderTaskSnapshot;
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4SourceNotificationSnapshot;
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4TaskCardSnapshot;
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationAiRouteDefinition;
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.ReservationV4NotificationStatus;
@@ -118,6 +119,7 @@ public class ReservationV4CommandServiceImpl implements ReservationV4CommandServ
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
ReservationV4SourceNotificationSnapshot notification = requireSourceNotification(notificationId);
requireHotelAccess(notification.hotelId());
validateSourceNotificationAckRoute(notification);
if (ReservationV4NotificationStatus.ACKED.name().equals(notification.notificationStatus())) {
return queryService.getSourceNotificationDetail(notification.hotelId(), notification.id());
}
@@ -143,6 +145,18 @@ public class ReservationV4CommandServiceImpl implements ReservationV4CommandServ
return queryService.getSourceNotificationDetail(notification.hotelId(), notification.id());
}
private void validateSourceNotificationAckRoute(ReservationV4SourceNotificationSnapshot notification) {
boolean ackableRoute = ReservationAiRouteDefinition.findByRouteCode(notification.routeCode())
.map(ReservationAiRouteDefinition::sourceMessageNotification)
.orElse(false);
if (!ackableRoute) {
throw error(
HttpStatus.CONFLICT,
"V4_SOURCE_NOTIFICATION_ROUTE_NOT_ACKABLE",
"该来源通知路由不允许通过 S10/S99 ack 接口确认。");
}
}
private void validateCardConfirmable(
ReservationV4OrderTaskSnapshot orderTask,
ReservationV4TaskCardSnapshot card) {

View File

@@ -195,10 +195,10 @@ public class ReservationV4QueryServiceImpl implements ReservationV4QueryService
Map<Long, List<ReservationV4OrderTaskSnapshot>> queueContext = findQueueContext(
orderTask.hotelId(),
List.of(orderTask));
ReservationV4ActionAvailabilityResult orderAvailability = orderTaskAvailability(orderTask, queueContext);
List<ReservationV4TaskCardSnapshot> cards = workflowRepository.findTaskCardsByOrderTaskId(
orderTask.hotelId(),
orderTask.id());
ReservationV4ActionAvailabilityResult orderAvailability = orderTaskAvailability(orderTask, queueContext, cards);
ReservationV4TaskCardSnapshot basicCard = findFirstCard(cards, ReservationV4CardType.BASIC_INFORMATION.name()).orElse(null);
ReservationV4TaskCardResult sourceMessageCard = findFirstCard(cards, ReservationV4CardType.SOURCE_MESSAGE_DISPLAY.name())
.map(card -> toCardResult(card, orderAvailability, basicCard))
@@ -263,7 +263,7 @@ public class ReservationV4QueryServiceImpl implements ReservationV4QueryService
ReservationV4SourceMessageSummaryResult sourceSummary,
Map<Long, List<ReservationV4OrderTaskSnapshot>> queueContext,
List<ReservationV4TaskCardSnapshot> cards) {
ReservationV4ActionAvailabilityResult availability = orderTaskAvailability(orderTask, queueContext);
ReservationV4ActionAvailabilityResult availability = orderTaskAvailability(orderTask, queueContext, cards);
return new ReservationV4OrderTaskListItemResult(
toOrderTaskSummary(orderTask),
sourceSummary,
@@ -395,7 +395,8 @@ public class ReservationV4QueryServiceImpl implements ReservationV4QueryService
private ReservationV4ActionAvailabilityResult orderTaskAvailability(
ReservationV4OrderTaskSnapshot orderTask,
Map<Long, List<ReservationV4OrderTaskSnapshot>> queueContext) {
Map<Long, List<ReservationV4OrderTaskSnapshot>> queueContext,
List<ReservationV4TaskCardSnapshot> cards) {
ReservationV4OrderTaskSnapshot blocker = findPriorBlockingOrderTask(orderTask, queueContext);
if (blocker != null) {
return new ReservationV4ActionAvailabilityResult(
@@ -421,16 +422,78 @@ public class ReservationV4QueryServiceImpl implements ReservationV4QueryService
null,
null);
}
return new ReservationV4ActionAvailabilityResult(
false,
false,
true,
true,
false,
ReservationV4ReadonlyReasonCode.PROCESSABLE.name(),
null,
null,
null);
return openOrderTaskAvailability(cards);
}
private ReservationV4ActionAvailabilityResult openOrderTaskAvailability(List<ReservationV4TaskCardSnapshot> cards) {
List<ReservationV4TaskCardSnapshot> safeCards = cards == null ? List.of() : cards;
ReservationV4TaskCardSnapshot basicCard = findFirstCard(
safeCards,
ReservationV4CardType.BASIC_INFORMATION.name()).orElse(null);
if (basicCard != null && ReservationV4CardStatus.REVIEW_REQUIRED.name().equals(basicCard.cardStatus())) {
return readOnlyAvailability(
ReservationV4ReadonlyReasonCode.REVIEW_API_PENDING.name(),
null,
"V4 人工复核写接口将在后续 checkpoint 开放。");
}
boolean hasConfirmableCard = safeCards.stream()
.anyMatch(card -> cardConfirmableAtOrderLevel(card, basicCard));
if (hasConfirmableCard) {
return new ReservationV4ActionAvailabilityResult(
false,
false,
true,
true,
false,
ReservationV4ReadonlyReasonCode.PROCESSABLE.name(),
null,
null,
null);
}
if (basicCard != null && !ReservationV4CardStatus.CONFIRMED.name().equals(basicCard.cardStatus())
&& hasPendingBusinessCard(safeCards)) {
return new ReservationV4ActionAvailabilityResult(
true,
true,
false,
false,
false,
ReservationV4ReadonlyReasonCode.PRIOR_CARD_NOT_CONFIRMED.name(),
null,
basicCard.id().toString(),
"Basic Information 卡未确认前,业务卡只能查看。");
}
boolean hasReviewCard = safeCards.stream()
.anyMatch(card -> ReservationV4CardStatus.REVIEW_REQUIRED.name().equals(card.cardStatus()));
if (hasReviewCard) {
return readOnlyAvailability(
ReservationV4ReadonlyReasonCode.REVIEW_API_PENDING.name(),
null,
"V4 人工复核写接口将在后续 checkpoint 开放。");
}
return readOnlyAvailability(ReservationV4ReadonlyReasonCode.CARD_LOCKED.name(), null, null);
}
private boolean cardConfirmableAtOrderLevel(
ReservationV4TaskCardSnapshot card,
ReservationV4TaskCardSnapshot basicCard) {
if (ReservationV4CardType.SOURCE_MESSAGE_DISPLAY.name().equals(card.cardType())) {
return false;
}
if (!ReservationV4CardStatus.PENDING_CONFIRM.name().equals(card.cardStatus())) {
return false;
}
if (ReservationV4CardType.BASIC_INFORMATION.name().equals(card.cardType())) {
return true;
}
return basicCard != null && ReservationV4CardStatus.CONFIRMED.name().equals(basicCard.cardStatus());
}
private boolean hasPendingBusinessCard(List<ReservationV4TaskCardSnapshot> cards) {
return cards.stream()
.filter(card -> !ReservationV4CardType.SOURCE_MESSAGE_DISPLAY.name().equals(card.cardType()))
.filter(card -> !ReservationV4CardType.BASIC_INFORMATION.name().equals(card.cardType()))
.anyMatch(card -> ReservationV4CardStatus.PENDING_CONFIRM.name().equals(card.cardStatus()));
}
private ReservationV4ActionAvailabilityResult cardAvailability(