- 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
109 lines
3.5 KiB
Go
109 lines
3.5 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
|
|
}
|
|
|
|
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
|
|
}
|