ai_giga_tcg/app/routes/inventory_management_routes.py
2025-05-30 17:31:59 -04:00

488 lines
21 KiB
Python

from fastapi import APIRouter, Depends, HTTPException
from datetime import datetime
from sqlalchemy.orm import Session
from sqlalchemy import and_, func
from app.db.database import get_db
from app.services.service_manager import ServiceManager
from app.contexts.inventory_item import InventoryItemContextFactory
from app.schemas.transaction import PurchaseTransactionCreate, SaleTransactionCreate, SealedExpectedValueCreate, GetAllTransactionsResponse, TransactionResponse, TransactionItemResponse, InventoryItemResponse, TCGPlayerProductResponse, OpenEventResponse, OpenEventCreate, OpenEventResultingItemsResponse, OpenEventsForInventoryItemResponse
from app.models.inventory_management import Transaction
from app.models.tcgplayer_products import TCGPlayerProduct
from typing import List
from fastapi.responses import StreamingResponse
router = APIRouter(prefix="/inventory")
service_manager = ServiceManager()
# Sealed Routes
@router.get("/sealed/boxes")
async def get_sealed_boxes(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)
):
sealed_box_service = service_manager.get_service("sealed_box")
raw_boxes = sealed_box_service.get_all(db, skip=skip, limit=limit)
boxes = []
for box in raw_boxes:
inventory_item = InventoryItemContextFactory(db).get_context(box.inventory_item)
boxes.append(inventory_item)
return boxes
@router.get("/sealed/boxes/{sealed_box_id}")
async def get_sealed_box(
sealed_box_id: int,
db: Session = Depends(get_db)
):
sealed_box_service = service_manager.get_service("sealed_box")
sealed_box = sealed_box_service.get(db, sealed_box_id)
return InventoryItemContextFactory(db).get_context(sealed_box.inventory_item)
@router.post("/sealed/boxes")
async def create_sealed_box(
product_id: int,
cost_basis: float,
case_id: int = None,
db: Session = Depends(get_db)
):
try:
sealed_box_service = service_manager.get_service("sealed_box")
sealed_box = await sealed_box_service.create_sealed_box(db, product_id, cost_basis, case_id)
return InventoryItemContextFactory(db).get_context(sealed_box.inventory_item)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/sealed/boxes/{sealed_box_id}/open")
async def open_sealed_box(
sealed_box_id: int,
manabox_file_upload_ids: List[int],
db: Session = Depends(get_db)
):
try:
inventory_service = service_manager.get_service("inventory")
sealed_box_service = service_manager.get_service("sealed_box")
file_service = service_manager.get_service("file")
sealed_box = sealed_box_service.get(db, sealed_box_id)
manabox_file_uploads = []
for manabox_file_upload_id in manabox_file_upload_ids:
manabox_file_upload = file_service.get_file(db, manabox_file_upload_id)
manabox_file_uploads.append(manabox_file_upload)
success = await inventory_service.process_manabox_import_staging(db, manabox_file_uploads, sealed_box)
if not success:
raise HTTPException(status_code=400, detail="Failed to process Manabox import staging")
return {"message": "Manabox import staging processed successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/sealed/cases")
async def get_sealed_cases(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)
):
sealed_case_service = service_manager.get_service("sealed_case")
raw_cases = sealed_case_service.get_all(db, skip=skip, limit=limit)
cases = []
for case in raw_cases:
inventory_item = InventoryItemContextFactory(db).get_context(case.inventory_item)
cases.append(inventory_item)
return cases
@router.get("/sealed/cases/{sealed_case_id}")
async def get_sealed_case(
sealed_case_id: int,
db: Session = Depends(get_db)
):
sealed_case_service = service_manager.get_service("sealed_case")
sealed_case = sealed_case_service.get(db, sealed_case_id)
return InventoryItemContextFactory(db).get_context(sealed_case.inventory_item)
@router.post("/sealed/cases")
async def create_sealed_case(
product_id: int,
cost_basis: float,
num_boxes: int,
db: Session = Depends(get_db)
):
try:
sealed_case_service = service_manager.get_service("sealed_case")
sealed_case = await sealed_case_service.create_sealed_case(db, product_id, cost_basis, num_boxes)
return InventoryItemContextFactory(db).get_context(sealed_case.inventory_item)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/sealed/cases/{sealed_case_id}/open")
async def open_sealed_case(
sealed_case_id: int,
db: Session = Depends(get_db)
):
try:
sealed_case_service = service_manager.get_service("sealed_case")
sealed_case = sealed_case_service.get(db, sealed_case_id)
await sealed_case_service.open_sealed_case(db, sealed_case)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# Transaction Routes
@router.post("/transactions/purchase")
async def create_purchase_transaction(
transaction_data: PurchaseTransactionCreate,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
transaction = await inventory_service.create_purchase_transaction(db, transaction_data)
return transaction
@router.post("/transactions/sale")
async def create_sale_transaction(
transaction_data: SaleTransactionCreate,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
transaction = await inventory_service.create_sale_transaction(db, transaction_data)
return transaction
@router.post("/customers")
async def create_customer(
customer_name: str,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
customer = await inventory_service.create_customer(db, customer_name)
return customer
@router.post("/vendors")
async def create_vendor(
vendor_name: str,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
vendor = await inventory_service.create_vendor(db, vendor_name)
return vendor
@router.get("/vendors")
async def get_vendors(
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
vendors = await inventory_service.get_vendors(db)
return vendors
@router.post("/marketplaces")
async def create_marketplace(
marketplace_name: str,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
marketplace = await inventory_service.create_marketplace(db, marketplace_name)
return marketplace
@router.get("/marketplaces")
async def get_marketplaces(
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
marketplaces = await inventory_service.get_marketplaces(db)
return marketplaces
@router.get("/products/search")
async def get_products(q: str, db: Session = Depends(get_db)):
query = ' & '.join(q.lower().split()) # This ensures all terms must match
products = db.query(TCGPlayerProduct).filter(
func.to_tsvector('english', TCGPlayerProduct.name)
.op('@@')(func.to_tsquery('english', query))
).all()
return products
@router.get("/products/{product_id}/expected-value")
async def get_expected_value(
product_id: int,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
expected_value = await inventory_service.get_expected_value(db, product_id)
return expected_value
@router.post("/products/expected-value")
async def create_expected_value(
expected_value_data: SealedExpectedValueCreate,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
expected_value = await inventory_service.create_expected_value(db, expected_value_data)
return expected_value
@router.post("/transactions/purchase")
async def create_purchase_transaction(
transaction_data: PurchaseTransactionCreate,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
transaction = await inventory_service.create_purchase_transaction(db, transaction_data)
return transaction
@router.get("/transactions")
async def get_transactions(
db: Session = Depends(get_db),
skip: int = 0,
limit: int = 100
):
inventory_service = service_manager.get_service("inventory")
total = db.query(func.count(Transaction.id)).filter(Transaction.deleted_at == None).scalar()
transactions = await inventory_service.get_transactions(db, skip, limit)
return GetAllTransactionsResponse(
total=total,
transactions=[TransactionResponse(
id=transaction.id,
vendor_id=transaction.vendor_id,
customer_id=transaction.customer_id,
marketplace_id=transaction.marketplace_id,
transaction_type=transaction.transaction_type,
transaction_date=transaction.transaction_date,
transaction_total_amount=transaction.transaction_total_amount,
transaction_notes=transaction.transaction_notes,
created_at=transaction.created_at,
updated_at=transaction.updated_at,
transaction_items=[TransactionItemResponse(
id=transaction_item.id,
transaction_id=transaction_item.transaction_id,
inventory_item_id=transaction_item.inventory_item_id,
unit_price=transaction_item.unit_price,
created_at=transaction_item.created_at,
updated_at=transaction_item.updated_at,
deleted_at=transaction_item.deleted_at
) for transaction_item in transaction.transaction_items]
) for transaction in transactions]
)
@router.get("/transactions/{transaction_id}")
async def get_transaction(
transaction_id: int,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
transaction = await inventory_service.get_transaction(db, transaction_id)
if not transaction:
raise HTTPException(status_code=404, detail="Transaction not found")
return TransactionResponse(
id=transaction.id,
vendor_id=transaction.vendor_id,
customer_id=transaction.customer_id,
marketplace_id=transaction.marketplace_id,
transaction_type=transaction.transaction_type,
transaction_date=transaction.transaction_date,
transaction_total_amount=transaction.transaction_total_amount,
transaction_notes=transaction.transaction_notes,
created_at=transaction.created_at,
updated_at=transaction.updated_at,
transaction_items=[TransactionItemResponse(
id=transaction_item.id,
transaction_id=transaction_item.transaction_id,
inventory_item_id=transaction_item.inventory_item_id,
unit_price=transaction_item.unit_price,
created_at=transaction_item.created_at,
updated_at=transaction_item.updated_at,
deleted_at=transaction_item.deleted_at
) for transaction_item in transaction.transaction_items]
)
@router.get("/items/{inventory_item_id}")
async def get_inventory_item(
inventory_item_id: int,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
inventory_item = await inventory_service.get_inventory_item(db, inventory_item_id)
marketplace_listing_service = service_manager.get_service("marketplace_listing")
marketplace = await inventory_service.create_marketplace(db, "Tcgplayer")
marketplace_listing = await marketplace_listing_service.get_marketplace_listing(db, inventory_item, marketplace)
if marketplace_listing is None:
listed_price = None
recommended_price = None
marketplace_listing_id = None
else:
if marketplace_listing.listed_price is not None:
listed_price = marketplace_listing.listed_price.price if marketplace_listing.listed_price.price is not None else None
else:
listed_price = None
if marketplace_listing.recommended_price is not None:
recommended_price = marketplace_listing.recommended_price.price if marketplace_listing.recommended_price.price is not None else None
else:
recommended_price = None
marketplace_listing_id = marketplace_listing.id
return InventoryItemResponse(
id=inventory_item.id,
physical_item_id=inventory_item.physical_item_id,
cost_basis=inventory_item.cost_basis,
parent_id=inventory_item.parent_id,
created_at=inventory_item.created_at,
updated_at=inventory_item.updated_at,
item_type=inventory_item.physical_item.item_type,
listed_price=listed_price,
recommended_price=recommended_price,
marketplace_listing_id=marketplace_listing_id,
product=TCGPlayerProductResponse(
id=inventory_item.physical_item.product_direct.id,
tcgplayer_product_id=inventory_item.physical_item.product_direct.tcgplayer_product_id,
name=inventory_item.physical_item.product_direct.name,
image_url=inventory_item.physical_item.product_direct.image_url,
category_id=inventory_item.physical_item.product_direct.category_id,
group_id=inventory_item.physical_item.product_direct.group_id,
url=inventory_item.physical_item.product_direct.url,
market_price=inventory_item.physical_item.product_direct.most_recent_tcgplayer_price.market_price,
category_name=inventory_item.physical_item.product_direct.category.name,
group_name=inventory_item.physical_item.product_direct.group.name
)
)
@router.post("/items/{inventory_item_id}/open")
async def open_box_or_case(
open_event_data: OpenEventCreate,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
inventory_item = await inventory_service.get_inventory_item(db, open_event_data.inventory_item_id)
file_service = service_manager.get_service("file")
files = [await file_service.get_file(db, file_id) for file_id in open_event_data.manabox_file_upload_ids]
if inventory_item.physical_item.item_type == "box":
box_service = service_manager.get_service("box")
open_event = await box_service.open_box(db, inventory_item.physical_item, files)
return OpenEventResponse(
id=open_event.id,
source_item_id=open_event.source_item_id,
created_at=open_event.created_at,
updated_at=open_event.updated_at
)
elif inventory_item.physical_item.item_type == "case":
case_service = service_manager.get_service("case")
open_event = await case_service.open_case(db, inventory_item.physical_item, files)
return OpenEventResponse(
id=open_event.id,
source_item_id=open_event.source_item_id,
created_at=open_event.created_at,
updated_at=open_event.updated_at
)
else:
raise HTTPException(status_code=400, detail="Invalid item type")
@router.get("/items/{inventory_item_id}/open-events")
async def get_open_events(
inventory_item_id: int,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
inventory_item = await inventory_service.get_inventory_item(db, inventory_item_id)
# Don't return open events for cards
if inventory_item.physical_item.item_type == 'card':
return OpenEventsForInventoryItemResponse(open_events=[])
open_events = await inventory_service.get_open_events_for_inventory_item(db, inventory_item)
return OpenEventsForInventoryItemResponse(
open_events=[OpenEventResponse(
id=open_event.id,
source_item_id=open_event.source_item_id,
created_at=open_event.created_at,
updated_at=open_event.updated_at
) for open_event in open_events]
)
@router.get("/items/{inventory_item_id}/open-events/{open_event_id}/resulting-items", response_model=List[InventoryItemResponse])
async def get_resulting_items(
inventory_item_id: int,
open_event_id: int,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
inventory_item = await inventory_service.get_inventory_item(db, inventory_item_id)
open_event = await inventory_service.get_open_event(db, inventory_item, open_event_id)
resulting_items = await inventory_service.get_resulting_items_for_open_event(db, open_event)
marketplace_listing_service = service_manager.get_service("marketplace_listing")
marketplace = await inventory_service.create_marketplace(db, "Tcgplayer")
marketplace_listing = await marketplace_listing_service.get_marketplace_listing(db, inventory_item, marketplace)
if marketplace_listing is None:
listed_price = None
recommended_price = None
marketplace_listing_id = None
else:
if marketplace_listing.listed_price is not None:
listed_price = marketplace_listing.listed_price.price if marketplace_listing.listed_price.price is not None else None
else:
listed_price = None
if marketplace_listing.recommended_price is not None:
recommended_price = marketplace_listing.recommended_price.price if marketplace_listing.recommended_price.price is not None else None
else:
recommended_price = None
marketplace_listing_id = marketplace_listing.id
return [InventoryItemResponse(
id=resulting_item.id,
physical_item_id=resulting_item.physical_item_id,
cost_basis=resulting_item.cost_basis,
parent_id=resulting_item.parent_id,
product=TCGPlayerProductResponse(
id=resulting_item.physical_item.product_direct.id,
tcgplayer_product_id=resulting_item.physical_item.product_direct.tcgplayer_product_id,
name=resulting_item.physical_item.product_direct.name,
image_url=resulting_item.physical_item.product_direct.image_url,
category_id=resulting_item.physical_item.product_direct.category_id,
group_id=resulting_item.physical_item.product_direct.group_id,
url=resulting_item.physical_item.product_direct.url,
market_price=resulting_item.physical_item.product_direct.most_recent_tcgplayer_price.market_price,
category_name=resulting_item.physical_item.product_direct.category.name,
group_name=resulting_item.physical_item.product_direct.group.name
),
item_type=resulting_item.physical_item.item_type,
marketplace_listing_id=marketplace_listing_id,
listed_price=listed_price,
recommended_price=recommended_price,
created_at=resulting_item.created_at,
updated_at=resulting_item.updated_at) for resulting_item in resulting_items]
@router.post("/items/{inventory_item_id}/open-events/{open_event_id}/create-listings")
async def create_marketplace_listings(
inventory_item_id: int,
open_event_id: int,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
inventory_item = await inventory_service.get_inventory_item(db, inventory_item_id)
open_event = await inventory_service.get_open_event(db, inventory_item, open_event_id)
resulting_items = await inventory_service.get_resulting_items_for_open_event(db, open_event)
marketplace = await inventory_service.create_marketplace(db, "Tcgplayer")
marketplace_listing_service = service_manager.get_service("marketplace_listing")
for resulting_item in resulting_items:
await marketplace_listing_service.create_marketplace_listing(db, resulting_item, marketplace)
return {"message": f"{len(resulting_items)} marketplace listings created successfully"}
@router.post("/items/{inventory_item_id}/open-events/{open_event_id}/confirm-listings")
async def confirm_listings(
inventory_item_id: int,
open_event_id: int,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
inventory_item = await inventory_service.get_inventory_item(db, inventory_item_id)
open_event = await inventory_service.get_open_event(db, inventory_item, open_event_id)
marketplace_listing_service = service_manager.get_service("marketplace_listing")
marketplace = await inventory_service.create_marketplace(db, "Tcgplayer")
try:
csv_string = await marketplace_listing_service.confirm_listings(db, open_event, marketplace)
if not csv_string:
raise ValueError("No CSV data generated")
# Create a streaming response with the CSV data
return StreamingResponse(
iter([csv_string]),
media_type="text/csv",
headers={
"Content-Disposition": f"attachment; filename=tcgplayer_add_file_{open_event.id}_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.csv"
}
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))