补充时间规范并修复非队列任务状态判断

This commit is contained in:
andy
2026-07-08 15:35:51 +08:00
parent 8a196f14ed
commit 5c5b8e33b0
4 changed files with 66 additions and 0 deletions

View File

@@ -93,6 +93,8 @@
- 生成代码前必须先参考 `AGENTS.md`、当前项目后端规范和既有代码习惯;遇到类职责或目录归属不确定时,先询问再继续,不能先写完再统一重构。 - 生成代码前必须先参考 `AGENTS.md`、当前项目后端规范和既有代码习惯;遇到类职责或目录归属不确定时,先询问再继续,不能先写完再统一重构。
- 不写魔法值,稳定业务代码使用常量或枚举。 - 不写魔法值,稳定业务代码使用常量或枚举。
- 集合、空值、字符串、时间、金额和 `BigDecimal` 使用安全写法。 - 集合、空值、字符串、时间、金额和 `BigDecimal` 使用安全写法。
- 后端业务时间点统一按 UTC 处理:数据库 `LocalDateTime` 默认表示 UTCAPI 返回时间点字段必须使用带 `Z` 的 ISO 8601 UTC 时间;新增响应 DTO 优先使用 `OffsetDateTime`,从数据库快照输出时使用 `UtcTimeFormatter` 统一转换。
- 入住日期、离店日期、酒店营业日等酒店本地业务日期不得和 UTC 时间点混用,应使用 `LocalDate` 或明确酒店时区语义的字段。
- 异常不吞掉,日志有上下文但不输出 Secret 或个人敏感信息。 - 异常不吞掉,日志有上下文但不输出 Secret 或个人敏感信息。
- 写操作考虑幂等、并发版本、事务边界、审计和失败恢复。 - 写操作考虑幂等、并发版本、事务边界、审计和失败恢复。
- 复杂业务规则、外部字段映射、脱敏和幂等逻辑必须有必要中文注释。 - 复杂业务规则、外部字段映射、脱敏和幂等逻辑必须有必要中文注释。

View File

@@ -210,6 +210,7 @@ groupCode
- 注释必须说明业务含义、来源或代码值范围。 - 注释必须说明业务含义、来源或代码值范围。
- 禁止使用“字段1”“备用字段”等模糊注释。 - 禁止使用“字段1”“备用字段”等模糊注释。
- 业务时间点以 UTC 写入数据库API 层负责返回带 `Z` 的 ISO 8601 UTC 时间,例如 `2026-07-08T03:00:00Z` - 业务时间点以 UTC 写入数据库API 层负责返回带 `Z` 的 ISO 8601 UTC 时间,例如 `2026-07-08T03:00:00Z`
- 新增 API 响应 DTO 中的时间点字段优先使用 `OffsetDateTime`;从数据库 `LocalDateTime` 快照输出到接口时,应通过 `UtcTimeFormatter` 转换,禁止直接把无时区 `LocalDateTime` 暴露给前端或 SuperAgent。
- 酒店本地业务日期,例如入住日期、离店日期、营业日,优先使用 `LocalDate` 或明确酒店时区语义的字段,不和 UTC 时间点混用。 - 酒店本地业务日期,例如入住日期、离店日期、营业日,优先使用 `LocalDate` 或明确酒店时区语义的字段,不和 UTC 时间点混用。
- JSON 字段只用于扩展元数据,不替代需要查询、约束或索引的正式列。 - JSON 字段只用于扩展元数据,不替代需要查询、约束或索引的正式列。

View File

@@ -31,6 +31,9 @@ public class ReservationTaskAvailabilityResolver {
* 计算任务可处理状态。FAILED 和 COMPLETED 视为前置任务结束,不阻塞后续任务。 * 计算任务可处理状态。FAILED 和 COMPLETED 视为前置任务结束,不阻塞后续任务。
*/ */
public ReservationTaskAvailabilityResult calculateAvailability(ReservationTaskSnapshot task) { public ReservationTaskAvailabilityResult calculateAvailability(ReservationTaskSnapshot task) {
if (!Boolean.TRUE.equals(task.queueParticipation())) {
return calculateAvailability(task, null);
}
ReservationTaskSnapshot blockingTask = workflowRepository ReservationTaskSnapshot blockingTask = workflowRepository
.findQueueTasksBefore(task.hotelId(), task.orderId(), task.executionOrder()) .findQueueTasksBefore(task.hotelId(), task.orderId(), task.executionOrder())
.stream() .stream()

View File

@@ -0,0 +1,60 @@
package cn.nianxx.thhotel.workflows.reservation.service.impl;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import cn.nianxx.thhotel.workflows.reservation.common.dto.ReservationTaskSnapshot;
import cn.nianxx.thhotel.workflows.reservation.common.enums.ReservationTaskStatus;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationTaskAvailabilityResult;
import cn.nianxx.thhotel.workflows.reservation.repository.ReservationAiWorkflowRepository;
import java.util.List;
import org.junit.jupiter.api.Test;
class ReservationTaskAvailabilityResolverTest {
@Test
void shouldReturnReadOnlyForNonQueueTaskWithoutQueryingPreviousTasks() {
ReservationAiWorkflowRepository workflowRepository = mock(ReservationAiWorkflowRepository.class);
when(workflowRepository.findQueueTasksBefore(anyString(), anyLong(), nullable(Integer.class)))
.thenReturn(List.of());
ReservationTaskAvailabilityResolver resolver = new ReservationTaskAvailabilityResolver(workflowRepository);
ReservationTaskSnapshot nonQueueTask = task(false, null, ReservationTaskStatus.PENDING_CONFIRM.name());
ReservationTaskAvailabilityResult result = resolver.calculateAvailability(nonQueueTask);
assertThat(result.blocked()).isFalse();
assertThat(result.readOnly()).isTrue();
assertThat(result.editable()).isFalse();
assertThat(result.confirmable()).isFalse();
assertThat(result.executable()).isFalse();
verify(workflowRepository, never()).findQueueTasksBefore(anyString(), anyLong(), nullable(Integer.class));
}
private ReservationTaskSnapshot task(Boolean queueParticipation, Integer executionOrder, String taskStatus) {
return new ReservationTaskSnapshot(
930000000000001001L,
"HOTEL-TEST",
930000000000001101L,
930000000000001201L,
930000000000001301L,
"message_notification",
"Message Notification",
"MESSAGE_NOTIFICATION",
"MESSAGE_NOTIFICATION",
"message_notification",
taskStatus,
queueParticipation,
executionOrder,
null,
null,
null,
false,
null);
}
}