81a94b4879
continuous-integration/drone/push Build is passing
- Introduced `PickAssignedCompanyID` function to determine the appropriate company ID for company-scoped operations, improving company assignment logic. - Added `ResolveMongoID` method in the tender service to map MongoDB IDs and AI procedure references to canonical tender IDs, enhancing ID resolution. - Updated `ToggleTenderApproval` to handle company ID validation and improve error handling, ensuring robust processing of tender approvals. - Enhanced logging for error scenarios in the tender approval service, providing better insights into failures during operations. - Refactored the `GetByID` and `GetByTenderAndCompany` methods in the tender approval repository to utilize custom error types for improved clarity. This update significantly improves the handling of company and tender operations, enhancing error management and overall service reliability.
437 lines
18 KiB
Go
437 lines
18 KiB
Go
package tender_approval
|
|
|
|
import (
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
"tm/internal/customer"
|
|
"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) ToggleTenderApproval(c echo.Context) error {
|
|
form, err := response.Parse[ToggleTenderApprovalForm](c)
|
|
if err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
requestedCompanyID := strings.TrimSpace(c.QueryParam("company_id"))
|
|
if form.CompanyID != nil && strings.TrimSpace(*form.CompanyID) != "" {
|
|
requestedCompanyID = strings.TrimSpace(*form.CompanyID)
|
|
}
|
|
|
|
activeCompanyID, _ := c.Get("company_id").(string)
|
|
assignedCompanyIDs, _ := c.Get("company_ids").([]string)
|
|
companyID, err := customer.PickAssignedCompanyID(activeCompanyID, assignedCompanyIDs, requestedCompanyID)
|
|
if err != nil {
|
|
if errors.Is(err, customer.ErrCompanyNotAssigned) {
|
|
return response.Forbidden(c, "Company is not assigned to customer")
|
|
}
|
|
return response.BadRequest(c, "Company ID is required", "")
|
|
}
|
|
|
|
tenderApproval, err := h.service.ToggleTenderApproval(c.Request().Context(), form, companyID)
|
|
if err != nil {
|
|
if errors.Is(err, ErrTenderNotFound) {
|
|
return response.NotFound(c, "Tender not found")
|
|
}
|
|
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.Success(c, tenderApproval, "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")
|
|
|
|
// Get the approval with tender details
|
|
tenderApprovalWithTender, err := h.service.GetTenderApprovalByIDWithTender(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, tenderApprovalWithTender, "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 path 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 {
|
|
companyID := c.Get("company_id").(string)
|
|
|
|
if companyID == "" {
|
|
return response.Unauthorized(c, "Unauthorized")
|
|
}
|
|
|
|
tenderID := c.Param("tender_id")
|
|
if tenderID == "" {
|
|
return response.BadRequest(c, "Missing required parameters", "tender_id required")
|
|
}
|
|
|
|
tenderApprovalWithTender, err := h.service.GetTenderApprovalByTenderAndCompanyWithTender(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, tenderApprovalWithTender, "Tender approval retrieved 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 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 submission_mode query string false "Filter by submission mode Enums(self-apply, partnership)"
|
|
// @Param status query string false "Filter by status Enums(submitted, rejected). Defaults to submitted when omitted."
|
|
// @Param created_from query integer false "Filter by created date from (Unix timestamp)"
|
|
// @Param created_to query integer false "Filter by created date to (Unix timestamp)"
|
|
// @Success 200 {object} response.APIResponse{data=TenderApprovalWithTenderListResponse} "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 {
|
|
|
|
// Parse pagination parameters
|
|
limit := 20
|
|
if l := c.QueryParam("limit"); l != "" {
|
|
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 100 {
|
|
limit = parsed
|
|
}
|
|
}
|
|
|
|
offset := 0
|
|
if o := c.QueryParam("offset"); o != "" {
|
|
if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 {
|
|
offset = parsed
|
|
}
|
|
}
|
|
|
|
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 != ApprovalStatusSubmitted && statusVal != ApprovalStatusRejected {
|
|
return response.BadRequest(c, "Invalid status value", "Must be 'submitted' or 'rejected'")
|
|
}
|
|
status = &statusVal
|
|
}
|
|
|
|
var from *int64
|
|
if fromParam := c.QueryParam("created_from"); fromParam != "" {
|
|
if parsed, err := strconv.ParseInt(fromParam, 10, 64); err == nil {
|
|
from = &parsed
|
|
}
|
|
}
|
|
|
|
var to *int64
|
|
if toParam := c.QueryParam("created_to"); toParam != "" {
|
|
if parsed, err := strconv.ParseInt(toParam, 10, 64); err == nil {
|
|
to = &parsed
|
|
}
|
|
}
|
|
|
|
tenderApprovalsWithTender, err := h.service.GetTenderApprovalsByCompanyIDWithTender(c.Request().Context(), companyID, submissionMode, status, from, to, limit, offset)
|
|
if err != nil {
|
|
return response.InternalServerError(c, "Failed to get tender approvals")
|
|
}
|
|
|
|
return response.Success(c, tenderApprovalsWithTender, "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.ListTenderApprovalsWithTender(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")
|
|
}
|
|
|
|
// GetCompanyTenderApprovalStats returns company-specific tender approval statistics
|
|
// @Summary Get company tender approval statistics
|
|
// @Description Get comprehensive tender approval statistics for a specific company
|
|
// @Tags TenderApprovals
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} response.APIResponse{data=CompanyTenderApprovalStatsResponse} "Company tender approval statistics 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/stats [get]
|
|
func (h *Handler) GetCompanyTenderApprovalStats(c echo.Context) error {
|
|
companyID := c.Get("company_id").(string)
|
|
if companyID == "" {
|
|
return response.Unauthorized(c, "Unauthorized")
|
|
}
|
|
|
|
stats, err := h.service.GetCompanyTenderApprovalStats(c.Request().Context(), companyID)
|
|
if err != nil {
|
|
return response.InternalServerError(c, "Failed to get company tender approval statistics")
|
|
}
|
|
|
|
return response.Success(c, stats, "Company 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 (submitted, rejected)
|
|
// @Tags Admin-TenderApprovals
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param status path string true "Tender approval status" Enums(submitted, 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 != ApprovalStatusSubmitted && status != ApprovalStatusRejected {
|
|
return response.BadRequest(c, "Invalid tender approval status", "Must be submitted 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
|
|
}
|
|
}
|
|
|
|
tenderApprovalsWithTender, total, err := h.service.GetTenderApprovalsByStatusWithTender(c.Request().Context(), status, limit, offset)
|
|
if err != nil {
|
|
return response.InternalServerError(c, "Failed to retrieve tender approvals by status")
|
|
}
|
|
|
|
meta := &response.Meta{
|
|
Total: int(total),
|
|
Limit: limit,
|
|
Offset: offset,
|
|
}
|
|
|
|
return response.SuccessWithMeta(c, tenderApprovalsWithTender, 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
|
|
}
|
|
}
|
|
|
|
tenderApprovalsWithTender, total, err := h.service.GetTenderApprovalsBySubmissionModeWithTender(c.Request().Context(), submissionMode, limit, offset)
|
|
if err != nil {
|
|
return response.InternalServerError(c, "Failed to retrieve tender approvals by submission mode")
|
|
}
|
|
|
|
meta := &response.Meta{
|
|
Total: int(total),
|
|
Limit: limit,
|
|
Offset: offset,
|
|
}
|
|
|
|
return response.SuccessWithMeta(c, tenderApprovalsWithTender, 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 Admin-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")
|
|
|
|
// Get the approval with tender details
|
|
tenderApprovalWithTender, err := h.service.GetTenderApprovalByIDWithTender(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, tenderApprovalWithTender, "Tender approval retrieved successfully")
|
|
}
|