From 132fef816ccb0b65a8d4fd785ba0b7dcf75629f1 Mon Sep 17 00:00:00 2001 From: Tiago Yamamoto Date: Sat, 27 Dec 2025 01:18:00 -0300 Subject: [PATCH] feat(backend): implement order state machine with validation --- backend/internal/domain/models.go | 3 +++ backend/internal/usecase/usecase.go | 31 ++++++++++++++++++++++++ backend/internal/usecase/usecase_test.go | 6 +++++ 3 files changed, 40 insertions(+) diff --git a/backend/internal/domain/models.go b/backend/internal/domain/models.go index bf6ca81..388ed0a 100644 --- a/backend/internal/domain/models.go +++ b/backend/internal/domain/models.go @@ -395,7 +395,10 @@ const ( OrderStatusPending OrderStatus = "Pendente" OrderStatusPaid OrderStatus = "Pago" OrderStatusInvoiced OrderStatus = "Faturado" + OrderStatusShipped OrderStatus = "Enviado" OrderStatusDelivered OrderStatus = "Entregue" + OrderStatusCompleted OrderStatus = "ConcluĂ­do" + OrderStatusCancelled OrderStatus = "Cancelado" ) // PaymentMethod enumerates supported payment types. diff --git a/backend/internal/usecase/usecase.go b/backend/internal/usecase/usecase.go index 46bff83..314e14b 100644 --- a/backend/internal/usecase/usecase.go +++ b/backend/internal/usecase/usecase.go @@ -337,6 +337,37 @@ func (s *Service) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, er } func (s *Service) UpdateOrderStatus(ctx context.Context, id uuid.UUID, status domain.OrderStatus) error { + order, err := s.repo.GetOrder(ctx, id) + if err != nil { + return err + } + + // State Machine Logic + switch order.Status { + case domain.OrderStatusPending: + if status != domain.OrderStatusPaid && status != domain.OrderStatusCancelled { + return errors.New("invalid transition from Pending") + } + case domain.OrderStatusPaid: + if status != domain.OrderStatusInvoiced && status != domain.OrderStatusShipped && status != domain.OrderStatusCancelled { + return errors.New("invalid transition from Paid") + } + case domain.OrderStatusInvoiced: // Can go to Shipped + if status != domain.OrderStatusShipped && status != domain.OrderStatusCancelled { + return errors.New("invalid transition from Invoiced") + } + case domain.OrderStatusShipped: + if status != domain.OrderStatusDelivered { + return errors.New("invalid transition from Shipped") + } + case domain.OrderStatusDelivered: + if status != domain.OrderStatusCompleted { + return errors.New("invalid transition from Delivered") + } + case domain.OrderStatusCompleted, domain.OrderStatusCancelled: + return errors.New("order is in terminal state") + } + return s.repo.UpdateOrderStatus(ctx, id, status) } diff --git a/backend/internal/usecase/usecase_test.go b/backend/internal/usecase/usecase_test.go index b1d79d8..46cf019 100644 --- a/backend/internal/usecase/usecase_test.go +++ b/backend/internal/usecase/usecase_test.go @@ -646,6 +646,12 @@ func TestUpdateOrderStatus(t *testing.T) { if err != nil { t.Fatalf("failed to update order status: %v", err) } + + // Test invalid transition + err = svc.UpdateOrderStatus(ctx, order.ID, domain.OrderStatusDelivered) // Paid -> Delivered is invalid (skip Shipped) + if err == nil { + t.Error("expected error for invalid transition Paid -> Delivered") + } } // --- User Tests ---