package tender import ( "strconv" "strings" "tm/pkg/logger" "tm/pkg/response" "github.com/asaskevich/govalidator" "github.com/labstack/echo/v4" ) // TenderHandler handles tender-related HTTP requests type TenderHandler struct { service TenderService logger logger.Logger } // NewTenderHandler creates a new tender handler func NewTenderHandler(service TenderService, logger logger.Logger) *TenderHandler { return &TenderHandler{ service: service, logger: logger, } } // GetTender retrieves a tender by ID // @Summary Get tender by ID // @Description Retrieve a specific tender by its ID // @Tags Admin-Tenders // @Produce json // @Param id path string true "Tender ID" // @Success 200 {object} response.APIResponse{data=TenderResponse} // @Failure 404 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /admin/tenders/{id} [get] // @Security BearerAuth func (h *TenderHandler) GetTender(c echo.Context) error { id := c.Param("id") if id == "" { return response.BadRequest(c, "Tender ID is required", "ID parameter cannot be empty") } tender, err := h.service.GetTenderByID(c.Request().Context(), id) if err != nil { return response.NotFound(c, "Tender not found") } return response.Success(c, tender, "Tender retrieved successfully") } // UpdateTender updates an existing tender // @Summary Update tender // @Description Update an existing tender with the provided information // @Tags Admin-Tenders // @Accept json // @Produce json // @Param id path string true "Tender ID" // @Param tender body UpdateTenderRequest true "Updated tender information" // @Success 200 {object} response.APIResponse{data=TenderResponse} // @Failure 400 {object} response.APIResponse // @Failure 404 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /admin/tenders/{id} [put] // @Security BearerAuth func (h *TenderHandler) UpdateTender(c echo.Context) error { id := c.Param("id") if id == "" { return response.BadRequest(c, "Tender ID is required", "ID parameter cannot be empty") } var req UpdateTenderRequest if err := c.Bind(&req); err != nil { return response.ValidationError(c, "Invalid request format", err.Error()) } req.ID = id if _, err := govalidator.ValidateStruct(req); err != nil { return response.ValidationError(c, "Validation failed", err.Error()) } tender, err := h.service.UpdateTender(c.Request().Context(), req) if err != nil { if err.Error() == "tender not found" { return response.NotFound(c, "Tender not found") } return response.InternalServerError(c, "Failed to update tender") } return response.Success(c, tender.ToTenderResponse(), "Tender updated successfully") } // DeleteTender deletes a tender // @Summary Delete tender // @Description Delete a tender by its ID // @Tags Admin-Tenders // @Param id path string true "Tender ID" // @Success 200 {object} response.APIResponse // @Failure 404 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /admin/tenders/{id} [delete] // @Security BearerAuth func (h *TenderHandler) DeleteTender(c echo.Context) error { id := c.Param("id") if id == "" { return response.BadRequest(c, "Tender ID is required", "ID parameter cannot be empty") } err := h.service.DeleteTender(c.Request().Context(), id) if err != nil { if err.Error() == "tender not found" { return response.NotFound(c, "Tender not found") } return response.InternalServerError(c, "Failed to delete tender") } return response.Success(c, map[string]interface{}{"deleted": true}, "Tender deleted successfully") } // ListTenders retrieves tenders with pagination and filtering // @Summary List tenders // @Description Retrieve tenders with pagination, filtering, and search capabilities with optional company-based matching // @Tags Admin-Tenders // @Produce json // @Param limit query int false "Number of items per page (default: 20, max: 100)" // @Param offset query int false "Number of items to skip (default: 0)" // @Param company_id query string false "Company ID for match percentage calculation and sorting" // @Param query query string false "Search query for title and description" // @Param notice_type query string false "Filter by notice type code" // @Param procurement_type query string false "Filter by procurement type code" // @Param country_codes query []string false "Filter by country codes (comma-separated)" // @Param status query []string false "Filter by status (comma-separated)" // @Param source query []string false "Filter by source (comma-separated)" // @Param min_estimated_value query number false "Minimum estimated value" // @Param max_estimated_value query number false "Maximum estimated value" // @Param currency query string false "Filter by currency" // @Success 200 {object} response.APIResponse{data=map[string]interface{}} // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /admin/tenders [get] // @Security BearerAuth func (h *TenderHandler) ListTenders(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 } } // Build search criteria criteria := TenderSearchCriteria{ Query: c.QueryParam("query"), NoticeType: c.QueryParam("notice_type"), ProcurementType: c.QueryParam("procurement_type"), Currency: c.QueryParam("currency"), } // Parse array parameters if countryCodes := c.QueryParam("country_codes"); countryCodes != "" { criteria.CountryCodes = parseStringArray(countryCodes) } if statuses := c.QueryParam("status"); statuses != "" { statusStrings := parseStringArray(statuses) criteria.Status = make([]TenderStatus, len(statusStrings)) for i, s := range statusStrings { criteria.Status[i] = TenderStatus(s) } } if sources := c.QueryParam("source"); sources != "" { sourceStrings := parseStringArray(sources) criteria.Source = make([]TenderSource, len(sourceStrings)) for i, s := range sourceStrings { criteria.Source[i] = TenderSource(s) } } // Parse numeric parameters if minValue := c.QueryParam("min_estimated_value"); minValue != "" { if parsed, err := strconv.ParseFloat(minValue, 64); err == nil { criteria.MinEstimatedValue = &parsed } } if maxValue := c.QueryParam("max_estimated_value"); maxValue != "" { if parsed, err := strconv.ParseFloat(maxValue, 64); err == nil { criteria.MaxEstimatedValue = &parsed } } // Parse company_id parameter for matching var companyID *string if companyIDParam := c.QueryParam("company_id"); companyIDParam != "" { companyID = &companyIDParam } req := ListTendersRequest{ Criteria: criteria, Limit: limit, Offset: offset, CompanyID: companyID, } result, err := h.service.ListTenders(c.Request().Context(), req) if err != nil { return response.InternalServerError(c, "Failed to retrieve tenders") } return response.Success(c, result, "Tenders retrieved successfully") } // GetPublicTenders retrieves public tenders for mobile app // @Summary Get public tenders // @Description Retrieve active tenders for mobile application users with automatic company-based matching from customer profile // @Tags Tenders // @Produce json // @Param limit query int false "Number of items per page (default: 20, max: 50)" // @Param offset query int false "Number of items to skip (default: 0)" // @Param country_code query string false "Filter by country code" // @Param min_estimated_value query number false "Minimum estimated value" // @Param max_estimated_value query number false "Maximum estimated value" // @Success 200 {object} response.APIResponse{data=map[string]interface{}} // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Security BearerAuth // @Router /api/v1/tenders [get] func (h *TenderHandler) GetPublicTenders(c echo.Context) error { // Parse pagination parameters (more restrictive for public API) limit := 20 if parsed, err := strconv.Atoi(c.QueryParam("limit")); err == nil && parsed > 0 && parsed <= 50 { limit = parsed } offset := 0 if parsed, err := strconv.Atoi(c.QueryParam("offset")); err == nil && parsed >= 0 { offset = parsed } // Build search criteria for public API (only active tenders) criteria := TenderSearchCriteria{ Status: []TenderStatus{TenderStatusActive}, } if countryCode := c.QueryParam("country_code"); countryCode != "" { criteria.CountryCodes = []string{countryCode} } // Parse numeric parameters if parsed, err := strconv.ParseFloat(c.QueryParam("min_estimated_value"), 64); err == nil { criteria.MinEstimatedValue = &parsed } if parsed, err := strconv.ParseFloat(c.QueryParam("max_estimated_value"), 64); err == nil { criteria.MaxEstimatedValue = &parsed } // Get company ID from customer context for automatic matching var companyID *string if customerCompanyID, ok := c.Get("company_id").(string); ok { companyID = &customerCompanyID } req := ListTendersRequest{ Criteria: criteria, Limit: limit, Offset: offset, CompanyID: companyID, } result, err := h.service.ListTenders(c.Request().Context(), req) if err != nil { return response.InternalServerError(c, "Failed to retrieve tenders") } return response.Success(c, result, "Tenders retrieved successfully") } // GetPublicTenderDetails retrieves public tender details for mobile app // @Summary Get public tender details // @Description Retrieve detailed information about a specific tender for mobile application users // @Tags Tenders // @Produce json // @Param id path string true "Tender ID" // @Success 200 {object} response.APIResponse{data=map[string]interface{}} // @Failure 404 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Security BearerAuth // @Router /api/v1/tenders/{id} [get] func (h *TenderHandler) GetPublicTenderDetails(c echo.Context) error { id := c.Param("id") if id == "" { return response.BadRequest(c, "Tender ID is required", "ID parameter cannot be empty") } tender, err := h.service.GetTenderByID(c.Request().Context(), id) if err != nil { return response.NotFound(c, "Tender not found") } return response.Success(c, tender, "Tender details retrieved successfully") } // parseStringArray parses a comma-separated string into a string array func parseStringArray(s string) []string { if s == "" { return nil } result := make([]string, 0) for _, item := range strings.Split(s, ",") { if trimmed := strings.TrimSpace(item); trimmed != "" { result = append(result, trimmed) } } return result }