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)).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))