Files
tm_back/internal/tender_approval/service.go
T
Mazyar 81a94b4879
continuous-integration/drone/push Build is passing
Enhance tender and company context handling with new features and error management
- 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.
2026-07-08 02:55:55 +03:30

979 lines
36 KiB
Go

package tender_approval
import (
"context"
"errors"
"math"
"strings"
"time"
"tm/internal/tender"
"tm/pkg/logger"
"tm/pkg/response"
)
// 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)
GetTenderApprovalByIDWithTender(ctx context.Context, id string) (*TenderApprovalWithTenderResponse, error)
GetTenderApprovalByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error)
GetTenderApprovalByTenderAndCompanyWithTender(ctx context.Context, tenderID, companyID string) (*TenderApprovalWithTenderResponse, error)
GetTenderApprovalsByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error)
GetTenderApprovalsByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, from *int64, to *int64, limit int, offset int) ([]*TenderApproval, int64, error)
GetTenderApprovalsByCompanyIDWithTender(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, from *int64, to *int64, limit int, offset int) (*TenderApprovalWithTenderListResponse, error)
GetTenderApprovalsByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error)
UpdateTenderApproval(ctx context.Context, id string, form *UpdateTenderApprovalForm) (*TenderApproval, error)
UpdateTenderApprovalWithTender(ctx context.Context, id string, form *UpdateTenderApprovalForm) (*TenderApprovalWithTenderResponse, error)
DeleteTenderApproval(ctx context.Context, id string) error
// Tender approval listing and search
ListTenderApprovals(ctx context.Context, form *ListTenderApprovalsForm) (*TenderApprovalListResponse, error)
ListTenderApprovalsWithTender(ctx context.Context, form *ListTenderApprovalsForm) (*TenderApprovalListResponseWithTender, error)
SearchTenderApprovals(ctx context.Context, form *TenderApprovalSearchForm) (*TenderApprovalListResponse, error)
SearchTenderApprovalsByCriteria(ctx context.Context, criteria *TenderApprovalSearchCriteria) ([]*TenderApproval, error)
// Business operations
ToggleTenderApproval(ctx context.Context, req *ToggleTenderApprovalForm, companyID string) (*TenderApprovalWithTenderResponse, error)
// 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)
GetTenderApprovalsByStatusWithTender(ctx context.Context, status ApprovalStatus, limit, offset int) ([]*TenderApprovalWithTenderResponse, int64, error)
GetTenderApprovalsBySubmissionMode(ctx context.Context, submissionMode SubmissionMode, limit, offset int) ([]*TenderApproval, int64, error)
GetTenderApprovalsBySubmissionModeWithTender(ctx context.Context, submissionMode SubmissionMode, limit, offset int) ([]*TenderApprovalWithTenderResponse, int64, error)
}
// tenderApprovalService implements the Service interface
type tenderApprovalService struct {
repository Repository
tenderService tender.Service
logger logger.Logger
}
// NewService creates a new tender approval service
func NewService(repository Repository, tenderService tender.Service, logger logger.Logger) Service {
return &tenderApprovalService{
repository: repository,
tenderService: tenderService,
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
}
// GetTenderApprovalByIDWithTender retrieves a tender approval by ID with tender details
func (s *tenderApprovalService) GetTenderApprovalByIDWithTender(ctx context.Context, id string) (*TenderApprovalWithTenderResponse, 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
}
// Get tender details
tenderResponse, err := s.tenderService.GetByID(ctx, tenderApproval.TenderID, "")
if err != nil {
s.logger.Warn("Failed to get tender details", map[string]interface{}{
"tender_id": tenderApproval.TenderID,
"error": err.Error(),
})
// Return approval without tender details rather than failing completely
return tenderApproval.ToResponseWithTender(nil), nil
}
// Convert tender response to tender details
tenderDetails := &TenderDetails{
ID: tenderResponse.ID,
NoticePublicationID: tenderResponse.NoticePublicationID,
PublicationDate: tenderResponse.PublicationDate,
Title: tenderResponse.Title,
Description: tenderResponse.Description,
ProcurementTypeCode: tenderResponse.ProcurementTypeCode,
ProcedureCode: tenderResponse.ProcedureCode,
EstimatedValue: tenderResponse.EstimatedValue,
Currency: tenderResponse.Currency,
Duration: tenderResponse.Duration,
DurationUnit: tenderResponse.DurationUnit,
TenderDeadline: tenderResponse.TenderDeadline,
SubmissionDeadline: tenderResponse.SubmissionDeadline,
CountryCode: tenderResponse.CountryCode,
BuyerOrganization: &OrganizationResponse{Name: tenderResponse.BuyerOrganization.Name},
Status: string(tenderResponse.Status),
}
s.logger.Info("Tender approval with tender details retrieved successfully", map[string]interface{}{
"tender_approval_id": id,
})
return tenderApproval.ToResponseWithTender(tenderDetails), 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
}
// GetTenderApprovalByTenderAndCompanyWithTender retrieves a tender approval by tender ID and company ID with tender details
func (s *tenderApprovalService) GetTenderApprovalByTenderAndCompanyWithTender(ctx context.Context, tenderID, companyID string) (*TenderApprovalWithTenderResponse, 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
}
// Get tender details
tenderResponse, err := s.tenderService.GetByID(ctx, tenderApproval.TenderID, "")
if err != nil {
s.logger.Warn("Failed to get tender details", map[string]interface{}{
"tender_id": tenderApproval.TenderID,
})
// Return approval without tender details rather than failing completely
return tenderApproval.ToResponseWithTender(nil), nil
}
// Convert tender response to tender details
tenderDetails := &TenderDetails{
ID: tenderResponse.ID,
NoticePublicationID: tenderResponse.NoticePublicationID,
PublicationDate: tenderResponse.PublicationDate,
Title: tenderResponse.Title,
Description: tenderResponse.Description,
ProcurementTypeCode: tenderResponse.ProcurementTypeCode,
ProcedureCode: tenderResponse.ProcedureCode,
EstimatedValue: tenderResponse.EstimatedValue,
Currency: tenderResponse.Currency,
Duration: tenderResponse.Duration,
DurationUnit: tenderResponse.DurationUnit,
TenderDeadline: tenderResponse.TenderDeadline,
SubmissionDeadline: tenderResponse.SubmissionDeadline,
CountryCode: tenderResponse.CountryCode,
BuyerOrganization: &OrganizationResponse{Name: tenderResponse.BuyerOrganization.Name},
Status: string(tenderResponse.Status),
}
s.logger.Info("Tender approval with tender details retrieved successfully", map[string]interface{}{
"tender_approval_id": tenderApproval.ID,
"tender_id": tenderApproval.TenderID,
"company_id": tenderApproval.CompanyID,
})
return tenderApproval.ToResponseWithTender(tenderDetails), 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, from *int64, to *int64, limit int, offset int) ([]*TenderApproval, int64, error) {
tenderApprovals, total, err := s.repository.GetByCompanyID(ctx, companyID, submissionMode, status, limit, offset, from, to)
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, 0, err
}
s.logger.Info("Tender approvals retrieved successfully", map[string]interface{}{
"company_id": companyID,
"count": len(tenderApprovals),
})
return tenderApprovals, total, nil
}
// GetTenderApprovalsByCompanyIDWithTender retrieves all tender approvals for a specific company with tender details
func (s *tenderApprovalService) GetTenderApprovalsByCompanyIDWithTender(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, from *int64, to *int64, limit, offset int) (*TenderApprovalWithTenderListResponse, error) {
tenderApprovals, total, err := s.repository.GetByCompanyID(ctx, companyID, submissionMode, status, limit, offset, from, to)
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
}
tenders := s.loadTendersByIDs(ctx, uniqueTenderIDs(tenderApprovals))
responses := &TenderApprovalWithTenderListResponse{
Approvals: mapApprovalsWithTender(tenderApprovals, tenders, listTenderDetailsFromResponse),
Metadata: &response.Meta{},
}
responses.Metadata = &response.Meta{
Total: int(total),
Limit: limit,
Offset: offset,
Pages: int(math.Ceil(float64(total) / float64(limit))),
Page: int(math.Ceil(float64(offset) / float64(limit))),
}
s.logger.Info("Tender approvals with tender details retrieved successfully", map[string]interface{}{
"company_id": companyID,
"count": len(responses.Approvals),
})
return responses, 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
}
// UpdateTenderApprovalWithTender updates a tender approval and returns it with tender details
func (s *tenderApprovalService) UpdateTenderApprovalWithTender(ctx context.Context, id string, form *UpdateTenderApprovalForm) (*TenderApprovalWithTenderResponse, 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")
}
// Get tender details
tenderResponse, err := s.tenderService.GetByID(ctx, tenderApproval.TenderID, "")
if err != nil {
s.logger.Warn("Failed to get tender details", map[string]interface{}{
"tender_id": tenderApproval.TenderID,
"error": err.Error(),
})
// Return approval without tender details rather than failing completely
return tenderApproval.ToResponseWithTender(nil), nil
}
// Convert tender response to tender details
tenderDetails := &TenderDetails{
ID: tenderResponse.ID,
NoticePublicationID: tenderResponse.NoticePublicationID,
PublicationDate: tenderResponse.PublicationDate,
Title: tenderResponse.Title,
Description: tenderResponse.Description,
ProcurementTypeCode: tenderResponse.ProcurementTypeCode,
ProcedureCode: tenderResponse.ProcedureCode,
EstimatedValue: tenderResponse.EstimatedValue,
Currency: tenderResponse.Currency,
Duration: tenderResponse.Duration,
DurationUnit: tenderResponse.DurationUnit,
TenderDeadline: tenderResponse.TenderDeadline,
SubmissionDeadline: tenderResponse.SubmissionDeadline,
CountryCode: tenderResponse.CountryCode,
BuyerOrganization: &OrganizationResponse{Name: tenderResponse.BuyerOrganization.Name},
Status: string(tenderResponse.Status),
}
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.ToResponseWithTender(tenderDetails), 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
}
// 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
}
// 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,
TotalSubmittedTenders: generalStats.SubmittedTenders,
TotalRejectedTenders: generalStats.RejectedTenders,
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
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
}
// GetTenderApprovalsByStatusWithTender retrieves tender approvals by status with tender details
func (s *tenderApprovalService) GetTenderApprovalsByStatusWithTender(ctx context.Context, status ApprovalStatus, limit, offset int) ([]*TenderApprovalWithTenderResponse, int64, error) {
tenderApprovals, total, err := s.GetTenderApprovalsByStatus(ctx, status, limit, offset)
if err != nil {
return nil, 0, err
}
tenders := s.loadTendersByIDs(ctx, uniqueTenderIDs(tenderApprovals))
responses := mapApprovalsWithTender(tenderApprovals, tenders, fullTenderDetailsFromResponse)
return responses, total, nil
}
// GetTenderApprovalsBySubmissionModeWithTender retrieves tender approvals by submission mode with tender details
func (s *tenderApprovalService) GetTenderApprovalsBySubmissionModeWithTender(ctx context.Context, submissionMode SubmissionMode, limit, offset int) ([]*TenderApprovalWithTenderResponse, int64, error) {
tenderApprovals, total, err := s.GetTenderApprovalsBySubmissionMode(ctx, submissionMode, limit, offset)
if err != nil {
return nil, 0, err
}
tenders := s.loadTendersByIDs(ctx, uniqueTenderIDs(tenderApprovals))
responses := mapApprovalsWithTender(tenderApprovals, tenders, fullTenderDetailsFromResponse)
return responses, total, nil
}
// ListTenderApprovalsWithTender lists tender approvals with search and filters, including tender details
func (s *tenderApprovalService) ListTenderApprovalsWithTender(ctx context.Context, form *ListTenderApprovalsForm) (*TenderApprovalListResponseWithTender, 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 {
for _, s := range form.Status {
status = append(status, ApprovalStatus(s))
}
}
// Convert submission mode strings to SubmissionMode
var submissionMode []SubmissionMode
if form.SubmissionMode != nil {
for _, sm := range form.SubmissionMode {
submissionMode = append(submissionMode, SubmissionMode(sm))
}
}
// Search 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 search tender approvals", map[string]interface{}{
"error": err.Error(),
})
return nil, errors.New("failed to search tender approvals")
}
// Get total count for pagination
total, err := s.repository.Count(ctx)
if err != nil {
s.logger.Error("Failed to count tender approvals", map[string]interface{}{
"error": err.Error(),
})
total = int64(len(tenderApprovals)) // Use actual count if total count fails
}
tenders := s.loadTendersByIDs(ctx, uniqueTenderIDs(tenderApprovals))
responses := mapApprovalsWithTender(tenderApprovals, tenders, fullTenderDetailsFromResponse)
// Calculate pagination metadata
pages := int(total) / limit
if int(total)%limit > 0 {
pages++
}
page := (offset / limit) + 1
meta := &TenderApprovalListResponseWithTender{
TenderApprovals: responses,
Metadata: &response.Meta{
Total: int(total),
Limit: limit,
Offset: offset,
Page: page,
Pages: pages,
},
}
return meta, nil
}
func (s *tenderApprovalService) ToggleTenderApproval(ctx context.Context, req *ToggleTenderApprovalForm, companyID string) (*TenderApprovalWithTenderResponse, error) {
companyID = strings.TrimSpace(companyID)
if companyID == "" {
return nil, ErrInvalidCompanyID
}
tenderID, err := s.tenderService.ResolveMongoID(ctx, req.TenderID)
if err != nil {
s.logger.Warn("Failed to resolve tender for approval toggle", map[string]interface{}{
"tender_ref": req.TenderID,
"company_id": companyID,
"error": err.Error(),
})
return nil, ErrTenderNotFound
}
existing, err := s.repository.GetByTenderAndCompany(ctx, tenderID, companyID)
if err != nil {
if !errors.Is(err, ErrTenderApprovalNotFound) {
s.logger.Error("Failed to load tender approval for toggle", map[string]interface{}{
"tender_id": tenderID,
"company_id": companyID,
"error": err.Error(),
})
return nil, err
}
approval := &TenderApproval{
TenderID: tenderID,
CompanyID: companyID,
SubmissionMode: req.SubmissionMode,
Status: req.Status,
}
err = s.repository.Create(ctx, approval)
if err != nil {
s.logger.Error("Failed to create new tender approval", map[string]interface{}{
"tender_id": tenderID,
"company_id": companyID,
"status": req.Status,
"submission_mode": req.SubmissionMode,
"error": err.Error(),
})
return nil, err
}
return s.toggleResponseWithTender(ctx, approval)
}
if existing.Status == req.Status {
err = s.repository.Delete(ctx, existing.GetID())
if err != nil {
s.logger.Error("Failed to delete tender approval during toggle", map[string]interface{}{
"error": err.Error(),
})
return nil, err
}
if req.Status == ApprovalStatusSubmitted {
existing.Status = "unsubmitted"
} else {
existing.Status = "unrejected"
}
return existing.ToResponseWithTender(nil), nil
}
if existing.Status == ApprovalStatusRejected && req.Status == ApprovalStatusSubmitted {
existing.Submit()
existing.SubmissionMode = req.SubmissionMode
} else if existing.Status == ApprovalStatusSubmitted && req.Status == ApprovalStatusRejected {
existing.Reject()
} else {
existing.Status = req.Status
existing.SubmissionMode = req.SubmissionMode
}
err = s.repository.Update(ctx, existing)
if err != nil {
s.logger.Error("Failed to update tender approval", map[string]interface{}{
"error": err.Error(),
})
return nil, err
}
return s.toggleResponseWithTender(ctx, existing)
}
func (s *tenderApprovalService) toggleResponseWithTender(ctx context.Context, approval *TenderApproval) (*TenderApprovalWithTenderResponse, error) {
if approval == nil {
return nil, ErrInvalidData
}
tenderResponse, err := s.tenderService.GetByID(ctx, approval.TenderID, "")
if err != nil {
return approval.ToResponseWithTender(nil), nil
}
return approval.ToResponseWithTender(listTenderDetailsFromResponse(tenderResponse)), 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,
"submitted_tenders": stats.SubmittedTenders,
"rejected_tenders": stats.RejectedTenders,
"self_apply_count": stats.SelfApplyCount,
"partnership_count": stats.PartnershipCount,
})
return stats, nil
}