diff --git a/cmd/web/docs/docs.go b/cmd/web/docs/docs.go index e961712..edc6090 100644 --- a/cmd/web/docs/docs.go +++ b/cmd/web/docs/docs.go @@ -334,377 +334,6 @@ const docTemplate = `{ } }, "/admin/v1/companies": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "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.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "List companies with filters and pagination", - "parameters": [ - { - "type": "string", - "description": "Search term to filter companies by name, description, or industry", - "name": "search", - "in": "query" - }, - { - "enum": [ - "private", - "public", - "government", - "ngo", - "startup" - ], - "type": "string", - "description": "Filter by company type", - "name": "type", - "in": "query" - }, - { - "enum": [ - "active", - "inactive", - "suspended", - "pending" - ], - "type": "string", - "description": "Filter by company status", - "name": "status", - "in": "query" - }, - { - "type": "string", - "description": "Filter by industry", - "name": "industry", - "in": "query" - }, - { - "type": "boolean", - "description": "Filter by verification status", - "name": "is_verified", - "in": "query" - }, - { - "type": "boolean", - "description": "Filter by compliance status", - "name": "is_compliant", - "in": "query" - }, - { - "type": "array", - "description": "Filter by CPV codes", - "name": "cpv_codes", - "in": "query" - }, - { - "type": "array", - "description": "Filter by categories", - "name": "categories", - "in": "query" - }, - { - "type": "array", - "description": "Filter by keywords", - "name": "keywords", - "in": "query" - }, - { - "type": "array", - "description": "Filter by specializations", - "name": "specializations", - "in": "query" - }, - { - "type": "integer", - "description": "Minimum employee count", - "name": "employee_count_min", - "in": "query" - }, - { - "type": "integer", - "description": "Maximum employee count", - "name": "employee_count_max", - "in": "query" - }, - { - "type": "number", - "description": "Minimum annual revenue", - "name": "annual_revenue_min", - "in": "query" - }, - { - "type": "number", - "description": "Maximum annual revenue", - "name": "annual_revenue_max", - "in": "query" - }, - { - "type": "integer", - "description": "Minimum founded year", - "name": "founded_year_min", - "in": "query" - }, - { - "type": "integer", - "description": "Maximum founded year", - "name": "founded_year_max", - "in": "query" - }, - { - "maximum": 100, - "minimum": 1, - "type": "integer", - "default": 20, - "description": "Number of companies per page (1-100)", - "name": "limit", - "in": "query" - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Number of companies to skip for pagination", - "name": "offset", - "in": "query" - }, - { - "enum": [ - "name", - "type", - "industry", - "created_at", - "updated_at", - "status", - "employee_count", - "annual_revenue" - ], - "type": "string", - "default": "created_at", - "description": "Field to sort by", - "name": "sort_by", - "in": "query" - }, - { - "enum": [ - "asc", - "desc" - ], - "type": "string", - "default": "desc", - "description": "Sort order", - "name": "sort_order", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Companies retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/company.CompanyListResponse" - } - } - } - ] - } - }, - "400": { - "description": "Bad request - Invalid query parameters", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid query parameters", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - }, - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "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.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Create a new company", - "parameters": [ - { - "description": "Company information including type, business details, address, tags, and optional customer assignment", - "name": "company", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/company.CreateCompanyForm" - } - } - ], - "responses": { - "201": { - "description": "Company created successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/company.CompanyResponse" - } - } - } - ] - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "409": { - "description": "Conflict - Company with this name/registration/tax ID already exists", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/companies/industry/{industry}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve companies filtered by their industry", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Get companies by industry", - "parameters": [ - { - "type": "string", - "description": "Industry", - "name": "industry", - "in": "path", - "required": true - }, - { - "maximum": 100, - "minimum": 1, - "type": "integer", - "default": 20, - "description": "Number of companies per page (1-100)", - "name": "limit", - "in": "query" - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Number of companies to skip for pagination", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Companies retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/company.CompanyResponse" - } - }, - "meta": { - "$ref": "#/definitions/response.Meta" - } - } - } - ] - } - }, - "400": { - "description": "Bad request - Invalid industry", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/companies/search": { "get": { "security": [ { @@ -854,16 +483,14 @@ const docTemplate = `{ } } } - } - }, - "/admin/v1/companies/stats": { - "get": { + }, + "post": { "security": [ { "BearerAuth": [] } ], - "description": "Get comprehensive company statistics for dashboard", + "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.", "consumes": [ "application/json" ], @@ -873,88 +500,21 @@ const docTemplate = `{ "tags": [ "Admin-Companies" ], - "summary": "Get company statistics", - "responses": { - "200": { - "description": "Company statistics retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/company.CompanyStatsResponse" - } - } - } - ] - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/companies/status/{status}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve companies filtered by their status (active, inactive, suspended, pending)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Get companies by status", + "summary": "Create a new company", "parameters": [ { - "enum": [ - "active", - "inactive", - "suspended", - "pending" - ], - "type": "string", - "description": "Company status", - "name": "status", - "in": "path", - "required": true - }, - { - "maximum": 100, - "minimum": 1, - "type": "integer", - "default": 20, - "description": "Number of companies per page (1-100)", - "name": "limit", - "in": "query" - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Number of companies to skip for pagination", - "name": "offset", - "in": "query" + "description": "Company information including type, business details, address, tags, and optional customer assignment", + "name": "company", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/company.CompanyForm" + } } ], "responses": { - "200": { - "description": "Companies retrieved successfully", + "201": { + "description": "Company created successfully", "schema": { "allOf": [ { @@ -964,13 +524,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/definitions/company.CompanyResponse" - } - }, - "meta": { - "$ref": "#/definitions/response.Meta" + "$ref": "#/definitions/company.CompanyResponse" } } } @@ -978,98 +532,19 @@ const docTemplate = `{ } }, "400": { - "description": "Bad request - Invalid company status", + "description": "Bad request - Invalid input data", "schema": { "$ref": "#/definitions/response.APIResponse" } }, - "500": { - "description": "Internal server error", + "409": { + "description": "Conflict - Company with this name/registration/tax ID already exists", "schema": { "$ref": "#/definitions/response.APIResponse" } - } - } - } - }, - "/admin/v1/companies/type/{type}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve companies filtered by their type (private, public, government, ngo, startup)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Get companies by type", - "parameters": [ - { - "enum": [ - "private", - "public", - "government", - "ngo", - "startup" - ], - "type": "string", - "description": "Company type", - "name": "type", - "in": "path", - "required": true }, - { - "maximum": 100, - "minimum": 1, - "type": "integer", - "default": 20, - "description": "Number of companies per page (1-100)", - "name": "limit", - "in": "query" - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Number of companies to skip for pagination", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Companies retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/company.CompanyResponse" - } - }, - "meta": { - "$ref": "#/definitions/response.Meta" - } - } - } - ] - } - }, - "400": { - "description": "Bad request - Invalid company type", + "422": { + "description": "Validation error - Invalid request data", "schema": { "$ref": "#/definitions/response.APIResponse" } @@ -1180,7 +655,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/company.UpdateCompanyForm" + "$ref": "#/definitions/company.CompanyForm" } } ], @@ -1289,61 +764,6 @@ const docTemplate = `{ } } }, - "/admin/v1/companies/{id}/activate": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Activate a suspended company account", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Activate company", - "parameters": [ - { - "type": "string", - "description": "Company ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Company activated successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid company ID", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Company not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/admin/v1/companies/{id}/status": { "patch": { "security": [ @@ -1376,7 +796,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/company.UpdateCompanyStatusForm" + "$ref": "#/definitions/company.StatusForm" } } ], @@ -1414,286 +834,6 @@ const docTemplate = `{ } } }, - "/admin/v1/companies/{id}/suspend": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Suspend a company account with reason", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Suspend company", - "parameters": [ - { - "type": "string", - "description": "Company ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Suspension information", - "name": "suspension", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/company.SuspendCompanyForm" - } - } - ], - "responses": { - "200": { - "description": "Company suspended successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Company not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/companies/{id}/tags": { - "put": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update company tags (CPV, categories, keywords, specializations, certifications)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Update company tags", - "parameters": [ - { - "type": "string", - "description": "Company ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Tags update information", - "name": "tags", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/company.UpdateCompanyTagsForm" - } - } - ], - "responses": { - "200": { - "description": "Company tags updated successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Company not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/companies/{id}/tags/add": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Add specific tags to a company", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Add tags to company", - "parameters": [ - { - "type": "string", - "description": "Company ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Tags to add", - "name": "tags", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/company.AddTagsForm" - } - } - ], - "responses": { - "200": { - "description": "Tags added successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Company not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/companies/{id}/tags/remove": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Remove specific tags from a company", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Remove tags from company", - "parameters": [ - { - "type": "string", - "description": "Company ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Tags to remove", - "name": "tags", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/company.RemoveTagsForm" - } - } - ], - "responses": { - "200": { - "description": "Tags removed successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Company not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/admin/v1/companies/{id}/verification": { "patch": { "security": [ @@ -1726,7 +866,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/company.UpdateCompanyVerificationForm" + "$ref": "#/definitions/company.VerificationForm" } } ], @@ -1764,61 +904,6 @@ const docTemplate = `{ } } }, - "/admin/v1/companies/{id}/verify": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Mark a company as verified", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Verify company", - "parameters": [ - { - "type": "string", - "description": "Company ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Company verified successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid company ID", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Company not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/admin/v1/customers": { "get": { "security": [ @@ -6047,41 +5132,6 @@ const docTemplate = `{ } } }, - "company.AddTagsForm": { - "type": "object", - "properties": { - "categories": { - "type": "array", - "items": { - "type": "string" - } - }, - "certifications": { - "type": "array", - "items": { - "type": "string" - } - }, - "cpv_codes": { - "type": "array", - "items": { - "type": "string" - } - }, - "keywords": { - "type": "array", - "items": { - "type": "string" - } - }, - "specializations": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, "company.Address": { "type": "object", "properties": { @@ -6122,25 +5172,72 @@ const docTemplate = `{ } } }, - "company.CPVCodeCount": { + "company.CompanyForm": { "type": "object", "properties": { - "count": { - "type": "integer" + "address": { + "description": "Address information", + "allOf": [ + { + "$ref": "#/definitions/company.AddressForm" + } + ] }, - "cpv_code": { - "type": "string" - } - } - }, - "company.CategoryCount": { - "type": "object", - "properties": { - "category": { + "annual_revenue": { + "type": "number" + }, + "currency": { "type": "string" }, - "count": { + "description": { + "type": "string" + }, + "email": { + "type": "string" + }, + "employee_count": { + "description": "Business information", "type": "integer" + }, + "founded_year": { + "type": "integer" + }, + "industry": { + "type": "string" + }, + "language": { + "description": "Settings", + "type": "string" + }, + "name": { + "type": "string" + }, + "phone": { + "description": "Contact information", + "type": "string" + }, + "registration_number": { + "type": "string" + }, + "tags": { + "description": "Tags for categorization and search", + "allOf": [ + { + "$ref": "#/definitions/company.CompanyTagsForm" + } + ] + }, + "tax_id": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "type": { + "type": "string" + }, + "website": { + "type": "string" } } }, @@ -6153,17 +5250,8 @@ const docTemplate = `{ "$ref": "#/definitions/company.CompanyResponse" } }, - "limit": { - "type": "integer" - }, - "offset": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "total_pages": { - "type": "integer" + "meta": { + "$ref": "#/definitions/response.Meta" } } }, @@ -6202,15 +5290,9 @@ const docTemplate = `{ "compliance_notes": { "type": "string" }, - "contact_person": { - "$ref": "#/definitions/company.ContactPerson" - }, "created_at": { "type": "integer" }, - "created_by": { - "type": "string" - }, "currency": { "type": "string" }, @@ -6268,61 +5350,11 @@ const docTemplate = `{ "updated_at": { "type": "integer" }, - "updated_by": { - "type": "string" - }, "website": { "type": "string" } } }, - "company.CompanyStatsResponse": { - "type": "object", - "properties": { - "active_companies": { - "type": "integer" - }, - "companies_by_industry": { - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int64" - } - }, - "companies_by_status": { - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int64" - } - }, - "companies_by_type": { - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int64" - } - }, - "top_categories": { - "type": "array", - "items": { - "$ref": "#/definitions/company.CategoryCount" - } - }, - "top_cpv_codes": { - "type": "array", - "items": { - "$ref": "#/definitions/company.CPVCodeCount" - } - }, - "total_companies": { - "type": "integer" - }, - "verified_companies": { - "type": "integer" - } - } - }, "company.CompanyTags": { "type": "object", "properties": { @@ -6398,276 +5430,18 @@ const docTemplate = `{ } } }, - "company.ContactPerson": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "first_name": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "is_primary": { - "type": "boolean" - }, - "last_name": { - "type": "string" - }, - "mobile": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "position": { - "type": "string" - } - } - }, - "company.ContactPersonForm": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "first_name": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "is_primary": { - "type": "boolean" - }, - "last_name": { - "type": "string" - }, - "mobile": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "position": { - "type": "string" - } - } - }, - "company.CreateCompanyForm": { - "type": "object", - "properties": { - "address": { - "description": "Address information", - "allOf": [ - { - "$ref": "#/definitions/company.AddressForm" - } - ] - }, - "annual_revenue": { - "type": "number" - }, - "contact_person": { - "description": "Contact information", - "allOf": [ - { - "$ref": "#/definitions/company.ContactPersonForm" - } - ] - }, - "currency": { - "type": "string" - }, - "description": { - "type": "string" - }, - "email": { - "type": "string" - }, - "employee_count": { - "description": "Business information", - "type": "integer" - }, - "founded_year": { - "type": "integer" - }, - "industry": { - "type": "string" - }, - "language": { - "description": "Settings", - "type": "string" - }, - "name": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "registration_number": { - "type": "string" - }, - "tags": { - "description": "Tags for categorization and search", - "allOf": [ - { - "$ref": "#/definitions/company.CompanyTagsForm" - } - ] - }, - "tax_id": { - "type": "string" - }, - "timezone": { - "type": "string" - }, - "type": { - "type": "string" - }, - "website": { - "type": "string" - } - } - }, - "company.RemoveTagsForm": { - "type": "object", - "properties": { - "categories": { - "type": "array", - "items": { - "type": "string" - } - }, - "certifications": { - "type": "array", - "items": { - "type": "string" - } - }, - "cpv_codes": { - "type": "array", - "items": { - "type": "string" - } - }, - "keywords": { - "type": "array", - "items": { - "type": "string" - } - }, - "specializations": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "company.SuspendCompanyForm": { + "company.StatusForm": { "type": "object", "properties": { "reason": { "type": "string" - } - } - }, - "company.UpdateCompanyForm": { - "type": "object", - "properties": { - "address": { - "description": "Address information", - "allOf": [ - { - "$ref": "#/definitions/company.AddressForm" - } - ] }, - "annual_revenue": { - "type": "number" - }, - "contact_person": { - "description": "Contact information", - "allOf": [ - { - "$ref": "#/definitions/company.ContactPersonForm" - } - ] - }, - "currency": { - "type": "string" - }, - "description": { - "type": "string" - }, - "email": { - "type": "string" - }, - "employee_count": { - "description": "Business information", - "type": "integer" - }, - "founded_year": { - "type": "integer" - }, - "industry": { - "type": "string" - }, - "language": { - "description": "Settings", - "type": "string" - }, - "name": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "registration_number": { - "type": "string" - }, - "tags": { - "description": "Tags for categorization and search", - "allOf": [ - { - "$ref": "#/definitions/company.CompanyTagsForm" - } - ] - }, - "tax_id": { - "type": "string" - }, - "timezone": { - "type": "string" - }, - "type": { - "type": "string" - }, - "website": { - "type": "string" - } - } - }, - "company.UpdateCompanyStatusForm": { - "type": "object", - "properties": { "status": { "type": "string" } } }, - "company.UpdateCompanyTagsForm": { - "type": "object", - "properties": { - "tags": { - "$ref": "#/definitions/company.CompanyTagsForm" - } - } - }, - "company.UpdateCompanyVerificationForm": { + "company.VerificationForm": { "type": "object", "properties": { "compliance_notes": { @@ -6752,9 +5526,6 @@ const docTemplate = `{ "items": { "$ref": "#/definitions/customer.CustomerResponse" } - }, - "meta": { - "$ref": "#/definitions/response.Meta" } } }, @@ -7664,9 +6435,6 @@ const docTemplate = `{ "user.UserListResponse": { "type": "object", "properties": { - "meta": { - "$ref": "#/definitions/response.Meta" - }, "users": { "type": "array", "items": { diff --git a/cmd/web/docs/swagger.json b/cmd/web/docs/swagger.json index 086f3fc..c8e9ac1 100644 --- a/cmd/web/docs/swagger.json +++ b/cmd/web/docs/swagger.json @@ -328,377 +328,6 @@ } }, "/admin/v1/companies": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "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.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "List companies with filters and pagination", - "parameters": [ - { - "type": "string", - "description": "Search term to filter companies by name, description, or industry", - "name": "search", - "in": "query" - }, - { - "enum": [ - "private", - "public", - "government", - "ngo", - "startup" - ], - "type": "string", - "description": "Filter by company type", - "name": "type", - "in": "query" - }, - { - "enum": [ - "active", - "inactive", - "suspended", - "pending" - ], - "type": "string", - "description": "Filter by company status", - "name": "status", - "in": "query" - }, - { - "type": "string", - "description": "Filter by industry", - "name": "industry", - "in": "query" - }, - { - "type": "boolean", - "description": "Filter by verification status", - "name": "is_verified", - "in": "query" - }, - { - "type": "boolean", - "description": "Filter by compliance status", - "name": "is_compliant", - "in": "query" - }, - { - "type": "array", - "description": "Filter by CPV codes", - "name": "cpv_codes", - "in": "query" - }, - { - "type": "array", - "description": "Filter by categories", - "name": "categories", - "in": "query" - }, - { - "type": "array", - "description": "Filter by keywords", - "name": "keywords", - "in": "query" - }, - { - "type": "array", - "description": "Filter by specializations", - "name": "specializations", - "in": "query" - }, - { - "type": "integer", - "description": "Minimum employee count", - "name": "employee_count_min", - "in": "query" - }, - { - "type": "integer", - "description": "Maximum employee count", - "name": "employee_count_max", - "in": "query" - }, - { - "type": "number", - "description": "Minimum annual revenue", - "name": "annual_revenue_min", - "in": "query" - }, - { - "type": "number", - "description": "Maximum annual revenue", - "name": "annual_revenue_max", - "in": "query" - }, - { - "type": "integer", - "description": "Minimum founded year", - "name": "founded_year_min", - "in": "query" - }, - { - "type": "integer", - "description": "Maximum founded year", - "name": "founded_year_max", - "in": "query" - }, - { - "maximum": 100, - "minimum": 1, - "type": "integer", - "default": 20, - "description": "Number of companies per page (1-100)", - "name": "limit", - "in": "query" - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Number of companies to skip for pagination", - "name": "offset", - "in": "query" - }, - { - "enum": [ - "name", - "type", - "industry", - "created_at", - "updated_at", - "status", - "employee_count", - "annual_revenue" - ], - "type": "string", - "default": "created_at", - "description": "Field to sort by", - "name": "sort_by", - "in": "query" - }, - { - "enum": [ - "asc", - "desc" - ], - "type": "string", - "default": "desc", - "description": "Sort order", - "name": "sort_order", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Companies retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/company.CompanyListResponse" - } - } - } - ] - } - }, - "400": { - "description": "Bad request - Invalid query parameters", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid query parameters", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - }, - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "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.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Create a new company", - "parameters": [ - { - "description": "Company information including type, business details, address, tags, and optional customer assignment", - "name": "company", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/company.CreateCompanyForm" - } - } - ], - "responses": { - "201": { - "description": "Company created successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/company.CompanyResponse" - } - } - } - ] - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "409": { - "description": "Conflict - Company with this name/registration/tax ID already exists", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/companies/industry/{industry}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve companies filtered by their industry", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Get companies by industry", - "parameters": [ - { - "type": "string", - "description": "Industry", - "name": "industry", - "in": "path", - "required": true - }, - { - "maximum": 100, - "minimum": 1, - "type": "integer", - "default": 20, - "description": "Number of companies per page (1-100)", - "name": "limit", - "in": "query" - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Number of companies to skip for pagination", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Companies retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/company.CompanyResponse" - } - }, - "meta": { - "$ref": "#/definitions/response.Meta" - } - } - } - ] - } - }, - "400": { - "description": "Bad request - Invalid industry", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/companies/search": { "get": { "security": [ { @@ -848,16 +477,14 @@ } } } - } - }, - "/admin/v1/companies/stats": { - "get": { + }, + "post": { "security": [ { "BearerAuth": [] } ], - "description": "Get comprehensive company statistics for dashboard", + "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.", "consumes": [ "application/json" ], @@ -867,88 +494,21 @@ "tags": [ "Admin-Companies" ], - "summary": "Get company statistics", - "responses": { - "200": { - "description": "Company statistics retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/company.CompanyStatsResponse" - } - } - } - ] - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/companies/status/{status}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve companies filtered by their status (active, inactive, suspended, pending)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Get companies by status", + "summary": "Create a new company", "parameters": [ { - "enum": [ - "active", - "inactive", - "suspended", - "pending" - ], - "type": "string", - "description": "Company status", - "name": "status", - "in": "path", - "required": true - }, - { - "maximum": 100, - "minimum": 1, - "type": "integer", - "default": 20, - "description": "Number of companies per page (1-100)", - "name": "limit", - "in": "query" - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Number of companies to skip for pagination", - "name": "offset", - "in": "query" + "description": "Company information including type, business details, address, tags, and optional customer assignment", + "name": "company", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/company.CompanyForm" + } } ], "responses": { - "200": { - "description": "Companies retrieved successfully", + "201": { + "description": "Company created successfully", "schema": { "allOf": [ { @@ -958,13 +518,7 @@ "type": "object", "properties": { "data": { - "type": "array", - "items": { - "$ref": "#/definitions/company.CompanyResponse" - } - }, - "meta": { - "$ref": "#/definitions/response.Meta" + "$ref": "#/definitions/company.CompanyResponse" } } } @@ -972,98 +526,19 @@ } }, "400": { - "description": "Bad request - Invalid company status", + "description": "Bad request - Invalid input data", "schema": { "$ref": "#/definitions/response.APIResponse" } }, - "500": { - "description": "Internal server error", + "409": { + "description": "Conflict - Company with this name/registration/tax ID already exists", "schema": { "$ref": "#/definitions/response.APIResponse" } - } - } - } - }, - "/admin/v1/companies/type/{type}": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Retrieve companies filtered by their type (private, public, government, ngo, startup)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Get companies by type", - "parameters": [ - { - "enum": [ - "private", - "public", - "government", - "ngo", - "startup" - ], - "type": "string", - "description": "Company type", - "name": "type", - "in": "path", - "required": true }, - { - "maximum": 100, - "minimum": 1, - "type": "integer", - "default": 20, - "description": "Number of companies per page (1-100)", - "name": "limit", - "in": "query" - }, - { - "minimum": 0, - "type": "integer", - "default": 0, - "description": "Number of companies to skip for pagination", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Companies retrieved successfully", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/response.APIResponse" - }, - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/company.CompanyResponse" - } - }, - "meta": { - "$ref": "#/definitions/response.Meta" - } - } - } - ] - } - }, - "400": { - "description": "Bad request - Invalid company type", + "422": { + "description": "Validation error - Invalid request data", "schema": { "$ref": "#/definitions/response.APIResponse" } @@ -1174,7 +649,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/company.UpdateCompanyForm" + "$ref": "#/definitions/company.CompanyForm" } } ], @@ -1283,61 +758,6 @@ } } }, - "/admin/v1/companies/{id}/activate": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Activate a suspended company account", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Activate company", - "parameters": [ - { - "type": "string", - "description": "Company ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Company activated successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid company ID", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Company not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/admin/v1/companies/{id}/status": { "patch": { "security": [ @@ -1370,7 +790,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/company.UpdateCompanyStatusForm" + "$ref": "#/definitions/company.StatusForm" } } ], @@ -1408,286 +828,6 @@ } } }, - "/admin/v1/companies/{id}/suspend": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Suspend a company account with reason", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Suspend company", - "parameters": [ - { - "type": "string", - "description": "Company ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Suspension information", - "name": "suspension", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/company.SuspendCompanyForm" - } - } - ], - "responses": { - "200": { - "description": "Company suspended successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Company not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/companies/{id}/tags": { - "put": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update company tags (CPV, categories, keywords, specializations, certifications)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Update company tags", - "parameters": [ - { - "type": "string", - "description": "Company ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Tags update information", - "name": "tags", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/company.UpdateCompanyTagsForm" - } - } - ], - "responses": { - "200": { - "description": "Company tags updated successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Company not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/companies/{id}/tags/add": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Add specific tags to a company", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Add tags to company", - "parameters": [ - { - "type": "string", - "description": "Company ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Tags to add", - "name": "tags", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/company.AddTagsForm" - } - } - ], - "responses": { - "200": { - "description": "Tags added successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Company not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/admin/v1/companies/{id}/tags/remove": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Remove specific tags from a company", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Remove tags from company", - "parameters": [ - { - "type": "string", - "description": "Company ID", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Tags to remove", - "name": "tags", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/company.RemoveTagsForm" - } - } - ], - "responses": { - "200": { - "description": "Tags removed successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid input data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Company not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "422": { - "description": "Validation error - Invalid request data", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/admin/v1/companies/{id}/verification": { "patch": { "security": [ @@ -1720,7 +860,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/company.UpdateCompanyVerificationForm" + "$ref": "#/definitions/company.VerificationForm" } } ], @@ -1758,61 +898,6 @@ } } }, - "/admin/v1/companies/{id}/verify": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Mark a company as verified", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Admin-Companies" - ], - "summary": "Verify company", - "parameters": [ - { - "type": "string", - "description": "Company ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Company verified successfully", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad request - Invalid company ID", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "404": { - "description": "Not found - Company not found", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/admin/v1/customers": { "get": { "security": [ @@ -6041,41 +5126,6 @@ } } }, - "company.AddTagsForm": { - "type": "object", - "properties": { - "categories": { - "type": "array", - "items": { - "type": "string" - } - }, - "certifications": { - "type": "array", - "items": { - "type": "string" - } - }, - "cpv_codes": { - "type": "array", - "items": { - "type": "string" - } - }, - "keywords": { - "type": "array", - "items": { - "type": "string" - } - }, - "specializations": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, "company.Address": { "type": "object", "properties": { @@ -6116,25 +5166,72 @@ } } }, - "company.CPVCodeCount": { + "company.CompanyForm": { "type": "object", "properties": { - "count": { - "type": "integer" + "address": { + "description": "Address information", + "allOf": [ + { + "$ref": "#/definitions/company.AddressForm" + } + ] }, - "cpv_code": { - "type": "string" - } - } - }, - "company.CategoryCount": { - "type": "object", - "properties": { - "category": { + "annual_revenue": { + "type": "number" + }, + "currency": { "type": "string" }, - "count": { + "description": { + "type": "string" + }, + "email": { + "type": "string" + }, + "employee_count": { + "description": "Business information", "type": "integer" + }, + "founded_year": { + "type": "integer" + }, + "industry": { + "type": "string" + }, + "language": { + "description": "Settings", + "type": "string" + }, + "name": { + "type": "string" + }, + "phone": { + "description": "Contact information", + "type": "string" + }, + "registration_number": { + "type": "string" + }, + "tags": { + "description": "Tags for categorization and search", + "allOf": [ + { + "$ref": "#/definitions/company.CompanyTagsForm" + } + ] + }, + "tax_id": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "type": { + "type": "string" + }, + "website": { + "type": "string" } } }, @@ -6147,17 +5244,8 @@ "$ref": "#/definitions/company.CompanyResponse" } }, - "limit": { - "type": "integer" - }, - "offset": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "total_pages": { - "type": "integer" + "meta": { + "$ref": "#/definitions/response.Meta" } } }, @@ -6196,15 +5284,9 @@ "compliance_notes": { "type": "string" }, - "contact_person": { - "$ref": "#/definitions/company.ContactPerson" - }, "created_at": { "type": "integer" }, - "created_by": { - "type": "string" - }, "currency": { "type": "string" }, @@ -6262,61 +5344,11 @@ "updated_at": { "type": "integer" }, - "updated_by": { - "type": "string" - }, "website": { "type": "string" } } }, - "company.CompanyStatsResponse": { - "type": "object", - "properties": { - "active_companies": { - "type": "integer" - }, - "companies_by_industry": { - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int64" - } - }, - "companies_by_status": { - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int64" - } - }, - "companies_by_type": { - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "int64" - } - }, - "top_categories": { - "type": "array", - "items": { - "$ref": "#/definitions/company.CategoryCount" - } - }, - "top_cpv_codes": { - "type": "array", - "items": { - "$ref": "#/definitions/company.CPVCodeCount" - } - }, - "total_companies": { - "type": "integer" - }, - "verified_companies": { - "type": "integer" - } - } - }, "company.CompanyTags": { "type": "object", "properties": { @@ -6392,276 +5424,18 @@ } } }, - "company.ContactPerson": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "first_name": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "is_primary": { - "type": "boolean" - }, - "last_name": { - "type": "string" - }, - "mobile": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "position": { - "type": "string" - } - } - }, - "company.ContactPersonForm": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "first_name": { - "type": "string" - }, - "full_name": { - "type": "string" - }, - "is_primary": { - "type": "boolean" - }, - "last_name": { - "type": "string" - }, - "mobile": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "position": { - "type": "string" - } - } - }, - "company.CreateCompanyForm": { - "type": "object", - "properties": { - "address": { - "description": "Address information", - "allOf": [ - { - "$ref": "#/definitions/company.AddressForm" - } - ] - }, - "annual_revenue": { - "type": "number" - }, - "contact_person": { - "description": "Contact information", - "allOf": [ - { - "$ref": "#/definitions/company.ContactPersonForm" - } - ] - }, - "currency": { - "type": "string" - }, - "description": { - "type": "string" - }, - "email": { - "type": "string" - }, - "employee_count": { - "description": "Business information", - "type": "integer" - }, - "founded_year": { - "type": "integer" - }, - "industry": { - "type": "string" - }, - "language": { - "description": "Settings", - "type": "string" - }, - "name": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "registration_number": { - "type": "string" - }, - "tags": { - "description": "Tags for categorization and search", - "allOf": [ - { - "$ref": "#/definitions/company.CompanyTagsForm" - } - ] - }, - "tax_id": { - "type": "string" - }, - "timezone": { - "type": "string" - }, - "type": { - "type": "string" - }, - "website": { - "type": "string" - } - } - }, - "company.RemoveTagsForm": { - "type": "object", - "properties": { - "categories": { - "type": "array", - "items": { - "type": "string" - } - }, - "certifications": { - "type": "array", - "items": { - "type": "string" - } - }, - "cpv_codes": { - "type": "array", - "items": { - "type": "string" - } - }, - "keywords": { - "type": "array", - "items": { - "type": "string" - } - }, - "specializations": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "company.SuspendCompanyForm": { + "company.StatusForm": { "type": "object", "properties": { "reason": { "type": "string" - } - } - }, - "company.UpdateCompanyForm": { - "type": "object", - "properties": { - "address": { - "description": "Address information", - "allOf": [ - { - "$ref": "#/definitions/company.AddressForm" - } - ] }, - "annual_revenue": { - "type": "number" - }, - "contact_person": { - "description": "Contact information", - "allOf": [ - { - "$ref": "#/definitions/company.ContactPersonForm" - } - ] - }, - "currency": { - "type": "string" - }, - "description": { - "type": "string" - }, - "email": { - "type": "string" - }, - "employee_count": { - "description": "Business information", - "type": "integer" - }, - "founded_year": { - "type": "integer" - }, - "industry": { - "type": "string" - }, - "language": { - "description": "Settings", - "type": "string" - }, - "name": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "registration_number": { - "type": "string" - }, - "tags": { - "description": "Tags for categorization and search", - "allOf": [ - { - "$ref": "#/definitions/company.CompanyTagsForm" - } - ] - }, - "tax_id": { - "type": "string" - }, - "timezone": { - "type": "string" - }, - "type": { - "type": "string" - }, - "website": { - "type": "string" - } - } - }, - "company.UpdateCompanyStatusForm": { - "type": "object", - "properties": { "status": { "type": "string" } } }, - "company.UpdateCompanyTagsForm": { - "type": "object", - "properties": { - "tags": { - "$ref": "#/definitions/company.CompanyTagsForm" - } - } - }, - "company.UpdateCompanyVerificationForm": { + "company.VerificationForm": { "type": "object", "properties": { "compliance_notes": { @@ -6746,9 +5520,6 @@ "items": { "$ref": "#/definitions/customer.CustomerResponse" } - }, - "meta": { - "$ref": "#/definitions/response.Meta" } } }, @@ -7658,9 +6429,6 @@ "user.UserListResponse": { "type": "object", "properties": { - "meta": { - "$ref": "#/definitions/response.Meta" - }, "users": { "type": "array", "items": { diff --git a/cmd/web/docs/swagger.yaml b/cmd/web/docs/swagger.yaml index eccfd8a..d760aec 100644 --- a/cmd/web/docs/swagger.yaml +++ b/cmd/web/docs/swagger.yaml @@ -23,29 +23,6 @@ definitions: example: 2.0.0 type: string type: object - company.AddTagsForm: - properties: - categories: - items: - type: string - type: array - certifications: - items: - type: string - type: array - cpv_codes: - items: - type: string - type: array - keywords: - items: - type: string - type: array - specializations: - items: - type: string - type: array - type: object company.Address: properties: city: @@ -72,19 +49,49 @@ definitions: street: type: string type: object - company.CPVCodeCount: + company.CompanyForm: properties: - count: - type: integer - cpv_code: + address: + allOf: + - $ref: '#/definitions/company.AddressForm' + description: Address information + annual_revenue: + type: number + currency: type: string - type: object - company.CategoryCount: - properties: - category: + description: type: string - count: + email: + type: string + employee_count: + description: Business information type: integer + founded_year: + type: integer + industry: + type: string + language: + description: Settings + type: string + name: + type: string + phone: + description: Contact information + type: string + registration_number: + type: string + tags: + allOf: + - $ref: '#/definitions/company.CompanyTagsForm' + description: Tags for categorization and search + tax_id: + type: string + timezone: + type: string + type: + type: string + website: + type: string type: object company.CompanyListResponse: properties: @@ -92,14 +99,8 @@ definitions: items: $ref: '#/definitions/company.CompanyResponse' type: array - limit: - type: integer - offset: - type: integer - total: - type: integer - total_pages: - type: integer + meta: + $ref: '#/definitions/response.Meta' type: object company.CompanyProfileResponse: properties: @@ -124,12 +125,8 @@ definitions: type: number compliance_notes: type: string - contact_person: - $ref: '#/definitions/company.ContactPerson' created_at: type: integer - created_by: - type: string currency: type: string description: @@ -168,43 +165,9 @@ definitions: type: string updated_at: type: integer - updated_by: - type: string website: type: string type: object - company.CompanyStatsResponse: - properties: - active_companies: - type: integer - companies_by_industry: - additionalProperties: - format: int64 - type: integer - type: object - companies_by_status: - additionalProperties: - format: int64 - type: integer - type: object - companies_by_type: - additionalProperties: - format: int64 - type: integer - type: object - top_categories: - items: - $ref: '#/definitions/company.CategoryCount' - type: array - top_cpv_codes: - items: - $ref: '#/definitions/company.CPVCodeCount' - type: array - total_companies: - type: integer - verified_companies: - type: integer - type: object company.CompanyTags: properties: categories: @@ -256,177 +219,14 @@ definitions: type: string type: array type: object - company.ContactPerson: - properties: - email: - type: string - first_name: - type: string - full_name: - type: string - is_primary: - type: boolean - last_name: - type: string - mobile: - type: string - phone: - type: string - position: - type: string - type: object - company.ContactPersonForm: - properties: - email: - type: string - first_name: - type: string - full_name: - type: string - is_primary: - type: boolean - last_name: - type: string - mobile: - type: string - phone: - type: string - position: - type: string - type: object - company.CreateCompanyForm: - properties: - address: - allOf: - - $ref: '#/definitions/company.AddressForm' - description: Address information - annual_revenue: - type: number - contact_person: - allOf: - - $ref: '#/definitions/company.ContactPersonForm' - description: Contact information - currency: - type: string - description: - type: string - email: - type: string - employee_count: - description: Business information - type: integer - founded_year: - type: integer - industry: - type: string - language: - description: Settings - type: string - name: - type: string - phone: - type: string - registration_number: - type: string - tags: - allOf: - - $ref: '#/definitions/company.CompanyTagsForm' - description: Tags for categorization and search - tax_id: - type: string - timezone: - type: string - type: - type: string - website: - type: string - type: object - company.RemoveTagsForm: - properties: - categories: - items: - type: string - type: array - certifications: - items: - type: string - type: array - cpv_codes: - items: - type: string - type: array - keywords: - items: - type: string - type: array - specializations: - items: - type: string - type: array - type: object - company.SuspendCompanyForm: + company.StatusForm: properties: reason: type: string - type: object - company.UpdateCompanyForm: - properties: - address: - allOf: - - $ref: '#/definitions/company.AddressForm' - description: Address information - annual_revenue: - type: number - contact_person: - allOf: - - $ref: '#/definitions/company.ContactPersonForm' - description: Contact information - currency: - type: string - description: - type: string - email: - type: string - employee_count: - description: Business information - type: integer - founded_year: - type: integer - industry: - type: string - language: - description: Settings - type: string - name: - type: string - phone: - type: string - registration_number: - type: string - tags: - allOf: - - $ref: '#/definitions/company.CompanyTagsForm' - description: Tags for categorization and search - tax_id: - type: string - timezone: - type: string - type: - type: string - website: - type: string - type: object - company.UpdateCompanyStatusForm: - properties: status: type: string type: object - company.UpdateCompanyTagsForm: - properties: - tags: - $ref: '#/definitions/company.CompanyTagsForm' - type: object - company.UpdateCompanyVerificationForm: + company.VerificationForm: properties: compliance_notes: type: string @@ -484,8 +284,6 @@ definitions: items: $ref: '#/definitions/customer.CustomerResponse' type: array - meta: - $ref: '#/definitions/response.Meta' type: object customer.CustomerResponse: properties: @@ -1108,8 +906,6 @@ definitions: type: object user.UserListResponse: properties: - meta: - $ref: '#/definitions/response.Meta' users: items: $ref: '#/definitions/user.UserResponse' @@ -1360,125 +1156,76 @@ paths: get: consumes: - application/json - 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. + description: Search companies with advanced filtering capabilities including + tags, business criteria, and location parameters: - - description: Search term to filter companies by name, description, or industry + - description: Search query in: query - name: search + name: q type: string - - description: Filter by company type - enum: - - private - - public - - government - - ngo - - startup - in: query - name: type - type: string - - description: Filter by company status - enum: - - active - - inactive - - suspended - - pending - in: query - name: status - type: string - - description: Filter by industry - in: query - name: industry - type: string - - description: Filter by verification status - in: query - name: is_verified - type: boolean - - description: Filter by compliance status - in: query - name: is_compliant - type: boolean - - description: Filter by CPV codes + - description: CPV codes filter in: query name: cpv_codes type: array - - description: Filter by categories + - description: Categories filter in: query name: categories type: array - - description: Filter by keywords + - description: Keywords filter in: query name: keywords type: array - - description: Filter by specializations + - description: Specializations filter in: query name: specializations type: array - - description: Minimum employee count + - description: Industry filter in: query - name: employee_count_min + name: industry + type: string + - description: Location filter + in: query + name: location + type: string + - description: Minimum employees + in: query + name: min_employees type: integer - - description: Maximum employee count + - description: Maximum employees in: query - name: employee_count_max + name: max_employees type: integer - - description: Minimum annual revenue + - description: Minimum revenue in: query - name: annual_revenue_min + name: min_revenue type: number - - description: Maximum annual revenue + - description: Maximum revenue in: query - name: annual_revenue_max + name: max_revenue type: number - - description: Minimum founded year + - description: Verification status in: query - name: founded_year_min - type: integer - - description: Maximum founded year + name: is_verified + type: boolean + - description: Compliance status in: query - name: founded_year_max - type: integer + name: is_compliant + type: boolean - default: 20 - description: Number of companies per page (1-100) + description: Limit in: query - maximum: 100 - minimum: 1 name: limit type: integer - default: 0 - description: Number of companies to skip for pagination + description: Offset in: query - minimum: 0 name: offset type: integer - - default: created_at - description: Field to sort by - enum: - - name - - type - - industry - - created_at - - updated_at - - status - - employee_count - - annual_revenue - in: query - name: sort_by - type: string - - default: desc - description: Sort order - enum: - - asc - - desc - in: query - name: sort_order - type: string produces: - application/json responses: "200": - description: Companies retrieved successfully + description: Companies search completed schema: allOf: - $ref: '#/definitions/response.APIResponse' @@ -1500,7 +1247,7 @@ paths: $ref: '#/definitions/response.APIResponse' security: - BearerAuth: [] - summary: List companies with filters and pagination + summary: Advanced search for companies tags: - Admin-Companies post: @@ -1516,7 +1263,7 @@ paths: name: company required: true schema: - $ref: '#/definitions/company.CreateCompanyForm' + $ref: '#/definitions/company.CompanyForm' produces: - application/json responses: @@ -1641,7 +1388,7 @@ paths: name: company required: true schema: - $ref: '#/definitions/company.UpdateCompanyForm' + $ref: '#/definitions/company.CompanyForm' produces: - application/json responses: @@ -1680,41 +1427,6 @@ paths: summary: Update company information tags: - Admin-Companies - /admin/v1/companies/{id}/activate: - post: - consumes: - - application/json - description: Activate a suspended company account - parameters: - - description: Company ID - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: Company activated successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - Invalid company ID - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Not found - Company not found - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Activate company - tags: - - Admin-Companies /admin/v1/companies/{id}/status: patch: consumes: @@ -1731,7 +1443,7 @@ paths: name: status required: true schema: - $ref: '#/definitions/company.UpdateCompanyStatusForm' + $ref: '#/definitions/company.StatusForm' produces: - application/json responses: @@ -1760,187 +1472,6 @@ paths: summary: Update company status tags: - Admin-Companies - /admin/v1/companies/{id}/suspend: - post: - consumes: - - application/json - description: Suspend a company account with reason - parameters: - - description: Company ID - in: path - name: id - required: true - type: string - - description: Suspension information - in: body - name: suspension - required: true - schema: - $ref: '#/definitions/company.SuspendCompanyForm' - produces: - - application/json - responses: - "200": - description: Company suspended successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - Invalid input data - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Not found - Company not found - schema: - $ref: '#/definitions/response.APIResponse' - "422": - description: Validation error - Invalid request data - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Suspend company - tags: - - Admin-Companies - /admin/v1/companies/{id}/tags: - put: - consumes: - - application/json - description: Update company tags (CPV, categories, keywords, specializations, - certifications) - parameters: - - description: Company ID - in: path - name: id - required: true - type: string - - description: Tags update information - in: body - name: tags - required: true - schema: - $ref: '#/definitions/company.UpdateCompanyTagsForm' - produces: - - application/json - responses: - "200": - description: Company tags updated successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - Invalid input data - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Not found - Company not found - schema: - $ref: '#/definitions/response.APIResponse' - "422": - description: Validation error - Invalid request data - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Update company tags - tags: - - Admin-Companies - /admin/v1/companies/{id}/tags/add: - post: - consumes: - - application/json - description: Add specific tags to a company - parameters: - - description: Company ID - in: path - name: id - required: true - type: string - - description: Tags to add - in: body - name: tags - required: true - schema: - $ref: '#/definitions/company.AddTagsForm' - produces: - - application/json - responses: - "200": - description: Tags added successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - Invalid input data - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Not found - Company not found - schema: - $ref: '#/definitions/response.APIResponse' - "422": - description: Validation error - Invalid request data - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Add tags to company - tags: - - Admin-Companies - /admin/v1/companies/{id}/tags/remove: - post: - consumes: - - application/json - description: Remove specific tags from a company - parameters: - - description: Company ID - in: path - name: id - required: true - type: string - - description: Tags to remove - in: body - name: tags - required: true - schema: - $ref: '#/definitions/company.RemoveTagsForm' - produces: - - application/json - responses: - "200": - description: Tags removed successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - Invalid input data - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Not found - Company not found - schema: - $ref: '#/definitions/response.APIResponse' - "422": - description: Validation error - Invalid request data - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Remove tags from company - tags: - - Admin-Companies /admin/v1/companies/{id}/verification: patch: consumes: @@ -1957,7 +1488,7 @@ paths: name: verification required: true schema: - $ref: '#/definitions/company.UpdateCompanyVerificationForm' + $ref: '#/definitions/company.VerificationForm' produces: - application/json responses: @@ -1986,337 +1517,6 @@ paths: summary: Update company verification status tags: - Admin-Companies - /admin/v1/companies/{id}/verify: - post: - consumes: - - application/json - description: Mark a company as verified - parameters: - - description: Company ID - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: Company verified successfully - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad request - Invalid company ID - schema: - $ref: '#/definitions/response.APIResponse' - "404": - description: Not found - Company not found - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Verify company - tags: - - Admin-Companies - /admin/v1/companies/industry/{industry}: - get: - consumes: - - application/json - description: Retrieve companies filtered by their industry - parameters: - - description: Industry - in: path - name: industry - required: true - type: string - - default: 20 - description: Number of companies per page (1-100) - in: query - maximum: 100 - minimum: 1 - name: limit - type: integer - - default: 0 - description: Number of companies to skip for pagination - in: query - minimum: 0 - name: offset - type: integer - produces: - - application/json - responses: - "200": - description: Companies retrieved successfully - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - items: - $ref: '#/definitions/company.CompanyResponse' - type: array - meta: - $ref: '#/definitions/response.Meta' - type: object - "400": - description: Bad request - Invalid industry - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Get companies by industry - tags: - - Admin-Companies - /admin/v1/companies/search: - get: - consumes: - - application/json - description: Search companies with advanced filtering capabilities including - tags, business criteria, and location - parameters: - - description: Search query - in: query - name: q - type: string - - description: CPV codes filter - in: query - name: cpv_codes - type: array - - description: Categories filter - in: query - name: categories - type: array - - description: Keywords filter - in: query - name: keywords - type: array - - description: Specializations filter - in: query - name: specializations - type: array - - description: Industry filter - in: query - name: industry - type: string - - description: Location filter - in: query - name: location - type: string - - description: Minimum employees - in: query - name: min_employees - type: integer - - description: Maximum employees - in: query - name: max_employees - type: integer - - description: Minimum revenue - in: query - name: min_revenue - type: number - - description: Maximum revenue - in: query - name: max_revenue - type: number - - description: Verification status - in: query - name: is_verified - type: boolean - - description: Compliance status - in: query - name: is_compliant - type: boolean - - default: 20 - description: Limit - in: query - name: limit - type: integer - - default: 0 - description: Offset - in: query - name: offset - type: integer - produces: - - application/json - responses: - "200": - description: Companies search completed - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - $ref: '#/definitions/company.CompanyListResponse' - type: object - "400": - description: Bad request - Invalid query parameters - schema: - $ref: '#/definitions/response.APIResponse' - "422": - description: Validation error - Invalid query parameters - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Advanced search for companies - tags: - - Admin-Companies - /admin/v1/companies/stats: - get: - consumes: - - application/json - description: Get comprehensive company statistics for dashboard - produces: - - application/json - responses: - "200": - description: Company statistics retrieved successfully - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - $ref: '#/definitions/company.CompanyStatsResponse' - type: object - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Get company statistics - tags: - - Admin-Companies - /admin/v1/companies/status/{status}: - get: - consumes: - - application/json - description: Retrieve companies filtered by their status (active, inactive, - suspended, pending) - parameters: - - description: Company status - enum: - - active - - inactive - - suspended - - pending - in: path - name: status - required: true - type: string - - default: 20 - description: Number of companies per page (1-100) - in: query - maximum: 100 - minimum: 1 - name: limit - type: integer - - default: 0 - description: Number of companies to skip for pagination - in: query - minimum: 0 - name: offset - type: integer - produces: - - application/json - responses: - "200": - description: Companies retrieved successfully - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - items: - $ref: '#/definitions/company.CompanyResponse' - type: array - meta: - $ref: '#/definitions/response.Meta' - type: object - "400": - description: Bad request - Invalid company status - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Get companies by status - tags: - - Admin-Companies - /admin/v1/companies/type/{type}: - get: - consumes: - - application/json - description: Retrieve companies filtered by their type (private, public, government, - ngo, startup) - parameters: - - description: Company type - enum: - - private - - public - - government - - ngo - - startup - in: path - name: type - required: true - type: string - - default: 20 - description: Number of companies per page (1-100) - in: query - maximum: 100 - minimum: 1 - name: limit - type: integer - - default: 0 - description: Number of companies to skip for pagination - in: query - minimum: 0 - name: offset - type: integer - produces: - - application/json - responses: - "200": - description: Companies retrieved successfully - schema: - allOf: - - $ref: '#/definitions/response.APIResponse' - - properties: - data: - items: - $ref: '#/definitions/company.CompanyResponse' - type: array - meta: - $ref: '#/definitions/response.Meta' - type: object - "400": - description: Bad request - Invalid company type - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - BearerAuth: [] - summary: Get companies by type - tags: - - Admin-Companies /admin/v1/customers: get: consumes: diff --git a/cmd/web/router/routes.go b/cmd/web/router/routes.go index 5703a46..03480fb 100644 --- a/cmd/web/router/routes.go +++ b/cmd/web/router/routes.go @@ -47,24 +47,13 @@ func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler companiesGP := adminV1.Group("/companies") { companiesGP.Use(userHandler.AuthMiddleware()) - companiesGP.POST("", companyHandler.CreateCompany) - companiesGP.GET("", companyHandler.ListCompanies) - companiesGP.GET("/:id", companyHandler.GetCompany) - companiesGP.PUT("/:id", companyHandler.UpdateCompany) - companiesGP.DELETE("/:id", companyHandler.DeleteCompany) - companiesGP.GET("/search", companyHandler.SearchCompanies) - companiesGP.PATCH("/:id/status", companyHandler.UpdateCompanyStatus) - 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) + companiesGP.POST("", companyHandler.Create) + companiesGP.GET("", companyHandler.Search) + companiesGP.GET("/:id", companyHandler.Get) + companiesGP.PUT("/:id", companyHandler.Update) + companiesGP.DELETE("/:id", companyHandler.Delete) + companiesGP.PATCH("/:id/status", companyHandler.UpdateStatus) + companiesGP.PATCH("/:id/verification", companyHandler.UpdateVerification) } // Admin Customers Routes diff --git a/internal/company/entity.go b/internal/company/entity.go index e521734..ba35457 100644 --- a/internal/company/entity.go +++ b/internal/company/entity.go @@ -13,7 +13,6 @@ const ( CompanyStatusActive CompanyStatus = "active" CompanyStatusInactive CompanyStatus = "inactive" CompanyStatusSuspended CompanyStatus = "suspended" - CompanyStatusPending CompanyStatus = "pending" ) // CompanyType represents the type of company @@ -48,9 +47,8 @@ type Company struct { Address *Address `bson:"address,omitempty" json:"address,omitempty"` // Contact information - ContactPerson *ContactPerson `bson:"contact_person,omitempty" json:"contact_person,omitempty"` - Phone *string `bson:"phone,omitempty" json:"phone,omitempty"` - Email *string `bson:"email,omitempty" json:"email,omitempty"` + Phone *string `bson:"phone,omitempty" json:"phone,omitempty"` + Email *string `bson:"email,omitempty" json:"email,omitempty"` // Tags for categorization and search Tags *CompanyTags `bson:"tags,omitempty" json:"tags,omitempty"` @@ -64,10 +62,6 @@ type Company struct { Language string `bson:"language" json:"language"` // Default: "en" Currency string `bson:"currency" json:"currency"` // Default: "USD" 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) @@ -80,22 +74,22 @@ func (c *Company) GetID() string { 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) { 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) { c.UpdatedAt = timestamp } -// GetCreatedAt returns the created timestamp (implements Timestampable interface) +// GetCreatedAt returns the created timestamp (implements Timestamp interface) func (c *Company) GetCreatedAt() int64 { return c.CreatedAt } -// GetUpdatedAt returns the updated timestamp (implements Timestampable interface) +// GetUpdatedAt returns the updated timestamp (implements Timestamp interface) func (c *Company) GetUpdatedAt() int64 { return c.UpdatedAt } @@ -109,18 +103,6 @@ type Address struct { 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 type CompanyTags struct { // CPV codes (Common Procurement Vocabulary) @@ -138,88 +120,3 @@ type CompanyTags struct { // Certifications 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, - } -} diff --git a/internal/company/form.go b/internal/company/form.go index adf424d..176850a 100644 --- a/internal/company/form.go +++ b/internal/company/form.go @@ -1,72 +1,61 @@ package company -// CreateCompanyForm represents the form for creating a new company -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"` +import "tm/pkg/response" - // Business information - 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)"` +// CompanyForm represents the form for creating/updating a new company +type ( + CompanyForm 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"` - // Address information - Address *AddressForm `json:"address,omitempty"` + // Business information + 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 - ContactPerson *ContactPersonForm `json:"contact_person,omitempty"` - Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"` - Email *string `json:"email,omitempty" valid:"optional,email"` + // Address information + Address *AddressForm `json:"address,omitempty"` - // Tags for categorization and search - Tags *CompanyTagsForm `json:"tags,omitempty"` + // Contact information + Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"` + Email *string `json:"email,omitempty" valid:"optional,email"` - // 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)"` -} + // Tags for categorization and search + Tags *CompanyTagsForm `json:"tags,omitempty"` -// UpdateCompanyForm represents the form for updating a company -type UpdateCompanyForm struct { - Name *string `json:"name,omitempty" valid:"optional,length(2|200)"` - Type *string `json:"type,omitempty" valid:"optional,in(private|public|government|ngo|startup)"` - 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"` + // 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)"` + } - // Business information - 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)"` + // AddressForm represents the form for company address + 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)"` + } - // Address information - Address *AddressForm `json:"address,omitempty"` + // CompanyTagsForm represents the form for company tags + 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 - ContactPerson *ContactPersonForm `json:"contact_person,omitempty"` - Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"` - 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"` +// SearchForm represents the form for searching companies with filters +type SearchForm struct { + Search *string `query:"q" valid:"optional"` Type *string `query:"type" valid:"optional,in(private|public|government|ngo|startup)"` Status *string `query:"status" valid:"optional,in(active|inactive|suspended|pending)"` Industry *string `query:"industry" valid:"optional"` @@ -90,122 +79,101 @@ type ListCompaniesForm struct { SortOrder *string `query:"sort_order" valid:"optional,in(asc|desc)"` } -// UpdateCompanyStatusForm represents the form for updating company status -type UpdateCompanyStatusForm struct { - Status string `json:"status" valid:"required,in(active|inactive|suspended|pending)"` +// StatusForm represents the form for updating company status +type StatusForm struct { + 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 -type UpdateCompanyVerificationForm struct { +// VerificationForm represents the form for updating company verification status +type VerificationForm struct { IsVerified bool `json:"is_verified"` IsCompliant bool `json:"is_compliant"` 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 type CompanyListResponse struct { - Companies []*CompanyResponse `json:"companies"` - Total int64 `json:"total"` - Limit int `json:"limit"` - Offset int `json:"offset"` - TotalPages int `json:"total_pages"` + Companies []*CompanyResponse `json:"companies"` + Meta *response.Meta `json:"-"` } -// Company search and matching DTOs -type CompanySearchForm struct { - Query *string `query:"q" valid:"optional"` - CPVCodes []string `query:"cpv_codes" valid:"optional"` - Categories []string `query:"categories" valid:"optional"` - Keywords []string `query:"keywords" valid:"optional"` - Specializations []string `query:"specializations" valid:"optional"` - Industry *string `query:"industry" valid:"optional"` - Location *string `query:"location" valid:"optional"` - MinEmployees *int `query:"min_employees" valid:"optional,min(1)"` - MaxEmployees *int `query:"max_employees" valid:"optional,min(1)"` - MinRevenue *float64 `query:"min_revenue" valid:"optional,min(0)"` - MaxRevenue *float64 `query:"max_revenue" valid:"optional,min(0)"` - IsVerified *bool `query:"is_verified" valid:"optional"` - IsCompliant *bool `query:"is_compliant" valid:"optional"` - Limit *int `query:"limit" valid:"optional,range(1|100)"` - Offset *int `query:"offset" valid:"optional,min(0)"` +// 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"` + 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"` } -// Company statistics DTOs -type CompanyStatsResponse struct { - TotalCompanies int64 `json:"total_companies"` - ActiveCompanies int64 `json:"active_companies"` - VerifiedCompanies int64 `json:"verified_companies"` - CompaniesByType map[string]int64 `json:"companies_by_type"` - CompaniesByIndustry map[string]int64 `json:"companies_by_industry"` - CompaniesByStatus map[string]int64 `json:"companies_by_status"` - TopCategories []CategoryCount `json:"top_categories"` - TopCPVCodes []CPVCodeCount `json:"top_cpv_codes"` +// 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, + 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 { - Category string `json:"category"` - Count int64 `json:"count"` +// CompanyProfileResponse represents the company profile data sent in API responses +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"` } -type CPVCodeCount struct { - CPVCode string `json:"cpv_code"` - Count int64 `json:"count"` +// 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, + } } diff --git a/internal/company/handler.go b/internal/company/handler.go index bf2778c..61ec4b0 100644 --- a/internal/company/handler.go +++ b/internal/company/handler.go @@ -1,7 +1,6 @@ package company import ( - "strconv" "tm/internal/user" "tm/pkg/logger" "tm/pkg/response" @@ -44,23 +43,23 @@ func (h *Handler) MyCompany(c echo.Context) error { 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 { 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 **************************** -// CreateCompany creates a new company (Web Panel) +// Create creates a new company (Web Panel) // @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. // @Tags Admin-Companies // @Accept 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" // @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" @@ -68,19 +67,13 @@ func (h *Handler) MyCompany(c echo.Context) error { // @Failure 500 {object} response.APIResponse "Internal server error" // @Security BearerAuth // @Router /admin/v1/companies [post] -func (h *Handler) CreateCompany(c echo.Context) error { - form, err := response.Parse[CreateCompanyForm](c) +func (h *Handler) Create(c echo.Context) error { + form, err := response.Parse[CompanyForm](c) if err != nil { return response.ValidationError(c, "Invalid request data", err.Error()) } - // Get user ID from JWT token context - 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) + company, err := h.service.Create(c.Request().Context(), form) if err != nil { if err.Error() == "company with this name 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.Created(c, company.ToResponse(), "Company created successfully") + return response.Created(c, company, "Company created successfully") } // 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" // @Security BearerAuth // @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") - company, err := h.service.GetCompanyByID(c.Request().Context(), id) + company, err := h.service.GetByID(c.Request().Context(), id) if err != nil { if err.Error() == "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.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 // @Description Update company information including business details, address, tags, and customer assignment // @Tags Admin-Companies // @Accept json // @Produce json // @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" // @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" // @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" // @Security BearerAuth // @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") - form, err := response.Parse[UpdateCompanyForm](c) + form, err := response.Parse[CompanyForm](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") - } - - company, err := h.service.UpdateCompany(c.Request().Context(), id, form, &updatedBy) + company, err := h.service.Update(c.Request().Context(), id, form) if err != nil { if err.Error() == "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.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 // @Description Soft delete a company by setting status to inactive // @Tags Admin-Companies @@ -178,16 +165,10 @@ func (h *Handler) UpdateCompany(c echo.Context) error { // @Failure 500 {object} response.APIResponse "Internal server error" // @Security BearerAuth // @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") - // Get user ID from JWT token context - deletedBy, err := user.GetUserIDFromContext(c) - if err != nil { - return response.Unauthorized(c, "User not authenticated") - } - - err = h.service.DeleteCompany(c.Request().Context(), id, &deletedBy) + err := h.service.Delete(c.Request().Context(), id) if err != nil { if err.Error() == "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") } -// ListCompanies lists companies with filters and pagination (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) +// Search searches companies with advanced filters (Web Panel) // @Summary Advanced search for companies // @Description Search companies with advanced filtering capabilities including tags, business criteria, and location // @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 500 {object} response.APIResponse "Internal server error" // @Security BearerAuth -// @Router /admin/v1/companies/search [get] -func (h *Handler) SearchCompanies(c echo.Context) error { - form, err := response.Parse[CompanySearchForm](c) +// @Router /admin/v1/companies [get] +func (h *Handler) Search(c echo.Context) error { + form, err := response.Parse[SearchForm](c) if err != nil { 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 { - 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 // @Description Update company account status (active, inactive, suspended, pending) // @Tags Admin-Companies // @Accept json // @Produce json // @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" // @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" // @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" // @Security BearerAuth // @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") - form, err := response.Parse[UpdateCompanyStatusForm](c) + form, err := response.Parse[StatusForm](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.UpdateCompanyStatus(c.Request().Context(), id, form, &updatedBy) + err = h.service.UpdateStatus(c.Request().Context(), id, form) if err != nil { if err.Error() == "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") } -// UpdateCompanyVerification updates company verification status (Web Panel) +// UpdateVerification updates company verification status (Web Panel) // @Summary Update company verification status // @Description Update company verification and compliance status // @Tags Admin-Companies // @Accept json // @Produce json // @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" // @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" // @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" // @Security BearerAuth // @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") - form, err := response.Parse[UpdateCompanyVerificationForm](c) + form, err := response.Parse[VerificationForm](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.UpdateCompanyVerification(c.Request().Context(), id, form, &updatedBy) + err = h.service.UpdateVerification(c.Request().Context(), id, form) if err != nil { if err.Error() == "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", }, "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") -} diff --git a/internal/company/repository.go b/internal/company/repository.go index 69570f0..3bc4c15 100644 --- a/internal/company/repository.go +++ b/internal/company/repository.go @@ -5,70 +5,38 @@ import ( "errors" "time" "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/primitive" ) // Repository defines the interface for company data operations type Repository interface { Create(ctx context.Context, company *Company) error + Update(ctx context.Context, company *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) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Company, error) GetByTaxID(ctx context.Context, taxID string) (*Company, error) - GetByIDs(ctx context.Context, ids []string) ([]*Company, error) - Update(ctx context.Context, company *Company) error + Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) ([]Company, int64, error) Delete(ctx context.Context, id string) error - List(ctx context.Context, limit, offset int) ([]*Company, error) - Search(ctx context.Context, search string, companyType *string, status *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, cpvCodes []string, categories []string, keywords []string, specializations []string, employeeCountMin *int, employeeCountMax *int, annualRevenueMin *float64, annualRevenueMax *float64, foundedYearMin *int, foundedYearMax *int, limit, offset int, sortBy, sortOrder string) ([]*Company, error) - Count(ctx context.Context) (int64, error) - CountByType(ctx context.Context, companyType CompanyType) (int64, error) - CountByStatus(ctx context.Context, status CompanyStatus) (int64, error) - CountByIndustry(ctx context.Context, industry string) (int64, error) - CountVerified(ctx context.Context) (int64, error) UpdateStatus(ctx context.Context, id string, status CompanyStatus) error UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error - UpdateTags(ctx context.Context, id string, tags *CompanyTags) error - AddTags(ctx context.Context, id string, cpvCodes []string, categories []string, keywords []string, specializations []string, certifications []string) error - RemoveTags(ctx context.Context, id string, cpvCodes []string, categories []string, keywords []string, specializations []string, certifications []string) error - GetCompanyStats(ctx context.Context) (*CompanyStatsResponse, error) - SearchByTags(ctx context.Context, cpvCodes []string, categories []string, keywords []string, specializations []string, limit, offset int) ([]*Company, error) } // companyRepository implements the Repository interface using the MongoDB ORM type companyRepository struct { - ormRepo mongo.Repository[Company] + ormRepo orm.Repository[Company] logger logger.Logger } // 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 - indexes := []mongo.Index{ - *mongo.CreateUniqueIndex("name_idx", bson.D{{Key: "name", Value: 1}}), - *mongo.CreateUniqueIndex("registration_number_idx", bson.D{{Key: "registration_number", Value: 1}}), - *mongo.CreateUniqueIndex("tax_id_idx", bson.D{{Key: "tax_id", Value: 1}}), - *mongo.NewIndex("type_idx", bson.D{{Key: "type", Value: 1}}), - *mongo.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}), - *mongo.NewIndex("industry_idx", bson.D{{Key: "industry", Value: 1}}), - *mongo.NewIndex("is_verified_idx", bson.D{{Key: "is_verified", Value: 1}}), - *mongo.NewIndex("is_compliant_idx", bson.D{{Key: "is_compliant", Value: 1}}), - *mongo.NewIndex("language_idx", bson.D{{Key: "language", Value: 1}}), - *mongo.NewIndex("currency_idx", bson.D{{Key: "currency", Value: 1}}), - *mongo.NewIndex("employee_count_idx", bson.D{{Key: "employee_count", Value: 1}}), - *mongo.NewIndex("annual_revenue_idx", bson.D{{Key: "annual_revenue", Value: 1}}), - *mongo.NewIndex("founded_year_idx", bson.D{{Key: "founded_year", Value: 1}}), - *mongo.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}), - // Tag indexes for efficient search - *mongo.NewIndex("cpv_codes_idx", bson.D{{Key: "tags.cpv_codes", Value: 1}}), - *mongo.NewIndex("categories_idx", bson.D{{Key: "tags.categories", Value: 1}}), - *mongo.NewIndex("keywords_idx", bson.D{{Key: "tags.keywords", Value: 1}}), - *mongo.NewIndex("specializations_idx", bson.D{{Key: "tags.specializations", Value: 1}}), - *mongo.NewIndex("certifications_idx", bson.D{{Key: "tags.certifications", Value: 1}}), - // Text index for search - *mongo.CreateTextIndex("search_idx", "name", "description", "industry"), + indexes := []orm.Index{ + *orm.NewIndex("registration_number_idx", bson.D{{Key: "registration_number", Value: 1}}), } // Create indexes @@ -80,7 +48,7 @@ func NewCompanyRepository(mongoManager *mongo.ConnectionManager, logger logger.L } // Create ORM repository - ormRepo := mongo.NewRepository[Company](mongoManager.GetCollection("companies"), logger) + ormRepo := orm.NewRepository[Company](mongoManager.GetCollection("companies"), logger) return &companyRepository{ ormRepo: ormRepo, @@ -93,7 +61,6 @@ func (r *companyRepository) Create(ctx context.Context, company *Company) error // Set created/updated timestamps using Unix timestamps now := time.Now().Unix() company.SetCreatedAt(now) - company.SetUpdatedAt(now) // Use ORM to create company err := r.ormRepo.Create(ctx, company) @@ -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) { company, err := r.ormRepo.FindByID(ctx, id) if err != nil { - if errors.Is(err, mongo.ErrDocumentNotFound) { + if errors.Is(err, orm.ErrDocumentNotFound) { return nil, errors.New("company not found") } 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 } -// GetByName retrieves a company by name -func (r *companyRepository) GetByName(ctx context.Context, name string) (*Company, error) { - filter := bson.M{"name": name} - company, err := r.ormRepo.FindOne(ctx, filter) +// GetByIDs retrieves companies by IDs +func (r *companyRepository) GetByIDs(ctx context.Context, ids []string) ([]Company, error) { + companies, err := r.ormRepo.FindAll(ctx, bson.M{"_id": bson.M{"$in": ids}}, orm.NewPaginationBuilder().Build()) 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 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 } // GetByRegistrationNumber retrieves a company by registration number func (r *companyRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Company, error) { - filter := bson.M{"registration_number": registrationNumber} - company, err := r.ormRepo.FindOne(ctx, filter) + company, err := r.ormRepo.FindOne(ctx, bson.M{"registration_number": registrationNumber}) if err != nil { - if errors.Is(err, mongo.ErrDocumentNotFound) { - return nil, errors.New("company not found") - } - r.logger.Error("Failed to get company by registration number", map[string]interface{}{ - "error": err.Error(), - "registration_number": registrationNumber, - }) return nil, err } - return company, nil } // GetByTaxID retrieves a company by tax ID func (r *companyRepository) GetByTaxID(ctx context.Context, taxID string) (*Company, error) { - filter := bson.M{"tax_id": taxID} - company, err := r.ormRepo.FindOne(ctx, filter) + company, err := r.ormRepo.FindOne(ctx, bson.M{"tax_id": taxID}) if err != nil { - if errors.Is(err, mongo.ErrDocumentNotFound) { - return nil, errors.New("company not found") - } - r.logger.Error("Failed to get company by tax ID", map[string]interface{}{ - "error": err.Error(), - "tax_id": taxID, - }) return nil, err } - return company, nil } -// GetByIDs retrieves multiple companies by their IDs -func (r *companyRepository) GetByIDs(ctx context.Context, ids []string) ([]*Company, error) { - if len(ids) == 0 { - return []*Company{}, nil - } - - // Convert string IDs to ObjectIDs for MongoDB query - objectIDs := make([]interface{}, len(ids)) - for i, id := range ids { - objectID, err := primitive.ObjectIDFromHex(id) - if err != nil { - r.logger.Warn("Invalid company ID format", map[string]interface{}{ - "id": id, - "error": err.Error(), - }) - continue - } - objectIDs[i] = objectID - } - - if len(objectIDs) == 0 { - return []*Company{}, nil - } - - filter := bson.M{"_id": bson.M{"$in": objectIDs}} - companies, err := r.ormRepo.FindMany(ctx, filter, len(ids)) - if err != nil { - r.logger.Error("Failed to get companies by IDs", map[string]interface{}{ - "error": err.Error(), - "ids": ids, - }) - return nil, err - } - - // Convert to pointer slice - result := make([]*Company, len(companies)) - for i := range companies { - result[i] = &companies[i] - } - - r.logger.Info("Retrieved companies by IDs", map[string]interface{}{ - "requested_count": len(ids), - "found_count": len(result), - }) - - return result, nil -} - // Update updates a company func (r *companyRepository) Update(ctx context.Context, company *Company) error { // Set updated timestamp using Unix timestamp @@ -257,20 +159,16 @@ func (r *companyRepository) Update(ctx context.Context, company *Company) error 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 { // Get company first - company, err := r.GetByID(ctx, id) + _, err := r.GetByID(ctx, id) if err != nil { return err } - // Update status to inactive - company.Status = CompanyStatusInactive - company.SetUpdatedAt(time.Now().Unix()) - // Update in database - err = r.ormRepo.Update(ctx, company) + err = r.ormRepo.Delete(ctx, id) if err != nil { r.logger.Error("Failed to delete company", map[string]interface{}{ "error": err.Error(), @@ -286,246 +184,122 @@ func (r *companyRepository) Delete(ctx context.Context, id string) error { return nil } -// List retrieves companies with pagination -func (r *companyRepository) List(ctx context.Context, limit, offset int) ([]*Company, error) { - // Build pagination - pagination := mongo.NewPaginationBuilder(). - Limit(limit). - Skip(offset). - SortDesc("created_at"). - Build() - - // Only active companies by default - filter := bson.M{"status": bson.M{"$ne": CompanyStatusInactive}} - - // Use ORM to find companies - result, err := r.ormRepo.FindAll(ctx, filter, pagination) - if err != nil { - r.logger.Error("Failed to list companies", map[string]interface{}{ - "error": err.Error(), - "limit": limit, - "offset": offset, - }) - return nil, err - } - - // Convert []Company to []*Company - companies := make([]*Company, len(result.Items)) - for i := range result.Items { - companies[i] = &result.Items[i] - } - - return companies, nil -} - // Search retrieves companies with search and filters -func (r *companyRepository) Search(ctx context.Context, search string, companyType *string, status *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, cpvCodes []string, categories []string, keywords []string, specializations []string, employeeCountMin *int, employeeCountMax *int, annualRevenueMin *float64, annualRevenueMax *float64, foundedYearMin *int, foundedYearMax *int, limit, offset int, sortBy, sortOrder string) ([]*Company, error) { +func (r *companyRepository) Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) ([]Company, int64, error) { // Build filter filter := bson.M{} - if search != "" { - filter["$text"] = bson.M{"$search": search} + if form.Search != nil { + filter["$text"] = bson.M{"$search": *form.Search} } - if companyType != nil { - filter["type"] = *companyType + if form.Type != nil { + filter["type"] = *form.Type } - if status != nil { - filter["status"] = *status + if form.Status != nil { + filter["status"] = *form.Status } - if industry != nil { - filter["industry"] = *industry + if form.Industry != nil { + filter["industry"] = *form.Industry } - if isVerified != nil { - filter["is_verified"] = *isVerified + if form.IsVerified != nil { + filter["is_verified"] = *form.IsVerified } - if isCompliant != nil { - filter["is_compliant"] = *isCompliant + if form.IsCompliant != nil { + filter["is_compliant"] = *form.IsCompliant } - if language != nil { - filter["language"] = *language + if form.Language != nil { + filter["language"] = *form.Language } - if currency != nil { - filter["currency"] = *currency + if form.Currency != nil { + filter["currency"] = *form.Currency } // Tag filters - if len(cpvCodes) > 0 { - filter["tags.cpv_codes"] = bson.M{"$in": cpvCodes} + if len(form.CPVCodes) > 0 { + filter["tags.cpv_codes"] = bson.M{"$in": form.CPVCodes} } - if len(categories) > 0 { - filter["tags.categories"] = bson.M{"$in": categories} + if len(form.Categories) > 0 { + filter["tags.categories"] = bson.M{"$in": form.Categories} } - if len(keywords) > 0 { - filter["tags.keywords"] = bson.M{"$in": keywords} + if len(form.Keywords) > 0 { + filter["tags.keywords"] = bson.M{"$in": form.Keywords} } - if len(specializations) > 0 { - filter["tags.specializations"] = bson.M{"$in": specializations} + if len(form.Specializations) > 0 { + filter["tags.specializations"] = bson.M{"$in": form.Specializations} } // Range filters - if employeeCountMin != nil || employeeCountMax != nil { + if form.EmployeeCountMin != nil || form.EmployeeCountMax != nil { rangeFilter := bson.M{} - if employeeCountMin != nil { - rangeFilter["$gte"] = *employeeCountMin + if form.EmployeeCountMin != nil { + rangeFilter["$gte"] = *form.EmployeeCountMin } - if employeeCountMax != nil { - rangeFilter["$lte"] = *employeeCountMax + if form.EmployeeCountMax != nil { + rangeFilter["$lte"] = *form.EmployeeCountMax } filter["employee_count"] = rangeFilter } - if annualRevenueMin != nil || annualRevenueMax != nil { + if form.AnnualRevenueMin != nil || form.AnnualRevenueMax != nil { rangeFilter := bson.M{} - if annualRevenueMin != nil { - rangeFilter["$gte"] = *annualRevenueMin + if form.AnnualRevenueMin != nil { + rangeFilter["$gte"] = *form.AnnualRevenueMin } - if annualRevenueMax != nil { - rangeFilter["$lte"] = *annualRevenueMax + if form.AnnualRevenueMax != nil { + rangeFilter["$lte"] = *form.AnnualRevenueMax } filter["annual_revenue"] = rangeFilter } - if foundedYearMin != nil || foundedYearMax != nil { + if form.FoundedYearMin != nil || form.FoundedYearMax != nil { rangeFilter := bson.M{} - if foundedYearMin != nil { - rangeFilter["$gte"] = *foundedYearMin + if form.FoundedYearMin != nil { + rangeFilter["$gte"] = *form.FoundedYearMin } - if foundedYearMax != nil { - rangeFilter["$lte"] = *foundedYearMax + if form.FoundedYearMax != nil { + rangeFilter["$lte"] = *form.FoundedYearMax } 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 - pagination := mongo.NewPaginationBuilder(). - Limit(limit). - Skip(offset) + paginationBuilder := orm.NewPaginationBuilder(). + Limit(pagination.Limit). + Skip(pagination.Offset). + SortBy(sort, sortOrder). + Build() - // Set sort - if sortBy != "" { - if sortOrder == "desc" { - pagination.SortDesc(sortBy) - } else { - pagination.SortAsc(sortBy) - } - } else { - pagination.SortDesc("created_at") // Default sort - } - - // Use ORM to find companies - result, err := r.ormRepo.FindAll(ctx, filter, pagination.Build()) + // Use ORM to find users + result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder) if err != nil { - r.logger.Error("Failed to search companies", map[string]interface{}{ - "error": err.Error(), - "search": search, - }) - return nil, err - } - - // Convert []Company to []*Company - companies := make([]*Company, len(result.Items)) - for i := range result.Items { - companies[i] = &result.Items[i] - } - - return companies, nil -} - -// Count counts all companies -func (r *companyRepository) Count(ctx context.Context) (int64, error) { - filter := bson.M{"status": bson.M{"$ne": CompanyStatusInactive}} - - count, err := r.ormRepo.Count(ctx, filter) - if err != nil { - r.logger.Error("Failed to count companies", map[string]interface{}{ + r.logger.Error("Failed to list users", map[string]interface{}{ "error": err.Error(), }) - return 0, err + return nil, 0, err } - return count, nil -} - -// CountByType counts companies by type -func (r *companyRepository) CountByType(ctx context.Context, companyType CompanyType) (int64, error) { - filter := bson.M{ - "type": companyType, - "status": bson.M{"$ne": CompanyStatusInactive}, - } - - count, err := r.ormRepo.Count(ctx, filter) - if err != nil { - r.logger.Error("Failed to count companies by type", map[string]interface{}{ - "error": err.Error(), - "company_type": companyType, - }) - return 0, err - } - - return count, nil -} - -// CountByStatus counts companies by status -func (r *companyRepository) CountByStatus(ctx context.Context, status CompanyStatus) (int64, error) { - filter := bson.M{"status": status} - - count, err := r.ormRepo.Count(ctx, filter) - if err != nil { - r.logger.Error("Failed to count companies by status", map[string]interface{}{ - "error": err.Error(), - "status": status, - }) - return 0, err - } - - return count, nil -} - -// CountByIndustry counts companies by industry -func (r *companyRepository) CountByIndustry(ctx context.Context, industry string) (int64, error) { - filter := bson.M{ - "industry": industry, - "status": bson.M{"$ne": CompanyStatusInactive}, - } - - count, err := r.ormRepo.Count(ctx, filter) - if err != nil { - r.logger.Error("Failed to count companies by industry", map[string]interface{}{ - "error": err.Error(), - "industry": industry, - }) - return 0, err - } - - return count, nil -} - -// CountVerified counts verified companies -func (r *companyRepository) CountVerified(ctx context.Context) (int64, error) { - filter := bson.M{ - "is_verified": true, - "status": bson.M{"$ne": CompanyStatusInactive}, - } - - count, err := r.ormRepo.Count(ctx, filter) - if err != nil { - r.logger.Error("Failed to count verified companies", map[string]interface{}{ - "error": err.Error(), - }) - return 0, err - } - - return count, nil + return result.Items, result.TotalCount, nil } // UpdateStatus updates company status @@ -591,226 +365,3 @@ func (r *companyRepository) UpdateVerification(ctx context.Context, id string, i 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 -} diff --git a/internal/company/service.go b/internal/company/service.go index 04b5b56..0b2bca2 100644 --- a/internal/company/service.go +++ b/internal/company/service.go @@ -3,46 +3,29 @@ package company import ( "context" "errors" - "time" "tm/pkg/logger" + "tm/pkg/response" ) // Service defines business logic for company operations type Service interface { // Core company operations - CreateCompany(ctx context.Context, form *CreateCompanyForm, createdBy *string) (*Company, error) - GetCompanyByID(ctx context.Context, id string) (*Company, error) - GetCompaniesByIDs(ctx context.Context, ids []string) ([]*Company, error) - UpdateCompany(ctx context.Context, id string, form *UpdateCompanyForm, updatedBy *string) (*Company, error) - DeleteCompany(ctx context.Context, id string, deletedBy *string) error + Create(ctx context.Context, form *CompanyForm) (*CompanyResponse, error) + Update(ctx context.Context, id string, form *CompanyForm) (*CompanyResponse, error) + GetByID(ctx context.Context, id string) (*CompanyResponse, error) + GetByIDs(ctx context.Context, ids []string) ([]*CompanyResponse, error) + Delete(ctx context.Context, id string) error // Company listing and search - ListCompanies(ctx context.Context, form *ListCompaniesForm) (*CompanyListResponse, error) - SearchCompanies(ctx context.Context, form *CompanySearchForm) (*CompanyListResponse, error) - SearchCompaniesByTags(ctx context.Context, cpvCodes []string, categories []string, keywords []string, specializations []string, limit, offset int) ([]*Company, error) + SearchCompanies(ctx context.Context, form *SearchForm, pagination *response.Pagination) (*CompanyListResponse, error) // Company management - UpdateCompanyStatus(ctx context.Context, id string, form *UpdateCompanyStatusForm, updatedBy *string) error - UpdateCompanyVerification(ctx context.Context, id string, form *UpdateCompanyVerificationForm, updatedBy *string) error + UpdateStatus(ctx context.Context, id string, form *StatusForm) error + UpdateVerification(ctx context.Context, id string, form *VerificationForm) error - // Tag management - UpdateCompanyTags(ctx context.Context, id string, form *UpdateCompanyTagsForm, updatedBy *string) error - AddTags(ctx context.Context, id string, form *AddTagsForm, updatedBy *string) error - RemoveTags(ctx context.Context, id string, form *RemoveTagsForm, updatedBy *string) error - - // Business operations - VerifyCompany(ctx context.Context, id string, verifiedBy *string) error - SuspendCompany(ctx context.Context, id string, reason string, suspendedBy *string) error - ActivateCompany(ctx context.Context, id string, activatedBy *string) error - - // Statistics and analytics - GetCompanyStats(ctx context.Context) (*CompanyStatsResponse, error) - - // Search and filtering helpers - GetCompaniesByType(ctx context.Context, companyType CompanyType, limit, offset int) ([]*Company, int64, error) - GetCompaniesByStatus(ctx context.Context, status CompanyStatus, limit, offset int) ([]*Company, int64, error) - GetCompaniesByIndustry(ctx context.Context, industry string, limit, offset int) ([]*Company, int64, error) + // Get my company + GetMyCompany(ctx context.Context, id string) (*CompanyProfileResponse, error) } // companyService implements the Service interface @@ -59,8 +42,8 @@ func NewCompanyService(repository Repository, logger logger.Logger) Service { } } -// CreateCompany creates a new company -func (s *companyService) CreateCompany(ctx context.Context, form *CreateCompanyForm, createdBy *string) (*Company, error) { +// Create creates a new company +func (s *companyService) Create(ctx context.Context, form *CompanyForm) (*CompanyResponse, error) { // Check if company name already exists existingCompany, _ := s.repository.GetByName(ctx, form.Name) if existingCompany != nil { @@ -83,7 +66,7 @@ func (s *companyService) CreateCompany(ctx context.Context, form *CreateCompanyF company := &Company{ Name: form.Name, Type: CompanyType(form.Type), - Status: CompanyStatusPending, + Status: CompanyStatusActive, RegistrationNumber: form.RegistrationNumber, TaxID: form.TaxID, Industry: form.Industry, @@ -93,7 +76,6 @@ func (s *companyService) CreateCompany(ctx context.Context, form *CreateCompanyF AnnualRevenue: form.AnnualRevenue, FoundedYear: form.FoundedYear, Address: s.convertAddressForm(form.Address), - ContactPerson: s.convertContactPersonForm(form.ContactPerson), Phone: form.Phone, Email: form.Email, Tags: s.convertCompanyTagsForm(form.Tags), @@ -102,7 +84,6 @@ func (s *companyService) CreateCompany(ctx context.Context, form *CreateCompanyF Language: s.getDefaultLanguage(form.Language), Currency: s.getDefaultCurrency(form.Currency), Timezone: s.getDefaultTimezone(form.Timezone), - CreatedBy: createdBy, } // Save to database @@ -120,14 +101,13 @@ func (s *companyService) CreateCompany(ctx context.Context, form *CreateCompanyF "company_id": company.ID, "name": company.Name, "type": company.Type, - "created_by": createdBy, }) - return company, nil + return company.ToResponse(), nil } // 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) if err != nil { 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, }) - return company, nil -} - -// GetCompaniesByIDs retrieves multiple companies by their IDs -func (s *companyService) GetCompaniesByIDs(ctx context.Context, ids []string) ([]*Company, error) { - if len(ids) == 0 { - return []*Company{}, nil - } - - companies, err := s.repository.GetByIDs(ctx, ids) - if err != nil { - s.logger.Error("Failed to get companies by IDs", map[string]interface{}{ - "error": err.Error(), - "ids": ids, - }) - return nil, err - } - - s.logger.Info("Companies retrieved successfully", map[string]interface{}{ - "requested_count": len(ids), - "found_count": len(companies), - }) - - return companies, nil + return company.ToResponse(), nil } // 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 company, err := s.repository.GetByID(ctx, id) 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) - if form.Name != nil && *form.Name != company.Name { - existingCompany, _ := s.repository.GetByName(ctx, *form.Name) + if form.Name != "" && form.Name != company.Name { + existingCompany, _ := s.repository.GetByName(ctx, form.Name) if existingCompany != nil { return nil, errors.New("company with this name already exists") } - company.Name = *form.Name + company.Name = form.Name } // Check if registration number already exists (if changing) - if form.RegistrationNumber != nil && *form.RegistrationNumber != company.RegistrationNumber { - existingCompany, _ := s.repository.GetByRegistrationNumber(ctx, *form.RegistrationNumber) + if form.RegistrationNumber != "" && form.RegistrationNumber != company.RegistrationNumber { + existingCompany, _ := s.repository.GetByRegistrationNumber(ctx, form.RegistrationNumber) if existingCompany != nil { return nil, errors.New("company with this registration number already exists") } - company.RegistrationNumber = *form.RegistrationNumber + company.RegistrationNumber = form.RegistrationNumber } // Check if tax ID already exists (if changing) - if form.TaxID != nil && *form.TaxID != company.TaxID { - existingCompany, _ := s.repository.GetByTaxID(ctx, *form.TaxID) + if form.TaxID != "" && form.TaxID != company.TaxID { + existingCompany, _ := s.repository.GetByTaxID(ctx, form.TaxID) if existingCompany != nil { return nil, errors.New("company with this tax ID already exists") } - company.TaxID = *form.TaxID + company.TaxID = form.TaxID } // Update fields if provided - if form.Type != nil { - company.Type = CompanyType(*form.Type) + if form.Type != "" { + company.Type = CompanyType(form.Type) } - if form.Industry != nil { - company.Industry = *form.Industry + if form.Industry != "" { + company.Industry = form.Industry } 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) } - if form.ContactPerson != nil { - company.ContactPerson = s.convertContactPersonForm(form.ContactPerson) - } - if form.Phone != nil { company.Phone = form.Phone } @@ -268,10 +221,6 @@ func (s *companyService) UpdateCompany(ctx context.Context, id string, form *Upd company.Timezone = *form.Timezone } - // Set updated by - company.UpdatedBy = updatedBy - company.UpdatedAt = time.Now().Unix() - // Update in database err = s.repository.Update(ctx, company) if err != nil { @@ -285,25 +234,15 @@ func (s *companyService) UpdateCompany(ctx context.Context, id string, form *Upd s.logger.Info("Company updated successfully", map[string]interface{}{ "company_id": company.ID, "name": company.Name, - "updated_by": updatedBy, }) - return company, nil + return company.ToResponse(), nil } // DeleteCompany deletes a company (soft delete) -func (s *companyService) DeleteCompany(ctx context.Context, id string, deletedBy *string) error { - // Get company to check if exists - company, err := s.repository.GetByID(ctx, id) - if err != nil { - return errors.New("company not found") - } - - // Set updated by before deletion - company.UpdatedBy = deletedBy - +func (s *companyService) Delete(ctx context.Context, id string) error { // Delete company (soft delete) - err = s.repository.Delete(ctx, id) + err := s.repository.Delete(ctx, id) if err != nil { s.logger.Error("Failed to delete company", map[string]interface{}{ "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{}{ "company_id": id, - "deleted_by": deletedBy, }) return nil } -// ListCompanies lists companies with search and filters -func (s *companyService) ListCompanies(ctx context.Context, form *ListCompaniesForm) (*CompanyListResponse, error) { - // Set defaults - limit := 20 - if form.Limit != nil { - limit = *form.Limit - } - - offset := 0 - if form.Offset != nil { - offset = *form.Offset - } - - search := "" - if form.Search != nil { - search = *form.Search - } - - sortBy := "created_at" - if form.SortBy != nil { - sortBy = *form.SortBy - } - - sortOrder := "desc" - if form.SortOrder != nil { - sortOrder = *form.SortOrder - } - - // Get companies - companies, err := s.repository.Search(ctx, search, form.Type, form.Status, form.Industry, form.IsVerified, form.IsCompliant, form.Language, form.Currency, form.CPVCodes, form.Categories, form.Keywords, form.Specializations, form.EmployeeCountMin, form.EmployeeCountMax, form.AnnualRevenueMin, form.AnnualRevenueMax, form.FoundedYearMin, form.FoundedYearMax, limit, offset, sortBy, sortOrder) - if err != nil { - s.logger.Error("Failed to list companies", map[string]interface{}{ - "error": err.Error(), - }) - return nil, errors.New("failed to list companies") - } - - // Convert to responses - var companyResponses []*CompanyResponse - for _, company := range companies { - companyResponses = append(companyResponses, company.ToResponse()) - } - - // TODO: Implement proper count for pagination metadata - total := int64(len(companies)) // This should be a separate count query - totalPages := (total + int64(limit) - 1) / int64(limit) - - return &CompanyListResponse{ - Companies: companyResponses, - Total: total, - Limit: limit, - Offset: offset, - TotalPages: int(totalPages), - }, nil -} - // SearchCompanies searches companies with advanced filters -func (s *companyService) SearchCompanies(ctx context.Context, form *CompanySearchForm) (*CompanyListResponse, error) { - // Set defaults - limit := 20 - if form.Limit != nil { - limit = *form.Limit - } - - offset := 0 - if form.Offset != nil { - offset = *form.Offset - } - - query := "" - if form.Query != nil { - query = *form.Query - } +func (s *companyService) SearchCompanies(ctx context.Context, form *SearchForm, pagination *response.Pagination) (*CompanyListResponse, error) { // 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 { s.logger.Error("Failed to search companies", map[string]interface{}{ "error": err.Error(), - "query": query, }) 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()) } - // 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), + Companies: companyResponses, + Meta: pagination.Response(total), }, nil } -// SearchCompaniesByTags searches companies by tags -func (s *companyService) SearchCompaniesByTags(ctx context.Context, cpvCodes []string, categories []string, keywords []string, specializations []string, limit, offset int) ([]*Company, error) { - companies, err := s.repository.SearchByTags(ctx, cpvCodes, categories, keywords, specializations, limit, offset) +// GetByIDs retrieves companies by IDs +func (s *companyService) GetByIDs(ctx context.Context, ids []string) ([]*CompanyResponse, error) { + companies, err := s.repository.GetByIDs(ctx, ids) if err != nil { - s.logger.Error("Failed to search companies by tags", map[string]interface{}{ - "error": err.Error(), - }) - return nil, errors.New("failed to search companies by tags") + return nil, errors.New("failed to get companies by IDs") } - return companies, nil + var companyResponses []*CompanyResponse + for _, company := range companies { + companyResponses = append(companyResponses, company.ToResponse()) + } + + return companyResponses, nil } // 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 _, err := s.repository.GetByID(ctx, id) if err != nil { @@ -445,13 +306,12 @@ func (s *companyService) UpdateCompanyStatus(ctx context.Context, id string, for } // Update status - status := CompanyStatus(form.Status) - err = s.repository.UpdateStatus(ctx, id, status) + err = s.repository.UpdateStatus(ctx, id, CompanyStatus(form.Status)) if err != nil { s.logger.Error("Failed to update company status", map[string]interface{}{ "error": err.Error(), "company_id": id, - "status": form.Status, + "status": CompanyStatus(form.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{}{ "company_id": id, "status": form.Status, - "updated_by": updatedBy, }) return nil } // UpdateCompanyVerification updates company verification status -func (s *companyService) UpdateCompanyVerification(ctx context.Context, id string, form *UpdateCompanyVerificationForm, updatedBy *string) error { +func (s *companyService) UpdateVerification(ctx context.Context, id string, form *VerificationForm) error { // Get company to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { @@ -487,258 +346,19 @@ func (s *companyService) UpdateCompanyVerification(ctx context.Context, id strin "company_id": id, "is_verified": form.IsVerified, "is_compliant": form.IsCompliant, - "updated_by": updatedBy, }) return nil } -// UpdateCompanyTags updates company tags -func (s *companyService) UpdateCompanyTags(ctx context.Context, id string, form *UpdateCompanyTagsForm, updatedBy *string) error { - // Get company to check if exists - _, err := s.repository.GetByID(ctx, id) - if err != nil { - return errors.New("company not found") - } - - // Update tags - tags := s.convertCompanyTagsForm(form.Tags) - err = s.repository.UpdateTags(ctx, id, tags) - if err != nil { - s.logger.Error("Failed to update company tags", map[string]interface{}{ - "error": err.Error(), - "company_id": id, - }) - return errors.New("failed to update company tags") - } - - s.logger.Info("Company tags updated successfully", map[string]interface{}{ - "company_id": id, - "updated_by": updatedBy, - }) - - return nil -} - -// AddTags adds tags to a company -func (s *companyService) AddTags(ctx context.Context, id string, form *AddTagsForm, updatedBy *string) error { - // Get company to check if exists - _, err := s.repository.GetByID(ctx, id) - if err != nil { - return errors.New("company not found") - } - - // Add tags - err = s.repository.AddTags(ctx, id, form.CPVCodes, form.Categories, form.Keywords, form.Specializations, form.Certifications) - if err != nil { - s.logger.Error("Failed to add tags to company", map[string]interface{}{ - "error": err.Error(), - "company_id": id, - }) - return errors.New("failed to add tags to company") - } - - s.logger.Info("Tags added to company successfully", map[string]interface{}{ - "company_id": id, - "updated_by": updatedBy, - }) - - return nil -} - -// RemoveTags removes tags from a company -func (s *companyService) RemoveTags(ctx context.Context, id string, form *RemoveTagsForm, updatedBy *string) error { - // Get company to check if exists - _, err := s.repository.GetByID(ctx, id) - if err != nil { - return errors.New("company not found") - } - - // Remove tags - err = s.repository.RemoveTags(ctx, id, form.CPVCodes, form.Categories, form.Keywords, form.Specializations, form.Certifications) - if err != nil { - s.logger.Error("Failed to remove tags from company", map[string]interface{}{ - "error": err.Error(), - "company_id": id, - }) - return errors.New("failed to remove tags from company") - } - - s.logger.Info("Tags removed from company successfully", map[string]interface{}{ - "company_id": id, - "updated_by": updatedBy, - }) - - return nil -} - -// VerifyCompany verifies a company -func (s *companyService) VerifyCompany(ctx context.Context, id string, verifiedBy *string) error { - // Get company to check if exists +// GetMyCompany retrieves the company for the authenticated user +func (s *companyService) GetMyCompany(ctx context.Context, id string) (*CompanyProfileResponse, error) { company, err := s.repository.GetByID(ctx, id) if err != nil { - return errors.New("company not found") + return nil, errors.New("company not found") } - // Update verification - err = s.repository.UpdateVerification(ctx, id, true, company.IsCompliant, company.ComplianceNotes) - if err != nil { - s.logger.Error("Failed to verify company", map[string]interface{}{ - "error": err.Error(), - "company_id": id, - }) - return errors.New("failed to verify company") - } - - s.logger.Info("Company verified successfully", map[string]interface{}{ - "company_id": id, - "verified_by": verifiedBy, - }) - - return nil -} - -// SuspendCompany suspends a company -func (s *companyService) SuspendCompany(ctx context.Context, id string, reason string, suspendedBy *string) error { - // Get company to check if exists - _, err := s.repository.GetByID(ctx, id) - if err != nil { - return errors.New("company not found") - } - - // Update status to suspended - err = s.repository.UpdateStatus(ctx, id, CompanyStatusSuspended) - if err != nil { - s.logger.Error("Failed to suspend company", map[string]interface{}{ - "error": err.Error(), - "company_id": id, - }) - return errors.New("failed to suspend company") - } - - s.logger.Info("Company suspended successfully", map[string]interface{}{ - "company_id": id, - "reason": reason, - "suspended_by": suspendedBy, - }) - - return nil -} - -// ActivateCompany activates a company -func (s *companyService) ActivateCompany(ctx context.Context, id string, activatedBy *string) error { - // Get company to check if exists - _, err := s.repository.GetByID(ctx, id) - if err != nil { - return errors.New("company not found") - } - - // Update status to active - err = s.repository.UpdateStatus(ctx, id, CompanyStatusActive) - if err != nil { - s.logger.Error("Failed to activate company", map[string]interface{}{ - "error": err.Error(), - "company_id": id, - }) - return errors.New("failed to activate company") - } - - s.logger.Info("Company activated successfully", map[string]interface{}{ - "company_id": id, - "activated_by": activatedBy, - }) - - return nil -} - -// GetCompanyStats returns company statistics -func (s *companyService) GetCompanyStats(ctx context.Context) (*CompanyStatsResponse, error) { - stats, err := s.repository.GetCompanyStats(ctx) - if err != nil { - s.logger.Error("Failed to get company stats", map[string]interface{}{ - "error": err.Error(), - }) - return nil, errors.New("failed to get company statistics") - } - - return stats, nil -} - -// GetCompaniesByType retrieves companies by type with pagination -func (s *companyService) GetCompaniesByType(ctx context.Context, companyType CompanyType, limit, offset int) ([]*Company, int64, error) { - // Use search method with type filter - typeStr := string(companyType) - companies, err := s.repository.Search(ctx, "", &typeStr, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, limit, offset, "created_at", "desc") - if err != nil { - s.logger.Error("Failed to get companies by type", map[string]interface{}{ - "error": err.Error(), - "company_type": companyType, - }) - return nil, 0, errors.New("failed to get companies by type") - } - - // Get total count - total, err := s.repository.CountByType(ctx, companyType) - if err != nil { - s.logger.Error("Failed to count companies by type", map[string]interface{}{ - "error": err.Error(), - "company_type": companyType, - }) - return companies, 0, nil // Return companies even if count fails - } - - return companies, total, nil -} - -// GetCompaniesByStatus retrieves companies by status with pagination -func (s *companyService) GetCompaniesByStatus(ctx context.Context, status CompanyStatus, limit, offset int) ([]*Company, int64, error) { - // Use search method with status filter - statusStr := string(status) - companies, err := s.repository.Search(ctx, "", nil, &statusStr, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, limit, offset, "created_at", "desc") - if err != nil { - s.logger.Error("Failed to get companies by status", map[string]interface{}{ - "error": err.Error(), - "status": status, - }) - return nil, 0, errors.New("failed to get companies by status") - } - - // Get total count - total, err := s.repository.CountByStatus(ctx, status) - if err != nil { - s.logger.Error("Failed to count companies by status", map[string]interface{}{ - "error": err.Error(), - "status": status, - }) - return companies, 0, nil - } - - return companies, total, nil -} - -// GetCompaniesByIndustry retrieves companies by industry with pagination -func (s *companyService) GetCompaniesByIndustry(ctx context.Context, industry string, limit, offset int) ([]*Company, int64, error) { - // Use search method with industry filter - companies, err := s.repository.Search(ctx, "", nil, nil, &industry, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, limit, offset, "created_at", "desc") - if err != nil { - s.logger.Error("Failed to get companies by industry", map[string]interface{}{ - "error": err.Error(), - "industry": industry, - }) - return nil, 0, errors.New("failed to get companies by industry") - } - - // Get total count - total, err := s.repository.CountByIndustry(ctx, industry) - if err != nil { - s.logger.Error("Failed to count companies by industry", map[string]interface{}{ - "error": err.Error(), - "industry": industry, - }) - return companies, 0, nil - } - - return companies, total, nil + return company.ToProfileResponse(), nil } // 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 { if form == nil { return nil diff --git a/internal/customer/form.go b/internal/customer/form.go index 1f124b6..78b4a67 100644 --- a/internal/customer/form.go +++ b/internal/customer/form.go @@ -41,7 +41,7 @@ type SearchCustomersForm struct { // CustomerListResponse represents the response for listing customers type CustomerListResponse struct { Customers []*CustomerResponse `json:"customers"` - Meta *response.Meta `json:"meta"` + Meta *response.Meta `json:"-"` } type LoginForm struct { diff --git a/internal/customer/handler.go b/internal/customer/handler.go index ef40f71..230b77d 100644 --- a/internal/customer/handler.go +++ b/internal/customer/handler.go @@ -192,7 +192,7 @@ func (h *Handler) Search(c echo.Context) error { 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) diff --git a/internal/customer/service.go b/internal/customer/service.go index 25acb04..4f2b5b1 100644 --- a/internal/customer/service.go +++ b/internal/customer/service.go @@ -62,7 +62,7 @@ func (s *customerService) Register(ctx context.Context, form *CreateCustomerForm var companyIDs []string if len(form.CompanyIDs) > 0 { for _, companyID := range form.CompanyIDs { - _, err := s.companyService.GetCompanyByID(ctx, companyID) + _, err := s.companyService.GetByID(ctx, companyID) if err != nil { s.logger.Error("Invalid company ID provided", map[string]interface{}{ "error": err.Error(), @@ -210,7 +210,7 @@ func (s *customerService) Update(ctx context.Context, id string, form *UpdateCus // Verify that all company IDs exist var validCompanyIDs []string for _, companyID := range form.CompanyIDs { - _, err := s.companyService.GetCompanyByID(ctx, companyID) + _, err := s.companyService.GetByID(ctx, companyID) if err != nil { s.logger.Error("Invalid company ID provided in update", map[string]interface{}{ "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 var validCompanyIDs []string for _, companyID := range companyIDs { - _, err = s.companyService.GetCompanyByID(ctx, companyID) + _, err = s.companyService.GetByID(ctx, companyID) if err != nil { s.logger.Error("Company not found during assignment", map[string]interface{}{ "error": err.Error(), @@ -695,7 +695,7 @@ func (s *customerService) loadCompaniesForCustomer(ctx context.Context, customer } // 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 { s.logger.Error("Failed to load companies for customer", map[string]interface{}{ "error": err.Error(), @@ -709,7 +709,7 @@ func (s *customerService) loadCompaniesForCustomer(ctx context.Context, customer var companySummaries []*CompanySummary for _, company := range companies { companySummaries = append(companySummaries, &CompanySummary{ - ID: company.ID.Hex(), + ID: company.ID, Name: company.Name, }) } diff --git a/internal/tender/service.go b/internal/tender/service.go index 82b069e..3c7c273 100644 --- a/internal/tender/service.go +++ b/internal/tender/service.go @@ -173,7 +173,7 @@ func (s *tenderService) ListTenders(ctx context.Context, req ListTendersRequest) } // 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 { s.logger.Error("Failed to get company for match calculation", map[string]interface{}{ "company_id": *req.CompanyID, @@ -234,7 +234,7 @@ func (s *tenderService) RecommendTenders(ctx context.Context, companyID *string, limit = 100 } - company, err := s.companyService.GetCompanyByID(ctx, *companyID) + company, err := s.companyService.GetByID(ctx, *companyID) if err != nil { s.logger.Error("Failed to get company", map[string]interface{}{ "company_id": *companyID, diff --git a/internal/user/form.go b/internal/user/form.go index 71b4d66..ef1ed9b 100644 --- a/internal/user/form.go +++ b/internal/user/form.go @@ -109,7 +109,7 @@ type AuthResponse struct { type UserListResponse struct { Users []*UserResponse `json:"users"` - Meta *response.Meta `json:"meta"` + Meta *response.Meta `json:"-"` } type SearchUsersForm struct { diff --git a/internal/user/handler.go b/internal/user/handler.go index c7fd111..8b35258 100644 --- a/internal/user/handler.go +++ b/internal/user/handler.go @@ -316,7 +316,7 @@ func (h *Handler) ListUsers(c echo.Context) error { 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) diff --git a/pkg/response/response.go b/pkg/response/response.go index 722ea49..240c8a9 100644 --- a/pkg/response/response.go +++ b/pkg/response/response.go @@ -12,8 +12,9 @@ import ( type APIResponse struct { Success bool `json:"success"` Message string `json:"message,omitempty"` - Data interface{} `json:",inline"` + Data interface{} `json:"data,omitempty"` Error *APIError `json:"error,omitempty"` + Meta *Meta `json:"meta,omitempty"` } // APIError represents error details in API response @@ -54,6 +55,7 @@ func SuccessWithMeta(c echo.Context, data interface{}, meta *Meta, message strin Success: true, Message: message, Data: data, + Meta: meta, }) }