1e998b365e
- Introduced a new company management domain, including entities, services, handlers, and forms for company operations. - Implemented CRUD functionality for companies, allowing for creation, retrieval, updating, and deletion of company records. - Enhanced API endpoints for company management, including search and filtering capabilities based on various criteria. - Integrated JWT-based user authorization for company management operations. - Updated Swagger documentation to include new company-related endpoints and detailed request/response structures. - Established MongoDB ORM integration for efficient data handling and repository management. - Added comprehensive validation rules for company forms to ensure data integrity and consistency. - Implemented logging for key operations within the company service and repository for better traceability.
878 lines
25 KiB
Go
878 lines
25 KiB
Go
package company
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
"tm/pkg/logger"
|
|
mongopkg "tm/pkg/mongo"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
)
|
|
|
|
// 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)
|
|
GetByCustomerID(ctx context.Context, customerID 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, hasCustomer *bool, 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)
|
|
CountWithCustomer(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
|
|
AssignCustomer(ctx context.Context, id string, customerID string) error
|
|
UnassignCustomer(ctx context.Context, id 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 mongopkg.Repository[Company]
|
|
logger logger.Logger
|
|
}
|
|
|
|
// NewCompanyRepository creates a new company repository
|
|
func NewCompanyRepository(mongoManager *mongopkg.ConnectionManager, logger logger.Logger) Repository {
|
|
// Create indexes using the ORM's index management
|
|
indexes := []mongopkg.Index{
|
|
*mongopkg.CreateUniqueIndex("name_idx", bson.D{{Key: "name", Value: 1}}),
|
|
*mongopkg.CreateUniqueIndex("registration_number_idx", bson.D{{Key: "registration_number", Value: 1}}),
|
|
*mongopkg.CreateUniqueIndex("tax_id_idx", bson.D{{Key: "tax_id", Value: 1}}),
|
|
*mongopkg.NewIndex("customer_id_idx", bson.D{{Key: "customer_id", Value: 1}}),
|
|
*mongopkg.NewIndex("type_idx", bson.D{{Key: "type", Value: 1}}),
|
|
*mongopkg.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}),
|
|
*mongopkg.NewIndex("industry_idx", bson.D{{Key: "industry", Value: 1}}),
|
|
*mongopkg.NewIndex("is_verified_idx", bson.D{{Key: "is_verified", Value: 1}}),
|
|
*mongopkg.NewIndex("is_compliant_idx", bson.D{{Key: "is_compliant", Value: 1}}),
|
|
*mongopkg.NewIndex("language_idx", bson.D{{Key: "language", Value: 1}}),
|
|
*mongopkg.NewIndex("currency_idx", bson.D{{Key: "currency", Value: 1}}),
|
|
*mongopkg.NewIndex("employee_count_idx", bson.D{{Key: "employee_count", Value: 1}}),
|
|
*mongopkg.NewIndex("annual_revenue_idx", bson.D{{Key: "annual_revenue", Value: 1}}),
|
|
*mongopkg.NewIndex("founded_year_idx", bson.D{{Key: "founded_year", Value: 1}}),
|
|
*mongopkg.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
|
|
// Tag indexes for efficient search
|
|
*mongopkg.NewIndex("cpv_codes_idx", bson.D{{Key: "tags.cpv_codes", Value: 1}}),
|
|
*mongopkg.NewIndex("categories_idx", bson.D{{Key: "tags.categories", Value: 1}}),
|
|
*mongopkg.NewIndex("keywords_idx", bson.D{{Key: "tags.keywords", Value: 1}}),
|
|
*mongopkg.NewIndex("specializations_idx", bson.D{{Key: "tags.specializations", Value: 1}}),
|
|
*mongopkg.NewIndex("certifications_idx", bson.D{{Key: "tags.certifications", Value: 1}}),
|
|
// Text index for search
|
|
*mongopkg.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 := mongopkg.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, mongopkg.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, mongopkg.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, mongopkg.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, mongopkg.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
|
|
}
|
|
|
|
// GetByCustomerID retrieves a company by customer ID
|
|
func (r *companyRepository) GetByCustomerID(ctx context.Context, customerID string) (*Company, error) {
|
|
filter := bson.M{"customer_id": customerID}
|
|
company, err := r.ormRepo.FindOne(ctx, filter)
|
|
if err != nil {
|
|
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
|
return nil, errors.New("company not found")
|
|
}
|
|
r.logger.Error("Failed to get company by customer ID", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"customer_id": customerID,
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
return company, 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 := mongopkg.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, hasCustomer *bool, 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
|
|
}
|
|
|
|
if hasCustomer != nil {
|
|
if *hasCustomer {
|
|
filter["customer_id"] = bson.M{"$ne": nil}
|
|
} else {
|
|
filter["customer_id"] = nil
|
|
}
|
|
}
|
|
|
|
// 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 := mongopkg.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
|
|
}
|
|
|
|
// CountWithCustomer counts companies with assigned customers
|
|
func (r *companyRepository) CountWithCustomer(ctx context.Context) (int64, error) {
|
|
filter := bson.M{
|
|
"customer_id": bson.M{"$ne": nil},
|
|
"status": bson.M{"$ne": CompanyStatusInactive},
|
|
}
|
|
|
|
count, err := r.ormRepo.Count(ctx, filter)
|
|
if err != nil {
|
|
r.logger.Error("Failed to count companies with customer", 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
|
|
}
|
|
|
|
// AssignCustomer assigns a customer to a company
|
|
func (r *companyRepository) AssignCustomer(ctx context.Context, id string, customerID string) error {
|
|
// Get company first
|
|
company, err := r.GetByID(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Assign customer
|
|
company.CustomerID = &customerID
|
|
company.SetUpdatedAt(time.Now().Unix())
|
|
|
|
// Update in database
|
|
err = r.ormRepo.Update(ctx, company)
|
|
if err != nil {
|
|
r.logger.Error("Failed to assign customer to company", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"company_id": id,
|
|
"customer_id": customerID,
|
|
})
|
|
return err
|
|
}
|
|
|
|
r.logger.Info("Customer assigned to company successfully", map[string]interface{}{
|
|
"company_id": id,
|
|
"customer_id": customerID,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// UnassignCustomer unassigns a customer from a company
|
|
func (r *companyRepository) UnassignCustomer(ctx context.Context, id string) error {
|
|
// Get company first
|
|
company, err := r.GetByID(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Unassign customer
|
|
company.CustomerID = nil
|
|
company.SetUpdatedAt(time.Now().Unix())
|
|
|
|
// Update in database
|
|
err = r.ormRepo.Update(ctx, company)
|
|
if err != nil {
|
|
r.logger.Error("Failed to unassign customer from company", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"company_id": id,
|
|
})
|
|
return err
|
|
}
|
|
|
|
r.logger.Info("Customer unassigned from company successfully", map[string]interface{}{
|
|
"company_id": id,
|
|
})
|
|
|
|
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)
|
|
companiesWithCustomer, _ := r.CountWithCustomer(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,
|
|
CompaniesWithCustomer: companiesWithCustomer,
|
|
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 := mongopkg.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
|
|
}
|