Implement Company Search and Update API Documentation

- Introduced a new search functionality for companies, allowing advanced filtering capabilities including tags, business criteria, and location.
- Updated the API documentation to reflect changes in the search endpoint, enhancing clarity for API consumers.
- Refactored existing company-related API endpoints for consistency, including renaming and restructuring routes.
- Enhanced response structures to return company entities directly, simplifying the response handling in API endpoints.
- Removed unused query parameters and handlers, streamlining the company management functionality.
This commit is contained in:
n.nakhostin
2025-09-08 11:23:15 +03:30
parent 777bbfd714
commit ee830f8c1b
16 changed files with 589 additions and 5338 deletions
+86 -1318
View File
File diff suppressed because it is too large Load Diff
+86 -1318
View File
File diff suppressed because it is too large Load Diff
+81 -881
View File
File diff suppressed because it is too large Load Diff
+7 -18
View File
@@ -47,24 +47,13 @@ func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler
companiesGP := adminV1.Group("/companies") companiesGP := adminV1.Group("/companies")
{ {
companiesGP.Use(userHandler.AuthMiddleware()) companiesGP.Use(userHandler.AuthMiddleware())
companiesGP.POST("", companyHandler.CreateCompany) companiesGP.POST("", companyHandler.Create)
companiesGP.GET("", companyHandler.ListCompanies) companiesGP.GET("", companyHandler.Search)
companiesGP.GET("/:id", companyHandler.GetCompany) companiesGP.GET("/:id", companyHandler.Get)
companiesGP.PUT("/:id", companyHandler.UpdateCompany) companiesGP.PUT("/:id", companyHandler.Update)
companiesGP.DELETE("/:id", companyHandler.DeleteCompany) companiesGP.DELETE("/:id", companyHandler.Delete)
companiesGP.GET("/search", companyHandler.SearchCompanies) companiesGP.PATCH("/:id/status", companyHandler.UpdateStatus)
companiesGP.PATCH("/:id/status", companyHandler.UpdateCompanyStatus) companiesGP.PATCH("/:id/verification", companyHandler.UpdateVerification)
companiesGP.PATCH("/:id/verification", companyHandler.UpdateCompanyVerification)
companiesGP.PUT("/:id/tags", companyHandler.UpdateCompanyTags)
companiesGP.POST("/:id/tags/add", companyHandler.AddTags)
companiesGP.POST("/:id/tags/remove", companyHandler.RemoveTags)
companiesGP.POST("/:id/verify", companyHandler.VerifyCompany)
companiesGP.POST("/:id/suspend", companyHandler.SuspendCompany)
companiesGP.POST("/:id/activate", companyHandler.ActivateCompany)
companiesGP.GET("/stats", companyHandler.GetCompanyStats)
companiesGP.GET("/type/:type", companyHandler.GetCompaniesByType)
companiesGP.GET("/status/:status", companyHandler.GetCompaniesByStatus)
companiesGP.GET("/industry/:industry", companyHandler.GetCompaniesByIndustry)
} }
// Admin Customers Routes // Admin Customers Routes
+6 -109
View File
@@ -13,7 +13,6 @@ const (
CompanyStatusActive CompanyStatus = "active" CompanyStatusActive CompanyStatus = "active"
CompanyStatusInactive CompanyStatus = "inactive" CompanyStatusInactive CompanyStatus = "inactive"
CompanyStatusSuspended CompanyStatus = "suspended" CompanyStatusSuspended CompanyStatus = "suspended"
CompanyStatusPending CompanyStatus = "pending"
) )
// CompanyType represents the type of company // CompanyType represents the type of company
@@ -48,9 +47,8 @@ type Company struct {
Address *Address `bson:"address,omitempty" json:"address,omitempty"` Address *Address `bson:"address,omitempty" json:"address,omitempty"`
// Contact information // Contact information
ContactPerson *ContactPerson `bson:"contact_person,omitempty" json:"contact_person,omitempty"` Phone *string `bson:"phone,omitempty" json:"phone,omitempty"`
Phone *string `bson:"phone,omitempty" json:"phone,omitempty"` Email *string `bson:"email,omitempty" json:"email,omitempty"`
Email *string `bson:"email,omitempty" json:"email,omitempty"`
// Tags for categorization and search // Tags for categorization and search
Tags *CompanyTags `bson:"tags,omitempty" json:"tags,omitempty"` Tags *CompanyTags `bson:"tags,omitempty" json:"tags,omitempty"`
@@ -64,10 +62,6 @@ type Company struct {
Language string `bson:"language" json:"language"` // Default: "en" Language string `bson:"language" json:"language"` // Default: "en"
Currency string `bson:"currency" json:"currency"` // Default: "USD" Currency string `bson:"currency" json:"currency"` // Default: "USD"
Timezone string `bson:"timezone" json:"timezone"` // Default: "UTC" Timezone string `bson:"timezone" json:"timezone"` // Default: "UTC"
// Audit fields
CreatedBy *string `bson:"created_by,omitempty" json:"created_by,omitempty"`
UpdatedBy *string `bson:"updated_by,omitempty" json:"updated_by,omitempty"`
} }
// SetID sets the company ID (implements IDSetter interface) // SetID sets the company ID (implements IDSetter interface)
@@ -80,22 +74,22 @@ func (c *Company) GetID() string {
return c.ID.Hex() return c.ID.Hex()
} }
// SetCreatedAt sets the created timestamp (implements Timestampable interface) // SetCreatedAt sets the created timestamp (implements Timestamp interface)
func (c *Company) SetCreatedAt(timestamp int64) { func (c *Company) SetCreatedAt(timestamp int64) {
c.CreatedAt = timestamp c.CreatedAt = timestamp
} }
// SetUpdatedAt sets the updated timestamp (implements Timestampable interface) // SetUpdatedAt sets the updated timestamp (implements Timestamp interface)
func (c *Company) SetUpdatedAt(timestamp int64) { func (c *Company) SetUpdatedAt(timestamp int64) {
c.UpdatedAt = timestamp c.UpdatedAt = timestamp
} }
// GetCreatedAt returns the created timestamp (implements Timestampable interface) // GetCreatedAt returns the created timestamp (implements Timestamp interface)
func (c *Company) GetCreatedAt() int64 { func (c *Company) GetCreatedAt() int64 {
return c.CreatedAt return c.CreatedAt
} }
// GetUpdatedAt returns the updated timestamp (implements Timestampable interface) // GetUpdatedAt returns the updated timestamp (implements Timestamp interface)
func (c *Company) GetUpdatedAt() int64 { func (c *Company) GetUpdatedAt() int64 {
return c.UpdatedAt return c.UpdatedAt
} }
@@ -109,18 +103,6 @@ type Address struct {
Country string `bson:"country" json:"country"` Country string `bson:"country" json:"country"`
} }
// ContactPerson represents a contact person for the company
type ContactPerson struct {
FirstName string `bson:"first_name" json:"first_name"`
LastName string `bson:"last_name" json:"last_name"`
FullName string `bson:"full_name" json:"full_name"`
Position string `bson:"position" json:"position"`
Email string `bson:"email" json:"email"`
Phone string `bson:"phone" json:"phone"`
Mobile *string `bson:"mobile,omitempty" json:"mobile,omitempty"`
IsPrimary bool `bson:"is_primary" json:"is_primary"`
}
// CompanyTags represents tags for company categorization and search // CompanyTags represents tags for company categorization and search
type CompanyTags struct { type CompanyTags struct {
// CPV codes (Common Procurement Vocabulary) // CPV codes (Common Procurement Vocabulary)
@@ -138,88 +120,3 @@ type CompanyTags struct {
// Certifications // Certifications
Certifications []string `bson:"certifications,omitempty" json:"certifications,omitempty"` Certifications []string `bson:"certifications,omitempty" json:"certifications,omitempty"`
} }
// CompanyResponse represents the company data sent in API responses
type CompanyResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Status string `json:"status"`
RegistrationNumber string `json:"registration_number"`
TaxID string `json:"tax_id"`
Industry string `json:"industry"`
Description *string `json:"description,omitempty"`
Website *string `json:"website,omitempty"`
EmployeeCount *int `json:"employee_count,omitempty"`
AnnualRevenue *float64 `json:"annual_revenue,omitempty"`
FoundedYear *int `json:"founded_year,omitempty"`
Address *Address `json:"address,omitempty"`
ContactPerson *ContactPerson `json:"contact_person,omitempty"`
Phone *string `json:"phone,omitempty"`
Email *string `json:"email,omitempty"`
Tags *CompanyTags `json:"tags,omitempty"`
IsVerified bool `json:"is_verified"`
IsCompliant bool `json:"is_compliant"`
ComplianceNotes *string `json:"compliance_notes,omitempty"`
Language string `json:"language"`
Currency string `json:"currency"`
Timezone string `json:"timezone"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
CreatedBy *string `json:"created_by,omitempty"`
UpdatedBy *string `json:"updated_by,omitempty"`
}
// ToResponse converts Company to CompanyResponse
func (c *Company) ToResponse() *CompanyResponse {
return &CompanyResponse{
ID: c.ID.Hex(),
Name: c.Name,
Type: string(c.Type),
Status: string(c.Status),
RegistrationNumber: c.RegistrationNumber,
TaxID: c.TaxID,
Industry: c.Industry,
Description: c.Description,
Website: c.Website,
EmployeeCount: c.EmployeeCount,
AnnualRevenue: c.AnnualRevenue,
FoundedYear: c.FoundedYear,
Address: c.Address,
ContactPerson: c.ContactPerson,
Phone: c.Phone,
Email: c.Email,
Tags: c.Tags,
IsVerified: c.IsVerified,
IsCompliant: c.IsCompliant,
ComplianceNotes: c.ComplianceNotes,
Language: c.Language,
Currency: c.Currency,
Timezone: c.Timezone,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
CreatedBy: c.CreatedBy,
UpdatedBy: c.UpdatedBy,
}
}
type CompanyProfileResponse struct {
ID string `json:"id"`
Name string `json:"name"`
RegistrationNumber string `json:"registration_number"`
Industry string `json:"industry"`
FoundedYear *int `json:"founded_year,omitempty"`
CreatedAt int64 `json:"created_at"`
}
// ToResponse converts Company to CompanyResponse
func (c *Company) ToProfileResponse() *CompanyProfileResponse {
return &CompanyProfileResponse{
ID: c.ID.Hex(),
Name: c.Name,
RegistrationNumber: c.RegistrationNumber,
Industry: c.Industry,
FoundedYear: c.FoundedYear,
CreatedAt: c.CreatedAt,
}
}
+127 -159
View File
@@ -1,72 +1,61 @@
package company package company
// CreateCompanyForm represents the form for creating a new company import "tm/pkg/response"
type CreateCompanyForm struct {
Name string `json:"name" valid:"required,length(2|200)"`
Type string `json:"type" valid:"required,in(private|public|government|ngo|startup)"`
RegistrationNumber string `json:"registration_number" valid:"required,length(5|50)"`
TaxID string `json:"tax_id" valid:"required,length(5|50)"`
Industry string `json:"industry" valid:"required,length(2|100)"`
Description *string `json:"description,omitempty" valid:"optional,length(10|1000)"`
Website *string `json:"website,omitempty" valid:"optional,url"`
// Business information // CompanyForm represents the form for creating/updating a new company
EmployeeCount *int `json:"employee_count,omitempty" valid:"optional,range(1|100000)"` type (
AnnualRevenue *float64 `json:"annual_revenue,omitempty" valid:"optional,range(0|999999999999)"` CompanyForm struct {
FoundedYear *int `json:"founded_year,omitempty" valid:"optional,range(1800|2100)"` Name string `json:"name" valid:"required,length(2|200)"`
Type string `json:"type" valid:"required,in(private|public|government|ngo|startup)"`
RegistrationNumber string `json:"registration_number" valid:"required,length(5|50)"`
TaxID string `json:"tax_id" valid:"required,length(5|50)"`
Industry string `json:"industry" valid:"required,length(2|100)"`
Description *string `json:"description,omitempty" valid:"optional,length(10|1000)"`
Website *string `json:"website,omitempty" valid:"optional,url"`
// Address information // Business information
Address *AddressForm `json:"address,omitempty"` EmployeeCount *int `json:"employee_count,omitempty" valid:"optional,range(1|100000)"`
AnnualRevenue *float64 `json:"annual_revenue,omitempty" valid:"optional,range(0|999999999999)"`
FoundedYear *int `json:"founded_year,omitempty" valid:"optional,range(1800|2100)"`
// Contact information // Address information
ContactPerson *ContactPersonForm `json:"contact_person,omitempty"` Address *AddressForm `json:"address,omitempty"`
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"`
Email *string `json:"email,omitempty" valid:"optional,email"`
// Tags for categorization and search // Contact information
Tags *CompanyTagsForm `json:"tags,omitempty"` Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"`
Email *string `json:"email,omitempty" valid:"optional,email"`
// Settings // Tags for categorization and search
Language *string `json:"language,omitempty" valid:"optional,in(en|ar|fr|es|de|zh|ja|ko)"` Tags *CompanyTagsForm `json:"tags,omitempty"`
Currency *string `json:"currency,omitempty" valid:"optional,in(USD|EUR|GBP|JPY|CAD|AUD|CHF|CNY|INR|BRL)"`
Timezone *string `json:"timezone,omitempty" valid:"optional,length(3|50)"`
}
// UpdateCompanyForm represents the form for updating a company // Settings
type UpdateCompanyForm struct { Language *string `json:"language,omitempty" valid:"optional,in(en|ar|fr|es|de|zh|ja|ko)"`
Name *string `json:"name,omitempty" valid:"optional,length(2|200)"` Currency *string `json:"currency,omitempty" valid:"optional,in(USD|EUR|GBP|JPY|CAD|AUD|CHF|CNY|INR|BRL)"`
Type *string `json:"type,omitempty" valid:"optional,in(private|public|government|ngo|startup)"` Timezone *string `json:"timezone,omitempty" valid:"optional,length(3|50)"`
RegistrationNumber *string `json:"registration_number,omitempty" valid:"optional,length(5|50)"` }
TaxID *string `json:"tax_id,omitempty" valid:"optional,length(5|50)"`
Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"`
Description *string `json:"description,omitempty" valid:"optional,length(10|1000)"`
Website *string `json:"website,omitempty" valid:"optional,url"`
// Business information // AddressForm represents the form for company address
EmployeeCount *int `json:"employee_count,omitempty" valid:"optional,range(1|100000)"` AddressForm struct {
AnnualRevenue *float64 `json:"annual_revenue,omitempty" valid:"optional,range(0|999999999999)"` Street string `json:"street" valid:"required,length(5|200)"`
FoundedYear *int `json:"founded_year,omitempty" valid:"optional,range(1800|2100)"` City string `json:"city" valid:"required,length(2|100)"`
State string `json:"state" valid:"required,length(2|100)"`
PostalCode string `json:"postal_code" valid:"required,length(3|20)"`
Country string `json:"country" valid:"required,length(2|100)"`
}
// Address information // CompanyTagsForm represents the form for company tags
Address *AddressForm `json:"address,omitempty"` CompanyTagsForm struct {
CPVCodes []string `json:"cpv_codes,omitempty" valid:"optional"`
Categories []string `json:"categories,omitempty" valid:"optional"`
Keywords []string `json:"keywords,omitempty" valid:"optional"`
Specializations []string `json:"specializations,omitempty" valid:"optional"`
Certifications []string `json:"certifications,omitempty" valid:"optional"`
}
)
// Contact information // SearchForm represents the form for searching companies with filters
ContactPerson *ContactPersonForm `json:"contact_person,omitempty"` type SearchForm struct {
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"` Search *string `query:"q" valid:"optional"`
Email *string `json:"email,omitempty" valid:"optional,email"`
// Tags for categorization and search
Tags *CompanyTagsForm `json:"tags,omitempty"`
// Settings
Language *string `json:"language,omitempty" valid:"optional,in(en|ar|fr|es|de|zh|ja|ko)"`
Currency *string `json:"currency,omitempty" valid:"optional,in(USD|EUR|GBP|JPY|CAD|AUD|CHF|CNY|INR|BRL)"`
Timezone *string `json:"timezone,omitempty" valid:"optional,length(3|50)"`
}
// ListCompaniesForm represents the form for listing companies with filters
type ListCompaniesForm struct {
Search *string `query:"search" valid:"optional"`
Type *string `query:"type" valid:"optional,in(private|public|government|ngo|startup)"` Type *string `query:"type" valid:"optional,in(private|public|government|ngo|startup)"`
Status *string `query:"status" valid:"optional,in(active|inactive|suspended|pending)"` Status *string `query:"status" valid:"optional,in(active|inactive|suspended|pending)"`
Industry *string `query:"industry" valid:"optional"` Industry *string `query:"industry" valid:"optional"`
@@ -90,122 +79,101 @@ type ListCompaniesForm struct {
SortOrder *string `query:"sort_order" valid:"optional,in(asc|desc)"` SortOrder *string `query:"sort_order" valid:"optional,in(asc|desc)"`
} }
// UpdateCompanyStatusForm represents the form for updating company status // StatusForm represents the form for updating company status
type UpdateCompanyStatusForm struct { type StatusForm struct {
Status string `json:"status" valid:"required,in(active|inactive|suspended|pending)"` Status string `json:"status" valid:"required,in(active|inactive|suspended)"`
Reason *string `json:"reason" valid:"optional,length(10|500)"`
} }
// UpdateCompanyVerificationForm represents the form for updating company verification status // VerificationForm represents the form for updating company verification status
type UpdateCompanyVerificationForm struct { type VerificationForm struct {
IsVerified bool `json:"is_verified"` IsVerified bool `json:"is_verified"`
IsCompliant bool `json:"is_compliant"` IsCompliant bool `json:"is_compliant"`
ComplianceNotes *string `json:"compliance_notes,omitempty" valid:"optional,length(0|1000)"` ComplianceNotes *string `json:"compliance_notes,omitempty" valid:"optional,length(0|1000)"`
} }
// UpdateCompanyTagsForm represents the form for updating company tags
type UpdateCompanyTagsForm struct {
Tags *CompanyTagsForm `json:"tags" valid:"required"`
}
// AddTagsForm represents the form for adding tags to a company
type AddTagsForm struct {
CPVCodes []string `json:"cpv_codes,omitempty" valid:"optional"`
Categories []string `json:"categories,omitempty" valid:"optional"`
Keywords []string `json:"keywords,omitempty" valid:"optional"`
Specializations []string `json:"specializations,omitempty" valid:"optional"`
Certifications []string `json:"certifications,omitempty" valid:"optional"`
}
// RemoveTagsForm represents the form for removing tags from a company
type RemoveTagsForm struct {
CPVCodes []string `json:"cpv_codes,omitempty" valid:"optional"`
Categories []string `json:"categories,omitempty" valid:"optional"`
Keywords []string `json:"keywords,omitempty" valid:"optional"`
Specializations []string `json:"specializations,omitempty" valid:"optional"`
Certifications []string `json:"certifications,omitempty" valid:"optional"`
}
// SuspendCompanyForm represents the form for suspending a company
type SuspendCompanyForm struct {
Reason string `json:"reason" valid:"required,length(10|500)"`
}
// AddressForm represents the form for company address
type AddressForm struct {
Street string `json:"street" valid:"required,length(5|200)"`
City string `json:"city" valid:"required,length(2|100)"`
State string `json:"state" valid:"required,length(2|100)"`
PostalCode string `json:"postal_code" valid:"required,length(3|20)"`
Country string `json:"country" valid:"required,length(2|100)"`
}
// ContactPersonForm represents the form for contact person
type ContactPersonForm struct {
FirstName string `json:"first_name" valid:"required,length(2|50)"`
LastName string `json:"last_name" valid:"required,length(2|50)"`
FullName string `json:"full_name" valid:"required,length(2|100)"`
Position string `json:"position" valid:"required,length(2|100)"`
Email string `json:"email" valid:"required,email"`
Phone string `json:"phone" valid:"required,length(10|20)"`
Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"`
IsPrimary bool `json:"is_primary"`
}
// CompanyTagsForm represents the form for company tags
type CompanyTagsForm struct {
CPVCodes []string `json:"cpv_codes,omitempty" valid:"optional"`
Categories []string `json:"categories,omitempty" valid:"optional"`
Keywords []string `json:"keywords,omitempty" valid:"optional"`
Specializations []string `json:"specializations,omitempty" valid:"optional"`
Certifications []string `json:"certifications,omitempty" valid:"optional"`
}
// CompanyListResponse represents the response for listing companies // CompanyListResponse represents the response for listing companies
type CompanyListResponse struct { type CompanyListResponse struct {
Companies []*CompanyResponse `json:"companies"` Companies []*CompanyResponse `json:"companies"`
Total int64 `json:"total"` Meta *response.Meta `json:"-"`
Limit int `json:"limit"`
Offset int `json:"offset"`
TotalPages int `json:"total_pages"`
} }
// Company search and matching DTOs // CompanyResponse represents the company data sent in API responses
type CompanySearchForm struct { type CompanyResponse struct {
Query *string `query:"q" valid:"optional"` ID string `json:"id"`
CPVCodes []string `query:"cpv_codes" valid:"optional"` Name string `json:"name"`
Categories []string `query:"categories" valid:"optional"` Type string `json:"type"`
Keywords []string `query:"keywords" valid:"optional"` Status string `json:"status"`
Specializations []string `query:"specializations" valid:"optional"` RegistrationNumber string `json:"registration_number"`
Industry *string `query:"industry" valid:"optional"` TaxID string `json:"tax_id"`
Location *string `query:"location" valid:"optional"` Industry string `json:"industry"`
MinEmployees *int `query:"min_employees" valid:"optional,min(1)"` Description *string `json:"description,omitempty"`
MaxEmployees *int `query:"max_employees" valid:"optional,min(1)"` Website *string `json:"website,omitempty"`
MinRevenue *float64 `query:"min_revenue" valid:"optional,min(0)"` EmployeeCount *int `json:"employee_count,omitempty"`
MaxRevenue *float64 `query:"max_revenue" valid:"optional,min(0)"` AnnualRevenue *float64 `json:"annual_revenue,omitempty"`
IsVerified *bool `query:"is_verified" valid:"optional"` FoundedYear *int `json:"founded_year,omitempty"`
IsCompliant *bool `query:"is_compliant" valid:"optional"` Address *Address `json:"address,omitempty"`
Limit *int `query:"limit" valid:"optional,range(1|100)"` Phone *string `json:"phone,omitempty"`
Offset *int `query:"offset" valid:"optional,min(0)"` Email *string `json:"email,omitempty"`
Tags *CompanyTags `json:"tags,omitempty"`
IsVerified bool `json:"is_verified"`
IsCompliant bool `json:"is_compliant"`
ComplianceNotes *string `json:"compliance_notes,omitempty"`
Language string `json:"language"`
Currency string `json:"currency"`
Timezone string `json:"timezone"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
} }
// Company statistics DTOs // ToResponse converts Company to CompanyResponse
type CompanyStatsResponse struct { func (c *Company) ToResponse() *CompanyResponse {
TotalCompanies int64 `json:"total_companies"` return &CompanyResponse{
ActiveCompanies int64 `json:"active_companies"` ID: c.ID.Hex(),
VerifiedCompanies int64 `json:"verified_companies"` Name: c.Name,
CompaniesByType map[string]int64 `json:"companies_by_type"` Type: string(c.Type),
CompaniesByIndustry map[string]int64 `json:"companies_by_industry"` Status: string(c.Status),
CompaniesByStatus map[string]int64 `json:"companies_by_status"` RegistrationNumber: c.RegistrationNumber,
TopCategories []CategoryCount `json:"top_categories"` TaxID: c.TaxID,
TopCPVCodes []CPVCodeCount `json:"top_cpv_codes"` Industry: c.Industry,
Description: c.Description,
Website: c.Website,
EmployeeCount: c.EmployeeCount,
AnnualRevenue: c.AnnualRevenue,
FoundedYear: c.FoundedYear,
Address: c.Address,
Phone: c.Phone,
Email: c.Email,
Tags: c.Tags,
IsVerified: c.IsVerified,
IsCompliant: c.IsCompliant,
ComplianceNotes: c.ComplianceNotes,
Language: c.Language,
Currency: c.Currency,
Timezone: c.Timezone,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
}
} }
type CategoryCount struct { // CompanyProfileResponse represents the company profile data sent in API responses
Category string `json:"category"` type CompanyProfileResponse struct {
Count int64 `json:"count"` ID string `json:"id"`
Name string `json:"name"`
RegistrationNumber string `json:"registration_number"`
Industry string `json:"industry"`
FoundedYear *int `json:"founded_year,omitempty"`
CreatedAt int64 `json:"created_at"`
} }
type CPVCodeCount struct { // ToResponse converts Company to CompanyResponse
CPVCode string `json:"cpv_code"` func (c *Company) ToProfileResponse() *CompanyProfileResponse {
Count int64 `json:"count"` return &CompanyProfileResponse{
ID: c.ID.Hex(),
Name: c.Name,
RegistrationNumber: c.RegistrationNumber,
Industry: c.Industry,
FoundedYear: c.FoundedYear,
CreatedAt: c.CreatedAt,
}
} }
+37 -532
View File
@@ -1,7 +1,6 @@
package company package company
import ( import (
"strconv"
"tm/internal/user" "tm/internal/user"
"tm/pkg/logger" "tm/pkg/logger"
"tm/pkg/response" "tm/pkg/response"
@@ -44,23 +43,23 @@ func (h *Handler) MyCompany(c echo.Context) error {
return response.Unauthorized(c, "User not authenticated") return response.Unauthorized(c, "User not authenticated")
} }
company, err := h.service.GetCompanyByID(c.Request().Context(), companyID) company, err := h.service.GetMyCompany(c.Request().Context(), companyID)
if err != nil { if err != nil {
return response.InternalServerError(c, "Failed to get company") return response.InternalServerError(c, "Failed to get company")
} }
return response.Success(c, company.ToProfileResponse(), "Company retrieved successfully") return response.Success(c, company, "Company retrieved successfully")
} }
// **************************** ADMIN ENDPOINTS **************************** // **************************** ADMIN ENDPOINTS ****************************
// CreateCompany creates a new company (Web Panel) // Create creates a new company (Web Panel)
// @Summary Create a new company // @Summary Create a new company
// @Description Create a new company with comprehensive information including business details, address, tags, and customer assignment. This endpoint is used by the web panel for full company registration. // @Description Create a new company with comprehensive information including business details, address, tags, and customer assignment. This endpoint is used by the web panel for full company registration.
// @Tags Admin-Companies // @Tags Admin-Companies
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param company body CreateCompanyForm true "Company information including type, business details, address, tags, and optional customer assignment" // @Param company body CompanyForm true "Company information including type, business details, address, tags, and optional customer assignment"
// @Success 201 {object} response.APIResponse{data=CompanyResponse} "Company created successfully" // @Success 201 {object} response.APIResponse{data=CompanyResponse} "Company created successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" // @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 409 {object} response.APIResponse "Conflict - Company with this name/registration/tax ID already exists" // @Failure 409 {object} response.APIResponse "Conflict - Company with this name/registration/tax ID already exists"
@@ -68,19 +67,13 @@ func (h *Handler) MyCompany(c echo.Context) error {
// @Failure 500 {object} response.APIResponse "Internal server error" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/companies [post] // @Router /admin/v1/companies [post]
func (h *Handler) CreateCompany(c echo.Context) error { func (h *Handler) Create(c echo.Context) error {
form, err := response.Parse[CreateCompanyForm](c) form, err := response.Parse[CompanyForm](c)
if err != nil { if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error()) return response.ValidationError(c, "Invalid request data", err.Error())
} }
// Get user ID from JWT token context company, err := h.service.Create(c.Request().Context(), form)
createdBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
company, err := h.service.CreateCompany(c.Request().Context(), form, &createdBy)
if err != nil { if err != nil {
if err.Error() == "company with this name already exists" || if err.Error() == "company with this name already exists" ||
err.Error() == "company with this registration number already exists" || err.Error() == "company with this registration number already exists" ||
@@ -90,7 +83,7 @@ func (h *Handler) CreateCompany(c echo.Context) error {
return response.InternalServerError(c, "Failed to create company") return response.InternalServerError(c, "Failed to create company")
} }
return response.Created(c, company.ToResponse(), "Company created successfully") return response.Created(c, company, "Company created successfully")
} }
// GetCompany retrieves a company by ID (Web Panel) // GetCompany retrieves a company by ID (Web Panel)
@@ -106,10 +99,10 @@ func (h *Handler) CreateCompany(c echo.Context) error {
// @Failure 500 {object} response.APIResponse "Internal server error" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/companies/{id} [get] // @Router /admin/v1/companies/{id} [get]
func (h *Handler) GetCompany(c echo.Context) error { func (h *Handler) Get(c echo.Context) error {
id := c.Param("id") id := c.Param("id")
company, err := h.service.GetCompanyByID(c.Request().Context(), id) company, err := h.service.GetByID(c.Request().Context(), id)
if err != nil { if err != nil {
if err.Error() == "company not found" { if err.Error() == "company not found" {
return response.NotFound(c, "Company not found") return response.NotFound(c, "Company not found")
@@ -117,17 +110,17 @@ func (h *Handler) GetCompany(c echo.Context) error {
return response.InternalServerError(c, "Failed to get company") return response.InternalServerError(c, "Failed to get company")
} }
return response.Success(c, company.ToResponse(), "Company retrieved successfully") return response.Success(c, company, "Company retrieved successfully")
} }
// UpdateCompany updates a company (Web Panel) // Update updates a company (Web Panel)
// @Summary Update company information // @Summary Update company information
// @Description Update company information including business details, address, tags, and customer assignment // @Description Update company information including business details, address, tags, and customer assignment
// @Tags Admin-Companies // @Tags Admin-Companies
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path string true "Company ID" // @Param id path string true "Company ID"
// @Param company body UpdateCompanyForm true "Company update information" // @Param company body CompanyForm true "Company update information"
// @Success 200 {object} response.APIResponse{data=CompanyResponse} "Company updated successfully" // @Success 200 {object} response.APIResponse{data=CompanyResponse} "Company updated successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" // @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Company not found" // @Failure 404 {object} response.APIResponse "Not found - Company not found"
@@ -136,20 +129,14 @@ func (h *Handler) GetCompany(c echo.Context) error {
// @Failure 500 {object} response.APIResponse "Internal server error" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/companies/{id} [put] // @Router /admin/v1/companies/{id} [put]
func (h *Handler) UpdateCompany(c echo.Context) error { func (h *Handler) Update(c echo.Context) error {
id := c.Param("id") id := c.Param("id")
form, err := response.Parse[UpdateCompanyForm](c) form, err := response.Parse[CompanyForm](c)
if err != nil { if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error()) return response.ValidationError(c, "Invalid request data", err.Error())
} }
// Get user ID from JWT token context company, err := h.service.Update(c.Request().Context(), id, form)
updatedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
company, err := h.service.UpdateCompany(c.Request().Context(), id, form, &updatedBy)
if err != nil { if err != nil {
if err.Error() == "company not found" { if err.Error() == "company not found" {
return response.NotFound(c, "Company not found") return response.NotFound(c, "Company not found")
@@ -162,10 +149,10 @@ func (h *Handler) UpdateCompany(c echo.Context) error {
return response.InternalServerError(c, "Failed to update company") return response.InternalServerError(c, "Failed to update company")
} }
return response.Success(c, company.ToResponse(), "Company updated successfully") return response.Success(c, company, "Company updated successfully")
} }
// DeleteCompany deletes a company (Web Panel) // Delete deletes a company (Web Panel)
// @Summary Delete company // @Summary Delete company
// @Description Soft delete a company by setting status to inactive // @Description Soft delete a company by setting status to inactive
// @Tags Admin-Companies // @Tags Admin-Companies
@@ -178,16 +165,10 @@ func (h *Handler) UpdateCompany(c echo.Context) error {
// @Failure 500 {object} response.APIResponse "Internal server error" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/companies/{id} [delete] // @Router /admin/v1/companies/{id} [delete]
func (h *Handler) DeleteCompany(c echo.Context) error { func (h *Handler) Delete(c echo.Context) error {
id := c.Param("id") id := c.Param("id")
// Get user ID from JWT token context err := h.service.Delete(c.Request().Context(), id)
deletedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.DeleteCompany(c.Request().Context(), id, &deletedBy)
if err != nil { if err != nil {
if err.Error() == "company not found" { if err.Error() == "company not found" {
return response.NotFound(c, "Company not found") return response.NotFound(c, "Company not found")
@@ -200,53 +181,7 @@ func (h *Handler) DeleteCompany(c echo.Context) error {
}, "Company deleted successfully") }, "Company deleted successfully")
} }
// ListCompanies lists companies with filters and pagination (Web Panel) // Search searches companies with advanced filters (Web Panel)
// @Summary List companies with filters and pagination
// @Description Retrieve a paginated list of companies with advanced filtering options including search, type, status, industry, verification status, tags, and business criteria. Supports sorting and pagination.
// @Tags Admin-Companies
// @Accept json
// @Produce json
// @Param search query string false "Search term to filter companies by name, description, or industry"
// @Param type query string false "Filter by company type" Enums(private, public, government, ngo, startup)
// @Param status query string false "Filter by company status" Enums(active, inactive, suspended, pending)
// @Param industry query string false "Filter by industry"
// @Param is_verified query boolean false "Filter by verification status"
// @Param is_compliant query boolean false "Filter by compliance status"
// @Param cpv_codes query array false "Filter by CPV codes"
// @Param categories query array false "Filter by categories"
// @Param keywords query array false "Filter by keywords"
// @Param specializations query array false "Filter by specializations"
// @Param employee_count_min query integer false "Minimum employee count"
// @Param employee_count_max query integer false "Maximum employee count"
// @Param annual_revenue_min query number false "Minimum annual revenue"
// @Param annual_revenue_max query number false "Maximum annual revenue"
// @Param founded_year_min query integer false "Minimum founded year"
// @Param founded_year_max query integer false "Maximum founded year"
// @Param limit query integer false "Number of companies per page (1-100)" minimum(1) maximum(100) default(20)
// @Param offset query integer false "Number of companies to skip for pagination" minimum(0) default(0)
// @Param sort_by query string false "Field to sort by" Enums(name, type, industry, created_at, updated_at, status, employee_count, annual_revenue) default(created_at)
// @Param sort_order query string false "Sort order" Enums(asc, desc) default(desc)
// @Success 200 {object} response.APIResponse{data=CompanyListResponse} "Companies retrieved successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid query parameters"
// @Failure 422 {object} response.APIResponse "Validation error - Invalid query parameters"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/companies [get]
func (h *Handler) ListCompanies(c echo.Context) error {
form, err := response.Parse[ListCompaniesForm](c)
if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error())
}
result, err := h.service.ListCompanies(c.Request().Context(), form)
if err != nil {
return response.InternalServerError(c, "Failed to list companies")
}
return response.Success(c, result, "Companies retrieved successfully")
}
// SearchCompanies searches companies with advanced filters (Web Panel)
// @Summary Advanced search for companies // @Summary Advanced search for companies
// @Description Search companies with advanced filtering capabilities including tags, business criteria, and location // @Description Search companies with advanced filtering capabilities including tags, business criteria, and location
// @Tags Admin-Companies // @Tags Admin-Companies
@@ -272,29 +207,29 @@ func (h *Handler) ListCompanies(c echo.Context) error {
// @Failure 422 {object} response.APIResponse "Validation error - Invalid query parameters" // @Failure 422 {object} response.APIResponse "Validation error - Invalid query parameters"
// @Failure 500 {object} response.APIResponse "Internal server error" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/companies/search [get] // @Router /admin/v1/companies [get]
func (h *Handler) SearchCompanies(c echo.Context) error { func (h *Handler) Search(c echo.Context) error {
form, err := response.Parse[CompanySearchForm](c) form, err := response.Parse[SearchForm](c)
if err != nil { if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error()) return response.ValidationError(c, "Invalid request data", err.Error())
} }
result, err := h.service.SearchCompanies(c.Request().Context(), form) result, err := h.service.SearchCompanies(c.Request().Context(), form, response.NewPagination(c))
if err != nil { if err != nil {
return response.InternalServerError(c, "Failed to search companies") return response.InternalServerError(c, "Failed to list companies")
} }
return response.Success(c, result, "Companies search completed") return response.SuccessWithMeta(c, result, result.Meta, "Companies retrieved successfully")
} }
// UpdateCompanyStatus updates company status (Web Panel) // UpdateStatus updates company status (Web Panel)
// @Summary Update company status // @Summary Update company status
// @Description Update company account status (active, inactive, suspended, pending) // @Description Update company account status (active, inactive, suspended, pending)
// @Tags Admin-Companies // @Tags Admin-Companies
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path string true "Company ID" // @Param id path string true "Company ID"
// @Param status body UpdateCompanyStatusForm true "New company status" // @Param status body StatusForm true "New company status"
// @Success 200 {object} response.APIResponse "Company status updated successfully" // @Success 200 {object} response.APIResponse "Company status updated successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" // @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Company not found" // @Failure 404 {object} response.APIResponse "Not found - Company not found"
@@ -302,20 +237,14 @@ func (h *Handler) SearchCompanies(c echo.Context) error {
// @Failure 500 {object} response.APIResponse "Internal server error" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/companies/{id}/status [patch] // @Router /admin/v1/companies/{id}/status [patch]
func (h *Handler) UpdateCompanyStatus(c echo.Context) error { func (h *Handler) UpdateStatus(c echo.Context) error {
id := c.Param("id") id := c.Param("id")
form, err := response.Parse[UpdateCompanyStatusForm](c) form, err := response.Parse[StatusForm](c)
if err != nil { if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error()) return response.ValidationError(c, "Invalid request data", err.Error())
} }
// Get user ID from JWT token context err = h.service.UpdateStatus(c.Request().Context(), id, form)
updatedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.UpdateCompanyStatus(c.Request().Context(), id, form, &updatedBy)
if err != nil { if err != nil {
if err.Error() == "company not found" { if err.Error() == "company not found" {
return response.NotFound(c, "Company not found") return response.NotFound(c, "Company not found")
@@ -328,14 +257,14 @@ func (h *Handler) UpdateCompanyStatus(c echo.Context) error {
}, "Company status updated successfully") }, "Company status updated successfully")
} }
// UpdateCompanyVerification updates company verification status (Web Panel) // UpdateVerification updates company verification status (Web Panel)
// @Summary Update company verification status // @Summary Update company verification status
// @Description Update company verification and compliance status // @Description Update company verification and compliance status
// @Tags Admin-Companies // @Tags Admin-Companies
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path string true "Company ID" // @Param id path string true "Company ID"
// @Param verification body UpdateCompanyVerificationForm true "Verification status update" // @Param verification body VerificationForm true "Verification status update"
// @Success 200 {object} response.APIResponse "Company verification updated successfully" // @Success 200 {object} response.APIResponse "Company verification updated successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" // @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Company not found" // @Failure 404 {object} response.APIResponse "Not found - Company not found"
@@ -343,20 +272,14 @@ func (h *Handler) UpdateCompanyStatus(c echo.Context) error {
// @Failure 500 {object} response.APIResponse "Internal server error" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/companies/{id}/verification [patch] // @Router /admin/v1/companies/{id}/verification [patch]
func (h *Handler) UpdateCompanyVerification(c echo.Context) error { func (h *Handler) UpdateVerification(c echo.Context) error {
id := c.Param("id") id := c.Param("id")
form, err := response.Parse[UpdateCompanyVerificationForm](c) form, err := response.Parse[VerificationForm](c)
if err != nil { if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error()) return response.ValidationError(c, "Invalid request data", err.Error())
} }
// Get user ID from JWT token context err = h.service.UpdateVerification(c.Request().Context(), id, form)
updatedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.UpdateCompanyVerification(c.Request().Context(), id, form, &updatedBy)
if err != nil { if err != nil {
if err.Error() == "company not found" { if err.Error() == "company not found" {
return response.NotFound(c, "Company not found") return response.NotFound(c, "Company not found")
@@ -368,421 +291,3 @@ func (h *Handler) UpdateCompanyVerification(c echo.Context) error {
"message": "Company verification updated successfully", "message": "Company verification updated successfully",
}, "Company verification updated successfully") }, "Company verification updated successfully")
} }
// UpdateCompanyTags updates company tags (Web Panel)
// @Summary Update company tags
// @Description Update company tags (CPV, categories, keywords, specializations, certifications)
// @Tags Admin-Companies
// @Accept json
// @Produce json
// @Param id path string true "Company ID"
// @Param tags body UpdateCompanyTagsForm true "Tags update information"
// @Success 200 {object} response.APIResponse "Company tags updated successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Company not found"
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/companies/{id}/tags [put]
func (h *Handler) UpdateCompanyTags(c echo.Context) error {
id := c.Param("id")
form, err := response.Parse[UpdateCompanyTagsForm](c)
if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error())
}
// Get user ID from JWT token context
updatedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.UpdateCompanyTags(c.Request().Context(), id, form, &updatedBy)
if err != nil {
if err.Error() == "company not found" {
return response.NotFound(c, "Company not found")
}
return response.InternalServerError(c, "Failed to update company tags")
}
return response.Success(c, map[string]interface{}{
"message": "Company tags updated successfully",
}, "Company tags updated successfully")
}
// AddTags adds tags to a company (Web Panel)
// @Summary Add tags to company
// @Description Add specific tags to a company
// @Tags Admin-Companies
// @Accept json
// @Produce json
// @Param id path string true "Company ID"
// @Param tags body AddTagsForm true "Tags to add"
// @Success 200 {object} response.APIResponse "Tags added successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Company not found"
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/companies/{id}/tags/add [post]
func (h *Handler) AddTags(c echo.Context) error {
id := c.Param("id")
form, err := response.Parse[AddTagsForm](c)
if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error())
}
// Get user ID from JWT token context
updatedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.AddTags(c.Request().Context(), id, form, &updatedBy)
if err != nil {
if err.Error() == "company not found" {
return response.NotFound(c, "Company not found")
}
return response.InternalServerError(c, "Failed to add tags")
}
return response.Success(c, map[string]interface{}{
"message": "Tags added successfully",
}, "Tags added successfully")
}
// RemoveTags removes tags from a company (Web Panel)
// @Summary Remove tags from company
// @Description Remove specific tags from a company
// @Tags Admin-Companies
// @Accept json
// @Produce json
// @Param id path string true "Company ID"
// @Param tags body RemoveTagsForm true "Tags to remove"
// @Success 200 {object} response.APIResponse "Tags removed successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Company not found"
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/companies/{id}/tags/remove [post]
func (h *Handler) RemoveTags(c echo.Context) error {
id := c.Param("id")
form, err := response.Parse[RemoveTagsForm](c)
if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error())
}
// Get user ID from JWT token context
updatedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.RemoveTags(c.Request().Context(), id, form, &updatedBy)
if err != nil {
if err.Error() == "company not found" {
return response.NotFound(c, "Company not found")
}
return response.InternalServerError(c, "Failed to remove tags")
}
return response.Success(c, map[string]interface{}{
"message": "Tags removed successfully",
}, "Tags removed successfully")
}
// VerifyCompany verifies a company (Web Panel)
// @Summary Verify company
// @Description Mark a company as verified
// @Tags Admin-Companies
// @Accept json
// @Produce json
// @Param id path string true "Company ID"
// @Success 200 {object} response.APIResponse "Company verified successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid company ID"
// @Failure 404 {object} response.APIResponse "Not found - Company not found"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/companies/{id}/verify [post]
func (h *Handler) VerifyCompany(c echo.Context) error {
id := c.Param("id")
// Get user ID from JWT token context
verifiedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.VerifyCompany(c.Request().Context(), id, &verifiedBy)
if err != nil {
if err.Error() == "company not found" {
return response.NotFound(c, "Company not found")
}
return response.InternalServerError(c, "Failed to verify company")
}
return response.Success(c, map[string]interface{}{
"message": "Company verified successfully",
}, "Company verified successfully")
}
// SuspendCompany suspends a company (Web Panel)
// @Summary Suspend company
// @Description Suspend a company account with reason
// @Tags Admin-Companies
// @Accept json
// @Produce json
// @Param id path string true "Company ID"
// @Param suspension body SuspendCompanyForm true "Suspension information"
// @Success 200 {object} response.APIResponse "Company suspended successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Company not found"
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/companies/{id}/suspend [post]
func (h *Handler) SuspendCompany(c echo.Context) error {
id := c.Param("id")
form, err := response.Parse[SuspendCompanyForm](c)
if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error())
}
// Get user ID from JWT token context
suspendedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.SuspendCompany(c.Request().Context(), id, form.Reason, &suspendedBy)
if err != nil {
if err.Error() == "company not found" {
return response.NotFound(c, "Company not found")
}
return response.InternalServerError(c, "Failed to suspend company")
}
return response.Success(c, map[string]interface{}{
"message": "Company suspended successfully",
}, "Company suspended successfully")
}
// ActivateCompany activates a company (Web Panel)
// @Summary Activate company
// @Description Activate a suspended company account
// @Tags Admin-Companies
// @Accept json
// @Produce json
// @Param id path string true "Company ID"
// @Success 200 {object} response.APIResponse "Company activated successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid company ID"
// @Failure 404 {object} response.APIResponse "Not found - Company not found"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/companies/{id}/activate [post]
func (h *Handler) ActivateCompany(c echo.Context) error {
id := c.Param("id")
// Get user ID from JWT token context
activatedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.ActivateCompany(c.Request().Context(), id, &activatedBy)
if err != nil {
if err.Error() == "company not found" {
return response.NotFound(c, "Company not found")
}
return response.InternalServerError(c, "Failed to activate company")
}
return response.Success(c, map[string]interface{}{
"message": "Company activated successfully",
}, "Company activated successfully")
}
// GetCompanyStats returns company statistics (Web Panel)
// @Summary Get company statistics
// @Description Get comprehensive company statistics for dashboard
// @Tags Admin-Companies
// @Accept json
// @Produce json
// @Success 200 {object} response.APIResponse{data=CompanyStatsResponse} "Company statistics retrieved successfully"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/companies/stats [get]
func (h *Handler) GetCompanyStats(c echo.Context) error {
stats, err := h.service.GetCompanyStats(c.Request().Context())
if err != nil {
return response.InternalServerError(c, "Failed to get company statistics")
}
return response.Success(c, stats, "Company statistics retrieved successfully")
}
// GetCompaniesByType retrieves companies by type (Web Panel)
// @Summary Get companies by type
// @Description Retrieve companies filtered by their type (private, public, government, ngo, startup)
// @Tags Admin-Companies
// @Accept json
// @Produce json
// @Param type path string true "Company type" Enums(private, public, government, ngo, startup)
// @Param limit query integer false "Number of companies per page (1-100)" minimum(1) maximum(100) default(20)
// @Param offset query integer false "Number of companies to skip for pagination" minimum(0) default(0)
// @Success 200 {object} response.APIResponse{data=[]CompanyResponse,meta=response.Meta} "Companies retrieved successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid company type"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/companies/type/{type} [get]
func (h *Handler) GetCompaniesByType(c echo.Context) error {
companyTypeStr := c.Param("type")
companyType := CompanyType(companyTypeStr)
// Validate company type
if companyType != CompanyTypePrivate && companyType != CompanyTypePublic &&
companyType != CompanyTypeGovernment && companyType != CompanyTypeNgo &&
companyType != CompanyTypeStartup {
return response.BadRequest(c, "Invalid company type", "Must be private, public, government, ngo, or startup")
}
limit := 20
if limitStr := c.QueryParam("limit"); limitStr != "" {
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 {
limit = parsedLimit
}
}
offset := 0
if offsetStr := c.QueryParam("offset"); offsetStr != "" {
if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
offset = parsedOffset
}
}
companies, total, err := h.service.GetCompaniesByType(c.Request().Context(), companyType, limit, offset)
if err != nil {
return response.InternalServerError(c, "Failed to retrieve companies by type")
}
var companyResponses []*CompanyResponse
for _, company := range companies {
companyResponses = append(companyResponses, company.ToResponse())
}
meta := &response.Meta{
Total: int(total),
Limit: limit,
Offset: offset,
}
return response.SuccessWithMeta(c, companyResponses, meta, "Companies retrieved successfully")
}
// GetCompaniesByStatus retrieves companies by status (Web Panel)
// @Summary Get companies by status
// @Description Retrieve companies filtered by their status (active, inactive, suspended, pending)
// @Tags Admin-Companies
// @Accept json
// @Produce json
// @Param status path string true "Company status" Enums(active, inactive, suspended, pending)
// @Param limit query integer false "Number of companies per page (1-100)" minimum(1) maximum(100) default(20)
// @Param offset query integer false "Number of companies to skip for pagination" minimum(0) default(0)
// @Success 200 {object} response.APIResponse{data=[]CompanyResponse,meta=response.Meta} "Companies retrieved successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid company status"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/companies/status/{status} [get]
func (h *Handler) GetCompaniesByStatus(c echo.Context) error {
statusStr := c.Param("status")
status := CompanyStatus(statusStr)
// Validate company status
if status != CompanyStatusActive && status != CompanyStatusInactive &&
status != CompanyStatusSuspended && status != CompanyStatusPending {
return response.BadRequest(c, "Invalid company status", "Must be active, inactive, suspended, or pending")
}
limit := 20
if limitStr := c.QueryParam("limit"); limitStr != "" {
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 {
limit = parsedLimit
}
}
offset := 0
if offsetStr := c.QueryParam("offset"); offsetStr != "" {
if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
offset = parsedOffset
}
}
companies, total, err := h.service.GetCompaniesByStatus(c.Request().Context(), status, limit, offset)
if err != nil {
return response.InternalServerError(c, "Failed to retrieve companies by status")
}
var companyResponses []*CompanyResponse
for _, company := range companies {
companyResponses = append(companyResponses, company.ToResponse())
}
meta := &response.Meta{
Total: int(total),
Limit: limit,
Offset: offset,
}
return response.SuccessWithMeta(c, companyResponses, meta, "Companies retrieved successfully")
}
// GetCompaniesByIndustry retrieves companies by industry (Web Panel)
// @Summary Get companies by industry
// @Description Retrieve companies filtered by their industry
// @Tags Admin-Companies
// @Accept json
// @Produce json
// @Param industry path string true "Industry"
// @Param limit query integer false "Number of companies per page (1-100)" minimum(1) maximum(100) default(20)
// @Param offset query integer false "Number of companies to skip for pagination" minimum(0) default(0)
// @Success 200 {object} response.APIResponse{data=[]CompanyResponse,meta=response.Meta} "Companies retrieved successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid industry"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/companies/industry/{industry} [get]
func (h *Handler) GetCompaniesByIndustry(c echo.Context) error {
industry := c.Param("industry")
limit := 20
if limitStr := c.QueryParam("limit"); limitStr != "" {
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 {
limit = parsedLimit
}
}
offset := 0
if offsetStr := c.QueryParam("offset"); offsetStr != "" {
if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
offset = parsedOffset
}
}
companies, total, err := h.service.GetCompaniesByIndustry(c.Request().Context(), industry, limit, offset)
if err != nil {
return response.InternalServerError(c, "Failed to retrieve companies by industry")
}
var companyResponses []*CompanyResponse
for _, company := range companies {
companyResponses = append(companyResponses, company.ToResponse())
}
meta := &response.Meta{
Total: int(total),
Limit: limit,
Offset: offset,
}
return response.SuccessWithMeta(c, companyResponses, meta, "Companies retrieved successfully")
}
+89 -538
View File
@@ -5,70 +5,38 @@ import (
"errors" "errors"
"time" "time"
"tm/pkg/logger" "tm/pkg/logger"
"tm/pkg/mongo" orm "tm/pkg/mongo"
"tm/pkg/response"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
) )
// Repository defines the interface for company data operations // Repository defines the interface for company data operations
type Repository interface { type Repository interface {
Create(ctx context.Context, company *Company) error Create(ctx context.Context, company *Company) error
Update(ctx context.Context, company *Company) error
GetByID(ctx context.Context, id string) (*Company, error) GetByID(ctx context.Context, id string) (*Company, error)
GetByIDs(ctx context.Context, ids []string) ([]Company, error)
GetByName(ctx context.Context, name string) (*Company, error) GetByName(ctx context.Context, name string) (*Company, error)
GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Company, error) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Company, error)
GetByTaxID(ctx context.Context, taxID string) (*Company, error) GetByTaxID(ctx context.Context, taxID string) (*Company, error)
GetByIDs(ctx context.Context, ids []string) ([]*Company, error) Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) ([]Company, int64, error)
Update(ctx context.Context, company *Company) error
Delete(ctx context.Context, id string) 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 UpdateStatus(ctx context.Context, id string, status CompanyStatus) error
UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) 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 // companyRepository implements the Repository interface using the MongoDB ORM
type companyRepository struct { type companyRepository struct {
ormRepo mongo.Repository[Company] ormRepo orm.Repository[Company]
logger logger.Logger logger logger.Logger
} }
// NewCompanyRepository creates a new company repository // NewCompanyRepository creates a new company repository
func NewCompanyRepository(mongoManager *mongo.ConnectionManager, logger logger.Logger) Repository { func NewCompanyRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
// Create indexes using the ORM's index management // Create indexes using the ORM's index management
indexes := []mongo.Index{ indexes := []orm.Index{
*mongo.CreateUniqueIndex("name_idx", bson.D{{Key: "name", Value: 1}}), *orm.NewIndex("registration_number_idx", bson.D{{Key: "registration_number", 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 // Create indexes
@@ -80,7 +48,7 @@ func NewCompanyRepository(mongoManager *mongo.ConnectionManager, logger logger.L
} }
// Create ORM repository // Create ORM repository
ormRepo := mongo.NewRepository[Company](mongoManager.GetCollection("companies"), logger) ormRepo := orm.NewRepository[Company](mongoManager.GetCollection("companies"), logger)
return &companyRepository{ return &companyRepository{
ormRepo: ormRepo, ormRepo: ormRepo,
@@ -93,7 +61,6 @@ func (r *companyRepository) Create(ctx context.Context, company *Company) error
// Set created/updated timestamps using Unix timestamps // Set created/updated timestamps using Unix timestamps
now := time.Now().Unix() now := time.Now().Unix()
company.SetCreatedAt(now) company.SetCreatedAt(now)
company.SetUpdatedAt(now)
// Use ORM to create company // Use ORM to create company
err := r.ormRepo.Create(ctx, company) err := r.ormRepo.Create(ctx, company)
@@ -119,7 +86,7 @@ func (r *companyRepository) Create(ctx context.Context, company *Company) error
func (r *companyRepository) GetByID(ctx context.Context, id string) (*Company, error) { func (r *companyRepository) GetByID(ctx context.Context, id string) (*Company, error) {
company, err := r.ormRepo.FindByID(ctx, id) company, err := r.ormRepo.FindByID(ctx, id)
if err != nil { if err != nil {
if errors.Is(err, mongo.ErrDocumentNotFound) { if errors.Is(err, orm.ErrDocumentNotFound) {
return nil, errors.New("company not found") return nil, errors.New("company not found")
} }
r.logger.Error("Failed to get company by ID", map[string]interface{}{ r.logger.Error("Failed to get company by ID", map[string]interface{}{
@@ -132,108 +99,43 @@ func (r *companyRepository) GetByID(ctx context.Context, id string) (*Company, e
return company, nil return company, nil
} }
// GetByName retrieves a company by name // GetByIDs retrieves companies by IDs
func (r *companyRepository) GetByName(ctx context.Context, name string) (*Company, error) { func (r *companyRepository) GetByIDs(ctx context.Context, ids []string) ([]Company, error) {
filter := bson.M{"name": name} companies, err := r.ormRepo.FindAll(ctx, bson.M{"_id": bson.M{"$in": ids}}, orm.NewPaginationBuilder().Build())
company, err := r.ormRepo.FindOne(ctx, filter)
if err != nil { 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 nil, err
} }
return companies.Items, nil
}
// GetByName retrieves a company by name
func (r *companyRepository) GetByName(ctx context.Context, name string) (*Company, error) {
company, err := r.ormRepo.FindOne(ctx, bson.M{"name": name})
if err != nil {
return nil, err
}
return company, nil return company, nil
} }
// GetByRegistrationNumber retrieves a company by registration number // GetByRegistrationNumber retrieves a company by registration number
func (r *companyRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Company, error) { func (r *companyRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Company, error) {
filter := bson.M{"registration_number": registrationNumber} company, err := r.ormRepo.FindOne(ctx, bson.M{"registration_number": registrationNumber})
company, err := r.ormRepo.FindOne(ctx, filter)
if err != nil { 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 nil, err
} }
return company, nil return company, nil
} }
// GetByTaxID retrieves a company by tax ID // GetByTaxID retrieves a company by tax ID
func (r *companyRepository) GetByTaxID(ctx context.Context, taxID string) (*Company, error) { func (r *companyRepository) GetByTaxID(ctx context.Context, taxID string) (*Company, error) {
filter := bson.M{"tax_id": taxID} company, err := r.ormRepo.FindOne(ctx, bson.M{"tax_id": taxID})
company, err := r.ormRepo.FindOne(ctx, filter)
if err != nil { 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 nil, err
} }
return company, nil 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 // Update updates a company
func (r *companyRepository) Update(ctx context.Context, company *Company) error { func (r *companyRepository) Update(ctx context.Context, company *Company) error {
// Set updated timestamp using Unix timestamp // Set updated timestamp using Unix timestamp
@@ -257,20 +159,16 @@ func (r *companyRepository) Update(ctx context.Context, company *Company) error
return nil return nil
} }
// Delete deletes a company (soft delete by setting status to inactive) // Delete deletes a company
func (r *companyRepository) Delete(ctx context.Context, id string) error { func (r *companyRepository) Delete(ctx context.Context, id string) error {
// Get company first // Get company first
company, err := r.GetByID(ctx, id) _, err := r.GetByID(ctx, id)
if err != nil { if err != nil {
return err return err
} }
// Update status to inactive
company.Status = CompanyStatusInactive
company.SetUpdatedAt(time.Now().Unix())
// Update in database // Update in database
err = r.ormRepo.Update(ctx, company) err = r.ormRepo.Delete(ctx, id)
if err != nil { if err != nil {
r.logger.Error("Failed to delete company", map[string]interface{}{ r.logger.Error("Failed to delete company", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
@@ -286,246 +184,122 @@ func (r *companyRepository) Delete(ctx context.Context, id string) error {
return nil 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 // 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) { func (r *companyRepository) Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) ([]Company, int64, error) {
// Build filter // Build filter
filter := bson.M{} filter := bson.M{}
if search != "" { if form.Search != nil {
filter["$text"] = bson.M{"$search": search} filter["$text"] = bson.M{"$search": *form.Search}
} }
if companyType != nil { if form.Type != nil {
filter["type"] = *companyType filter["type"] = *form.Type
} }
if status != nil { if form.Status != nil {
filter["status"] = *status filter["status"] = *form.Status
} }
if industry != nil { if form.Industry != nil {
filter["industry"] = *industry filter["industry"] = *form.Industry
} }
if isVerified != nil { if form.IsVerified != nil {
filter["is_verified"] = *isVerified filter["is_verified"] = *form.IsVerified
} }
if isCompliant != nil { if form.IsCompliant != nil {
filter["is_compliant"] = *isCompliant filter["is_compliant"] = *form.IsCompliant
} }
if language != nil { if form.Language != nil {
filter["language"] = *language filter["language"] = *form.Language
} }
if currency != nil { if form.Currency != nil {
filter["currency"] = *currency filter["currency"] = *form.Currency
} }
// Tag filters // Tag filters
if len(cpvCodes) > 0 { if len(form.CPVCodes) > 0 {
filter["tags.cpv_codes"] = bson.M{"$in": cpvCodes} filter["tags.cpv_codes"] = bson.M{"$in": form.CPVCodes}
} }
if len(categories) > 0 { if len(form.Categories) > 0 {
filter["tags.categories"] = bson.M{"$in": categories} filter["tags.categories"] = bson.M{"$in": form.Categories}
} }
if len(keywords) > 0 { if len(form.Keywords) > 0 {
filter["tags.keywords"] = bson.M{"$in": keywords} filter["tags.keywords"] = bson.M{"$in": form.Keywords}
} }
if len(specializations) > 0 { if len(form.Specializations) > 0 {
filter["tags.specializations"] = bson.M{"$in": specializations} filter["tags.specializations"] = bson.M{"$in": form.Specializations}
} }
// Range filters // Range filters
if employeeCountMin != nil || employeeCountMax != nil { if form.EmployeeCountMin != nil || form.EmployeeCountMax != nil {
rangeFilter := bson.M{} rangeFilter := bson.M{}
if employeeCountMin != nil { if form.EmployeeCountMin != nil {
rangeFilter["$gte"] = *employeeCountMin rangeFilter["$gte"] = *form.EmployeeCountMin
} }
if employeeCountMax != nil { if form.EmployeeCountMax != nil {
rangeFilter["$lte"] = *employeeCountMax rangeFilter["$lte"] = *form.EmployeeCountMax
} }
filter["employee_count"] = rangeFilter filter["employee_count"] = rangeFilter
} }
if annualRevenueMin != nil || annualRevenueMax != nil { if form.AnnualRevenueMin != nil || form.AnnualRevenueMax != nil {
rangeFilter := bson.M{} rangeFilter := bson.M{}
if annualRevenueMin != nil { if form.AnnualRevenueMin != nil {
rangeFilter["$gte"] = *annualRevenueMin rangeFilter["$gte"] = *form.AnnualRevenueMin
} }
if annualRevenueMax != nil { if form.AnnualRevenueMax != nil {
rangeFilter["$lte"] = *annualRevenueMax rangeFilter["$lte"] = *form.AnnualRevenueMax
} }
filter["annual_revenue"] = rangeFilter filter["annual_revenue"] = rangeFilter
} }
if foundedYearMin != nil || foundedYearMax != nil { if form.FoundedYearMin != nil || form.FoundedYearMax != nil {
rangeFilter := bson.M{} rangeFilter := bson.M{}
if foundedYearMin != nil { if form.FoundedYearMin != nil {
rangeFilter["$gte"] = *foundedYearMin rangeFilter["$gte"] = *form.FoundedYearMin
} }
if foundedYearMax != nil { if form.FoundedYearMax != nil {
rangeFilter["$lte"] = *foundedYearMax rangeFilter["$lte"] = *form.FoundedYearMax
} }
filter["founded_year"] = rangeFilter filter["founded_year"] = rangeFilter
} }
// Build sort
sort := "created_at"
if pagination.SortBy != "" {
sort = pagination.SortBy
}
sortOrder := "desc"
if pagination.SortOrder != "" {
sortOrder = pagination.SortOrder
}
// Build pagination // Build pagination
pagination := mongo.NewPaginationBuilder(). paginationBuilder := orm.NewPaginationBuilder().
Limit(limit). Limit(pagination.Limit).
Skip(offset) Skip(pagination.Offset).
SortBy(sort, sortOrder).
Build()
// Set sort // Use ORM to find users
if sortBy != "" { result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder)
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 { if err != nil {
r.logger.Error("Failed to search companies", map[string]interface{}{ r.logger.Error("Failed to list users", 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(), "error": err.Error(),
}) })
return 0, err return nil, 0, err
} }
return count, nil return result.Items, result.TotalCount, 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 // UpdateStatus updates company status
@@ -591,226 +365,3 @@ func (r *companyRepository) UpdateVerification(ctx context.Context, id string, i
return nil 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
}
+56 -453
View File
@@ -3,46 +3,29 @@ package company
import ( import (
"context" "context"
"errors" "errors"
"time"
"tm/pkg/logger" "tm/pkg/logger"
"tm/pkg/response"
) )
// Service defines business logic for company operations // Service defines business logic for company operations
type Service interface { type Service interface {
// Core company operations // Core company operations
CreateCompany(ctx context.Context, form *CreateCompanyForm, createdBy *string) (*Company, error) Create(ctx context.Context, form *CompanyForm) (*CompanyResponse, error)
GetCompanyByID(ctx context.Context, id string) (*Company, error) Update(ctx context.Context, id string, form *CompanyForm) (*CompanyResponse, error)
GetCompaniesByIDs(ctx context.Context, ids []string) ([]*Company, error) GetByID(ctx context.Context, id string) (*CompanyResponse, error)
UpdateCompany(ctx context.Context, id string, form *UpdateCompanyForm, updatedBy *string) (*Company, error) GetByIDs(ctx context.Context, ids []string) ([]*CompanyResponse, error)
DeleteCompany(ctx context.Context, id string, deletedBy *string) error Delete(ctx context.Context, id string) error
// Company listing and search // Company listing and search
ListCompanies(ctx context.Context, form *ListCompaniesForm) (*CompanyListResponse, error) SearchCompanies(ctx context.Context, form *SearchForm, pagination *response.Pagination) (*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 // Company management
UpdateCompanyStatus(ctx context.Context, id string, form *UpdateCompanyStatusForm, updatedBy *string) error UpdateStatus(ctx context.Context, id string, form *StatusForm) error
UpdateCompanyVerification(ctx context.Context, id string, form *UpdateCompanyVerificationForm, updatedBy *string) error UpdateVerification(ctx context.Context, id string, form *VerificationForm) error
// Tag management // Get my company
UpdateCompanyTags(ctx context.Context, id string, form *UpdateCompanyTagsForm, updatedBy *string) error GetMyCompany(ctx context.Context, id string) (*CompanyProfileResponse, 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 // companyService implements the Service interface
@@ -59,8 +42,8 @@ func NewCompanyService(repository Repository, logger logger.Logger) Service {
} }
} }
// CreateCompany creates a new company // Create creates a new company
func (s *companyService) CreateCompany(ctx context.Context, form *CreateCompanyForm, createdBy *string) (*Company, error) { func (s *companyService) Create(ctx context.Context, form *CompanyForm) (*CompanyResponse, error) {
// Check if company name already exists // Check if company name already exists
existingCompany, _ := s.repository.GetByName(ctx, form.Name) existingCompany, _ := s.repository.GetByName(ctx, form.Name)
if existingCompany != nil { if existingCompany != nil {
@@ -83,7 +66,7 @@ func (s *companyService) CreateCompany(ctx context.Context, form *CreateCompanyF
company := &Company{ company := &Company{
Name: form.Name, Name: form.Name,
Type: CompanyType(form.Type), Type: CompanyType(form.Type),
Status: CompanyStatusPending, Status: CompanyStatusActive,
RegistrationNumber: form.RegistrationNumber, RegistrationNumber: form.RegistrationNumber,
TaxID: form.TaxID, TaxID: form.TaxID,
Industry: form.Industry, Industry: form.Industry,
@@ -93,7 +76,6 @@ func (s *companyService) CreateCompany(ctx context.Context, form *CreateCompanyF
AnnualRevenue: form.AnnualRevenue, AnnualRevenue: form.AnnualRevenue,
FoundedYear: form.FoundedYear, FoundedYear: form.FoundedYear,
Address: s.convertAddressForm(form.Address), Address: s.convertAddressForm(form.Address),
ContactPerson: s.convertContactPersonForm(form.ContactPerson),
Phone: form.Phone, Phone: form.Phone,
Email: form.Email, Email: form.Email,
Tags: s.convertCompanyTagsForm(form.Tags), Tags: s.convertCompanyTagsForm(form.Tags),
@@ -102,7 +84,6 @@ func (s *companyService) CreateCompany(ctx context.Context, form *CreateCompanyF
Language: s.getDefaultLanguage(form.Language), Language: s.getDefaultLanguage(form.Language),
Currency: s.getDefaultCurrency(form.Currency), Currency: s.getDefaultCurrency(form.Currency),
Timezone: s.getDefaultTimezone(form.Timezone), Timezone: s.getDefaultTimezone(form.Timezone),
CreatedBy: createdBy,
} }
// Save to database // Save to database
@@ -120,14 +101,13 @@ func (s *companyService) CreateCompany(ctx context.Context, form *CreateCompanyF
"company_id": company.ID, "company_id": company.ID,
"name": company.Name, "name": company.Name,
"type": company.Type, "type": company.Type,
"created_by": createdBy,
}) })
return company, nil return company.ToResponse(), nil
} }
// GetCompanyByID retrieves a company by ID // GetCompanyByID retrieves a company by ID
func (s *companyService) GetCompanyByID(ctx context.Context, id string) (*Company, error) { func (s *companyService) GetByID(ctx context.Context, id string) (*CompanyResponse, error) {
company, err := s.repository.GetByID(ctx, id) company, err := s.repository.GetByID(ctx, id)
if err != nil { if err != nil {
s.logger.Error("Failed to get company by ID", map[string]interface{}{ s.logger.Error("Failed to get company by ID", map[string]interface{}{
@@ -142,34 +122,11 @@ func (s *companyService) GetCompanyByID(ctx context.Context, id string) (*Compan
"name": company.Name, "name": company.Name,
}) })
return company, nil return company.ToResponse(), 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 // UpdateCompany updates a company
func (s *companyService) UpdateCompany(ctx context.Context, id string, form *UpdateCompanyForm, updatedBy *string) (*Company, error) { func (s *companyService) Update(ctx context.Context, id string, form *CompanyForm) (*CompanyResponse, error) {
// Get existing company // Get existing company
company, err := s.repository.GetByID(ctx, id) company, err := s.repository.GetByID(ctx, id)
if err != nil { if err != nil {
@@ -181,39 +138,39 @@ func (s *companyService) UpdateCompany(ctx context.Context, id string, form *Upd
} }
// Check if name already exists (if changing name) // Check if name already exists (if changing name)
if form.Name != nil && *form.Name != company.Name { if form.Name != "" && form.Name != company.Name {
existingCompany, _ := s.repository.GetByName(ctx, *form.Name) existingCompany, _ := s.repository.GetByName(ctx, form.Name)
if existingCompany != nil { if existingCompany != nil {
return nil, errors.New("company with this name already exists") return nil, errors.New("company with this name already exists")
} }
company.Name = *form.Name company.Name = form.Name
} }
// Check if registration number already exists (if changing) // Check if registration number already exists (if changing)
if form.RegistrationNumber != nil && *form.RegistrationNumber != company.RegistrationNumber { if form.RegistrationNumber != "" && form.RegistrationNumber != company.RegistrationNumber {
existingCompany, _ := s.repository.GetByRegistrationNumber(ctx, *form.RegistrationNumber) existingCompany, _ := s.repository.GetByRegistrationNumber(ctx, form.RegistrationNumber)
if existingCompany != nil { if existingCompany != nil {
return nil, errors.New("company with this registration number already exists") return nil, errors.New("company with this registration number already exists")
} }
company.RegistrationNumber = *form.RegistrationNumber company.RegistrationNumber = form.RegistrationNumber
} }
// Check if tax ID already exists (if changing) // Check if tax ID already exists (if changing)
if form.TaxID != nil && *form.TaxID != company.TaxID { if form.TaxID != "" && form.TaxID != company.TaxID {
existingCompany, _ := s.repository.GetByTaxID(ctx, *form.TaxID) existingCompany, _ := s.repository.GetByTaxID(ctx, form.TaxID)
if existingCompany != nil { if existingCompany != nil {
return nil, errors.New("company with this tax ID already exists") return nil, errors.New("company with this tax ID already exists")
} }
company.TaxID = *form.TaxID company.TaxID = form.TaxID
} }
// Update fields if provided // Update fields if provided
if form.Type != nil { if form.Type != "" {
company.Type = CompanyType(*form.Type) company.Type = CompanyType(form.Type)
} }
if form.Industry != nil { if form.Industry != "" {
company.Industry = *form.Industry company.Industry = form.Industry
} }
if form.Description != nil { if form.Description != nil {
@@ -240,10 +197,6 @@ func (s *companyService) UpdateCompany(ctx context.Context, id string, form *Upd
company.Address = s.convertAddressForm(form.Address) company.Address = s.convertAddressForm(form.Address)
} }
if form.ContactPerson != nil {
company.ContactPerson = s.convertContactPersonForm(form.ContactPerson)
}
if form.Phone != nil { if form.Phone != nil {
company.Phone = form.Phone company.Phone = form.Phone
} }
@@ -268,10 +221,6 @@ func (s *companyService) UpdateCompany(ctx context.Context, id string, form *Upd
company.Timezone = *form.Timezone company.Timezone = *form.Timezone
} }
// Set updated by
company.UpdatedBy = updatedBy
company.UpdatedAt = time.Now().Unix()
// Update in database // Update in database
err = s.repository.Update(ctx, company) err = s.repository.Update(ctx, company)
if err != nil { if err != nil {
@@ -285,25 +234,15 @@ func (s *companyService) UpdateCompany(ctx context.Context, id string, form *Upd
s.logger.Info("Company updated successfully", map[string]interface{}{ s.logger.Info("Company updated successfully", map[string]interface{}{
"company_id": company.ID, "company_id": company.ID,
"name": company.Name, "name": company.Name,
"updated_by": updatedBy,
}) })
return company, nil return company.ToResponse(), nil
} }
// DeleteCompany deletes a company (soft delete) // DeleteCompany deletes a company (soft delete)
func (s *companyService) DeleteCompany(ctx context.Context, id string, deletedBy *string) error { func (s *companyService) Delete(ctx context.Context, id 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) // Delete company (soft delete)
err = s.repository.Delete(ctx, id) err := s.repository.Delete(ctx, id)
if err != nil { if err != nil {
s.logger.Error("Failed to delete company", map[string]interface{}{ s.logger.Error("Failed to delete company", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
@@ -314,92 +253,19 @@ func (s *companyService) DeleteCompany(ctx context.Context, id string, deletedBy
s.logger.Info("Company deleted successfully", map[string]interface{}{ s.logger.Info("Company deleted successfully", map[string]interface{}{
"company_id": id, "company_id": id,
"deleted_by": deletedBy,
}) })
return nil 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 // SearchCompanies searches companies with advanced filters
func (s *companyService) SearchCompanies(ctx context.Context, form *CompanySearchForm) (*CompanyListResponse, error) { func (s *companyService) SearchCompanies(ctx context.Context, form *SearchForm, pagination *response.Pagination) (*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 // 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") companies, total, err := s.repository.Search(ctx, form, pagination)
if err != nil { if err != nil {
s.logger.Error("Failed to search companies", map[string]interface{}{ s.logger.Error("Failed to search companies", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"query": query,
}) })
return nil, errors.New("failed to search companies") return nil, errors.New("failed to search companies")
} }
@@ -410,34 +276,29 @@ func (s *companyService) SearchCompanies(ctx context.Context, form *CompanySearc
companyResponses = append(companyResponses, company.ToResponse()) 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{ return &CompanyListResponse{
Companies: companyResponses, Companies: companyResponses,
Total: total, Meta: pagination.Response(total),
Limit: limit,
Offset: offset,
TotalPages: int(totalPages),
}, nil }, nil
} }
// SearchCompaniesByTags searches companies by tags // GetByIDs retrieves companies by IDs
func (s *companyService) SearchCompaniesByTags(ctx context.Context, cpvCodes []string, categories []string, keywords []string, specializations []string, limit, offset int) ([]*Company, error) { func (s *companyService) GetByIDs(ctx context.Context, ids []string) ([]*CompanyResponse, error) {
companies, err := s.repository.SearchByTags(ctx, cpvCodes, categories, keywords, specializations, limit, offset) companies, err := s.repository.GetByIDs(ctx, ids)
if err != nil { if err != nil {
s.logger.Error("Failed to search companies by tags", map[string]interface{}{ return nil, errors.New("failed to get companies by IDs")
"error": err.Error(),
})
return nil, errors.New("failed to search companies by tags")
} }
return companies, nil var companyResponses []*CompanyResponse
for _, company := range companies {
companyResponses = append(companyResponses, company.ToResponse())
}
return companyResponses, nil
} }
// UpdateCompanyStatus updates company status // UpdateCompanyStatus updates company status
func (s *companyService) UpdateCompanyStatus(ctx context.Context, id string, form *UpdateCompanyStatusForm, updatedBy *string) error { func (s *companyService) UpdateStatus(ctx context.Context, id string, form *StatusForm) error {
// Get company to check if exists // Get company to check if exists
_, err := s.repository.GetByID(ctx, id) _, err := s.repository.GetByID(ctx, id)
if err != nil { if err != nil {
@@ -445,13 +306,12 @@ func (s *companyService) UpdateCompanyStatus(ctx context.Context, id string, for
} }
// Update status // Update status
status := CompanyStatus(form.Status) err = s.repository.UpdateStatus(ctx, id, CompanyStatus(form.Status))
err = s.repository.UpdateStatus(ctx, id, status)
if err != nil { if err != nil {
s.logger.Error("Failed to update company status", map[string]interface{}{ s.logger.Error("Failed to update company status", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"company_id": id, "company_id": id,
"status": form.Status, "status": CompanyStatus(form.Status),
}) })
return errors.New("failed to update company status") return errors.New("failed to update company status")
} }
@@ -459,14 +319,13 @@ func (s *companyService) UpdateCompanyStatus(ctx context.Context, id string, for
s.logger.Info("Company status updated successfully", map[string]interface{}{ s.logger.Info("Company status updated successfully", map[string]interface{}{
"company_id": id, "company_id": id,
"status": form.Status, "status": form.Status,
"updated_by": updatedBy,
}) })
return nil return nil
} }
// UpdateCompanyVerification updates company verification status // UpdateCompanyVerification updates company verification status
func (s *companyService) UpdateCompanyVerification(ctx context.Context, id string, form *UpdateCompanyVerificationForm, updatedBy *string) error { func (s *companyService) UpdateVerification(ctx context.Context, id string, form *VerificationForm) error {
// Get company to check if exists // Get company to check if exists
_, err := s.repository.GetByID(ctx, id) _, err := s.repository.GetByID(ctx, id)
if err != nil { if err != nil {
@@ -487,258 +346,19 @@ func (s *companyService) UpdateCompanyVerification(ctx context.Context, id strin
"company_id": id, "company_id": id,
"is_verified": form.IsVerified, "is_verified": form.IsVerified,
"is_compliant": form.IsCompliant, "is_compliant": form.IsCompliant,
"updated_by": updatedBy,
}) })
return nil return nil
} }
// UpdateCompanyTags updates company tags // GetMyCompany retrieves the company for the authenticated user
func (s *companyService) UpdateCompanyTags(ctx context.Context, id string, form *UpdateCompanyTagsForm, updatedBy *string) error { func (s *companyService) GetMyCompany(ctx context.Context, id string) (*CompanyProfileResponse, 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) company, err := s.repository.GetByID(ctx, id)
if err != nil { if err != nil {
return errors.New("company not found") return nil, errors.New("company not found")
} }
// Update verification return company.ToProfileResponse(), nil
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 // Helper methods for converting forms to domain objects
@@ -756,23 +376,6 @@ func (s *companyService) convertAddressForm(form *AddressForm) *Address {
} }
} }
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 { func (s *companyService) convertCompanyTagsForm(form *CompanyTagsForm) *CompanyTags {
if form == nil { if form == nil {
return nil return nil
+1 -1
View File
@@ -41,7 +41,7 @@ type SearchCustomersForm struct {
// CustomerListResponse represents the response for listing customers // CustomerListResponse represents the response for listing customers
type CustomerListResponse struct { type CustomerListResponse struct {
Customers []*CustomerResponse `json:"customers"` Customers []*CustomerResponse `json:"customers"`
Meta *response.Meta `json:"meta"` Meta *response.Meta `json:"-"`
} }
type LoginForm struct { type LoginForm struct {
+1 -1
View File
@@ -192,7 +192,7 @@ func (h *Handler) Search(c echo.Context) error {
return response.InternalServerError(c, "Failed to list customers") return response.InternalServerError(c, "Failed to list customers")
} }
return response.Success(c, result, "Customers retrieved successfully") return response.SuccessWithMeta(c, result, result.Meta, "Customers retrieved successfully")
} }
// UpdateCustomerStatus updates customer status (Web Panel) // UpdateCustomerStatus updates customer status (Web Panel)
+5 -5
View File
@@ -62,7 +62,7 @@ func (s *customerService) Register(ctx context.Context, form *CreateCustomerForm
var companyIDs []string var companyIDs []string
if len(form.CompanyIDs) > 0 { if len(form.CompanyIDs) > 0 {
for _, companyID := range form.CompanyIDs { for _, companyID := range form.CompanyIDs {
_, err := s.companyService.GetCompanyByID(ctx, companyID) _, err := s.companyService.GetByID(ctx, companyID)
if err != nil { if err != nil {
s.logger.Error("Invalid company ID provided", map[string]interface{}{ s.logger.Error("Invalid company ID provided", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
@@ -210,7 +210,7 @@ func (s *customerService) Update(ctx context.Context, id string, form *UpdateCus
// Verify that all company IDs exist // Verify that all company IDs exist
var validCompanyIDs []string var validCompanyIDs []string
for _, companyID := range form.CompanyIDs { for _, companyID := range form.CompanyIDs {
_, err := s.companyService.GetCompanyByID(ctx, companyID) _, err := s.companyService.GetByID(ctx, companyID)
if err != nil { if err != nil {
s.logger.Error("Invalid company ID provided in update", map[string]interface{}{ s.logger.Error("Invalid company ID provided in update", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
@@ -317,7 +317,7 @@ func (s *customerService) AssignCompanies(ctx context.Context, customerID string
// Verify companies exist and update customer's company IDs // Verify companies exist and update customer's company IDs
var validCompanyIDs []string var validCompanyIDs []string
for _, companyID := range companyIDs { for _, companyID := range companyIDs {
_, err = s.companyService.GetCompanyByID(ctx, companyID) _, err = s.companyService.GetByID(ctx, companyID)
if err != nil { if err != nil {
s.logger.Error("Company not found during assignment", map[string]interface{}{ s.logger.Error("Company not found during assignment", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
@@ -695,7 +695,7 @@ func (s *customerService) loadCompaniesForCustomer(ctx context.Context, customer
} }
// Load companies in batch using the new GetCompaniesByIDs method // Load companies in batch using the new GetCompaniesByIDs method
companies, err := s.companyService.GetCompaniesByIDs(ctx, customer.CompanyIDs) companies, err := s.companyService.GetByIDs(ctx, customer.CompanyIDs)
if err != nil { if err != nil {
s.logger.Error("Failed to load companies for customer", map[string]interface{}{ s.logger.Error("Failed to load companies for customer", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
@@ -709,7 +709,7 @@ func (s *customerService) loadCompaniesForCustomer(ctx context.Context, customer
var companySummaries []*CompanySummary var companySummaries []*CompanySummary
for _, company := range companies { for _, company := range companies {
companySummaries = append(companySummaries, &CompanySummary{ companySummaries = append(companySummaries, &CompanySummary{
ID: company.ID.Hex(), ID: company.ID,
Name: company.Name, Name: company.Name,
}) })
} }
+2 -2
View File
@@ -173,7 +173,7 @@ func (s *tenderService) ListTenders(ctx context.Context, req ListTendersRequest)
} }
// Get company CPV codes for match calculation // Get company CPV codes for match calculation
comp, err := s.companyService.GetCompanyByID(ctx, *req.CompanyID) comp, err := s.companyService.GetByID(ctx, *req.CompanyID)
if err != nil { if err != nil {
s.logger.Error("Failed to get company for match calculation", map[string]interface{}{ s.logger.Error("Failed to get company for match calculation", map[string]interface{}{
"company_id": *req.CompanyID, "company_id": *req.CompanyID,
@@ -234,7 +234,7 @@ func (s *tenderService) RecommendTenders(ctx context.Context, companyID *string,
limit = 100 limit = 100
} }
company, err := s.companyService.GetCompanyByID(ctx, *companyID) company, err := s.companyService.GetByID(ctx, *companyID)
if err != nil { if err != nil {
s.logger.Error("Failed to get company", map[string]interface{}{ s.logger.Error("Failed to get company", map[string]interface{}{
"company_id": *companyID, "company_id": *companyID,
+1 -1
View File
@@ -109,7 +109,7 @@ type AuthResponse struct {
type UserListResponse struct { type UserListResponse struct {
Users []*UserResponse `json:"users"` Users []*UserResponse `json:"users"`
Meta *response.Meta `json:"meta"` Meta *response.Meta `json:"-"`
} }
type SearchUsersForm struct { type SearchUsersForm struct {
+1 -1
View File
@@ -316,7 +316,7 @@ func (h *Handler) ListUsers(c echo.Context) error {
return response.InternalServerError(c, "Failed to list users") return response.InternalServerError(c, "Failed to list users")
} }
return response.Success(c, users, "Users retrieved successfully") return response.SuccessWithMeta(c, users, users.Meta, "Users retrieved successfully")
} }
// GetUserByID gets a user by ID (admin only) // GetUserByID gets a user by ID (admin only)
+3 -1
View File
@@ -12,8 +12,9 @@ import (
type APIResponse struct { type APIResponse struct {
Success bool `json:"success"` Success bool `json:"success"`
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
Data interface{} `json:",inline"` Data interface{} `json:"data,omitempty"`
Error *APIError `json:"error,omitempty"` Error *APIError `json:"error,omitempty"`
Meta *Meta `json:"meta,omitempty"`
} }
// APIError represents error details in API response // APIError represents error details in API response
@@ -54,6 +55,7 @@ func SuccessWithMeta(c echo.Context, data interface{}, meta *Meta, message strin
Success: true, Success: true,
Message: message, Message: message,
Data: data, Data: data,
Meta: meta,
}) })
} }