Restore api/migrations/models/services source files accidentally removed in gitignore commit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c8fad1cf36
commit
907243c2cd
31 changed files with 29855 additions and 0 deletions
205
backend/api/field_mappings.py
Normal file
205
backend/api/field_mappings.py
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
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"}
|
||||
Loading…
Add table
Add a link
Reference in a new issue