统一V4待处理计数并补齐审计查询

This commit is contained in:
andy
2026-07-20 12:19:05 +07:00
parent c5244bc652
commit 5b4257b125
15 changed files with 438 additions and 19 deletions

View File

@@ -588,12 +588,16 @@ class ReservationFrontendQueryControllerTest {
.value(contains("ACTIVE")))
.andExpect(jsonPath("$.items[?(@.order_id=='" + activeOrderId + "')].open_task_count")
.value(contains(1)))
.andExpect(jsonPath("$.items[?(@.order_id=='" + activeOrderId + "')].open_work_item_count")
.value(contains(0)))
.andExpect(jsonPath("$.items[?(@.order_id=='" + activeOrderId + "')].next_processable_task_id")
.value(contains(pendingTaskId.toString())))
.andExpect(jsonPath("$.items[?(@.order_id=='" + endedOrderId + "')].order_status")
.value(contains("ENDED")))
.andExpect(jsonPath("$.items[?(@.order_id=='" + endedOrderId + "')].open_task_count")
.value(contains(0)))
.andExpect(jsonPath("$.items[?(@.order_id=='" + endedOrderId + "')].open_work_item_count")
.value(contains(0)))
.andExpect(jsonPath("$.page.total").value(2));
performAuthorized(mockMvc, adminToken(), get("/api/reservation/orders")
@@ -732,6 +736,8 @@ class ReservationFrontendQueryControllerTest {
.value(contains(legacyTaskId.toString())))
.andExpect(jsonPath("$.items[?(@.order_id=='" + basicFirstOrderId + "')].v4_open_order_task_count")
.value(contains(2)))
.andExpect(jsonPath("$.items[?(@.order_id=='" + basicFirstOrderId + "')].open_work_item_count")
.value(contains(2)))
.andExpect(jsonPath("$.items[?(@.order_id=='" + basicFirstOrderId + "')].next_v4_order_task_id")
.value(contains(basicFirstOrderTask.id().toString())))
.andExpect(jsonPath("$.items[?(@.order_id=='" + basicFirstOrderId + "')].next_v4_action_card_id")
@@ -742,6 +748,8 @@ class ReservationFrontendQueryControllerTest {
.value(contains("PENDING_CONFIRM")))
.andExpect(jsonPath("$.items[?(@.order_id=='" + reviewFirstOrderId + "')].v4_open_order_task_count")
.value(contains(1)))
.andExpect(jsonPath("$.items[?(@.order_id=='" + reviewFirstOrderId + "')].open_work_item_count")
.value(contains(1)))
.andExpect(jsonPath("$.items[?(@.order_id=='" + reviewFirstOrderId + "')].next_v4_order_task_id")
.value(contains(reviewOrderTask.id().toString())))
.andExpect(jsonPath("$.items[?(@.order_id=='" + reviewFirstOrderId + "')].next_v4_action_card_id")
@@ -760,6 +768,8 @@ class ReservationFrontendQueryControllerTest {
.value(contains("PENDING_CONFIRM")))
.andExpect(jsonPath("$.items[?(@.order_id=='" + completedOnlyOrderId + "')].v4_open_order_task_count")
.value(contains(0)))
.andExpect(jsonPath("$.items[?(@.order_id=='" + completedOnlyOrderId + "')].open_work_item_count")
.value(contains(0)))
.andExpect(jsonPath("$.items[?(@.order_id=='" + completedOnlyOrderId + "')].next_v4_order_task_id")
.value(contains(nullValue())))
.andExpect(jsonPath("$.items[?(@.order_id=='" + completedOnlyOrderId + "')].next_v4_action_type")
@@ -768,6 +778,8 @@ class ReservationFrontendQueryControllerTest {
.value(contains(nullValue())))
.andExpect(jsonPath("$.items[?(@.order_id=='" + noV4OrderId + "')].v4_open_order_task_count")
.value(contains(0)))
.andExpect(jsonPath("$.items[?(@.order_id=='" + noV4OrderId + "')].open_work_item_count")
.value(contains(0)))
.andExpect(jsonPath("$.items[?(@.order_id=='" + noV4OrderId + "')].next_v4_action_type")
.value(contains("NONE")));
}

View File

@@ -22,6 +22,7 @@ import cn.nianxx.thhotel.platform.message.common.result.SourceMessageCaptureResu
import cn.nianxx.thhotel.platform.message.service.SourceMessageCaptureService;
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationAiBatchDraft;
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.ReservationV4OrderTaskDraft;
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4OrderTaskSnapshot;
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationV4SourceNotificationDraft;
@@ -367,6 +368,58 @@ class ReservationV4QueryControllerTest {
.andExpect(content().string(not(containsString("Sensitive raw notification body"))));
}
@Test
void shouldReturnV4OrderTaskAudits() throws Exception {
ReservationV4OrderTaskSnapshot orderTask = seedOrderTask(
"mail-v4-query-order-task-audits-001",
Instant.parse("2026-07-18T04:10:00Z"));
insertV4OrderTaskAudit(orderTask, "V4_CARD_CONFIRM", "v4-query-admin",
Instant.parse("2026-07-18T04:11:00Z"));
insertV4OrderTaskAudit(orderTask, "V4_CARD_REVIEW_RESOLVE", "v4-query-admin",
Instant.parse("2026-07-18T04:12:00Z"));
ReservationV4OrderTaskSnapshot otherOrderTask = seedOrderTask(
"mail-v4-query-order-task-audits-other-001",
Instant.parse("2026-07-18T04:13:00Z"));
insertV4OrderTaskAudit(otherOrderTask, "V4_CARD_CONFIRM", "v4-query-admin",
Instant.parse("2026-07-18T04:14:00Z"));
performAuthorized(mockMvc, adminToken(), get("/api/reservation/order-tasks/{orderTaskId}/audits",
orderTask.id()).param("hotel_id", HOTEL_ID))
.andExpect(status().isOk())
.andExpect(jsonPath("$.order_task_id").value(orderTask.id().toString()))
.andExpect(jsonPath("$.items.length()").value(2))
.andExpect(jsonPath("$.items[0].action").value("V4_CARD_CONFIRM"))
.andExpect(jsonPath("$.items[0].actor_id").value("v4-query-admin"))
.andExpect(jsonPath("$.items[0].after_snapshot.v4_order_task_id").value(orderTask.id().toString()))
.andExpect(jsonPath("$.items[1].action").value("V4_CARD_REVIEW_RESOLVE"))
.andExpect(jsonPath("$.items[1].after_snapshot.v4_order_task_id").value(orderTask.id().toString()))
.andExpect(jsonPath("$.items[0].occurred_at").value(matchesPattern(UTC_INSTANT_PATTERN)))
.andExpect(content().string(not(containsString(otherOrderTask.id().toString()))))
.andExpect(content().string(not(containsString("Sensitive raw audit body"))));
}
@Test
void shouldReturnV4SourceNotificationAudits() throws Exception {
ReservationV4SourceNotificationSnapshot notification = seedSourceNotification(
"mail-v4-query-notification-audits-001",
"S10",
Instant.parse("2026-07-18T04:20:00Z"));
insertV4SourceNotificationAudit(notification, "v4-query-admin",
Instant.parse("2026-07-18T04:21:00Z"));
performAuthorized(mockMvc, adminToken(), get("/api/reservation/source-notifications/{notificationId}/audits",
notification.id()).param("hotel_id", HOTEL_ID))
.andExpect(status().isOk())
.andExpect(jsonPath("$.notification_id").value(notification.id().toString()))
.andExpect(jsonPath("$.items.length()").value(1))
.andExpect(jsonPath("$.items[0].action").value("V4_SOURCE_NOTIFICATION_ACK"))
.andExpect(jsonPath("$.items[0].actor_type").value("USER"))
.andExpect(jsonPath("$.items[0].after_snapshot.v4_source_notification_id")
.value(notification.id().toString()))
.andExpect(jsonPath("$.items[0].occurred_at").value(matchesPattern(UTC_INSTANT_PATTERN)))
.andExpect(content().string(not(containsString("Sensitive raw audit body"))));
}
@Test
void shouldRejectV4WorkbenchItemsWithoutLogin() throws Exception {
mockMvc.perform(get("/api/reservation/workbench-items")
@@ -400,6 +453,14 @@ class ReservationV4QueryControllerTest {
notification.id()).param("hotel_id", HOTEL_ID))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.error_code").value("FRONTEND_PERMISSION_DENIED"));
performAuthorized(mockMvc, noPermissionToken(), get("/api/reservation/order-tasks/{orderTaskId}/audits",
orderTask.id()).param("hotel_id", HOTEL_ID))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.error_code").value("FRONTEND_PERMISSION_DENIED"));
performAuthorized(mockMvc, noPermissionToken(), get("/api/reservation/source-notifications/{notificationId}/audits",
notification.id()).param("hotel_id", HOTEL_ID))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.error_code").value("FRONTEND_PERMISSION_DENIED"));
}
@Test
@@ -427,6 +488,14 @@ class ReservationV4QueryControllerTest {
notification.id()).param("hotel_id", OTHER_HOTEL_ID))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.error_code").value("HOTEL_ACCESS_DENIED"));
performAuthorized(mockMvc, adminToken(), get("/api/reservation/order-tasks/{orderTaskId}/audits",
orderTask.id()).param("hotel_id", OTHER_HOTEL_ID))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.error_code").value("HOTEL_ACCESS_DENIED"));
performAuthorized(mockMvc, adminToken(), get("/api/reservation/source-notifications/{notificationId}/audits",
notification.id()).param("hotel_id", OTHER_HOTEL_ID))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.error_code").value("HOTEL_ACCESS_DENIED"));
}
@Test
@@ -670,4 +739,49 @@ class ReservationV4QueryControllerTest {
"PAYMENT.attachment_ids 未匹配来源附件。",
now));
}
private void insertV4OrderTaskAudit(
ReservationV4OrderTaskSnapshot orderTask,
String action,
String actorId,
Instant occurredAt) {
aiWorkflowRepository.insertAuditLog(new ReservationAuditLogDraft(
HOTEL_ID,
orderTask.orderId(),
null,
null,
"USER",
actorId,
action,
"前端查询 V4 审计测试。",
"""
{"v4_order_task_id":"%s","card_status":"PENDING_CONFIRM","raw":"Sensitive raw audit body"}
""".formatted(orderTask.id()),
"""
{"v4_order_task_id":"%s","card_status":"CONFIRMED","field_pointers":["/basic_information/account_code"]}
""".formatted(orderTask.id()),
LocalDateTime.ofInstant(occurredAt, ZoneOffset.UTC)));
}
private void insertV4SourceNotificationAudit(
ReservationV4SourceNotificationSnapshot notification,
String actorId,
Instant occurredAt) {
aiWorkflowRepository.insertAuditLog(new ReservationAuditLogDraft(
HOTEL_ID,
null,
null,
null,
"USER",
actorId,
"V4_SOURCE_NOTIFICATION_ACK",
"确认已处理。",
"""
{"v4_source_notification_id":"%s","notification_status":"ACK_REQUIRED","raw":"Sensitive raw audit body"}
""".formatted(notification.id()),
"""
{"v4_source_notification_id":"%s","notification_status":"ACKED"}
""".formatted(notification.id()),
LocalDateTime.ofInstant(occurredAt, ZoneOffset.UTC)));
}
}