Coverage for src / idx_api / models / brokerage.py: 100%
63 statements
« prev ^ index » next coverage.py v7.13.1, created at 2025-12-28 11:16 -0700
« prev ^ index » next coverage.py v7.13.1, created at 2025-12-28 11:16 -0700
1"""Brokerage model - represents the real estate firm/company."""
3from datetime import datetime
4from typing import TYPE_CHECKING, Optional
6from sqlalchemy import JSON, Boolean, DateTime, Float, Integer, String, Text
7from sqlalchemy.orm import Mapped, mapped_column, relationship
9from idx_api.models.base import Base, TimestampMixin
11if TYPE_CHECKING:
12 from idx_api.models.agent import Agent
13 from idx_api.models.api_key import APIKey
14 from idx_api.models.broker import Broker
15 from idx_api.models.brokerage_content_source import BrokerageContentSource
16 from idx_api.models.brokerage_domain import BrokerageDomain
17 from idx_api.models.brokerage_service_area import BrokerageServiceArea
18 from idx_api.models.brokerage_vision_settings import BrokerageVisionSettings
19 from idx_api.models.client import Client
20 from idx_api.models.lead import Lead
21 from idx_api.models.tour_request import TourRequest
22 from idx_api.models.user import User
25class Brokerage(Base, TimestampMixin):
26 """Real estate brokerage/firm - represents the company/organization."""
28 __tablename__ = "brokerages"
30 id: Mapped[int] = mapped_column(primary_key=True)
31 slug: Mapped[str] = mapped_column(String(100), unique=True, index=True, nullable=False)
32 name: Mapped[str] = mapped_column(String(200), nullable=False)
33 tagline: Mapped[Optional[str]] = mapped_column(String(500))
34 description: Mapped[Optional[str]] = mapped_column(Text) # Longer marketing description
36 # NOTE: Contact information (email, phone) moved to Broker contact model
38 # Address information
39 address_street: Mapped[Optional[str]] = mapped_column(String(200))
40 address_city: Mapped[Optional[str]] = mapped_column(String(100))
41 address_state: Mapped[Optional[str]] = mapped_column(String(100))
42 address_state_abbr: Mapped[Optional[str]] = mapped_column(String(10))
43 address_zip: Mapped[Optional[str]] = mapped_column(String(20))
44 address_country: Mapped[Optional[str]] = mapped_column(String(10), default="US")
46 # Map defaults for property search
47 map_center_lat: Mapped[Optional[float]] = mapped_column(Float)
48 map_center_lng: Mapped[Optional[float]] = mapped_column(Float)
49 map_zoom: Mapped[Optional[int]] = mapped_column(Integer)
50 map_bounds_north: Mapped[Optional[float]] = mapped_column(Float)
51 map_bounds_south: Mapped[Optional[float]] = mapped_column(Float)
52 map_bounds_east: Mapped[Optional[float]] = mapped_column(Float)
53 map_bounds_west: Mapped[Optional[float]] = mapped_column(Float)
55 # Feature flags - control which features are enabled for this brokerage
56 feature_semantic_search: Mapped[bool] = mapped_column(Boolean, default=True, server_default='1')
57 feature_map_search: Mapped[bool] = mapped_column(Boolean, default=True, server_default='1')
58 feature_favorites: Mapped[bool] = mapped_column(Boolean, default=True, server_default='1')
59 feature_contact_form: Mapped[bool] = mapped_column(Boolean, default=True, server_default='1')
60 feature_virtual_tours: Mapped[bool] = mapped_column(Boolean, default=False, server_default='0')
61 feature_mortgage_calculator: Mapped[bool] = mapped_column(Boolean, default=True, server_default='1')
63 # License information (firm-level)
64 license_type: Mapped[Optional[str]] = mapped_column(String(100))
65 license_number: Mapped[Optional[str]] = mapped_column(String(100))
67 # Branding
68 logo_url: Mapped[Optional[str]] = mapped_column(String(500))
69 primary_color: Mapped[Optional[str]] = mapped_column(String(7)) # Hex color #RRGGBB
71 # Social media links
72 social_facebook: Mapped[Optional[str]] = mapped_column(String(500))
73 social_twitter: Mapped[Optional[str]] = mapped_column(String(500))
74 social_linkedin: Mapped[Optional[str]] = mapped_column(String(500))
75 social_pinterest: Mapped[Optional[str]] = mapped_column(String(500))
76 social_instagram: Mapped[Optional[str]] = mapped_column(String(500))
77 social_youtube: Mapped[Optional[str]] = mapped_column(String(500))
79 # Franchise and Specializations
80 franchise_affiliation: Mapped[Optional[str]] = mapped_column(String(200)) # e.g., "Keller Williams"
81 va_loans: Mapped[bool] = mapped_column(Boolean, default=False, server_default='0')
82 military_specialist: Mapped[bool] = mapped_column(Boolean, default=False, server_default='0')
83 website: Mapped[Optional[str]] = mapped_column(String(500)) # Brokerage website URL
85 # Hero section customization
86 hero_headline: Mapped[Optional[str]] = mapped_column(String(500))
87 hero_subtitle: Mapped[Optional[str]] = mapped_column(String(1000))
88 hero_search_examples: Mapped[Optional[list[str]]] = mapped_column(JSON)
90 # Navigation (JSON arrays of {label, href, disabled?})
91 nav_main: Mapped[Optional[list[dict]]] = mapped_column(JSON)
92 nav_footer: Mapped[Optional[list[dict]]] = mapped_column(JSON)
94 # Billing
95 stripe_customer_id: Mapped[Optional[str]] = mapped_column(String(100))
97 # Soft delete
98 disabled_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
100 # Relationships
101 brokers: Mapped[list["Broker"]] = relationship(back_populates="brokerage", cascade="all, delete-orphan")
102 agents: Mapped[list["Agent"]] = relationship(back_populates="brokerage")
103 users: Mapped[list["User"]] = relationship(back_populates="brokerage")
104 api_keys: Mapped[list["APIKey"]] = relationship(back_populates="brokerage")
105 tour_requests: Mapped[list["TourRequest"]] = relationship(
106 back_populates="assigned_brokerage", foreign_keys="TourRequest.assigned_brokerage_id"
107 )
108 domains: Mapped[list["BrokerageDomain"]] = relationship(back_populates="brokerage", cascade="all, delete-orphan")
109 service_areas: Mapped[list["BrokerageServiceArea"]] = relationship(
110 back_populates="brokerage", cascade="all, delete-orphan", order_by="BrokerageServiceArea.display_order"
111 )
112 vision_settings: Mapped[Optional["BrokerageVisionSettings"]] = relationship(
113 back_populates="brokerage", cascade="all, delete-orphan", uselist=False
114 )
115 content_source: Mapped[Optional["BrokerageContentSource"]] = relationship(
116 back_populates="brokerage", cascade="all, delete-orphan", uselist=False
117 )
118 clients: Mapped[list["Client"]] = relationship(back_populates="brokerage", cascade="all, delete-orphan")
119 leads: Mapped[list["Lead"]] = relationship(back_populates="brokerage", cascade="all, delete-orphan")
121 def __repr__(self) -> str:
122 return f"<Brokerage(id={self.id}, slug='{self.slug}', name='{self.name}')>"