# models/database/order_item_exception.py """ Order Item Exception model for tracking unmatched products during marketplace imports. When a marketplace order contains a GTIN that doesn't match any product in the vendor's catalog, the order is still imported but the item is linked to a placeholder product and an exception is recorded here for resolution. """ from sqlalchemy import ( Column, DateTime, ForeignKey, Index, Integer, String, Text, ) from sqlalchemy.orm import relationship from app.core.database import Base from models.database.base import TimestampMixin class OrderItemException(Base, TimestampMixin): """ Tracks unmatched order items requiring admin/vendor resolution. When a marketplace order is imported and a product cannot be found by GTIN, the order item is linked to a placeholder product and this exception record is created. The order cannot be confirmed until all exceptions are resolved. """ __tablename__ = "order_item_exceptions" id = Column(Integer, primary_key=True, index=True) # Link to the order item (one-to-one) order_item_id = Column( Integer, ForeignKey("order_items.id", ondelete="CASCADE"), nullable=False, unique=True, ) # Vendor ID for efficient querying (denormalized from order) vendor_id = Column( Integer, ForeignKey("vendors.id"), nullable=False, index=True ) # Original data from marketplace (preserved for matching) original_gtin = Column(String(50), nullable=True, index=True) original_product_name = Column(String(500), nullable=True) original_sku = Column(String(100), nullable=True) # Exception classification # product_not_found: GTIN not in vendor catalog # gtin_mismatch: GTIN format issue # duplicate_gtin: Multiple products with same GTIN exception_type = Column( String(50), nullable=False, default="product_not_found" ) # Resolution status # pending: Awaiting resolution # resolved: Product has been assigned # ignored: Marked as ignored (still blocks confirmation) status = Column(String(50), nullable=False, default="pending", index=True) # Resolution details (populated when resolved) resolved_product_id = Column( Integer, ForeignKey("products.id"), nullable=True ) resolved_at = Column(DateTime(timezone=True), nullable=True) resolved_by = Column(Integer, ForeignKey("users.id"), nullable=True) resolution_notes = Column(Text, nullable=True) # Relationships order_item = relationship("OrderItem", back_populates="exception") vendor = relationship("Vendor") resolved_product = relationship("Product") resolver = relationship("User") # Composite indexes for common queries __table_args__ = ( Index("idx_exception_vendor_status", "vendor_id", "status"), Index("idx_exception_gtin", "vendor_id", "original_gtin"), ) def __repr__(self): return ( f"" ) @property def is_pending(self) -> bool: """Check if exception is pending resolution.""" return self.status == "pending" @property def is_resolved(self) -> bool: """Check if exception has been resolved.""" return self.status == "resolved" @property def is_ignored(self) -> bool: """Check if exception has been ignored.""" return self.status == "ignored" @property def blocks_confirmation(self) -> bool: """Check if this exception blocks order confirmation.""" # Both pending and ignored exceptions block confirmation return self.status in ("pending", "ignored")