saveinmed/backend/internal/usecase/shipping_usecase.go

112 lines
3.6 KiB
Go

package usecase
import (
"context"
"errors"
"math"
"github.com/gofrs/uuid/v5"
"github.com/saveinmed/backend-go/internal/domain"
)
// CreateShipment persists a freight label for an order if not already present.
func (s *Service) CreateShipment(ctx context.Context, shipment *domain.Shipment) error {
if _, err := s.repo.GetOrder(ctx, shipment.OrderID); err != nil {
return err
}
shipment.ID = uuid.Must(uuid.NewV7())
shipment.Status = "Label gerada"
return s.repo.CreateShipment(ctx, shipment)
}
// GetShipmentByOrderID returns freight details for an order.
func (s *Service) GetShipmentByOrderID(ctx context.Context, orderID uuid.UUID) (*domain.Shipment, error) {
return s.repo.GetShipmentByOrderID(ctx, orderID)
}
// ListShipments returns a paginated list of shipments matching the filter.
func (s *Service) ListShipments(ctx context.Context, filter domain.ShipmentFilter, page, pageSize int) (*domain.ShipmentPage, error) {
if pageSize <= 0 {
pageSize = 20
}
if page <= 0 {
page = 1
}
filter.Limit = pageSize
filter.Offset = (page - 1) * pageSize
shipments, total, err := s.repo.ListShipments(ctx, filter)
if err != nil {
return nil, err
}
return &domain.ShipmentPage{Shipments: shipments, Total: total, Page: page, PageSize: pageSize}, nil
}
// GetShippingSettings returns the seller's shipping configuration.
func (s *Service) GetShippingSettings(ctx context.Context, vendorID uuid.UUID) (*domain.ShippingSettings, error) {
return s.repo.GetShippingSettings(ctx, vendorID)
}
// UpsertShippingSettings creates or updates the seller's shipping configuration.
func (s *Service) UpsertShippingSettings(ctx context.Context, settings *domain.ShippingSettings) error {
return s.repo.UpsertShippingSettings(ctx, settings)
}
// CalculateShipping computes the shipping fee and distance from the seller to
// the buyer's address. It uses Mapbox for road distance and falls back to
// haversine. Returns (feeCents, distanceKm, error).
func (s *Service) CalculateShipping(ctx context.Context, buyerAddress *domain.Address, sellerID uuid.UUID, cartTotalCents int64) (int64, float64, error) {
settings, err := s.repo.GetShippingSettings(ctx, sellerID)
if err != nil {
return 0, 0, nil // No settings → free shipping
}
if settings == nil || !settings.Active {
return 0, 0, nil
}
if buyerAddress.Latitude == 0 || buyerAddress.Longitude == 0 {
return settings.MinFeeCents, 0, nil
}
distKm, err := s.mapbox.GetDrivingDistance(settings.Latitude, settings.Longitude, buyerAddress.Latitude, buyerAddress.Longitude)
if err != nil {
distKm = haversine(buyerAddress.Latitude, buyerAddress.Longitude, settings.Latitude, settings.Longitude)
}
if distKm > settings.MaxRadiusKm {
return 0, distKm, errors.New("address out of delivery range")
}
if settings.FreeShippingThresholdCents != nil && cartTotalCents >= *settings.FreeShippingThresholdCents {
return 0, distKm, nil
}
var fee int64
switch {
case settings.PricePerKmCents > 0:
fee = int64(distKm * float64(settings.PricePerKmCents))
case settings.MinFeeCents > 0:
fee = settings.MinFeeCents
}
if settings.MinFeeCents > 0 && fee < settings.MinFeeCents {
fee = settings.MinFeeCents
}
return fee, distKm, nil
}
// haversine calculates the great-circle distance between two points in km.
func haversine(lat1, lon1, lat2, lon2 float64) float64 {
const R = 6371
dLat := (lat2 - lat1) * (math.Pi / 180.0)
dLon := (lon2 - lon1) * (math.Pi / 180.0)
lat1Rad := lat1 * (math.Pi / 180.0)
lat2Rad := lat2 * (math.Pi / 180.0)
a := math.Sin(dLat/2)*math.Sin(dLat/2) +
math.Sin(dLon/2)*math.Sin(dLon/2)*math.Cos(lat1Rad)*math.Cos(lat2Rad)
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
return R * c
}