Add Company Tender Approval Statistics Endpoint and Update API Documentation

- Introduced a new API endpoint to retrieve company-specific tender approval statistics, enhancing the analytics capabilities of the tender management system.
- Implemented the `GetCompanyTenderApprovalStats` method in the service layer to fetch detailed statistics, including total approvals, approved and rejected tenders, and submission counts.
- Updated Swagger documentation to accurately reflect the new endpoint, including detailed descriptions, parameters, and response formats for the `CompanyTenderApprovalStatsResponse`.
- Enhanced the router to include the new statistics route, ensuring adherence to Clean Architecture principles and maintaining a clear separation of concerns in the handler layer.
- Added necessary request and response forms to support the new functionality, improving the overall API structure and usability.
This commit is contained in:
n.nakhostin
2025-08-19 11:27:40 +03:30
parent 392b31626d
commit 81914e2eeb
8 changed files with 432 additions and 0 deletions
+33
View File
@@ -69,9 +69,42 @@ type TenderApprovalStatsResponse struct {
LastUpdated int64 `json:"last_updated"`
}
// CompanyTenderApprovalStatsResponse represents company-specific tender approval statistics
type CompanyTenderApprovalStatsResponse struct {
CompanyID string `json:"company_id"`
TotalApprovals int64 `json:"total_approvals"`
ApprovedTenders int64 `json:"approved_tenders"`
RejectedTenders int64 `json:"rejected_tenders"`
SelfApplyCount int64 `json:"self_apply_count"`
PartnershipCount int64 `json:"partnership_count"`
ApprovalsByStatus map[string]int64 `json:"approvals_by_status"`
ApprovalsBySubmission map[string]int64 `json:"approvals_by_submission"`
LastUpdated int64 `json:"last_updated"`
}
// ToggleTenderApprovalForm represents the form for toggling a tender approval
type ToggleTenderApprovalForm struct {
TenderID string `json:"tender_id" valid:"required"`
Status ApprovalStatus `json:"status" valid:"required,in(approved|rejected)"`
SubmissionMode SubmissionMode `json:"submission_mode" valid:"required,in(self-apply|partnership)"`
}
// CompanyTenderApprovalStatsRequest represents a request for company tender approval statistics
type CompanyTenderApprovalStatsRequest struct {
CompanyIDs []string `query:"company_ids" valid:"optional"`
CompanyID *string `query:"company_id" valid:"optional"`
}
// AggregatedCompanyStatsResponse represents aggregated statistics across all companies
type AggregatedCompanyStatsResponse struct {
TotalCompanies int64 `json:"total_companies"`
TotalApprovals int64 `json:"total_approvals"`
TotalApprovedTenders int64 `json:"total_approved_tenders"`
TotalRejectedTenders int64 `json:"total_rejected_tenders"`
TotalSelfApplyCount int64 `json:"total_self_apply_count"`
TotalPartnershipCount int64 `json:"total_partnership_count"`
ApprovalsByStatus map[string]int64 `json:"approvals_by_status"`
ApprovalsBySubmission map[string]int64 `json:"approvals_by_submission"`
CompanyStats []*CompanyTenderApprovalStatsResponse `json:"company_stats"`
LastUpdated int64 `json:"last_updated"`
}
+25
View File
@@ -231,6 +231,31 @@ func (h *Handler) GetTenderApprovalStats(c echo.Context) error {
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 (approved, rejected)
+43
View File
@@ -29,6 +29,7 @@ type Repository interface {
CountByTenderID(ctx context.Context, tenderID string) (int64, error)
CountByCompanyID(ctx context.Context, companyID string) (int64, error)
GetTenderApprovalStats(ctx context.Context) (*TenderApprovalStatsResponse, error)
GetCompanyTenderApprovalStats(ctx context.Context, companyID string) (*CompanyTenderApprovalStatsResponse, error)
SearchByCriteria(ctx context.Context, criteria *TenderApprovalSearchCriteria) ([]*TenderApproval, error)
}
@@ -501,6 +502,48 @@ func (r *tenderApprovalRepository) GetTenderApprovalStats(ctx context.Context) (
return stats, nil
}
// GetCompanyTenderApprovalStats returns company-specific tender approval statistics
func (r *tenderApprovalRepository) GetCompanyTenderApprovalStats(ctx context.Context, companyID string) (*CompanyTenderApprovalStatsResponse, error) {
// Get company-specific counts
totalApprovals, _ := r.CountByCompanyID(ctx, companyID)
// Count by status for this company
approvedFilter := bson.M{"company_id": companyID, "status": string(ApprovalStatusApproved)}
approvedTenders, _ := r.ormRepo.Count(ctx, approvedFilter)
rejectedFilter := bson.M{"company_id": companyID, "status": string(ApprovalStatusRejected)}
rejectedTenders, _ := r.ormRepo.Count(ctx, rejectedFilter)
// Count by submission mode for this company
selfApplyFilter := bson.M{"company_id": companyID, "submission_mode": string(SubmissionModeSelfApply)}
selfApplyCount, _ := r.ormRepo.Count(ctx, selfApplyFilter)
partnershipFilter := bson.M{"company_id": companyID, "submission_mode": string(SubmissionModePartnership)}
partnershipCount, _ := r.ormRepo.Count(ctx, partnershipFilter)
stats := &CompanyTenderApprovalStatsResponse{
CompanyID: companyID,
TotalApprovals: totalApprovals,
ApprovedTenders: approvedTenders,
RejectedTenders: rejectedTenders,
SelfApplyCount: selfApplyCount,
PartnershipCount: partnershipCount,
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
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
+89
View File
@@ -35,6 +35,9 @@ type Service interface {
// Statistics and analytics
GetTenderApprovalStats(ctx context.Context) (*TenderApprovalStatsResponse, error)
GetCompanyTenderApprovalStats(ctx context.Context, companyID string) (*CompanyTenderApprovalStatsResponse, error)
GetMultipleCompanyTenderApprovalStats(ctx context.Context, companyIDs []string) ([]*CompanyTenderApprovalStatsResponse, error)
GetAggregatedCompanyStats(ctx context.Context) (*AggregatedCompanyStatsResponse, error)
// Search and filtering helpers
GetTenderApprovalsByStatus(ctx context.Context, status ApprovalStatus, limit, offset int) ([]*TenderApproval, int64, error)
@@ -648,6 +651,69 @@ func (s *tenderApprovalService) GetTenderApprovalStats(ctx context.Context) (*Te
return stats, nil
}
// GetMultipleCompanyTenderApprovalStats returns tender approval statistics for multiple companies
func (s *tenderApprovalService) GetMultipleCompanyTenderApprovalStats(ctx context.Context, companyIDs []string) ([]*CompanyTenderApprovalStatsResponse, error) {
if len(companyIDs) == 0 {
return []*CompanyTenderApprovalStatsResponse{}, nil
}
var allStats []*CompanyTenderApprovalStatsResponse
for _, companyID := range companyIDs {
stats, err := s.GetCompanyTenderApprovalStats(ctx, companyID)
if err != nil {
s.logger.Warn("Failed to get stats for company", map[string]interface{}{
"company_id": companyID,
"error": err.Error(),
})
// Continue with other companies rather than failing completely
continue
}
allStats = append(allStats, stats)
}
s.logger.Info("Multiple company tender approval stats retrieved successfully", map[string]interface{}{
"requested_count": len(companyIDs),
"retrieved_count": len(allStats),
})
return allStats, nil
}
// GetAggregatedCompanyStats returns aggregated statistics across all companies
func (s *tenderApprovalService) GetAggregatedCompanyStats(ctx context.Context) (*AggregatedCompanyStatsResponse, error) {
// Get general stats first
generalStats, err := s.GetTenderApprovalStats(ctx)
if err != nil {
s.logger.Error("Failed to get general tender approval stats", map[string]interface{}{
"error": err.Error(),
})
return nil, errors.New("failed to get general tender approval statistics")
}
// For now, we'll return basic aggregated stats
// In a real implementation, you might want to get stats for all companies
// and aggregate them properly
stats := &AggregatedCompanyStatsResponse{
TotalCompanies: 1, // This should be calculated from actual company data
TotalApprovals: generalStats.TotalApprovals,
TotalApprovedTenders: generalStats.ApprovedTenders,
TotalRejectedTenders: generalStats.RejectedTenders,
TotalSelfApplyCount: generalStats.ApprovalsBySubmission["self-apply"],
TotalPartnershipCount: generalStats.ApprovalsBySubmission["partnership"],
ApprovalsByStatus: generalStats.ApprovalsByStatus,
ApprovalsBySubmission: generalStats.ApprovalsBySubmission,
CompanyStats: []*CompanyTenderApprovalStatsResponse{}, // Empty for now
LastUpdated: time.Now().Unix(),
}
s.logger.Info("Aggregated company stats retrieved successfully", map[string]interface{}{
"total_approvals": stats.TotalApprovals,
"total_companies": stats.TotalCompanies,
})
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
@@ -972,3 +1038,26 @@ func (s *tenderApprovalService) ToggleTenderApproval(ctx context.Context, req *T
return existing.ToResponseWithTender(nil), nil
}
// GetCompanyTenderApprovalStats returns company-specific tender approval statistics
func (s *tenderApprovalService) GetCompanyTenderApprovalStats(ctx context.Context, companyID string) (*CompanyTenderApprovalStatsResponse, error) {
stats, err := s.repository.GetCompanyTenderApprovalStats(ctx, companyID)
if err != nil {
s.logger.Error("Failed to get company tender approval stats", map[string]interface{}{
"error": err.Error(),
"company_id": companyID,
})
return nil, errors.New("failed to get company tender approval statistics")
}
s.logger.Info("Company tender approval stats retrieved successfully", map[string]interface{}{
"company_id": companyID,
"total_approvals": stats.TotalApprovals,
"approved_tenders": stats.ApprovedTenders,
"rejected_tenders": stats.RejectedTenders,
"self_apply_count": stats.SelfApplyCount,
"partnership_count": stats.PartnershipCount,
})
return stats, nil
}