555 lines
20 KiB
Python
555 lines
20 KiB
Python
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.
|
|
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
|
|
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"))
|
|
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)
|
|
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, ForeignKey("boxes.product_id"), nullable=True)
|
|
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"
|
|
|
|
@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("warehouse.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)
|
|
|
|
|
|
## 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"
|
|
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" |