Adding OData Import

This commit is contained in:
Ian Roddis committed 2026-06-25 15:52:21 -03:00
1 parent c8696e67b5
commit 291416544e
18 files changed
+547 -3

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
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),
)
+2 -1
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter
from app.api import audit_log, auth, iso_threads, manufacturers, part_types, roles, scope_models, sub_part_types, unc_threads, users
from app.api import audit_log, auth, imported_parts, iso_threads, manufacturers, part_types, roles, scope_models, sub_part_types, unc_threads, users
api_router = APIRouter()
api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
@@ -12,4 +12,5 @@ api_router.include_router(manufacturers.router, prefix="/manufacturers", tags=["
api_router.include_router(scope_models.router, prefix="/scope-models", tags=["scope-models"])
api_router.include_router(part_types.router, prefix="/part-types", tags=["part-types"])
api_router.include_router(sub_part_types.router, prefix="/sub-part-types", tags=["sub-part-types"])
api_router.include_router(imported_parts.router, prefix="/imported-parts", tags=["imported-parts"])
api_router.include_router(audit_log.router, prefix="/audit-log", tags=["audit-log"])