from typing import Optional from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from pydantic import BaseModel from database import get_db from models.user import User from models.field_mapping import ( FieldMapping, AZURE_INVOICE_FIELDS, AZURE_LINE_ITEM_FIELDS, TARGET_INVOICE_FIELDS, TARGET_LINE_ITEM_FIELDS ) from auth import get_current_user, require_cap router = APIRouter() class FieldMappingResponse(BaseModel): id: int supplier_id: int | None source_field: str target_field: str field_type: str transform: str priority: int class Config: from_attributes = True class FieldMappingCreate(BaseModel): supplier_id: Optional[int] = None source_field: str target_field: str field_type: str = "invoice" transform: str = "direct" priority: int = 0 class FieldMappingUpdate(BaseModel): source_field: Optional[str] = None target_field: Optional[str] = None field_type: Optional[str] = None transform: Optional[str] = None priority: Optional[int] = None class FieldOptionsResponse(BaseModel): azure_invoice_fields: list[str] azure_line_item_fields: list[str] target_invoice_fields: list[str] target_line_item_fields: list[str] @router.get("/options", response_model=FieldOptionsResponse) async def get_field_options(): """Get available field names for creating mappings""" return FieldOptionsResponse( azure_invoice_fields=AZURE_INVOICE_FIELDS, azure_line_item_fields=AZURE_LINE_ITEM_FIELDS, target_invoice_fields=TARGET_INVOICE_FIELDS, target_line_item_fields=TARGET_LINE_ITEM_FIELDS ) @router.get("/", response_model=list[FieldMappingResponse]) async def list_field_mappings( supplier_id: Optional[int] = None, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """List field mappings for the current kitchen, optionally filtered by supplier""" query = select(FieldMapping).where( FieldMapping.kitchen_id == current_user.kitchen_id ) if supplier_id is not None: # Get mappings for specific supplier OR global (supplier_id=null) query = query.where( (FieldMapping.supplier_id == supplier_id) | (FieldMapping.supplier_id.is_(None)) ) query = query.order_by(FieldMapping.priority.desc(), FieldMapping.id) result = await db.execute(query) mappings = result.scalars().all() return [ FieldMappingResponse( id=m.id, supplier_id=m.supplier_id, source_field=m.source_field, target_field=m.target_field, field_type=m.field_type, transform=m.transform, priority=m.priority ) for m in mappings ] @router.post("/", response_model=FieldMappingResponse) async def create_field_mapping( mapping: FieldMappingCreate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """Create a new field mapping""" # Validate target field valid_targets = ( TARGET_INVOICE_FIELDS if mapping.field_type == "invoice" else TARGET_LINE_ITEM_FIELDS ) if mapping.target_field not in valid_targets: raise HTTPException( status_code=400, detail=f"Invalid target field. Valid options: {valid_targets}" ) new_mapping = FieldMapping( kitchen_id=current_user.kitchen_id, supplier_id=mapping.supplier_id, source_field=mapping.source_field, target_field=mapping.target_field, field_type=mapping.field_type, transform=mapping.transform, priority=mapping.priority ) db.add(new_mapping) await db.commit() await db.refresh(new_mapping) return FieldMappingResponse( id=new_mapping.id, supplier_id=new_mapping.supplier_id, source_field=new_mapping.source_field, target_field=new_mapping.target_field, field_type=new_mapping.field_type, transform=new_mapping.transform, priority=new_mapping.priority ) @router.patch("/{mapping_id}", response_model=FieldMappingResponse) async def update_field_mapping( mapping_id: int, update: FieldMappingUpdate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """Update a field mapping""" result = await db.execute( select(FieldMapping).where( FieldMapping.id == mapping_id, FieldMapping.kitchen_id == current_user.kitchen_id ) ) mapping = result.scalar_one_or_none() if not mapping: raise HTTPException(status_code=404, detail="Field mapping not found") update_data = update.model_dump(exclude_unset=True) for field, value in update_data.items(): setattr(mapping, field, value) await db.commit() await db.refresh(mapping) return FieldMappingResponse( id=mapping.id, supplier_id=mapping.supplier_id, source_field=mapping.source_field, target_field=mapping.target_field, field_type=mapping.field_type, transform=mapping.transform, priority=mapping.priority ) @router.delete("/{mapping_id}") async def delete_field_mapping( mapping_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """Delete a field mapping""" result = await db.execute( select(FieldMapping).where( FieldMapping.id == mapping_id, FieldMapping.kitchen_id == current_user.kitchen_id ) ) mapping = result.scalar_one_or_none() if not mapping: raise HTTPException(status_code=404, detail="Field mapping not found") await db.delete(mapping) await db.commit() return {"message": "Field mapping deleted"}