Files
tm_back/internal/tender_approval/repository.go
T
n.nakhostin 5d721705b7 Update MongoDB Driver and Configuration for Company Entities
- Updated the MongoDB driver from v1.17.4 to v2.3.0, ensuring compatibility with the latest features and improvements.
- Refactored company-related entity and repository files to utilize the new driver, replacing `primitive.ObjectID` with `bson.ObjectID` for ID handling.
- Adjusted the configuration in `.drone.yml` to include the `main` branch in the trigger settings, enhancing CI/CD pipeline flexibility.
2025-09-09 18:53:39 +03:30

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 NewTenderApprovalRepository(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")
}