from sqlalchemy import Column, Integer, String, Boolean, Float, ForeignKey, Text from sqlalchemy.orm import relationship from database import Base class Category(Base): __tablename__ = "categories" id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False) color = Column(String, nullable=True) sort_order = Column(Integer, default=0) # self-referential: null = top-level, non-null = sub-category of parent parent_id = Column(Integer, ForeignKey("categories.id"), nullable=True) # position of the "General" group (direct products) among sub-categories general_sort_order = Column(Integer, default=0, nullable=False) # sub-categories only: if True, the accordion section is expanded by default on the PWA auto_expanded = Column(Boolean, default=False, nullable=False) products = relationship("Product", back_populates="category") subcategories = relationship("Category", back_populates="parent", foreign_keys="Category.parent_id") parent = relationship("Category", back_populates="subcategories", remote_side="Category.id", foreign_keys="Category.parent_id") class Product(Base): __tablename__ = "products" id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False) category_id = Column(Integer, ForeignKey("categories.id"), nullable=True) base_price = Column(Float, nullable=False) is_available = Column(Boolean, default=True, nullable=False) # "active" | "archived" — archived products are kept for order history but hidden from active use lifecycle_status = Column(String, default="active", nullable=False) printer_zone_id = Column(Integer, ForeignKey("printers.id"), nullable=True) image_url = Column(String, nullable=True) sort_order = Column(Integer, default=0, nullable=False) category = relationship("Category", back_populates="products") printer_zone = relationship("Printer", back_populates="products") quick_options = relationship("ProductQuickOption", back_populates="product", cascade="all, delete-orphan") options = relationship("ProductOption", back_populates="product", cascade="all, delete-orphan") ingredients = relationship("ProductIngredient", back_populates="product", cascade="all, delete-orphan") preference_sets = relationship("ProductPreferenceSet", back_populates="product", cascade="all, delete-orphan") order_items = relationship("OrderItem", back_populates="product") class ProductOption(Base): __tablename__ = "product_options" id = Column(Integer, primary_key=True, index=True) product_id = Column(Integer, ForeignKey("products.id"), nullable=False) name = Column(String, nullable=False) extra_cost = Column(Float, default=0.0) allow_multiple = Column(Boolean, default=False, nullable=False) # JSON array [{name, extra_cost, is_default}] — sub-options shown when this option is checked sub_choices = Column(Text, nullable=True) is_favorite = Column(Boolean, default=False, nullable=False) favorite_sort_order = Column(Integer, default=0, nullable=False) product = relationship("Product", back_populates="options") class ProductQuickOption(Base): __tablename__ = "product_quick_options" id = Column(Integer, primary_key=True, index=True) product_id = Column(Integer, ForeignKey("products.id"), nullable=False) name = Column(String, nullable=False) price = Column(Float, default=0.0, nullable=False) allow_multiple = Column(Boolean, default=False, nullable=False) sort_order = Column(Integer, default=0, nullable=False) is_favorite = Column(Boolean, default=False, nullable=False) favorite_sort_order = Column(Integer, default=0, nullable=False) product = relationship("Product", back_populates="quick_options") class ProductIngredient(Base): __tablename__ = "product_ingredients" id = Column(Integer, primary_key=True, index=True) product_id = Column(Integer, ForeignKey("products.id"), nullable=False) name = Column(String, nullable=False) extra_cost = Column(Float, default=0.0) is_favorite = Column(Boolean, default=False, nullable=False) favorite_sort_order = Column(Integer, default=0, nullable=False) product = relationship("Product", back_populates="ingredients") class ProductPreferenceSet(Base): __tablename__ = "product_preference_sets" id = Column(Integer, primary_key=True, index=True) product_id = Column(Integer, ForeignKey("products.id"), nullable=False) name = Column(String, nullable=False) default_choice_id = Column(Integer, nullable=True) # JSON: {name, default_choice_index, choices:[{name,extra_cost,is_default}]} # Shared sub-set shown for all choices that don't have disables_subset=True shared_subset = Column(Text, nullable=True) is_favorite = Column(Boolean, default=False, nullable=False) favorite_sort_order = Column(Integer, default=0, nullable=False) product = relationship("Product", back_populates="preference_sets") choices = relationship("ProductPreferenceChoice", back_populates="set", cascade="all, delete-orphan") class ProductPreferenceChoice(Base): __tablename__ = "product_preference_choices" id = Column(Integer, primary_key=True, index=True) set_id = Column(Integer, ForeignKey("product_preference_sets.id"), nullable=False) name = Column(String, nullable=False) extra_cost = Column(Float, default=0.0) # JSON array of sub-choice objects: [{name, extra_cost, is_default}] # Per-choice inline sub-preference shown only when this choice is selected. sub_choices = Column(Text, nullable=True) # When True this choice hides the set-level shared_subset on the PWA. disables_subset = Column(Boolean, default=False, nullable=False) set = relationship("ProductPreferenceSet", back_populates="choices")