"""CRM foundation DTOs — Phase 6.0 pipelines + Phase 6.1 core entities.""" from __future__ import annotations from datetime import date, datetime from decimal import Decimal from uuid import UUID from pydantic import BaseModel, Field from app.models.types import ( AddressType, AttachmentKind, AuditAction, ContactKind, ContactMethod, ContactStatus, CrmEntityType, CustomFieldType, LeadPriority, LeadStatus, OpportunityStatus, OrganizationKind, OrganizationStatus, QuoteStatus, TagScope, ) from app.schemas.common import ORMBase # ── Lead ───────────────────────────────────────────────────────────────────── class LeadCreate(BaseModel): first_name: str = Field(default="", max_length=100) last_name: str = Field(default="", max_length=100) full_name: str | None = Field(default=None, max_length=255) title: str | None = Field(default=None, max_length=255) company: str | None = Field(default=None, max_length=255) company_name: str | None = Field(default=None, max_length=255) job_title: str | None = Field(default=None, max_length=150) email: str | None = Field(default=None, max_length=255) mobile: str | None = Field(default=None, max_length=50) phone: str | None = Field(default=None, max_length=50) website: str | None = Field(default=None, max_length=255) lead_source_id: UUID | None = None lead_status_id: UUID | None = None status: LeadStatus = LeadStatus.NEW source: str | None = Field(default=None, max_length=100) assigned_user_id: str | None = Field(default=None, max_length=100) priority: LeadPriority = LeadPriority.MEDIUM estimated_value: Decimal = Decimal("0") expected_close_date: date | None = None description: str | None = None owner_user_id: str | None = Field(default=None, max_length=100) lead_number: str | None = Field(default=None, max_length=50) contact_id: UUID | None = None organization_id: UUID | None = None class LeadUpdate(BaseModel): first_name: str | None = Field(default=None, max_length=100) last_name: str | None = Field(default=None, max_length=100) full_name: str | None = Field(default=None, max_length=255) title: str | None = Field(default=None, max_length=255) company: str | None = Field(default=None, max_length=255) job_title: str | None = Field(default=None, max_length=150) email: str | None = Field(default=None, max_length=255) mobile: str | None = Field(default=None, max_length=50) phone: str | None = Field(default=None, max_length=50) website: str | None = Field(default=None, max_length=255) lead_source_id: UUID | None = None lead_status_id: UUID | None = None status: LeadStatus | None = None source: str | None = Field(default=None, max_length=100) assigned_user_id: str | None = Field(default=None, max_length=100) priority: LeadPriority | None = None estimated_value: Decimal | None = None expected_close_date: date | None = None description: str | None = None owner_user_id: str | None = Field(default=None, max_length=100) contact_id: UUID | None = None organization_id: UUID | None = None class LeadRead(ORMBase): id: UUID tenant_id: UUID lead_number: str first_name: str last_name: str full_name: str title: str | None company: str | None job_title: str | None email: str | None mobile: str | None phone: str | None website: str | None lead_source_id: UUID | None lead_status_id: UUID | None status: LeadStatus source: str | None assigned_user_id: str | None priority: LeadPriority estimated_value: Decimal expected_close_date: date | None description: str | None owner_user_id: str contact_id: UUID | None organization_id: UUID | None is_deleted: bool created_by: str | None updated_by: str | None created_at: datetime updated_at: datetime class LeadAssignRequest(BaseModel): assigned_user_id: str = Field(max_length=100) note: str | None = None # ── Contact ────────────────────────────────────────────────────────────────── class ContactCreate(BaseModel): first_name: str = Field(max_length=100) last_name: str = Field(max_length=100) kind: ContactKind = ContactKind.BUSINESS contact_type_id: UUID | None = None email: str | None = Field(default=None, max_length=255) phone: str | None = Field(default=None, max_length=50) emails: list[str] | None = None phones: list[str] | None = None default_contact_method: ContactMethod | None = None social_links: dict[str, str] | None = None birthday: date | None = None job_title: str | None = Field(default=None, max_length=150) job_position: str | None = Field(default=None, max_length=150) organization_id: UUID | None = None owner_user_id: str | None = Field(default=None, max_length=100) notes_summary: str | None = None status: ContactStatus = ContactStatus.ACTIVE class ContactUpdate(BaseModel): first_name: str | None = Field(default=None, max_length=100) last_name: str | None = Field(default=None, max_length=100) kind: ContactKind | None = None contact_type_id: UUID | None = None email: str | None = Field(default=None, max_length=255) phone: str | None = Field(default=None, max_length=50) emails: list[str] | None = None phones: list[str] | None = None default_contact_method: ContactMethod | None = None social_links: dict[str, str] | None = None birthday: date | None = None job_title: str | None = Field(default=None, max_length=150) job_position: str | None = Field(default=None, max_length=150) organization_id: UUID | None = None owner_user_id: str | None = Field(default=None, max_length=100) notes_summary: str | None = None status: ContactStatus | None = None class ContactRead(ORMBase): id: UUID tenant_id: UUID first_name: str last_name: str full_name: str kind: ContactKind contact_type_id: UUID | None email: str | None phone: str | None emails: list | None phones: list | None default_contact_method: ContactMethod | None social_links: dict | None birthday: date | None job_title: str | None job_position: str | None status: ContactStatus organization_id: UUID | None owner_user_id: str | None notes_summary: str | None is_deleted: bool created_by: str | None updated_by: str | None created_at: datetime updated_at: datetime # ── Organization ───────────────────────────────────────────────────────────── class OrganizationCreate(BaseModel): name: str = Field(max_length=255) kind: OrganizationKind = OrganizationKind.COMPANY legal_name: str | None = Field(default=None, max_length=255) trade_name: str | None = Field(default=None, max_length=255) national_identifier: str | None = Field(default=None, max_length=50) tax_number: str | None = Field(default=None, max_length=50) organization_type_id: UUID | None = None organization_industry_id: UUID | None = None industry: str | None = Field(default=None, max_length=150) website: str | None = Field(default=None, max_length=255) email: str | None = Field(default=None, max_length=255) phone: str | None = Field(default=None, max_length=50) parent_organization_id: UUID | None = None owner_user_id: str | None = Field(default=None, max_length=100) description: str | None = None status: OrganizationStatus = OrganizationStatus.ACTIVE class OrganizationUpdate(BaseModel): name: str | None = Field(default=None, max_length=255) kind: OrganizationKind | None = None legal_name: str | None = Field(default=None, max_length=255) trade_name: str | None = Field(default=None, max_length=255) national_identifier: str | None = Field(default=None, max_length=50) tax_number: str | None = Field(default=None, max_length=50) organization_type_id: UUID | None = None organization_industry_id: UUID | None = None industry: str | None = Field(default=None, max_length=150) website: str | None = Field(default=None, max_length=255) email: str | None = Field(default=None, max_length=255) phone: str | None = Field(default=None, max_length=50) parent_organization_id: UUID | None = None owner_user_id: str | None = Field(default=None, max_length=100) description: str | None = None status: OrganizationStatus | None = None class OrganizationRead(ORMBase): id: UUID tenant_id: UUID name: str kind: OrganizationKind legal_name: str | None trade_name: str | None national_identifier: str | None tax_number: str | None organization_type_id: UUID | None organization_industry_id: UUID | None industry: str | None website: str | None email: str | None phone: str | None parent_organization_id: UUID | None status: OrganizationStatus owner_user_id: str | None description: str | None is_deleted: bool created_by: str | None updated_by: str | None created_at: datetime updated_at: datetime # ── Lookups ────────────────────────────────────────────────────────────────── class LookupCreate(BaseModel): code: str = Field(max_length=50) name: str = Field(max_length=150) description: str | None = None is_active: bool = True class LeadStatusCreate(BaseModel): code: str = Field(max_length=50) name: str = Field(max_length=150) position: int = 0 is_terminal: bool = False is_converted: bool = False is_active: bool = True color: str | None = Field(default=None, max_length=20) class LookupRead(ORMBase): id: UUID tenant_id: UUID code: str name: str is_active: bool created_at: datetime class LeadSourceRead(LookupRead): description: str | None class LeadStatusRead(ORMBase): id: UUID tenant_id: UUID code: str name: str position: int is_terminal: bool is_converted: bool is_active: bool color: str | None created_at: datetime # ── Tags / Addresses / Notes / Attachments / Custom Fields / Audit ─────────── class TagCreate(BaseModel): name: str = Field(max_length=100) color: str | None = Field(default=None, max_length=20) group_name: str | None = Field(default=None, max_length=100) scope: TagScope = TagScope.ANY is_active: bool = True class TagRead(ORMBase): id: UUID tenant_id: UUID name: str color: str | None group_name: str | None scope: TagScope is_active: bool created_at: datetime class TagAssignRequest(BaseModel): tag_id: UUID class AddressCreate(BaseModel): entity_type: CrmEntityType entity_id: UUID address_type: AddressType label: str | None = Field(default=None, max_length=100) line1: str = Field(max_length=255) line2: str | None = Field(default=None, max_length=255) city: str | None = Field(default=None, max_length=100) state: str | None = Field(default=None, max_length=100) postal_code: str | None = Field(default=None, max_length=30) country: str | None = Field(default=None, max_length=100) latitude: Decimal | None = None longitude: Decimal | None = None is_default: bool = False class AddressUpdate(BaseModel): address_type: AddressType | None = None label: str | None = Field(default=None, max_length=100) line1: str | None = Field(default=None, max_length=255) line2: str | None = Field(default=None, max_length=255) city: str | None = Field(default=None, max_length=100) state: str | None = Field(default=None, max_length=100) postal_code: str | None = Field(default=None, max_length=30) country: str | None = Field(default=None, max_length=100) latitude: Decimal | None = None longitude: Decimal | None = None is_default: bool | None = None class AddressRead(ORMBase): id: UUID tenant_id: UUID entity_type: CrmEntityType entity_id: UUID address_type: AddressType label: str | None line1: str line2: str | None city: str | None state: str | None postal_code: str | None country: str | None latitude: Decimal | None longitude: Decimal | None is_default: bool is_deleted: bool created_at: datetime updated_at: datetime class NoteCreate(BaseModel): entity_type: CrmEntityType entity_id: UUID body: str body_format: str = "richtext" is_pinned: bool = False class NoteUpdate(BaseModel): body: str | None = None is_pinned: bool | None = None class NoteRead(ORMBase): id: UUID tenant_id: UUID entity_type: CrmEntityType entity_id: UUID body: str body_format: str version: int is_pinned: bool is_deleted: bool created_by: str | None updated_by: str | None created_at: datetime updated_at: datetime class AttachmentCreate(BaseModel): entity_type: CrmEntityType entity_id: UUID file_storage_id: str = Field(max_length=100) file_name: str = Field(max_length=255) content_type: str | None = Field(default=None, max_length=150) size_bytes: int | None = None kind: AttachmentKind = AttachmentKind.DOCUMENT description: str | None = None class AttachmentRead(ORMBase): id: UUID tenant_id: UUID entity_type: CrmEntityType entity_id: UUID file_storage_id: str file_name: str content_type: str | None size_bytes: int | None kind: AttachmentKind description: str | None is_deleted: bool created_by: str | None created_at: datetime class CustomFieldCreate(BaseModel): entity_type: CrmEntityType field_key: str = Field(max_length=100) label: str = Field(max_length=150) field_type: CustomFieldType is_required: bool = False options: list[str] | None = None validation_rules: dict | None = None position: int = 0 is_active: bool = True class CustomFieldRead(ORMBase): id: UUID tenant_id: UUID entity_type: CrmEntityType field_key: str label: str field_type: CustomFieldType is_required: bool options: list | None validation_rules: dict | None position: int is_active: bool created_at: datetime class CustomFieldValueUpsert(BaseModel): custom_field_id: UUID entity_type: CrmEntityType entity_id: UUID value_text: str | None = None value_number: Decimal | None = None value_boolean: bool | None = None value_date: date | None = None value_json: list | dict | None = None class CustomFieldValueRead(ORMBase): id: UUID tenant_id: UUID custom_field_id: UUID entity_type: CrmEntityType entity_id: UUID value_text: str | None value_number: Decimal | None value_boolean: bool | None value_date: date | None value_json: list | dict | None created_at: datetime class AuditRead(ORMBase): id: UUID tenant_id: UUID entity_type: str entity_id: UUID action: AuditAction actor_user_id: str | None changes: dict | None message: str | None created_at: datetime class LeadAssignmentRead(ORMBase): id: UUID tenant_id: UUID lead_id: UUID assigned_user_id: str assigned_by: str | None assigned_at: datetime note: str | None is_current: bool created_at: datetime # ── Pipeline / Opportunity (Phase 6.2) / Activity (Phase 6.3) / Quote ──────── from app.schemas.collaboration import ( # noqa: E402 SalesActivityCreate, SalesActivityRead, SalesActivityUpdate, ) from app.schemas.sales_engine import ( # noqa: E402 OpportunityCreate, OpportunityLoseRequest, OpportunityRead, OpportunityStageChangeRequest, OpportunityUpdate, OpportunityWinRequest, PipelineCreate, PipelineRead, PipelineStageCreate, PipelineStageRead, PipelineStageUpdate, PipelineUpdate, StageHistoryRead, ) class QuoteCreate(BaseModel): title: str = Field(max_length=255) quote_number: str = Field(max_length=50) amount: Decimal = Decimal("0") currency_code: str = Field(default="IRR", max_length=3) valid_until: date | None = None opportunity_id: UUID | None = None organization_id: UUID | None = None contact_id: UUID | None = None owner_user_id: str | None = Field(default=None, max_length=100) notes: str | None = None class QuoteUpdate(BaseModel): title: str | None = Field(default=None, max_length=255) amount: Decimal | None = None currency_code: str | None = Field(default=None, max_length=3) valid_until: date | None = None opportunity_id: UUID | None = None organization_id: UUID | None = None contact_id: UUID | None = None owner_user_id: str | None = Field(default=None, max_length=100) notes: str | None = None status: QuoteStatus | None = None class QuoteRead(ORMBase): id: UUID tenant_id: UUID title: str quote_number: str status: QuoteStatus amount: Decimal currency_code: str valid_until: date | None opportunity_id: UUID | None organization_id: UUID | None contact_id: UUID | None owner_user_id: str | None notes: str | None created_at: datetime updated_at: datetime