from db.models import Box, File, StagedFileProduct, Product, OpenBoxCard, OpenBox, Inventory from db.utils import db_transaction from uuid import uuid4 as uuid from datetime import datetime from sqlalchemy.orm import Session from sqlalchemy.engine.result import Row from schemas.box import CreateOpenBoxResponse, CreateSealedBoxResponse, CreateBoxResponse import logging from typing import Any from db.utils import db_transaction from services.inventory import InventoryService logger = logging.getLogger(__name__) class BoxService: def __init__(self, db: Session, inventory_service: InventoryService): self.db = db self.inventory_service = inventory_service def validate_file_ids(self, file_ids: list[str]): # check if all file_ids are valid for file_id in file_ids: if self.db.query(File).filter(File.id == file_id).first() is None: raise Exception(f"File ID {file_id} not found") def get_staged_product_data(self, file_ids: list[str]) -> StagedFileProduct: staged_product_data = self.db.query(StagedFileProduct).filter(StagedFileProduct.file_id.in_(file_ids)).all() return staged_product_data def aggregate_staged_product_data(self, staged_product_data: list[Row]) -> dict[Product, int]: product_data = {} for row in staged_product_data: product = self.db.query(Product).filter(Product.id == row.product_id).first() if product not in product_data: product_data[product] = 0 product_data[product] += row.quantity return product_data def find_product_for_box_data(self, create_box_data: dict[str, Any]) -> Product: existing_product = self.db.query(Product).filter( Product.name == create_box_data["name"], # TODO: needs complex enum Product.type == "box", Product.set_code == create_box_data["set_code"], # TODO: needs complex enum Product.set_name == create_box_data["set_name"], # TODO: needs complex enum Product.product_line == create_box_data["product_line"]).first() return existing_product def create_product_for_box(self, create_box_data: dict[str, Any]) -> Product: product = Product( id=str(uuid()), name=create_box_data["name"], type="box", set_code=create_box_data["set_code"], set_name=create_box_data["set_name"], product_line=create_box_data["product_line"] ) self.db.add(product) return product def create_box_db(self, product: Product, create_box_data: dict[str, Any]) -> Box: box = Box( product_id=product.id, type=create_box_data["type"], sku=create_box_data["sku"], num_cards_expected=create_box_data["num_cards_expected"] ) self.db.add(box) return box def create_open_box(self, product: Product, create_box_data: dict[str, Any]) -> OpenBox: open_box = OpenBox( id = str(uuid()), product_id=product.id, num_cards_actual=create_box_data["num_cards_actual"], date_opened=datetime.strptime(create_box_data["date_opened"], "%Y-%m-%d") ) self.db.add(open_box) return open_box def add_products_to_open_box(self, open_box: OpenBox, product_data: dict[Product, int]) -> None: for product, quantity in product_data.items(): open_box_card = OpenBoxCard( id=str(uuid()), open_box_id=open_box.id, card_id=product.id, quantity=quantity ) self.db.add(open_box_card) def format_response(self, open_box: OpenBox = None, inventory: Inventory = None) -> CreateBoxResponse: response = CreateBoxResponse(success=True) return response def create_box(self, create_box_data: dict[str, Any], file_ids: list[str] = None) -> CreateBoxResponse: sealed = create_box_data["sealed"] assert isinstance(sealed, bool) if file_ids and not sealed: self.validate_file_ids(file_ids) staged_product_data = self.get_staged_product_data(file_ids) product_data = self.aggregate_staged_product_data(staged_product_data) elif file_ids and sealed: raise Exception("Cannot add cards with a sealed box") # find product with all same box data existing_product = self.find_product_for_box_data(create_box_data) if existing_product: box_product = existing_product try: with db_transaction(self.db): if not existing_product: box_product = self.create_product_for_box(create_box_data) box = self.create_box_db(box_product, create_box_data) if not sealed: open_box = self.create_open_box(box_product, create_box_data) if file_ids: process_staged_products = self.inventory_service.process_staged_products(product_data) self.add_products_to_open_box(open_box, product_data) # should be the file service handling this but im about to die irl # update file id status to processed for file_id in file_ids: file = self.db.query(File).filter(File.id == file_id).first() file.status = "processed" self.db.add(file) return self.format_response(open_box=open_box) elif not file_ids and sealed: # add sealed box to inventory inventory = self.inventory_service.add_sealed_box_to_inventory(box_product, 1) return self.format_response(inventory=inventory) except Exception as e: logger.error(f"Error creating box: {str(e)}") raise e