package company import ( "context" "errors" "time" "tm/pkg/logger" "tm/pkg/mongo" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) // Repository defines the interface for company data operations type Repository interface { Create(ctx context.Context, company *Company) error GetByID(ctx context.Context, id string) (*Company, error) GetByName(ctx context.Context, name string) (*Company, error) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Company, error) GetByTaxID(ctx context.Context, taxID string) (*Company, error) GetByIDs(ctx context.Context, ids []string) ([]*Company, error) Update(ctx context.Context, company *Company) error Delete(ctx context.Context, id string) error List(ctx context.Context, limit, offset int) ([]*Company, error) Search(ctx context.Context, search string, companyType *string, status *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, cpvCodes []string, categories []string, keywords []string, specializations []string, employeeCountMin *int, employeeCountMax *int, annualRevenueMin *float64, annualRevenueMax *float64, foundedYearMin *int, foundedYearMax *int, limit, offset int, sortBy, sortOrder string) ([]*Company, error) Count(ctx context.Context) (int64, error) CountByType(ctx context.Context, companyType CompanyType) (int64, error) CountByStatus(ctx context.Context, status CompanyStatus) (int64, error) CountByIndustry(ctx context.Context, industry string) (int64, error) CountVerified(ctx context.Context) (int64, error) UpdateStatus(ctx context.Context, id string, status CompanyStatus) error UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error UpdateTags(ctx context.Context, id string, tags *CompanyTags) error AddTags(ctx context.Context, id string, cpvCodes []string, categories []string, keywords []string, specializations []string, certifications []string) error RemoveTags(ctx context.Context, id string, cpvCodes []string, categories []string, keywords []string, specializations []string, certifications []string) error GetCompanyStats(ctx context.Context) (*CompanyStatsResponse, error) SearchByTags(ctx context.Context, cpvCodes []string, categories []string, keywords []string, specializations []string, limit, offset int) ([]*Company, error) } // companyRepository implements the Repository interface using the MongoDB ORM type companyRepository struct { ormRepo mongo.Repository[Company] logger logger.Logger } // NewCompanyRepository creates a new company repository func NewCompanyRepository(mongoManager *mongo.ConnectionManager, logger logger.Logger) Repository { // Create indexes using the ORM's index management indexes := []mongo.Index{ *mongo.CreateUniqueIndex("name_idx", bson.D{{Key: "name", Value: 1}}), *mongo.CreateUniqueIndex("registration_number_idx", bson.D{{Key: "registration_number", Value: 1}}), *mongo.CreateUniqueIndex("tax_id_idx", bson.D{{Key: "tax_id", Value: 1}}), *mongo.NewIndex("type_idx", bson.D{{Key: "type", Value: 1}}), *mongo.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}), *mongo.NewIndex("industry_idx", bson.D{{Key: "industry", Value: 1}}), *mongo.NewIndex("is_verified_idx", bson.D{{Key: "is_verified", Value: 1}}), *mongo.NewIndex("is_compliant_idx", bson.D{{Key: "is_compliant", Value: 1}}), *mongo.NewIndex("language_idx", bson.D{{Key: "language", Value: 1}}), *mongo.NewIndex("currency_idx", bson.D{{Key: "currency", Value: 1}}), *mongo.NewIndex("employee_count_idx", bson.D{{Key: "employee_count", Value: 1}}), *mongo.NewIndex("annual_revenue_idx", bson.D{{Key: "annual_revenue", Value: 1}}), *mongo.NewIndex("founded_year_idx", bson.D{{Key: "founded_year", Value: 1}}), *mongo.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}), // Tag indexes for efficient search *mongo.NewIndex("cpv_codes_idx", bson.D{{Key: "tags.cpv_codes", Value: 1}}), *mongo.NewIndex("categories_idx", bson.D{{Key: "tags.categories", Value: 1}}), *mongo.NewIndex("keywords_idx", bson.D{{Key: "tags.keywords", Value: 1}}), *mongo.NewIndex("specializations_idx", bson.D{{Key: "tags.specializations", Value: 1}}), *mongo.NewIndex("certifications_idx", bson.D{{Key: "tags.certifications", Value: 1}}), // Text index for search *mongo.CreateTextIndex("search_idx", "name", "description", "industry"), } // Create indexes err := mongoManager.CreateIndexes("companies", indexes) if err != nil { logger.Warn("Failed to create company indexes", map[string]interface{}{ "error": err.Error(), }) } // Create ORM repository ormRepo := mongo.NewRepository[Company](mongoManager.GetCollection("companies"), logger) return &companyRepository{ ormRepo: ormRepo, logger: logger, } } // Create creates a new company func (r *companyRepository) Create(ctx context.Context, company *Company) error { // Set created/updated timestamps using Unix timestamps now := time.Now().Unix() company.SetCreatedAt(now) company.SetUpdatedAt(now) // Use ORM to create company err := r.ormRepo.Create(ctx, company) if err != nil { r.logger.Error("Failed to create company", map[string]interface{}{ "error": err.Error(), "name": company.Name, "registration_number": company.RegistrationNumber, }) return err } r.logger.Info("Company created successfully", map[string]interface{}{ "company_id": company.ID, "name": company.Name, "type": company.Type, }) return nil } // GetByID retrieves a company by ID func (r *companyRepository) GetByID(ctx context.Context, id string) (*Company, error) { company, err := r.ormRepo.FindByID(ctx, id) if err != nil { if errors.Is(err, mongo.ErrDocumentNotFound) { return nil, errors.New("company not found") } r.logger.Error("Failed to get company by ID", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return nil, err } return company, nil } // GetByName retrieves a company by name func (r *companyRepository) GetByName(ctx context.Context, name string) (*Company, error) { filter := bson.M{"name": name} company, err := r.ormRepo.FindOne(ctx, filter) if err != nil { if errors.Is(err, mongo.ErrDocumentNotFound) { return nil, errors.New("company not found") } r.logger.Error("Failed to get company by name", map[string]interface{}{ "error": err.Error(), "name": name, }) return nil, err } return company, nil } // GetByRegistrationNumber retrieves a company by registration number func (r *companyRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Company, error) { filter := bson.M{"registration_number": registrationNumber} company, err := r.ormRepo.FindOne(ctx, filter) if err != nil { if errors.Is(err, mongo.ErrDocumentNotFound) { return nil, errors.New("company not found") } r.logger.Error("Failed to get company by registration number", map[string]interface{}{ "error": err.Error(), "registration_number": registrationNumber, }) return nil, err } return company, nil } // GetByTaxID retrieves a company by tax ID func (r *companyRepository) GetByTaxID(ctx context.Context, taxID string) (*Company, error) { filter := bson.M{"tax_id": taxID} company, err := r.ormRepo.FindOne(ctx, filter) if err != nil { if errors.Is(err, mongo.ErrDocumentNotFound) { return nil, errors.New("company not found") } r.logger.Error("Failed to get company by tax ID", map[string]interface{}{ "error": err.Error(), "tax_id": taxID, }) return nil, err } return company, nil } // GetByIDs retrieves multiple companies by their IDs func (r *companyRepository) GetByIDs(ctx context.Context, ids []string) ([]*Company, error) { if len(ids) == 0 { return []*Company{}, nil } // Convert string IDs to ObjectIDs for MongoDB query objectIDs := make([]interface{}, len(ids)) for i, id := range ids { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { r.logger.Warn("Invalid company ID format", map[string]interface{}{ "id": id, "error": err.Error(), }) continue } objectIDs[i] = objectID } if len(objectIDs) == 0 { return []*Company{}, nil } filter := bson.M{"_id": bson.M{"$in": objectIDs}} companies, err := r.ormRepo.FindMany(ctx, filter, len(ids)) if err != nil { r.logger.Error("Failed to get companies by IDs", map[string]interface{}{ "error": err.Error(), "ids": ids, }) return nil, err } // Convert to pointer slice result := make([]*Company, len(companies)) for i := range companies { result[i] = &companies[i] } r.logger.Info("Retrieved companies by IDs", map[string]interface{}{ "requested_count": len(ids), "found_count": len(result), }) return result, nil } // Update updates a company func (r *companyRepository) Update(ctx context.Context, company *Company) error { // Set updated timestamp using Unix timestamp company.SetUpdatedAt(time.Now().Unix()) // Use ORM to update company err := r.ormRepo.Update(ctx, company) if err != nil { r.logger.Error("Failed to update company", map[string]interface{}{ "error": err.Error(), "company_id": company.ID, }) return err } r.logger.Info("Company updated successfully", map[string]interface{}{ "company_id": company.ID, "name": company.Name, }) return nil } // Delete deletes a company (soft delete by setting status to inactive) func (r *companyRepository) Delete(ctx context.Context, id string) error { // Get company first company, err := r.GetByID(ctx, id) if err != nil { return err } // Update status to inactive company.Status = CompanyStatusInactive company.SetUpdatedAt(time.Now().Unix()) // Update in database err = r.ormRepo.Update(ctx, company) if err != nil { r.logger.Error("Failed to delete company", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return err } r.logger.Info("Company deleted successfully", map[string]interface{}{ "company_id": id, }) return nil } // List retrieves companies with pagination func (r *companyRepository) List(ctx context.Context, limit, offset int) ([]*Company, error) { // Build pagination pagination := mongo.NewPaginationBuilder(). Limit(limit). Skip(offset). SortDesc("created_at"). Build() // Only active companies by default filter := bson.M{"status": bson.M{"$ne": CompanyStatusInactive}} // Use ORM to find companies result, err := r.ormRepo.FindAll(ctx, filter, pagination) if err != nil { r.logger.Error("Failed to list companies", map[string]interface{}{ "error": err.Error(), "limit": limit, "offset": offset, }) return nil, err } // Convert []Company to []*Company companies := make([]*Company, len(result.Items)) for i := range result.Items { companies[i] = &result.Items[i] } return companies, nil } // Search retrieves companies with search and filters func (r *companyRepository) Search(ctx context.Context, search string, companyType *string, status *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, cpvCodes []string, categories []string, keywords []string, specializations []string, employeeCountMin *int, employeeCountMax *int, annualRevenueMin *float64, annualRevenueMax *float64, foundedYearMin *int, foundedYearMax *int, limit, offset int, sortBy, sortOrder string) ([]*Company, error) { // Build filter filter := bson.M{} if search != "" { filter["$text"] = bson.M{"$search": search} } if companyType != nil { filter["type"] = *companyType } if status != nil { filter["status"] = *status } if industry != nil { filter["industry"] = *industry } if isVerified != nil { filter["is_verified"] = *isVerified } if isCompliant != nil { filter["is_compliant"] = *isCompliant } if language != nil { filter["language"] = *language } if currency != nil { filter["currency"] = *currency } // Tag filters if len(cpvCodes) > 0 { filter["tags.cpv_codes"] = bson.M{"$in": cpvCodes} } if len(categories) > 0 { filter["tags.categories"] = bson.M{"$in": categories} } if len(keywords) > 0 { filter["tags.keywords"] = bson.M{"$in": keywords} } if len(specializations) > 0 { filter["tags.specializations"] = bson.M{"$in": specializations} } // Range filters if employeeCountMin != nil || employeeCountMax != nil { rangeFilter := bson.M{} if employeeCountMin != nil { rangeFilter["$gte"] = *employeeCountMin } if employeeCountMax != nil { rangeFilter["$lte"] = *employeeCountMax } filter["employee_count"] = rangeFilter } if annualRevenueMin != nil || annualRevenueMax != nil { rangeFilter := bson.M{} if annualRevenueMin != nil { rangeFilter["$gte"] = *annualRevenueMin } if annualRevenueMax != nil { rangeFilter["$lte"] = *annualRevenueMax } filter["annual_revenue"] = rangeFilter } if foundedYearMin != nil || foundedYearMax != nil { rangeFilter := bson.M{} if foundedYearMin != nil { rangeFilter["$gte"] = *foundedYearMin } if foundedYearMax != nil { rangeFilter["$lte"] = *foundedYearMax } filter["founded_year"] = rangeFilter } // Build pagination pagination := mongo.NewPaginationBuilder(). Limit(limit). Skip(offset) // Set sort if sortBy != "" { if sortOrder == "desc" { pagination.SortDesc(sortBy) } else { pagination.SortAsc(sortBy) } } else { pagination.SortDesc("created_at") // Default sort } // Use ORM to find companies result, err := r.ormRepo.FindAll(ctx, filter, pagination.Build()) if err != nil { r.logger.Error("Failed to search companies", map[string]interface{}{ "error": err.Error(), "search": search, }) return nil, err } // Convert []Company to []*Company companies := make([]*Company, len(result.Items)) for i := range result.Items { companies[i] = &result.Items[i] } return companies, nil } // Count counts all companies func (r *companyRepository) Count(ctx context.Context) (int64, error) { filter := bson.M{"status": bson.M{"$ne": CompanyStatusInactive}} count, err := r.ormRepo.Count(ctx, filter) if err != nil { r.logger.Error("Failed to count companies", map[string]interface{}{ "error": err.Error(), }) return 0, err } return count, nil } // CountByType counts companies by type func (r *companyRepository) CountByType(ctx context.Context, companyType CompanyType) (int64, error) { filter := bson.M{ "type": companyType, "status": bson.M{"$ne": CompanyStatusInactive}, } count, err := r.ormRepo.Count(ctx, filter) if err != nil { r.logger.Error("Failed to count companies by type", map[string]interface{}{ "error": err.Error(), "company_type": companyType, }) return 0, err } return count, nil } // CountByStatus counts companies by status func (r *companyRepository) CountByStatus(ctx context.Context, status CompanyStatus) (int64, error) { filter := bson.M{"status": status} count, err := r.ormRepo.Count(ctx, filter) if err != nil { r.logger.Error("Failed to count companies by status", map[string]interface{}{ "error": err.Error(), "status": status, }) return 0, err } return count, nil } // CountByIndustry counts companies by industry func (r *companyRepository) CountByIndustry(ctx context.Context, industry string) (int64, error) { filter := bson.M{ "industry": industry, "status": bson.M{"$ne": CompanyStatusInactive}, } count, err := r.ormRepo.Count(ctx, filter) if err != nil { r.logger.Error("Failed to count companies by industry", map[string]interface{}{ "error": err.Error(), "industry": industry, }) return 0, err } return count, nil } // CountVerified counts verified companies func (r *companyRepository) CountVerified(ctx context.Context) (int64, error) { filter := bson.M{ "is_verified": true, "status": bson.M{"$ne": CompanyStatusInactive}, } count, err := r.ormRepo.Count(ctx, filter) if err != nil { r.logger.Error("Failed to count verified companies", map[string]interface{}{ "error": err.Error(), }) return 0, err } return count, nil } // UpdateStatus updates company status func (r *companyRepository) UpdateStatus(ctx context.Context, id string, status CompanyStatus) error { // Get company first company, err := r.GetByID(ctx, id) if err != nil { return err } // Update status company.Status = status company.SetUpdatedAt(time.Now().Unix()) // Update in database err = r.ormRepo.Update(ctx, company) if err != nil { r.logger.Error("Failed to update company status", map[string]interface{}{ "error": err.Error(), "company_id": id, "status": status, }) return err } r.logger.Info("Company status updated successfully", map[string]interface{}{ "company_id": id, "status": status, }) return nil } // UpdateVerification updates company verification status func (r *companyRepository) UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error { // Get company first company, err := r.GetByID(ctx, id) if err != nil { return err } // Update verification fields company.IsVerified = isVerified company.IsCompliant = isCompliant company.ComplianceNotes = complianceNotes company.SetUpdatedAt(time.Now().Unix()) // Update in database err = r.ormRepo.Update(ctx, company) if err != nil { r.logger.Error("Failed to update company verification", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return err } r.logger.Info("Company verification updated successfully", map[string]interface{}{ "company_id": id, "is_verified": isVerified, "is_compliant": isCompliant, }) return nil } // UpdateTags updates company tags func (r *companyRepository) UpdateTags(ctx context.Context, id string, tags *CompanyTags) error { // Get company first company, err := r.GetByID(ctx, id) if err != nil { return err } // Update tags company.Tags = tags company.SetUpdatedAt(time.Now().Unix()) // Update in database err = r.ormRepo.Update(ctx, company) if err != nil { r.logger.Error("Failed to update company tags", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return err } r.logger.Info("Company tags updated successfully", map[string]interface{}{ "company_id": id, }) return nil } // AddTags adds tags to a company func (r *companyRepository) AddTags(ctx context.Context, id string, cpvCodes []string, categories []string, keywords []string, specializations []string, certifications []string) error { // Get company first company, err := r.GetByID(ctx, id) if err != nil { return err } // Initialize tags if nil if company.Tags == nil { company.Tags = &CompanyTags{} } // Add tags (avoiding duplicates) company.Tags.CPVCodes = r.addUniqueStrings(company.Tags.CPVCodes, cpvCodes) company.Tags.Categories = r.addUniqueStrings(company.Tags.Categories, categories) company.Tags.Keywords = r.addUniqueStrings(company.Tags.Keywords, keywords) company.Tags.Specializations = r.addUniqueStrings(company.Tags.Specializations, specializations) company.Tags.Certifications = r.addUniqueStrings(company.Tags.Certifications, certifications) company.SetUpdatedAt(time.Now().Unix()) // Update in database err = r.ormRepo.Update(ctx, company) if err != nil { r.logger.Error("Failed to add tags to company", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return err } r.logger.Info("Tags added to company successfully", map[string]interface{}{ "company_id": id, }) return nil } // RemoveTags removes tags from a company func (r *companyRepository) RemoveTags(ctx context.Context, id string, cpvCodes []string, categories []string, keywords []string, specializations []string, certifications []string) error { // Get company first company, err := r.GetByID(ctx, id) if err != nil { return err } // Initialize tags if nil if company.Tags == nil { company.Tags = &CompanyTags{} } // Remove tags company.Tags.CPVCodes = r.removeStrings(company.Tags.CPVCodes, cpvCodes) company.Tags.Categories = r.removeStrings(company.Tags.Categories, categories) company.Tags.Keywords = r.removeStrings(company.Tags.Keywords, keywords) company.Tags.Specializations = r.removeStrings(company.Tags.Specializations, specializations) company.Tags.Certifications = r.removeStrings(company.Tags.Certifications, certifications) company.SetUpdatedAt(time.Now().Unix()) // Update in database err = r.ormRepo.Update(ctx, company) if err != nil { r.logger.Error("Failed to remove tags from company", map[string]interface{}{ "error": err.Error(), "company_id": id, }) return err } r.logger.Info("Tags removed from company successfully", map[string]interface{}{ "company_id": id, }) return nil } // GetCompanyStats returns company statistics func (r *companyRepository) GetCompanyStats(ctx context.Context) (*CompanyStatsResponse, error) { // This would typically use aggregation pipelines for better performance // For now, we'll use basic counts totalCompanies, _ := r.Count(ctx) activeCompanies, _ := r.CountByStatus(ctx, CompanyStatusActive) verifiedCompanies, _ := r.CountVerified(ctx) // For this implementation, we'll return basic stats // In a real implementation, you'd use MongoDB aggregation pipelines stats := &CompanyStatsResponse{ TotalCompanies: totalCompanies, ActiveCompanies: activeCompanies, VerifiedCompanies: verifiedCompanies, CompaniesByType: make(map[string]int64), CompaniesByIndustry: make(map[string]int64), CompaniesByStatus: make(map[string]int64), TopCategories: []CategoryCount{}, TopCPVCodes: []CPVCodeCount{}, } return stats, nil } // SearchByTags searches companies by tags func (r *companyRepository) SearchByTags(ctx context.Context, cpvCodes []string, categories []string, keywords []string, specializations []string, limit, offset int) ([]*Company, error) { filter := bson.M{} // Build tag filters var tagFilters []bson.M if len(cpvCodes) > 0 { tagFilters = append(tagFilters, bson.M{"tags.cpv_codes": bson.M{"$in": cpvCodes}}) } if len(categories) > 0 { tagFilters = append(tagFilters, bson.M{"tags.categories": bson.M{"$in": categories}}) } if len(keywords) > 0 { tagFilters = append(tagFilters, bson.M{"tags.keywords": bson.M{"$in": keywords}}) } if len(specializations) > 0 { tagFilters = append(tagFilters, bson.M{"tags.specializations": bson.M{"$in": specializations}}) } if len(tagFilters) > 0 { filter["$or"] = tagFilters } // Only active companies filter["status"] = bson.M{"$ne": CompanyStatusInactive} // Build pagination pagination := mongo.NewPaginationBuilder(). Limit(limit). Skip(offset). SortDesc("created_at"). Build() // Use ORM to find companies result, err := r.ormRepo.FindAll(ctx, filter, pagination) if err != nil { r.logger.Error("Failed to search companies by tags", map[string]interface{}{ "error": err.Error(), }) return nil, err } // Convert []Company to []*Company companies := make([]*Company, len(result.Items)) for i := range result.Items { companies[i] = &result.Items[i] } return companies, nil } // Helper functions for tag management func (r *companyRepository) addUniqueStrings(existing []string, new []string) []string { existingMap := make(map[string]bool) for _, s := range existing { existingMap[s] = true } result := make([]string, len(existing)) copy(result, existing) for _, s := range new { if !existingMap[s] { result = append(result, s) existingMap[s] = true } } return result } func (r *companyRepository) removeStrings(existing []string, toRemove []string) []string { removeMap := make(map[string]bool) for _, s := range toRemove { removeMap[s] = true } var result []string for _, s := range existing { if !removeMap[s] { result = append(result, s) } } return result }