diff --git a/cmd/web/docs/docs.go b/cmd/web/docs/docs.go index c321011..3111882 100644 --- a/cmd/web/docs/docs.go +++ b/cmd/web/docs/docs.go @@ -3890,6 +3890,432 @@ const docTemplate = `{ } } }, + "/admin/v1/tender-approvals": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve a paginated list of tender approvals with filtering options", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Admin-TenderApprovals" + ], + "summary": "List tender approvals with filters and pagination", + "parameters": [ + { + "type": "string", + "description": "Filter by tender ID", + "name": "tender_id", + "in": "query" + }, + { + "type": "string", + "description": "Filter by company ID", + "name": "company_id", + "in": "query" + }, + { + "type": "array", + "description": "Filter by status", + "name": "status", + "in": "query" + }, + { + "type": "array", + "description": "Filter by submission mode", + "name": "submission_mode", + "in": "query" + }, + { + "type": "integer", + "description": "Filter by creation date from (Unix timestamp)", + "name": "created_at_from", + "in": "query" + }, + { + "type": "integer", + "description": "Filter by creation date to (Unix timestamp)", + "name": "created_at_to", + "in": "query" + }, + { + "maximum": 100, + "minimum": 1, + "type": "integer", + "default": 20, + "description": "Number of approvals per page (1-100)", + "name": "limit", + "in": "query" + }, + { + "minimum": 0, + "type": "integer", + "default": 0, + "description": "Number of approvals to skip for pagination", + "name": "offset", + "in": "query" + }, + { + "enum": [ + "created_at", + "updated_at", + "status", + "submission_mode" + ], + "type": "string", + "default": "created_at", + "description": "Field to sort by", + "name": "sort_by", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "default": "desc", + "description": "Sort order", + "name": "sort_order", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Tender approvals retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/tender_approval.TenderApprovalListResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid query parameters", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "422": { + "description": "Validation error - Invalid query parameters", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/admin/v1/tender-approvals/stats": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get comprehensive tender approval statistics for dashboard", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Admin-TenderApprovals" + ], + "summary": "Get tender approval statistics", + "responses": { + "200": { + "description": "Tender approval statistics retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/tender_approval.TenderApprovalStatsResponse" + } + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/admin/v1/tender-approvals/status/{status}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve tender approvals filtered by their status (approved, rejected)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Admin-TenderApprovals" + ], + "summary": "Get tender approvals by status", + "parameters": [ + { + "enum": [ + "approved", + "rejected" + ], + "type": "string", + "description": "Tender approval status", + "name": "status", + "in": "path", + "required": true + }, + { + "maximum": 100, + "minimum": 1, + "type": "integer", + "default": 20, + "description": "Number of approvals per page (1-100)", + "name": "limit", + "in": "query" + }, + { + "minimum": 0, + "type": "integer", + "default": 0, + "description": "Number of approvals to skip for pagination", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Tender approvals retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + }, + "meta": { + "$ref": "#/definitions/response.Meta" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid tender approval status", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/admin/v1/tender-approvals/submission-mode/{submission_mode}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve tender approvals filtered by their submission mode (self-apply, partnership)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Admin-TenderApprovals" + ], + "summary": "Get tender approvals by submission mode", + "parameters": [ + { + "enum": [ + "self-apply", + "partnership" + ], + "type": "string", + "description": "Tender approval submission mode", + "name": "submission_mode", + "in": "path", + "required": true + }, + { + "maximum": 100, + "minimum": 1, + "type": "integer", + "default": 20, + "description": "Number of approvals per page (1-100)", + "name": "limit", + "in": "query" + }, + { + "minimum": 0, + "type": "integer", + "default": 0, + "description": "Number of approvals to skip for pagination", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Tender approvals retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + }, + "meta": { + "$ref": "#/definitions/response.Meta" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid tender approval submission mode", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/admin/v1/tender-approvals/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve detailed tender approval information by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "TenderApprovals" + ], + "summary": "Get tender approval by ID", + "parameters": [ + { + "type": "string", + "description": "Tender Approval ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Tender approval retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid tender approval ID", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "404": { + "description": "Not found - Tender approval not found", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/admin/v1/users": { "get": { "security": [ @@ -5406,6 +5832,423 @@ const docTemplate = `{ } } }, + "/api/v1/tender-approvals": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve all tender approvals for a specific company with optional filtering", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "TenderApprovals" + ], + "summary": "Get tender approvals by company ID", + "parameters": [ + { + "enum": [ + "self-apply", + "partnership" + ], + "type": "string", + "description": "Filter by submission mode Enums(self-apply, partnership)", + "name": "submission_mode", + "in": "query" + }, + { + "enum": [ + "approved", + "rejected" + ], + "type": "string", + "description": "Filter by status Enums(approved, rejected)", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Tender approvals retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid company ID", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new tender approval for a company to approve or reject a tender", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "TenderApprovals" + ], + "summary": "Create a new tender approval", + "parameters": [ + { + "description": "Tender approval information", + "name": "tender_approval", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/tender_approval.CreateTenderApprovalForm" + } + } + ], + "responses": { + "201": { + "description": "Tender approval created successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid input data", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "409": { + "description": "Conflict - Tender approval already exists for this tender and company", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "422": { + "description": "Validation error - Invalid request data", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tender-approvals/tender/{tender_id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve tender approval information for a specific tender and company combination", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "TenderApprovals" + ], + "summary": "Get tender approval by tender and company", + "parameters": [ + { + "type": "string", + "description": "Tender ID", + "name": "tender_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "Tender approval retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid tender ID or company ID", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "404": { + "description": "Not found - Tender approval not found", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tender-approvals/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve detailed tender approval information by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "TenderApprovals" + ], + "summary": "Get tender approval by ID", + "parameters": [ + { + "type": "string", + "description": "Tender Approval ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Tender approval retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid tender approval ID", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "404": { + "description": "Not found - Tender approval not found", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update tender approval information", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "TenderApprovals" + ], + "summary": "Update tender approval", + "parameters": [ + { + "type": "string", + "description": "Tender Approval ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Tender approval update information", + "name": "tender_approval", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/tender_approval.UpdateTenderApprovalForm" + } + } + ], + "responses": { + "200": { + "description": "Tender approval updated successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid input data", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "404": { + "description": "Not found - Tender approval not found", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "422": { + "description": "Validation error - Invalid request data", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a tender approval", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "TenderApprovals" + ], + "summary": "Delete tender approval", + "parameters": [ + { + "type": "string", + "description": "Tender Approval ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Tender approval deleted successfully", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad request - Invalid tender approval ID", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "404": { + "description": "Not found - Tender approval not found", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/tenders": { "get": { "security": [ @@ -6608,10 +7451,12 @@ const docTemplate = `{ "type": "object", "properties": { "password": { - "type": "string" + "type": "string", + "example": "Nima.1998" }, "username": { - "type": "string" + "type": "string", + "example": "nakhostin" } } }, @@ -6963,6 +7808,9 @@ const docTemplate = `{ "status": { "$ref": "#/definitions/tender.TenderStatus" }, + "submission_deadline": { + "type": "integer" + }, "tender_deadline": { "type": "integer" }, @@ -7015,6 +7863,146 @@ const docTemplate = `{ } } }, + "tender_approval.ApprovalStatus": { + "type": "string", + "enum": [ + "approved", + "rejected" + ], + "x-enum-comments": { + "ApprovalStatusApproved": "Company approved the tender", + "ApprovalStatusRejected": "Company rejected the tender" + }, + "x-enum-descriptions": [ + "Company approved the tender", + "Company rejected the tender" + ], + "x-enum-varnames": [ + "ApprovalStatusApproved", + "ApprovalStatusRejected" + ] + }, + "tender_approval.CreateTenderApprovalForm": { + "type": "object", + "properties": { + "status": { + "$ref": "#/definitions/tender_approval.ApprovalStatus" + }, + "submission_mode": { + "$ref": "#/definitions/tender_approval.SubmissionMode" + }, + "tender_id": { + "type": "string" + } + } + }, + "tender_approval.SubmissionMode": { + "type": "string", + "enum": [ + "self-apply", + "partnership" + ], + "x-enum-comments": { + "SubmissionModePartnership": "Company applies for the tender with a partner", + "SubmissionModeSelfApply": "Company applies for the tender on their own" + }, + "x-enum-descriptions": [ + "Company applies for the tender on their own", + "Company applies for the tender with a partner" + ], + "x-enum-varnames": [ + "SubmissionModeSelfApply", + "SubmissionModePartnership" + ] + }, + "tender_approval.TenderApprovalListResponse": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "tender_approvals": { + "type": "array", + "items": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + }, + "total": { + "type": "integer" + }, + "total_pages": { + "type": "integer" + } + } + }, + "tender_approval.TenderApprovalResponse": { + "type": "object", + "properties": { + "company_id": { + "type": "string" + }, + "created_at": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "submission_mode": { + "type": "string" + }, + "tender_id": { + "type": "string" + } + } + }, + "tender_approval.TenderApprovalStatsResponse": { + "type": "object", + "properties": { + "approvals_by_status": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + }, + "approvals_by_submission": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + }, + "approved_tenders": { + "type": "integer" + }, + "last_updated": { + "type": "integer" + }, + "rejected_tenders": { + "type": "integer" + }, + "total_approvals": { + "type": "integer" + } + } + }, + "tender_approval.UpdateTenderApprovalForm": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "submission_mode": { + "type": "string" + } + } + }, "user.AuthResponse": { "type": "object", "properties": { @@ -7320,6 +8308,10 @@ const docTemplate = `{ "description": "Administrative feedback management operations for web panel including CRUD operations, search, statistics, and comprehensive filtering with pagination", "name": "Admin-Feedback" }, + { + "description": "Administrative tender approval management operations for web panel including CRUD operations, search, statistics, and comprehensive filtering with pagination", + "name": "Admin-TenderApprovals" + }, { "description": "Customer authentication and authorization operations for mobile application including login, logout, token refresh, and profile access", "name": "Authorization" @@ -7331,6 +8323,10 @@ const docTemplate = `{ { "description": "Public feedback management operations for mobile application including like, dislike, and comment on tenders", "name": "Feedback" + }, + { + "description": "Public tender approval management operations for mobile application including tender approval listing and detailed tender approval information", + "name": "TenderApprovals" } ] }` diff --git a/cmd/web/docs/swagger.json b/cmd/web/docs/swagger.json index 282c1ae..2fd91f0 100644 --- a/cmd/web/docs/swagger.json +++ b/cmd/web/docs/swagger.json @@ -3884,6 +3884,432 @@ } } }, + "/admin/v1/tender-approvals": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve a paginated list of tender approvals with filtering options", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Admin-TenderApprovals" + ], + "summary": "List tender approvals with filters and pagination", + "parameters": [ + { + "type": "string", + "description": "Filter by tender ID", + "name": "tender_id", + "in": "query" + }, + { + "type": "string", + "description": "Filter by company ID", + "name": "company_id", + "in": "query" + }, + { + "type": "array", + "description": "Filter by status", + "name": "status", + "in": "query" + }, + { + "type": "array", + "description": "Filter by submission mode", + "name": "submission_mode", + "in": "query" + }, + { + "type": "integer", + "description": "Filter by creation date from (Unix timestamp)", + "name": "created_at_from", + "in": "query" + }, + { + "type": "integer", + "description": "Filter by creation date to (Unix timestamp)", + "name": "created_at_to", + "in": "query" + }, + { + "maximum": 100, + "minimum": 1, + "type": "integer", + "default": 20, + "description": "Number of approvals per page (1-100)", + "name": "limit", + "in": "query" + }, + { + "minimum": 0, + "type": "integer", + "default": 0, + "description": "Number of approvals to skip for pagination", + "name": "offset", + "in": "query" + }, + { + "enum": [ + "created_at", + "updated_at", + "status", + "submission_mode" + ], + "type": "string", + "default": "created_at", + "description": "Field to sort by", + "name": "sort_by", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "default": "desc", + "description": "Sort order", + "name": "sort_order", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Tender approvals retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/tender_approval.TenderApprovalListResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid query parameters", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "422": { + "description": "Validation error - Invalid query parameters", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/admin/v1/tender-approvals/stats": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get comprehensive tender approval statistics for dashboard", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Admin-TenderApprovals" + ], + "summary": "Get tender approval statistics", + "responses": { + "200": { + "description": "Tender approval statistics retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/tender_approval.TenderApprovalStatsResponse" + } + } + } + ] + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/admin/v1/tender-approvals/status/{status}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve tender approvals filtered by their status (approved, rejected)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Admin-TenderApprovals" + ], + "summary": "Get tender approvals by status", + "parameters": [ + { + "enum": [ + "approved", + "rejected" + ], + "type": "string", + "description": "Tender approval status", + "name": "status", + "in": "path", + "required": true + }, + { + "maximum": 100, + "minimum": 1, + "type": "integer", + "default": 20, + "description": "Number of approvals per page (1-100)", + "name": "limit", + "in": "query" + }, + { + "minimum": 0, + "type": "integer", + "default": 0, + "description": "Number of approvals to skip for pagination", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Tender approvals retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + }, + "meta": { + "$ref": "#/definitions/response.Meta" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid tender approval status", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/admin/v1/tender-approvals/submission-mode/{submission_mode}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve tender approvals filtered by their submission mode (self-apply, partnership)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Admin-TenderApprovals" + ], + "summary": "Get tender approvals by submission mode", + "parameters": [ + { + "enum": [ + "self-apply", + "partnership" + ], + "type": "string", + "description": "Tender approval submission mode", + "name": "submission_mode", + "in": "path", + "required": true + }, + { + "maximum": 100, + "minimum": 1, + "type": "integer", + "default": 20, + "description": "Number of approvals per page (1-100)", + "name": "limit", + "in": "query" + }, + { + "minimum": 0, + "type": "integer", + "default": 0, + "description": "Number of approvals to skip for pagination", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Tender approvals retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + }, + "meta": { + "$ref": "#/definitions/response.Meta" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid tender approval submission mode", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/admin/v1/tender-approvals/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve detailed tender approval information by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "TenderApprovals" + ], + "summary": "Get tender approval by ID", + "parameters": [ + { + "type": "string", + "description": "Tender Approval ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Tender approval retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid tender approval ID", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "404": { + "description": "Not found - Tender approval not found", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/admin/v1/users": { "get": { "security": [ @@ -5400,6 +5826,423 @@ } } }, + "/api/v1/tender-approvals": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve all tender approvals for a specific company with optional filtering", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "TenderApprovals" + ], + "summary": "Get tender approvals by company ID", + "parameters": [ + { + "enum": [ + "self-apply", + "partnership" + ], + "type": "string", + "description": "Filter by submission mode Enums(self-apply, partnership)", + "name": "submission_mode", + "in": "query" + }, + { + "enum": [ + "approved", + "rejected" + ], + "type": "string", + "description": "Filter by status Enums(approved, rejected)", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Tender approvals retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid company ID", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new tender approval for a company to approve or reject a tender", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "TenderApprovals" + ], + "summary": "Create a new tender approval", + "parameters": [ + { + "description": "Tender approval information", + "name": "tender_approval", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/tender_approval.CreateTenderApprovalForm" + } + } + ], + "responses": { + "201": { + "description": "Tender approval created successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid input data", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "409": { + "description": "Conflict - Tender approval already exists for this tender and company", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "422": { + "description": "Validation error - Invalid request data", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tender-approvals/tender/{tender_id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve tender approval information for a specific tender and company combination", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "TenderApprovals" + ], + "summary": "Get tender approval by tender and company", + "parameters": [ + { + "type": "string", + "description": "Tender ID", + "name": "tender_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "Tender approval retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid tender ID or company ID", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "404": { + "description": "Not found - Tender approval not found", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tender-approvals/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve detailed tender approval information by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "TenderApprovals" + ], + "summary": "Get tender approval by ID", + "parameters": [ + { + "type": "string", + "description": "Tender Approval ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Tender approval retrieved successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid tender approval ID", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "404": { + "description": "Not found - Tender approval not found", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update tender approval information", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "TenderApprovals" + ], + "summary": "Update tender approval", + "parameters": [ + { + "type": "string", + "description": "Tender Approval ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Tender approval update information", + "name": "tender_approval", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/tender_approval.UpdateTenderApprovalForm" + } + } + ], + "responses": { + "200": { + "description": "Tender approval updated successfully", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad request - Invalid input data", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "404": { + "description": "Not found - Tender approval not found", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "422": { + "description": "Validation error - Invalid request data", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a tender approval", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "TenderApprovals" + ], + "summary": "Delete tender approval", + "parameters": [ + { + "type": "string", + "description": "Tender Approval ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Tender approval deleted successfully", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad request - Invalid tender approval ID", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "404": { + "description": "Not found - Tender approval not found", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/tenders": { "get": { "security": [ @@ -6602,10 +7445,12 @@ "type": "object", "properties": { "password": { - "type": "string" + "type": "string", + "example": "Nima.1998" }, "username": { - "type": "string" + "type": "string", + "example": "nakhostin" } } }, @@ -6957,6 +7802,9 @@ "status": { "$ref": "#/definitions/tender.TenderStatus" }, + "submission_deadline": { + "type": "integer" + }, "tender_deadline": { "type": "integer" }, @@ -7009,6 +7857,146 @@ } } }, + "tender_approval.ApprovalStatus": { + "type": "string", + "enum": [ + "approved", + "rejected" + ], + "x-enum-comments": { + "ApprovalStatusApproved": "Company approved the tender", + "ApprovalStatusRejected": "Company rejected the tender" + }, + "x-enum-descriptions": [ + "Company approved the tender", + "Company rejected the tender" + ], + "x-enum-varnames": [ + "ApprovalStatusApproved", + "ApprovalStatusRejected" + ] + }, + "tender_approval.CreateTenderApprovalForm": { + "type": "object", + "properties": { + "status": { + "$ref": "#/definitions/tender_approval.ApprovalStatus" + }, + "submission_mode": { + "$ref": "#/definitions/tender_approval.SubmissionMode" + }, + "tender_id": { + "type": "string" + } + } + }, + "tender_approval.SubmissionMode": { + "type": "string", + "enum": [ + "self-apply", + "partnership" + ], + "x-enum-comments": { + "SubmissionModePartnership": "Company applies for the tender with a partner", + "SubmissionModeSelfApply": "Company applies for the tender on their own" + }, + "x-enum-descriptions": [ + "Company applies for the tender on their own", + "Company applies for the tender with a partner" + ], + "x-enum-varnames": [ + "SubmissionModeSelfApply", + "SubmissionModePartnership" + ] + }, + "tender_approval.TenderApprovalListResponse": { + "type": "object", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "tender_approvals": { + "type": "array", + "items": { + "$ref": "#/definitions/tender_approval.TenderApprovalResponse" + } + }, + "total": { + "type": "integer" + }, + "total_pages": { + "type": "integer" + } + } + }, + "tender_approval.TenderApprovalResponse": { + "type": "object", + "properties": { + "company_id": { + "type": "string" + }, + "created_at": { + "type": "integer" + }, + "id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "submission_mode": { + "type": "string" + }, + "tender_id": { + "type": "string" + } + } + }, + "tender_approval.TenderApprovalStatsResponse": { + "type": "object", + "properties": { + "approvals_by_status": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + }, + "approvals_by_submission": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + }, + "approved_tenders": { + "type": "integer" + }, + "last_updated": { + "type": "integer" + }, + "rejected_tenders": { + "type": "integer" + }, + "total_approvals": { + "type": "integer" + } + } + }, + "tender_approval.UpdateTenderApprovalForm": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "submission_mode": { + "type": "string" + } + } + }, "user.AuthResponse": { "type": "object", "properties": { @@ -7314,6 +8302,10 @@ "description": "Administrative feedback management operations for web panel including CRUD operations, search, statistics, and comprehensive filtering with pagination", "name": "Admin-Feedback" }, + { + "description": "Administrative tender approval management operations for web panel including CRUD operations, search, statistics, and comprehensive filtering with pagination", + "name": "Admin-TenderApprovals" + }, { "description": "Customer authentication and authorization operations for mobile application including login, logout, token refresh, and profile access", "name": "Authorization" @@ -7325,6 +8317,10 @@ { "description": "Public feedback management operations for mobile application including like, dislike, and comment on tenders", "name": "Feedback" + }, + { + "description": "Public tender approval management operations for mobile application including tender approval listing and detailed tender approval information", + "name": "TenderApprovals" } ] } \ No newline at end of file diff --git a/cmd/web/docs/swagger.yaml b/cmd/web/docs/swagger.yaml index a55a19b..e99a696 100644 --- a/cmd/web/docs/swagger.yaml +++ b/cmd/web/docs/swagger.yaml @@ -638,8 +638,10 @@ definitions: customer.LoginForm: properties: password: + example: Nima.1998 type: string username: + example: nakhostin type: string type: object customer.RefreshTokenForm: @@ -761,10 +763,14 @@ definitions: enum: - like - dislike + - unlike + - undislike type: string x-enum-varnames: - FeedbackTypeLike - FeedbackTypeDislike + - FeedbackTypeUnLike + - FeedbackTypeUnDislike feedback.ToggleFeedbackForm: properties: feedback_type: @@ -869,6 +875,8 @@ definitions: type: integer status: $ref: '#/definitions/tender.TenderStatus' + submission_deadline: + type: integer tender_deadline: type: integer title: @@ -906,6 +914,101 @@ definitions: title: type: string type: object + tender_approval.ApprovalStatus: + enum: + - approved + - rejected + type: string + x-enum-comments: + ApprovalStatusApproved: Company approved the tender + ApprovalStatusRejected: Company rejected the tender + x-enum-descriptions: + - Company approved the tender + - Company rejected the tender + x-enum-varnames: + - ApprovalStatusApproved + - ApprovalStatusRejected + tender_approval.CreateTenderApprovalForm: + properties: + status: + $ref: '#/definitions/tender_approval.ApprovalStatus' + submission_mode: + $ref: '#/definitions/tender_approval.SubmissionMode' + tender_id: + type: string + type: object + tender_approval.SubmissionMode: + enum: + - self-apply + - partnership + type: string + x-enum-comments: + SubmissionModePartnership: Company applies for the tender with a partner + SubmissionModeSelfApply: Company applies for the tender on their own + x-enum-descriptions: + - Company applies for the tender on their own + - Company applies for the tender with a partner + x-enum-varnames: + - SubmissionModeSelfApply + - SubmissionModePartnership + tender_approval.TenderApprovalListResponse: + properties: + limit: + type: integer + offset: + type: integer + tender_approvals: + items: + $ref: '#/definitions/tender_approval.TenderApprovalResponse' + type: array + total: + type: integer + total_pages: + type: integer + type: object + tender_approval.TenderApprovalResponse: + properties: + company_id: + type: string + created_at: + type: integer + id: + type: string + status: + type: string + submission_mode: + type: string + tender_id: + type: string + type: object + tender_approval.TenderApprovalStatsResponse: + properties: + approvals_by_status: + additionalProperties: + format: int64 + type: integer + type: object + approvals_by_submission: + additionalProperties: + format: int64 + type: integer + type: object + approved_tenders: + type: integer + last_updated: + type: integer + rejected_tenders: + type: integer + total_approvals: + type: integer + type: object + tender_approval.UpdateTenderApprovalForm: + properties: + status: + type: string + submission_mode: + type: string + type: object user.AuthResponse: properties: access_token: @@ -3582,6 +3685,275 @@ paths: summary: Initiate password reset process tags: - Admin-Authorization + /admin/v1/tender-approvals: + get: + consumes: + - application/json + description: Retrieve a paginated list of tender approvals with filtering options + parameters: + - description: Filter by tender ID + in: query + name: tender_id + type: string + - description: Filter by company ID + in: query + name: company_id + type: string + - description: Filter by status + in: query + name: status + type: array + - description: Filter by submission mode + in: query + name: submission_mode + type: array + - description: Filter by creation date from (Unix timestamp) + in: query + name: created_at_from + type: integer + - description: Filter by creation date to (Unix timestamp) + in: query + name: created_at_to + type: integer + - default: 20 + description: Number of approvals per page (1-100) + in: query + maximum: 100 + minimum: 1 + name: limit + type: integer + - default: 0 + description: Number of approvals to skip for pagination + in: query + minimum: 0 + name: offset + type: integer + - default: created_at + description: Field to sort by + enum: + - created_at + - updated_at + - status + - submission_mode + in: query + name: sort_by + type: string + - default: desc + description: Sort order + enum: + - asc + - desc + in: query + name: sort_order + type: string + produces: + - application/json + responses: + "200": + description: Tender approvals retrieved successfully + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + data: + $ref: '#/definitions/tender_approval.TenderApprovalListResponse' + type: object + "400": + description: Bad request - Invalid query parameters + schema: + $ref: '#/definitions/response.APIResponse' + "422": + description: Validation error - Invalid query parameters + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - BearerAuth: [] + summary: List tender approvals with filters and pagination + tags: + - Admin-TenderApprovals + /admin/v1/tender-approvals/{id}: + get: + consumes: + - application/json + description: Retrieve detailed tender approval information by ID + parameters: + - description: Tender Approval ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: Tender approval retrieved successfully + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + data: + $ref: '#/definitions/tender_approval.TenderApprovalResponse' + type: object + "400": + description: Bad request - Invalid tender approval ID + schema: + $ref: '#/definitions/response.APIResponse' + "404": + description: Not found - Tender approval not found + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - BearerAuth: [] + summary: Get tender approval by ID + tags: + - TenderApprovals + /admin/v1/tender-approvals/stats: + get: + consumes: + - application/json + description: Get comprehensive tender approval statistics for dashboard + produces: + - application/json + responses: + "200": + description: Tender approval statistics retrieved successfully + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + data: + $ref: '#/definitions/tender_approval.TenderApprovalStatsResponse' + type: object + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - BearerAuth: [] + summary: Get tender approval statistics + tags: + - Admin-TenderApprovals + /admin/v1/tender-approvals/status/{status}: + get: + consumes: + - application/json + description: Retrieve tender approvals filtered by their status (approved, rejected) + parameters: + - description: Tender approval status + enum: + - approved + - rejected + in: path + name: status + required: true + type: string + - default: 20 + description: Number of approvals per page (1-100) + in: query + maximum: 100 + minimum: 1 + name: limit + type: integer + - default: 0 + description: Number of approvals to skip for pagination + in: query + minimum: 0 + name: offset + type: integer + produces: + - application/json + responses: + "200": + description: Tender approvals retrieved successfully + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + data: + items: + $ref: '#/definitions/tender_approval.TenderApprovalResponse' + type: array + meta: + $ref: '#/definitions/response.Meta' + type: object + "400": + description: Bad request - Invalid tender approval status + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - BearerAuth: [] + summary: Get tender approvals by status + tags: + - Admin-TenderApprovals + /admin/v1/tender-approvals/submission-mode/{submission_mode}: + get: + consumes: + - application/json + description: Retrieve tender approvals filtered by their submission mode (self-apply, + partnership) + parameters: + - description: Tender approval submission mode + enum: + - self-apply + - partnership + in: path + name: submission_mode + required: true + type: string + - default: 20 + description: Number of approvals per page (1-100) + in: query + maximum: 100 + minimum: 1 + name: limit + type: integer + - default: 0 + description: Number of approvals to skip for pagination + in: query + minimum: 0 + name: offset + type: integer + produces: + - application/json + responses: + "200": + description: Tender approvals retrieved successfully + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + data: + items: + $ref: '#/definitions/tender_approval.TenderApprovalResponse' + type: array + meta: + $ref: '#/definitions/response.Meta' + type: object + "400": + description: Bad request - Invalid tender approval submission mode + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - BearerAuth: [] + summary: Get tender approvals by submission mode + tags: + - Admin-TenderApprovals /admin/v1/users: get: consumes: @@ -4295,6 +4667,70 @@ paths: summary: Get feedback by ID tags: - Feedback + /api/v1/feedback/tender/{tender_id}: + get: + consumes: + - application/json + description: Retrieve feedback details by tender ID. This endpoint provides + basic feedback information for public users. + parameters: + - description: Tender ID (MongoDB ObjectID format) + in: path + maxLength: 24 + minLength: 24 + name: tender_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: Feedback retrieved successfully + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + data: + $ref: '#/definitions/feedback.FeedbackResponse' + type: object + "400": + description: Bad request - Invalid tender ID format + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + error: + type: string + message: + type: string + type: object + "404": + description: Feedback not found + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + error: + type: string + message: + type: string + type: object + "500": + description: Internal server error + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + error: + type: string + message: + type: string + type: object + security: + - BearerAuth: [] + summary: Get feedback by tender ID + tags: + - Feedback /api/v1/profile: get: consumes: @@ -4442,6 +4878,264 @@ paths: summary: Refresh customer access token tags: - Authorization + /api/v1/tender-approvals: + get: + consumes: + - application/json + description: Retrieve all tender approvals for a specific company with optional + filtering + parameters: + - description: Filter by submission mode Enums(self-apply, partnership) + enum: + - self-apply + - partnership + in: query + name: submission_mode + type: string + - description: Filter by status Enums(approved, rejected) + enum: + - approved + - rejected + in: query + name: status + type: string + produces: + - application/json + responses: + "200": + description: Tender approvals retrieved successfully + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + data: + items: + $ref: '#/definitions/tender_approval.TenderApprovalResponse' + type: array + type: object + "400": + description: Bad request - Invalid company ID + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - BearerAuth: [] + summary: Get tender approvals by company ID + tags: + - TenderApprovals + post: + consumes: + - application/json + description: Create a new tender approval for a company to approve or reject + a tender + parameters: + - description: Tender approval information + in: body + name: tender_approval + required: true + schema: + $ref: '#/definitions/tender_approval.CreateTenderApprovalForm' + produces: + - application/json + responses: + "201": + description: Tender approval created successfully + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + data: + $ref: '#/definitions/tender_approval.TenderApprovalResponse' + type: object + "400": + description: Bad request - Invalid input data + schema: + $ref: '#/definitions/response.APIResponse' + "409": + description: Conflict - Tender approval already exists for this tender and + company + schema: + $ref: '#/definitions/response.APIResponse' + "422": + description: Validation error - Invalid request data + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - BearerAuth: [] + summary: Create a new tender approval + tags: + - TenderApprovals + /api/v1/tender-approvals/{id}: + delete: + consumes: + - application/json + description: Delete a tender approval + parameters: + - description: Tender Approval ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: Tender approval deleted successfully + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad request - Invalid tender approval ID + schema: + $ref: '#/definitions/response.APIResponse' + "404": + description: Not found - Tender approval not found + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - BearerAuth: [] + summary: Delete tender approval + tags: + - TenderApprovals + get: + consumes: + - application/json + description: Retrieve detailed tender approval information by ID + parameters: + - description: Tender Approval ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: Tender approval retrieved successfully + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + data: + $ref: '#/definitions/tender_approval.TenderApprovalResponse' + type: object + "400": + description: Bad request - Invalid tender approval ID + schema: + $ref: '#/definitions/response.APIResponse' + "404": + description: Not found - Tender approval not found + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - BearerAuth: [] + summary: Get tender approval by ID + tags: + - TenderApprovals + put: + consumes: + - application/json + description: Update tender approval information + parameters: + - description: Tender Approval ID + in: path + name: id + required: true + type: string + - description: Tender approval update information + in: body + name: tender_approval + required: true + schema: + $ref: '#/definitions/tender_approval.UpdateTenderApprovalForm' + produces: + - application/json + responses: + "200": + description: Tender approval updated successfully + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + data: + $ref: '#/definitions/tender_approval.TenderApprovalResponse' + type: object + "400": + description: Bad request - Invalid input data + schema: + $ref: '#/definitions/response.APIResponse' + "404": + description: Not found - Tender approval not found + schema: + $ref: '#/definitions/response.APIResponse' + "422": + description: Validation error - Invalid request data + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - BearerAuth: [] + summary: Update tender approval + tags: + - TenderApprovals + /api/v1/tender-approvals/tender/{tender_id}: + get: + consumes: + - application/json + description: Retrieve tender approval information for a specific tender and + company combination + parameters: + - description: Tender ID + in: query + name: tender_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: Tender approval retrieved successfully + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + data: + $ref: '#/definitions/tender_approval.TenderApprovalResponse' + type: object + "400": + description: Bad request - Invalid tender ID or company ID + schema: + $ref: '#/definitions/response.APIResponse' + "404": + description: Not found - Tender approval not found + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - BearerAuth: [] + summary: Get tender approval by tender and company + tags: + - TenderApprovals /api/v1/tenders: get: description: Retrieve active tenders for mobile application users with automatic @@ -4493,7 +5187,7 @@ paths: summary: Get public tenders tags: - Tenders - /api/v1/tenders/read/{id}: + /api/v1/tenders/details/{id}: get: description: Retrieve detailed information about a specific tender for mobile application users @@ -4612,6 +5306,10 @@ tags: - description: Administrative feedback management operations for web panel including CRUD operations, search, statistics, and comprehensive filtering with pagination name: Admin-Feedback +- description: Administrative tender approval management operations for web panel + including CRUD operations, search, statistics, and comprehensive filtering with + pagination + name: Admin-TenderApprovals - description: Customer authentication and authorization operations for mobile application including login, logout, token refresh, and profile access name: Authorization @@ -4621,3 +5319,6 @@ tags: - description: Public feedback management operations for mobile application including like, dislike, and comment on tenders name: Feedback +- description: Public tender approval management operations for mobile application + including tender approval listing and detailed tender approval information + name: TenderApprovals diff --git a/cmd/web/main.go b/cmd/web/main.go index 2250fa1..7529e75 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -41,6 +41,9 @@ package main // @tag.name Admin-Feedback // @tag.description Administrative feedback management operations for web panel including CRUD operations, search, statistics, and comprehensive filtering with pagination +// @tag.name Admin-TenderApprovals +// @tag.description Administrative tender approval management operations for web panel including CRUD operations, search, statistics, and comprehensive filtering with pagination + // @tag.name Authorization // @tag.description Customer authentication and authorization operations for mobile application including login, logout, token refresh, and profile access @@ -50,6 +53,9 @@ package main // @tag.name Feedback // @tag.description Public feedback management operations for mobile application including like, dislike, and comment on tenders +// @tag.name TenderApprovals +// @tag.description Public tender approval management operations for mobile application including tender approval listing and detailed tender approval information + import ( "context" "fmt" @@ -59,6 +65,7 @@ import ( "tm/internal/customer" "tm/internal/feedback" "tm/internal/tender" + "tm/internal/tender_approval" "tm/internal/user" _ "tm/cmd/web/docs" // This is generated by swag @@ -103,9 +110,9 @@ func main() { companyRepository := company.NewCompanyRepository(mongoManager, logger) tenderRepository := tender.NewTenderRepository(mongoManager, logger) feedbackRepo := feedback.NewFeedbackRepository(mongoManager, logger) - + tenderApprovalRepository := tender_approval.NewTenderApprovalRepository(mongoManager, logger) logger.Info("Repositories initialized successfully", map[string]interface{}{ - "repositories": []string{"customer", "user", "company", "tender", "feedback"}, + "repositories": []string{"customer", "user", "company", "tender", "feedback", "tender_approval"}, }) // Initialize validation service @@ -117,9 +124,9 @@ func main() { customerService := customer.NewCustomerService(customerRepository, logger, customerAuthService, companyService) tenderService := tender.NewTenderService(tenderRepository, companyService, logger) feedbackService := feedback.NewFeedbackService(feedbackRepo, logger) - + tenderApprovalService := tender_approval.NewTenderApprovalService(tenderApprovalRepository, logger) logger.Info("Services initialized successfully", map[string]interface{}{ - "services": []string{"customer", "user", "company", "tender", "feedback"}, + "services": []string{"customer", "user", "company", "tender", "feedback", "tender_approval"}, }) // Initialize handlers with services @@ -128,17 +135,18 @@ func main() { companyHandler := company.NewHandler(companyService, userHandler, logger) tenderHandler := tender.NewTenderHandler(tenderService, logger) feedbackHandler := feedback.NewFeedbackHandler(feedbackService, userHandler, customerHandler, logger) + tenderApprovalHandler := tender_approval.NewHandler(tenderApprovalService, logger) logger.Info("Handlers initialized successfully", map[string]interface{}{ - "handlers": []string{"customer", "user", "company", "tender", "feedback"}, + "handlers": []string{"customer", "user", "company", "tender", "feedback", "tender_approval"}, }) // Initialize HTTP server e := initHTTPServer(conf, logger) // Register routes - router.RegisterAdminRoutes(e, userHandler, companyHandler, customerHandler, tenderHandler, feedbackHandler) - router.RegisterPublicRoutes(e, customerHandler, tenderHandler, feedbackHandler) + router.RegisterAdminRoutes(e, userHandler, companyHandler, customerHandler, tenderHandler, feedbackHandler, tenderApprovalHandler) + router.RegisterPublicRoutes(e, customerHandler, tenderHandler, feedbackHandler, tenderApprovalHandler) // Start server serverAddr := fmt.Sprintf("%s:%d", conf.Server.Host, conf.Server.Port) diff --git a/cmd/web/router/routes.go b/cmd/web/router/routes.go index cc244d7..4042786 100644 --- a/cmd/web/router/routes.go +++ b/cmd/web/router/routes.go @@ -5,12 +5,13 @@ import ( "tm/internal/customer" "tm/internal/feedback" "tm/internal/tender" + "tm/internal/tender_approval" "tm/internal/user" "github.com/labstack/echo/v4" ) -func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler *company.Handler, customerHandler *customer.Handler, tenderHandler *tender.TenderHandler, feedbackHandler *feedback.Handler) { +func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler *company.Handler, customerHandler *customer.Handler, tenderHandler *tender.TenderHandler, feedbackHandler *feedback.Handler, tenderApprovalHandler *tender_approval.Handler) { adminV1 := e.Group("/admin/v1") // Admin Users Routes @@ -107,9 +108,21 @@ func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler feedbackGP.GET("/:id", feedbackHandler.GetFeedback) feedbackGP.DELETE("/:id", feedbackHandler.DeleteFeedback) } + + // Admin Tender-Approvals Routes + tenderApprovalGP := adminV1.Group("/tender-approvals") + { + tenderApprovalGP.Use(userHandler.AuthMiddleware()) + tenderApprovalGP.GET("", tenderApprovalHandler.ListTenderApprovals) + tenderApprovalGP.GET("/:id", tenderApprovalHandler.GetTenderApproval) + tenderApprovalGP.PUT("/:id", tenderApprovalHandler.UpdateTenderApproval) + tenderApprovalGP.GET("/stats", tenderApprovalHandler.GetTenderApprovalStats) + tenderApprovalGP.GET("/submission-mode/:submission_mode", tenderApprovalHandler.GetTenderApprovalsBySubmissionMode) + tenderApprovalGP.GET("/status/:status", tenderApprovalHandler.GetTenderApprovalsByStatus) + } } -func RegisterPublicRoutes(e *echo.Echo, customerHandler *customer.Handler, tenderHandler *tender.TenderHandler, feedbackHandler *feedback.Handler) { +func RegisterPublicRoutes(e *echo.Echo, customerHandler *customer.Handler, tenderHandler *tender.TenderHandler, feedbackHandler *feedback.Handler, tenderApprovalHandler *tender_approval.Handler) { v1 := e.Group("/api/v1") customerGP := v1.Group("/profile") @@ -141,4 +154,16 @@ func RegisterPublicRoutes(e *echo.Echo, customerHandler *customer.Handler, tende feedbackGP.GET("/tender/:tender_id", feedbackHandler.PublicGetFeedbackByTenderID) feedbackGP.GET("/:id", feedbackHandler.PublicGetFeedback) } + + // Public Tender Approvals Routes + tenderApprovalGP := v1.Group("/tender-approvals") + { + tenderApprovalGP.Use(customerHandler.AuthMiddleware()) + tenderApprovalGP.POST("", tenderApprovalHandler.CreateTenderApproval) + tenderApprovalGP.PUT("/:id", tenderApprovalHandler.UpdateTenderApproval) + tenderApprovalGP.GET("", tenderApprovalHandler.PublicGetTenderApprovals) + tenderApprovalGP.GET("/:id", tenderApprovalHandler.GetPublicTenderApproval) + tenderApprovalGP.GET("/tender/:tender_id", tenderApprovalHandler.GetTenderApprovalByTenderAndCompany) + tenderApprovalGP.DELETE("/:id", tenderApprovalHandler.DeleteTenderApproval) + } } diff --git a/internal/customer/form.go b/internal/customer/form.go index 09b29f8..8efbbbf 100644 --- a/internal/customer/form.go +++ b/internal/customer/form.go @@ -177,8 +177,8 @@ type UpdateCustomerMobileForm struct { // Customer Authentication DTOs type LoginForm struct { - Username string `json:"username" valid:"required"` - Password string `json:"password" valid:"required"` + Username string `json:"username" valid:"required" example:"nakhostin"` + Password string `json:"password" valid:"required" example:"Nima.1998"` } type RefreshTokenForm struct { diff --git a/internal/tender/form.go b/internal/tender/form.go index b954179..34a606c 100644 --- a/internal/tender/form.go +++ b/internal/tender/form.go @@ -41,6 +41,7 @@ type TenderResponse struct { Duration string `json:"duration"` DurationUnit string `json:"duration_unit"` TenderDeadline int64 `json:"tender_deadline"` + SubmissionDeadline int64 `json:"submission_deadline"` CountryCode string `json:"country_code"` BuyerOrganization *OrganizationResponse `json:"buyer_organization"` Status TenderStatus `json:"status"` @@ -65,6 +66,7 @@ func (t *Tender) ToTenderResponse() *TenderResponse { Duration: t.Duration, DurationUnit: t.DurationUnit, TenderDeadline: t.TenderDeadline, + SubmissionDeadline: t.SubmissionDeadline, CountryCode: t.CountryCode, BuyerOrganization: &OrganizationResponse{Name: t.BuyerOrganization.Name}, Status: t.Status, diff --git a/internal/tender_approval/entity.go b/internal/tender_approval/entity.go new file mode 100644 index 0000000..8fcfd3f --- /dev/null +++ b/internal/tender_approval/entity.go @@ -0,0 +1,125 @@ +package tender_approval + +import ( + "time" + "tm/pkg/mongo" +) + +// ApprovalStatus represents the approval status of a tender by a company +type ApprovalStatus string + +const ( + ApprovalStatusApproved ApprovalStatus = "approved" // Company approved the tender + ApprovalStatusRejected ApprovalStatus = "rejected" // Company rejected the tender +) + +// SubmissionMode represents the submission mode of a tender by a company +type SubmissionMode string + +const ( + SubmissionModeSelfApply SubmissionMode = "self-apply" // Company applies for the tender on their own + SubmissionModePartnership SubmissionMode = "partnership" // Company applies for the tender with a partner +) + +// TenderApproval represents a company's approval decision for a specific tender +type TenderApproval struct { + mongo.Model `bson:",inline"` + + // Core relationships + TenderID string `bson:"tender_id" json:"tender_id"` // Reference to the tender + CompanyID string `bson:"company_id" json:"company_id"` // Reference to the company + + // Approval decision + SubmissionMode SubmissionMode `bson:"submission_mode" json:"submission_mode"` + Status ApprovalStatus `bson:"status" json:"status"` // Current approval status +} + +// TenderApprovalRequest represents a request to create or update a tender approval +type TenderApprovalRequest struct { + TenderID string `json:"tender_id" validate:"required"` + CompanyID string `json:"company_id" validate:"required"` + SubmissionMode SubmissionMode `json:"submission_mode" validate:"required"` + Status ApprovalStatus `json:"status,omitempty"` +} + +// TenderApprovalResponse represents the response data for tender approval +type TenderApprovalResponse struct { + ID string `json:"id"` + TenderID string `json:"tender_id"` + CompanyID string `json:"company_id"` + SubmissionMode string `json:"submission_mode"` + Status string `json:"status"` + CreatedAt int64 `json:"created_at"` +} + +// TenderApprovalSearchCriteria represents search criteria for tender approvals +type TenderApprovalSearchCriteria struct { + TenderID *string `json:"tender_id,omitempty"` + CompanyID *string `json:"company_id,omitempty"` + Status []ApprovalStatus `json:"status,omitempty"` + SubmissionMode []SubmissionMode `json:"submission_mode,omitempty"` + CreatedAtFrom *int64 `json:"created_at_from,omitempty"` + CreatedAtTo *int64 `json:"created_at_to,omitempty"` + Limit int `json:"limit,omitempty"` + Offset int `json:"offset,omitempty"` +} + +// TenderApprovalStatistics represents statistics for tender approvals +type TenderApprovalStatistics struct { + TotalApprovals int64 `json:"total_approvals"` + LastUpdated int64 `json:"last_updated"` +} + +// GetID returns the tender approval ID +func (ta *TenderApproval) GetID() string { + return ta.ID.Hex() +} + +// IsApproved returns true if the tender was approved +func (ta *TenderApproval) IsApproved() bool { + return ta.Status == ApprovalStatusApproved +} + +// IsRejected returns true if the tender was rejected +func (ta *TenderApproval) IsRejected() bool { + return ta.Status == ApprovalStatusRejected +} + +// HasDecision returns true if a decision has been made +func (ta *TenderApproval) HasDecision() bool { + return ta.Status == ApprovalStatusApproved || ta.Status == ApprovalStatusRejected +} + +// Approve approves the tender with optional notes +func (ta *TenderApproval) Approve(notes *string) { + ta.Status = ApprovalStatusApproved + ta.CreatedAt = time.Now().Unix() +} + +// Reject rejects the tender with a reason +func (ta *TenderApproval) Reject(reason string) { + ta.Status = ApprovalStatusRejected + ta.CreatedAt = time.Now().Unix() +} + +// SetCreatedAt sets the created timestamp (implements Timestampable interface) +func (ta *TenderApproval) SetCreatedAt(timestamp int64) { + ta.CreatedAt = timestamp +} + +// SetUpdatedAt sets the updated timestamp (implements Timestampable interface) +func (ta *TenderApproval) SetUpdatedAt(timestamp int64) { + ta.UpdatedAt = timestamp +} + +// ToResponse converts TenderApproval to TenderApprovalResponse +func (ta *TenderApproval) ToResponse() *TenderApprovalResponse { + return &TenderApprovalResponse{ + ID: ta.ID.Hex(), + TenderID: ta.TenderID, + CompanyID: ta.CompanyID, + SubmissionMode: string(ta.SubmissionMode), + Status: string(ta.Status), + CreatedAt: ta.CreatedAt, + } +} diff --git a/internal/tender_approval/errors.go b/internal/tender_approval/errors.go new file mode 100644 index 0000000..e9291ea --- /dev/null +++ b/internal/tender_approval/errors.go @@ -0,0 +1,17 @@ +package tender_approval + +import "errors" + +// Common error messages for tender approval operations +var ( + ErrTenderApprovalNotFound = errors.New("tender approval not found") + ErrTenderApprovalExists = errors.New("tender approval already exists for this tender and company") + ErrInvalidStatus = errors.New("invalid tender approval status") + ErrInvalidSubmissionMode = errors.New("invalid submission mode") + ErrTenderNotFound = errors.New("tender not found") + ErrCompanyNotFound = errors.New("company not found") + ErrInvalidTenderID = errors.New("invalid tender ID") + ErrInvalidCompanyID = errors.New("invalid company ID") + ErrUnauthorized = errors.New("unauthorized to perform this action") + ErrInvalidData = errors.New("invalid tender approval data") +) diff --git a/internal/tender_approval/form.go b/internal/tender_approval/form.go new file mode 100644 index 0000000..ba0b615 --- /dev/null +++ b/internal/tender_approval/form.go @@ -0,0 +1,70 @@ +package tender_approval + +// CreateTenderApprovalForm represents the form for creating a new tender approval +type CreateTenderApprovalForm struct { + TenderID string `json:"tender_id" valid:"required"` + SubmissionMode SubmissionMode `json:"submission_mode" valid:"required,in(self-apply|partnership)"` + Status ApprovalStatus `json:"status,omitempty" valid:"optional,in(approved|rejected)"` +} + +// UpdateTenderApprovalForm represents the form for updating a tender approval +type UpdateTenderApprovalForm struct { + SubmissionMode *string `json:"submission_mode,omitempty" valid:"optional,in(self-apply|partnership)"` + Status *string `json:"status,omitempty" valid:"optional,in(approved|rejected)"` +} + +// ListTenderApprovalsForm represents the form for listing tender approvals with filters +type ListTenderApprovalsForm struct { + TenderID *string `query:"tender_id" valid:"optional"` + CompanyID *string `query:"company_id" valid:"optional"` + Status []string `query:"status" valid:"optional"` + SubmissionMode []string `query:"submission_mode" valid:"optional"` + CreatedAtFrom *int64 `query:"created_at_from" valid:"optional"` + CreatedAtTo *int64 `query:"created_at_to" valid:"optional"` + Limit *int `query:"limit" valid:"optional,range(1|100)"` + Offset *int `query:"offset" valid:"optional,min(0)"` + SortBy *string `query:"sort_by" valid:"optional,in(created_at|updated_at|status|submission_mode)"` + SortOrder *string `query:"sort_order" valid:"optional,in(asc|desc)"` +} + +// ApproveTenderForm represents the form for approving a tender +type ApproveTenderForm struct { + Notes *string `json:"notes,omitempty" valid:"optional,length(0|1000)"` +} + +// RejectTenderForm represents the form for rejecting a tender +type RejectTenderForm struct { + Reason string `json:"reason" valid:"required,length(10|500)"` +} + +// TenderApprovalListResponse represents the response for listing tender approvals +type TenderApprovalListResponse struct { + TenderApprovals []*TenderApprovalResponse `json:"tender_approvals"` + Total int64 `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` + TotalPages int `json:"total_pages"` +} + +// TenderApproval search and matching DTOs +type TenderApprovalSearchForm struct { + Query *string `query:"q" valid:"optional"` + TenderID *string `query:"tender_id" valid:"optional"` + CompanyID *string `query:"company_id" valid:"optional"` + Status []string `query:"status" valid:"optional"` + SubmissionMode []string `query:"submission_mode" valid:"optional"` + CreatedAtFrom *int64 `query:"created_at_from" valid:"optional"` + CreatedAtTo *int64 `query:"created_at_to" valid:"optional"` + Limit *int `query:"limit" valid:"optional,range(1|100)"` + Offset *int `query:"offset" valid:"optional,min(0)"` +} + +// TenderApproval statistics DTOs +type TenderApprovalStatsResponse struct { + TotalApprovals int64 `json:"total_approvals"` + ApprovedTenders int64 `json:"approved_tenders"` + RejectedTenders int64 `json:"rejected_tenders"` + ApprovalsByStatus map[string]int64 `json:"approvals_by_status"` + ApprovalsBySubmission map[string]int64 `json:"approvals_by_submission"` + LastUpdated int64 `json:"last_updated"` +} diff --git a/internal/tender_approval/handler.go b/internal/tender_approval/handler.go new file mode 100644 index 0000000..edf8e68 --- /dev/null +++ b/internal/tender_approval/handler.go @@ -0,0 +1,437 @@ +package tender_approval + +import ( + "strconv" + "tm/pkg/logger" + "tm/pkg/response" + + "github.com/labstack/echo/v4" +) + +// Handler handles HTTP requests for tender approval operations +type Handler struct { + service Service + logger logger.Logger +} + +// NewHandler creates a new tender approval handler +func NewHandler(service Service, logger logger.Logger) *Handler { + return &Handler{ + service: service, + logger: logger, + } +} + +// CreateTenderApproval creates a new tender approval +// @Summary Create a new tender approval +// @Description Create a new tender approval for a company to approve or reject a tender +// @Tags TenderApprovals +// @Accept json +// @Produce json +// @Param tender_approval body CreateTenderApprovalForm true "Tender approval information" +// @Success 201 {object} response.APIResponse{data=TenderApprovalResponse} "Tender approval created successfully" +// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" +// @Failure 409 {object} response.APIResponse "Conflict - Tender approval already exists for this tender and company" +// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data" +// @Failure 500 {object} response.APIResponse "Internal server error" +// @Security BearerAuth +// @Router /api/v1/tender-approvals [post] +func (h *Handler) CreateTenderApproval(c echo.Context) error { + form, err := response.Parse[CreateTenderApprovalForm](c) + if err != nil { + return response.ValidationError(c, "Invalid request data", err.Error()) + } + + // Get company ID from context + companyID := c.Get("company_id").(string) + if companyID == "" { + return response.Unauthorized(c, "Unauthorized") + } + + tenderApproval, err := h.service.CreateTenderApproval(c.Request().Context(), form, companyID) + if err != nil { + if err.Error() == "tender approval already exists for this tender and company" { + return response.Conflict(c, err.Error()) + } + return response.InternalServerError(c, "Failed to create tender approval") + } + + return response.Created(c, tenderApproval.ToResponse(), "Tender approval created successfully") +} + +// UpdateTenderApproval updates a tender approval +// @Summary Update tender approval +// @Description Update tender approval information +// @Tags TenderApprovals +// @Accept json +// @Produce json +// @Param id path string true "Tender Approval ID" +// @Param tender_approval body UpdateTenderApprovalForm true "Tender approval update information" +// @Success 200 {object} response.APIResponse{data=TenderApprovalResponse} "Tender approval updated successfully" +// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data" +// @Failure 404 {object} response.APIResponse "Not found - Tender approval not found" +// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data" +// @Failure 500 {object} response.APIResponse "Internal server error" +// @Security BearerAuth +// @Router /api/v1/tender-approvals/{id} [put] +func (h *Handler) UpdateTenderApproval(c echo.Context) error { + id := c.Param("id") + form, err := response.Parse[UpdateTenderApprovalForm](c) + if err != nil { + return response.ValidationError(c, "Invalid request data", err.Error()) + } + + tenderApproval, err := h.service.UpdateTenderApproval(c.Request().Context(), id, form) + if err != nil { + if err.Error() == "tender approval not found" { + return response.NotFound(c, "Tender approval not found") + } + return response.InternalServerError(c, "Failed to update tender approval") + } + + return response.Success(c, tenderApproval.ToResponse(), "Tender approval updated successfully") +} + +// GetTenderApproval retrieves a tender approval by ID +// @Summary Get tender approval by ID +// @Description Retrieve detailed tender approval information by ID +// @Tags TenderApprovals +// @Accept json +// @Produce json +// @Param id path string true "Tender Approval ID" +// @Success 200 {object} response.APIResponse{data=TenderApprovalResponse} "Tender approval retrieved successfully" +// @Failure 400 {object} response.APIResponse "Bad request - Invalid tender approval ID" +// @Failure 404 {object} response.APIResponse "Not found - Tender approval not found" +// @Failure 500 {object} response.APIResponse "Internal server error" +// @Security BearerAuth +// @Router /api/v1/tender-approvals/{id} [get] +func (h *Handler) GetPublicTenderApproval(c echo.Context) error { + id := c.Param("id") + + tenderApproval, err := h.service.GetTenderApprovalByID(c.Request().Context(), id) + if err != nil { + if err.Error() == "tender approval not found" { + return response.NotFound(c, "Tender approval not found") + } + return response.InternalServerError(c, "Failed to get tender approval") + } + + return response.Success(c, tenderApproval.ToResponse(), "Tender approval retrieved successfully") +} + +// GetTenderApprovalByTenderAndCompany retrieves a tender approval by tender ID and company ID +// @Summary Get tender approval by tender and company +// @Description Retrieve tender approval information for a specific tender and company combination +// @Tags TenderApprovals +// @Accept json +// @Produce json +// @Param tender_id query string true "Tender ID" +// @Success 200 {object} response.APIResponse{data=TenderApprovalResponse} "Tender approval retrieved successfully" +// @Failure 400 {object} response.APIResponse "Bad request - Invalid tender ID or company ID" +// @Failure 404 {object} response.APIResponse "Not found - Tender approval not found" +// @Failure 500 {object} response.APIResponse "Internal server error" +// @Security BearerAuth +// @Router /api/v1/tender-approvals/tender/{tender_id} [get] +func (h *Handler) GetTenderApprovalByTenderAndCompany(c echo.Context) error { + tenderID := c.QueryParam("tender_id") + companyID := c.Get("company_id").(string) + + if companyID == "" { + return response.Unauthorized(c, "Unauthorized") + } + + if tenderID == "" { + return response.BadRequest(c, "Missing required parameters", "tender_id and company_id are required") + } + + tenderApproval, err := h.service.GetTenderApprovalByTenderAndCompany(c.Request().Context(), tenderID, companyID) + if err != nil { + if err.Error() == "tender approval not found" { + return response.NotFound(c, "Tender approval not found") + } + return response.InternalServerError(c, "Failed to get tender approval") + } + + return response.Success(c, tenderApproval.ToResponse(), "Tender approval retrieved successfully") +} + +// DeleteTenderApproval deletes a tender approval +// @Summary Delete tender approval +// @Description Delete a tender approval +// @Tags TenderApprovals +// @Accept json +// @Produce json +// @Param id path string true "Tender Approval ID" +// @Success 200 {object} response.APIResponse "Tender approval deleted successfully" +// @Failure 400 {object} response.APIResponse "Bad request - Invalid tender approval ID" +// @Failure 404 {object} response.APIResponse "Not found - Tender approval not found" +// @Failure 500 {object} response.APIResponse "Internal server error" +// @Security BearerAuth +// @Router /api/v1/tender-approvals/{id} [delete] +func (h *Handler) DeleteTenderApproval(c echo.Context) error { + id := c.Param("id") + + err := h.service.DeleteTenderApproval(c.Request().Context(), id) + if err != nil { + if err.Error() == "tender approval not found" { + return response.NotFound(c, "Tender approval not found") + } + return response.InternalServerError(c, "Failed to delete tender approval") + } + + return response.Success(c, map[string]interface{}{ + "message": "Tender approval deleted successfully", + }, "Tender approval deleted successfully") +} + +// GetTenderApprovalsByCompanyID retrieves all tender approvals for a specific company +// @Summary Get tender approvals by company ID +// @Description Retrieve all tender approvals for a specific company with optional filtering +// @Tags TenderApprovals +// @Accept json +// @Produce json +// @Param submission_mode query string false "Filter by submission mode Enums(self-apply, partnership)" +// @Param status query string false "Filter by status Enums(approved, rejected)" +// @Success 200 {object} response.APIResponse{data=[]TenderApprovalResponse} "Tender approvals retrieved successfully" +// @Failure 400 {object} response.APIResponse "Bad request - Invalid company ID" +// @Failure 500 {object} response.APIResponse "Internal server error" +// @Security BearerAuth +// @Router /api/v1/tender-approvals [get] +func (h *Handler) PublicGetTenderApprovals(c echo.Context) error { + companyID := c.Get("company_id").(string) + if companyID == "" { + return response.Unauthorized(c, "Unauthorized") + } + + // Get query parameters for filtering + submissionModeStr := c.QueryParam("submission_mode") + statusStr := c.QueryParam("status") + + // Parse submission mode filter + var submissionMode *SubmissionMode + if submissionModeStr != "" { + mode := SubmissionMode(submissionModeStr) + if mode != SubmissionModeSelfApply && mode != SubmissionModePartnership { + return response.BadRequest(c, "Invalid submission_mode value", "Must be 'self-apply' or 'partnership'") + } + submissionMode = &mode + } + + // Parse status filter + var status *ApprovalStatus + if statusStr != "" { + statusVal := ApprovalStatus(statusStr) + if statusVal != ApprovalStatusApproved && statusVal != ApprovalStatusRejected { + return response.BadRequest(c, "Invalid status value", "Must be 'approved' or 'rejected'") + } + status = &statusVal + } + + tenderApprovals, err := h.service.GetTenderApprovalsByCompanyID(c.Request().Context(), companyID, submissionMode, status) + if err != nil { + return response.InternalServerError(c, "Failed to get tender approvals") + } + + var responses []*TenderApprovalResponse + for _, approval := range tenderApprovals { + responses = append(responses, approval.ToResponse()) + } + + return response.Success(c, responses, "Tender approvals retrieved successfully") +} + +// **************** Admin Routes **************** + +// ListTenderApprovals lists tender approvals with filters and pagination +// @Summary List tender approvals with filters and pagination +// @Description Retrieve a paginated list of tender approvals with filtering options +// @Tags Admin-TenderApprovals +// @Accept json +// @Produce json +// @Param tender_id query string false "Filter by tender ID" +// @Param company_id query string false "Filter by company ID" +// @Param status query array false "Filter by status" +// @Param submission_mode query array false "Filter by submission mode" +// @Param created_at_from query integer false "Filter by creation date from (Unix timestamp)" +// @Param created_at_to query integer false "Filter by creation date to (Unix timestamp)" +// @Param limit query integer false "Number of approvals per page (1-100)" minimum(1) maximum(100) default(20) +// @Param offset query integer false "Number of approvals to skip for pagination" minimum(0) default(0) +// @Param sort_by query string false "Field to sort by" Enums(created_at, updated_at, status, submission_mode) default(created_at) +// @Param sort_order query string false "Sort order" Enums(asc, desc) default(desc) +// @Success 200 {object} response.APIResponse{data=TenderApprovalListResponse} "Tender approvals retrieved successfully" +// @Failure 400 {object} response.APIResponse "Bad request - Invalid query parameters" +// @Failure 422 {object} response.APIResponse "Validation error - Invalid query parameters" +// @Failure 500 {object} response.APIResponse "Internal server error" +// @Security BearerAuth +// @Router /admin/v1/tender-approvals [get] +func (h *Handler) ListTenderApprovals(c echo.Context) error { + form, err := response.Parse[ListTenderApprovalsForm](c) + if err != nil { + return response.ValidationError(c, "Invalid request data", err.Error()) + } + + result, err := h.service.ListTenderApprovals(c.Request().Context(), form) + if err != nil { + return response.InternalServerError(c, "Failed to list tender approvals") + } + + return response.Success(c, result, "Tender approvals retrieved successfully") +} + +// GetTenderApprovalStats returns tender approval statistics +// @Summary Get tender approval statistics +// @Description Get comprehensive tender approval statistics for dashboard +// @Tags Admin-TenderApprovals +// @Accept json +// @Produce json +// @Success 200 {object} response.APIResponse{data=TenderApprovalStatsResponse} "Tender approval statistics retrieved successfully" +// @Failure 500 {object} response.APIResponse "Internal server error" +// @Security BearerAuth +// @Router /admin/v1/tender-approvals/stats [get] +func (h *Handler) GetTenderApprovalStats(c echo.Context) error { + stats, err := h.service.GetTenderApprovalStats(c.Request().Context()) + if err != nil { + return response.InternalServerError(c, "Failed to get tender approval statistics") + } + + return response.Success(c, stats, "Tender approval statistics retrieved successfully") +} + +// GetTenderApprovalsByStatus retrieves tender approvals by status +// @Summary Get tender approvals by status +// @Description Retrieve tender approvals filtered by their status (approved, rejected) +// @Tags Admin-TenderApprovals +// @Accept json +// @Produce json +// @Param status path string true "Tender approval status" Enums(approved, rejected) +// @Param limit query integer false "Number of approvals per page (1-100)" minimum(1) maximum(100) default(20) +// @Param offset query integer false "Number of approvals to skip for pagination" minimum(0) default(0) +// @Success 200 {object} response.APIResponse{data=[]TenderApprovalResponse,meta=response.Meta} "Tender approvals retrieved successfully" +// @Failure 400 {object} response.APIResponse "Bad request - Invalid tender approval status" +// @Failure 500 {object} response.APIResponse "Internal server error" +// @Security BearerAuth +// @Router /admin/v1/tender-approvals/status/{status} [get] +func (h *Handler) GetTenderApprovalsByStatus(c echo.Context) error { + statusStr := c.Param("status") + status := ApprovalStatus(statusStr) + + // Validate status + if status != ApprovalStatusApproved && status != ApprovalStatusRejected { + return response.BadRequest(c, "Invalid tender approval status", "Must be approved or rejected") + } + + limit := 20 + if limitStr := c.QueryParam("limit"); limitStr != "" { + if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 { + limit = parsedLimit + } + } + + offset := 0 + if offsetStr := c.QueryParam("offset"); offsetStr != "" { + if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 { + offset = parsedOffset + } + } + + tenderApprovals, total, err := h.service.GetTenderApprovalsByStatus(c.Request().Context(), status, limit, offset) + if err != nil { + return response.InternalServerError(c, "Failed to retrieve tender approvals by status") + } + + var responses []*TenderApprovalResponse + for _, approval := range tenderApprovals { + responses = append(responses, approval.ToResponse()) + } + + meta := &response.Meta{ + Total: int(total), + Limit: limit, + Offset: offset, + } + + return response.SuccessWithMeta(c, responses, meta, "Tender approvals retrieved successfully") +} + +// GetTenderApprovalsBySubmissionMode retrieves tender approvals by submission mode +// @Summary Get tender approvals by submission mode +// @Description Retrieve tender approvals filtered by their submission mode (self-apply, partnership) +// @Tags Admin-TenderApprovals +// @Accept json +// @Produce json +// @Param submission_mode path string true "Tender approval submission mode" Enums(self-apply, partnership) +// @Param limit query integer false "Number of approvals per page (1-100)" minimum(1) maximum(100) default(20) +// @Param offset query integer false "Number of approvals to skip for pagination" minimum(0) default(0) +// @Success 200 {object} response.APIResponse{data=[]TenderApprovalResponse,meta=response.Meta} "Tender approvals retrieved successfully" +// @Failure 400 {object} response.APIResponse "Bad request - Invalid tender approval submission mode" +// @Failure 500 {object} response.APIResponse "Internal server error" +// @Security BearerAuth +// @Router /admin/v1/tender-approvals/submission-mode/{submission_mode} [get] +func (h *Handler) GetTenderApprovalsBySubmissionMode(c echo.Context) error { + submissionModeStr := c.Param("submission_mode") + submissionMode := SubmissionMode(submissionModeStr) + + // Validate submission mode + if submissionMode != SubmissionModeSelfApply && submissionMode != SubmissionModePartnership { + return response.BadRequest(c, "Invalid tender approval submission mode", "Must be self-apply or partnership") + } + + limit := 20 + if limitStr := c.QueryParam("limit"); limitStr != "" { + if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 { + limit = parsedLimit + } + } + + offset := 0 + if offsetStr := c.QueryParam("offset"); offsetStr != "" { + if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 { + offset = parsedOffset + } + } + + tenderApprovals, total, err := h.service.GetTenderApprovalsBySubmissionMode(c.Request().Context(), submissionMode, limit, offset) + if err != nil { + return response.InternalServerError(c, "Failed to retrieve tender approvals by submission mode") + } + + var responses []*TenderApprovalResponse + for _, approval := range tenderApprovals { + responses = append(responses, approval.ToResponse()) + } + + meta := &response.Meta{ + Total: int(total), + Limit: limit, + Offset: offset, + } + + return response.SuccessWithMeta(c, responses, meta, "Tender approvals retrieved successfully") +} + +// GetTenderApproval retrieves a tender approval by ID +// @Summary Get tender approval by ID +// @Description Retrieve detailed tender approval information by ID +// @Tags TenderApprovals +// @Accept json +// @Produce json +// @Param id path string true "Tender Approval ID" +// @Success 200 {object} response.APIResponse{data=TenderApprovalResponse} "Tender approval retrieved successfully" +// @Failure 400 {object} response.APIResponse "Bad request - Invalid tender approval ID" +// @Failure 404 {object} response.APIResponse "Not found - Tender approval not found" +// @Failure 500 {object} response.APIResponse "Internal server error" +// @Security BearerAuth +// @Router /admin/v1/tender-approvals/{id} [get] +func (h *Handler) GetTenderApproval(c echo.Context) error { + id := c.Param("id") + + tenderApproval, err := h.service.GetTenderApprovalByID(c.Request().Context(), id) + if err != nil { + if err.Error() == "tender approval not found" { + return response.NotFound(c, "Tender approval not found") + } + return response.InternalServerError(c, "Failed to get tender approval") + } + + return response.Success(c, tenderApproval.ToResponse(), "Tender approval retrieved successfully") +} diff --git a/internal/tender_approval/repository.go b/internal/tender_approval/repository.go new file mode 100644 index 0000000..1d4372f --- /dev/null +++ b/internal/tender_approval/repository.go @@ -0,0 +1,519 @@ +package tender_approval + +import ( + "context" + "errors" + "time" + "tm/pkg/logger" + "tm/pkg/mongo" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +// Repository defines the interface for tender approval data operations +type Repository interface { + Create(ctx context.Context, tenderApproval *TenderApproval) error + GetByID(ctx context.Context, id string) (*TenderApproval, error) + GetByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error) + GetByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error) + GetByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus) ([]*TenderApproval, error) + GetByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error) + Update(ctx context.Context, tenderApproval *TenderApproval) error + Delete(ctx context.Context, id string) error + List(ctx context.Context, limit, offset int) ([]*TenderApproval, error) + Search(ctx context.Context, tenderID *string, companyID *string, status []ApprovalStatus, submissionMode []SubmissionMode, createdAtFrom *int64, createdAtTo *int64, limit, offset int, sortBy, sortOrder string) ([]*TenderApproval, error) + Count(ctx context.Context) (int64, error) + CountByStatus(ctx context.Context, status ApprovalStatus) (int64, error) + CountBySubmissionMode(ctx context.Context, submissionMode SubmissionMode) (int64, error) + CountByTenderID(ctx context.Context, tenderID string) (int64, error) + CountByCompanyID(ctx context.Context, companyID string) (int64, error) + GetTenderApprovalStats(ctx context.Context) (*TenderApprovalStatsResponse, error) + SearchByCriteria(ctx context.Context, criteria *TenderApprovalSearchCriteria) ([]*TenderApproval, error) +} + +// tenderApprovalRepository implements the Repository interface using the MongoDB ORM +type tenderApprovalRepository struct { + ormRepo mongo.Repository[TenderApproval] + logger logger.Logger +} + +// NewTenderApprovalRepository creates a new tender approval repository +func NewTenderApprovalRepository(mongoManager *mongo.ConnectionManager, logger logger.Logger) Repository { + // Create indexes using the ORM's index management + indexes := []mongo.Index{ + *mongo.CreateUniqueIndex("tender_company_idx", bson.D{{Key: "tender_id", Value: 1}, {Key: "company_id", Value: 1}}), + *mongo.NewIndex("tender_id_idx", bson.D{{Key: "tender_id", Value: 1}}), + *mongo.NewIndex("company_id_idx", bson.D{{Key: "company_id", Value: 1}}), + *mongo.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}), + *mongo.NewIndex("submission_mode_idx", bson.D{{Key: "submission_mode", Value: 1}}), + *mongo.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}), + *mongo.NewIndex("updated_at_idx", bson.D{{Key: "updated_at", Value: -1}}), + } + + // Create indexes + err := mongoManager.CreateIndexes("tender_approvals", indexes) + if err != nil { + logger.Warn("Failed to create tender approval indexes", map[string]interface{}{ + "error": err.Error(), + }) + } + + // Create ORM repository + ormRepo := mongo.NewRepository[TenderApproval](mongoManager.GetCollection("tender_approvals"), logger) + + return &tenderApprovalRepository{ + ormRepo: ormRepo, + logger: logger, + } +} + +// Create creates a new tender approval +func (r *tenderApprovalRepository) Create(ctx context.Context, tenderApproval *TenderApproval) error { + // Set created/updated timestamps using Unix timestamps + now := time.Now().Unix() + tenderApproval.SetCreatedAt(now) + tenderApproval.SetUpdatedAt(now) + + // Use ORM to create tender approval + err := r.ormRepo.Create(ctx, tenderApproval) + if err != nil { + r.logger.Error("Failed to create tender approval", map[string]interface{}{ + "error": err.Error(), + "tender_id": tenderApproval.TenderID, + "company_id": tenderApproval.CompanyID, + }) + return err + } + + r.logger.Info("Tender approval created successfully", map[string]interface{}{ + "tender_approval_id": tenderApproval.ID, + "tender_id": tenderApproval.TenderID, + "company_id": tenderApproval.CompanyID, + "status": tenderApproval.Status, + }) + + return nil +} + +// GetByID retrieves a tender approval by ID +func (r *tenderApprovalRepository) GetByID(ctx context.Context, id string) (*TenderApproval, error) { + tenderApproval, err := r.ormRepo.FindByID(ctx, id) + if err != nil { + if errors.Is(err, mongo.ErrDocumentNotFound) { + return nil, errors.New("tender approval not found") + } + r.logger.Error("Failed to get tender approval by ID", map[string]interface{}{ + "error": err.Error(), + "tender_approval_id": id, + }) + return nil, err + } + + return tenderApproval, nil +} + +// GetByTenderAndCompany retrieves a tender approval by tender ID and company ID +func (r *tenderApprovalRepository) GetByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error) { + filter := bson.M{ + "tender_id": tenderID, + "company_id": companyID, + } + + tenderApproval, err := r.ormRepo.FindOne(ctx, filter) + if err != nil { + if errors.Is(err, mongo.ErrDocumentNotFound) { + return nil, errors.New("tender approval not found") + } + r.logger.Error("Failed to get tender approval by tender and company", map[string]interface{}{ + "error": err.Error(), + "tender_id": tenderID, + "company_id": companyID, + }) + return nil, err + } + + return tenderApproval, nil +} + +// GetByTenderID retrieves all tender approvals for a specific tender +func (r *tenderApprovalRepository) GetByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error) { + filter := bson.M{"tender_id": tenderID} + + result, err := r.ormRepo.FindAll(ctx, filter, mongo.NewPaginationBuilder().SortDesc("created_at").Build()) + if err != nil { + r.logger.Error("Failed to get tender approvals by tender ID", map[string]interface{}{ + "error": err.Error(), + "tender_id": tenderID, + }) + return nil, err + } + + // Convert []TenderApproval to []*TenderApproval + tenderApprovals := make([]*TenderApproval, len(result.Items)) + for i := range result.Items { + tenderApprovals[i] = &result.Items[i] + } + + return tenderApprovals, nil +} + +// GetByCompanyID retrieves all tender approvals for a specific company with optional filters +func (r *tenderApprovalRepository) GetByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus) ([]*TenderApproval, error) { + filter := bson.M{"company_id": companyID} + + // Add submission mode filter if provided + if submissionMode != nil { + filter["submission_mode"] = *submissionMode + } + + // Add status filter if provided + if status != nil { + filter["status"] = *status + } + + result, err := r.ormRepo.FindAll(ctx, filter, mongo.NewPaginationBuilder().SortDesc("created_at").Build()) + if err != nil { + r.logger.Error("Failed to get tender approvals by company ID", map[string]interface{}{ + "error": err.Error(), + "company_id": companyID, + }) + return nil, err + } + + // Convert []TenderApproval to []*TenderApproval + tenderApprovals := make([]*TenderApproval, len(result.Items)) + for i := range result.Items { + tenderApprovals[i] = &result.Items[i] + } + + return tenderApprovals, nil +} + +// GetByIDs retrieves multiple tender approvals by their IDs +func (r *tenderApprovalRepository) GetByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error) { + if len(ids) == 0 { + return []*TenderApproval{}, nil + } + + // Convert string IDs to ObjectIDs for MongoDB query + objectIDs := make([]interface{}, len(ids)) + for i, id := range ids { + objectID, err := primitive.ObjectIDFromHex(id) + if err != nil { + r.logger.Warn("Invalid tender approval ID format", map[string]interface{}{ + "id": id, + "error": err.Error(), + }) + continue + } + objectIDs[i] = objectID + } + + if len(objectIDs) == 0 { + return []*TenderApproval{}, nil + } + + filter := bson.M{"_id": bson.M{"$in": objectIDs}} + result, err := r.ormRepo.FindAll(ctx, filter, mongo.NewPaginationBuilder().Limit(len(ids)).Build()) + if err != nil { + r.logger.Error("Failed to get tender approvals by IDs", map[string]interface{}{ + "error": err.Error(), + "ids": ids, + }) + return nil, err + } + + // Convert []TenderApproval to []*TenderApproval + tenderApprovals := make([]*TenderApproval, len(result.Items)) + for i := range result.Items { + tenderApprovals[i] = &result.Items[i] + } + + r.logger.Info("Retrieved tender approvals by IDs", map[string]interface{}{ + "requested_count": len(ids), + "found_count": len(tenderApprovals), + }) + + return tenderApprovals, nil +} + +// Update updates a tender approval +func (r *tenderApprovalRepository) Update(ctx context.Context, tenderApproval *TenderApproval) error { + // Set updated timestamp using Unix timestamp + tenderApproval.SetUpdatedAt(time.Now().Unix()) + + // Use ORM to update tender approval + err := r.ormRepo.Update(ctx, tenderApproval) + if err != nil { + r.logger.Error("Failed to update tender approval", map[string]interface{}{ + "error": err.Error(), + "tender_approval_id": tenderApproval.ID, + }) + return err + } + + r.logger.Info("Tender approval updated successfully", map[string]interface{}{ + "tender_approval_id": tenderApproval.ID, + "tender_id": tenderApproval.TenderID, + "company_id": tenderApproval.CompanyID, + }) + + return nil +} + +// Delete deletes a tender approval +func (r *tenderApprovalRepository) Delete(ctx context.Context, id string) error { + // Get tender approval first + tenderApproval, err := r.GetByID(ctx, id) + if err != nil { + return err + } + + // Delete from database + err = r.ormRepo.Delete(ctx, id) + if err != nil { + r.logger.Error("Failed to delete tender approval", map[string]interface{}{ + "error": err.Error(), + "tender_approval_id": id, + }) + return err + } + + r.logger.Info("Tender approval deleted successfully", map[string]interface{}{ + "tender_approval_id": id, + "tender_id": tenderApproval.TenderID, + "company_id": tenderApproval.CompanyID, + }) + + return nil +} + +// List retrieves tender approvals with pagination +func (r *tenderApprovalRepository) List(ctx context.Context, limit, offset int) ([]*TenderApproval, error) { + // Build pagination + pagination := mongo.NewPaginationBuilder(). + Limit(limit). + Skip(offset). + SortDesc("created_at"). + Build() + + // Use ORM to find tender approvals + result, err := r.ormRepo.FindAll(ctx, bson.M{}, pagination) + if err != nil { + r.logger.Error("Failed to list tender approvals", map[string]interface{}{ + "error": err.Error(), + "limit": limit, + "offset": offset, + }) + return nil, err + } + + // Convert []TenderApproval to []*TenderApproval + tenderApprovals := make([]*TenderApproval, len(result.Items)) + for i := range result.Items { + tenderApprovals[i] = &result.Items[i] + } + + return tenderApprovals, nil +} + +// Search retrieves tender approvals with search and filters +func (r *tenderApprovalRepository) Search(ctx context.Context, tenderID *string, companyID *string, status []ApprovalStatus, submissionMode []SubmissionMode, createdAtFrom *int64, createdAtTo *int64, limit, offset int, sortBy, sortOrder string) ([]*TenderApproval, error) { + // Build filter + filter := bson.M{} + + if tenderID != nil { + filter["tender_id"] = *tenderID + } + + if companyID != nil { + filter["company_id"] = *companyID + } + + if len(status) > 0 { + statusStrings := make([]string, len(status)) + for i, s := range status { + statusStrings[i] = string(s) + } + filter["status"] = bson.M{"$in": statusStrings} + } + + if len(submissionMode) > 0 { + modeStrings := make([]string, len(submissionMode)) + for i, m := range submissionMode { + modeStrings[i] = string(m) + } + filter["submission_mode"] = bson.M{"$in": modeStrings} + } + + // Date range filters + if createdAtFrom != nil || createdAtTo != nil { + dateFilter := bson.M{} + if createdAtFrom != nil { + dateFilter["$gte"] = *createdAtFrom + } + if createdAtTo != nil { + dateFilter["$lte"] = *createdAtTo + } + filter["created_at"] = dateFilter + } + + // Build pagination + pagination := mongo.NewPaginationBuilder(). + Limit(limit). + Skip(offset) + + // Set sort + if sortBy != "" { + if sortOrder == "desc" { + pagination.SortDesc(sortBy) + } else { + pagination.SortAsc(sortBy) + } + } else { + pagination.SortDesc("created_at") // Default sort + } + + // Use ORM to find tender approvals + result, err := r.ormRepo.FindAll(ctx, filter, pagination.Build()) + if err != nil { + r.logger.Error("Failed to search tender approvals", map[string]interface{}{ + "error": err.Error(), + }) + return nil, err + } + + // Convert []TenderApproval to []*TenderApproval + tenderApprovals := make([]*TenderApproval, len(result.Items)) + for i := range result.Items { + tenderApprovals[i] = &result.Items[i] + } + + return tenderApprovals, nil +} + +// Count counts all tender approvals +func (r *tenderApprovalRepository) Count(ctx context.Context) (int64, error) { + count, err := r.ormRepo.Count(ctx, bson.M{}) + if err != nil { + r.logger.Error("Failed to count tender approvals", map[string]interface{}{ + "error": err.Error(), + }) + return 0, err + } + + return count, nil +} + +// CountByStatus counts tender approvals by status +func (r *tenderApprovalRepository) CountByStatus(ctx context.Context, status ApprovalStatus) (int64, error) { + filter := bson.M{"status": string(status)} + + count, err := r.ormRepo.Count(ctx, filter) + if err != nil { + r.logger.Error("Failed to count tender approvals by status", map[string]interface{}{ + "error": err.Error(), + "status": status, + }) + return 0, err + } + + return count, nil +} + +// CountBySubmissionMode counts tender approvals by submission mode +func (r *tenderApprovalRepository) CountBySubmissionMode(ctx context.Context, submissionMode SubmissionMode) (int64, error) { + filter := bson.M{"submission_mode": string(submissionMode)} + + count, err := r.ormRepo.Count(ctx, filter) + if err != nil { + r.logger.Error("Failed to count tender approvals by submission mode", map[string]interface{}{ + "error": err.Error(), + "submission_mode": submissionMode, + }) + return 0, err + } + + return count, nil +} + +// CountByTenderID counts tender approvals for a specific tender +func (r *tenderApprovalRepository) CountByTenderID(ctx context.Context, tenderID string) (int64, error) { + filter := bson.M{"tender_id": tenderID} + + count, err := r.ormRepo.Count(ctx, filter) + if err != nil { + r.logger.Error("Failed to count tender approvals by tender ID", map[string]interface{}{ + "error": err.Error(), + "tender_id": tenderID, + }) + return 0, err + } + + return count, nil +} + +// CountByCompanyID counts tender approvals for a specific company +func (r *tenderApprovalRepository) CountByCompanyID(ctx context.Context, companyID string) (int64, error) { + filter := bson.M{"company_id": companyID} + + count, err := r.ormRepo.Count(ctx, filter) + if err != nil { + r.logger.Error("Failed to count tender approvals by company ID", map[string]interface{}{ + "error": err.Error(), + "company_id": companyID, + }) + return 0, err + } + + return count, nil +} + +// GetTenderApprovalStats returns tender approval statistics +func (r *tenderApprovalRepository) GetTenderApprovalStats(ctx context.Context) (*TenderApprovalStatsResponse, error) { + // Get basic counts + totalApprovals, _ := r.Count(ctx) + approvedTenders, _ := r.CountByStatus(ctx, ApprovalStatusApproved) + rejectedTenders, _ := r.CountByStatus(ctx, ApprovalStatusRejected) + + // For this implementation, we'll return basic stats + // In a real implementation, you'd use MongoDB aggregation pipelines + stats := &TenderApprovalStatsResponse{ + TotalApprovals: totalApprovals, + ApprovedTenders: approvedTenders, + RejectedTenders: rejectedTenders, + ApprovalsByStatus: make(map[string]int64), + ApprovalsBySubmission: make(map[string]int64), + LastUpdated: time.Now().Unix(), + } + + // Add status breakdown + stats.ApprovalsByStatus["approved"] = approvedTenders + stats.ApprovalsByStatus["rejected"] = rejectedTenders + + // Add submission mode breakdown + selfApplyCount, _ := r.CountBySubmissionMode(ctx, SubmissionModeSelfApply) + partnershipCount, _ := r.CountBySubmissionMode(ctx, SubmissionModePartnership) + stats.ApprovalsBySubmission["self-apply"] = selfApplyCount + stats.ApprovalsBySubmission["partnership"] = partnershipCount + + return stats, nil +} + +// SearchByCriteria searches tender approvals using search criteria +func (r *tenderApprovalRepository) SearchByCriteria(ctx context.Context, criteria *TenderApprovalSearchCriteria) ([]*TenderApproval, error) { + // Convert criteria to search parameters + var status []ApprovalStatus + if criteria.Status != nil { + status = criteria.Status + } + + var submissionMode []SubmissionMode + if criteria.SubmissionMode != nil { + submissionMode = criteria.SubmissionMode + } + + // Use the search method + return r.Search(ctx, criteria.TenderID, criteria.CompanyID, status, submissionMode, criteria.CreatedAtFrom, criteria.CreatedAtTo, criteria.Limit, criteria.Offset, "created_at", "desc") +} diff --git a/internal/tender_approval/service.go b/internal/tender_approval/service.go new file mode 100644 index 0000000..76ad470 --- /dev/null +++ b/internal/tender_approval/service.go @@ -0,0 +1,527 @@ +package tender_approval + +import ( + "context" + "errors" + "time" + + "tm/pkg/logger" +) + +// Service defines business logic for tender approval operations +type Service interface { + // Core tender approval operations + CreateTenderApproval(ctx context.Context, form *CreateTenderApprovalForm, companyID string) (*TenderApproval, error) + GetTenderApprovalByID(ctx context.Context, id string) (*TenderApproval, error) + GetTenderApprovalByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error) + GetTenderApprovalsByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error) + GetTenderApprovalsByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus) ([]*TenderApproval, error) + GetTenderApprovalsByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error) + UpdateTenderApproval(ctx context.Context, id string, form *UpdateTenderApprovalForm) (*TenderApproval, error) + DeleteTenderApproval(ctx context.Context, id string) error + + // Tender approval listing and search + ListTenderApprovals(ctx context.Context, form *ListTenderApprovalsForm) (*TenderApprovalListResponse, error) + SearchTenderApprovals(ctx context.Context, form *TenderApprovalSearchForm) (*TenderApprovalListResponse, error) + SearchTenderApprovalsByCriteria(ctx context.Context, criteria *TenderApprovalSearchCriteria) ([]*TenderApproval, error) + + // Business operations + ApproveTender(ctx context.Context, id string, form *ApproveTenderForm) (*TenderApproval, error) + RejectTender(ctx context.Context, id string, form *RejectTenderForm) (*TenderApproval, error) + + // Statistics and analytics + GetTenderApprovalStats(ctx context.Context) (*TenderApprovalStatsResponse, error) + + // Search and filtering helpers + GetTenderApprovalsByStatus(ctx context.Context, status ApprovalStatus, limit, offset int) ([]*TenderApproval, int64, error) + GetTenderApprovalsBySubmissionMode(ctx context.Context, submissionMode SubmissionMode, limit, offset int) ([]*TenderApproval, int64, error) +} + +// tenderApprovalService implements the Service interface +type tenderApprovalService struct { + repository Repository + logger logger.Logger +} + +// NewTenderApprovalService creates a new tender approval service +func NewTenderApprovalService(repository Repository, logger logger.Logger) Service { + return &tenderApprovalService{ + repository: repository, + logger: logger, + } +} + +// CreateTenderApproval creates a new tender approval +func (s *tenderApprovalService) CreateTenderApproval(ctx context.Context, form *CreateTenderApprovalForm, companyID string) (*TenderApproval, error) { + // Check if tender approval already exists for this tender and company + existingApproval, _ := s.repository.GetByTenderAndCompany(ctx, form.TenderID, companyID) + if existingApproval != nil { + return nil, errors.New("tender approval already exists for this tender and company") + } + + // Create tender approval + tenderApproval := &TenderApproval{ + TenderID: form.TenderID, + CompanyID: companyID, + SubmissionMode: SubmissionMode(form.SubmissionMode), + Status: ApprovalStatus(form.Status), + } + + // Save to database + err := s.repository.Create(ctx, tenderApproval) + if err != nil { + s.logger.Error("Failed to create tender approval", map[string]interface{}{ + "error": err.Error(), + "tender_id": form.TenderID, + "company_id": companyID, + }) + return nil, err + } + + s.logger.Info("Tender approval created successfully", map[string]interface{}{ + "tender_approval_id": tenderApproval.ID, + "tender_id": tenderApproval.TenderID, + "company_id": tenderApproval.CompanyID, + "submission_mode": tenderApproval.SubmissionMode, + "status": tenderApproval.Status, + }) + + return tenderApproval, nil +} + +// GetTenderApprovalByID retrieves a tender approval by ID +func (s *tenderApprovalService) GetTenderApprovalByID(ctx context.Context, id string) (*TenderApproval, error) { + tenderApproval, err := s.repository.GetByID(ctx, id) + if err != nil { + s.logger.Error("Failed to get tender approval by ID", map[string]interface{}{ + "error": err.Error(), + "tender_approval_id": id, + }) + return nil, err + } + + s.logger.Info("Tender approval retrieved successfully", map[string]interface{}{ + "tender_approval_id": tenderApproval.ID, + "tender_id": tenderApproval.TenderID, + "company_id": tenderApproval.CompanyID, + }) + + return tenderApproval, nil +} + +// GetTenderApprovalByTenderAndCompany retrieves a tender approval by tender ID and company ID +func (s *tenderApprovalService) GetTenderApprovalByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error) { + tenderApproval, err := s.repository.GetByTenderAndCompany(ctx, tenderID, companyID) + if err != nil { + s.logger.Error("Failed to get tender approval by tender and company", map[string]interface{}{ + "error": err.Error(), + "tender_id": tenderID, + "company_id": companyID, + }) + return nil, err + } + + s.logger.Info("Tender approval retrieved successfully", map[string]interface{}{ + "tender_approval_id": tenderApproval.ID, + "tender_id": tenderApproval.TenderID, + "company_id": tenderApproval.CompanyID, + }) + + return tenderApproval, nil +} + +// GetTenderApprovalsByTenderID retrieves all tender approvals for a specific tender +func (s *tenderApprovalService) GetTenderApprovalsByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error) { + tenderApprovals, err := s.repository.GetByTenderID(ctx, tenderID) + if err != nil { + s.logger.Error("Failed to get tender approvals by tender ID", map[string]interface{}{ + "error": err.Error(), + "tender_id": tenderID, + }) + return nil, err + } + + s.logger.Info("Tender approvals retrieved successfully", map[string]interface{}{ + "tender_id": tenderID, + "count": len(tenderApprovals), + }) + + return tenderApprovals, nil +} + +// GetTenderApprovalsByCompanyID retrieves all tender approvals for a specific company with optional filters +func (s *tenderApprovalService) GetTenderApprovalsByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus) ([]*TenderApproval, error) { + tenderApprovals, err := s.repository.GetByCompanyID(ctx, companyID, submissionMode, status) + if err != nil { + s.logger.Error("Failed to get tender approvals by company ID", map[string]interface{}{ + "error": err.Error(), + "company_id": companyID, + }) + return nil, err + } + + s.logger.Info("Tender approvals retrieved successfully", map[string]interface{}{ + "company_id": companyID, + "count": len(tenderApprovals), + }) + + return tenderApprovals, nil +} + +// GetTenderApprovalsByIDs retrieves multiple tender approvals by their IDs +func (s *tenderApprovalService) GetTenderApprovalsByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error) { + if len(ids) == 0 { + return []*TenderApproval{}, nil + } + + tenderApprovals, err := s.repository.GetByIDs(ctx, ids) + if err != nil { + s.logger.Error("Failed to get tender approvals by IDs", map[string]interface{}{ + "error": err.Error(), + "ids": ids, + }) + return nil, err + } + + s.logger.Info("Tender approvals retrieved successfully", map[string]interface{}{ + "requested_count": len(ids), + "found_count": len(tenderApprovals), + }) + + return tenderApprovals, nil +} + +// UpdateTenderApproval updates a tender approval +func (s *tenderApprovalService) UpdateTenderApproval(ctx context.Context, id string, form *UpdateTenderApprovalForm) (*TenderApproval, error) { + // Get existing tender approval + tenderApproval, err := s.repository.GetByID(ctx, id) + if err != nil { + s.logger.Error("Failed to get tender approval for update", map[string]interface{}{ + "error": err.Error(), + "tender_approval_id": id, + }) + return nil, errors.New("tender approval not found") + } + + // Update fields if provided + if form.SubmissionMode != nil { + tenderApproval.SubmissionMode = SubmissionMode(*form.SubmissionMode) + } + + if form.Status != nil { + tenderApproval.Status = ApprovalStatus(*form.Status) + } + + // Set updated timestamp + tenderApproval.UpdatedAt = time.Now().Unix() + + // Update in database + err = s.repository.Update(ctx, tenderApproval) + if err != nil { + s.logger.Error("Failed to update tender approval", map[string]interface{}{ + "error": err.Error(), + "tender_approval_id": id, + }) + return nil, errors.New("failed to update tender approval") + } + + s.logger.Info("Tender approval updated successfully", map[string]interface{}{ + "tender_approval_id": tenderApproval.ID, + "tender_id": tenderApproval.TenderID, + "company_id": tenderApproval.CompanyID, + }) + + return tenderApproval, nil +} + +// DeleteTenderApproval deletes a tender approval +func (s *tenderApprovalService) DeleteTenderApproval(ctx context.Context, id string) error { + // Get tender approval to check if exists + tenderApproval, err := s.repository.GetByID(ctx, id) + if err != nil { + return errors.New("tender approval not found") + } + + // Delete tender approval + err = s.repository.Delete(ctx, id) + if err != nil { + s.logger.Error("Failed to delete tender approval", map[string]interface{}{ + "error": err.Error(), + "tender_approval_id": id, + }) + return errors.New("failed to delete tender approval") + } + + s.logger.Info("Tender approval deleted successfully", map[string]interface{}{ + "tender_approval_id": id, + "tender_id": tenderApproval.TenderID, + "company_id": tenderApproval.CompanyID, + }) + + return nil +} + +// ListTenderApprovals lists tender approvals with search and filters +func (s *tenderApprovalService) ListTenderApprovals(ctx context.Context, form *ListTenderApprovalsForm) (*TenderApprovalListResponse, error) { + // Set defaults + limit := 20 + if form.Limit != nil { + limit = *form.Limit + } + + offset := 0 + if form.Offset != nil { + offset = *form.Offset + } + + sortBy := "created_at" + if form.SortBy != nil { + sortBy = *form.SortBy + } + + sortOrder := "desc" + if form.SortOrder != nil { + sortOrder = *form.SortOrder + } + + // Convert status strings to ApprovalStatus + var status []ApprovalStatus + if form.Status != nil { + status = make([]ApprovalStatus, len(form.Status)) + for i, s := range form.Status { + status[i] = ApprovalStatus(s) + } + } + + // Convert submission mode strings to SubmissionMode + var submissionMode []SubmissionMode + if form.SubmissionMode != nil { + submissionMode = make([]SubmissionMode, len(form.SubmissionMode)) + for i, m := range form.SubmissionMode { + submissionMode[i] = SubmissionMode(m) + } + } + + // Get tender approvals + tenderApprovals, err := s.repository.Search(ctx, form.TenderID, form.CompanyID, status, submissionMode, form.CreatedAtFrom, form.CreatedAtTo, limit, offset, sortBy, sortOrder) + if err != nil { + s.logger.Error("Failed to list tender approvals", map[string]interface{}{ + "error": err.Error(), + }) + return nil, errors.New("failed to list tender approvals") + } + + // Convert to responses + var tenderApprovalResponses []*TenderApprovalResponse + for _, approval := range tenderApprovals { + tenderApprovalResponses = append(tenderApprovalResponses, approval.ToResponse()) + } + + // TODO: Implement proper count for pagination metadata + total := int64(len(tenderApprovals)) // This should be a separate count query + totalPages := (total + int64(limit) - 1) / int64(limit) + + return &TenderApprovalListResponse{ + TenderApprovals: tenderApprovalResponses, + Total: total, + Limit: limit, + Offset: offset, + TotalPages: int(totalPages), + }, nil +} + +// SearchTenderApprovals searches tender approvals with advanced filters +func (s *tenderApprovalService) SearchTenderApprovals(ctx context.Context, form *TenderApprovalSearchForm) (*TenderApprovalListResponse, error) { + // Set defaults + limit := 20 + if form.Limit != nil { + limit = *form.Limit + } + + offset := 0 + if form.Offset != nil { + offset = *form.Offset + } + + // Convert status strings to ApprovalStatus + var status []ApprovalStatus + if form.Status != nil { + status = make([]ApprovalStatus, len(form.Status)) + for i, s := range form.Status { + status[i] = ApprovalStatus(s) + } + } + + // Convert submission mode strings to SubmissionMode + var submissionMode []SubmissionMode + if form.SubmissionMode != nil { + submissionMode = make([]SubmissionMode, len(form.SubmissionMode)) + for i, m := range form.SubmissionMode { + submissionMode[i] = SubmissionMode(m) + } + } + + // Get tender approvals using search + tenderApprovals, err := s.repository.Search(ctx, form.TenderID, form.CompanyID, status, submissionMode, form.CreatedAtFrom, form.CreatedAtTo, limit, offset, "created_at", "desc") + if err != nil { + s.logger.Error("Failed to search tender approvals", map[string]interface{}{ + "error": err.Error(), + }) + return nil, errors.New("failed to search tender approvals") + } + + // Convert to responses + var tenderApprovalResponses []*TenderApprovalResponse + for _, approval := range tenderApprovals { + tenderApprovalResponses = append(tenderApprovalResponses, approval.ToResponse()) + } + + // TODO: Implement proper count for pagination metadata + total := int64(len(tenderApprovals)) // This should be a separate count query + totalPages := (total + int64(limit) - 1) / int64(limit) + + return &TenderApprovalListResponse{ + TenderApprovals: tenderApprovalResponses, + Total: total, + Limit: limit, + Offset: offset, + TotalPages: int(totalPages), + }, nil +} + +// SearchTenderApprovalsByCriteria searches tender approvals using search criteria +func (s *tenderApprovalService) SearchTenderApprovalsByCriteria(ctx context.Context, criteria *TenderApprovalSearchCriteria) ([]*TenderApproval, error) { + tenderApprovals, err := s.repository.SearchByCriteria(ctx, criteria) + if err != nil { + s.logger.Error("Failed to search tender approvals by criteria", map[string]interface{}{ + "error": err.Error(), + }) + return nil, errors.New("failed to search tender approvals by criteria") + } + + return tenderApprovals, nil +} + +// ApproveTender approves a tender +func (s *tenderApprovalService) ApproveTender(ctx context.Context, id string, form *ApproveTenderForm) (*TenderApproval, error) { + // Get tender approval to check if exists + tenderApproval, err := s.repository.GetByID(ctx, id) + if err != nil { + return nil, errors.New("tender approval not found") + } + + // Approve the tender + tenderApproval.Approve(form.Notes) + + // Update in database + err = s.repository.Update(ctx, tenderApproval) + if err != nil { + s.logger.Error("Failed to approve tender", map[string]interface{}{ + "error": err.Error(), + "tender_approval_id": id, + }) + return nil, errors.New("failed to approve tender") + } + + s.logger.Info("Tender approved successfully", map[string]interface{}{ + "tender_approval_id": tenderApproval.ID, + "tender_id": tenderApproval.TenderID, + "company_id": tenderApproval.CompanyID, + }) + + return tenderApproval, nil +} + +// RejectTender rejects a tender +func (s *tenderApprovalService) RejectTender(ctx context.Context, id string, form *RejectTenderForm) (*TenderApproval, error) { + // Get tender approval to check if exists + tenderApproval, err := s.repository.GetByID(ctx, id) + if err != nil { + return nil, errors.New("tender approval not found") + } + + // Reject the tender + tenderApproval.Reject(form.Reason) + + // Update in database + err = s.repository.Update(ctx, tenderApproval) + if err != nil { + s.logger.Error("Failed to reject tender", map[string]interface{}{ + "error": err.Error(), + "tender_approval_id": id, + }) + return nil, errors.New("failed to reject tender") + } + + s.logger.Info("Tender rejected successfully", map[string]interface{}{ + "tender_approval_id": tenderApproval.ID, + "tender_id": tenderApproval.TenderID, + "company_id": tenderApproval.CompanyID, + "reason": form.Reason, + }) + + return tenderApproval, nil +} + +// GetTenderApprovalStats returns tender approval statistics +func (s *tenderApprovalService) GetTenderApprovalStats(ctx context.Context) (*TenderApprovalStatsResponse, error) { + stats, err := s.repository.GetTenderApprovalStats(ctx) + if err != nil { + s.logger.Error("Failed to get tender approval stats", map[string]interface{}{ + "error": err.Error(), + }) + return nil, errors.New("failed to get tender approval statistics") + } + + return stats, nil +} + +// GetTenderApprovalsByStatus retrieves tender approvals by status with pagination +func (s *tenderApprovalService) GetTenderApprovalsByStatus(ctx context.Context, status ApprovalStatus, limit, offset int) ([]*TenderApproval, int64, error) { + // Use search method with status filter + tenderApprovals, err := s.repository.Search(ctx, nil, nil, []ApprovalStatus{status}, nil, nil, nil, limit, offset, "created_at", "desc") + if err != nil { + s.logger.Error("Failed to get tender approvals by status", map[string]interface{}{ + "error": err.Error(), + "status": status, + }) + return nil, 0, errors.New("failed to get tender approvals by status") + } + + // Get total count + total, err := s.repository.CountByStatus(ctx, status) + if err != nil { + s.logger.Error("Failed to count tender approvals by status", map[string]interface{}{ + "error": err.Error(), + "status": status, + }) + return tenderApprovals, 0, nil // Return approvals even if count fails + } + + return tenderApprovals, total, nil +} + +// GetTenderApprovalsBySubmissionMode retrieves tender approvals by submission mode with pagination +func (s *tenderApprovalService) GetTenderApprovalsBySubmissionMode(ctx context.Context, submissionMode SubmissionMode, limit, offset int) ([]*TenderApproval, int64, error) { + // Use search method with submission mode filter + tenderApprovals, err := s.repository.Search(ctx, nil, nil, nil, []SubmissionMode{submissionMode}, nil, nil, limit, offset, "created_at", "desc") + if err != nil { + s.logger.Error("Failed to get tender approvals by submission mode", map[string]interface{}{ + "error": err.Error(), + "submission_mode": submissionMode, + }) + return nil, 0, errors.New("failed to get tender approvals by submission mode") + } + + // Get total count + total, err := s.repository.CountBySubmissionMode(ctx, submissionMode) + if err != nil { + s.logger.Error("Failed to count tender approvals by submission mode", map[string]interface{}{ + "error": err.Error(), + "submission_mode": submissionMode, + }) + return tenderApprovals, 0, nil + } + + return tenderApprovals, total, nil +} diff --git a/internal/tender_approval/validation.go b/internal/tender_approval/validation.go new file mode 100644 index 0000000..f7fb7be --- /dev/null +++ b/internal/tender_approval/validation.go @@ -0,0 +1,176 @@ +package tender_approval + +import ( + "regexp" + "strings" +) + +// Validation functions for tender approval domain + +// IsValidTenderID validates if the tender ID format is correct +func IsValidTenderID(tenderID string) bool { + if tenderID == "" { + return false + } + + // Tender ID should be a non-empty string + // You can add more specific validation rules here based on your tender ID format + return len(tenderID) > 0 && len(tenderID) <= 100 +} + +// IsValidCompanyID validates if the company ID format is correct +func IsValidCompanyID(companyID string) bool { + if companyID == "" { + return false + } + + // Company ID should be a non-empty string + // You can add more specific validation rules here based on your company ID format + return len(companyID) > 0 && len(companyID) <= 100 +} + +// IsValidSubmissionMode validates if the submission mode is valid +func IsValidSubmissionMode(mode string) bool { + validModes := []string{ + string(SubmissionModeSelfApply), + string(SubmissionModePartnership), + } + + for _, validMode := range validModes { + if strings.EqualFold(mode, validMode) { + return true + } + } + + return false +} + +// IsValidStatus validates if the approval status is valid +func IsValidStatus(status string) bool { + validStatuses := []string{ + string(ApprovalStatusApproved), + string(ApprovalStatusRejected), + } + + for _, validStatus := range validStatuses { + if strings.EqualFold(status, validStatus) { + return true + } + } + + return false +} + +// IsValidNotes validates if the notes field is valid +func IsValidNotes(notes *string) bool { + if notes == nil { + return true // Notes are optional + } + + // Notes should be between 0 and 1000 characters + return len(*notes) <= 1000 +} + +// IsValidReason validates if the rejection reason is valid +func IsValidReason(reason string) bool { + // Reason should be between 10 and 500 characters + if len(reason) < 10 || len(reason) > 500 { + return false + } + + // Reason should not contain only whitespace + if strings.TrimSpace(reason) == "" { + return false + } + + return true +} + +// IsValidTimestamp validates if the timestamp is within reasonable range +func IsValidTimestamp(timestamp int64) bool { + // Timestamp should be positive and not too far in the future + // You can adjust these limits based on your requirements + if timestamp <= 0 { + return false + } + + // Check if timestamp is not too far in the future (e.g., 10 years from now) + maxFutureTimestamp := int64(10 * 365 * 24 * 60 * 60) // 10 years in seconds + return timestamp <= maxFutureTimestamp +} + +// IsValidLimit validates if the limit parameter is valid +func IsValidLimit(limit int) bool { + return limit > 0 && limit <= 100 +} + +// IsValidOffset validates if the offset parameter is valid +func IsValidOffset(offset int) bool { + return offset >= 0 +} + +// IsValidSortBy validates if the sort by parameter is valid +func IsValidSortBy(sortBy string) bool { + validSortFields := []string{ + "created_at", + "updated_at", + "status", + "submission_mode", + } + + for _, validField := range validSortFields { + if strings.EqualFold(sortBy, validField) { + return true + } + } + + return false +} + +// IsValidSortOrder validates if the sort order parameter is valid +func IsValidSortOrder(sortOrder string) bool { + validOrders := []string{"asc", "desc"} + + for _, validOrder := range validOrders { + if strings.EqualFold(sortOrder, validOrder) { + return true + } + } + + return false +} + +// SanitizeString removes potentially dangerous characters from input strings +func SanitizeString(input string) string { + // Remove HTML tags + htmlTagRegex := regexp.MustCompile(`<[^>]*>`) + sanitized := htmlTagRegex.ReplaceAllString(input, "") + + // Remove script tags + scriptRegex := regexp.MustCompile(`(?i)]*>.*?`) + sanitized = scriptRegex.ReplaceAllString(sanitized, "") + + // Trim whitespace + sanitized = strings.TrimSpace(sanitized) + + return sanitized +} + +// ValidateSearchQuery validates if the search query is valid +func ValidateSearchQuery(query string) bool { + if query == "" { + return true // Empty query is valid + } + + // Query should not be too long + if len(query) > 200 { + return false + } + + // Query should not contain only special characters + if regexp.MustCompile(`^[^a-zA-Z0-9\s]+$`).MatchString(query) { + return false + } + + return true +}