94d0fbef38
- Introduced a new tender approval management system, including the creation of tender approval entities, services, and repositories. - Implemented CRUD operations for tender approvals, allowing companies to approve or reject tenders. - Developed administrative and public API endpoints for listing, retrieving, and updating tender approvals with comprehensive filtering options. - Enhanced Swagger documentation to accurately reflect the new endpoints, including detailed descriptions, parameters, and response formats. - Updated existing routes to include tender approval management, ensuring adherence to Clean Architecture principles and maintaining a clear separation of concerns in the handler layer.
528 lines
18 KiB
Go
528 lines
18 KiB
Go
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
|
|
}
|