"""Models for Fan entities.""" from __future__ import annotations from typing import List from typing import Optional from pydantic import BaseModel from pydantic import validator class ProfileUpdatedChangedField(BaseModel): """Model for Fan profile updatedFields.""" fieldName: str newValue: str | None oldValue: str | None class ProfileUpdatedV1(BaseModel): """Model for Fan ProfileUpdated messages.""" id: str crmId: str | None updatedAt: str updatedFields: List[ProfileUpdatedChangedField] class ProfileDeletedV1(BaseModel): """Model for Fan ProfileDeleted messages.""" id: str crmId: str | None deletedAt: str class SForceFanUpdate(BaseModel): """Model for a SalesForce Fan updates. A delete operation is considered an update where the Delete_My_Information__c key exists with a True value. """ Birthdate__c: Optional[str] First_Name__c: Optional[str] Last_Name__c: Optional[str] Email__c: Optional[str] Mobile_Phone__c: Optional[str] Birthday_mm_dd__c: Optional[str] Gender__c: Optional[str] Country_Region__c: Optional[str] City__c: Optional[str] Address_1__c: Optional[str] # Address_2__c: Optional[str] TBC Postal_Code__c: Optional[str] # Only for deletions Delete_My_Information__c: Optional[bool] Delete_Request_Date__c: Optional[str] Delete_Reason__c: Optional[str] @validator('Gender__c') def map_gender(cls, v: str | None) -> str | None: """Map Preference Center gender value to SalesForce.""" if v is None: return None gender_mapping = { 'MALE': 'Male', 'FEMALE': 'Female', 'PREFER_NOT_TO_ANSWER': 'Prefer not to answer', 'NON_BINARY_OTHER': 'Non-binary/Other', } return gender_mapping.get(v, v) @staticmethod def _get_mapped_field(field_name: str) -> str: """Retrieve the correct SalesForce Fan fieldname.""" sf_field_mapping = { 'dateOfBirth': 'Birthdate__c', 'firstName': 'First_Name__c', 'lastName': 'Last_Name__c', 'email': 'Email__c', 'phoneNumber': 'Mobile_Phone__c', 'birthday': 'Birthday_mm_dd__c', 'gender': 'Gender__c', 'countryCode': 'Country_Region__c', 'city': 'City__c', 'address': 'Address_1__c', 'zipCode': 'Postal_Code__c' } return sf_field_mapping.get(field_name) @classmethod def from_profile_update(cls, source_data: ProfileUpdatedV1) -> SForceFanUpdate: """Create instance from an existing ProfileUpdated.""" sf_dict = {} for updated_field in source_data.updatedFields: sf_field_name = cls._get_mapped_field(updated_field.fieldName) if sf_field_name: sf_dict[sf_field_name] = updated_field.newValue return cls(**sf_dict) @classmethod def from_profile_delete(cls, source_data: ProfileDeletedV1) -> SForceFanUpdate: """Create a deletion update instance.""" sf_dict = {} sf_dict['Delete_My_Information__c'] = True sf_dict['Delete_Reason__c'] = 'Fan Requested' sf_dict['Delete_Request_Date__c'] = source_data.deletedAt return cls(**sf_dict) def to_dict(self): """Convert to dict, excluding unset values.""" return self.dict(exclude_unset=True) # Update forward ref for ProfileUpdatedV1 in SForceFanUpdate SForceFanUpdate.update_forward_refs()