from fastapi import APIRouter, Depends, HTTPException, status from typing import List from sqlalchemy.orm import Session from .. import models, schemas, auth from ..database import get_db import json router = APIRouter( prefix="/operations", tags=["Operations"] ) @router.post("/check-in", response_model=schemas.Item) def check_in_item( op: schemas.OperationCreate, db: Session = Depends(get_db), current_user: auth.TokenData = Depends(auth.get_current_user) ): """[C-01] Check-in item — only for authenticated users. [M-02] user_id from token.""" if op.quantity <= 0: raise HTTPException(status_code=400, detail="Quantity must be greater than zero") item = db.query(models.Item).filter(models.Item.barcode == op.barcode).first() if not item: raise HTTPException(status_code=404, detail="Item not found. Register item first.") # Update quantity item.quantity += op.quantity # Create Mandatory Audit Log — [M-02] user_id from token item_snapshot = { "barcode": item.barcode, "name": item.name, "category": item.category, "part_number": item.part_number } audit = models.AuditLog( user_id=current_user.sub, action="CHECK_IN", target_item_id=item.id, target_item_name=item.name, target_item_pn=item.part_number, target_item_barcode=item.barcode, target_snapshot=json.dumps(item_snapshot), quantity_change=op.quantity ) db.add(audit) db.commit() db.refresh(item) return item @router.post("/check-out", response_model=schemas.Item) def check_out_item( op: schemas.OperationCreate, db: Session = Depends(get_db), current_user: auth.TokenData = Depends(auth.get_current_user) ): """[C-01] Check-out item — only for authenticated users.""" if op.quantity <= 0: raise HTTPException(status_code=400, detail="Quantity must be greater than zero") item = db.query(models.Item).filter(models.Item.barcode == op.barcode).first() if not item: raise HTTPException(status_code=404, detail="Item not found") if item.quantity < op.quantity: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Insufficient stock") # Update quantity item.quantity -= op.quantity # Create Mandatory Audit Log item_snapshot = { "barcode": item.barcode, "name": item.name, "category": item.category, "part_number": item.part_number } audit = models.AuditLog( user_id=current_user.sub, action="CHECK_OUT", target_item_id=item.id, target_item_name=item.name, target_item_pn=item.part_number, target_item_barcode=item.barcode, target_snapshot=json.dumps(item_snapshot), quantity_change=-op.quantity ) db.add(audit) db.commit() db.refresh(item) return item @router.post("/trash", response_model=schemas.Item) def trash_item( op: schemas.TrashOperationCreate, db: Session = Depends(get_db), current_user: auth.TokenData = Depends(auth.get_current_user) ): """[C-01] Trash item — only for authenticated users.""" if op.quantity <= 0: raise HTTPException(status_code=400, detail="Quantity must be greater than zero") item = db.query(models.Item).filter(models.Item.barcode == op.barcode).first() if not item: raise HTTPException(status_code=404, detail="Item not found") if item.quantity < op.quantity: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Insufficient stock to trash") # Update quantity item.quantity -= op.quantity # Create Mandatory Audit Log with TRASH action and reason in details item_snapshot = { "barcode": item.barcode, "name": item.name, "category": item.category, "part_number": item.part_number } audit = models.AuditLog( user_id=current_user.sub, action="TRASH", target_item_id=item.id, target_item_name=item.name, target_item_pn=item.part_number, target_item_barcode=item.barcode, target_snapshot=json.dumps(item_snapshot), quantity_change=-op.quantity, details=op.reason ) db.add(audit) db.commit() db.refresh(item) return item @router.post("/bulk-check-out") def bulk_check_out( bulk_op: schemas.BulkOperationCreate, db: Session = Depends(get_db), current_user: auth.TokenData = Depends(auth.get_current_user) ): """[C-01] Bulk check-out — only for authenticated users.""" results = {"success": [], "errors": []} for op in bulk_op.items: try: item = db.query(models.Item).filter(models.Item.barcode == op.barcode).first() if not item: results["errors"].append({"barcode": op.barcode, "error": "Not found"}) continue if item.quantity < op.quantity: results["errors"].append({"barcode": op.barcode, "error": f"Insufficient stock (Available: {item.quantity})"}) continue # Update quantity item.quantity -= op.quantity # Log individual audit for this item item_snapshot = { "barcode": item.barcode, "name": item.name, "category": item.category, "part_number": item.part_number } audit = models.AuditLog( user_id=current_user.sub, action="BULK_CHECK_OUT", target_item_id=item.id, target_item_name=item.name, target_item_pn=item.part_number, target_item_barcode=item.barcode, target_snapshot=json.dumps(item_snapshot), quantity_change=-op.quantity ) db.add(audit) results["success"].append({"barcode": op.barcode, "new_quantity": item.quantity}) except Exception as e: results["errors"].append({"barcode": op.barcode, "error": str(e)}) db.commit() return results @router.post("/bulk-sync") def bulk_sync( payload: schemas.SyncPayload, db: Session = Depends(get_db), current_user: auth.TokenData = Depends(auth.get_current_user) ): """[C-01] Bulk sync offline operations — only for authenticated users.""" results = {"success": [], "errors": []} for op in payload.operations: try: # DEDUPLICATION CHECK: If this UUID already exists, skip it if op.uuid: existing = db.query(models.AuditLog).filter(models.AuditLog.uuid == op.uuid).first() if existing: results["success"].append({"barcode": op.barcode, "type": op.type, "note": "Already synced"}) continue item = db.query(models.Item).filter(models.Item.barcode == op.barcode).first() if not item: results["errors"].append({"barcode": op.barcode, "error": "Item not found"}) continue if op.type == "CHECK_IN": item.quantity += op.quantity change = op.quantity elif op.type == "CHECK_OUT" or op.type == "TRASH": if item.quantity < op.quantity: results["errors"].append({"barcode": op.barcode, "error": f"Insufficient stock (Available: {item.quantity})"}) continue item.quantity -= op.quantity change = -op.quantity else: results["errors"].append({"barcode": op.barcode, "error": f"Invalid operation type: {op.type}"}) continue # Log audit with original offline timestamp and UUID item_snapshot = { "barcode": item.barcode, "name": item.name, "category": item.category, "part_number": item.part_number } audit = models.AuditLog( user_id=current_user.sub, action=op.type, target_item_id=item.id, target_item_name=item.name, target_item_pn=item.part_number, target_item_barcode=item.barcode, target_snapshot=json.dumps(item_snapshot), quantity_change=change, timestamp=op.timestamp, uuid=op.uuid, details="Offline Synchronization" ) db.add(audit) results["success"].append({"barcode": op.barcode, "type": op.type}) except Exception as e: results["errors"].append({"barcode": op.barcode, "error": str(e)}) db.commit() return results @router.get("/logs", response_model=List[schemas.AuditLogResponse]) def get_logs( limit: int = 50, db: Session = Depends(get_db), current_user: auth.TokenData = Depends(auth.get_current_user) ): """[C-01] Audit logs list — only for authenticated users.""" # Join with User to get the username directly logs_with_users = db.query( models.AuditLog.id, models.AuditLog.timestamp, models.AuditLog.user_id, models.User.username, models.AuditLog.action, models.AuditLog.target_item_id, models.AuditLog.target_item_name, models.AuditLog.target_item_pn, models.AuditLog.target_item_barcode, models.AuditLog.target_snapshot, models.AuditLog.quantity_change, models.AuditLog.details ).join(models.User, models.AuditLog.user_id == models.User.id).order_by(models.AuditLog.timestamp.desc()).limit(limit).all() # Mapper to dictionary for Pydantic return [ { "id": l.id, "timestamp": l.timestamp, "user_id": l.user_id, "username": l.username, "action": l.action, "target_item_id": l.target_item_id, "target_item_name": l.target_item_name, "target_item_pn": l.target_item_pn, "target_item_barcode": l.target_item_barcode, "target_snapshot": l.target_snapshot, "quantity_change": l.quantity_change, "details": l.details } for l in logs_with_users ]