from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_db from app.crud import audit as audit_crud from app.crud import imported_part as imported_part_crud from app.crud import imported_part_config as config_crud from app.deps import require_permission from app.models.user import User from app.schemas.imported_part import ( ImportedPartConfigRead, ImportedPartConfigUpdate, ImportedPartRead, SyncResult, ) from app.services import odata as odata_service router = APIRouter() @router.get("", response_model=list[ImportedPartRead]) async def list_imported_parts( skip: int = 0, limit: int = 500, db: AsyncSession = Depends(get_db), _: User = Depends(require_permission("imported_parts:read")), ): return await imported_part_crud.list_all(db, skip=skip, limit=limit) @router.get("/config", response_model=ImportedPartConfigRead) async def get_config( db: AsyncSession = Depends(get_db), _: User = Depends(require_permission("imported_parts:write")), ): config = await config_crud.get(db) if config is None: return ImportedPartConfigRead(odata_url=None, username=None, has_password=False) return ImportedPartConfigRead( odata_url=config.odata_url, username=config.username, has_password=bool(config.password), ) @router.patch("/config", response_model=ImportedPartConfigRead) async def update_config( payload: ImportedPartConfigUpdate, db: AsyncSession = Depends(get_db), actor: User = Depends(require_permission("imported_parts:write")), ): current = await config_crud.get(db) # Keep existing password if not provided in the update new_password = payload.password if payload.password is not None else (current.password if current else None) config = await config_crud.upsert( db, odata_url=payload.odata_url, username=payload.username, password=new_password, ) await audit_crud.log( db, actor=actor, action="update", resource_type="ImportedPartConfig", resource_id=1, description="Updated ImportedPart OData configuration", ) return ImportedPartConfigRead( odata_url=config.odata_url, username=config.username, has_password=bool(config.password), ) @router.post("/refresh", response_model=SyncResult) async def refresh_imported_parts( db: AsyncSession = Depends(get_db), actor: User = Depends(require_permission("imported_parts:write")), ): config = await config_crud.get(db) if not config or not config.odata_url: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="OData URL is not configured.", ) records = await odata_service.fetch(config.odata_url, config.username, config.password) created, updated = await imported_part_crud.upsert_bulk(db, records) await audit_crud.log( db, actor=actor, action="update", resource_type="ImportedPart", resource_id=None, description=f"OData sync: {created} created, {updated} updated ({len(records)} total records)", ) return SyncResult(created=created, updated=updated, total=len(records)) @router.get("/{inventory_id}", response_model=ImportedPartRead) async def get_imported_part( inventory_id: str, db: AsyncSession = Depends(get_db), _: User = Depends(require_permission("imported_parts:read")), ): part = await imported_part_crud.get(db, inventory_id) if part is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Imported part not found") return part @router.delete("/{inventory_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_imported_part( inventory_id: str, db: AsyncSession = Depends(get_db), actor: User = Depends(require_permission("imported_parts:delete")), ): part = await imported_part_crud.get(db, inventory_id) if part is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Imported part not found") await imported_part_crud.delete(db, part) await audit_crud.log( db, actor=actor, action="delete", resource_type="ImportedPart", resource_id=None, description=audit_crud.describe_delete("ImportedPart", 0, inventory_id), )