538 lines
15 KiB
Go
538 lines
15 KiB
Go
package company
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"mime/multipart"
|
|
|
|
"tm/internal/company_category"
|
|
"tm/pkg/filestore"
|
|
"tm/pkg/logger"
|
|
"tm/pkg/response"
|
|
)
|
|
|
|
// Service defines business logic for company operations
|
|
type Service interface {
|
|
// Core company operations
|
|
Create(ctx context.Context, form *CompanyForm) (*CompanyResponse, error)
|
|
Update(ctx context.Context, id string, form *CompanyForm) (*CompanyResponse, error)
|
|
GetByID(ctx context.Context, id string) (*CompanyResponse, error)
|
|
GetByIDs(ctx context.Context, ids []string) ([]*CompanyResponse, error)
|
|
GetNamesByIDs(ctx context.Context, ids []string) (map[string]*CompanyResponse, error)
|
|
Delete(ctx context.Context, id string) error
|
|
|
|
// Company listing and search
|
|
SearchCompanies(ctx context.Context, form *SearchForm, pagination *response.Pagination) (*CompanyListResponse, error)
|
|
|
|
// Company management
|
|
UpdateStatus(ctx context.Context, id string, form *StatusForm) error
|
|
UpdateVerification(ctx context.Context, id string, form *VerificationForm) error
|
|
|
|
// Get my company
|
|
GetMyCompany(ctx context.Context, id string) (*CompanyProfileResponse, error)
|
|
|
|
// UploadDocuments uploads files and attaches them to a company
|
|
UploadDocuments(ctx context.Context, companyID, uploadedBy string, files []*multipart.FileHeader) (*UploadDocumentsResponse, error)
|
|
}
|
|
|
|
// companyService implements the Service interface
|
|
type companyService struct {
|
|
repository Repository
|
|
categoryService company_category.Service
|
|
fileStore filestore.FileStoreService
|
|
logger logger.Logger
|
|
}
|
|
|
|
// NewService creates a new company service
|
|
func NewService(repository Repository, categoryService company_category.Service, fileStore filestore.FileStoreService, logger logger.Logger) Service {
|
|
return &companyService{
|
|
repository: repository,
|
|
categoryService: categoryService,
|
|
fileStore: fileStore,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Create creates a new company
|
|
func (s *companyService) Create(ctx context.Context, form *CompanyForm) (*CompanyResponse, error) {
|
|
// Validate categories if provided
|
|
if form.Tags != nil && len(form.Tags.Categories) > 0 {
|
|
validCategories, err := s.categoryService.GetByIDs(ctx, form.Tags.Categories)
|
|
if err != nil {
|
|
s.logger.Error("Failed to validate categories", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"categories": form.Tags.Categories,
|
|
})
|
|
return nil, errors.New("failed to validate categories")
|
|
}
|
|
|
|
// Check if all provided categories exist
|
|
if len(validCategories) != len(form.Tags.Categories) {
|
|
return nil, errors.New("one or more categories are invalid")
|
|
}
|
|
}
|
|
|
|
// 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: CompanyStatusActive,
|
|
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),
|
|
Phone: form.Phone,
|
|
Email: form.Email,
|
|
DocumentFileIDs: form.DocumentFileIDs,
|
|
Tags: s.convertCompanyTagsForm(form.Tags),
|
|
IsVerified: false,
|
|
IsCompliant: false,
|
|
Language: s.getDefaultLanguage(form.Language),
|
|
Currency: s.getDefaultCurrency(form.Currency),
|
|
Timezone: s.getDefaultTimezone(form.Timezone),
|
|
}
|
|
|
|
// 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,
|
|
})
|
|
|
|
return company.ToResponse(nil), nil
|
|
}
|
|
|
|
// GetCompanyByID retrieves a company by ID
|
|
func (s *companyService) GetByID(ctx context.Context, id string) (*CompanyResponse, 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,
|
|
})
|
|
|
|
// Get category details for response
|
|
categoryDetails, err := s.categoryService.GetByIDs(ctx, company.Tags.Categories)
|
|
if err != nil {
|
|
s.logger.Warn("Failed to fetch category details for response", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"company_id": company.ID,
|
|
})
|
|
}
|
|
|
|
var convertedCategories []*CategoryResponse
|
|
for _, cat := range categoryDetails {
|
|
convertedCategories = append(convertedCategories, &CategoryResponse{
|
|
ID: cat.ID,
|
|
Name: cat.Name,
|
|
})
|
|
}
|
|
|
|
return company.ToResponse(convertedCategories), nil
|
|
}
|
|
|
|
// UpdateCompany updates a company
|
|
func (s *companyService) Update(ctx context.Context, id string, form *CompanyForm) (*CompanyResponse, error) {
|
|
// Validate categories if provided
|
|
if form.Tags != nil && len(form.Tags.Categories) > 0 {
|
|
validCategories, err := s.categoryService.GetByIDs(ctx, form.Tags.Categories)
|
|
if err != nil {
|
|
s.logger.Error("Failed to validate categories", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"categories": form.Tags.Categories,
|
|
})
|
|
return nil, errors.New("failed to validate categories")
|
|
}
|
|
|
|
// Check if all provided categories exist
|
|
if len(validCategories) != len(form.Tags.Categories) {
|
|
return nil, errors.New("one or more categories are invalid")
|
|
}
|
|
}
|
|
|
|
// 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 != "" && 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 != "" && 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 != "" && 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 != "" {
|
|
company.Type = CompanyType(form.Type)
|
|
}
|
|
|
|
if form.Industry != "" {
|
|
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.Phone != nil {
|
|
company.Phone = form.Phone
|
|
}
|
|
|
|
if form.Email != nil {
|
|
company.Email = form.Email
|
|
}
|
|
|
|
if form.DocumentFileIDs != nil {
|
|
company.DocumentFileIDs = form.DocumentFileIDs
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// 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,
|
|
})
|
|
|
|
return company.ToResponse(nil), nil
|
|
}
|
|
|
|
// DeleteCompany deletes a company (soft delete)
|
|
func (s *companyService) Delete(ctx context.Context, id string) error {
|
|
// 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,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// SearchCompanies searches companies with advanced filters
|
|
func (s *companyService) SearchCompanies(ctx context.Context, form *SearchForm, pagination *response.Pagination) (*CompanyListResponse, error) {
|
|
|
|
// Get companies using search
|
|
page, err := s.repository.Search(ctx, form, pagination)
|
|
if err != nil {
|
|
s.logger.Error("Failed to search companies", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
var companyResponses []*CompanyResponse
|
|
for _, company := range page.Items {
|
|
// Get category details for response
|
|
categoryDetails, err := s.categoryService.GetByIDs(ctx, company.Tags.Categories)
|
|
if err != nil {
|
|
s.logger.Warn("Failed to fetch category details for response", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"company_id": company.ID,
|
|
})
|
|
}
|
|
|
|
var convertedCategories []*CategoryResponse
|
|
for _, cat := range categoryDetails {
|
|
convertedCategories = append(convertedCategories, &CategoryResponse{
|
|
ID: cat.ID,
|
|
Name: cat.Name,
|
|
})
|
|
}
|
|
|
|
companyResponses = append(companyResponses, company.ToResponse(convertedCategories))
|
|
}
|
|
|
|
return &CompanyListResponse{
|
|
Companies: companyResponses,
|
|
Meta: pagination.ListMeta(page.TotalCount, page.NextCursor, page.HasMore, page.PageOffset),
|
|
}, nil
|
|
}
|
|
|
|
// GetByIDs retrieves companies by IDs
|
|
func (s *companyService) GetByIDs(ctx context.Context, ids []string) ([]*CompanyResponse, error) {
|
|
companies, err := s.repository.GetByIDs(ctx, ids)
|
|
if err != nil {
|
|
return nil, errors.New("failed to get companies by IDs")
|
|
}
|
|
|
|
var companyResponses []*CompanyResponse
|
|
for _, company := range companies {
|
|
// Get category details for response
|
|
categoryDetails, err := s.categoryService.GetByIDs(ctx, company.Tags.Categories)
|
|
if err != nil {
|
|
s.logger.Warn("Failed to fetch category details for response", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"company_id": company.ID,
|
|
})
|
|
}
|
|
|
|
var convertedCategories []*CategoryResponse
|
|
for _, cat := range categoryDetails {
|
|
convertedCategories = append(convertedCategories, &CategoryResponse{
|
|
ID: cat.ID,
|
|
Name: cat.Name,
|
|
})
|
|
}
|
|
|
|
companyResponses = append(companyResponses, company.ToResponse(convertedCategories))
|
|
}
|
|
|
|
return companyResponses, nil
|
|
}
|
|
|
|
// GetNamesByIDs returns company id and name keyed by company ID without loading categories.
|
|
func (s *companyService) GetNamesByIDs(ctx context.Context, ids []string) (map[string]*CompanyResponse, error) {
|
|
if len(ids) == 0 {
|
|
return map[string]*CompanyResponse{}, nil
|
|
}
|
|
|
|
companies, err := s.repository.GetByIDs(ctx, ids)
|
|
if err != nil {
|
|
s.logger.Error("Failed to get companies by IDs for names", map[string]interface{}{
|
|
"count": len(ids),
|
|
"error": err.Error(),
|
|
})
|
|
return nil, errors.New("failed to get companies by IDs")
|
|
}
|
|
|
|
out := make(map[string]*CompanyResponse, len(companies))
|
|
for _, c := range companies {
|
|
id := c.ID.Hex()
|
|
out[id] = &CompanyResponse{ID: id, Name: c.Name}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
// UpdateCompanyStatus updates company status
|
|
func (s *companyService) UpdateStatus(ctx context.Context, id string, form *StatusForm) error {
|
|
// Get company to check if exists
|
|
_, err := s.repository.GetByID(ctx, id)
|
|
if err != nil {
|
|
return errors.New("company not found")
|
|
}
|
|
|
|
// Update status
|
|
err = s.repository.UpdateStatus(ctx, id, CompanyStatus(form.Status))
|
|
if err != nil {
|
|
s.logger.Error("Failed to update company status", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"company_id": id,
|
|
"status": CompanyStatus(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,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateCompanyVerification updates company verification status
|
|
func (s *companyService) UpdateVerification(ctx context.Context, id string, form *VerificationForm) 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,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetMyCompany retrieves the company for the authenticated user
|
|
func (s *companyService) GetMyCompany(ctx context.Context, id string) (*CompanyProfileResponse, error) {
|
|
company, err := s.repository.GetByID(ctx, id)
|
|
if err != nil {
|
|
return nil, errors.New("company not found")
|
|
}
|
|
|
|
return company.ToProfileResponse(), 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) 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"
|
|
}
|