a342da193e
- Updated feedback repository and service methods to use a consistent naming convention, changing `NewTenderRepository` and similar methods to `NewRepository`. - Refactored feedback handler methods to improve clarity by renaming methods such as `ListFeedback` to `Search` and `GetFeedback` to `Get`. - Enhanced feedback response structures to include detailed company and tender information, improving the clarity of feedback data returned to API consumers. - Updated Swagger documentation to reflect changes in feedback response structures and endpoint paths, ensuring accurate representation of the API for consumers.
552 lines
18 KiB
Go
552 lines
18 KiB
Go
package tender_approval
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
"tm/pkg/logger"
|
|
"tm/pkg/mongo"
|
|
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
)
|
|
|
|
// Repository defines the interface for tender approval data operations
|
|
type Repository interface {
|
|
Create(ctx context.Context, tenderApproval *TenderApproval) error
|
|
GetByID(ctx context.Context, id string) (*TenderApproval, error)
|
|
GetByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error)
|
|
GetByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error)
|
|
GetByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, limit int, offset int, from *int64, to *int64) ([]*TenderApproval, int64, error)
|
|
GetByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error)
|
|
Update(ctx context.Context, tenderApproval *TenderApproval) error
|
|
Delete(ctx context.Context, id string) error
|
|
List(ctx context.Context, limit, offset int) ([]*TenderApproval, error)
|
|
Search(ctx context.Context, tenderID *string, companyID *string, status []ApprovalStatus, submissionMode []SubmissionMode, createdAtFrom *int64, createdAtTo *int64, limit, offset int, sortBy, sortOrder string) ([]*TenderApproval, error)
|
|
Count(ctx context.Context) (int64, error)
|
|
CountByStatus(ctx context.Context, status ApprovalStatus) (int64, error)
|
|
CountBySubmissionMode(ctx context.Context, submissionMode SubmissionMode) (int64, error)
|
|
CountByTenderID(ctx context.Context, tenderID string) (int64, error)
|
|
CountByCompanyID(ctx context.Context, companyID string) (int64, error)
|
|
GetTenderApprovalStats(ctx context.Context) (*TenderApprovalStatsResponse, error)
|
|
GetCompanyTenderApprovalStats(ctx context.Context, companyID string) (*CompanyTenderApprovalStatsResponse, error)
|
|
SearchByCriteria(ctx context.Context, criteria *TenderApprovalSearchCriteria) ([]*TenderApproval, error)
|
|
}
|
|
|
|
// tenderApprovalRepository implements the Repository interface using the MongoDB ORM
|
|
type tenderApprovalRepository struct {
|
|
ormRepo mongo.Repository[TenderApproval]
|
|
logger logger.Logger
|
|
}
|
|
|
|
// NewTenderApprovalRepository creates a new tender approval repository
|
|
func NewRepository(mongoManager *mongo.ConnectionManager, logger logger.Logger) Repository {
|
|
// Create indexes using the ORM's index management
|
|
indexes := []mongo.Index{
|
|
*mongo.CreateUniqueIndex("tender_company_idx", bson.D{{Key: "tender_id", Value: 1}, {Key: "company_id", Value: 1}}),
|
|
*mongo.NewIndex("tender_id_idx", bson.D{{Key: "tender_id", Value: 1}}),
|
|
*mongo.NewIndex("company_id_idx", bson.D{{Key: "company_id", Value: 1}}),
|
|
*mongo.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}),
|
|
*mongo.NewIndex("submission_mode_idx", bson.D{{Key: "submission_mode", Value: 1}}),
|
|
*mongo.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
|
|
*mongo.NewIndex("updated_at_idx", bson.D{{Key: "updated_at", Value: -1}}),
|
|
}
|
|
|
|
// Create indexes
|
|
err := mongoManager.CreateIndexes("tender_approvals", indexes)
|
|
if err != nil {
|
|
logger.Warn("Failed to create tender approval indexes", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
// Create ORM repository
|
|
ormRepo := mongo.NewRepository[TenderApproval](mongoManager.GetCollection("tender_approvals"), logger)
|
|
|
|
return &tenderApprovalRepository{
|
|
ormRepo: ormRepo,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Create creates a new tender approval
|
|
func (r *tenderApprovalRepository) Create(ctx context.Context, tenderApproval *TenderApproval) error {
|
|
// Set created/updated timestamps using Unix timestamps
|
|
now := time.Now().Unix()
|
|
tenderApproval.SetCreatedAt(now)
|
|
tenderApproval.SetUpdatedAt(now)
|
|
|
|
// Use ORM to create tender approval
|
|
err := r.ormRepo.Create(ctx, tenderApproval)
|
|
if err != nil {
|
|
r.logger.Error("Failed to create tender approval", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"tender_id": tenderApproval.TenderID,
|
|
"company_id": tenderApproval.CompanyID,
|
|
})
|
|
return err
|
|
}
|
|
|
|
r.logger.Info("Tender approval created successfully", map[string]interface{}{
|
|
"tender_approval_id": tenderApproval.ID,
|
|
"tender_id": tenderApproval.TenderID,
|
|
"company_id": tenderApproval.CompanyID,
|
|
"status": tenderApproval.Status,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetByID retrieves a tender approval by ID
|
|
func (r *tenderApprovalRepository) GetByID(ctx context.Context, id string) (*TenderApproval, error) {
|
|
tenderApproval, err := r.ormRepo.FindByID(ctx, id)
|
|
if err != nil {
|
|
if errors.Is(err, mongo.ErrDocumentNotFound) {
|
|
return nil, errors.New("tender approval not found")
|
|
}
|
|
r.logger.Error("Failed to get tender approval by ID", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"tender_approval_id": id,
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
return tenderApproval, nil
|
|
}
|
|
|
|
// GetByTenderAndCompany retrieves a tender approval by tender ID and company ID
|
|
func (r *tenderApprovalRepository) GetByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error) {
|
|
filter := bson.M{
|
|
"tender_id": tenderID,
|
|
"company_id": companyID,
|
|
}
|
|
|
|
tenderApproval, err := r.ormRepo.FindOne(ctx, filter)
|
|
if err != nil {
|
|
if errors.Is(err, mongo.ErrDocumentNotFound) {
|
|
return nil, errors.New("tender approval not found")
|
|
}
|
|
r.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
|
|
}
|
|
|
|
return tenderApproval, nil
|
|
}
|
|
|
|
// GetByTenderID retrieves all tender approvals for a specific tender
|
|
func (r *tenderApprovalRepository) GetByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error) {
|
|
filter := bson.M{"tender_id": tenderID}
|
|
|
|
result, err := r.ormRepo.FindAll(ctx, filter, mongo.NewPaginationBuilder().SortDesc("created_at").Build())
|
|
if err != nil {
|
|
r.logger.Error("Failed to get tender approvals by tender ID", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"tender_id": tenderID,
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
// Convert []TenderApproval to []*TenderApproval
|
|
tenderApprovals := make([]*TenderApproval, len(result.Items))
|
|
for i := range result.Items {
|
|
tenderApprovals[i] = &result.Items[i]
|
|
}
|
|
|
|
return tenderApprovals, nil
|
|
}
|
|
|
|
// GetByCompanyID retrieves all tender approvals for a specific company with optional filters
|
|
func (r *tenderApprovalRepository) GetByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus, limit int, offset int, from *int64, to *int64) ([]*TenderApproval, int64, error) {
|
|
filter := bson.M{"company_id": companyID}
|
|
|
|
pagination := mongo.Pagination{
|
|
Limit: limit,
|
|
Skip: offset,
|
|
SortField: "created_at",
|
|
SortOrder: -1,
|
|
}
|
|
|
|
// Add submission mode filter if provided
|
|
if submissionMode != nil {
|
|
filter["submission_mode"] = *submissionMode
|
|
}
|
|
|
|
// Add status filter if provided
|
|
if status != nil {
|
|
filter["status"] = *status
|
|
}
|
|
|
|
if from != nil {
|
|
filter["created_at"] = bson.M{"$gte": *from}
|
|
}
|
|
if to != nil {
|
|
filter["created_at"] = bson.M{"$lte": *to}
|
|
}
|
|
|
|
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
|
if err != nil {
|
|
r.logger.Error("Failed to get tender approvals by company ID", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"company_id": companyID,
|
|
})
|
|
return nil, 0, err
|
|
}
|
|
|
|
// Convert []TenderApproval to []*TenderApproval
|
|
tenderApprovals := make([]*TenderApproval, len(result.Items))
|
|
for i := range result.Items {
|
|
tenderApprovals[i] = &result.Items[i]
|
|
}
|
|
|
|
return tenderApprovals, result.TotalCount, nil
|
|
}
|
|
|
|
// GetByIDs retrieves multiple tender approvals by their IDs
|
|
func (r *tenderApprovalRepository) GetByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error) {
|
|
if len(ids) == 0 {
|
|
return []*TenderApproval{}, nil
|
|
}
|
|
|
|
// Convert string IDs to ObjectIDs for MongoDB query
|
|
objectIDs := make([]interface{}, len(ids))
|
|
for i, id := range ids {
|
|
objectID, err := bson.ObjectIDFromHex(id)
|
|
if err != nil {
|
|
r.logger.Warn("Invalid tender approval ID format", map[string]interface{}{
|
|
"id": id,
|
|
"error": err.Error(),
|
|
})
|
|
continue
|
|
}
|
|
objectIDs[i] = objectID
|
|
}
|
|
|
|
if len(objectIDs) == 0 {
|
|
return []*TenderApproval{}, nil
|
|
}
|
|
|
|
filter := bson.M{"_id": bson.M{"$in": objectIDs}}
|
|
result, err := r.ormRepo.FindAll(ctx, filter, mongo.NewPaginationBuilder().Limit(len(ids)).Build())
|
|
if err != nil {
|
|
r.logger.Error("Failed to get tender approvals by IDs", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"ids": ids,
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
// Convert []TenderApproval to []*TenderApproval
|
|
tenderApprovals := make([]*TenderApproval, len(result.Items))
|
|
for i := range result.Items {
|
|
tenderApprovals[i] = &result.Items[i]
|
|
}
|
|
|
|
r.logger.Info("Retrieved tender approvals by IDs", map[string]interface{}{
|
|
"requested_count": len(ids),
|
|
"found_count": len(tenderApprovals),
|
|
})
|
|
|
|
return tenderApprovals, nil
|
|
}
|
|
|
|
// Update updates a tender approval
|
|
func (r *tenderApprovalRepository) Update(ctx context.Context, tenderApproval *TenderApproval) error {
|
|
// Set updated timestamp using Unix timestamp
|
|
tenderApproval.SetUpdatedAt(time.Now().Unix())
|
|
|
|
// Use ORM to update tender approval
|
|
err := r.ormRepo.Update(ctx, tenderApproval)
|
|
if err != nil {
|
|
r.logger.Error("Failed to update tender approval", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"tender_approval_id": tenderApproval.ID,
|
|
})
|
|
return err
|
|
}
|
|
|
|
r.logger.Info("Tender approval updated successfully", map[string]interface{}{
|
|
"tender_approval_id": tenderApproval.ID,
|
|
"tender_id": tenderApproval.TenderID,
|
|
"company_id": tenderApproval.CompanyID,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// Delete deletes a tender approval
|
|
func (r *tenderApprovalRepository) Delete(ctx context.Context, id string) error {
|
|
// Get tender approval first
|
|
tenderApproval, err := r.GetByID(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Delete from database
|
|
err = r.ormRepo.Delete(ctx, id)
|
|
if err != nil {
|
|
r.logger.Error("Failed to delete tender approval", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"tender_approval_id": id,
|
|
})
|
|
return err
|
|
}
|
|
|
|
r.logger.Info("Tender approval deleted successfully", map[string]interface{}{
|
|
"tender_approval_id": id,
|
|
"tender_id": tenderApproval.TenderID,
|
|
"company_id": tenderApproval.CompanyID,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// List retrieves tender approvals with pagination
|
|
func (r *tenderApprovalRepository) List(ctx context.Context, limit, offset int) ([]*TenderApproval, error) {
|
|
// Build pagination
|
|
pagination := mongo.NewPaginationBuilder().
|
|
Limit(limit).
|
|
Skip(offset).
|
|
SortDesc("created_at").
|
|
Build()
|
|
|
|
// Use ORM to find tender approvals
|
|
result, err := r.ormRepo.FindAll(ctx, bson.M{}, pagination)
|
|
if err != nil {
|
|
r.logger.Error("Failed to list tender approvals", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"limit": limit,
|
|
"offset": offset,
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
// Convert []TenderApproval to []*TenderApproval
|
|
tenderApprovals := make([]*TenderApproval, len(result.Items))
|
|
for i := range result.Items {
|
|
tenderApprovals[i] = &result.Items[i]
|
|
}
|
|
|
|
return tenderApprovals, nil
|
|
}
|
|
|
|
// Search retrieves tender approvals with search and filters
|
|
func (r *tenderApprovalRepository) Search(ctx context.Context, tenderID *string, companyID *string, status []ApprovalStatus, submissionMode []SubmissionMode, createdAtFrom *int64, createdAtTo *int64, limit, offset int, sortBy, sortOrder string) ([]*TenderApproval, error) {
|
|
// Build filter
|
|
filter := bson.M{}
|
|
|
|
if tenderID != nil {
|
|
filter["tender_id"] = *tenderID
|
|
}
|
|
|
|
if companyID != nil {
|
|
filter["company_id"] = *companyID
|
|
}
|
|
|
|
if len(status) > 0 {
|
|
statusStrings := make([]string, len(status))
|
|
for i, s := range status {
|
|
statusStrings[i] = string(s)
|
|
}
|
|
filter["status"] = bson.M{"$in": statusStrings}
|
|
}
|
|
|
|
if len(submissionMode) > 0 {
|
|
modeStrings := make([]string, len(submissionMode))
|
|
for i, m := range submissionMode {
|
|
modeStrings[i] = string(m)
|
|
}
|
|
filter["submission_mode"] = bson.M{"$in": modeStrings}
|
|
}
|
|
|
|
// Date range filters
|
|
if createdAtFrom != nil || createdAtTo != nil {
|
|
dateFilter := bson.M{}
|
|
if createdAtFrom != nil {
|
|
dateFilter["$gte"] = *createdAtFrom
|
|
}
|
|
if createdAtTo != nil {
|
|
dateFilter["$lte"] = *createdAtTo
|
|
}
|
|
filter["created_at"] = dateFilter
|
|
}
|
|
|
|
// Build pagination
|
|
pagination := mongo.NewPaginationBuilder().
|
|
Limit(limit).
|
|
Skip(offset)
|
|
|
|
// Set sort
|
|
if sortBy != "" {
|
|
if sortOrder == "desc" {
|
|
pagination.SortDesc(sortBy)
|
|
} else {
|
|
pagination.SortAsc(sortBy)
|
|
}
|
|
} else {
|
|
pagination.SortDesc("created_at") // Default sort
|
|
}
|
|
|
|
// Use ORM to find tender approvals
|
|
result, err := r.ormRepo.FindAll(ctx, filter, pagination.Build())
|
|
if err != nil {
|
|
r.logger.Error("Failed to search tender approvals", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
// Convert []TenderApproval to []*TenderApproval
|
|
tenderApprovals := make([]*TenderApproval, len(result.Items))
|
|
for i := range result.Items {
|
|
tenderApprovals[i] = &result.Items[i]
|
|
}
|
|
|
|
return tenderApprovals, nil
|
|
}
|
|
|
|
// Count counts all tender approvals
|
|
func (r *tenderApprovalRepository) Count(ctx context.Context) (int64, error) {
|
|
count, err := r.ormRepo.Count(ctx, bson.M{})
|
|
if err != nil {
|
|
r.logger.Error("Failed to count tender approvals", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// CountByStatus counts tender approvals by status
|
|
func (r *tenderApprovalRepository) CountByStatus(ctx context.Context, status ApprovalStatus) (int64, error) {
|
|
filter := bson.M{"status": string(status)}
|
|
|
|
count, err := r.ormRepo.Count(ctx, filter)
|
|
if err != nil {
|
|
r.logger.Error("Failed to count tender approvals by status", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"status": status,
|
|
})
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// CountBySubmissionMode counts tender approvals by submission mode
|
|
func (r *tenderApprovalRepository) CountBySubmissionMode(ctx context.Context, submissionMode SubmissionMode) (int64, error) {
|
|
filter := bson.M{"submission_mode": string(submissionMode)}
|
|
|
|
count, err := r.ormRepo.Count(ctx, filter)
|
|
if err != nil {
|
|
r.logger.Error("Failed to count tender approvals by submission mode", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"submission_mode": submissionMode,
|
|
})
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// CountByTenderID counts tender approvals for a specific tender
|
|
func (r *tenderApprovalRepository) CountByTenderID(ctx context.Context, tenderID string) (int64, error) {
|
|
filter := bson.M{"tender_id": tenderID}
|
|
|
|
count, err := r.ormRepo.Count(ctx, filter)
|
|
if err != nil {
|
|
r.logger.Error("Failed to count tender approvals by tender ID", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"tender_id": tenderID,
|
|
})
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// CountByCompanyID counts tender approvals for a specific company
|
|
func (r *tenderApprovalRepository) CountByCompanyID(ctx context.Context, companyID string) (int64, error) {
|
|
filter := bson.M{"company_id": companyID}
|
|
|
|
count, err := r.ormRepo.Count(ctx, filter)
|
|
if err != nil {
|
|
r.logger.Error("Failed to count tender approvals by company ID", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"company_id": companyID,
|
|
})
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// GetTenderApprovalStats returns tender approval statistics
|
|
func (r *tenderApprovalRepository) GetTenderApprovalStats(ctx context.Context) (*TenderApprovalStatsResponse, error) {
|
|
// Get basic counts
|
|
totalApprovals, _ := r.Count(ctx)
|
|
submittedTenders, _ := r.CountByStatus(ctx, ApprovalStatusSubmitted)
|
|
rejectedTenders, _ := r.CountByStatus(ctx, ApprovalStatusRejected)
|
|
|
|
stats := &TenderApprovalStatsResponse{
|
|
TotalApprovals: totalApprovals,
|
|
SubmittedTenders: submittedTenders,
|
|
RejectedTenders: rejectedTenders,
|
|
LastUpdated: time.Now().Unix(),
|
|
}
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
// GetCompanyTenderApprovalStats returns company-specific tender approval statistics
|
|
func (r *tenderApprovalRepository) GetCompanyTenderApprovalStats(ctx context.Context, companyID string) (*CompanyTenderApprovalStatsResponse, error) {
|
|
// Get company-specific counts
|
|
totalApprovals, _ := r.CountByCompanyID(ctx, companyID)
|
|
|
|
// Count by status for this company
|
|
submittedFilter := bson.M{"company_id": companyID, "status": string(ApprovalStatusSubmitted)}
|
|
submittedTenders, _ := r.ormRepo.Count(ctx, submittedFilter)
|
|
|
|
rejectedFilter := bson.M{"company_id": companyID, "status": string(ApprovalStatusRejected)}
|
|
rejectedTenders, _ := r.ormRepo.Count(ctx, rejectedFilter)
|
|
|
|
// Count by submission mode for this company
|
|
selfApplyFilter := bson.M{"company_id": companyID, "status": string(ApprovalStatusSubmitted), "submission_mode": string(SubmissionModeSelfApply)}
|
|
selfApplyCount, _ := r.ormRepo.Count(ctx, selfApplyFilter)
|
|
|
|
partnershipFilter := bson.M{"company_id": companyID, "status": string(ApprovalStatusSubmitted), "submission_mode": string(SubmissionModePartnership)}
|
|
partnershipCount, _ := r.ormRepo.Count(ctx, partnershipFilter)
|
|
|
|
stats := &CompanyTenderApprovalStatsResponse{
|
|
CompanyID: companyID,
|
|
TotalApprovals: totalApprovals,
|
|
SubmittedTenders: submittedTenders,
|
|
RejectedTenders: rejectedTenders,
|
|
SelfApplyCount: selfApplyCount,
|
|
PartnershipCount: partnershipCount,
|
|
LastUpdated: time.Now().Unix(),
|
|
}
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
// SearchByCriteria searches tender approvals using search criteria
|
|
func (r *tenderApprovalRepository) SearchByCriteria(ctx context.Context, criteria *TenderApprovalSearchCriteria) ([]*TenderApproval, error) {
|
|
// Convert criteria to search parameters
|
|
var status []ApprovalStatus
|
|
if criteria.Status != nil {
|
|
status = criteria.Status
|
|
}
|
|
|
|
var submissionMode []SubmissionMode
|
|
if criteria.SubmissionMode != nil {
|
|
submissionMode = criteria.SubmissionMode
|
|
}
|
|
|
|
// Use the search method
|
|
return r.Search(ctx, criteria.TenderID, criteria.CompanyID, status, submissionMode, criteria.CreatedAtFrom, criteria.CreatedAtTo, criteria.Limit, criteria.Offset, "created_at", "desc")
|
|
}
|