saveinmed/backend/internal/usecase/cart_usecase.go
caio-machado-dev bf85072bff chore: remove legacy services and restructure monorepo
- remove backend-old (Medusa), saveinmed-frontend (Next.js/Appwrite) and marketplace dirs
- split Go usecases by domain and move notifications/payments to infrastructure
- reorganize frontend pages into auth, dashboard and marketplace modules
- add Makefile, docker-compose.yml and architecture docs
2026-02-25 16:51:34 -03:00

154 lines
4.5 KiB
Go

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
}