Refactor Tender Management API Endpoints and Update Documentation
- Renamed and restructured tender-related API endpoints for improved clarity and consistency, including changing the route from `/admin/tenders` to `/admin/v1/companies` and updating the associated methods. - Introduced a new SearchForm structure for listing tenders with advanced filtering capabilities, enhancing the API's search functionality. - Updated the service and repository layers to align with the new endpoint structure, ensuring proper handling of tender data. - Enhanced Swagger and YAML documentation to reflect the changes in endpoint structure, including detailed descriptions and examples for the new search functionality. - Improved error handling and response structures to provide clearer feedback to API consumers, ensuring a more robust and user-friendly experience.
This commit is contained in:
+361
-309
@@ -24,315 +24,6 @@ const docTemplate = `{
|
|||||||
"host": "{{.Host}}",
|
"host": "{{.Host}}",
|
||||||
"basePath": "{{.BasePath}}",
|
"basePath": "{{.BasePath}}",
|
||||||
"paths": {
|
"paths": {
|
||||||
"/admin/tenders": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"BearerAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Retrieve tenders with pagination, filtering, and search capabilities with optional company-based matching",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Admin-Tenders"
|
|
||||||
],
|
|
||||||
"summary": "List tenders",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of items per page (default: 20, max: 100)",
|
|
||||||
"name": "limit",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of items to skip (default: 0)",
|
|
||||||
"name": "offset",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Company ID for match percentage calculation and sorting",
|
|
||||||
"name": "company_id",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Search query for title and description",
|
|
||||||
"name": "query",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Filter by notice type code",
|
|
||||||
"name": "notice_type",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Filter by procurement type code",
|
|
||||||
"name": "procurement_type",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"collectionFormat": "csv",
|
|
||||||
"description": "Filter by country codes (comma-separated)",
|
|
||||||
"name": "country_codes",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"collectionFormat": "csv",
|
|
||||||
"description": "Filter by status (comma-separated)",
|
|
||||||
"name": "status",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"collectionFormat": "csv",
|
|
||||||
"description": "Filter by source (comma-separated)",
|
|
||||||
"name": "source",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "number",
|
|
||||||
"description": "Minimum estimated value",
|
|
||||||
"name": "min_estimated_value",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "number",
|
|
||||||
"description": "Maximum estimated value",
|
|
||||||
"name": "max_estimated_value",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Filter by currency",
|
|
||||||
"name": "currency",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/admin/tenders/{id}": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"BearerAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Retrieve a specific tender by its ID",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Admin-Tenders"
|
|
||||||
],
|
|
||||||
"summary": "Get tender by ID",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Tender ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/tender.TenderResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Not Found",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"put": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"BearerAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Update an existing tender with the provided information",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Admin-Tenders"
|
|
||||||
],
|
|
||||||
"summary": "Update tender",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Tender ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Updated tender information",
|
|
||||||
"name": "tender",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/tender.UpdateTenderRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/tender.TenderResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Not Found",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"BearerAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Delete a tender by its ID",
|
|
||||||
"tags": [
|
|
||||||
"Admin-Tenders"
|
|
||||||
],
|
|
||||||
"summary": "Delete tender",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Tender ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Not Found",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/admin/v1/companies": {
|
"/admin/v1/companies": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -3452,6 +3143,367 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/v1/tenders": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Retrieve tenders with pagination, filtering, and search capabilities with optional company-based matching",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Admin-Tenders"
|
||||||
|
],
|
||||||
|
"summary": "List tenders",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Company ID for match percentage calculation and sorting",
|
||||||
|
"name": "company_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Search query for title and description",
|
||||||
|
"name": "q",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Filter by notice type code",
|
||||||
|
"name": "notice_type",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Filter by procurement type code",
|
||||||
|
"name": "procurement_type",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "csv",
|
||||||
|
"description": "Filter by country codes (comma-separated)",
|
||||||
|
"name": "country_codes",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "csv",
|
||||||
|
"description": "Filter by status (comma-separated)",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "csv",
|
||||||
|
"description": "Filter by source (comma-separated)",
|
||||||
|
"name": "source",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"description": "Minimum estimated value",
|
||||||
|
"name": "min_estimated_value",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"description": "Maximum estimated value",
|
||||||
|
"name": "max_estimated_value",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Filter by currency",
|
||||||
|
"name": "currency",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"description": "Deadline from",
|
||||||
|
"name": "deadline_from",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"description": "Deadline to",
|
||||||
|
"name": "deadline_to",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"description": "Publication date from",
|
||||||
|
"name": "publication_date_from",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"description": "Publication date to",
|
||||||
|
"name": "publication_date_to",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "csv",
|
||||||
|
"description": "Filter by languages (comma-separated)",
|
||||||
|
"name": "languages",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Filter by buyer organization ID",
|
||||||
|
"name": "buyer_organization_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Number of items per page (default: 20, max: 100)",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Number of items to skip (default: 0)",
|
||||||
|
"name": "offset",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Sort by field",
|
||||||
|
"name": "sort_by",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Sort order (asc or desc)",
|
||||||
|
"name": "sort_order",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/admin/v1/tenders/{id}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Retrieve a specific tender by its ID",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Admin-Tenders"
|
||||||
|
],
|
||||||
|
"summary": "Get tender by ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Tender ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/tender.TenderResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Update an existing tender with the provided information",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Admin-Tenders"
|
||||||
|
],
|
||||||
|
"summary": "Update tender",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Tender ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Updated tender information",
|
||||||
|
"name": "tender",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/tender.UpdateTenderRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/tender.TenderResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Delete a tender by its ID",
|
||||||
|
"tags": [
|
||||||
|
"Admin-Tenders"
|
||||||
|
],
|
||||||
|
"summary": "Delete tender",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Tender ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/v1/users": {
|
"/admin/v1/users": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|||||||
+361
-309
@@ -18,315 +18,6 @@
|
|||||||
"host": "localhost:8082",
|
"host": "localhost:8082",
|
||||||
"basePath": "/",
|
"basePath": "/",
|
||||||
"paths": {
|
"paths": {
|
||||||
"/admin/tenders": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"BearerAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Retrieve tenders with pagination, filtering, and search capabilities with optional company-based matching",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Admin-Tenders"
|
|
||||||
],
|
|
||||||
"summary": "List tenders",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of items per page (default: 20, max: 100)",
|
|
||||||
"name": "limit",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of items to skip (default: 0)",
|
|
||||||
"name": "offset",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Company ID for match percentage calculation and sorting",
|
|
||||||
"name": "company_id",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Search query for title and description",
|
|
||||||
"name": "query",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Filter by notice type code",
|
|
||||||
"name": "notice_type",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Filter by procurement type code",
|
|
||||||
"name": "procurement_type",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"collectionFormat": "csv",
|
|
||||||
"description": "Filter by country codes (comma-separated)",
|
|
||||||
"name": "country_codes",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"collectionFormat": "csv",
|
|
||||||
"description": "Filter by status (comma-separated)",
|
|
||||||
"name": "status",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"collectionFormat": "csv",
|
|
||||||
"description": "Filter by source (comma-separated)",
|
|
||||||
"name": "source",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "number",
|
|
||||||
"description": "Minimum estimated value",
|
|
||||||
"name": "min_estimated_value",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "number",
|
|
||||||
"description": "Maximum estimated value",
|
|
||||||
"name": "max_estimated_value",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Filter by currency",
|
|
||||||
"name": "currency",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/admin/tenders/{id}": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"BearerAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Retrieve a specific tender by its ID",
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Admin-Tenders"
|
|
||||||
],
|
|
||||||
"summary": "Get tender by ID",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Tender ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/tender.TenderResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Not Found",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"put": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"BearerAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Update an existing tender with the provided information",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Admin-Tenders"
|
|
||||||
],
|
|
||||||
"summary": "Update tender",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Tender ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Updated tender information",
|
|
||||||
"name": "tender",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/tender.UpdateTenderRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/tender.TenderResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Not Found",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"BearerAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Delete a tender by its ID",
|
|
||||||
"tags": [
|
|
||||||
"Admin-Tenders"
|
|
||||||
],
|
|
||||||
"summary": "Delete tender",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Tender ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Not Found",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/response.APIResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/admin/v1/companies": {
|
"/admin/v1/companies": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -3446,6 +3137,367 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/v1/tenders": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Retrieve tenders with pagination, filtering, and search capabilities with optional company-based matching",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Admin-Tenders"
|
||||||
|
],
|
||||||
|
"summary": "List tenders",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Company ID for match percentage calculation and sorting",
|
||||||
|
"name": "company_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Search query for title and description",
|
||||||
|
"name": "q",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Filter by notice type code",
|
||||||
|
"name": "notice_type",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Filter by procurement type code",
|
||||||
|
"name": "procurement_type",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "csv",
|
||||||
|
"description": "Filter by country codes (comma-separated)",
|
||||||
|
"name": "country_codes",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "csv",
|
||||||
|
"description": "Filter by status (comma-separated)",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "csv",
|
||||||
|
"description": "Filter by source (comma-separated)",
|
||||||
|
"name": "source",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"description": "Minimum estimated value",
|
||||||
|
"name": "min_estimated_value",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"description": "Maximum estimated value",
|
||||||
|
"name": "max_estimated_value",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Filter by currency",
|
||||||
|
"name": "currency",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"description": "Deadline from",
|
||||||
|
"name": "deadline_from",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"description": "Deadline to",
|
||||||
|
"name": "deadline_to",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"description": "Publication date from",
|
||||||
|
"name": "publication_date_from",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"description": "Publication date to",
|
||||||
|
"name": "publication_date_to",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "csv",
|
||||||
|
"description": "Filter by languages (comma-separated)",
|
||||||
|
"name": "languages",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Filter by buyer organization ID",
|
||||||
|
"name": "buyer_organization_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Number of items per page (default: 20, max: 100)",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Number of items to skip (default: 0)",
|
||||||
|
"name": "offset",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Sort by field",
|
||||||
|
"name": "sort_by",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Sort order (asc or desc)",
|
||||||
|
"name": "sort_order",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/admin/v1/tenders/{id}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Retrieve a specific tender by its ID",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Admin-Tenders"
|
||||||
|
],
|
||||||
|
"summary": "Get tender by ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Tender ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/tender.TenderResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Update an existing tender with the provided information",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Admin-Tenders"
|
||||||
|
],
|
||||||
|
"summary": "Update tender",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Tender ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Updated tender information",
|
||||||
|
"name": "tender",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/tender.UpdateTenderRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/tender.TenderResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Delete a tender by its ID",
|
||||||
|
"tags": [
|
||||||
|
"Admin-Tenders"
|
||||||
|
],
|
||||||
|
"summary": "Delete tender",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Tender ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/v1/users": {
|
"/admin/v1/users": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|||||||
+228
-193
@@ -1027,199 +1027,6 @@ info:
|
|||||||
title: Tender Management API
|
title: Tender Management API
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
paths:
|
paths:
|
||||||
/admin/tenders:
|
|
||||||
get:
|
|
||||||
description: Retrieve tenders with pagination, filtering, and search capabilities
|
|
||||||
with optional company-based matching
|
|
||||||
parameters:
|
|
||||||
- description: 'Number of items per page (default: 20, max: 100)'
|
|
||||||
in: query
|
|
||||||
name: limit
|
|
||||||
type: integer
|
|
||||||
- description: 'Number of items to skip (default: 0)'
|
|
||||||
in: query
|
|
||||||
name: offset
|
|
||||||
type: integer
|
|
||||||
- description: Company ID for match percentage calculation and sorting
|
|
||||||
in: query
|
|
||||||
name: company_id
|
|
||||||
type: string
|
|
||||||
- description: Search query for title and description
|
|
||||||
in: query
|
|
||||||
name: query
|
|
||||||
type: string
|
|
||||||
- description: Filter by notice type code
|
|
||||||
in: query
|
|
||||||
name: notice_type
|
|
||||||
type: string
|
|
||||||
- description: Filter by procurement type code
|
|
||||||
in: query
|
|
||||||
name: procurement_type
|
|
||||||
type: string
|
|
||||||
- collectionFormat: csv
|
|
||||||
description: Filter by country codes (comma-separated)
|
|
||||||
in: query
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
name: country_codes
|
|
||||||
type: array
|
|
||||||
- collectionFormat: csv
|
|
||||||
description: Filter by status (comma-separated)
|
|
||||||
in: query
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
name: status
|
|
||||||
type: array
|
|
||||||
- collectionFormat: csv
|
|
||||||
description: Filter by source (comma-separated)
|
|
||||||
in: query
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
name: source
|
|
||||||
type: array
|
|
||||||
- description: Minimum estimated value
|
|
||||||
in: query
|
|
||||||
name: min_estimated_value
|
|
||||||
type: number
|
|
||||||
- description: Maximum estimated value
|
|
||||||
in: query
|
|
||||||
name: max_estimated_value
|
|
||||||
type: number
|
|
||||||
- description: Filter by currency
|
|
||||||
in: query
|
|
||||||
name: currency
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
allOf:
|
|
||||||
- $ref: '#/definitions/response.APIResponse'
|
|
||||||
- properties:
|
|
||||||
data:
|
|
||||||
additionalProperties: true
|
|
||||||
type: object
|
|
||||||
type: object
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
security:
|
|
||||||
- BearerAuth: []
|
|
||||||
summary: List tenders
|
|
||||||
tags:
|
|
||||||
- Admin-Tenders
|
|
||||||
/admin/tenders/{id}:
|
|
||||||
delete:
|
|
||||||
description: Delete a tender by its ID
|
|
||||||
parameters:
|
|
||||||
- description: Tender ID
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
"404":
|
|
||||||
description: Not Found
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
security:
|
|
||||||
- BearerAuth: []
|
|
||||||
summary: Delete tender
|
|
||||||
tags:
|
|
||||||
- Admin-Tenders
|
|
||||||
get:
|
|
||||||
description: Retrieve a specific tender by its ID
|
|
||||||
parameters:
|
|
||||||
- description: Tender ID
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
allOf:
|
|
||||||
- $ref: '#/definitions/response.APIResponse'
|
|
||||||
- properties:
|
|
||||||
data:
|
|
||||||
$ref: '#/definitions/tender.TenderResponse'
|
|
||||||
type: object
|
|
||||||
"404":
|
|
||||||
description: Not Found
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
security:
|
|
||||||
- BearerAuth: []
|
|
||||||
summary: Get tender by ID
|
|
||||||
tags:
|
|
||||||
- Admin-Tenders
|
|
||||||
put:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Update an existing tender with the provided information
|
|
||||||
parameters:
|
|
||||||
- description: Tender ID
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- description: Updated tender information
|
|
||||||
in: body
|
|
||||||
name: tender
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/tender.UpdateTenderRequest'
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
allOf:
|
|
||||||
- $ref: '#/definitions/response.APIResponse'
|
|
||||||
- properties:
|
|
||||||
data:
|
|
||||||
$ref: '#/definitions/tender.TenderResponse'
|
|
||||||
type: object
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
"404":
|
|
||||||
description: Not Found
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
security:
|
|
||||||
- BearerAuth: []
|
|
||||||
summary: Update tender
|
|
||||||
tags:
|
|
||||||
- Admin-Tenders
|
|
||||||
/admin/v1/companies:
|
/admin/v1/companies:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
@@ -3200,6 +3007,234 @@ paths:
|
|||||||
summary: Get tender approvals by submission mode
|
summary: Get tender approvals by submission mode
|
||||||
tags:
|
tags:
|
||||||
- Admin-TenderApprovals
|
- Admin-TenderApprovals
|
||||||
|
/admin/v1/tenders:
|
||||||
|
get:
|
||||||
|
description: Retrieve tenders with pagination, filtering, and search capabilities
|
||||||
|
with optional company-based matching
|
||||||
|
parameters:
|
||||||
|
- description: Company ID for match percentage calculation and sorting
|
||||||
|
in: query
|
||||||
|
name: company_id
|
||||||
|
type: string
|
||||||
|
- description: Search query for title and description
|
||||||
|
in: query
|
||||||
|
name: q
|
||||||
|
type: string
|
||||||
|
- description: Filter by notice type code
|
||||||
|
in: query
|
||||||
|
name: notice_type
|
||||||
|
type: string
|
||||||
|
- description: Filter by procurement type code
|
||||||
|
in: query
|
||||||
|
name: procurement_type
|
||||||
|
type: string
|
||||||
|
- collectionFormat: csv
|
||||||
|
description: Filter by country codes (comma-separated)
|
||||||
|
in: query
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
name: country_codes
|
||||||
|
type: array
|
||||||
|
- collectionFormat: csv
|
||||||
|
description: Filter by status (comma-separated)
|
||||||
|
in: query
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
name: status
|
||||||
|
type: array
|
||||||
|
- collectionFormat: csv
|
||||||
|
description: Filter by source (comma-separated)
|
||||||
|
in: query
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
name: source
|
||||||
|
type: array
|
||||||
|
- description: Minimum estimated value
|
||||||
|
in: query
|
||||||
|
name: min_estimated_value
|
||||||
|
type: number
|
||||||
|
- description: Maximum estimated value
|
||||||
|
in: query
|
||||||
|
name: max_estimated_value
|
||||||
|
type: number
|
||||||
|
- description: Filter by currency
|
||||||
|
in: query
|
||||||
|
name: currency
|
||||||
|
type: string
|
||||||
|
- description: Deadline from
|
||||||
|
in: query
|
||||||
|
name: deadline_from
|
||||||
|
type: number
|
||||||
|
- description: Deadline to
|
||||||
|
in: query
|
||||||
|
name: deadline_to
|
||||||
|
type: number
|
||||||
|
- description: Publication date from
|
||||||
|
in: query
|
||||||
|
name: publication_date_from
|
||||||
|
type: number
|
||||||
|
- description: Publication date to
|
||||||
|
in: query
|
||||||
|
name: publication_date_to
|
||||||
|
type: number
|
||||||
|
- collectionFormat: csv
|
||||||
|
description: Filter by languages (comma-separated)
|
||||||
|
in: query
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
name: languages
|
||||||
|
type: array
|
||||||
|
- description: Filter by buyer organization ID
|
||||||
|
in: query
|
||||||
|
name: buyer_organization_id
|
||||||
|
type: string
|
||||||
|
- description: 'Number of items per page (default: 20, max: 100)'
|
||||||
|
in: query
|
||||||
|
name: limit
|
||||||
|
type: integer
|
||||||
|
- description: 'Number of items to skip (default: 0)'
|
||||||
|
in: query
|
||||||
|
name: offset
|
||||||
|
type: integer
|
||||||
|
- description: Sort by field
|
||||||
|
in: query
|
||||||
|
name: sort_by
|
||||||
|
type: string
|
||||||
|
- description: Sort order (asc or desc)
|
||||||
|
in: query
|
||||||
|
name: sort_order
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/response.APIResponse'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: List tenders
|
||||||
|
tags:
|
||||||
|
- Admin-Tenders
|
||||||
|
/admin/v1/tenders/{id}:
|
||||||
|
delete:
|
||||||
|
description: Delete a tender by its ID
|
||||||
|
parameters:
|
||||||
|
- description: Tender ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Delete tender
|
||||||
|
tags:
|
||||||
|
- Admin-Tenders
|
||||||
|
get:
|
||||||
|
description: Retrieve a specific tender by its ID
|
||||||
|
parameters:
|
||||||
|
- description: Tender ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/response.APIResponse'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/tender.TenderResponse'
|
||||||
|
type: object
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Get tender by ID
|
||||||
|
tags:
|
||||||
|
- Admin-Tenders
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Update an existing tender with the provided information
|
||||||
|
parameters:
|
||||||
|
- description: Tender ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Updated tender information
|
||||||
|
in: body
|
||||||
|
name: tender
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/tender.UpdateTenderRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/response.APIResponse'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/tender.TenderResponse'
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Update tender
|
||||||
|
tags:
|
||||||
|
- Admin-Tenders
|
||||||
/admin/v1/users:
|
/admin/v1/users:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
@@ -85,10 +85,10 @@ func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler
|
|||||||
tendersGP := adminV1.Group("/tenders")
|
tendersGP := adminV1.Group("/tenders")
|
||||||
{
|
{
|
||||||
tendersGP.Use(userHandler.AuthMiddleware())
|
tendersGP.Use(userHandler.AuthMiddleware())
|
||||||
tendersGP.GET("", tenderHandler.ListTenders)
|
tendersGP.GET("", tenderHandler.Search)
|
||||||
tendersGP.GET("/:id", tenderHandler.GetTender)
|
tendersGP.GET("/:id", tenderHandler.Get)
|
||||||
tendersGP.PUT("/:id", tenderHandler.UpdateTender)
|
tendersGP.PUT("/:id", tenderHandler.Update)
|
||||||
tendersGP.DELETE("/:id", tenderHandler.DeleteTender)
|
tendersGP.DELETE("/:id", tenderHandler.Delete)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Admin Feedback Routes
|
// Admin Feedback Routes
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ type FeedbackService interface {
|
|||||||
// feedbackService implements FeedbackService interface
|
// feedbackService implements FeedbackService interface
|
||||||
type feedbackService struct {
|
type feedbackService struct {
|
||||||
feedbackRepo Repository
|
feedbackRepo Repository
|
||||||
tenderService tender.TenderService
|
tenderService tender.Service
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFeedbackService creates a new feedback service
|
// NewFeedbackService creates a new feedback service
|
||||||
func NewFeedbackService(feedbackRepo Repository, tenderService tender.TenderService, logger logger.Logger) FeedbackService {
|
func NewFeedbackService(feedbackRepo Repository, tenderService tender.Service, logger logger.Logger) FeedbackService {
|
||||||
return &feedbackService{
|
return &feedbackService{
|
||||||
feedbackRepo: feedbackRepo,
|
feedbackRepo: feedbackRepo,
|
||||||
tenderService: tenderService,
|
tenderService: tenderService,
|
||||||
@@ -133,7 +133,7 @@ func (s *feedbackService) ListFeedback(ctx context.Context, criteria FeedbackSea
|
|||||||
|
|
||||||
result := make([]*FeedbackWithTenderResponse, len(feedbacks))
|
result := make([]*FeedbackWithTenderResponse, len(feedbacks))
|
||||||
for i, feedback := range feedbacks {
|
for i, feedback := range feedbacks {
|
||||||
tender, err := s.tenderService.GetTenderByID(ctx, feedback.TenderID)
|
tender, err := s.tenderService.GetByID(ctx, feedback.TenderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get tender", map[string]interface{}{
|
s.logger.Error("Failed to get tender", map[string]interface{}{
|
||||||
"tender_id": feedback.TenderID,
|
"tender_id": feedback.TenderID,
|
||||||
|
|||||||
@@ -119,27 +119,6 @@ const (
|
|||||||
TenderSourceBulkImport TenderSource = "bulk_import"
|
TenderSourceBulkImport TenderSource = "bulk_import"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TenderSearchCriteria represents search criteria for tenders
|
|
||||||
type TenderSearchCriteria struct {
|
|
||||||
Query string `json:"query,omitempty"`
|
|
||||||
NoticeType string `json:"notice_type,omitempty"`
|
|
||||||
ProcurementType string `json:"procurement_type,omitempty"`
|
|
||||||
CountryCodes []string `json:"country_codes,omitempty"`
|
|
||||||
RegionCodes []string `json:"region_codes,omitempty"`
|
|
||||||
Classifications []string `json:"classifications,omitempty"`
|
|
||||||
MinEstimatedValue *float64 `json:"min_estimated_value,omitempty"`
|
|
||||||
MaxEstimatedValue *float64 `json:"max_estimated_value,omitempty"`
|
|
||||||
Currency string `json:"currency,omitempty"`
|
|
||||||
DeadlineFrom *int64 `json:"deadline_from,omitempty"`
|
|
||||||
DeadlineTo *int64 `json:"deadline_to,omitempty"`
|
|
||||||
PublicationDateFrom *int64 `json:"publication_date_from,omitempty"`
|
|
||||||
PublicationDateTo *int64 `json:"publication_date_to,omitempty"`
|
|
||||||
Status []TenderStatus `json:"status,omitempty"`
|
|
||||||
Source []TenderSource `json:"source,omitempty"`
|
|
||||||
BuyerOrganizationID string `json:"buyer_organization_id,omitempty"`
|
|
||||||
Languages []string `json:"languages,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TenderStatistics represents tender statistics
|
// TenderStatistics represents tender statistics
|
||||||
type TenderStatistics struct {
|
type TenderStatistics struct {
|
||||||
TotalTenders int64 `json:"total_tenders"`
|
TotalTenders int64 `json:"total_tenders"`
|
||||||
|
|||||||
+24
-12
@@ -13,20 +13,32 @@ type UpdateTenderRequest struct {
|
|||||||
TenderDeadline *int64 `json:"tender_deadline,omitempty"` // Unix milliseconds
|
TenderDeadline *int64 `json:"tender_deadline,omitempty"` // Unix milliseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTendersRequest represents a request to list tenders with pagination and filtering
|
// SearchForm represents a request to list tenders with pagination and filtering
|
||||||
type ListTendersRequest struct {
|
type SearchForm struct {
|
||||||
Criteria TenderSearchCriteria `json:"criteria"`
|
Search *string `query:"q" valid:"optional"`
|
||||||
Limit int `json:"limit" valid:"range(1|100)~Limit must be between 1 and 100"`
|
NoticeType *string `query:"notice_type" valid:"optional"`
|
||||||
Offset int `json:"offset" valid:"range(0|999999)~Offset must be non-negative"`
|
ProcurementType *string `query:"procurement_type" valid:"optional"`
|
||||||
CompanyID *string `json:"company_id,omitempty"` // Optional company ID for match percentage calculation
|
CountryCodes []string `query:"country_codes" valid:"optional"`
|
||||||
From *int64 `json:"from,omitempty"`
|
RegionCodes []string `query:"region_codes" valid:"optional"`
|
||||||
To *int64 `json:"to,omitempty"`
|
CompanyID *string `query:"company_id" valid:"optional"`
|
||||||
|
Status []string `query:"status" valid:"optional"`
|
||||||
|
Classifications []string `query:"classifications" valid:"optional"`
|
||||||
|
MinEstimatedValue *float64 `query:"min_estimated_value" valid:"optional"`
|
||||||
|
MaxEstimatedValue *float64 `query:"max_estimated_value" valid:"optional"`
|
||||||
|
Currency string `query:"currency" valid:"optional"`
|
||||||
|
DeadlineFrom *int64 `query:"deadline_from" valid:"optional"`
|
||||||
|
DeadlineTo *int64 `query:"deadline_to" valid:"optional"`
|
||||||
|
PublicationDateFrom *int64 `query:"publication_date_from" valid:"optional"`
|
||||||
|
PublicationDateTo *int64 `query:"publication_date_to" valid:"optional"`
|
||||||
|
Source []string `query:"source" valid:"optional"`
|
||||||
|
BuyerOrganizationID *string `query:"buyer_organization_id" valid:"optional"`
|
||||||
|
Languages []string `query:"languages" valid:"optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTendersResponse represents the response for listing tenders
|
// SearchResponse represents the response for listing tenders
|
||||||
type ListTendersResponse struct {
|
type SearchResponse struct {
|
||||||
Tenders []TenderResponse `json:"tenders"`
|
Tenders []TenderResponse `json:"tenders"`
|
||||||
Metadata *response.Meta `json:"metadata"`
|
Metadata *response.Meta `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TenderResponse represents a tender in API responses
|
// TenderResponse represents a tender in API responses
|
||||||
@@ -57,7 +69,7 @@ type OrganizationResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ToTenderResponse converts a Tender entity to TenderResponse
|
// ToTenderResponse converts a Tender entity to TenderResponse
|
||||||
func (t *Tender) ToTenderResponse() *TenderResponse {
|
func (t *Tender) ToResponse() *TenderResponse {
|
||||||
response := &TenderResponse{
|
response := &TenderResponse{
|
||||||
ID: t.ID.Hex(),
|
ID: t.ID.Hex(),
|
||||||
NoticePublicationID: t.NoticePublicationID,
|
NoticePublicationID: t.NoticePublicationID,
|
||||||
|
|||||||
+66
-195
@@ -1,8 +1,6 @@
|
|||||||
package tender
|
package tender
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"tm/pkg/logger"
|
"tm/pkg/logger"
|
||||||
"tm/pkg/response"
|
"tm/pkg/response"
|
||||||
|
|
||||||
@@ -12,18 +10,22 @@ import (
|
|||||||
|
|
||||||
// TenderHandler handles tender-related HTTP requests
|
// TenderHandler handles tender-related HTTP requests
|
||||||
type TenderHandler struct {
|
type TenderHandler struct {
|
||||||
service TenderService
|
service Service
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTenderHandler creates a new tender handler
|
// NewTenderHandler creates a new tender handler
|
||||||
func NewTenderHandler(service TenderService, logger logger.Logger) *TenderHandler {
|
func NewTenderHandler(service Service, logger logger.Logger) *TenderHandler {
|
||||||
return &TenderHandler{
|
return &TenderHandler{
|
||||||
service: service,
|
service: service,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *****************************************************
|
||||||
|
// * ADMIN HANDLERS *
|
||||||
|
// *****************************************************
|
||||||
|
|
||||||
// GetTender retrieves a tender by ID
|
// GetTender retrieves a tender by ID
|
||||||
// @Summary Get tender by ID
|
// @Summary Get tender by ID
|
||||||
// @Description Retrieve a specific tender by its ID
|
// @Description Retrieve a specific tender by its ID
|
||||||
@@ -33,15 +35,15 @@ func NewTenderHandler(service TenderService, logger logger.Logger) *TenderHandle
|
|||||||
// @Success 200 {object} response.APIResponse{data=TenderResponse}
|
// @Success 200 {object} response.APIResponse{data=TenderResponse}
|
||||||
// @Failure 404 {object} response.APIResponse
|
// @Failure 404 {object} response.APIResponse
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /admin/tenders/{id} [get]
|
// @Router /admin/v1/tenders/{id} [get]
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
func (h *TenderHandler) GetTender(c echo.Context) error {
|
func (h *TenderHandler) Get(c echo.Context) error {
|
||||||
id := c.Param("id")
|
id := c.Param("id")
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return response.BadRequest(c, "Tender ID is required", "ID parameter cannot be empty")
|
return response.BadRequest(c, "Tender ID is required", "ID parameter cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
tender, err := h.service.GetTenderByID(c.Request().Context(), id)
|
tender, err := h.service.GetByID(c.Request().Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.NotFound(c, "Tender not found")
|
return response.NotFound(c, "Tender not found")
|
||||||
}
|
}
|
||||||
@@ -61,9 +63,9 @@ func (h *TenderHandler) GetTender(c echo.Context) error {
|
|||||||
// @Failure 400 {object} response.APIResponse
|
// @Failure 400 {object} response.APIResponse
|
||||||
// @Failure 404 {object} response.APIResponse
|
// @Failure 404 {object} response.APIResponse
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /admin/tenders/{id} [put]
|
// @Router /admin/v1/tenders/{id} [put]
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
func (h *TenderHandler) UpdateTender(c echo.Context) error {
|
func (h *TenderHandler) Update(c echo.Context) error {
|
||||||
id := c.Param("id")
|
id := c.Param("id")
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return response.BadRequest(c, "Tender ID is required", "ID parameter cannot be empty")
|
return response.BadRequest(c, "Tender ID is required", "ID parameter cannot be empty")
|
||||||
@@ -80,7 +82,7 @@ func (h *TenderHandler) UpdateTender(c echo.Context) error {
|
|||||||
return response.ValidationError(c, "Validation failed", err.Error())
|
return response.ValidationError(c, "Validation failed", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
tender, err := h.service.UpdateTender(c.Request().Context(), req)
|
tender, err := h.service.Update(c.Request().Context(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "tender not found" {
|
if err.Error() == "tender not found" {
|
||||||
return response.NotFound(c, "Tender not found")
|
return response.NotFound(c, "Tender not found")
|
||||||
@@ -88,7 +90,7 @@ func (h *TenderHandler) UpdateTender(c echo.Context) error {
|
|||||||
return response.InternalServerError(c, "Failed to update tender")
|
return response.InternalServerError(c, "Failed to update tender")
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Success(c, tender.ToTenderResponse(), "Tender updated successfully")
|
return response.Success(c, tender, "Tender updated successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteTender deletes a tender
|
// DeleteTender deletes a tender
|
||||||
@@ -99,15 +101,15 @@ func (h *TenderHandler) UpdateTender(c echo.Context) error {
|
|||||||
// @Success 200 {object} response.APIResponse
|
// @Success 200 {object} response.APIResponse
|
||||||
// @Failure 404 {object} response.APIResponse
|
// @Failure 404 {object} response.APIResponse
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /admin/tenders/{id} [delete]
|
// @Router /admin/v1/tenders/{id} [delete]
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
func (h *TenderHandler) DeleteTender(c echo.Context) error {
|
func (h *TenderHandler) Delete(c echo.Context) error {
|
||||||
id := c.Param("id")
|
id := c.Param("id")
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return response.BadRequest(c, "Tender ID is required", "ID parameter cannot be empty")
|
return response.BadRequest(c, "Tender ID is required", "ID parameter cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.service.DeleteTender(c.Request().Context(), id)
|
err := h.service.Delete(c.Request().Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "tender not found" {
|
if err.Error() == "tender not found" {
|
||||||
return response.NotFound(c, "Tender not found")
|
return response.NotFound(c, "Tender not found")
|
||||||
@@ -123,10 +125,8 @@ func (h *TenderHandler) DeleteTender(c echo.Context) error {
|
|||||||
// @Description Retrieve tenders with pagination, filtering, and search capabilities with optional company-based matching
|
// @Description Retrieve tenders with pagination, filtering, and search capabilities with optional company-based matching
|
||||||
// @Tags Admin-Tenders
|
// @Tags Admin-Tenders
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param limit query int false "Number of items per page (default: 20, max: 100)"
|
|
||||||
// @Param offset query int false "Number of items to skip (default: 0)"
|
|
||||||
// @Param company_id query string false "Company ID for match percentage calculation and sorting"
|
// @Param company_id query string false "Company ID for match percentage calculation and sorting"
|
||||||
// @Param query query string false "Search query for title and description"
|
// @Param q query string false "Search query for title and description"
|
||||||
// @Param notice_type query string false "Filter by notice type code"
|
// @Param notice_type query string false "Filter by notice type code"
|
||||||
// @Param procurement_type query string false "Filter by procurement type code"
|
// @Param procurement_type query string false "Filter by procurement type code"
|
||||||
// @Param country_codes query []string false "Filter by country codes (comma-separated)"
|
// @Param country_codes query []string false "Filter by country codes (comma-separated)"
|
||||||
@@ -135,90 +135,45 @@ func (h *TenderHandler) DeleteTender(c echo.Context) error {
|
|||||||
// @Param min_estimated_value query number false "Minimum estimated value"
|
// @Param min_estimated_value query number false "Minimum estimated value"
|
||||||
// @Param max_estimated_value query number false "Maximum estimated value"
|
// @Param max_estimated_value query number false "Maximum estimated value"
|
||||||
// @Param currency query string false "Filter by currency"
|
// @Param currency query string false "Filter by currency"
|
||||||
|
// @Param deadline_from query number false "Deadline from"
|
||||||
|
// @Param deadline_to query number false "Deadline to"
|
||||||
|
// @Param publication_date_from query number false "Publication date from"
|
||||||
|
// @Param publication_date_to query number false "Publication date to"
|
||||||
|
// @Param languages query []string false "Filter by languages (comma-separated)"
|
||||||
|
// @Param buyer_organization_id query string false "Filter by buyer organization ID"
|
||||||
|
// @Param limit query int false "Number of items per page (default: 20, max: 100)"
|
||||||
|
// @Param offset query int false "Number of items to skip (default: 0)"
|
||||||
|
// @Param sort_by query string false "Sort by field"
|
||||||
|
// @Param sort_order query string false "Sort order (asc or desc)"
|
||||||
// @Success 200 {object} response.APIResponse{data=map[string]interface{}}
|
// @Success 200 {object} response.APIResponse{data=map[string]interface{}}
|
||||||
// @Failure 400 {object} response.APIResponse
|
// @Failure 400 {object} response.APIResponse
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /admin/tenders [get]
|
// @Router /admin/v1/tenders [get]
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
func (h *TenderHandler) ListTenders(c echo.Context) error {
|
func (h *TenderHandler) Search(c echo.Context) error {
|
||||||
// Parse pagination parameters
|
form, err := response.Parse[SearchForm](c)
|
||||||
limit := 20
|
|
||||||
if l := c.QueryParam("limit"); l != "" {
|
|
||||||
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 100 {
|
|
||||||
limit = parsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset := 0
|
|
||||||
if o := c.QueryParam("offset"); o != "" {
|
|
||||||
if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 {
|
|
||||||
offset = parsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build search criteria
|
|
||||||
criteria := TenderSearchCriteria{
|
|
||||||
Query: c.QueryParam("query"),
|
|
||||||
NoticeType: c.QueryParam("notice_type"),
|
|
||||||
ProcurementType: c.QueryParam("procurement_type"),
|
|
||||||
Currency: c.QueryParam("currency"),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse array parameters
|
|
||||||
if countryCodes := c.QueryParam("country_codes"); countryCodes != "" {
|
|
||||||
criteria.CountryCodes = parseStringArray(countryCodes)
|
|
||||||
}
|
|
||||||
|
|
||||||
if statuses := c.QueryParam("status"); statuses != "" {
|
|
||||||
statusStrings := parseStringArray(statuses)
|
|
||||||
criteria.Status = make([]TenderStatus, len(statusStrings))
|
|
||||||
for i, s := range statusStrings {
|
|
||||||
criteria.Status[i] = TenderStatus(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sources := c.QueryParam("source"); sources != "" {
|
|
||||||
sourceStrings := parseStringArray(sources)
|
|
||||||
criteria.Source = make([]TenderSource, len(sourceStrings))
|
|
||||||
for i, s := range sourceStrings {
|
|
||||||
criteria.Source[i] = TenderSource(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse numeric parameters
|
|
||||||
if minValue := c.QueryParam("min_estimated_value"); minValue != "" {
|
|
||||||
if parsed, err := strconv.ParseFloat(minValue, 64); err == nil {
|
|
||||||
criteria.MinEstimatedValue = &parsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if maxValue := c.QueryParam("max_estimated_value"); maxValue != "" {
|
|
||||||
if parsed, err := strconv.ParseFloat(maxValue, 64); err == nil {
|
|
||||||
criteria.MaxEstimatedValue = &parsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse company_id parameter for matching
|
|
||||||
var companyID *string
|
|
||||||
if companyIDParam := c.QueryParam("company_id"); companyIDParam != "" {
|
|
||||||
companyID = &companyIDParam
|
|
||||||
}
|
|
||||||
|
|
||||||
req := ListTendersRequest{
|
|
||||||
Criteria: criteria,
|
|
||||||
Limit: limit,
|
|
||||||
Offset: offset,
|
|
||||||
CompanyID: companyID,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := h.service.ListTenders(c.Request().Context(), req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.InternalServerError(c, "Failed to retrieve tenders")
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Success(c, result, "Tenders retrieved successfully")
|
// Validate form
|
||||||
|
if _, err := govalidator.ValidateStruct(form); err != nil {
|
||||||
|
return response.ValidationError(c, "Validation failed", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call service
|
||||||
|
tenders, err := h.service.Search(c.Request().Context(), form, response.NewPagination(c))
|
||||||
|
if err != nil {
|
||||||
|
return response.InternalServerError(c, "Failed to list tenders")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.SuccessWithMeta(c, tenders, tenders.Metadata, "Tenders retrieved successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *****************************************************
|
||||||
|
// * PUBLIC HANDLERS *
|
||||||
|
// *****************************************************
|
||||||
|
|
||||||
// GetPublicTenders retrieves public tenders for mobile app
|
// GetPublicTenders retrieves public tenders for mobile app
|
||||||
// @Summary Get public tenders
|
// @Summary Get public tenders
|
||||||
// @Description Retrieve active tenders for mobile application users with automatic company-based matching from customer profile
|
// @Description Retrieve active tenders for mobile application users with automatic company-based matching from customer profile
|
||||||
@@ -237,64 +192,26 @@ func (h *TenderHandler) ListTenders(c echo.Context) error {
|
|||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Router /api/v1/tenders [get]
|
// @Router /api/v1/tenders [get]
|
||||||
func (h *TenderHandler) GetPublicTenders(c echo.Context) error {
|
func (h *TenderHandler) GetPublicTenders(c echo.Context) error {
|
||||||
// Parse pagination parameters (more restrictive for public API)
|
|
||||||
limit := 20
|
form, err := response.Parse[SearchForm](c)
|
||||||
if parsed, err := strconv.Atoi(c.QueryParam("limit")); err == nil && parsed > 0 && parsed <= 50 {
|
if err != nil {
|
||||||
limit = parsed
|
return response.BadRequest(c, "Invalid request format", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
offset := 0
|
if _, err := govalidator.ValidateStruct(form); err != nil {
|
||||||
if parsed, err := strconv.Atoi(c.QueryParam("offset")); err == nil && parsed >= 0 {
|
return response.ValidationError(c, err.Error(), "")
|
||||||
offset = parsed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build search criteria for public API (only active tenders)
|
form.Status = []string{string(TenderStatusActive)}
|
||||||
criteria := TenderSearchCriteria{
|
|
||||||
Status: []TenderStatus{TenderStatusActive},
|
|
||||||
}
|
|
||||||
|
|
||||||
if countryCode := c.QueryParam("country_code"); countryCode != "" {
|
|
||||||
criteria.CountryCodes = []string{countryCode}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse numeric parameters
|
|
||||||
if parsed, err := strconv.ParseFloat(c.QueryParam("min_estimated_value"), 64); err == nil {
|
|
||||||
criteria.MinEstimatedValue = &parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
if parsed, err := strconv.ParseFloat(c.QueryParam("max_estimated_value"), 64); err == nil {
|
|
||||||
criteria.MaxEstimatedValue = &parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get company ID from customer context for automatic matching
|
// Get company ID from customer context for automatic matching
|
||||||
var companyID *string
|
var companyID *string
|
||||||
if customerCompanyID, ok := c.Get("company_id").(string); ok {
|
if customerCompanyID, ok := c.Get("company_id").(string); ok {
|
||||||
companyID = &customerCompanyID
|
companyID = &customerCompanyID
|
||||||
}
|
}
|
||||||
|
form.CompanyID = companyID
|
||||||
|
|
||||||
var from *int64
|
result, err := h.service.Search(c.Request().Context(), form, response.NewPagination(c))
|
||||||
if fromParam := c.QueryParam("publication_from"); fromParam != "" {
|
|
||||||
if parsed, err := strconv.ParseInt(fromParam, 10, 64); err == nil {
|
|
||||||
from = &parsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var to *int64
|
|
||||||
if toParam := c.QueryParam("publication_to"); toParam != "" {
|
|
||||||
if parsed, err := strconv.ParseInt(toParam, 10, 64); err == nil {
|
|
||||||
to = &parsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
req := ListTendersRequest{
|
|
||||||
Criteria: criteria,
|
|
||||||
Limit: limit,
|
|
||||||
Offset: offset,
|
|
||||||
CompanyID: companyID,
|
|
||||||
From: from,
|
|
||||||
To: to,
|
|
||||||
}
|
|
||||||
result, err := h.service.ListTenders(c.Request().Context(), req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.InternalServerError(c, "Failed to retrieve tenders")
|
return response.InternalServerError(c, "Failed to retrieve tenders")
|
||||||
}
|
}
|
||||||
@@ -320,56 +237,26 @@ func (h *TenderHandler) GetPublicTenders(c echo.Context) error {
|
|||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Router /api/v1/tenders/recommend [get]
|
// @Router /api/v1/tenders/recommend [get]
|
||||||
func (h *TenderHandler) RecommendTenders(c echo.Context) error {
|
func (h *TenderHandler) RecommendTenders(c echo.Context) error {
|
||||||
// Parse pagination parameters (more restrictive for public API)
|
|
||||||
limit := 20
|
form, err := response.Parse[SearchForm](c)
|
||||||
if parsed, err := strconv.Atoi(c.QueryParam("limit")); err == nil && parsed > 0 && parsed <= 50 {
|
if err != nil {
|
||||||
limit = parsed
|
return response.BadRequest(c, "Invalid request format", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
offset := 0
|
if _, err := govalidator.ValidateStruct(form); err != nil {
|
||||||
if parsed, err := strconv.Atoi(c.QueryParam("offset")); err == nil && parsed >= 0 {
|
return response.ValidationError(c, err.Error(), "")
|
||||||
offset = parsed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build search criteria for public API (only active tenders)
|
form.Status = []string{string(TenderStatusActive)}
|
||||||
criteria := TenderSearchCriteria{
|
|
||||||
Status: []TenderStatus{TenderStatusActive},
|
|
||||||
}
|
|
||||||
|
|
||||||
if countryCode := c.QueryParam("country_code"); countryCode != "" {
|
|
||||||
criteria.CountryCodes = []string{countryCode}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse numeric parameters
|
|
||||||
if parsed, err := strconv.ParseFloat(c.QueryParam("min_estimated_value"), 64); err == nil {
|
|
||||||
criteria.MinEstimatedValue = &parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
if parsed, err := strconv.ParseFloat(c.QueryParam("max_estimated_value"), 64); err == nil {
|
|
||||||
criteria.MaxEstimatedValue = &parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get company ID from customer context for automatic matching
|
|
||||||
var companyID *string
|
var companyID *string
|
||||||
if customerCompanyID, ok := c.Get("company_id").(string); ok {
|
if customerCompanyID, ok := c.Get("company_id").(string); ok {
|
||||||
companyID = &customerCompanyID
|
companyID = &customerCompanyID
|
||||||
}
|
}
|
||||||
|
|
||||||
var from *int64
|
form.CompanyID = companyID
|
||||||
if fromParam := c.QueryParam("publication_from"); fromParam != "" {
|
|
||||||
if parsed, err := strconv.ParseInt(fromParam, 10, 64); err == nil {
|
|
||||||
from = &parsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var to *int64
|
result, err := h.service.Recommend(c.Request().Context(), form, response.NewPagination(c))
|
||||||
if toParam := c.QueryParam("publication_to"); toParam != "" {
|
|
||||||
if parsed, err := strconv.ParseInt(toParam, 10, 64); err == nil {
|
|
||||||
to = &parsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := h.service.RecommendTenders(c.Request().Context(), companyID, from, to, limit, offset)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.InternalServerError(c, "Failed to retrieve tenders")
|
return response.InternalServerError(c, "Failed to retrieve tenders")
|
||||||
}
|
}
|
||||||
@@ -394,26 +281,10 @@ func (h *TenderHandler) GetPublicTenderDetails(c echo.Context) error {
|
|||||||
return response.BadRequest(c, "Tender ID is required", "ID parameter cannot be empty")
|
return response.BadRequest(c, "Tender ID is required", "ID parameter cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
tender, err := h.service.GetTenderByID(c.Request().Context(), id)
|
tender, err := h.service.GetByID(c.Request().Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.NotFound(c, "Tender not found")
|
return response.NotFound(c, "Tender not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Success(c, tender, "Tender details retrieved successfully")
|
return response.Success(c, tender, "Tender details retrieved successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseStringArray parses a comma-separated string into a string array
|
|
||||||
func parseStringArray(s string) []string {
|
|
||||||
if s == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]string, 0)
|
|
||||||
for _, item := range strings.Split(s, ",") {
|
|
||||||
if trimmed := strings.TrimSpace(item); trimmed != "" {
|
|
||||||
result = append(result, trimmed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|||||||
+100
-240
@@ -4,7 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
"tm/pkg/logger"
|
"tm/pkg/logger"
|
||||||
mongopkg "tm/pkg/mongo"
|
orm "tm/pkg/mongo"
|
||||||
|
"tm/pkg/response"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
@@ -17,81 +18,73 @@ type TenderRepository interface {
|
|||||||
GetByID(ctx context.Context, id string) (*Tender, error)
|
GetByID(ctx context.Context, id string) (*Tender, error)
|
||||||
GetByContractNoticeID(ctx context.Context, contractNoticeID string) (*Tender, error)
|
GetByContractNoticeID(ctx context.Context, contractNoticeID string) (*Tender, error)
|
||||||
GetByNoticePublicationID(ctx context.Context, noticePublicationID string) (*Tender, error)
|
GetByNoticePublicationID(ctx context.Context, noticePublicationID string) (*Tender, error)
|
||||||
Update(ctx context.Context, tender *Tender) error
|
|
||||||
Delete(ctx context.Context, id string) error
|
|
||||||
List(ctx context.Context, criteria TenderSearchCriteria, limit, offset int, from *int64, to *int64) ([]Tender, int64, error)
|
|
||||||
|
|
||||||
// Bulk operations
|
|
||||||
CreateBatch(ctx context.Context, tenders []Tender) error
|
|
||||||
UpdateBatch(ctx context.Context, tenders []Tender) error
|
|
||||||
|
|
||||||
// Search and filtering
|
|
||||||
Search(ctx context.Context, criteria TenderSearchCriteria) ([]Tender, error)
|
|
||||||
GetByCountryCode(ctx context.Context, countryCode string, limit, offset int) ([]Tender, error)
|
|
||||||
GetByStatus(ctx context.Context, status TenderStatus, limit, offset int) ([]Tender, error)
|
|
||||||
GetExpiredTenders(ctx context.Context, beforeDate int64) ([]Tender, error)
|
|
||||||
GetActiveTenders(ctx context.Context, limit, offset int) ([]Tender, error)
|
|
||||||
|
|
||||||
// Statistics and aggregations
|
|
||||||
GetStatistics(ctx context.Context) (*TenderStatistics, error)
|
|
||||||
GetTenderCountByCountry(ctx context.Context) (map[string]int64, error)
|
GetTenderCountByCountry(ctx context.Context) (map[string]int64, error)
|
||||||
GetTenderCountByType(ctx context.Context) (map[string]int64, error)
|
GetTenderCountByType(ctx context.Context) (map[string]int64, error)
|
||||||
GetTenderCountByClassification(ctx context.Context) (map[string]int64, error)
|
GetTenderCountByClassification(ctx context.Context) (map[string]int64, error)
|
||||||
|
Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) ([]Tender, int64, error)
|
||||||
|
GetExpiredTenders(ctx context.Context, beforeDate int64) ([]Tender, error)
|
||||||
|
Update(ctx context.Context, tender *Tender) error
|
||||||
|
Delete(ctx context.Context, id string) error
|
||||||
|
GetStatistics(ctx context.Context) (*TenderStatistics, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionName() string {
|
||||||
|
return "tenders"
|
||||||
}
|
}
|
||||||
|
|
||||||
// tenderRepository implements TenderRepository interface using MongoDB ORM
|
// tenderRepository implements TenderRepository interface using MongoDB ORM
|
||||||
type tenderRepository struct {
|
type tenderRepository struct {
|
||||||
ormRepo mongopkg.Repository[Tender]
|
ormRepo orm.Repository[Tender]
|
||||||
mongoManager *mongopkg.ConnectionManager
|
mongoManager *orm.ConnectionManager
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTenderRepository creates a new tender repository
|
// NewTenderRepository creates a new tender repository
|
||||||
func NewTenderRepository(mongoManager *mongopkg.ConnectionManager, logger logger.Logger) TenderRepository {
|
func NewTenderRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) TenderRepository {
|
||||||
// Create indexes for tenders collection
|
// Create indexes for tenders collection
|
||||||
tenderIndexes := []mongopkg.Index{
|
tenderIndexes := []orm.Index{
|
||||||
*mongopkg.CreateUniqueIndex("notice_publication_id_idx", bson.D{{Key: "notice_publication_id", Value: 1}}),
|
*orm.CreateUniqueIndex("notice_publication_id_idx", bson.D{{Key: "notice_publication_id", Value: 1}}),
|
||||||
*mongopkg.NewIndex("contract_notice_id_idx", bson.D{{Key: "contract_notice_id", Value: 1}}),
|
*orm.NewIndex("contract_notice_id_idx", bson.D{{Key: "contract_notice_id", Value: 1}}),
|
||||||
*mongopkg.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}),
|
*orm.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}),
|
||||||
*mongopkg.NewIndex("source_idx", bson.D{{Key: "source", Value: 1}}),
|
*orm.NewIndex("source_idx", bson.D{{Key: "source", Value: 1}}),
|
||||||
*mongopkg.NewIndex("country_code_idx", bson.D{{Key: "country_code", Value: 1}}),
|
*orm.NewIndex("country_code_idx", bson.D{{Key: "country_code", Value: 1}}),
|
||||||
*mongopkg.NewIndex("notice_type_code_idx", bson.D{{Key: "notice_type_code", Value: 1}}),
|
*orm.NewIndex("notice_type_code_idx", bson.D{{Key: "notice_type_code", Value: 1}}),
|
||||||
*mongopkg.NewIndex("procurement_type_code_idx", bson.D{{Key: "procurement_type_code", Value: 1}}),
|
*orm.NewIndex("procurement_type_code_idx", bson.D{{Key: "procurement_type_code", Value: 1}}),
|
||||||
*mongopkg.NewIndex("main_classification_idx", bson.D{{Key: "main_classification", Value: 1}}),
|
*orm.NewIndex("main_classification_idx", bson.D{{Key: "main_classification", Value: 1}}),
|
||||||
*mongopkg.NewIndex("publication_date_idx", bson.D{{Key: "publication_date", Value: -1}}),
|
*orm.NewIndex("publication_date_idx", bson.D{{Key: "publication_date", Value: -1}}),
|
||||||
*mongopkg.NewIndex("tender_deadline_idx", bson.D{{Key: "tender_deadline", Value: 1}}),
|
*orm.NewIndex("tender_deadline_idx", bson.D{{Key: "tender_deadline", Value: 1}}),
|
||||||
*mongopkg.NewIndex("estimated_value_idx", bson.D{{Key: "estimated_value", Value: -1}}),
|
*orm.NewIndex("estimated_value_idx", bson.D{{Key: "estimated_value", Value: -1}}),
|
||||||
*mongopkg.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
|
*orm.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
|
||||||
*mongopkg.NewIndex("updated_at_idx", bson.D{{Key: "updated_at", Value: -1}}),
|
*orm.NewIndex("updated_at_idx", bson.D{{Key: "updated_at", Value: -1}}),
|
||||||
// Compound indexes for common queries
|
// Compound indexes for common queries
|
||||||
*mongopkg.NewIndex("status_country_idx", bson.D{
|
*orm.NewIndex("status_country_idx", bson.D{
|
||||||
{Key: "status", Value: 1},
|
{Key: "status", Value: 1},
|
||||||
{Key: "country_code", Value: 1},
|
{Key: "country_code", Value: 1},
|
||||||
}),
|
}),
|
||||||
*mongopkg.NewIndex("notice_type_status_idx", bson.D{
|
*orm.NewIndex("notice_type_status_idx", bson.D{
|
||||||
{Key: "notice_type_code", Value: 1},
|
{Key: "notice_type_code", Value: 1},
|
||||||
{Key: "status", Value: 1},
|
{Key: "status", Value: 1},
|
||||||
}),
|
}),
|
||||||
// Index for CPV matching queries
|
// Index for CPV matching queries
|
||||||
*mongopkg.NewIndex("cpv_matching_idx", bson.D{
|
*orm.NewIndex("cpv_matching_idx", bson.D{
|
||||||
{Key: "status", Value: 1},
|
{Key: "status", Value: 1},
|
||||||
{Key: "main_classification", Value: 1},
|
{Key: "main_classification", Value: 1},
|
||||||
{Key: "additional_classifications", Value: 1},
|
{Key: "additional_classifications", Value: 1},
|
||||||
{Key: "publication_date", Value: -1},
|
{Key: "publication_date", Value: -1},
|
||||||
}),
|
}),
|
||||||
// Text index for search
|
// Text index for search
|
||||||
*mongopkg.CreateTextIndex("search_idx", "title", "description"),
|
*orm.CreateTextIndex("search_idx", "title", "description"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create indexes
|
// Create indexes
|
||||||
if err := mongoManager.CreateIndexes("tenders", tenderIndexes); err != nil {
|
if err := mongoManager.CreateIndexes(collectionName(), tenderIndexes); err != nil {
|
||||||
logger.Warn("Failed to create tender indexes", map[string]interface{}{
|
logger.Warn("Failed to create tender indexes", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create ORM repositories
|
// Create ORM repositories
|
||||||
tenderOrmRepo := mongopkg.NewRepository[Tender](mongoManager.GetCollection("tenders"), logger)
|
tenderOrmRepo := orm.NewRepository[Tender](mongoManager.GetCollection(collectionName()), logger)
|
||||||
|
|
||||||
return &tenderRepository{
|
return &tenderRepository{
|
||||||
ormRepo: tenderOrmRepo,
|
ormRepo: tenderOrmRepo,
|
||||||
@@ -102,6 +95,9 @@ func NewTenderRepository(mongoManager *mongopkg.ConnectionManager, logger logger
|
|||||||
|
|
||||||
// Create creates a new tender
|
// Create creates a new tender
|
||||||
func (r *tenderRepository) Create(ctx context.Context, tender *Tender) error {
|
func (r *tenderRepository) Create(ctx context.Context, tender *Tender) error {
|
||||||
|
now := time.Now().Unix()
|
||||||
|
tender.SetCreatedAt(now)
|
||||||
|
|
||||||
err := r.ormRepo.Create(ctx, tender)
|
err := r.ormRepo.Create(ctx, tender)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logger.Error("Failed to create tender", map[string]interface{}{
|
r.logger.Error("Failed to create tender", map[string]interface{}{
|
||||||
@@ -136,7 +132,7 @@ func (r *tenderRepository) GetByID(ctx context.Context, id string) (*Tender, err
|
|||||||
// GetByContractNoticeID retrieves a tender by contract notice ID
|
// GetByContractNoticeID retrieves a tender by contract notice ID
|
||||||
func (r *tenderRepository) GetByContractNoticeID(ctx context.Context, contractNoticeID string) (*Tender, error) {
|
func (r *tenderRepository) GetByContractNoticeID(ctx context.Context, contractNoticeID string) (*Tender, error) {
|
||||||
filter := bson.M{"contract_notice_id": contractNoticeID}
|
filter := bson.M{"contract_notice_id": contractNoticeID}
|
||||||
pagination := mongopkg.Pagination{Limit: 1}
|
pagination := orm.Pagination{Limit: 1}
|
||||||
|
|
||||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -148,7 +144,7 @@ func (r *tenderRepository) GetByContractNoticeID(ctx context.Context, contractNo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(result.Items) == 0 {
|
if len(result.Items) == 0 {
|
||||||
return nil, mongopkg.ErrDocumentNotFound
|
return nil, orm.ErrDocumentNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return &result.Items[0], nil
|
return &result.Items[0], nil
|
||||||
@@ -157,7 +153,7 @@ func (r *tenderRepository) GetByContractNoticeID(ctx context.Context, contractNo
|
|||||||
// GetByNoticePublicationID retrieves a tender by notice publication ID
|
// GetByNoticePublicationID retrieves a tender by notice publication ID
|
||||||
func (r *tenderRepository) GetByNoticePublicationID(ctx context.Context, noticePublicationID string) (*Tender, error) {
|
func (r *tenderRepository) GetByNoticePublicationID(ctx context.Context, noticePublicationID string) (*Tender, error) {
|
||||||
filter := bson.M{"notice_publication_id": noticePublicationID}
|
filter := bson.M{"notice_publication_id": noticePublicationID}
|
||||||
pagination := mongopkg.Pagination{Limit: 1}
|
pagination := orm.Pagination{Limit: 1}
|
||||||
|
|
||||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -169,7 +165,7 @@ func (r *tenderRepository) GetByNoticePublicationID(ctx context.Context, noticeP
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(result.Items) == 0 {
|
if len(result.Items) == 0 {
|
||||||
return nil, mongopkg.ErrDocumentNotFound
|
return nil, orm.ErrDocumentNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return &result.Items[0], nil
|
return &result.Items[0], nil
|
||||||
@@ -213,28 +209,31 @@ func (r *tenderRepository) Delete(ctx context.Context, id string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// List retrieves tenders with pagination and filtering
|
// Search searches tenders based on criteria
|
||||||
func (r *tenderRepository) List(ctx context.Context, criteria TenderSearchCriteria, limit, offset int, from *int64, to *int64) ([]Tender, int64, error) {
|
func (r *tenderRepository) Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) ([]Tender, int64, error) {
|
||||||
filter := r.buildSearchFilter(criteria)
|
filter := r.buildSearchFilter(form)
|
||||||
|
|
||||||
pagination := mongopkg.Pagination{
|
// Build sort
|
||||||
Limit: limit,
|
sort := "created_at"
|
||||||
Skip: offset,
|
if pagination.SortBy != "" {
|
||||||
SortField: "publication_date",
|
sort = pagination.SortBy
|
||||||
SortOrder: -1,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if from != nil {
|
sortOrder := "desc"
|
||||||
filter["publication_date"] = bson.M{"$gte": *from}
|
if pagination.SortOrder != "" {
|
||||||
|
sortOrder = pagination.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
if to != nil {
|
// Build pagination
|
||||||
filter["publication_date"] = bson.M{"$lte": *to}
|
paginationBuilder := orm.NewPaginationBuilder().
|
||||||
}
|
Limit(pagination.Limit).
|
||||||
|
Skip(pagination.Offset).
|
||||||
|
SortBy(sort, sortOrder).
|
||||||
|
Build()
|
||||||
|
|
||||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logger.Error("Failed to list tenders", map[string]interface{}{
|
r.logger.Error("Failed to search tenders", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
@@ -243,123 +242,6 @@ func (r *tenderRepository) List(ctx context.Context, criteria TenderSearchCriter
|
|||||||
return result.Items, result.TotalCount, nil
|
return result.Items, result.TotalCount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateBatch creates multiple tenders in a single operation
|
|
||||||
func (r *tenderRepository) CreateBatch(ctx context.Context, tenders []Tender) error {
|
|
||||||
if len(tenders) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range tenders {
|
|
||||||
if err := r.ormRepo.Create(ctx, &tenders[i]); err != nil {
|
|
||||||
r.logger.Error("Failed to create tender in batch", map[string]interface{}{
|
|
||||||
"tender_id": tenders[i].ID,
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.logger.Info("Tender batch created successfully", map[string]interface{}{
|
|
||||||
"count": len(tenders),
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateBatch updates multiple tenders
|
|
||||||
func (r *tenderRepository) UpdateBatch(ctx context.Context, tenders []Tender) error {
|
|
||||||
if len(tenders) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now().Unix()
|
|
||||||
|
|
||||||
for i := range tenders {
|
|
||||||
tenders[i].UpdatedAt = now
|
|
||||||
|
|
||||||
if err := r.ormRepo.Update(ctx, &tenders[i]); err != nil {
|
|
||||||
r.logger.Error("Failed to update tender in batch", map[string]interface{}{
|
|
||||||
"tender_id": tenders[i].ID,
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.logger.Info("Tender batch updated successfully", map[string]interface{}{
|
|
||||||
"count": len(tenders),
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search searches tenders based on criteria
|
|
||||||
func (r *tenderRepository) Search(ctx context.Context, criteria TenderSearchCriteria) ([]Tender, error) {
|
|
||||||
filter := r.buildSearchFilter(criteria)
|
|
||||||
|
|
||||||
pagination := mongopkg.Pagination{
|
|
||||||
Limit: 100,
|
|
||||||
SortField: "created_at",
|
|
||||||
SortOrder: -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Error("Failed to search tenders", map[string]interface{}{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.Items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetByCountryCode retrieves tenders by country code
|
|
||||||
func (r *tenderRepository) GetByCountryCode(ctx context.Context, countryCode string, limit, offset int) ([]Tender, error) {
|
|
||||||
filter := bson.M{"country_code": countryCode}
|
|
||||||
|
|
||||||
pagination := mongopkg.Pagination{
|
|
||||||
Limit: limit,
|
|
||||||
Skip: offset,
|
|
||||||
SortField: "created_at",
|
|
||||||
SortOrder: -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Error("Failed to get tenders by country code", map[string]interface{}{
|
|
||||||
"country_code": countryCode,
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.Items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetByStatus retrieves tenders by status
|
|
||||||
func (r *tenderRepository) GetByStatus(ctx context.Context, status TenderStatus, limit, offset int) ([]Tender, error) {
|
|
||||||
filter := bson.M{"status": status}
|
|
||||||
|
|
||||||
pagination := mongopkg.Pagination{
|
|
||||||
Limit: limit,
|
|
||||||
Skip: offset,
|
|
||||||
SortField: "created_at",
|
|
||||||
SortOrder: -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Error("Failed to get tenders by status", map[string]interface{}{
|
|
||||||
"status": status,
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.Items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetExpiredTenders retrieves tenders that are expired
|
// GetExpiredTenders retrieves tenders that are expired
|
||||||
func (r *tenderRepository) GetExpiredTenders(ctx context.Context, beforeDate int64) ([]Tender, error) {
|
func (r *tenderRepository) GetExpiredTenders(ctx context.Context, beforeDate int64) ([]Tender, error) {
|
||||||
filter := bson.M{
|
filter := bson.M{
|
||||||
@@ -374,7 +256,7 @@ func (r *tenderRepository) GetExpiredTenders(ctx context.Context, beforeDate int
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pagination := mongopkg.Pagination{
|
pagination := orm.Pagination{
|
||||||
Limit: 1000, // Large limit for maintenance operations
|
Limit: 1000, // Large limit for maintenance operations
|
||||||
SortField: "tender_deadline",
|
SortField: "tender_deadline",
|
||||||
SortOrder: 1,
|
SortOrder: 1,
|
||||||
@@ -392,28 +274,6 @@ func (r *tenderRepository) GetExpiredTenders(ctx context.Context, beforeDate int
|
|||||||
return result.Items, nil
|
return result.Items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetActiveTenders retrieves active tenders
|
|
||||||
func (r *tenderRepository) GetActiveTenders(ctx context.Context, limit, offset int) ([]Tender, error) {
|
|
||||||
filter := bson.M{"status": TenderStatusActive}
|
|
||||||
|
|
||||||
pagination := mongopkg.Pagination{
|
|
||||||
Limit: limit,
|
|
||||||
Skip: offset,
|
|
||||||
SortField: "tender_deadline",
|
|
||||||
SortOrder: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Error("Failed to get active tenders", map[string]interface{}{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.Items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStatistics calculates tender statistics using aggregation
|
// GetStatistics calculates tender statistics using aggregation
|
||||||
func (r *tenderRepository) GetStatistics(ctx context.Context) (*TenderStatistics, error) {
|
func (r *tenderRepository) GetStatistics(ctx context.Context) (*TenderStatistics, error) {
|
||||||
stats := &TenderStatistics{
|
stats := &TenderStatistics{
|
||||||
@@ -557,88 +417,88 @@ func (r *tenderRepository) GetTenderCountByClassification(ctx context.Context) (
|
|||||||
return counts, nil
|
return counts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildSearchFilter builds MongoDB filter from search criteria
|
// buildSearchFilter builds MongoDB filter from search vvvform
|
||||||
func (r *tenderRepository) buildSearchFilter(criteria TenderSearchCriteria) bson.M {
|
func (r *tenderRepository) buildSearchFilter(form *SearchForm) bson.M {
|
||||||
filter := bson.M{}
|
filter := bson.M{}
|
||||||
|
|
||||||
if criteria.Query != "" {
|
if form.Search != nil {
|
||||||
filter["$text"] = bson.M{"$search": criteria.Query}
|
filter["$text"] = bson.M{"$search": *form.Search}
|
||||||
}
|
}
|
||||||
|
|
||||||
if criteria.NoticeType != "" {
|
if form.NoticeType != nil {
|
||||||
filter["notice_type_code"] = criteria.NoticeType
|
filter["notice_type_code"] = form.NoticeType
|
||||||
}
|
}
|
||||||
|
|
||||||
if criteria.ProcurementType != "" {
|
if form.ProcurementType != nil {
|
||||||
filter["procurement_type_code"] = criteria.ProcurementType
|
filter["procurement_type_code"] = form.ProcurementType
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(criteria.CountryCodes) > 0 {
|
if len(form.CountryCodes) > 0 {
|
||||||
filter["country_code"] = bson.M{"$in": criteria.CountryCodes}
|
filter["country_code"] = bson.M{"$in": form.CountryCodes}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(criteria.RegionCodes) > 0 {
|
if len(form.RegionCodes) > 0 {
|
||||||
filter["region_code"] = bson.M{"$in": criteria.RegionCodes}
|
filter["region_code"] = bson.M{"$in": form.RegionCodes}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(criteria.Classifications) > 0 {
|
if len(form.Classifications) > 0 {
|
||||||
filter["$or"] = []bson.M{
|
filter["$or"] = []bson.M{
|
||||||
{"main_classification": bson.M{"$in": criteria.Classifications}},
|
{"main_classification": bson.M{"$in": form.Classifications}},
|
||||||
{"additional_classifications": bson.M{"$in": criteria.Classifications}},
|
{"additional_classifications": bson.M{"$in": form.Classifications}},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if criteria.MinEstimatedValue != nil || criteria.MaxEstimatedValue != nil {
|
if form.MinEstimatedValue != nil || form.MaxEstimatedValue != nil {
|
||||||
valueFilter := bson.M{}
|
valueFilter := bson.M{}
|
||||||
if criteria.MinEstimatedValue != nil {
|
if form.MinEstimatedValue != nil {
|
||||||
valueFilter["$gte"] = *criteria.MinEstimatedValue
|
valueFilter["$gte"] = *form.MinEstimatedValue
|
||||||
}
|
}
|
||||||
if criteria.MaxEstimatedValue != nil {
|
if form.MaxEstimatedValue != nil {
|
||||||
valueFilter["$lte"] = *criteria.MaxEstimatedValue
|
valueFilter["$lte"] = *form.MaxEstimatedValue
|
||||||
}
|
}
|
||||||
filter["estimated_value"] = valueFilter
|
filter["estimated_value"] = valueFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
if criteria.Currency != "" {
|
if form.Currency != "" {
|
||||||
filter["currency"] = criteria.Currency
|
filter["currency"] = form.Currency
|
||||||
}
|
}
|
||||||
|
|
||||||
if criteria.DeadlineFrom != nil || criteria.DeadlineTo != nil {
|
if form.DeadlineFrom != nil || form.DeadlineTo != nil {
|
||||||
deadlineFilter := bson.M{}
|
deadlineFilter := bson.M{}
|
||||||
if criteria.DeadlineFrom != nil {
|
if form.DeadlineFrom != nil {
|
||||||
deadlineFilter["$gte"] = *criteria.DeadlineFrom
|
deadlineFilter["$gte"] = *form.DeadlineFrom
|
||||||
}
|
}
|
||||||
if criteria.DeadlineTo != nil {
|
if form.DeadlineTo != nil {
|
||||||
deadlineFilter["$lte"] = *criteria.DeadlineTo
|
deadlineFilter["$lte"] = *form.DeadlineTo
|
||||||
}
|
}
|
||||||
filter["tender_deadline"] = deadlineFilter
|
filter["tender_deadline"] = deadlineFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
if criteria.PublicationDateFrom != nil || criteria.PublicationDateTo != nil {
|
if form.PublicationDateFrom != nil || form.PublicationDateTo != nil {
|
||||||
pubDateFilter := bson.M{}
|
pubDateFilter := bson.M{}
|
||||||
if criteria.PublicationDateFrom != nil {
|
if form.PublicationDateFrom != nil {
|
||||||
pubDateFilter["$gte"] = *criteria.PublicationDateFrom
|
pubDateFilter["$gte"] = *form.PublicationDateFrom
|
||||||
}
|
}
|
||||||
if criteria.PublicationDateTo != nil {
|
if form.PublicationDateTo != nil {
|
||||||
pubDateFilter["$lte"] = *criteria.PublicationDateTo
|
pubDateFilter["$lte"] = *form.PublicationDateTo
|
||||||
}
|
}
|
||||||
filter["publication_date"] = pubDateFilter
|
filter["publication_date"] = pubDateFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(criteria.Status) > 0 {
|
if len(form.Status) > 0 {
|
||||||
filter["status"] = bson.M{"$in": criteria.Status}
|
filter["status"] = bson.M{"$in": form.Status}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(criteria.Source) > 0 {
|
if len(form.Source) > 0 {
|
||||||
filter["source"] = bson.M{"$in": criteria.Source}
|
filter["source"] = bson.M{"$in": form.Source}
|
||||||
}
|
}
|
||||||
|
|
||||||
if criteria.BuyerOrganizationID != "" {
|
if form.BuyerOrganizationID != nil {
|
||||||
filter["buyer_organization.id"] = criteria.BuyerOrganizationID
|
filter["buyer_organization.id"] = *form.BuyerOrganizationID
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(criteria.Languages) > 0 {
|
if len(form.Languages) > 0 {
|
||||||
filter["notice_language_code"] = bson.M{"$in": criteria.Languages}
|
filter["notice_language_code"] = bson.M{"$in": form.Languages}
|
||||||
}
|
}
|
||||||
|
|
||||||
return filter
|
return filter
|
||||||
|
|||||||
+56
-83
@@ -3,7 +3,6 @@ package tender
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"time"
|
"time"
|
||||||
"tm/internal/company"
|
"tm/internal/company"
|
||||||
"tm/pkg/logger"
|
"tm/pkg/logger"
|
||||||
@@ -11,17 +10,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// TenderService interface defines tender business logic operations
|
// TenderService interface defines tender business logic operations
|
||||||
type TenderService interface {
|
type Service interface {
|
||||||
// Tender operations
|
// Tender operations
|
||||||
GetTenderByID(ctx context.Context, id string) (*TenderResponse, error)
|
GetByID(ctx context.Context, id string) (*TenderResponse, error)
|
||||||
UpdateTender(ctx context.Context, req UpdateTenderRequest) (*Tender, error)
|
Update(ctx context.Context, req UpdateTenderRequest) (*TenderResponse, error)
|
||||||
DeleteTender(ctx context.Context, id string) error
|
Delete(ctx context.Context, id string) error
|
||||||
ListTenders(ctx context.Context, req ListTendersRequest) (*ListTendersResponse, error)
|
Search(ctx context.Context, req *SearchForm, pagination *response.Pagination) (*SearchResponse, error)
|
||||||
RecommendTenders(ctx context.Context, companyID *string, from *int64, to *int64, limit, offset int) (*ListTendersResponse, error)
|
Recommend(ctx context.Context, form *SearchForm, pagination *response.Pagination) (*SearchResponse, error)
|
||||||
|
|
||||||
// Maintenance operations
|
// Maintenance operations
|
||||||
UpdateExpiredTenders(ctx context.Context) error
|
UpdateExpiredTenders(ctx context.Context) error
|
||||||
CleanupOldData(ctx context.Context, olderThan time.Duration) error
|
CleanupOldData(ctx context.Context, olderThan time.Duration, pagination *response.Pagination) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// tenderService implements TenderService interface
|
// tenderService implements TenderService interface
|
||||||
@@ -32,7 +31,7 @@ type tenderService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewTenderService creates a new tender service
|
// NewTenderService creates a new tender service
|
||||||
func NewTenderService(repository TenderRepository, companyService company.Service, logger logger.Logger) TenderService {
|
func NewTenderService(repository TenderRepository, companyService company.Service, logger logger.Logger) Service {
|
||||||
return &tenderService{
|
return &tenderService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
companyService: companyService,
|
companyService: companyService,
|
||||||
@@ -41,7 +40,7 @@ func NewTenderService(repository TenderRepository, companyService company.Servic
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetTenderByID retrieves a tender by ID
|
// GetTenderByID retrieves a tender by ID
|
||||||
func (s *tenderService) GetTenderByID(ctx context.Context, id string) (*TenderResponse, error) {
|
func (s *tenderService) GetByID(ctx context.Context, id string) (*TenderResponse, error) {
|
||||||
s.logger.Info("Retrieving tender by ID", map[string]interface{}{
|
s.logger.Info("Retrieving tender by ID", map[string]interface{}{
|
||||||
"tender_id": id,
|
"tender_id": id,
|
||||||
})
|
})
|
||||||
@@ -55,11 +54,11 @@ func (s *tenderService) GetTenderByID(ctx context.Context, id string) (*TenderRe
|
|||||||
return nil, fmt.Errorf("failed to retrieve tender: %w", err)
|
return nil, fmt.Errorf("failed to retrieve tender: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tender.ToTenderResponse(), nil
|
return tender.ToResponse(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateTender updates an existing tender
|
// UpdateTender updates an existing tender
|
||||||
func (s *tenderService) UpdateTender(ctx context.Context, req UpdateTenderRequest) (*Tender, error) {
|
func (s *tenderService) Update(ctx context.Context, req UpdateTenderRequest) (*TenderResponse, error) {
|
||||||
s.logger.Info("Updating tender", map[string]interface{}{
|
s.logger.Info("Updating tender", map[string]interface{}{
|
||||||
"tender_id": req.ID,
|
"tender_id": req.ID,
|
||||||
})
|
})
|
||||||
@@ -106,11 +105,11 @@ func (s *tenderService) UpdateTender(ctx context.Context, req UpdateTenderReques
|
|||||||
"tender_id": tender.ID,
|
"tender_id": tender.ID,
|
||||||
})
|
})
|
||||||
|
|
||||||
return tender, nil
|
return tender.ToResponse(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteTender deletes a tender
|
// Delete deletes a tender
|
||||||
func (s *tenderService) DeleteTender(ctx context.Context, id string) error {
|
func (s *tenderService) Delete(ctx context.Context, id string) error {
|
||||||
s.logger.Info("Deleting tender", map[string]interface{}{
|
s.logger.Info("Deleting tender", map[string]interface{}{
|
||||||
"tender_id": id,
|
"tender_id": id,
|
||||||
})
|
})
|
||||||
@@ -131,23 +130,16 @@ func (s *tenderService) DeleteTender(ctx context.Context, id string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListTenders retrieves tenders with pagination and filtering
|
// ListTenders retrieves tenders with pagination and filtering
|
||||||
func (s *tenderService) ListTenders(ctx context.Context, req ListTendersRequest) (*ListTendersResponse, error) {
|
func (s *tenderService) Search(ctx context.Context, form *SearchForm, pagination *response.Pagination) (*SearchResponse, error) {
|
||||||
s.logger.Info("Listing tenders", map[string]interface{}{
|
s.logger.Info("Listing tenders", map[string]interface{}{
|
||||||
"limit": req.Limit,
|
"limit": pagination.Limit,
|
||||||
"offset": req.Offset,
|
"offset": pagination.Offset,
|
||||||
"company_id": req.CompanyID,
|
"company_id": form.CompanyID,
|
||||||
"from": req.From,
|
"from": form.DeadlineFrom,
|
||||||
"to": req.To,
|
"to": form.DeadlineTo,
|
||||||
})
|
})
|
||||||
|
|
||||||
if req.Limit <= 0 {
|
tenders, total, err := s.repository.Search(ctx, form, pagination)
|
||||||
req.Limit = 20
|
|
||||||
}
|
|
||||||
if req.Limit > 100 {
|
|
||||||
req.Limit = 100
|
|
||||||
}
|
|
||||||
|
|
||||||
tenders, total, err := s.repository.List(ctx, req.Criteria, req.Limit, req.Offset, req.From, req.To)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to list tenders", map[string]interface{}{
|
s.logger.Error("Failed to list tenders", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
@@ -156,43 +148,36 @@ func (s *tenderService) ListTenders(ctx context.Context, req ListTendersRequest)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no company ID provided, return tenders without match percentage
|
// If no company ID provided, return tenders without match percentage
|
||||||
if req.CompanyID == nil || *req.CompanyID == "" {
|
if form.CompanyID == nil || *form.CompanyID == "" {
|
||||||
tenderResponses := make([]TenderResponse, 0, len(tenders))
|
tenderResponses := make([]TenderResponse, 0, len(tenders))
|
||||||
for _, tender := range tenders {
|
for _, tender := range tenders {
|
||||||
tenderResponses = append(tenderResponses, *tender.ToTenderResponse())
|
tenderResponses = append(tenderResponses, *tender.ToResponse())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ListTendersResponse{
|
return &SearchResponse{
|
||||||
Tenders: tenderResponses,
|
Tenders: tenderResponses,
|
||||||
Metadata: &response.Meta{
|
Metadata: pagination.Response(total),
|
||||||
Total: int(total),
|
|
||||||
Limit: req.Limit,
|
|
||||||
Offset: req.Offset,
|
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get company CPV codes for match calculation
|
// Get company CPV codes for match calculation
|
||||||
comp, err := s.companyService.GetByID(ctx, *req.CompanyID)
|
comp, err := s.companyService.GetByID(ctx, *form.CompanyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get company for match calculation", map[string]interface{}{
|
s.logger.Error("Failed to get company for match calculation", map[string]interface{}{
|
||||||
"company_id": *req.CompanyID,
|
"company_id": *form.CompanyID,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
// Continue without match percentage if company not found
|
// Continue without match percentage if company not found
|
||||||
tenderResponses := make([]TenderResponse, 0, len(tenders))
|
tenderResponses := make([]TenderResponse, 0, len(tenders))
|
||||||
for _, tender := range tenders {
|
for _, tender := range tenders {
|
||||||
tenderResponses = append(tenderResponses, *tender.ToTenderResponse())
|
tenderResponses = append(tenderResponses, *tender.ToResponse())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ListTendersResponse{
|
return &SearchResponse{
|
||||||
Tenders: tenderResponses,
|
Tenders: tenderResponses,
|
||||||
Metadata: &response.Meta{
|
Metadata: pagination.Response(total),
|
||||||
Total: int(total),
|
|
||||||
Limit: req.Limit,
|
|
||||||
Offset: req.Offset,
|
|
||||||
},
|
},
|
||||||
}, nil
|
nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var companyCPVCodes []string
|
var companyCPVCodes []string
|
||||||
@@ -201,52 +186,42 @@ func (s *tenderService) ListTenders(ctx context.Context, req ListTendersRequest)
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Info("Tenders sorted by match percentage", map[string]interface{}{
|
s.logger.Info("Tenders sorted by match percentage", map[string]interface{}{
|
||||||
"company_id": *req.CompanyID,
|
"company_id": *form.CompanyID,
|
||||||
"company_cpv_codes": len(companyCPVCodes),
|
"company_cpv_codes": len(companyCPVCodes),
|
||||||
"total_tenders": len(tenders),
|
"total_tenders": len(tenders),
|
||||||
})
|
})
|
||||||
|
|
||||||
tenderResponses := make([]TenderResponse, 0, len(tenders))
|
tenderResponses := make([]TenderResponse, 0, len(tenders))
|
||||||
for _, tender := range tenders {
|
for _, tender := range tenders {
|
||||||
tenderResponses = append(tenderResponses, *tender.ToTenderResponse())
|
tenderResponses = append(tenderResponses, *tender.ToResponse())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ListTendersResponse{
|
return &SearchResponse{
|
||||||
Tenders: tenderResponses,
|
Tenders: tenderResponses,
|
||||||
Metadata: &response.Meta{
|
Metadata: pagination.Response(total),
|
||||||
Total: int(total),
|
|
||||||
Limit: req.Limit,
|
|
||||||
Offset: req.Offset,
|
|
||||||
},
|
},
|
||||||
}, nil
|
nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTenders retrieves tenders with pagination and filtering
|
// Recommend retrieves tenders with pagination and filtering
|
||||||
func (s *tenderService) RecommendTenders(ctx context.Context, companyID *string, from *int64, to *int64, limit, offset int) (*ListTendersResponse, error) {
|
func (s *tenderService) Recommend(ctx context.Context, form *SearchForm, pagination *response.Pagination) (*SearchResponse, error) {
|
||||||
s.logger.Info("Recommend tenders", map[string]interface{}{
|
s.logger.Info("Recommend tenders", map[string]interface{}{
|
||||||
"company_id": companyID,
|
"company_id": form.CompanyID,
|
||||||
})
|
})
|
||||||
|
|
||||||
if limit <= 0 {
|
company, err := s.companyService.GetByID(ctx, *form.CompanyID)
|
||||||
limit = 20
|
|
||||||
}
|
|
||||||
if limit > 100 {
|
|
||||||
limit = 100
|
|
||||||
}
|
|
||||||
|
|
||||||
company, err := s.companyService.GetByID(ctx, *companyID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get company", map[string]interface{}{
|
s.logger.Error("Failed to get company", map[string]interface{}{
|
||||||
"company_id": *companyID,
|
"company_id": form.CompanyID,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return nil, fmt.Errorf("failed to get company: %w", err)
|
return nil, fmt.Errorf("failed to get company: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tenders, total, err := s.repository.List(ctx, TenderSearchCriteria{
|
form.Status = []string{string(TenderStatusActive)}
|
||||||
Status: []TenderStatus{TenderStatusActive},
|
form.Classifications = company.Tags.CPVCodes
|
||||||
Classifications: company.Tags.CPVCodes,
|
|
||||||
}, limit, offset, from, to)
|
tenders, total, err := s.repository.Search(ctx, form, pagination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to list tenders", map[string]interface{}{
|
s.logger.Error("Failed to list tenders", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
@@ -256,21 +231,17 @@ func (s *tenderService) RecommendTenders(ctx context.Context, companyID *string,
|
|||||||
|
|
||||||
tenderResponses := make([]TenderResponse, 0, len(tenders))
|
tenderResponses := make([]TenderResponse, 0, len(tenders))
|
||||||
for _, tender := range tenders {
|
for _, tender := range tenders {
|
||||||
tenderResponses = append(tenderResponses, *tender.ToTenderResponse())
|
tenderResponses = append(tenderResponses, *tender.ToResponse())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ListTendersResponse{
|
return &SearchResponse{
|
||||||
Tenders: tenderResponses,
|
Tenders: tenderResponses,
|
||||||
Metadata: &response.Meta{
|
Metadata: pagination.Response(total),
|
||||||
Total: int(total),
|
|
||||||
Limit: limit,
|
|
||||||
Offset: offset,
|
|
||||||
Pages: int(math.Ceil(float64(total) / float64(limit))),
|
|
||||||
Page: int(math.Ceil(float64(offset) / float64(limit))),
|
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete deletes a tender
|
||||||
|
|
||||||
// GetTenderStatistics retrieves tender statistics
|
// GetTenderStatistics retrieves tender statistics
|
||||||
func (s *tenderService) GetTenderStatistics(ctx context.Context) (*TenderStatistics, error) {
|
func (s *tenderService) GetTenderStatistics(ctx context.Context) (*TenderStatistics, error) {
|
||||||
s.logger.Info("Retrieving tender statistics", map[string]interface{}{})
|
s.logger.Info("Retrieving tender statistics", map[string]interface{}{})
|
||||||
@@ -323,20 +294,22 @@ func (s *tenderService) UpdateExpiredTenders(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CleanupOldData removes old data based on retention policy
|
// CleanupOldData removes old data based on retention policy
|
||||||
func (s *tenderService) CleanupOldData(ctx context.Context, olderThan time.Duration) error {
|
func (s *tenderService) CleanupOldData(ctx context.Context, olderThan time.Duration, pagination *response.Pagination) error {
|
||||||
s.logger.Info("Cleaning up old data", map[string]interface{}{
|
s.logger.Info("Cleaning up old data", map[string]interface{}{
|
||||||
"older_than": olderThan.String(),
|
"older_than": olderThan.String(),
|
||||||
|
"limit": pagination.Limit,
|
||||||
|
"offset": pagination.Offset,
|
||||||
})
|
})
|
||||||
|
|
||||||
cutoffTime := time.Now().Add(-olderThan).Unix()
|
cutoffTime := time.Now().Add(-olderThan).Unix()
|
||||||
|
|
||||||
// For now, we'll just log what would be cleaned up
|
// For now, we'll just log what would be cleaned up
|
||||||
// In production, you might want to actually delete old data
|
// In production, you might want to actually delete old data
|
||||||
criteria := TenderSearchCriteria{
|
form := &SearchForm{
|
||||||
Status: []TenderStatus{TenderStatusExpired, TenderStatusCancelled, TenderStatusAwarded},
|
Status: []string{string(TenderStatusExpired), string(TenderStatusCancelled), string(TenderStatusAwarded)},
|
||||||
}
|
}
|
||||||
|
|
||||||
oldTenders, err := s.repository.Search(ctx, criteria)
|
oldTenders, _, err := s.repository.Search(ctx, form, pagination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to find old tenders for cleanup", map[string]interface{}{
|
s.logger.Error("Failed to find old tenders for cleanup", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ type Service interface {
|
|||||||
// tenderApprovalService implements the Service interface
|
// tenderApprovalService implements the Service interface
|
||||||
type tenderApprovalService struct {
|
type tenderApprovalService struct {
|
||||||
repository Repository
|
repository Repository
|
||||||
tenderService tender.TenderService
|
tenderService tender.Service
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTenderApprovalService creates a new tender approval service
|
// NewTenderApprovalService creates a new tender approval service
|
||||||
func NewTenderApprovalService(repository Repository, tenderService tender.TenderService, logger logger.Logger) Service {
|
func NewTenderApprovalService(repository Repository, tenderService tender.Service, logger logger.Logger) Service {
|
||||||
return &tenderApprovalService{
|
return &tenderApprovalService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
tenderService: tenderService,
|
tenderService: tenderService,
|
||||||
@@ -134,7 +134,7 @@ func (s *tenderApprovalService) GetTenderApprovalByIDWithTender(ctx context.Cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get tender details
|
// Get tender details
|
||||||
tenderResponse, err := s.tenderService.GetTenderByID(ctx, tenderApproval.TenderID)
|
tenderResponse, err := s.tenderService.GetByID(ctx, tenderApproval.TenderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("Failed to get tender details", map[string]interface{}{
|
s.logger.Warn("Failed to get tender details", map[string]interface{}{
|
||||||
"tender_id": tenderApproval.TenderID,
|
"tender_id": tenderApproval.TenderID,
|
||||||
@@ -205,7 +205,7 @@ func (s *tenderApprovalService) GetTenderApprovalByTenderAndCompanyWithTender(ct
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get tender details
|
// Get tender details
|
||||||
tenderResponse, err := s.tenderService.GetTenderByID(ctx, tenderApproval.TenderID)
|
tenderResponse, err := s.tenderService.GetByID(ctx, tenderApproval.TenderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("Failed to get tender details", map[string]interface{}{
|
s.logger.Warn("Failed to get tender details", map[string]interface{}{
|
||||||
"tender_id": tenderApproval.TenderID,
|
"tender_id": tenderApproval.TenderID,
|
||||||
@@ -299,7 +299,7 @@ func (s *tenderApprovalService) GetTenderApprovalsByCompanyIDWithTender(ctx cont
|
|||||||
}
|
}
|
||||||
for _, approval := range tenderApprovals {
|
for _, approval := range tenderApprovals {
|
||||||
// Get tender details
|
// Get tender details
|
||||||
tenderResponse, err := s.tenderService.GetTenderByID(ctx, approval.TenderID)
|
tenderResponse, err := s.tenderService.GetByID(ctx, approval.TenderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("Failed to get tender details", map[string]interface{}{
|
s.logger.Warn("Failed to get tender details", map[string]interface{}{
|
||||||
"tender_id": approval.TenderID,
|
"tender_id": approval.TenderID,
|
||||||
@@ -444,7 +444,7 @@ func (s *tenderApprovalService) UpdateTenderApprovalWithTender(ctx context.Conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get tender details
|
// Get tender details
|
||||||
tenderResponse, err := s.tenderService.GetTenderByID(ctx, tenderApproval.TenderID)
|
tenderResponse, err := s.tenderService.GetByID(ctx, tenderApproval.TenderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("Failed to get tender details", map[string]interface{}{
|
s.logger.Warn("Failed to get tender details", map[string]interface{}{
|
||||||
"tender_id": tenderApproval.TenderID,
|
"tender_id": tenderApproval.TenderID,
|
||||||
@@ -784,7 +784,7 @@ func (s *tenderApprovalService) GetTenderApprovalsByStatusWithTender(ctx context
|
|||||||
var responses []*TenderApprovalWithTenderResponse
|
var responses []*TenderApprovalWithTenderResponse
|
||||||
for _, approval := range tenderApprovals {
|
for _, approval := range tenderApprovals {
|
||||||
// Get tender details
|
// Get tender details
|
||||||
tenderResponse, err := s.tenderService.GetTenderByID(ctx, approval.TenderID)
|
tenderResponse, err := s.tenderService.GetByID(ctx, approval.TenderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("Failed to get tender details", map[string]interface{}{
|
s.logger.Warn("Failed to get tender details", map[string]interface{}{
|
||||||
"tender_id": approval.TenderID,
|
"tender_id": approval.TenderID,
|
||||||
@@ -832,7 +832,7 @@ func (s *tenderApprovalService) GetTenderApprovalsBySubmissionModeWithTender(ctx
|
|||||||
var responses []*TenderApprovalWithTenderResponse
|
var responses []*TenderApprovalWithTenderResponse
|
||||||
for _, approval := range tenderApprovals {
|
for _, approval := range tenderApprovals {
|
||||||
// Get tender details
|
// Get tender details
|
||||||
tenderResponse, err := s.tenderService.GetTenderByID(ctx, approval.TenderID)
|
tenderResponse, err := s.tenderService.GetByID(ctx, approval.TenderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("Failed to get tender details", map[string]interface{}{
|
s.logger.Warn("Failed to get tender details", map[string]interface{}{
|
||||||
"tender_id": approval.TenderID,
|
"tender_id": approval.TenderID,
|
||||||
@@ -930,7 +930,7 @@ func (s *tenderApprovalService) ListTenderApprovalsWithTender(ctx context.Contex
|
|||||||
var responses []*TenderApprovalWithTenderResponse
|
var responses []*TenderApprovalWithTenderResponse
|
||||||
for _, approval := range tenderApprovals {
|
for _, approval := range tenderApprovals {
|
||||||
// Get tender details
|
// Get tender details
|
||||||
tenderResponse, err := s.tenderService.GetTenderByID(ctx, approval.TenderID)
|
tenderResponse, err := s.tenderService.GetByID(ctx, approval.TenderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("Failed to get tender details", map[string]interface{}{
|
s.logger.Warn("Failed to get tender details", map[string]interface{}{
|
||||||
"tender_id": approval.TenderID,
|
"tender_id": approval.TenderID,
|
||||||
|
|||||||
Reference in New Issue
Block a user