Lets an employee walk the premises, scan each object's QR to confirm it's present and record where it was found (defaulting to the last picked location), and track found/not-found progress for the walk. Scanning a box's QR shows its contents read-only without affecting the found/not-found counts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
79 lines
2.7 KiB
Python
79 lines
2.7 KiB
Python
import uuid
|
|
from datetime import datetime, timezone
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
from app.models.object import InventoryObject
|
|
from app.schemas.object import BoxRef, ObjectCreate, ObjectRead, ObjectUpdate
|
|
from app.services.photos import build_photo_read, delete_photo
|
|
from app.services.qrcodes import object_public_url
|
|
|
|
|
|
def build_object_read(obj: InventoryObject) -> ObjectRead:
|
|
return ObjectRead(
|
|
id=obj.id,
|
|
inventory_number=obj.inventory_number,
|
|
name=obj.name,
|
|
description=obj.description,
|
|
box_id=obj.box_id,
|
|
box=BoxRef.model_validate(obj.box) if obj.box else None,
|
|
created_at=obj.created_at,
|
|
updated_at=obj.updated_at,
|
|
last_seen_at=obj.last_seen_at,
|
|
qr_code_url=object_public_url(obj.id),
|
|
photos=[build_photo_read(p) for p in obj.photos],
|
|
)
|
|
|
|
|
|
def _select_with_relations():
|
|
return select(InventoryObject).options(
|
|
selectinload(InventoryObject.photos), selectinload(InventoryObject.box)
|
|
)
|
|
|
|
|
|
async def list_objects(db: AsyncSession, search: str | None = None) -> list[InventoryObject]:
|
|
stmt = _select_with_relations().order_by(InventoryObject.created_at.desc())
|
|
if search:
|
|
like = f"%{search}%"
|
|
stmt = stmt.where(
|
|
(InventoryObject.inventory_number.ilike(like)) | (InventoryObject.name.ilike(like))
|
|
)
|
|
result = await db.execute(stmt)
|
|
return list(result.scalars().all())
|
|
|
|
|
|
async def get_object(db: AsyncSession, object_id: uuid.UUID) -> InventoryObject | None:
|
|
stmt = _select_with_relations().where(InventoryObject.id == object_id)
|
|
result = await db.execute(stmt)
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
async def create_object(db: AsyncSession, data: ObjectCreate) -> InventoryObject:
|
|
obj = InventoryObject(**data.model_dump())
|
|
db.add(obj)
|
|
await db.commit()
|
|
return await get_object(db, obj.id)
|
|
|
|
|
|
async def update_object(db: AsyncSession, obj: InventoryObject, data: ObjectUpdate) -> InventoryObject:
|
|
for field, value in data.model_dump(exclude_unset=True).items():
|
|
setattr(obj, field, value)
|
|
await db.commit()
|
|
return await get_object(db, obj.id)
|
|
|
|
|
|
async def mark_object_found(db: AsyncSession, obj: InventoryObject, box_id: uuid.UUID | None) -> InventoryObject:
|
|
obj.box_id = box_id
|
|
obj.last_seen_at = datetime.now(timezone.utc)
|
|
await db.commit()
|
|
return await get_object(db, obj.id)
|
|
|
|
|
|
async def delete_object(db: AsyncSession, minio_client, obj: InventoryObject) -> None:
|
|
for photo in list(obj.photos):
|
|
await delete_photo(db, minio_client, photo)
|
|
await db.delete(obj)
|
|
await db.commit()
|