package service import ( "bugulma/backend/internal/domain" "bugulma/backend/internal/repository" "context" "encoding/json" "log" "time" "github.com/google/uuid" "gorm.io/datatypes" ) // OrganizationService handles business logic for organizations type OrganizationService struct { repo domain.OrganizationRepository graphRepo *repository.GraphOrganizationRepository } // NewOrganizationService creates a new organization service func NewOrganizationService(repo domain.OrganizationRepository, graphRepo *repository.GraphOrganizationRepository) *OrganizationService { return &OrganizationService{ repo: repo, graphRepo: graphRepo, } } // SetGraphRepository sets the graph repository (for optional graph operations) func (s *OrganizationService) SetGraphRepository(graphRepo *repository.GraphOrganizationRepository) { s.graphRepo = graphRepo } // CreateOrganizationRequest represents the request to create an organization type CreateOrganizationRequest struct { // Required fields Name string Subtype domain.OrganizationSubtype Sector string // Basic information Description string LogoURL string GalleryImages []string Website string Address string Latitude float64 Longitude float64 // Business-specific fields LegalForm string PrimaryContactEmail string PrimaryContactPhone string IndustrialSector string CompanySize int YearsOperation int SupplyChainRole domain.SupplyChainRole Certifications []string BusinessFocus []string StrategicVision string DriversBarriers string ReadinessMaturity *int TrustScore *float64 // Technical capabilities TechnicalExpertise []string AvailableTechnology []string ManagementSystems []string // Products and Services SellsProducts []domain.ProductJSON OffersServices []domain.ServiceJSON NeedsServices []domain.ServiceNeedJSON // Cultural/Historical building fields YearBuilt string BuilderOwner string Architect string OriginalPurpose string CurrentUse string Style string Materials string Storeys *int HeritageStatus string // Metadata Verified bool Notes string Sources []string // Relationships TrustNetwork []string ExistingSymbioticRelationships []string } // Create creates a new organization func (s *OrganizationService) Create(ctx context.Context, req CreateOrganizationRequest) (*domain.Organization, error) { // Marshal contact info contactInfo := domain.PrimaryContact{ Email: req.PrimaryContactEmail, Phone: req.PrimaryContactPhone, } contactJSON, _ := json.Marshal(contactInfo) // Marshal arrays certsJSON, _ := json.Marshal(req.Certifications) focusJSON, _ := json.Marshal(req.BusinessFocus) techExpertiseJSON, _ := json.Marshal(req.TechnicalExpertise) technologyJSON, _ := json.Marshal(req.AvailableTechnology) managementJSON, _ := json.Marshal(req.ManagementSystems) trustNetJSON, _ := json.Marshal(req.TrustNetwork) symbiosisJSON, _ := json.Marshal(req.ExistingSymbioticRelationships) galleryImagesJSON, _ := json.Marshal(req.GalleryImages) // Marshal complex JSON objects productsJSON, _ := json.Marshal(req.SellsProducts) servicesJSON, _ := json.Marshal(req.OffersServices) needsJSON, _ := json.Marshal(req.NeedsServices) sourcesJSON, _ := json.Marshal(req.Sources) // Set defaults readinessMaturity := 3 if req.ReadinessMaturity != nil { readinessMaturity = *req.ReadinessMaturity } trustScore := 0.7 if req.TrustScore != nil { trustScore = *req.TrustScore } storeys := 0 if req.Storeys != nil { storeys = *req.Storeys } org := &domain.Organization{ ID: uuid.New().String(), Name: req.Name, Subtype: req.Subtype, Sector: req.Sector, Description: req.Description, LogoURL: req.LogoURL, GalleryImages: datatypes.JSON(galleryImagesJSON), Website: req.Website, Latitude: req.Latitude, Longitude: req.Longitude, LegalForm: req.LegalForm, PrimaryContact: datatypes.JSON(contactJSON), IndustrialSector: req.IndustrialSector, CompanySize: req.CompanySize, YearsOperation: req.YearsOperation, SupplyChainRole: req.SupplyChainRole, Certifications: datatypes.JSON(certsJSON), BusinessFocus: datatypes.JSON(focusJSON), StrategicVision: req.StrategicVision, DriversBarriers: req.DriversBarriers, ReadinessMaturity: readinessMaturity, TrustScore: trustScore, TechnicalExpertise: datatypes.JSON(techExpertiseJSON), AvailableTechnology: datatypes.JSON(technologyJSON), ManagementSystems: datatypes.JSON(managementJSON), SellsProducts: datatypes.JSON(productsJSON), OffersServices: datatypes.JSON(servicesJSON), NeedsServices: datatypes.JSON(needsJSON), YearBuilt: req.YearBuilt, BuilderOwner: req.BuilderOwner, Architect: req.Architect, OriginalPurpose: req.OriginalPurpose, CurrentUse: req.CurrentUse, Style: req.Style, Materials: req.Materials, Storeys: storeys, HeritageStatus: req.HeritageStatus, Verified: req.Verified, Notes: req.Notes, Sources: datatypes.JSON(sourcesJSON), TrustNetwork: datatypes.JSON(trustNetJSON), ExistingSymbioticRelationships: datatypes.JSON(symbiosisJSON), CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := s.repo.Create(ctx, org); err != nil { return nil, err } if s.graphRepo != nil { if err := s.graphRepo.SyncToGraph(ctx, org); err != nil { log.Printf("Warning: Failed to sync organization %s to graph database: %v", org.ID, err) } } return org, nil } // GetByID retrieves an organization by ID func (s *OrganizationService) GetByID(ctx context.Context, id string) (*domain.Organization, error) { return s.repo.GetByID(ctx, id) } // GetAll retrieves all organizations func (s *OrganizationService) GetAll(ctx context.Context) ([]*domain.Organization, error) { return s.repo.GetAll(ctx) } // GetBySubtype retrieves organizations by subtype func (s *OrganizationService) GetBySubtype(ctx context.Context, subtype domain.OrganizationSubtype) ([]*domain.Organization, error) { return s.repo.GetBySubtype(ctx, subtype) } // GetBySector retrieves organizations by sector func (s *OrganizationService) GetBySector(ctx context.Context, sector string) ([]*domain.Organization, error) { return s.repo.GetBySector(ctx, sector) } // Search performs fuzzy search on organizations // limit: maximum number of results to return (default: 50) func (s *OrganizationService) Search(ctx context.Context, query string, limit int) ([]*domain.Organization, error) { if limit <= 0 { limit = 50 // Default limit } if limit > 200 { limit = 200 // Maximum limit } return s.repo.Search(ctx, query, limit) } // SearchSuggestions returns autocomplete suggestions for search queries // limit: maximum number of suggestions to return (default: 10) func (s *OrganizationService) SearchSuggestions(ctx context.Context, query string, limit int) ([]string, error) { if limit <= 0 { limit = 10 // Default limit } if limit > 50 { limit = 50 // Maximum limit } return s.repo.SearchSuggestions(ctx, query, limit) } // GetByCertification retrieves organizations by certification func (s *OrganizationService) GetByCertification(ctx context.Context, cert string) ([]*domain.Organization, error) { return s.repo.GetByCertification(ctx, cert) } // GetWithinRadius retrieves organizations within a radius func (s *OrganizationService) GetWithinRadius(ctx context.Context, lat, lng, radiusKm float64) ([]*domain.Organization, error) { return s.repo.GetWithinRadius(ctx, lat, lng, radiusKm) } // Update updates an organization func (s *OrganizationService) Update(ctx context.Context, org *domain.Organization) error { org.UpdatedAt = time.Now() if err := s.repo.Update(ctx, org); err != nil { return err } if s.graphRepo != nil { if err := s.graphRepo.SyncToGraph(ctx, org); err != nil { // Log error } } return nil } // Delete deletes an organization func (s *OrganizationService) Delete(ctx context.Context, id string) error { if s.graphRepo != nil { if err := s.graphRepo.DeleteFromGraph(ctx, id); err != nil { // Log error } } return s.repo.Delete(ctx, id) } // AddProduct adds a product to an organization's offerings func (s *OrganizationService) AddProduct(ctx context.Context, orgID string, product domain.ProductJSON) error { org, err := s.repo.GetByID(ctx, orgID) if err != nil { return err } var products []domain.ProductJSON if org.SellsProducts != nil { json.Unmarshal(org.SellsProducts, &products) } products = append(products, product) productsJSON, _ := json.Marshal(products) org.SellsProducts = datatypes.JSON(productsJSON) return s.Update(ctx, org) } // AddService adds a service to an organization's offerings func (s *OrganizationService) AddService(ctx context.Context, orgID string, service domain.ServiceJSON) error { org, err := s.repo.GetByID(ctx, orgID) if err != nil { return err } var services []domain.ServiceJSON if org.OffersServices != nil { json.Unmarshal(org.OffersServices, &services) } services = append(services, service) servicesJSON, _ := json.Marshal(services) org.OffersServices = datatypes.JSON(servicesJSON) return s.Update(ctx, org) } // GetSectorStats returns the top sectors by organization count func (s *OrganizationService) GetSectorStats(ctx context.Context, limit int) ([]domain.SectorStat, error) { return s.repo.GetSectorStats(ctx, limit) }