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) type = Column(String) # box or card product_line = Column(String) # pokemon, mtg, etc. 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 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. set_code = Column(String) sku = Column(String, nullable=True) 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 """ __tablename__ = "cards" product_id = Column(String, ForeignKey("products.id"), primary_key=True) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class CardManabox(Base): __tablename__ = "manabox_cards" 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(Integer) scryfall_id = Column(String) condition = Column(String) language = Column(String) class CardTCGPlayer(Base): __tablename__ = "tcgplayer_cards" product_id = Column(String, ForeignKey("cards.product_id"), 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) class Warehouse(Base): """ container that is associated with a user and contains inventory and stock """ __tablename__ = "warehouses" 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 Stock(Base): """ contains products that are listed for sale """ __tablename__ = "stocks" product_id = Column(String, ForeignKey("products.id"), primary_key=True) warehouse_id = Column(String, ForeignKey("warehouses.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__ = "inventories" product_id = Column(String, ForeignKey("products.id"), primary_key=True) warehouse_id = Column(String, ForeignKey("warehouses.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) username = Column(String) 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) box_id = Column(String, nullable=True) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class Price(Base): __tablename__ = "prices" 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" @validates("type") def validate_type(self, key, type: str): if type not in StorageBlockTypeEnum or type.lower() not in StorageBlockTypeEnum: raise ValueError(f"Invalid storage block type: {type}") return type id = Column(String, primary_key=True) warehouse_id = Column(String, ForeignKey("warehouses.id")) name = Column(String) type = Column(String) # rare or common date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) 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_blocks" 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 OpenBoxCard(Base): """ OpenedBoxCard represents the relationship between an opened box and the cards it contains """ __tablename__ = "open_box_cards" 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_sales" 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) 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 Orders(Base): __tablename__ = 'orders' id = Column(String, primary_key=True) order_id = Column(String, unique=True) buyer_name = Column(String) recipient_name = Column(String) recipient_address_one = Column(String) recipient_address_two = Column(String) recipient_city = Column(String) recipient_state = Column(String) recipient_zip = Column(String) recipient_country = Column(String) order_date = Column(String) status = Column(String) num_products = Column(Integer) num_cards = Column(Integer) product_amount = Column(Float) shipping_amount = Column(Float) gross_amount = Column(Float) fee_amount = Column(Float) net_amount = Column(Float) direct_fee_amount = Column(Float) date_created = Column(DateTime, default=datetime.now) date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now) class OrderProducts(Base): __tablename__ = 'order_products' id = Column(String, primary_key=True) order_id = Column(String, ForeignKey('orders.id')) product_id = Column(String, ForeignKey('products.id')) quantity = Column(Integer) unit_price = Column(Float) # 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" SET = "set" class ProductLineEnum(str, Enum): MTG = "mtg" POKEMON = "pokemon" class ProductTypeEnum(str, Enum): BOX = "box" CARD = "card" class StorageBlockTypeEnum(str, Enum): RARE = "rare" COMMON = "common"