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 ApproveTender(ctx context.Context, id string, form *ApproveTenderForm) (*TenderApproval, error) RejectTender(ctx context.Context, id string, form *RejectTenderForm) (*TenderApproval, error) // Statistics and analytics GetTenderApprovalStats(ctx context.Context) (*TenderApprovalStatsResponse, 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 } // ApproveTender approves a tender func (s *tenderApprovalService) ApproveTender(ctx context.Context, id string, form *ApproveTenderForm) (*TenderApproval, error) { // Get tender approval to check if exists tenderApproval, err := s.repository.GetByID(ctx, id) if err != nil { return nil, errors.New("tender approval not found") } // Approve the tender tenderApproval.Approve(form.Notes) // Update in database err = s.repository.Update(ctx, tenderApproval) if err != nil { s.logger.Error("Failed to approve tender", map[string]interface{}{ "error": err.Error(), "tender_approval_id": id, }) return nil, errors.New("failed to approve tender") } s.logger.Info("Tender approved successfully", map[string]interface{}{ "tender_approval_id": tenderApproval.ID, "tender_id": tenderApproval.TenderID, "company_id": tenderApproval.CompanyID, }) return tenderApproval, nil } // RejectTender rejects a tender func (s *tenderApprovalService) RejectTender(ctx context.Context, id string, form *RejectTenderForm) (*TenderApproval, error) { // Get tender approval to check if exists tenderApproval, err := s.repository.GetByID(ctx, id) if err != nil { return nil, errors.New("tender approval not found") } // Reject the tender tenderApproval.Reject(form.Reason) // Update in database err = s.repository.Update(ctx, tenderApproval) if err != nil { s.logger.Error("Failed to reject tender", map[string]interface{}{ "error": err.Error(), "tender_approval_id": id, }) return nil, errors.New("failed to reject tender") } s.logger.Info("Tender rejected successfully", map[string]interface{}{ "tender_approval_id": tenderApproval.ID, "tender_id": tenderApproval.TenderID, "company_id": tenderApproval.CompanyID, "reason": form.Reason, }) return tenderApproval, 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 } // 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 }