5d721705b7
- 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.
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 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")
|
|
}
|