"""Endpoints del módulo Tarifario.""" from datetime import date from fastapi import APIRouter, Depends, File, Form, Query, Response, UploadFile, status from sqlalchemy.orm import Session from core.database import get_core_db from core.security import get_current_user from . import service from .dto import ( CostRequest, CostResult, ImportPreview, RateBreakDTO, RateChargeCreate, RateChargeResponse, RateChargeUpdate, RateLaneCreate, RateLaneResponse, RateSheetCreate, RateSheetResponse, RateSheetUpdate, ) router = APIRouter(prefix="/rate-sheets", tags=["Tarifario"]) def _ctx(current_user: dict): return current_user["tenant_id"], current_user.get("sub") or current_user.get("id") def _sheet_out(db: Session, tenant_id: int, sheet) -> RateSheetResponse: out = RateSheetResponse.model_validate(sheet) out.lane_count = service.lane_count(db, tenant_id, sheet.id) return out def _lane_out(db: Session, lane) -> RateLaneResponse: out = RateLaneResponse.model_validate(lane) out.breaks = [RateBreakDTO.model_validate(b) for b in service.breaks_of(db, lane.id)] return out # ---------------- Tarifarios ---------------- @router.get("", response_model=list[RateSheetResponse]) def list_sheets( company_id: int = Query(...), mode: str | None = Query(None), supplier_id: int | None = Query(None), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): tenant_id, _ = _ctx(current_user) sheets = service.list_sheets(db, tenant_id, company_id, mode=mode, supplier_id=supplier_id) return [_sheet_out(db, tenant_id, s) for s in sheets] @router.post("", response_model=RateSheetResponse, status_code=status.HTTP_201_CREATED) def create_sheet( data: RateSheetCreate, company_id: int = Query(...), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): tenant_id, user_id = _ctx(current_user) sheet = service.create_sheet(db, tenant_id, company_id, data, user_id) return _sheet_out(db, tenant_id, sheet) @router.get("/template") def download_template( mode: str = Query(..., description="aereo | maritimo_fcl | maritimo_lcl | terrestre"), company_id: int = Query(...), current_user: dict = Depends(get_current_user), ): content = service.build_template(mode) return Response( content=content, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={"Content-Disposition": f'attachment; filename="plantilla_tarifario_{mode}.xlsx"'}, ) @router.post("/import/preview", response_model=ImportPreview) async def import_preview( company_id: int = Query(...), mode: str = Form(...), file: UploadFile = File(...), current_user: dict = Depends(get_current_user), ): content = await file.read() return service.parse_excel(mode, content) @router.post("/import", response_model=RateSheetResponse, status_code=status.HTTP_201_CREATED) async def import_sheet( company_id: int = Query(...), mode: str = Form(...), name: str = Form(...), supplier_id: int | None = Form(None), currency: str = Form("USD"), valid_from: date | None = Form(None), valid_to: date | None = Form(None), default_origin: str | None = Form(None), file: UploadFile = File(...), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): tenant_id, user_id = _ctx(current_user) content = await file.read() header = RateSheetCreate( mode=mode, name=name, supplier_id=supplier_id, currency=currency, valid_from=valid_from, valid_to=valid_to, default_origin=default_origin, ) sheet = service.import_from_excel(db, tenant_id, company_id, mode, content, header, user_id) return _sheet_out(db, tenant_id, sheet) @router.get("/{sheet_id}", response_model=RateSheetResponse) def get_sheet( sheet_id: int, company_id: int = Query(...), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): tenant_id, _ = _ctx(current_user) return _sheet_out(db, tenant_id, service.get_sheet(db, tenant_id, company_id, sheet_id)) @router.patch("/{sheet_id}", response_model=RateSheetResponse) def update_sheet( sheet_id: int, data: RateSheetUpdate, company_id: int = Query(...), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): tenant_id, user_id = _ctx(current_user) return _sheet_out(db, tenant_id, service.update_sheet(db, tenant_id, company_id, sheet_id, data, user_id)) @router.delete("/{sheet_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_sheet( sheet_id: int, company_id: int = Query(...), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): tenant_id, _ = _ctx(current_user) service.delete_sheet(db, tenant_id, company_id, sheet_id) # ---------------- Rutas (lanes) ---------------- @router.get("/{sheet_id}/lanes", response_model=list[RateLaneResponse]) def list_lanes( sheet_id: int, company_id: int = Query(...), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): tenant_id, _ = _ctx(current_user) service.get_sheet(db, tenant_id, company_id, sheet_id) return [_lane_out(db, lane) for lane in service.list_lanes(db, tenant_id, sheet_id)] @router.post("/{sheet_id}/lanes", response_model=RateLaneResponse, status_code=status.HTTP_201_CREATED) def create_lane( sheet_id: int, data: RateLaneCreate, company_id: int = Query(...), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): tenant_id, _ = _ctx(current_user) lane = service.create_lane(db, tenant_id, company_id, sheet_id, data) return _lane_out(db, lane) @router.delete("/{sheet_id}/lanes/{lane_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_lane( sheet_id: int, lane_id: int, company_id: int = Query(...), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): tenant_id, _ = _ctx(current_user) service.delete_lane(db, tenant_id, sheet_id, lane_id) # ---------------- Cargos adicionales ---------------- @router.get("/{sheet_id}/charges", response_model=list[RateChargeResponse]) def list_charges( sheet_id: int, company_id: int = Query(...), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): tenant_id, _ = _ctx(current_user) service.get_sheet(db, tenant_id, company_id, sheet_id) return service.list_charges(db, tenant_id, sheet_id) @router.post("/{sheet_id}/charges", response_model=RateChargeResponse, status_code=status.HTTP_201_CREATED) def create_charge( sheet_id: int, data: RateChargeCreate, company_id: int = Query(...), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): tenant_id, _ = _ctx(current_user) return service.create_charge(db, tenant_id, company_id, sheet_id, data) @router.patch("/{sheet_id}/charges/{charge_id}", response_model=RateChargeResponse) def update_charge( sheet_id: int, charge_id: int, data: RateChargeUpdate, company_id: int = Query(...), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): tenant_id, _ = _ctx(current_user) return service.update_charge(db, tenant_id, sheet_id, charge_id, data) @router.delete("/{sheet_id}/charges/{charge_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_charge( sheet_id: int, charge_id: int, company_id: int = Query(...), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): tenant_id, _ = _ctx(current_user) service.delete_charge(db, tenant_id, sheet_id, charge_id) # ---------------- Motor de costeo ---------------- cost_router = APIRouter(tags=["Tarifario"]) @cost_router.post("/rate-quote", response_model=CostResult) def rate_quote( req: CostRequest, company_id: int = Query(...), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): """Calcula opciones de costo (por proveedor) para una ruta/carga.""" tenant_id, _ = _ctx(current_user) options = service.quote_cost(db, tenant_id, company_id, req) return CostResult(request=req, options=options) @cost_router.get("/rate-locations") def rate_locations( mode: str = Query(...), company_id: int = Query(...), current_user: dict = Depends(get_current_user), db: Session = Depends(get_core_db), ): """Orígenes/destinos cotizables (de los tarifarios activos) para alinear el cotizador.""" tenant_id, _ = _ctx(current_user) return service.lane_locations(db, tenant_id, company_id, mode)