Files
skeleton-api/app/api/imported_parts.py
T

155 lines
4.9 KiB
Python

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 augmented_part_sync
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"
)
archived = await augmented_part_sync.delete_imported_part_and_archive_orphans(
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),
)
if archived:
await audit_crud.log(
db,
actor=actor,
action="update",
resource_type="AugmentedPart",
resource_id=None,
description=(
f"Archived {archived} augmented part(s) left with no remaining "
f"imported parts after deleting {inventory_id!r}"
),
)