data model change and some new services
This commit is contained in:
381
db/models.py
381
db/models.py
@@ -1,31 +1,353 @@
|
||||
from sqlalchemy import Column, Integer, String, Float, Boolean, DateTime, ForeignKey
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.orm import relationship, validates
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class Box(Base):
|
||||
__tablename__ = "boxes"
|
||||
## Core Models
|
||||
|
||||
id = Column(String, primary_key=True, index=True)
|
||||
upload_id = Column(String, ForeignKey("upload_history.upload_id"))
|
||||
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)
|
||||
type = Column(String)
|
||||
cost = Column(Float)
|
||||
date_purchased = Column(DateTime)
|
||||
date_opened = Column(DateTime)
|
||||
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.
|
||||
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)
|
||||
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 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)
|
||||
# upload_id = Column(String)
|
||||
# box_id = Column(String, nullable=True)
|
||||
name = Column(String)
|
||||
set_code = Column(String)
|
||||
set_name = Column(String)
|
||||
@@ -50,6 +372,8 @@ class UploadHistory(Base):
|
||||
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)
|
||||
@@ -159,4 +483,37 @@ class UnmatchedManaboxData(Base):
|
||||
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)
|
||||
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"
|
Reference in New Issue
Block a user