from datetime import datetime, timedelta from typing import Any, List, Optional from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.orm import Session from app import crud from app.api import deps from app.models.transaction import TransactionType from app.models.user import User from app.schemas.transaction import Transaction, TransactionCreate, TransactionUpdate router = APIRouter() @router.get("/", response_model=List[Transaction]) def read_transactions( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, item_id: Optional[int] = None, transaction_type: Optional[TransactionType] = None, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None, current_user: User = Depends(deps.get_current_user), ) -> Any: """ Retrieve transactions with optional filtering. """ if item_id: transactions = crud.transaction.get_by_item(db, item_id=item_id, skip=skip, limit=limit) elif transaction_type: transactions = crud.transaction.get_by_type(db, transaction_type=transaction_type, skip=skip, limit=limit) elif start_date and end_date: transactions = crud.transaction.get_by_date_range( db, start_date=start_date, end_date=end_date, skip=skip, limit=limit ) else: transactions = crud.transaction.get_multi(db, skip=skip, limit=limit) return transactions @router.post("/", response_model=Transaction, status_code=status.HTTP_201_CREATED) def create_transaction( *, db: Session = Depends(deps.get_db), transaction_in: TransactionCreate, current_user: User = Depends(deps.get_current_user), ) -> Any: """ Create new transaction (stock in or stock out). """ # Check if item exists item = crud.item.get(db, id=transaction_in.item_id) if not item: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Item not found", ) # For stock out transactions, check if there's enough stock if transaction_in.transaction_type == TransactionType.STOCK_OUT and item.quantity < transaction_in.quantity: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Insufficient stock. Available: {item.quantity}, Requested: {transaction_in.quantity}", ) # Create transaction with item quantity update transaction = crud.transaction.create_with_item_update( db, obj_in=transaction_in, user_id=current_user.id ) return transaction @router.get("/{transaction_id}", response_model=Transaction) def read_transaction( *, db: Session = Depends(deps.get_db), transaction_id: int, current_user: User = Depends(deps.get_current_user), ) -> Any: """ Get transaction by ID. """ transaction = crud.transaction.get(db, id=transaction_id) if not transaction: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Transaction not found", ) return transaction @router.put("/{transaction_id}", response_model=Transaction) def update_transaction( *, db: Session = Depends(deps.get_db), transaction_id: int, transaction_in: TransactionUpdate, current_user: User = Depends(deps.get_current_user), ) -> Any: """ Update a transaction's metadata (notes, reference) only. Quantity and item changes are not allowed to maintain inventory integrity. """ transaction = crud.transaction.get(db, id=transaction_id) if not transaction: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Transaction not found", ) # Only allow updating certain fields (reference, notes) # Do not allow changing quantity, unit price to maintain inventory integrity update_data = transaction_in.dict(exclude_unset=True) for field in ["quantity", "unit_price"]: if field in update_data: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Cannot update {field} after transaction is created", ) # Calculate total_price if unit_price is updated if "unit_price" in update_data: update_data["total_price"] = transaction.quantity * update_data["unit_price"] transaction = crud.transaction.update(db, db_obj=transaction, obj_in=update_data) return transaction @router.get("/by-item/{item_id}", response_model=List[Transaction]) def read_item_transactions( *, db: Session = Depends(deps.get_db), item_id: int, skip: int = 0, limit: int = 100, current_user: User = Depends(deps.get_current_user), ) -> Any: """ Get all transactions for a specific item. """ # Check if item exists item = crud.item.get(db, id=item_id) if not item: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Item not found", ) transactions = crud.transaction.get_by_item(db, item_id=item_id, skip=skip, limit=limit) return transactions @router.get("/report/daily/", response_model=List[Transaction]) def get_daily_transactions( *, db: Session = Depends(deps.get_db), date: Optional[datetime] = Query(None), current_user: User = Depends(deps.get_current_user), ) -> Any: """ Get all transactions for a specific day. """ # Default to today if no date is provided if not date: date = datetime.now() start_date = datetime(date.year, date.month, date.day, 0, 0, 0) end_date = start_date + timedelta(days=1) - timedelta(microseconds=1) transactions = crud.transaction.get_by_date_range( db, start_date=start_date, end_date=end_date ) return transactions