from fastapi import APIRouter, Depends, Query from typing import Optional from sqlalchemy.ext.asyncio import AsyncSession from auth.models import TokenPayload from auth.dependencies import require_permission from crm.models import OrderCreate, OrderUpdate, OrderInDB, OrderListResponse from crm import service from database.postgres import get_pg_session from shared.audit import log_action router = APIRouter(prefix="/api/crm/customers/{customer_id}/orders", tags=["crm-orders"]) @router.get("", response_model=OrderListResponse) def list_orders( customer_id: str, _user: TokenPayload = Depends(require_permission("crm", "view")), ): orders = service.list_orders(customer_id) return OrderListResponse(orders=orders, total=len(orders)) # IMPORTANT: specific sub-paths must come before /{order_id} @router.get("/next-order-number") def get_next_order_number( customer_id: str, _user: TokenPayload = Depends(require_permission("crm", "view")), ): """Return the next globally unique order number (ORD-DDMMYY-NNN across all customers).""" return {"order_number": service._generate_order_number(customer_id)} @router.post("/init-negotiations", response_model=OrderInDB, status_code=201) async def init_negotiations( customer_id: str, body: dict, _user: TokenPayload = Depends(require_permission("crm", "edit")), db: AsyncSession = Depends(get_pg_session), ): order = service.init_negotiations( customer_id=customer_id, title=body.get("title", ""), note=body.get("note", ""), date=body.get("date"), created_by=body.get("created_by", ""), ) await log_action(db, _user.sub, _user.name or _user.email, "CREATE", "order", order.id, order.order_number or order.id, meta={"action_detail": "negotiations_started"}) return order @router.post("", response_model=OrderInDB, status_code=201) async def create_order( customer_id: str, body: OrderCreate, _user: TokenPayload = Depends(require_permission("crm", "edit")), db: AsyncSession = Depends(get_pg_session), ): order = service.create_order(customer_id, body) await log_action(db, _user.sub, _user.name or _user.email, "CREATE", "order", order.id, order.order_number or order.id) return order @router.get("/{order_id}", response_model=OrderInDB) def get_order( customer_id: str, order_id: str, _user: TokenPayload = Depends(require_permission("crm", "view")), ): return service.get_order(customer_id, order_id) @router.patch("/{order_id}", response_model=OrderInDB) async def update_order( customer_id: str, order_id: str, body: OrderUpdate, _user: TokenPayload = Depends(require_permission("crm", "edit")), db: AsyncSession = Depends(get_pg_session), ): old = service.get_order(customer_id, order_id) order = service.update_order(customer_id, order_id, body) action = "STATUS_CHANGE" if body.status is not None else "UPDATE" _SKIP = {"updated_at", "id", "customer_id", "items", "timeline", "discount", "shipping", "payment_status"} changes = { k: {"old": getattr(old, k, None), "new": getattr(order, k, None)} for k in body.model_fields_set if k not in _SKIP and getattr(old, k, None) != getattr(order, k, None) } await log_action(db, _user.sub, _user.name or _user.email, action, "order", order_id, order.order_number or order_id, changes=changes or None) return order @router.delete("/{order_id}", status_code=204) async def delete_order( customer_id: str, order_id: str, _user: TokenPayload = Depends(require_permission("crm", "edit")), db: AsyncSession = Depends(get_pg_session), ): service.delete_order(customer_id, order_id) await log_action(db, _user.sub, _user.name or _user.email, "DELETE", "order", order_id, order_id) @router.post("/{order_id}/timeline", response_model=OrderInDB) def append_timeline_event( customer_id: str, order_id: str, body: dict, _user: TokenPayload = Depends(require_permission("crm", "edit")), ): return service.append_timeline_event(customer_id, order_id, body) @router.patch("/{order_id}/timeline/{index}", response_model=OrderInDB) def update_timeline_event( customer_id: str, order_id: str, index: int, body: dict, _user: TokenPayload = Depends(require_permission("crm", "edit")), ): return service.update_timeline_event(customer_id, order_id, index, body) @router.delete("/{order_id}/timeline/{index}", response_model=OrderInDB) def delete_timeline_event( customer_id: str, order_id: str, index: int, _user: TokenPayload = Depends(require_permission("crm", "edit")), ): return service.delete_timeline_event(customer_id, order_id, index) @router.patch("/{order_id}/payment-status", response_model=OrderInDB) def update_payment_status( customer_id: str, order_id: str, body: dict, _user: TokenPayload = Depends(require_permission("crm", "edit")), ): return service.update_order_payment_status(customer_id, order_id, body) # ── Global order list (collection group) ───────────────────────────────────── # Separate router registered at /api/crm/orders for the global OrderList page global_router = APIRouter(prefix="/api/crm/orders", tags=["crm-orders-global"]) @global_router.get("") def list_all_orders( status: Optional[str] = Query(None), _user: TokenPayload = Depends(require_permission("crm", "view")), ): orders = service.list_all_orders(status=status) # Enrich with customer names customer_ids = list({o.customer_id for o in orders if o.customer_id}) customer_names: dict[str, str] = {} for cid in customer_ids: try: c = service.get_customer(cid) parts = [c.name, c.organization] if c.organization else [c.name] customer_names[cid] = " / ".join(filter(None, parts)) except Exception: pass enriched = [] for o in orders: d = o.model_dump() d["customer_name"] = customer_names.get(o.customer_id) enriched.append(d) return {"orders": enriched, "total": len(enriched)}