from sqlalchemy import Column, Integer, String, Float, Boolean, DateTime, ForeignKey from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship, validates from datetime import datetime from enum import Enum import logging logger = logging.getLogger(__name__) Base = declarative_base() ## Core Models class Product(Base): """ product is the concept of a physical item that can be sold """ __tablename__ = "products" @validates("type") def validate_type(self, key, type: str): if type not in ProductTypeEnum or type.lower() not in ProductTypeEnum: raise ValueError(f"Invalid product type: {type}") return type @validates("product_line") def validate_product_line(self, key, product_line: str): if product_line not in ProductLineEnum or product_line.lower() not in ProductLineEnum: raise ValueError(f"Invalid product line: {product_line}") return product_line id = Column(String, primary_key=True) name = Column(String) type = Column(String) # box or card product_line = Column(String) # pokemon, mtg, etc. set_name = Column(String) set_code = Column(String) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class Sale(Base): """ sale represents a transaction where a product was sold to a customer on a marketplace """ __tablename__ = "sales" id = Column(String, primary_key=True) ledger_id = Column(String, ForeignKey("ledgers.id")) customer_id = Column(String, ForeignKey("customers.id")) marketplace_id = Column(String, ForeignKey("marketplaces.id")) amount = Column(Float) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class Order(Base): __tablename__ = "orders" id = Column(String, primary_key=True) sale_id = Column(String, ForeignKey("sales.id")) class Ledger(Base): """ ledger associates financial transactions with a user """ __tablename__ = "ledgers" id = Column(String, primary_key=True) user_id = Column(String, ForeignKey("users.id")) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class Expense(Base): """ expense is any cash outflow associated with moving a product can be optionally associated with a sale or a product """ __tablename__ = "expenses" id = Column(String, primary_key=True) ledger_id = Column(String, ForeignKey("ledgers.id")) product_id = Column(String, ForeignKey("products.id"), nullable=True) sale_id = Column(String, ForeignKey("sales.id"), nullable=True) cost = Column(Float) type = Column(String) # price paid, cogs, shipping, refund, supplies, subscription, fee, etc. date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class Marketplace(Base): """ Marketplace represents a marketplace where products can be sold """ __tablename__ = "marketplaces" id = Column(String, primary_key=True) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class Box(Base): """ Box Represents a physical product with a sku that contains trading cards Boxes can be sealed or opened Opened boxes have cards associated with them A box contains cards regardless of the inventory status of those cards """ __tablename__ = "boxes" @validates("type") def validate_type(self, key, type: str): if type not in BoxTypeEnum or type.lower() not in BoxTypeEnum: raise ValueError(f"Invalid box type: {type}") return type product_id = Column(String, ForeignKey("products.id"), primary_key=True) type = Column(String) # collector box, play box, etc. sku = Column(String) num_cards_expected = Column(Integer, nullable=True) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class OpenBox(Base): __tablename__ = "open_boxes" id = Column(String, primary_key=True) product_id = Column(String, ForeignKey("products.id")) num_cards_actual = Column(Integer) date_opened = Column(DateTime, default=datetime.now) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class Card(Base): """ Card represents the concept of a distinct card Cards have metadata from different sources internal: box, inventory, upload external: price, attributes - scryfall, tcgplayer, manabox """ __tablename__ = "cards" @validates("rarity") def validate_rarity(self, key, rarity: str): single_character_rarity = {'m': 'mythic', 'r': 'rare', 'u': 'uncommon', 'c': 'common', 'l': 'land', 'p': 'promo', 's': 'special'} if rarity not in RarityEnum: if rarity.lower() in RarityEnum: rarity = rarity.lower() elif rarity in single_character_rarity: rarity = single_character_rarity[rarity] else: raise ValueError(f"Invalid rarity: {rarity}") return rarity @validates("condition") def validate_condition(self, key, condition: str): if condition not in ConditionEnum: if condition.lower() in ConditionEnum: condition = condition.lower() elif condition.lower().strip().replace(' ', '_') in ConditionEnum: condition = condition.lower().strip().replace(' ', '_') else: raise ValueError(f"Invalid condition: {condition}") return condition product_id = Column(String, ForeignKey("products.id"), primary_key=True) number = Column(String) foil = Column(String) rarity = Column(String) condition = Column(String) language = Column(String) scryfall_id = Column(String) manabox_id = Column(String) tcgplayer_id = Column(Integer) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class CardManabox(Base): __tablename__ = "card_manabox" product_id = Column(String, ForeignKey("cards.product_id"), primary_key=True) name = Column(String) set_code = Column(String) set_name = Column(String) collector_number = Column(String) foil = Column(String) rarity = Column(String) manabox_id = Column(String) scryfall_id = Column(String) condition = Column(String) language = Column(String) class CardTCGPlayer(Base): __tablename__ = "card_tcgplayer" product_id = Column(String, ForeignKey("cards.product_id"), primary_key=True) group_id = Column(Integer, ForeignKey("tcgplayer_groups.group_id")) tcgplayer_id = Column(Integer) product_line = Column(String) set_name = Column(String) product_name = Column(String) title = Column(String) number = Column(String) rarity = Column(String) condition = Column(String) class Warehouse(Base): """ container that is associated with a user and contains inventory and stock """ __tablename__ = "warehouse" id = Column(String, primary_key=True) user_id = Column(String, ForeignKey("users.id"), default="admin") date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class Stock(Base): """ contains products that are listed for sale """ __tablename__ = "stock" product_id = Column(String, ForeignKey("products.id"), primary_key=True) warehouse_id = Column(String, ForeignKey("warehouse.id"), default="default") marketplace_id = Column(String, ForeignKey("marketplaces.id")) quantity = Column(Integer) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class Inventory(Base): """ contains products in inventory (not necessarily listed for sale) sealed product in breakdown queue, held sealed product, speculatively held singles, etc. inventory can contain products across multiple marketplaces """ __tablename__ = "inventory" product_id = Column(String, ForeignKey("products.id"), primary_key=True) warehouse_id = Column(String, ForeignKey("warehouse.id"), default="default") quantity = Column(Integer) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class User(Base): """ User represents a user in the system """ __tablename__ = "users" id = Column(String, primary_key=True) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class Customer(Base): """ Customer represents a customer that has purchased at least 1 product """ __tablename__ = "customers" id = Column(String, primary_key=True) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class StagedFileProduct(Base): __tablename__ = "staged_file_products" id = Column(String, primary_key=True) product_id = Column(String, ForeignKey("products.id")) file_id = Column(String, ForeignKey("files.id")) quantity = Column(Integer) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class File(Base): """ File represents a file that has been uploaded to or retrieved by the system """ __tablename__ = "files" id = Column(String, primary_key=True) type = Column(String) # upload, export, etc. source = Column(String) # manabox, tcgplayer, etc. service = Column(String) # pricing, data, etc. filename = Column(String) filepath = Column(String) # backup location filesize_kb = Column(Float) status = Column(String) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class Price(Base): __tablename__ = "price" id = Column(String, primary_key=True) product_id = Column(String, ForeignKey("products.id")) marketplace_id = Column(String, ForeignKey("marketplaces.id")) type = Column(String) # market, direct, low, low_with_shipping, marketplace price = Column(Float) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class StorageBlock(Base): """ StorageBlock represents a physical storage location for products (50 card indexed block in a box) """ __tablename__ = "storage_blocks" id = Column(String, primary_key=True) warehouse_id = Column(String, ForeignKey("warehouse.id")) name = Column(String) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) ## Relationships class ProductBlock(Base): """ ProductBlock represents the relationship between a product and a storage block which products are in a block and at what index """ __tablename__ = "product_block" id = Column(String, primary_key=True) product_id = Column(String, ForeignKey("products.id")) block_id = Column(String, ForeignKey("storage_blocks.id")) block_index = Column(Integer) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class ProductMarketPrice(Base): __tablename__ = "product_market_price" id = Column(String, primary_key=True) product_id = Column(String, ForeignKey("products.id")) marketplace_id = Column(String, ForeignKey("marketplaces.id")) price_id = Column(String, ForeignKey("price.id")) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class OpenBoxCard(Base): """ OpenedBoxCard represents the relationship between an opened box and the cards it contains """ __tablename__ = "open_box_card" id = Column(String, primary_key=True) open_box_id = Column(String, ForeignKey("open_boxes.id")) card_id = Column(String, ForeignKey("cards.product_id")) quantity = Column(Integer) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class ProductSale(Base): """ ProductSale represents the relationship between products and sales """ __tablename__ = "product_sale" id = Column(String, primary_key=True) product_id = Column(String, ForeignKey("products.id")) sale_id = Column(String, ForeignKey("sales.id")) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) ## older class ManaboxExportData(Base): __tablename__ = "manabox_export_data" id = Column(String, primary_key=True) # upload_id = Column(String) # box_id = Column(String, nullable=True) name = Column(String) set_code = Column(String) set_name = Column(String) collector_number = Column(String) foil = Column(String) rarity = Column(String) quantity = Column(Integer) manabox_id = Column(String) scryfall_id = Column(String) purchase_price = Column(Float) misprint = Column(String) altered = Column(String) condition = Column(String) language = Column(String) purchase_price_currency = Column(String) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class UploadHistory(Base): __tablename__ = "upload_history" id = Column(String, primary_key=True) upload_id = Column(String) filename = Column(String) file_size_kb = Column(Float) num_rows = Column(Integer) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) status = Column(String) class TCGPlayerGroups(Base): __tablename__ = 'tcgplayer_groups' id = Column(String, primary_key=True) group_id = Column(Integer) name = Column(String) abbreviation = Column(String) is_supplemental = Column(String) published_on = Column(String) modified_on = Column(String) category_id = Column(Integer) class TCGPlayerInventory(Base): __tablename__ = 'tcgplayer_inventory' # TCGplayer Id,Product Line,Set Name,Product Name,Title,Number,Rarity,Condition,TCG Market Price,TCG Direct Low,TCG Low Price With Shipping,TCG Low Price,Total Quantity,Add to Quantity,TCG Marketplace Price,Photo URL id = Column(String, primary_key=True) export_id = Column(String) tcgplayer_product_id = Column(String, ForeignKey("tcgplayer_product.id"), nullable=True) tcgplayer_id = Column(Integer) product_line = Column(String) set_name = Column(String) product_name = Column(String) title = Column(String) number = Column(String) rarity = Column(String) condition = Column(String) tcg_market_price = Column(Float) tcg_direct_low = Column(Float) tcg_low_price_with_shipping = Column(Float) tcg_low_price = Column(Float) total_quantity = Column(Integer) add_to_quantity = Column(Integer) tcg_marketplace_price = Column(Float) photo_url = Column(String) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class TCGPlayerExportHistory(Base): __tablename__ = 'tcgplayer_export_history' id = Column(String, primary_key=True) type = Column(String) pricing_export_id = Column(String) inventory_export_id = Column(String) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class TCGPlayerPricingHistory(Base): __tablename__ = 'tcgplayer_pricing_history' id = Column(String, primary_key=True) tcgplayer_product_id = Column(String, ForeignKey("tcgplayer_product.id")) export_id = Column(String) group_id = Column(Integer) tcgplayer_id = Column(Integer) tcg_market_price = Column(Float) tcg_direct_low = Column(Float) tcg_low_price_with_shipping = Column(Float) tcg_low_price = Column(Float) tcg_marketplace_price = Column(Float) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class TCGPlayerProduct(Base): __tablename__ = 'tcgplayer_product' id = Column(String, primary_key=True) group_id = Column(Integer) tcgplayer_id = Column(Integer) product_line = Column(String) set_name = Column(String) product_name = Column(String) title = Column(String) number = Column(String) rarity = Column(String) condition = Column(String) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class ManaboxTCGPlayerMapping(Base): __tablename__ = 'manabox_tcgplayer_mapping' id = Column(String, primary_key=True) manabox_id = Column(String, ForeignKey("manabox_export_data.id")) tcgplayer_id = Column(Integer, ForeignKey("tcgplayer_inventory.tcgplayer_id")) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class SetCodeGroupIdMapping(Base): __tablename__ = 'set_code_group_id_mapping' id = Column(String, primary_key=True) set_code = Column(String) group_id = Column(Integer) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class UnmatchedManaboxData(Base): __tablename__ = 'unmatched_manabox_data' id = Column(String, primary_key=True) manabox_id = Column(String, ForeignKey("manabox_export_data.id")) reason = Column(String) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) # enums class RarityEnum(str, Enum): COMMON = "common" UNCOMMON = "uncommon" RARE = "rare" MYTHIC = "mythic" LAND = "land" PROMO = "promo" SPECIAL = "special" class ConditionEnum(str, Enum): MINT = "mint" NEAR_MINT = "near_mint" LIGHTLY_PLAYED = "lightly_played" MODERATELY_PLAYED = "moderately_played" HEAVILY_PLAYED = "heavily_played" DAMAGED = "damaged" class BoxTypeEnum(str, Enum): COLLECTOR = "collector" PLAY = "play" DRAFT = "draft" COMMANDER = "commander" class ProductLineEnum(str, Enum): MTG = "mtg" POKEMON = "pokemon" class ProductTypeEnum(str, Enum): BOX = "box" CARD = "card"