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 }