补齐订单详情V4任务时间线

This commit is contained in:
andy
2026-07-19 13:15:14 +07:00
parent 10c28f0acb
commit 13364e7edf
9 changed files with 496 additions and 25 deletions

View File

@@ -24,14 +24,23 @@ import cn.nianxx.thhotel.platform.message.service.SourceMessageQueryService;
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.ReservationTaskDraft;
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4OrderTaskDraft;
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4OrderTaskSnapshot;
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4TaskCardDraft;
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.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.ReservationV4OrderTaskStatus;
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationV4TargetResolutionStatus;
import cn.nianxx.thhotel.workflows.reservation.repository.ReservationAiWorkflowRepository;
import cn.nianxx.thhotel.workflows.reservation.repository.ReservationV4WorkflowRepository;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -73,6 +82,9 @@ class ReservationFrontendQueryControllerTest {
@MockitoSpyBean
private ReservationAiWorkflowRepository workflowRepository;
@Autowired
private ReservationV4WorkflowRepository v4WorkflowRepository;
@Autowired
private JdbcTemplate jdbcTemplate;
@@ -409,6 +421,7 @@ class ReservationFrontendQueryControllerTest {
.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("$.v4_order_tasks.length()").value(0))
.andExpect(jsonPath("$.warnings.length()").value(0));
verify(workflowRepository, never()).findQueueTasksBefore(anyString(), anyLong(), any());
@@ -417,7 +430,119 @@ class ReservationFrontendQueryControllerTest {
.param("include_tasks", "false"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.order.order_id").value(orderId.toString()))
.andExpect(jsonPath("$.tasks.length()").value(0));
.andExpect(jsonPath("$.tasks.length()").value(0))
.andExpect(jsonPath("$.v4_order_tasks.length()").value(0));
}
@Test
void shouldReturnOrderDetailWithV4OrderTaskTimeline() throws Exception {
SourceMessageCaptureResult laterSource = captureSourceMessage(
"mail-frontend-order-detail-v4-later-001",
"Frontend V4 Later",
Instant.parse("2026-07-08T09:00:00Z"));
SourceMessageCaptureResult earlierSource = captureSourceMessage(
"mail-frontend-order-detail-v4-earlier-001",
"Frontend V4 Earlier",
Instant.parse("2026-07-08T07:00:00Z"));
Long orderId = 930000000000003001L;
insertActiveGroupOrder(orderId, earlierSource.inboxId(), "GRP-FRONTEND-V4-DETAIL-001");
ReservationV4OrderTaskSnapshot laterOrderTask = insertV4OrderTask(
930000000000003101L,
laterSource,
orderId,
"order-later",
"GRP-FRONTEND-V4-LATER-001",
Instant.parse("2026-07-08T09:00:00Z"),
HOTEL_ID);
ReservationV4OrderTaskSnapshot earlierOrderTask = insertV4OrderTask(
930000000000003102L,
earlierSource,
orderId,
"order-earlier",
"GRP-FRONTEND-V4-EARLIER-001",
Instant.parse("2026-07-08T07:00:00Z"),
HOTEL_ID);
insertV4TaskCard(earlierOrderTask, ReservationV4CardType.SOURCE_MESSAGE_DISPLAY.name(), null, 0, 10,
ReservationV4CardStatus.READONLY.name(), null, """
{"card_type":"SOURCE_MESSAGE_DISPLAY","source_message":{"subject":"Frontend V4 Earlier"}}
""");
insertV4TaskCard(earlierOrderTask, ReservationV4CardType.BASIC_INFORMATION.name(), null, 0, 20,
ReservationV4CardStatus.CONFIRMED.name(), null, """
{"card_type":"BASIC_INFORMATION","order_ref":"order-earlier"}
""");
insertV4TaskCard(earlierOrderTask, ReservationV4CardType.ROOM_INFORMATION.name(), "NEW_BOOKING", 1, 30,
ReservationV4CardStatus.REVIEW_REQUIRED.name(), "PENDING", """
{"card_type":"ROOM_INFORMATION","event_type":"NEW_BOOKING","missing_fields":["room_items"]}
""");
insertV4TaskCard(laterOrderTask, ReservationV4CardType.SOURCE_MESSAGE_DISPLAY.name(), null, 0, 10,
ReservationV4CardStatus.READONLY.name(), null, "{}");
insertV4TaskCard(laterOrderTask, ReservationV4CardType.BASIC_INFORMATION.name(), null, 0, 20,
ReservationV4CardStatus.PENDING_CONFIRM.name(), null, "{}");
updateV4OrderTaskUpdatedAt(earlierOrderTask.id(), Instant.parse("2026-07-08T07:10:00Z"));
updateV4TaskCardUpdatedAt(earlierOrderTask.id(), 30, Instant.parse("2026-07-08T07:45:00Z"));
performAuthorized(mockMvc, adminToken(), get("/api/reservation/orders/{orderId}", orderId)
.param("hotel_id", HOTEL_ID)
.param("include_tasks", "true"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.tasks.length()").value(0))
.andExpect(jsonPath("$.v4_order_tasks.length()").value(2))
.andExpect(jsonPath("$.v4_order_tasks[0].order_task_id").value(earlierOrderTask.id().toString()))
.andExpect(jsonPath("$.v4_order_tasks[0].order_ref").value("order-earlier"))
.andExpect(jsonPath("$.v4_order_tasks[0].order_task_status").value("OPEN"))
.andExpect(jsonPath("$.v4_order_tasks[0].card_counts.total_count").value(3))
.andExpect(jsonPath("$.v4_order_tasks[0].card_counts.readonly_count").value(1))
.andExpect(jsonPath("$.v4_order_tasks[0].card_counts.review_required_count").value(1))
.andExpect(jsonPath("$.v4_order_tasks[0].card_counts.confirmed_count").value(1))
.andExpect(jsonPath("$.v4_order_tasks[0].source_message_summary.source_message_id")
.value(earlierSource.inboxId().toString()))
.andExpect(jsonPath("$.v4_order_tasks[0].source_message_summary.subject").value("Frontend V4 Earlier"))
.andExpect(jsonPath("$.v4_order_tasks[0].source_received_at").value("2026-07-08T07:00:00Z"))
.andExpect(jsonPath("$.v4_order_tasks[0].created_at").value(matchesPattern(UTC_INSTANT_PATTERN)))
.andExpect(jsonPath("$.v4_order_tasks[0].updated_at").value(matchesPattern(UTC_INSTANT_PATTERN)))
.andExpect(jsonPath("$.v4_order_tasks[0].latest_activity_at").value("2026-07-08T07:45:00Z"))
.andExpect(jsonPath("$.v4_order_tasks[1].order_task_id").value(laterOrderTask.id().toString()))
.andExpect(jsonPath("$.v4_order_tasks[1].source_received_at").value("2026-07-08T09:00:00Z"));
}
@Test
void shouldHideV4OrderTaskTimelineWhenIncludeTasksFalseAndIgnoreCrossHotelRows() throws Exception {
SourceMessageCaptureResult source = captureSourceMessage(
"mail-frontend-order-detail-v4-compatible-001",
"Frontend V4 Compatible",
Instant.parse("2026-07-08T10:00:00Z"));
Long orderId = 930000000000003201L;
insertActiveGroupOrder(orderId, source.inboxId(), "GRP-FRONTEND-V4-COMPATIBLE-001");
insertV4OrderTask(
930000000000003202L,
source,
orderId,
"order-visible",
"GRP-FRONTEND-V4-COMPATIBLE-001",
Instant.parse("2026-07-08T10:00:00Z"),
HOTEL_ID);
insertV4OrderTask(
930000000000003203L,
source,
orderId,
"order-cross-hotel",
"GRP-FRONTEND-V4-CROSS-001",
Instant.parse("2026-07-08T10:05:00Z"),
"HOTEL-OTHER");
performAuthorized(mockMvc, adminToken(), get("/api/reservation/orders/{orderId}", orderId)
.param("hotel_id", HOTEL_ID)
.param("include_tasks", "false"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.tasks.length()").value(0))
.andExpect(jsonPath("$.v4_order_tasks.length()").value(0));
performAuthorized(mockMvc, adminToken(), get("/api/reservation/orders/{orderId}", orderId)
.param("hotel_id", HOTEL_ID)
.param("include_tasks", "true"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.v4_order_tasks.length()").value(1))
.andExpect(jsonPath("$.v4_order_tasks[0].order_ref").value("order-visible"));
}
@Test
@@ -528,6 +653,74 @@ class ReservationFrontendQueryControllerTest {
""", orderVisibility, orderId);
}
private void updateV4OrderTaskUpdatedAt(Long orderTaskId, Instant updatedAt) {
jdbcTemplate.update("""
UPDATE workflow_reservation_v4_order_task
SET updated_at = ?
WHERE id = ?
""", Timestamp.valueOf(LocalDateTime.ofInstant(updatedAt, ZoneOffset.UTC)), orderTaskId);
}
private void updateV4TaskCardUpdatedAt(Long orderTaskId, Integer cardSortOrder, Instant updatedAt) {
jdbcTemplate.update("""
UPDATE workflow_reservation_v4_task_card
SET updated_at = ?
WHERE v4_order_task_id = ?
AND card_sort_order = ?
""", Timestamp.valueOf(LocalDateTime.ofInstant(updatedAt, ZoneOffset.UTC)), orderTaskId, cardSortOrder);
}
private ReservationV4OrderTaskSnapshot insertV4OrderTask(
Long aiBatchId,
SourceMessageCaptureResult source,
Long orderId,
String orderRef,
String targetLocatorValue,
Instant sourceReceivedAt,
String hotelId) {
LocalDateTime now = LocalDateTime.ofInstant(sourceReceivedAt.plusSeconds(10), ZoneOffset.UTC);
return v4WorkflowRepository.findOrCreateOrderTask(new ReservationV4OrderTaskDraft(
hotelId,
source.inboxId(),
aiBatchId,
orderRef,
1,
orderId,
"GROUP",
"GROUP_CODE",
targetLocatorValue,
ReservationV4TargetResolutionStatus.RESOLVED.name(),
ReservationV4OrderTaskStatus.OPEN.name(),
LocalDateTime.ofInstant(sourceReceivedAt, ZoneOffset.UTC),
now));
}
private void insertV4TaskCard(
ReservationV4OrderTaskSnapshot orderTask,
String cardType,
String eventType,
Integer sourceEventIndex,
Integer cardSortOrder,
String cardStatus,
String reviewStatus,
String displayPayloadJson) {
v4WorkflowRepository.insertTaskCard(new ReservationV4TaskCardDraft(
orderTask.hotelId(),
orderTask.id(),
orderTask.sourceMessageId(),
eventType == null ? null : orderTask.aiBatchId() + cardSortOrder,
cardType,
eventType,
sourceEventIndex,
cardSortOrder,
cardStatus,
reviewStatus,
"{}",
displayPayloadJson,
null,
orderTask.createdAt()));
}
private void insertActiveGroupOrder(Long orderId, Long sourceMessageId, String groupCode) {
insertGroupOrder(orderId, sourceMessageId, groupCode, "ACTIVE");
}