package domain import ( "context" "encoding/json" "time" "gorm.io/datatypes" ) // unmarshalStringSlice safely unmarshals datatypes.JSON to []string func unmarshalStringSlice(data datatypes.JSON) []string { if len(data) == 0 { return []string{} } var result []string if err := json.Unmarshal(data, &result); err != nil { // If it's not an array, return empty slice return []string{} } return result } // MarshalJSON custom marshaling to ensure arrays are always arrays func (o Organization) MarshalJSON() ([]byte, error) { type Alias Organization return json.Marshal(&struct { GalleryImages []string `json:"GalleryImages"` Certifications []string `json:"Certifications"` BusinessFocus []string `json:"BusinessFocus"` TechnicalExpertise []string `json:"TechnicalExpertise"` AvailableTechnology []string `json:"AvailableTechnology"` ManagementSystems []string `json:"ManagementSystems"` TrustNetwork []string `json:"TrustNetwork"` ExistingSymbioticRelationships []string `json:"ExistingSymbioticRelationships"` *Alias }{ GalleryImages: unmarshalStringSlice(o.GalleryImages), Certifications: unmarshalStringSlice(o.Certifications), BusinessFocus: unmarshalStringSlice(o.BusinessFocus), TechnicalExpertise: unmarshalStringSlice(o.TechnicalExpertise), AvailableTechnology: unmarshalStringSlice(o.AvailableTechnology), ManagementSystems: unmarshalStringSlice(o.ManagementSystems), TrustNetwork: unmarshalStringSlice(o.TrustNetwork), ExistingSymbioticRelationships: unmarshalStringSlice(o.ExistingSymbioticRelationships), Alias: (*Alias)(&o), }) } // OrganizationSubtype defines the category of organization type OrganizationSubtype string const ( SubtypeCommercial OrganizationSubtype = "commercial" SubtypeCultural OrganizationSubtype = "cultural" SubtypeGovernment OrganizationSubtype = "government" SubtypeReligious OrganizationSubtype = "religious" SubtypeEducational OrganizationSubtype = "educational" SubtypeInfrastructure OrganizationSubtype = "infrastructure" SubtypeHealthcare OrganizationSubtype = "healthcare" SubtypeOther OrganizationSubtype = "other" ) // SectorStat represents sector statistics type SectorStat struct { Sector string `json:"sector"` Count int `json:"count"` } // LegalForm represents the legal structure of a business organization type LegalForm string const ( LegalFormLLC LegalForm = "LLC" LegalFormCorporation LegalForm = "corporation" LegalFormPartnership LegalForm = "partnership" LegalFormSoleProprietorship LegalForm = "sole_proprietorship" LegalFormGmbH LegalForm = "GmbH" LegalFormUG LegalForm = "UG" LegalFormAG LegalForm = "AG" ) // SupplyChainRole represents the organization's role in the supply chain type SupplyChainRole string const ( RoleManufacturer SupplyChainRole = "manufacturer" RoleSupplier SupplyChainRole = "supplier" RoleDistributor SupplyChainRole = "distributor" RoleConsumer SupplyChainRole = "consumer" ) // PrimaryContact stores contact information type PrimaryContact struct { Email string `json:"email"` Phone string `json:"phone"` } // ProductJSON represents a product offered by an organization (for API/JSON serialization) type ProductJSON struct { Name string `json:"name"` Category string `json:"category"` UnitPrice float64 `json:"unit_price"` MOQ int `json:"moq,omitempty"` Certifications []string `json:"certifications,omitempty"` } // ServiceJSON represents a service offered by an organization (for API/JSON serialization) type ServiceJSON struct { Type string `json:"type"` // maintenance, consulting, transport, inspection Domain string `json:"domain"` // compressors, HVAC, waste_management, etc. OnSite bool `json:"on_site"` // on-site or remote HourlyRate float64 `json:"hourly_rate"` // €/hour ServiceAreaKm float64 `json:"service_area_km"` // service radius Certifications []string `json:"certifications,omitempty"` } // ServiceNeedJSON represents a service needed by an organization (for API/JSON serialization) type ServiceNeedJSON struct { Type string `json:"type"` Urgency string `json:"urgency,omitempty"` // low, medium, high Budget float64 `json:"budget,omitempty"` PreferredProviders []string `json:"preferred_providers,omitempty"` // Organization IDs } type Organization struct { ID string `gorm:"primaryKey;type:text" json:"ID"` Name string `gorm:"not null;type:text;index" json:"Name"` // Primary name (Russian) // Subtype categorization (commercial, healthcare, educational, etc.) Subtype OrganizationSubtype `gorm:"not null;type:varchar(50);index" json:"Subtype"` Sector string `gorm:"type:text;index" json:"Sector"` // NACE code or category // Basic information Description string `gorm:"type:text" json:"Description"` LogoURL string `gorm:"column:logo_url;type:text" json:"LogoURL"` GalleryImages datatypes.JSON `gorm:"column:gallery_images;type:jsonb;default:'[]'" json:"-"` // []string - URLs of gallery images Website string `gorm:"type:text" json:"Website"` // Geolocation (from primary address) Latitude float64 `gorm:"type:double precision;index:idx_org_location" json:"Latitude"` Longitude float64 `gorm:"type:double precision;index:idx_org_location" json:"Longitude"` // Business-specific fields (for commercial subtype) LegalForm string `gorm:"type:varchar(50)" json:"LegalForm"` PrimaryContact datatypes.JSON `gorm:"type:jsonb" json:"PrimaryContact"` // PrimaryContact IndustrialSector string `gorm:"type:varchar(10)" json:"IndustrialSector"` CompanySize int `gorm:"type:integer" json:"CompanySize"` YearsOperation int `gorm:"type:integer" json:"YearsOperation"` SupplyChainRole SupplyChainRole `gorm:"type:varchar(50)" json:"SupplyChainRole"` Certifications datatypes.JSON `gorm:"default:'[]'" json:"-"` // []string BusinessFocus datatypes.JSON `gorm:"default:'[]'" json:"-"` // []string StrategicVision string `gorm:"type:text" json:"StrategicVision"` DriversBarriers string `gorm:"type:text" json:"DriversBarriers"` ReadinessMaturity int `gorm:"type:integer;default:3" json:"ReadinessMaturity"` TrustScore float64 `gorm:"type:double precision;default:0.7" json:"TrustScore"` TechnicalExpertise datatypes.JSON `gorm:"default:'[]'" json:"-"` // []string AvailableTechnology datatypes.JSON `gorm:"default:'[]'" json:"-"` // []string ManagementSystems datatypes.JSON `gorm:"default:'[]'" json:"-"` // []string // Products and Services SellsProducts datatypes.JSON `gorm:"default:'[]'" json:"SellsProducts"` // []Product OffersServices datatypes.JSON `gorm:"default:'[]'" json:"OffersServices"` // []Service NeedsServices datatypes.JSON `gorm:"default:'[]'" json:"NeedsServices"` // []ServiceNeed // Historical/cultural building fields (for non-commercial subtypes) YearBuilt string `gorm:"type:text" json:"YearBuilt"` BuilderOwner string `gorm:"type:text" json:"BuilderOwner"` Architect string `gorm:"type:text" json:"Architect"` OriginalPurpose string `gorm:"type:text" json:"OriginalPurpose"` CurrentUse string `gorm:"type:text" json:"CurrentUse"` Style string `gorm:"type:text" json:"Style"` Materials string `gorm:"type:text" json:"Materials"` Storeys int `gorm:"type:integer" json:"Storeys"` HeritageStatus string `gorm:"type:text" json:"HeritageStatus"` // Metadata Verified bool `gorm:"default:false;index" json:"Verified"` Notes string `gorm:"type:text" json:"Notes"` Sources datatypes.JSON `gorm:"type:jsonb" json:"Sources"` // Relationships (will be populated via associations) TrustNetwork datatypes.JSON `gorm:"type:jsonb;default:'[]'" json:"-"` // []string - Organization IDs ExistingSymbioticRelationships datatypes.JSON `gorm:"type:jsonb;default:'[]'" json:"-"` // []string - Organization IDs // Products/materials for legacy compatibility Products datatypes.JSON `gorm:"type:jsonb" json:"Products"` // Timestamps CreatedAt time.Time `gorm:"autoCreateTime;index" json:"CreatedAt"` UpdatedAt time.Time `gorm:"autoUpdateTime" json:"UpdatedAt"` // Associations Addresses []Address `gorm:"many2many:organization_addresses;" json:"Addresses,omitempty"` OwnedSites []Site `gorm:"foreignKey:OwnerOrganizationID" json:"OwnedSites,omitempty"` OperatingSites []Site `gorm:"many2many:site_operating_organizations;" json:"OperatingSites,omitempty"` ResourceFlows []ResourceFlow `gorm:"foreignKey:OrganizationID" json:"ResourceFlows,omitempty"` } // TableName specifies the table name for GORM func (Organization) TableName() string { return "organizations" } // PrimaryAddress returns the primary (headquarters) address formatted string func (o *Organization) PrimaryAddress() string { for _, addr := range o.Addresses { if addr.AddressType == AddressTypeHeadquarters { if addr.FormattedRu != "" { return addr.FormattedRu } if addr.FormattedEn != "" { return addr.FormattedEn } } } // Fallback to any address if no headquarters found if len(o.Addresses) > 0 { if o.Addresses[0].FormattedRu != "" { return o.Addresses[0].FormattedRu } if o.Addresses[0].FormattedEn != "" { return o.Addresses[0].FormattedEn } } return "" } type OrganizationRepository interface { Create(ctx context.Context, org *Organization) error GetByID(ctx context.Context, id string) (*Organization, error) GetAll(ctx context.Context) ([]*Organization, error) Update(ctx context.Context, org *Organization) error Delete(ctx context.Context, id string) error GetBySector(ctx context.Context, sector string) ([]*Organization, error) GetBySubtype(ctx context.Context, subtype OrganizationSubtype) ([]*Organization, error) GetWithinRadius(ctx context.Context, lat, lng, radiusKm float64) ([]*Organization, error) GetByCertification(ctx context.Context, cert string) ([]*Organization, error) Search(ctx context.Context, query string, limit int) ([]*Organization, error) SearchSuggestions(ctx context.Context, query string, limit int) ([]string, error) GetSectorStats(ctx context.Context, limit int) ([]SectorStat, error) }