package tender_approval import ( "context" "errors" "time" "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) GetTenderApprovalByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error) GetTenderApprovalsByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error) GetTenderApprovalsByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus) ([]*TenderApproval, error) GetTenderApprovalsByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error) UpdateTenderApproval(ctx context.Context, id string, form *UpdateTenderApprovalForm) (*TenderApproval, error) DeleteTenderApproval(ctx context.Context, id string) error // Tender approval listing and search ListTenderApprovals(ctx context.Context, form *ListTenderApprovalsForm) (*TenderApprovalListResponse, 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) GetTenderApprovalsBySubmissionMode(ctx context.Context, submissionMode SubmissionMode, limit, offset int) ([]*TenderApproval, int64, error) } // tenderApprovalService implements the Service interface type tenderApprovalService struct { repository Repository logger logger.Logger } // NewTenderApprovalService creates a new tender approval service func NewTenderApprovalService(repository Repository, logger logger.Logger) Service { return &tenderApprovalService{ repository: repository, 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 } // 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 } // 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 } // 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 } // 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 }