- 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
154 lines
4.5 KiB
Go
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
|
|
}
|