from sqlalchemy import Column, Integer, String, Text, DateTime, Boolean, ForeignKey, Time, Float from sqlalchemy.orm import relationship from sqlalchemy.sql import func from pgvector.sqlalchemy import Vector from database import Base class Customer(Base): __tablename__ = "customers" id = Column(Integer, primary_key=True, index=True) email = Column(String, unique=True, index=True, nullable=False) hashed_password = Column(String, nullable=True) # Nullable for Google users full_name = Column(String) phone = Column(String) location = Column(String, nullable=True) # Customer location/city profile_picture = Column(String, nullable=True) # URL from Google is_active = Column(Boolean, default=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) # Google OAuth google_id = Column(String, nullable=True, index=True) # Preferences skin_type = Column(String, nullable=True) # e.g., "Oily", "Dry", "Combination", "Sensitive", "Normal" categories = Column(Text, nullable=True) # JSON array: ["Skincare", "Haircare", "Makeup"] budget_range = Column(String, nullable=True) # e.g., "0-50", "50-100", "100-200", "200+" # Relationships payments = relationship("Payment", back_populates="customer", foreign_keys="Payment.customer_id") class ServiceProvider(Base): __tablename__ = "service_providers" id = Column(Integer, primary_key=True, index=True) email = Column(String, unique=True, index=True, nullable=False) hashed_password = Column(String, nullable=True) # Nullable for Google users full_name = Column(String) business_name = Column(String) phone = Column(String) city = Column(String) bio = Column(Text) profile_photo = Column(String, nullable=True) # File path or URL is_active = Column(Boolean, default=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) # New fields for provider signup cnic_id = Column(String, nullable=True) # CNIC/ID Number certificates = Column(Text, nullable=True) # Certifications/Qualifications business_license = Column(String, nullable=True) # Business license file path or number # Google OAuth google_id = Column(String, nullable=True, index=True) # Removed unique=True to allow multiple NULLs profile_picture = Column(String, nullable=True) # URL from Google # Timezone and Calendar Integration timezone = Column(String, default="UTC") # e.g., "America/New_York", "Asia/Karachi" google_calendar_id = Column(String, nullable=True) # Google Calendar ID for integration google_calendar_access_token = Column(Text, nullable=True) # Encrypted access token google_calendar_refresh_token = Column(Text, nullable=True) # Encrypted refresh token # Provider Level (based on ratings) level = Column(String, default="beginner") # beginner, skilled, expert # Stripe Connect (payouts) stripe_account_id = Column(String, nullable=True) # Connected account ID (acct_xxx) stripe_onboarding_complete = Column(Boolean, default=False) # Bank/payout details completed # AI Assistant / Provider Embeddings embedding = Column(Vector(384), nullable=True) # pgvector mapping -> vector(384) # Relationships portfolio_items = relationship("PortfolioItem", back_populates="provider", cascade="all, delete-orphan") services = relationship("Service", back_populates="provider", cascade="all, delete-orphan") # Use provider_id as the primary FK linking bookings to this provider bookings = relationship( "Booking", back_populates="provider", cascade="all, delete-orphan", foreign_keys="Booking.provider_id", ) payments = relationship("Payment", back_populates="provider", foreign_keys="Payment.provider_id") class PortfolioItem(Base): __tablename__ = "portfolio_items" id = Column(Integer, primary_key=True, index=True) provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=False) title = Column(String, nullable=False) description = Column(Text, nullable=True) experience_details = Column(Text, nullable=True) image_url = Column(String, nullable=True) video_url = Column(String, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) provider = relationship("ServiceProvider", back_populates="portfolio_items") class Service(Base): __tablename__ = "services" id = Column(Integer, primary_key=True, index=True) provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=False) name = Column(String, nullable=False) category = Column(String, nullable=True) description = Column(Text, nullable=True) price = Column(String, nullable=False) duration = Column(String, nullable=True) # e.g., "60 minutes" is_active = Column(Boolean, default=True) availability_schedule = Column(Text, nullable=True) # JSON string for backward compatibility created_at = Column(DateTime(timezone=True), server_default=func.now()) provider = relationship("ServiceProvider", back_populates="services") bookings = relationship("Booking", back_populates="service", cascade="all, delete-orphan") time_slots = relationship("TimeSlot", back_populates="service", cascade="all, delete-orphan") class TimeSlot(Base): __tablename__ = "time_slots" id = Column(Integer, primary_key=True, index=True) service_id = Column(Integer, ForeignKey("services.id"), nullable=False) slot_date = Column(DateTime(timezone=True), nullable=False) is_available = Column(Boolean, default=True) # Can be marked unavailable by provider created_at = Column(DateTime(timezone=True), server_default=func.now()) service = relationship("Service", back_populates="time_slots") booking = relationship("Booking", back_populates="time_slot", uselist=False) # One booking per slot class Booking(Base): __tablename__ = "bookings" id = Column(Integer, primary_key=True, index=True) customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False) provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=False) service_id = Column(Integer, ForeignKey("services.id"), nullable=False) time_slot_id = Column(Integer, ForeignKey("time_slots.id"), nullable=True) # Reference to time slot booking_date = Column(DateTime(timezone=True), nullable=False) # Kept for backward compatibility end_date = Column(DateTime(timezone=True), nullable=True) # Calculated from service duration status = Column(String, default="pending") # pending, confirmed, completed, cancelled, rejected # Optional duplicated status field for richer workflows booking_status = Column(String, nullable=True) # Mirrors/extends status when using advanced flows notes = Column(Text, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) # Google Calendar Integration google_calendar_event_id = Column(String, nullable=True) # Google Calendar event ID # Notification tracking reminder_sent = Column(Boolean, default=False) # Payment Integration (card capture + escrow + payouts) payment_status = Column( String, default="UNPAID" ) # UNPAID, HELD_IN_ESCROW, RELEASED_TO_PROVIDER, REFUNDED, FAILED payment_id = Column(Integer, ForeignKey("payments.id"), nullable=True) # Reference to payment # Stripe identifiers (stored on booking for escrow / payouts / refunds) stripe_payment_intent_id = Column(String, nullable=True, index=True) stripe_charge_id = Column(String, nullable=True, index=True) stripe_transfer_id = Column(String, nullable=True, index=True) stripe_refund_id = Column(String, nullable=True, index=True) # Provider assignment tracking (original/assigned provider) original_provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=True) assigned_provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=True) customer = relationship("Customer") provider = relationship( "ServiceProvider", back_populates="bookings", foreign_keys=[provider_id], ) # Relationships for advanced provider assignment original_provider = relationship( "ServiceProvider", foreign_keys=[original_provider_id], lazy="joined", ) assigned_provider = relationship( "ServiceProvider", foreign_keys=[assigned_provider_id], lazy="joined", ) service = relationship("Service", back_populates="bookings") time_slot = relationship("TimeSlot", back_populates="booking") payment = relationship("Payment", back_populates="booking", uselist=False, foreign_keys="[Payment.booking_id]", primaryjoin="Booking.id == Payment.booking_id") class Rating(Base): """Customer rating for a completed booking/service.""" __tablename__ = "ratings" id = Column(Integer, primary_key=True, index=True) booking_id = Column(Integer, ForeignKey("bookings.id"), unique=True, nullable=False) customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False) provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=False) service_id = Column(Integer, ForeignKey("services.id"), nullable=False) rating = Column(Integer, nullable=False) # 1-5 comment = Column(Text, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) class Payment(Base): __tablename__ = "payments" id = Column(Integer, primary_key=True, index=True) booking_id = Column(Integer, ForeignKey("bookings.id"), nullable=False) customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False) provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=False) # Payment Details amount = Column(String, nullable=False) # Amount in currency (e.g., "50.00") currency = Column(String, default="USD") # Currency code payment_method = Column(String, nullable=True) # card, bank_transfer, etc. # Stripe Integration stripe_payment_intent_id = Column(String, nullable=True, index=True) # Stripe Payment Intent ID stripe_charge_id = Column(String, nullable=True) # Stripe Charge ID stripe_customer_id = Column(String, nullable=True) # Stripe Customer ID # Payment Status status = Column(String, default="pending") # pending, succeeded, failed, refunded, cancelled failure_reason = Column(Text, nullable=True) # Reason if payment failed # Timestamps created_at = Column(DateTime(timezone=True), server_default=func.now()) paid_at = Column(DateTime(timezone=True), nullable=True) refunded_at = Column(DateTime(timezone=True), nullable=True) # Relationships booking = relationship("Booking", back_populates="payment", foreign_keys=[booking_id]) customer = relationship("Customer", back_populates="payments", foreign_keys=[customer_id]) provider = relationship("ServiceProvider", back_populates="payments", foreign_keys=[provider_id]) class Notification(Base): """General-purpose notification for customers and providers.""" __tablename__ = "notifications" id = Column(Integer, primary_key=True, index=True) # One of customer_id or provider_id must be set customer_id = Column(Integer, ForeignKey("customers.id"), nullable=True, index=True) provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=True, index=True) booking_id = Column(Integer, ForeignKey("bookings.id"), nullable=True) type = Column(String, nullable=False) # booking_cancelled_by_provider, standby_added, refund_processed, standby_selected title = Column(String, nullable=False) message = Column(Text, nullable=False) is_read = Column(Boolean, default=False) data = Column(Text, nullable=True) # JSON string for extra payload (e.g. standby provider list) created_at = Column(DateTime(timezone=True), server_default=func.now()) customer = relationship("Customer") provider = relationship("ServiceProvider") class SkinAnalysis(Base): """Stores AI skin analysis results for a customer (text only, no image).""" __tablename__ = "skin_analyses" id = Column(Integer, primary_key=True, index=True) customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False) results = Column(Text, nullable=False) # JSON string: list of {label, display, score, rank} image_url = Column(String, nullable=True) # Always None — kept for future use notes = Column(Text, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) customer = relationship("Customer") # Standby Support System Models - Re-enabled for provider cancellation workflow