488 lines
21 KiB
Python
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)).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)) |