Spaces:
Sleeping
Sleeping
| """ | |
| CUSTOMER Models | |
| """ | |
| from sqlalchemy import Column, String, Boolean, Integer, Text, Date, DateTime, Numeric, ForeignKey, Double | |
| from sqlalchemy.dialects.postgresql import UUID, JSONB, ARRAY | |
| from sqlalchemy.orm import relationship | |
| from app.models.base import BaseModel | |
| from app.models.enums import * | |
| class Customer(BaseModel): | |
| """ | |
| Customers (End Users) | |
| Customers are the end-users who purchase telecom services. | |
| This table stores their identity and primary contact information. | |
| Service-specific addresses are stored in sales_orders and subscriptions. | |
| Key Features: | |
| - Deduplication by phone_primary (unique constraint) | |
| - Location hierarchy: Country → Region → City → Address → Coordinates | |
| - Links to project_region for administrative region tracking | |
| - Flexible additional_metadata JSONB for future needs | |
| Business Rules: | |
| - One customer per phone number globally (prevents duplicates) | |
| - phone_primary is required and must be unique | |
| - phone_alternative can be used for alternate contact | |
| - Linked to a client (telecom operator) | |
| - Can have multiple subscriptions (one per service address) | |
| """ | |
| __tablename__ = "customers" | |
| # Organization Link | |
| client_id = Column(UUID(as_uuid=True), ForeignKey("clients.id", ondelete="RESTRICT"), nullable=False, index=True) | |
| # Identity | |
| customer_name = Column(Text, nullable=False) | |
| id_number = Column(Text, nullable=True) # National ID or passport | |
| business_name = Column(Text, nullable=True) # For business customers | |
| # Contact Information | |
| phone_primary = Column(Text, nullable=False, index=True) # Unique constraint via index | |
| phone_alternative = Column(Text, nullable=True, index=True) | |
| email = Column(Text, nullable=True) | |
| # Primary Address (where customer can be reached, not necessarily service address) | |
| # Region reference provides country/region/city hierarchy | |
| project_region_id = Column(UUID(as_uuid=True), ForeignKey("project_regions.id", ondelete="SET NULL"), nullable=True, index=True) | |
| primary_address_line1 = Column(Text, nullable=True) | |
| primary_address_line2 = Column(Text, nullable=True) | |
| primary_maps_link = Column(Text, nullable=True) # Google Maps share link (primary source) | |
| primary_latitude = Column(Double, nullable=True) | |
| primary_longitude = Column(Double, nullable=True) | |
| # Status | |
| is_active = Column(Boolean, default=True, nullable=False) | |
| # Additional metadata for future flexibility | |
| additional_metadata = Column(JSONB, default={}, nullable=False) | |
| # Relationships | |
| client = relationship("Client", back_populates="customers") | |
| project_region = relationship("ProjectRegion", foreign_keys=[project_region_id]) | |
| sales_orders = relationship("SalesOrder", back_populates="customer", lazy="dynamic") | |
| subscriptions = relationship("Subscription", back_populates="customer", lazy="dynamic") | |
| incidents = relationship("Incident", back_populates="customer", lazy="dynamic") | |
| communications = relationship("CustomerCommunication", back_populates="customer", cascade="all, delete-orphan", lazy="select") | |
| def __repr__(self): | |
| return f"<Customer {self.customer_name} ({self.phone_primary})>" | |
| def has_active_subscriptions(self) -> bool: | |
| """Check if customer has any active subscriptions""" | |
| return any(sub.status == 'active' for sub in self.subscriptions) | |
| def subscription_count(self) -> int: | |
| """Count of all subscriptions (active and inactive)""" | |
| return self.subscriptions.count() | |
| def has_location(self) -> bool: | |
| """Check if customer has location coordinates""" | |
| return self.primary_latitude is not None and self.primary_longitude is not None | |