from typing import Optional from fastapi import APIRouter, File, Query, Request, UploadFile from app.modules.also.backend.service import also_service from app.modules.also.models.schemas import ( AlsoApproveResult, AlsoCompanyMappingUpsert, AlsoDifferenceItem, AlsoDashboardSummaryResponse, AlsoImportJobCreate, AlsoImportJobResponse, AlsoImportLinesRequest, AlsoManualCompanyMapRequest, AlsoManualProductMapRequest, AlsoProductMappingUpsert, AlsoQueueApproveRequest, AlsoQueueLineResponse, AlsoQueueProcessRequest, AlsoQueueProcessResult, ) router = APIRouter() def _user_id_from_request(request: Request) -> Optional[int]: raw_user_id = getattr(request.state, "user_id", None) if raw_user_id is None: return None try: return int(raw_user_id) except (TypeError, ValueError): return None @router.get("/also/config") async def also_config() -> dict: return also_service.get_config() @router.post("/also/import-jobs", response_model=AlsoImportJobResponse) async def create_import_job(payload: AlsoImportJobCreate, request: Request): return also_service.create_import_job(payload, _user_id_from_request(request)) @router.get("/also/import-jobs", response_model=list[AlsoImportJobResponse]) async def list_import_jobs( status: Optional[str] = Query(default=None), limit: int = Query(default=100, ge=1, le=500), ): return also_service.list_import_jobs(status=status, limit=limit) @router.get("/also/import-jobs/{job_id}") async def get_import_job(job_id: int): return also_service.get_import_job(job_id) @router.delete("/also/import-jobs/{job_id}") async def delete_import_job( job_id: int, delete_order_drafts: bool = Query(default=False), ): return also_service.delete_import_job(job_id=job_id, delete_order_drafts=delete_order_drafts) @router.post("/also/import-jobs/{job_id}/auto-map") async def auto_map_import_job(job_id: int): return also_service.auto_map_import_job(job_id=job_id) @router.post("/also/import-lines/{line_id}/map-company") async def manual_map_company_for_line( line_id: int, payload: AlsoManualCompanyMapRequest, request: Request, ): return also_service.manual_map_company_for_line( line_id=line_id, customer_id=payload.customer_id, notes=payload.notes, updated_by_user_id=_user_id_from_request(request), ) @router.post("/also/import-lines/{line_id}/map-product") async def manual_map_product_for_line( line_id: int, payload: AlsoManualProductMapRequest, request: Request, ): return also_service.manual_map_product_for_line( line_id=line_id, product_id=payload.product_id, notes=payload.notes, updated_by_user_id=_user_id_from_request(request), ) @router.post("/also/import-upload") async def upload_import_file( request: Request, file: UploadFile = File(...), ): payload = await file.read() return also_service.import_billing_upload( file_name=file.filename or "also-billing.xlsx", file_bytes=payload, imported_by_user_id=_user_id_from_request(request), source_label="Manual upload", ) @router.post("/also/import-jobs/{job_id}/lines") async def import_lines(job_id: int, payload: AlsoImportLinesRequest): return also_service.import_lines(job_id=job_id, payload=payload) @router.get("/also/import-jobs/{job_id}/lines") async def get_import_job_lines( job_id: int, status: Optional[str] = Query(default=None), limit: int = Query(default=500, ge=1, le=2000), ): return also_service.get_import_job_lines(job_id=job_id, status=status, limit=limit) @router.get("/also/queue", response_model=list[AlsoQueueLineResponse]) async def get_queue( status: Optional[str] = Query(default=None), limit: int = Query(default=200, ge=1, le=1000), ): return also_service.get_queue(status=status, limit=limit) @router.get("/also/dashboard/summary", response_model=AlsoDashboardSummaryResponse) async def get_dashboard_summary(): return also_service.get_dashboard_summary() @router.get("/also/dashboard/differences", response_model=list[AlsoDifferenceItem]) async def get_dashboard_differences(limit: int = Query(default=50, ge=1, le=200)): return also_service.get_monthly_differences(limit=limit) @router.get("/also/dashboard/status-breakdown") async def get_status_breakdown(): return also_service.get_status_breakdown() @router.get("/also/dashboard/monthly-history") async def get_monthly_history(months: int = Query(default=6, ge=1, le=24)): return also_service.get_monthly_history(months=months) @router.post("/also/queue/run-matching", response_model=AlsoQueueProcessResult) async def run_matching(payload: AlsoQueueProcessRequest): return also_service.run_matching( import_job_id=payload.import_job_id, line_ids=payload.line_ids, limit=payload.limit, ) @router.post("/also/queue/run-validation", response_model=AlsoQueueProcessResult) async def run_validation(payload: AlsoQueueProcessRequest): return also_service.run_validation( import_job_id=payload.import_job_id, line_ids=payload.line_ids, limit=payload.limit, ) @router.post("/also/queue/approve", response_model=AlsoApproveResult) async def approve_queue(payload: AlsoQueueApproveRequest, request: Request): return also_service.approve_lines_to_drafts( import_job_id=payload.import_job_id, line_ids=payload.line_ids, approved_by_user_id=_user_id_from_request(request), ) @router.put("/also/mappings/company") async def upsert_company_mapping(payload: AlsoCompanyMappingUpsert): return also_service.upsert_company_mapping(payload) @router.put("/also/mappings/product") async def upsert_product_mapping(payload: AlsoProductMappingUpsert): return also_service.upsert_product_mapping(payload)