Add Tender Approval Management Functionality and API Enhancements
- Introduced a new tender approval management system, including the creation of tender approval entities, services, and repositories. - Implemented CRUD operations for tender approvals, allowing companies to approve or reject tenders. - Developed administrative and public API endpoints for listing, retrieving, and updating tender approvals with comprehensive filtering options. - Enhanced Swagger documentation to accurately reflect the new endpoints, including detailed descriptions, parameters, and response formats. - Updated existing routes to include tender approval management, ensuring adherence to Clean Architecture principles and maintaining a clear separation of concerns in the handler layer.
This commit is contained in:
@@ -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")
|
||||
}
|
||||
Reference in New Issue
Block a user