Add quote calculations query
All checks were successful
Build Docker Image / build (push) Successful in 1m53s
All checks were successful
Build Docker Image / build (push) Successful in 1m53s
This commit is contained in:
@@ -161,6 +161,11 @@ class OfferWithRouteType(graphene.ObjectType):
|
||||
routes = graphene.List(lambda: RoutePathType)
|
||||
|
||||
|
||||
class OfferCalculationType(graphene.ObjectType):
|
||||
"""Calculation result that may include one or multiple offers."""
|
||||
offers = graphene.List(lambda: OfferWithRouteType)
|
||||
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
"""Root query."""
|
||||
MAX_EXPANSIONS = 20000
|
||||
@@ -341,6 +346,18 @@ class Query(graphene.ObjectType):
|
||||
description="Find nearest offers to coordinates with optional routes to hub",
|
||||
)
|
||||
|
||||
quote_calculations = graphene.List(
|
||||
OfferCalculationType,
|
||||
lat=graphene.Float(required=True, description="Latitude"),
|
||||
lon=graphene.Float(required=True, description="Longitude"),
|
||||
radius=graphene.Float(default_value=500, description="Search radius in km"),
|
||||
product_uuid=graphene.String(description="Filter by product UUID"),
|
||||
hub_uuid=graphene.String(description="Hub UUID - if provided, calculates routes to this hub"),
|
||||
quantity=graphene.Float(description="Requested quantity to cover (optional)"),
|
||||
limit=graphene.Int(default_value=10, description="Max calculations"),
|
||||
description="Get quote calculations (single offer or split offers)",
|
||||
)
|
||||
|
||||
nearest_suppliers = graphene.List(
|
||||
SupplierType,
|
||||
lat=graphene.Float(required=True, description="Latitude"),
|
||||
@@ -1734,6 +1751,87 @@ class Query(graphene.ObjectType):
|
||||
logger.error("Error finding nearest offers: %s", e)
|
||||
return []
|
||||
|
||||
def resolve_quote_calculations(self, info, lat, lon, radius=500, product_uuid=None, hub_uuid=None, quantity=None, limit=10):
|
||||
"""Return calculations as arrays of offers. If quantity provided, may return split offers."""
|
||||
def _parse_number(value):
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
return float(str(value).replace(',', '.'))
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
def _offer_quantity(offer):
|
||||
return _parse_number(getattr(offer, 'quantity', None))
|
||||
|
||||
def _offer_price(offer):
|
||||
return _parse_number(getattr(offer, 'price_per_unit', None))
|
||||
|
||||
def _offer_distance(offer):
|
||||
if getattr(offer, 'distance_km', None) is not None:
|
||||
return offer.distance_km or 0
|
||||
if getattr(offer, 'routes', None):
|
||||
route = offer.routes[0] if offer.routes else None
|
||||
if route and route.total_distance_km is not None:
|
||||
return route.total_distance_km or 0
|
||||
return 0
|
||||
|
||||
offers = self.resolve_nearest_offers(
|
||||
info,
|
||||
lat=lat,
|
||||
lon=lon,
|
||||
radius=radius,
|
||||
product_uuid=product_uuid,
|
||||
hub_uuid=hub_uuid,
|
||||
limit=max(limit * 4, limit),
|
||||
)
|
||||
|
||||
if not offers:
|
||||
return []
|
||||
|
||||
quantity_value = _parse_number(quantity)
|
||||
offers_sorted = sorted(
|
||||
offers,
|
||||
key=lambda o: (
|
||||
_offer_price(o) if _offer_price(o) is not None else float('inf'),
|
||||
_offer_distance(o),
|
||||
),
|
||||
)
|
||||
|
||||
calculations = []
|
||||
|
||||
# If no requested quantity, return single-offer calculations.
|
||||
if not quantity_value or quantity_value <= 0:
|
||||
return [OfferCalculationType(offers=[offer]) for offer in offers_sorted[:limit]]
|
||||
|
||||
# Try single offer that covers quantity.
|
||||
single = next(
|
||||
(offer for offer in offers_sorted if (_offer_quantity(offer) or 0) >= quantity_value),
|
||||
None,
|
||||
)
|
||||
if single:
|
||||
calculations.append(OfferCalculationType(offers=[single]))
|
||||
|
||||
# Build a split offer (two offers) if possible.
|
||||
if len(offers_sorted) >= 2:
|
||||
primary = offers_sorted[0]
|
||||
remaining = quantity_value - (_offer_quantity(primary) or 0)
|
||||
if remaining <= 0:
|
||||
secondary = offers_sorted[1]
|
||||
else:
|
||||
secondary = next(
|
||||
(offer for offer in offers_sorted[1:] if (_offer_quantity(offer) or 0) >= remaining),
|
||||
offers_sorted[1],
|
||||
)
|
||||
if secondary:
|
||||
calculations.append(OfferCalculationType(offers=[primary, secondary]))
|
||||
|
||||
# Fallback: ensure at least one calculation.
|
||||
if not calculations:
|
||||
calculations.append(OfferCalculationType(offers=[offers_sorted[0]]))
|
||||
|
||||
return calculations[:limit]
|
||||
|
||||
def resolve_nearest_suppliers(self, info, lat, lon, radius=1000, product_uuid=None, limit=12):
|
||||
"""Find nearest suppliers to coordinates, optionally filtered by product."""
|
||||
db = get_db()
|
||||
|
||||
Reference in New Issue
Block a user