package usecase import ( "context" "errors" "fmt" "time" "github.com/gofrs/uuid/v5" "github.com/saveinmed/backend-go/internal/domain" ) // AddItemToCart validates stock, applies the buyer fee and persists the cart item, // then returns the refreshed cart summary. func (s *Service) AddItemToCart(ctx context.Context, buyerID, productID uuid.UUID, quantity int64) (*domain.CartSummary, error) { if quantity <= 0 { return nil, errors.New("quantity must be greater than zero") } product, err := s.repo.GetProduct(ctx, productID) if err != nil { return nil, err } unitPrice := product.PriceCents if s.buyerFeeRate > 0 { unitPrice = int64(float64(unitPrice) * (1 + s.buyerFeeRate)) } _, err = s.repo.AddCartItem(ctx, &domain.CartItem{ ID: uuid.Must(uuid.NewV7()), BuyerID: buyerID, ProductID: productID, Quantity: quantity, UnitCents: unitPrice, }) if err != nil { return nil, err } return s.cartSummary(ctx, buyerID) } // ReplaceCart atomically replaces all cart items for a buyer with a new set. func (s *Service) ReplaceCart(ctx context.Context, buyerID uuid.UUID, reqItems []domain.CartItem) (*domain.CartSummary, error) { var validItems []domain.CartItem for _, item := range reqItems { product, err := s.repo.GetProduct(ctx, item.ProductID) if err != nil { continue // Skip unavailable products silently } unitPrice := product.PriceCents if s.buyerFeeRate > 0 { unitPrice = int64(float64(unitPrice) * (1 + s.buyerFeeRate)) } validItems = append(validItems, domain.CartItem{ ID: uuid.Must(uuid.NewV7()), BuyerID: buyerID, ProductID: item.ProductID, Quantity: item.Quantity, UnitCents: unitPrice, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), }) } if err := s.repo.ReplaceCart(ctx, buyerID, validItems); err != nil { return nil, err } return s.cartSummary(ctx, buyerID) } // ListCart returns the current cart with B2B discounts applied. func (s *Service) ListCart(ctx context.Context, buyerID uuid.UUID) (*domain.CartSummary, error) { return s.cartSummary(ctx, buyerID) } // RemoveCartItem deletes a cart row by its ID and returns the refreshed summary. func (s *Service) RemoveCartItem(ctx context.Context, buyerID, cartItemID uuid.UUID) (*domain.CartSummary, error) { if err := s.repo.DeleteCartItem(ctx, cartItemID, buyerID); err != nil { return nil, err } return s.cartSummary(ctx, buyerID) } // RemoveCartItemByProduct removes all cart rows for a given product and returns the refreshed summary. func (s *Service) RemoveCartItemByProduct(ctx context.Context, buyerID, productID uuid.UUID) (*domain.CartSummary, error) { if err := s.repo.DeleteCartItemByProduct(ctx, buyerID, productID); err != nil { return nil, err } return s.cartSummary(ctx, buyerID) } // ClearCart removes all cart items for a buyer and returns the empty summary. func (s *Service) ClearCart(ctx context.Context, buyerID uuid.UUID) (*domain.CartSummary, error) { if err := s.repo.ClearCart(ctx, buyerID); err != nil { return nil, err } return s.cartSummary(ctx, buyerID) } // Reorder re-adds items from a past order into the cart, warning for unavailable items. func (s *Service) Reorder(ctx context.Context, buyerID, orderID uuid.UUID) (*domain.CartSummary, []string, error) { order, err := s.repo.GetOrder(ctx, orderID) if err != nil { return nil, nil, err } if order.BuyerID != buyerID { return nil, nil, errors.New("order does not belong to buyer") } var warnings []string for _, item := range order.Items { if _, err := s.AddItemToCart(ctx, buyerID, item.ProductID, int64(item.Quantity)); err != nil { warnings = append(warnings, fmt.Sprintf("Item %s not added: %v", item.ProductID, err)) } } summary, err := s.cartSummary(ctx, buyerID) return summary, warnings, err } // cartSummary assembles the CartSummary DTO, applying volume discounts. func (s *Service) cartSummary(ctx context.Context, buyerID uuid.UUID) (*domain.CartSummary, error) { items, err := s.repo.ListCartItems(ctx, buyerID) if err != nil { return nil, err } var subtotal int64 for i := range items { subtotal += items[i].UnitCents * items[i].Quantity } summary := &domain.CartSummary{ ID: buyerID, Items: items, SubtotalCents: subtotal, } // Apply 5% B2B volume discount for baskets ≥ R$1.000,00 if subtotal >= 100000 { summary.DiscountCents = int64(float64(subtotal) * 0.05) summary.DiscountReason = "5% B2B volume discount" } summary.TotalCents = subtotal - summary.DiscountCents return summary, nil }