package company import ( "context" "errors" "time" "tm/pkg/logger" ) // Service defines business logic for company operations type Service interface { // Core company operations CreateCompany(ctx context.Context, form *CreateCompanyForm, createdBy *string) (*Company, error) GetCompanyByID(ctx context.Context, id string) (*Company, error) GetCompaniesByIDs(ctx context.Context, ids []string) ([]*Company, error) UpdateCompany(ctx context.Context, id string, form *UpdateCompanyForm, updatedBy *string) (*Company, error) DeleteCompany(ctx context.Context, id string, deletedBy *string) error // Company listing and search ListCompanies(ctx context.Context, form *ListCompaniesForm) (*CompanyListResponse, error) SearchCompanies(ctx context.Context, form *CompanySearchForm) (*CompanyListResponse, error) SearchCompaniesByTags(ctx context.Context, cpvCodes []string, categories []string, keywords []string, specializations []string, limit, offset int) ([]*Company, error) // Company management UpdateCompanyStatus(ctx context.Context, id string, form *UpdateCompanyStatusForm, updatedBy *string) error UpdateCompanyVerification(ctx context.Context, id string, form *UpdateCompanyVerificationForm, updatedBy *string) error // Tag management UpdateCompanyTags(ctx context.Context, id string, form *UpdateCompanyTagsForm, updatedBy *string) error AddTags(ctx context.Context, id string, form *AddTagsForm, updatedBy *string) error RemoveTags(ctx context.Context, id string, form *RemoveTagsForm, updatedBy *string) error // Business operations VerifyCompany(ctx context.Context, id string, verifiedBy *string) error SuspendCompany(ctx context.Context, id string, reason string, suspendedBy *string) error ActivateCompany(ctx context.Context, id string, activatedBy *string) error // Statistics and analytics GetCompanyStats(ctx context.Context) (*CompanyStatsResponse, error) // Search and filtering helpers GetCompaniesByType(ctx context.Context, companyType CompanyType, limit, offset int) ([]*Company, int64, error) GetCompaniesByStatus(ctx context.Context, status CompanyStatus, limit, offset int) ([]*Company, int64, error) GetCompaniesByIndustry(ctx context.Context, industry string, limit, offset int) ([]*Company, int64, error) } // companyService implements the Service interface type companyService struct { repository Repository logger logger.Logger } // NewCompanyService creates a new company service func NewCompanyService(repository Repository, logger logger.Logger) Service { return &companyService{ repository: repository, logger: logger, } } // CreateCompany creates a new company func (s *companyService) CreateCompany(ctx context.Context, form *CreateCompanyForm, createdBy *string) (*Company, error) { // Check if company name already exists existingCompany, _ := s.repository.GetByName(ctx, form.Name) if existingCompany != nil { return nil, errors.New("company with this name already exists") } // Check if registration number already exists existingCompany, _ = s.repository.GetByRegistrationNumber(ctx, form.RegistrationNumber) if existingCompany != nil { return nil, errors.New("company with this registration number already exists") } // Check if tax ID already exists existingCompany, _ = s.repository.GetByTaxID(ctx, form.TaxID) if existingCompany != nil { return nil, errors.New("company with this tax ID already exists") } // Create company company := &Company{ Name: form.Name, Type: CompanyType(form.Type), Status: CompanyStatusPending, RegistrationNumber: form.RegistrationNumber, TaxID: form.TaxID, Industry: form.Industry, Description: form.Description, Website: form.Website, EmployeeCount: form.EmployeeCount, AnnualRevenue: form.AnnualRevenue, FoundedYear: form.FoundedYear, Address: s.convertAddressForm(form.Address), ContactPerson: s.convertContactPersonForm(form.ContactPerson), Phone: form.Phone, Email: form.Email, Tags: s.convertCompanyTagsForm(form.Tags), IsVerified: false, IsCompliant: false, Language: s.getDefaultLanguage(form.Language), Currency: s.getDefaultCurrency(form.Currency), Timezone: s.getDefaultTimezone(form.Timezone), CreatedBy: createdBy, } // Save to database err := s.repository.Create(ctx, company) if err != nil { s.logger.Error("Failed to create company", map[string]interface{}{ "error": err.Error(), "name": form.Name, "registration_number": form.RegistrationNumber, }) return nil, err } s.logger.Info("Company created successfully", map[string]interface{}{ "company_id": company.ID, "name": company.Name, "type": company.Type, "created_by": createdBy, }) return company, nil } // GetCompanyByID retrieves a company by ID func (s *companyService) GetCompanyByID(ctx context.Context, id string) (*Company, error) { company, err := s.repository.GetByID(ctx, id) if err != nil { s.logger.Error("Failed to get company by ID", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return nil, err } s.logger.Info("Company retrieved successfully", map[string]interface{}{ "company_id": company.ID, "name": company.Name, }) return company, nil } // GetCompaniesByIDs retrieves multiple companies by their IDs func (s *companyService) GetCompaniesByIDs(ctx context.Context, ids []string) ([]*Company, error) { if len(ids) == 0 { return []*Company{}, nil } companies, err := s.repository.GetByIDs(ctx, ids) if err != nil { s.logger.Error("Failed to get companies by IDs", map[string]interface{}{ "error": err.Error(), "ids": ids, }) return nil, err } s.logger.Info("Companies retrieved successfully", map[string]interface{}{ "requested_count": len(ids), "found_count": len(companies), }) return companies, nil } // UpdateCompany updates a company func (s *companyService) UpdateCompany(ctx context.Context, id string, form *UpdateCompanyForm, updatedBy *string) (*Company, error) { // Get existing company company, err := s.repository.GetByID(ctx, id) if err != nil { s.logger.Error("Failed to get company for update", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return nil, errors.New("company not found") } // Check if name already exists (if changing name) if form.Name != nil && *form.Name != company.Name { existingCompany, _ := s.repository.GetByName(ctx, *form.Name) if existingCompany != nil { return nil, errors.New("company with this name already exists") } company.Name = *form.Name } // Check if registration number already exists (if changing) if form.RegistrationNumber != nil && *form.RegistrationNumber != company.RegistrationNumber { existingCompany, _ := s.repository.GetByRegistrationNumber(ctx, *form.RegistrationNumber) if existingCompany != nil { return nil, errors.New("company with this registration number already exists") } company.RegistrationNumber = *form.RegistrationNumber } // Check if tax ID already exists (if changing) if form.TaxID != nil && *form.TaxID != company.TaxID { existingCompany, _ := s.repository.GetByTaxID(ctx, *form.TaxID) if existingCompany != nil { return nil, errors.New("company with this tax ID already exists") } company.TaxID = *form.TaxID } // Update fields if provided if form.Type != nil { company.Type = CompanyType(*form.Type) } if form.Industry != nil { company.Industry = *form.Industry } if form.Description != nil { company.Description = form.Description } if form.Website != nil { company.Website = form.Website } if form.EmployeeCount != nil { company.EmployeeCount = form.EmployeeCount } if form.AnnualRevenue != nil { company.AnnualRevenue = form.AnnualRevenue } if form.FoundedYear != nil { company.FoundedYear = form.FoundedYear } if form.Address != nil { company.Address = s.convertAddressForm(form.Address) } if form.ContactPerson != nil { company.ContactPerson = s.convertContactPersonForm(form.ContactPerson) } if form.Phone != nil { company.Phone = form.Phone } if form.Email != nil { company.Email = form.Email } if form.Tags != nil { company.Tags = s.convertCompanyTagsForm(form.Tags) } if form.Language != nil { company.Language = *form.Language } if form.Currency != nil { company.Currency = *form.Currency } if form.Timezone != nil { company.Timezone = *form.Timezone } // Set updated by company.UpdatedBy = updatedBy company.UpdatedAt = time.Now().Unix() // Update in database err = s.repository.Update(ctx, company) if err != nil { s.logger.Error("Failed to update company", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return nil, errors.New("failed to update company") } s.logger.Info("Company updated successfully", map[string]interface{}{ "company_id": company.ID, "name": company.Name, "updated_by": updatedBy, }) return company, nil } // DeleteCompany deletes a company (soft delete) func (s *companyService) DeleteCompany(ctx context.Context, id string, deletedBy *string) error { // Get company to check if exists company, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("company not found") } // Set updated by before deletion company.UpdatedBy = deletedBy // Delete company (soft delete) err = s.repository.Delete(ctx, id) if err != nil { s.logger.Error("Failed to delete company", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return errors.New("failed to delete company") } s.logger.Info("Company deleted successfully", map[string]interface{}{ "company_id": id, "deleted_by": deletedBy, }) return nil } // ListCompanies lists companies with search and filters func (s *companyService) ListCompanies(ctx context.Context, form *ListCompaniesForm) (*CompanyListResponse, error) { // Set defaults limit := 20 if form.Limit != nil { limit = *form.Limit } offset := 0 if form.Offset != nil { offset = *form.Offset } search := "" if form.Search != nil { search = *form.Search } sortBy := "created_at" if form.SortBy != nil { sortBy = *form.SortBy } sortOrder := "desc" if form.SortOrder != nil { sortOrder = *form.SortOrder } // Get companies companies, err := s.repository.Search(ctx, search, form.Type, form.Status, form.Industry, form.IsVerified, form.IsCompliant, form.Language, form.Currency, form.CPVCodes, form.Categories, form.Keywords, form.Specializations, form.EmployeeCountMin, form.EmployeeCountMax, form.AnnualRevenueMin, form.AnnualRevenueMax, form.FoundedYearMin, form.FoundedYearMax, limit, offset, sortBy, sortOrder) if err != nil { s.logger.Error("Failed to list companies", map[string]interface{}{ "error": err.Error(), }) return nil, errors.New("failed to list companies") } // Convert to responses var companyResponses []*CompanyResponse for _, company := range companies { companyResponses = append(companyResponses, company.ToResponse()) } // TODO: Implement proper count for pagination metadata total := int64(len(companies)) // This should be a separate count query totalPages := (total + int64(limit) - 1) / int64(limit) return &CompanyListResponse{ Companies: companyResponses, Total: total, Limit: limit, Offset: offset, TotalPages: int(totalPages), }, nil } // SearchCompanies searches companies with advanced filters func (s *companyService) SearchCompanies(ctx context.Context, form *CompanySearchForm) (*CompanyListResponse, error) { // Set defaults limit := 20 if form.Limit != nil { limit = *form.Limit } offset := 0 if form.Offset != nil { offset = *form.Offset } query := "" if form.Query != nil { query = *form.Query } // Get companies using search companies, err := s.repository.Search(ctx, query, nil, nil, form.Industry, form.IsVerified, form.IsCompliant, nil, nil, form.CPVCodes, form.Categories, form.Keywords, form.Specializations, form.MinEmployees, form.MaxEmployees, form.MinRevenue, form.MaxRevenue, nil, nil, limit, offset, "created_at", "desc") if err != nil { s.logger.Error("Failed to search companies", map[string]interface{}{ "error": err.Error(), "query": query, }) return nil, errors.New("failed to search companies") } // Convert to responses var companyResponses []*CompanyResponse for _, company := range companies { companyResponses = append(companyResponses, company.ToResponse()) } // TODO: Implement proper count for pagination metadata total := int64(len(companies)) // This should be a separate count query totalPages := (total + int64(limit) - 1) / int64(limit) return &CompanyListResponse{ Companies: companyResponses, Total: total, Limit: limit, Offset: offset, TotalPages: int(totalPages), }, nil } // SearchCompaniesByTags searches companies by tags func (s *companyService) SearchCompaniesByTags(ctx context.Context, cpvCodes []string, categories []string, keywords []string, specializations []string, limit, offset int) ([]*Company, error) { companies, err := s.repository.SearchByTags(ctx, cpvCodes, categories, keywords, specializations, limit, offset) if err != nil { s.logger.Error("Failed to search companies by tags", map[string]interface{}{ "error": err.Error(), }) return nil, errors.New("failed to search companies by tags") } return companies, nil } // UpdateCompanyStatus updates company status func (s *companyService) UpdateCompanyStatus(ctx context.Context, id string, form *UpdateCompanyStatusForm, updatedBy *string) error { // Get company to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("company not found") } // Update status status := CompanyStatus(form.Status) err = s.repository.UpdateStatus(ctx, id, status) if err != nil { s.logger.Error("Failed to update company status", map[string]interface{}{ "error": err.Error(), "company_id": id, "status": form.Status, }) return errors.New("failed to update company status") } s.logger.Info("Company status updated successfully", map[string]interface{}{ "company_id": id, "status": form.Status, "updated_by": updatedBy, }) return nil } // UpdateCompanyVerification updates company verification status func (s *companyService) UpdateCompanyVerification(ctx context.Context, id string, form *UpdateCompanyVerificationForm, updatedBy *string) error { // Get company to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("company not found") } // Update verification err = s.repository.UpdateVerification(ctx, id, form.IsVerified, form.IsCompliant, form.ComplianceNotes) if err != nil { s.logger.Error("Failed to update company verification", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return errors.New("failed to update company verification") } s.logger.Info("Company verification updated successfully", map[string]interface{}{ "company_id": id, "is_verified": form.IsVerified, "is_compliant": form.IsCompliant, "updated_by": updatedBy, }) return nil } // UpdateCompanyTags updates company tags func (s *companyService) UpdateCompanyTags(ctx context.Context, id string, form *UpdateCompanyTagsForm, updatedBy *string) error { // Get company to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("company not found") } // Update tags tags := s.convertCompanyTagsForm(form.Tags) err = s.repository.UpdateTags(ctx, id, tags) if err != nil { s.logger.Error("Failed to update company tags", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return errors.New("failed to update company tags") } s.logger.Info("Company tags updated successfully", map[string]interface{}{ "company_id": id, "updated_by": updatedBy, }) return nil } // AddTags adds tags to a company func (s *companyService) AddTags(ctx context.Context, id string, form *AddTagsForm, updatedBy *string) error { // Get company to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("company not found") } // Add tags err = s.repository.AddTags(ctx, id, form.CPVCodes, form.Categories, form.Keywords, form.Specializations, form.Certifications) if err != nil { s.logger.Error("Failed to add tags to company", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return errors.New("failed to add tags to company") } s.logger.Info("Tags added to company successfully", map[string]interface{}{ "company_id": id, "updated_by": updatedBy, }) return nil } // RemoveTags removes tags from a company func (s *companyService) RemoveTags(ctx context.Context, id string, form *RemoveTagsForm, updatedBy *string) error { // Get company to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("company not found") } // Remove tags err = s.repository.RemoveTags(ctx, id, form.CPVCodes, form.Categories, form.Keywords, form.Specializations, form.Certifications) if err != nil { s.logger.Error("Failed to remove tags from company", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return errors.New("failed to remove tags from company") } s.logger.Info("Tags removed from company successfully", map[string]interface{}{ "company_id": id, "updated_by": updatedBy, }) return nil } // VerifyCompany verifies a company func (s *companyService) VerifyCompany(ctx context.Context, id string, verifiedBy *string) error { // Get company to check if exists company, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("company not found") } // Update verification err = s.repository.UpdateVerification(ctx, id, true, company.IsCompliant, company.ComplianceNotes) if err != nil { s.logger.Error("Failed to verify company", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return errors.New("failed to verify company") } s.logger.Info("Company verified successfully", map[string]interface{}{ "company_id": id, "verified_by": verifiedBy, }) return nil } // SuspendCompany suspends a company func (s *companyService) SuspendCompany(ctx context.Context, id string, reason string, suspendedBy *string) error { // Get company to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("company not found") } // Update status to suspended err = s.repository.UpdateStatus(ctx, id, CompanyStatusSuspended) if err != nil { s.logger.Error("Failed to suspend company", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return errors.New("failed to suspend company") } s.logger.Info("Company suspended successfully", map[string]interface{}{ "company_id": id, "reason": reason, "suspended_by": suspendedBy, }) return nil } // ActivateCompany activates a company func (s *companyService) ActivateCompany(ctx context.Context, id string, activatedBy *string) error { // Get company to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("company not found") } // Update status to active err = s.repository.UpdateStatus(ctx, id, CompanyStatusActive) if err != nil { s.logger.Error("Failed to activate company", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return errors.New("failed to activate company") } s.logger.Info("Company activated successfully", map[string]interface{}{ "company_id": id, "activated_by": activatedBy, }) return nil } // GetCompanyStats returns company statistics func (s *companyService) GetCompanyStats(ctx context.Context) (*CompanyStatsResponse, error) { stats, err := s.repository.GetCompanyStats(ctx) if err != nil { s.logger.Error("Failed to get company stats", map[string]interface{}{ "error": err.Error(), }) return nil, errors.New("failed to get company statistics") } return stats, nil } // GetCompaniesByType retrieves companies by type with pagination func (s *companyService) GetCompaniesByType(ctx context.Context, companyType CompanyType, limit, offset int) ([]*Company, int64, error) { // Use search method with type filter typeStr := string(companyType) companies, err := s.repository.Search(ctx, "", &typeStr, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, limit, offset, "created_at", "desc") if err != nil { s.logger.Error("Failed to get companies by type", map[string]interface{}{ "error": err.Error(), "company_type": companyType, }) return nil, 0, errors.New("failed to get companies by type") } // Get total count total, err := s.repository.CountByType(ctx, companyType) if err != nil { s.logger.Error("Failed to count companies by type", map[string]interface{}{ "error": err.Error(), "company_type": companyType, }) return companies, 0, nil // Return companies even if count fails } return companies, total, nil } // GetCompaniesByStatus retrieves companies by status with pagination func (s *companyService) GetCompaniesByStatus(ctx context.Context, status CompanyStatus, limit, offset int) ([]*Company, int64, error) { // Use search method with status filter statusStr := string(status) companies, err := s.repository.Search(ctx, "", nil, &statusStr, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, limit, offset, "created_at", "desc") if err != nil { s.logger.Error("Failed to get companies by status", map[string]interface{}{ "error": err.Error(), "status": status, }) return nil, 0, errors.New("failed to get companies by status") } // Get total count total, err := s.repository.CountByStatus(ctx, status) if err != nil { s.logger.Error("Failed to count companies by status", map[string]interface{}{ "error": err.Error(), "status": status, }) return companies, 0, nil } return companies, total, nil } // GetCompaniesByIndustry retrieves companies by industry with pagination func (s *companyService) GetCompaniesByIndustry(ctx context.Context, industry string, limit, offset int) ([]*Company, int64, error) { // Use search method with industry filter companies, err := s.repository.Search(ctx, "", nil, nil, &industry, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, limit, offset, "created_at", "desc") if err != nil { s.logger.Error("Failed to get companies by industry", map[string]interface{}{ "error": err.Error(), "industry": industry, }) return nil, 0, errors.New("failed to get companies by industry") } // Get total count total, err := s.repository.CountByIndustry(ctx, industry) if err != nil { s.logger.Error("Failed to count companies by industry", map[string]interface{}{ "error": err.Error(), "industry": industry, }) return companies, 0, nil } return companies, total, nil } // Helper methods for converting forms to domain objects func (s *companyService) convertAddressForm(form *AddressForm) *Address { if form == nil { return nil } return &Address{ Street: form.Street, City: form.City, State: form.State, PostalCode: form.PostalCode, Country: form.Country, } } func (s *companyService) convertContactPersonForm(form *ContactPersonForm) *ContactPerson { if form == nil { return nil } return &ContactPerson{ FirstName: form.FirstName, LastName: form.LastName, FullName: form.FullName, Position: form.Position, Email: form.Email, Phone: form.Phone, Mobile: form.Mobile, IsPrimary: form.IsPrimary, } } func (s *companyService) convertCompanyTagsForm(form *CompanyTagsForm) *CompanyTags { if form == nil { return nil } return &CompanyTags{ CPVCodes: form.CPVCodes, Categories: form.Categories, Keywords: form.Keywords, Specializations: form.Specializations, Certifications: form.Certifications, } } func (s *companyService) getDefaultLanguage(language *string) string { if language != nil { return *language } return "en" } func (s *companyService) getDefaultCurrency(currency *string) string { if currency != nil { return *currency } return "USD" } func (s *companyService) getDefaultTimezone(timezone *string) string { if timezone != nil { return *timezone } return "UTC" }