Files
tm_back/internal/tender_approval/service.go
T
n.nakhostin b26d77b4cf Enhance Feedback and Tender Approval Services with Toggle Functionality
- Updated the ToggleFeedback method in the feedback service to change the FeedbackType to "unlike" or "undislike" based on the current state, improving user interaction with feedback.
- Modified the ToggleTenderApproval method in the tender approval service to update the Status to "unapproved" or "unrejected" depending on the current approval state, streamlining tender approval management.
- These enhancements improve the overall functionality and user experience of the tender management system.
2025-08-20 12:38:54 +03:30

1070 lines
40 KiB
Go

package tender_approval
import (
"context"
"errors"
"time"
"tm/internal/tender"
"tm/pkg/logger"
)
// 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) ([]*TenderApproval, error)
GetTenderApprovalsByCompanyIDWithTender(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus) ([]*TenderApprovalWithTenderResponse, 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.TenderService
logger logger.Logger
}
// NewTenderApprovalService creates a new tender approval service
func NewTenderApprovalService(repository Repository, tenderService tender.TenderService, 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.GetTenderByID(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.GetTenderByID(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) ([]*TenderApproval, error) {
tenderApprovals, err := s.repository.GetByCompanyID(ctx, companyID, submissionMode, status)
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
}
s.logger.Info("Tender approvals retrieved successfully", map[string]interface{}{
"company_id": companyID,
"count": len(tenderApprovals),
})
return tenderApprovals, 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) ([]*TenderApprovalWithTenderResponse, error) {
tenderApprovals, err := s.repository.GetByCompanyID(ctx, companyID, submissionMode, status)
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
}
// Convert to response with tender details
var responses []*TenderApprovalWithTenderResponse
for _, approval := range tenderApprovals {
// Get tender details
tenderResponse, err := s.tenderService.GetTenderByID(ctx, approval.TenderID)
if err != nil {
s.logger.Warn("Failed to get tender details", map[string]interface{}{
"tender_id": approval.TenderID,
"error": err.Error(),
})
// Continue without tender details rather than failing completely
responses = append(responses, approval.ToResponseWithTender(nil))
continue
}
// Convert tender response to tender details
tenderDetails := &TenderDetails{
ID: tenderResponse.ID,
Title: tenderResponse.Title,
Description: tenderResponse.Description,
EstimatedValue: tenderResponse.EstimatedValue,
Currency: tenderResponse.Currency,
TenderDeadline: tenderResponse.TenderDeadline,
SubmissionDeadline: tenderResponse.SubmissionDeadline,
CountryCode: tenderResponse.CountryCode,
BuyerOrganization: &OrganizationResponse{Name: tenderResponse.BuyerOrganization.Name},
Status: string(tenderResponse.Status),
}
responses = append(responses, approval.ToResponseWithTender(tenderDetails))
}
s.logger.Info("Tender approvals with tender details retrieved successfully", map[string]interface{}{
"company_id": companyID,
"count": len(responses),
})
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.GetTenderByID(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,
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
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
}
// Convert to response with tender details
var responses []*TenderApprovalWithTenderResponse
for _, approval := range tenderApprovals {
// Get tender details
tenderResponse, err := s.tenderService.GetTenderByID(ctx, approval.TenderID)
if err != nil {
s.logger.Warn("Failed to get tender details", map[string]interface{}{
"tender_id": approval.TenderID,
"error": err.Error(),
})
// Continue without tender details rather than failing completely
responses = append(responses, approval.ToResponseWithTender(nil))
continue
}
// 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.ProcurementTypeCode,
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),
}
responses = append(responses, approval.ToResponseWithTender(tenderDetails))
}
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
}
// Convert to response with tender details
var responses []*TenderApprovalWithTenderResponse
for _, approval := range tenderApprovals {
// Get tender details
tenderResponse, err := s.tenderService.GetTenderByID(ctx, approval.TenderID)
if err != nil {
s.logger.Warn("Failed to get tender details", map[string]interface{}{
"tender_id": approval.TenderID,
"error": err.Error(),
})
// Continue without tender details rather than failing completely
responses = append(responses, approval.ToResponseWithTender(nil))
continue
}
// 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),
}
responses = append(responses, approval.ToResponseWithTender(tenderDetails))
}
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
}
// Convert to response with tender details
var responses []*TenderApprovalWithTenderResponse
for _, approval := range tenderApprovals {
// Get tender details
tenderResponse, err := s.tenderService.GetTenderByID(ctx, approval.TenderID)
if err != nil {
s.logger.Warn("Failed to get tender details", map[string]interface{}{
"tender_id": approval.TenderID,
"error": err.Error(),
})
// Continue without tender details rather than failing completely
responses = append(responses, approval.ToResponseWithTender(nil))
continue
}
// 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),
}
responses = append(responses, approval.ToResponseWithTender(tenderDetails))
}
// Calculate pagination metadata
pages := int(total) / limit
if int(total)%limit > 0 {
pages++
}
page := (offset / limit) + 1
meta := &TenderApprovalListResponseWithTender{
TenderApprovals: responses,
Total: 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) {
existing, err := s.repository.GetByTenderAndCompany(ctx, req.TenderID, companyID)
if err != nil {
approval := &TenderApproval{
TenderID: req.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": req.TenderID,
"company_id": companyID,
"status": req.Status,
"submission_mode": req.SubmissionMode,
"error": err.Error(),
})
return nil, err
}
return approval.ToResponseWithTender(nil), nil
}
if existing.Status == req.Status && existing.SubmissionMode == req.SubmissionMode {
err = s.repository.Delete(ctx, existing.GetID())
if err != nil {
s.logger.Error("Failed to update tender approval", map[string]interface{}{
"error": err.Error(),
})
return nil, err
}
if req.Status == ApprovalStatusApproved {
existing.Status = "unapproved"
} else {
existing.Status = "unrejected"
}
return existing.ToResponseWithTender(nil), nil
} else if existing.Status == req.Status && existing.SubmissionMode != req.SubmissionMode {
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 existing.ToResponseWithTender(nil), nil
}
if existing.Status == ApprovalStatusApproved {
existing.Reject()
} else {
existing.Approve()
}
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 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
}