diff --git a/backend/src/main/java/se/bilhalsning/entity/OrderStatus.java b/backend/src/main/java/se/bilhalsning/entity/OrderStatus.java index 221fa60..09cf9d4 100644 --- a/backend/src/main/java/se/bilhalsning/entity/OrderStatus.java +++ b/backend/src/main/java/se/bilhalsning/entity/OrderStatus.java @@ -3,6 +3,7 @@ package se.bilhalsning.entity; public enum OrderStatus { PENDING_PAYMENT("pending_payment"), PAID("paid"), + PENDING_REVIEW("pending_review"), PROCESSING("processing"), SENT("sent"), DELIVERED("delivered"), diff --git a/backend/src/main/java/se/bilhalsning/service/AdminOrderStatusRules.java b/backend/src/main/java/se/bilhalsning/service/AdminOrderStatusRules.java index 605a07d..8f0129d 100644 --- a/backend/src/main/java/se/bilhalsning/service/AdminOrderStatusRules.java +++ b/backend/src/main/java/se/bilhalsning/service/AdminOrderStatusRules.java @@ -53,6 +53,7 @@ public final class AdminOrderStatusRules { private static List allowedTargets(OrderStatus from, Order order) { return switch (from) { case PENDING_PAYMENT -> List.of(OrderStatus.FAILED); + case PENDING_REVIEW -> List.of(OrderStatus.PROCESSING, OrderStatus.CANCELLED, OrderStatus.FAILED); case PROCESSING -> List.of(OrderStatus.FAILED); case SENT -> List.of(OrderStatus.DELIVERED, OrderStatus.FAILED); case DELIVERED -> List.of(OrderStatus.FAILED); diff --git a/backend/src/main/java/se/bilhalsning/service/AdminOrderWorkflowService.java b/backend/src/main/java/se/bilhalsning/service/AdminOrderWorkflowService.java index 5d6ef10..6de8571 100644 --- a/backend/src/main/java/se/bilhalsning/service/AdminOrderWorkflowService.java +++ b/backend/src/main/java/se/bilhalsning/service/AdminOrderWorkflowService.java @@ -34,6 +34,9 @@ public class AdminOrderWorkflowService { if (newStatus == OrderStatus.FAILED && previousStatus != OrderStatus.FAILED) { orderNotificationService.notifyOrderFailed(saved); } + if (newStatus == OrderStatus.PROCESSING && previousStatus == OrderStatus.PENDING_REVIEW) { + orderNotificationService.notifyOrderProcessing(saved); + } return saved; } diff --git a/backend/src/main/java/se/bilhalsning/service/OrderService.java b/backend/src/main/java/se/bilhalsning/service/OrderService.java index 34528e9..5f0b675 100644 --- a/backend/src/main/java/se/bilhalsning/service/OrderService.java +++ b/backend/src/main/java/se/bilhalsning/service/OrderService.java @@ -64,6 +64,14 @@ public class OrderService { * Honor-system payment confirmation for guest orders. Mirrors * {@link #confirmPayment(UUID, UUID)} but authenticates via the * guest token instead of {@code userId}. + * + *

Guest self-confirmed orders enter {@link OrderStatus#PENDING_REVIEW} + * rather than {@link OrderStatus#PROCESSING} because the payment is + * not verified — the customer simply clicked "I have paid" on the + * payment page. An admin must manually review and advance the order + * to PROCESSING (which triggers fulfillment notification). This + * prevents a guest from marking an order paid without paying and + * having it automatically sent to fulfillment. */ public Order confirmGuestPayment(UUID guestToken) { Order order = getOrderByGuestToken(guestToken); @@ -71,10 +79,8 @@ public class OrderService { throw new InvalidOrderStateException( "Beställningen kan inte ändras i detta tillstånd"); } - order.setStatus(OrderStatus.PROCESSING); - Order saved = orderRepository.save(order); - orderNotificationService.notifyOrderProcessing(saved); - return saved; + order.setStatus(OrderStatus.PENDING_REVIEW); + return orderRepository.save(order); } public List getOrdersByUserId(UUID userId) { diff --git a/backend/src/test/java/se/bilhalsning/service/AdminOrderStatusRulesTest.java b/backend/src/test/java/se/bilhalsning/service/AdminOrderStatusRulesTest.java index d57c02c..eb940e8 100644 --- a/backend/src/test/java/se/bilhalsning/service/AdminOrderStatusRulesTest.java +++ b/backend/src/test/java/se/bilhalsning/service/AdminOrderStatusRulesTest.java @@ -30,6 +30,32 @@ class AdminOrderStatusRulesTest { assertFalse(AdminOrderStatusRules.canRegisterShipment(order)); } + @Test + void shouldAllowProcessingCancelledFailedFromPendingReview() { + Order order = orderWithStatus(OrderStatus.PENDING_REVIEW); + + assertEquals( + java.util.List.of("pending_review", "processing", "cancelled", "failed"), + AdminOrderStatusRules.allowedStatusValues(order)); + assertFalse(AdminOrderStatusRules.canRegisterShipment(order)); + } + + @Test + void shouldValidatePendingReviewToProcessingTransition() { + Order order = orderWithStatus(OrderStatus.PENDING_REVIEW); + + assertDoesNotThrow(() -> + AdminOrderStatusRules.validateTransition(order, OrderStatus.PROCESSING)); + } + + @Test + void shouldRejectPendingReviewToSentTransition() { + Order order = orderWithStatus(OrderStatus.PENDING_REVIEW); + + assertThrows(se.bilhalsning.exception.InvalidOrderStateException.class, + () -> AdminOrderStatusRules.validateTransition(order, OrderStatus.SENT)); + } + @Test void shouldExposeFailedRecoveryOptionsWhenTrackingExists() { Order order = orderWithStatus(OrderStatus.FAILED); diff --git a/backend/src/test/java/se/bilhalsning/service/OrderServiceTest.java b/backend/src/test/java/se/bilhalsning/service/OrderServiceTest.java index 48a3a51..a10f340 100644 --- a/backend/src/test/java/se/bilhalsning/service/OrderServiceTest.java +++ b/backend/src/test/java/se/bilhalsning/service/OrderServiceTest.java @@ -344,8 +344,8 @@ class OrderServiceTest { Order result = orderService.confirmGuestPayment(token); - assertEquals(OrderStatus.PROCESSING, result.getStatus()); - verify(orderNotificationService).notifyOrderProcessing(result); + assertEquals(OrderStatus.PENDING_REVIEW, result.getStatus()); + verify(orderNotificationService, never()).notifyOrderProcessing(any()); } @Test diff --git a/frontend/src/pages/GuestOrderPage.vue b/frontend/src/pages/GuestOrderPage.vue index 5cbd9a4..ebaaf1c 100644 --- a/frontend/src/pages/GuestOrderPage.vue +++ b/frontend/src/pages/GuestOrderPage.vue @@ -15,6 +15,8 @@ const statusLabel = computed(() => { switch (order.value.status) { case 'pending_payment': return 'Väntar på betalning' + case 'pending_review': + return 'Betalning mottagen, under granskning' case 'paid': case 'processing': return 'Behandlas'