package model import ( "encoding/json" "strings" "gorm.io/datatypes" "gorm.io/gorm" ) // Contact represents a customer/end-user across channels. // Reference: Chatwoot app/models/contact.rb — P2B M4 spec type Contact struct { Base AccountID uint `gorm:"index;not null" json:"account_id"` Name string `gorm:"size:255" json:"name"` MiddleName string `gorm:"size:255;default:''" json:"middle_name,omitempty"` LastName string `gorm:"size:255;default:''" json:"last_name,omitempty"` Email string `gorm:"size:255;index" json:"email,omitempty"` PhoneNumber string `gorm:"size:50;index" json:"phone_number,omitempty"` AvatarURL string `gorm:"size:512" json:"avatar_url,omitempty"` Identifier string `gorm:"size:255;index" json:"identifier,omitempty"` CountryCode string `gorm:"size:10;default:''" json:"country_code,omitempty"` Location string `gorm:"size:255;default:''" json:"location,omitempty"` AdditionalAttributes datatypes.JSON `gorm:"type:jsonb;default:'{}'" json:"additional_attributes,omitempty"` CustomAttributes datatypes.JSON `gorm:"type:jsonb;default:'{}'" json:"custom_attributes,omitempty"` Blocked bool `gorm:"default:false" json:"blocked"` ContactType string `gorm:"size:50;default:''" json:"contact_type,omitempty"` SourceID string `gorm:"size:255" json:"source_id,omitempty"` CompanyID *uint `json:"company_id,omitempty"` LastActivityAt *int64 `gorm:"index" json:"last_activity_at,omitempty"` } func (Contact) TableName() string { return "contacts" } // BeforeSave syncs location/country_code from additional_attributes and upgrades contact_type. // Reference: Chatwoot before_save :sync_contact_attributes → Contacts::SyncAttributes func (c *Contact) BeforeSave(tx *gorm.DB) error { c.syncLocationAndCountryCode() c.syncContactType() return nil } // syncLocationAndCountryCode copies city → Location and country → CountryCode from additional_attributes. // Reference: Chatwoot Contacts::SyncAttributes#update_contact_location_and_country_code func (c *Contact) syncLocationAndCountryCode() { if c.AdditionalAttributes != nil { attrs := make(map[string]interface{}) if err := json.Unmarshal(c.AdditionalAttributes, &attrs); err == nil { if city, ok := attrs["city"].(string); ok && city != "" { c.Location = city } if country, ok := attrs["country"].(string); ok && country != "" { c.CountryCode = country } } } } // syncContactType upgrades visitor → lead if email/phone/social details present. // Reference: Chatwoot Contacts::SyncAttributes#set_contact_type func (c *Contact) syncContactType() { if c.ContactType != "visitor" { return } if c.Email != "" || c.PhoneNumber != "" { c.ContactType = "lead" return } // Check social details in additional_attributes (social_* keys) if c.AdditionalAttributes != nil { attrs := make(map[string]interface{}) if err := json.Unmarshal(c.AdditionalAttributes, &attrs); err == nil { for key, val := range attrs { if strings.HasPrefix(key, "social_") { if s, ok := val.(string); ok && s != "" { c.ContactType = "lead" return } } } } } }