Add quote calculations query
All checks were successful
Build Docker Image / build (push) Successful in 1m53s

This commit is contained in:
Ruslan Bakiev
2026-02-06 18:57:24 +07:00
parent 443dc7fa5d
commit f5f261ff89

View File

@@ -161,6 +161,11 @@ class OfferWithRouteType(graphene.ObjectType):
routes = graphene.List(lambda: RoutePathType) 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): class Query(graphene.ObjectType):
"""Root query.""" """Root query."""
MAX_EXPANSIONS = 20000 MAX_EXPANSIONS = 20000
@@ -341,6 +346,18 @@ class Query(graphene.ObjectType):
description="Find nearest offers to coordinates with optional routes to hub", 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( nearest_suppliers = graphene.List(
SupplierType, SupplierType,
lat=graphene.Float(required=True, description="Latitude"), lat=graphene.Float(required=True, description="Latitude"),
@@ -1734,6 +1751,87 @@ class Query(graphene.ObjectType):
logger.error("Error finding nearest offers: %s", e) logger.error("Error finding nearest offers: %s", e)
return [] 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): def resolve_nearest_suppliers(self, info, lat, lon, radius=1000, product_uuid=None, limit=12):
"""Find nearest suppliers to coordinates, optionally filtered by product.""" """Find nearest suppliers to coordinates, optionally filtered by product."""
db = get_db() db = get_db()