adaae4c4bc
continuous-integration/drone/push Build is passing
- Replaced the direct fetching of tender details within the `GetTenderApprovalsByCompanyIDWithTender`, `GetTenderApprovalsByStatusWithTender`, and `GetTenderApprovalsBySubmissionModeWithTender` methods with a new approach that loads tender details in bulk using `loadTendersByIDs`. - Introduced helper functions `fullTenderDetailsFromResponse` and `listTenderDetailsFromResponse` to streamline the mapping of tender responses to `TenderDetails`. - Added a new file `tender_details.go` to encapsulate tender detail mapping logic, improving code organization and readability. This update enhances the performance of the tender approval service by reducing redundant calls to fetch tender details, leading to more efficient data handling.
948 lines
34 KiB
Go
948 lines
34 KiB
Go
package tender_approval
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"math"
|
|
"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) {
|
|
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 {
|
|
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 == ApprovalStatusSubmitted {
|
|
existing.Status = "unsubmitted"
|
|
} 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 == ApprovalStatusSubmitted {
|
|
existing.Reject()
|
|
} else {
|
|
existing.Submit()
|
|
}
|
|
|
|
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,
|
|
"submitted_tenders": stats.SubmittedTenders,
|
|
"rejected_tenders": stats.RejectedTenders,
|
|
"self_apply_count": stats.SelfApplyCount,
|
|
"partnership_count": stats.PartnershipCount,
|
|
})
|
|
|
|
return stats, nil
|
|
}
|