225 lines
8.0 KiB
Python
225 lines
8.0 KiB
Python
"""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)
|