diff --git a/src/main/java/com/yau/digitalrmb/exchange/interfaces/rest/ExchangeTrainingAttemptController.java b/src/main/java/com/yau/digitalrmb/exchange/interfaces/rest/ExchangeTrainingAttemptController.java index 3f4864f..e828448 100644 --- a/src/main/java/com/yau/digitalrmb/exchange/interfaces/rest/ExchangeTrainingAttemptController.java +++ b/src/main/java/com/yau/digitalrmb/exchange/interfaces/rest/ExchangeTrainingAttemptController.java @@ -54,6 +54,13 @@ public class ExchangeTrainingAttemptController { return ok(attempts.current(ExperimentModule.EXCHANGE, subject(users.getCurrentUser()))); } + @GetMapping("/latest") + @Operation(summary = "查询最近一次兑换实验", + description = "含已完成的实验,用于区分「从未开始」与「已完成」;已取消的不返回。") + public ApiResponse latest() { + return ok(attempts.latest(ExperimentModule.EXCHANGE, subject(users.getCurrentUser()))); + } + @GetMapping("/{attemptId}") @Operation(summary = "查询兑换实验详情和已保存动作") public ApiResponse detail(@PathVariable UUID attemptId) { diff --git a/src/main/java/com/yau/digitalrmb/training/attempt/application/ExperimentAttemptService.java b/src/main/java/com/yau/digitalrmb/training/attempt/application/ExperimentAttemptService.java index b36a1a5..4573cb5 100644 --- a/src/main/java/com/yau/digitalrmb/training/attempt/application/ExperimentAttemptService.java +++ b/src/main/java/com/yau/digitalrmb/training/attempt/application/ExperimentAttemptService.java @@ -50,6 +50,13 @@ public class ExperimentAttemptService { return toAttemptView(attempt, repository.findActions(attempt.getId())); } + @Transactional(readOnly = true) + public ExperimentAttemptView latest(ExperimentModule module, ExperimentSubject subject) { + ExperimentAttempt attempt = repository.findLatest(module, subject) + .orElseThrow(() -> notFound("未找到兑换实验记录")); + return toAttemptView(attempt, repository.findActions(attempt.getId())); + } + @Transactional(readOnly = true) public ExperimentAttemptView detail(UUID attemptId, ExperimentSubject subject) { ExperimentAttempt attempt = repository.findById(attemptId, subject) diff --git a/src/main/java/com/yau/digitalrmb/training/attempt/domain/ExperimentAttemptRepository.java b/src/main/java/com/yau/digitalrmb/training/attempt/domain/ExperimentAttemptRepository.java index 6df78ae..f544030 100644 --- a/src/main/java/com/yau/digitalrmb/training/attempt/domain/ExperimentAttemptRepository.java +++ b/src/main/java/com/yau/digitalrmb/training/attempt/domain/ExperimentAttemptRepository.java @@ -7,6 +7,8 @@ import java.util.UUID; public interface ExperimentAttemptRepository { ExperimentAttempt create(ExperimentModule module, ExperimentSubject subject); Optional findCurrent(ExperimentModule module, ExperimentSubject subject); + /** 最近一次未取消的实验,含已完成的;用于区分「从未开始」与「已完成」。 */ + Optional findLatest(ExperimentModule module, ExperimentSubject subject); Optional findById(UUID id, ExperimentSubject subject); Optional findByIdForUpdate(UUID id, ExperimentSubject subject); void save(ExperimentAttempt attempt); diff --git a/src/main/java/com/yau/digitalrmb/training/attempt/infrastructure/JdbcExperimentAttemptRepository.java b/src/main/java/com/yau/digitalrmb/training/attempt/infrastructure/JdbcExperimentAttemptRepository.java index 9759580..9d38bed 100644 --- a/src/main/java/com/yau/digitalrmb/training/attempt/infrastructure/JdbcExperimentAttemptRepository.java +++ b/src/main/java/com/yau/digitalrmb/training/attempt/infrastructure/JdbcExperimentAttemptRepository.java @@ -59,6 +59,17 @@ public class JdbcExperimentAttemptRepository implements ExperimentAttemptReposit return optional(values); } + @Override + public Optional findLatest(ExperimentModule module, ExperimentSubject subject) { + List values = jdbc.query("SELECT * FROM training_experiment_attempt WHERE module_code=? " + + "AND user_id=? AND school_id=? AND class_id=? " + + "AND organization_id=? " + + "AND status IN ('IN_PROGRESS','SUBMITTED','COMPLETED') ORDER BY attempt_no DESC LIMIT 1", + attemptMapper(), module.name(), subject.getUserId(), subject.getSchoolId(), subject.getClassId(), + subject.getOrganizationId()); + return optional(values); + } + @Override public Optional findById(UUID id, ExperimentSubject subject) { return findById(id, subject, false); diff --git a/src/test/java/com/yau/digitalrmb/exchange/interfaces/rest/ExchangeTrainingAttemptControllerTest.java b/src/test/java/com/yau/digitalrmb/exchange/interfaces/rest/ExchangeTrainingAttemptControllerTest.java index 94b0ea2..af465dc 100644 --- a/src/test/java/com/yau/digitalrmb/exchange/interfaces/rest/ExchangeTrainingAttemptControllerTest.java +++ b/src/test/java/com/yau/digitalrmb/exchange/interfaces/rest/ExchangeTrainingAttemptControllerTest.java @@ -87,6 +87,8 @@ class ExchangeTrainingAttemptControllerTest { mockMvc.perform(get("/api/v1/exchange/attempts/current").with(jwtForUser())) .andExpect(status().isOk()).andExpect(jsonPath("$.data.attemptId").value(attemptId.toString())); + mockMvc.perform(get("/api/v1/exchange/attempts/latest").with(jwtForUser())) + .andExpect(status().isOk()).andExpect(jsonPath("$.data.attemptId").value(attemptId.toString())); mockMvc.perform(get("/api/v1/exchange/attempts/{id}", attemptId).with(jwtForUser())) .andExpect(status().isOk()).andExpect(jsonPath("$.data.attemptId").value(attemptId.toString())); mockMvc.perform(post("/api/v1/exchange/attempts/{id}/cancel", attemptId).with(jwtForUser())) diff --git a/src/test/java/com/yau/digitalrmb/training/attempt/infrastructure/JdbcExperimentAttemptRepositoryTest.java b/src/test/java/com/yau/digitalrmb/training/attempt/infrastructure/JdbcExperimentAttemptRepositoryTest.java index 364b42c..771a537 100644 --- a/src/test/java/com/yau/digitalrmb/training/attempt/infrastructure/JdbcExperimentAttemptRepositoryTest.java +++ b/src/test/java/com/yau/digitalrmb/training/attempt/infrastructure/JdbcExperimentAttemptRepositoryTest.java @@ -41,6 +41,34 @@ class JdbcExperimentAttemptRepositoryTest { assertEquals(second.getId(), repository.findCurrent(ExperimentModule.EXCHANGE, subject).get().getId()); } + @Test + void findLatestReturnsCompletedAttemptButCurrentDoesNot() { + ExperimentSubject subject = new ExperimentSubject("user-latest", 10L, 20L, "ORG-1"); + + ExperimentAttempt first = repository.create(ExperimentModule.EXCHANGE, subject); + first.complete(java.time.Instant.now()); + repository.save(first); + + assertFalse(repository.findCurrent(ExperimentModule.EXCHANGE, subject).isPresent()); + assertEquals(first.getId(), repository.findLatest(ExperimentModule.EXCHANGE, subject).get().getId()); + } + + @Test + void findLatestSkipsCancelledAttemptsAndPrefersTheNewestOne() { + ExperimentSubject subject = new ExperimentSubject("user-latest-2", 10L, 20L, "ORG-1"); + + ExperimentAttempt first = repository.create(ExperimentModule.EXCHANGE, subject); + first.complete(java.time.Instant.now()); + repository.save(first); + ExperimentAttempt second = repository.create(ExperimentModule.EXCHANGE, subject); + assertEquals(second.getId(), repository.findLatest(ExperimentModule.EXCHANGE, subject).get().getId()); + + second.cancel(); + repository.save(second); + assertEquals(first.getId(), repository.findLatest(ExperimentModule.EXCHANGE, subject).get().getId()); + assertFalse(repository.findLatest(ExperimentModule.PAYMENT, subject).isPresent()); + } + @Test void scopesAttemptReadsToUserSchoolClassAndOrganization() { ExperimentSubject owner = new ExperimentSubject("user-1", 10L, 20L, "ORG-1");