"""Objednávky přepravy / svozu — tvorba (batch), stav, vyhledání, zrušení. Stejně jako zásilky jsou objednávky asynchronní: POST order/batch vrátí batchId (Location hlavička), stav zpracování se polluje přes GET order/batch/{batchId}. Typy objednávek (pole orderType v těle): - CollectionOrder — svoz z registrované adresy zákazníka (bez recipient) - TransportOrder — přeprava z libovolné adresy (sender i recipient povinné) """ import asyncio import time from typing import Any from fastapi import APIRouter, Body, Depends, Query from ..config import ( BATCH_POLL_INTERVAL_SECONDS, BATCH_WAIT_TIMEOUT_SECONDS, TRANSLITERATE_DEFAULT, ) from ..cpl_client import ( batch_id_from_location, cpl_request, ensure_success, relay_json, ) from ..credentials import Credentials, get_credentials from ..logging_config import get_logger from ..transliterate import transliterate_json log = get_logger("pplcpl.orders") router = APIRouter(prefix="/orders", tags=["orders"]) _EXAMPLE_ORDER_BODY = { "orders": [ { "orderType": "TransportOrder", "referenceId": "ORD-0001", "productType": "BUSS", "shipmentCount": 1, "sendDate": "2026-07-17", "customerReference": "Zakazka 123", "email": "odesilatel@example.com", "sender": { "name": "Firma s.r.o.", "street": "Prazska 123/4", "city": "Praha", "zipCode": "10000", "country": "CZ", "phone": "+420601123456", }, "recipient": { "name": "Jan Novak", "street": "Brnenska 10", "city": "Brno", "zipCode": "60200", "country": "CZ", "phone": "+420602123456", }, } ] } _PENDING_STATES = ("Accepted", "InProcess") def _maybe_transliterate(body: dict, transliterate: bool | None) -> dict: apply = TRANSLITERATE_DEFAULT if transliterate is None else transliterate return transliterate_json(body) if apply else body @router.post( "/batch", status_code=201, summary="Vytvoření objednávky přepravy / svozu (asynchronní)", ) async def create_order_batch( body: dict = Body(..., examples=[_EXAMPLE_ORDER_BODY]), transliterate: bool | None = Query(default=None), creds: Credentials = Depends(get_credentials), ): """`POST order/batch` — vrací batchId z Location hlavičky (max 100 objednávek).""" resp = await cpl_request( creds, "POST", "/order/batch", json_body=_maybe_transliterate(body, transliterate) ) ensure_success(resp) return { "batchId": batch_id_from_location(resp), "location": resp.headers.get("location"), "correlationId": resp.headers.get("x-correlation-id"), } @router.get("/batch/{batch_id}", summary="Stav zpracování objednávky (batch)") async def get_order_batch_status( batch_id: str, creds: Credentials = Depends(get_credentials), ): """`GET order/batch/{batchId}` — importState: Accepted/InProcess/Complete/Error.""" resp = await cpl_request(creds, "GET", f"/order/batch/{batch_id}") return relay_json(resp) @router.post( "/create-and-wait", summary="Vytvoření objednávky a počkání na zpracování (synchronní obálka)", ) async def create_order_and_wait( body: dict = Body(..., examples=[_EXAMPLE_ORDER_BODY]), timeout_seconds: float = Query(default=BATCH_WAIT_TIMEOUT_SECONDS, ge=1, le=120), transliterate: bool | None = Query(default=None), creds: Credentials = Depends(get_credentials), ): """Convenience: POST order/batch + polling stavu v jednom requestu. `completed=false` znamená timeout — zpracování v PPL běží dál.""" create_resp = await cpl_request( creds, "POST", "/order/batch", json_body=_maybe_transliterate(body, transliterate) ) ensure_success(create_resp) batch_id = batch_id_from_location(create_resp) deadline = time.monotonic() + timeout_seconds status_data: dict = {} completed = False while True: status_resp = await cpl_request(creds, "GET", f"/order/batch/{batch_id}") ensure_success(status_resp) status_data = status_resp.json() or {} items = status_data.get("items") or [] pending = [i for i in items if i.get("importState") in _PENDING_STATES] if items and not pending: completed = True break if time.monotonic() >= deadline: log.warning( "Order batch %s nebyl zpracován do %ss, vracím completed=false.", batch_id, timeout_seconds, ) break await asyncio.sleep(BATCH_POLL_INTERVAL_SECONDS) return {"batchId": batch_id, "completed": completed, **status_data} @router.get("", summary="Vyhledání objednávek přepravy") async def find_orders( shipment_numbers: list[str] | None = Query(default=None, alias="shipmentNumbers"), customer_references: list[str] | None = Query( default=None, alias="customerReferences" ), order_references: list[str] | None = Query( default=None, alias="orderReferences", description="referenceId hodnoty z POST order/batch.", ), order_numbers: list[str] | None = Query(default=None, alias="orderNumbers"), order_ids: list[int] | None = Query(default=None, alias="orderIds"), date_from: str | None = Query(default=None, alias="dateFrom"), date_to: str | None = Query(default=None, alias="dateTo"), send_date: str | None = Query(default=None, alias="sendDate"), product_type: str | None = Query(default=None, alias="productType"), order_states: str | None = Query( default=None, alias="orderStates", description="None, Created, PickedUp, NotPickedUp, Canceled.", ), order_type: str | None = Query( default=None, alias="orderType", description="CollectionOrder / TransportOrder." ), limit: int = Query(default=100, ge=1, le=1000), offset: int = Query(default=0, ge=0), creds: Credentials = Depends(get_credentials), ): """`GET order` — informace o objednávkách vč. stavu a přidělených čísel zásilek.""" params: dict[str, Any] = {"Limit": limit, "Offset": offset} if shipment_numbers: params["ShipmentNumbers"] = shipment_numbers if customer_references: params["CustomerReferences"] = customer_references if order_references: params["OrderReferences"] = order_references if order_numbers: params["OrderNumbers"] = order_numbers if order_ids: params["OrderIds"] = order_ids if date_from: params["DateFrom"] = date_from if date_to: params["DateTo"] = date_to if send_date: params["SendDate"] = send_date if product_type: params["ProductType"] = product_type if order_states: params["OrderStates"] = order_states if order_type: params["OrderType"] = order_type resp = await cpl_request(creds, "GET", "/order", params=params) return relay_json(resp) @router.post("/cancel", summary="Zrušení objednávky svozu / přepravy") async def cancel_order( customer_reference: str | None = Query( default=None, alias="customerReference", description="Reference odesílatele." ), order_reference: str | None = Query( default=None, alias="orderReference", description="Reference objednávky." ), body: dict = Body(default={}, examples=[{"note": "Zrušeno zákazníkem"}]), creds: Credentials = Depends(get_credentials), ): """`POST order/cancel` — identifikace přes customerReference nebo orderReference.""" params: dict[str, Any] = {} if customer_reference: params["customerReference"] = customer_reference if order_reference: params["orderReference"] = order_reference resp = await cpl_request( creds, "POST", "/order/cancel", params=params or None, json_body=body or {} ) return relay_json(resp)