From adaae4c4bc521aef94fc981a81dda901ecbbee51 Mon Sep 17 00:00:00 2001 From: Mazyar Date: Wed, 8 Jul 2026 01:17:17 +0330 Subject: [PATCH] Refactor tender approval service to optimize tender detail retrieval - 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. --- internal/tender_approval/service.go | 151 ++------------------- internal/tender_approval/tender_details.go | 110 +++++++++++++++ 2 files changed, 119 insertions(+), 142 deletions(-) create mode 100644 internal/tender_approval/tender_details.go diff --git a/internal/tender_approval/service.go b/internal/tender_approval/service.go index 3f196da..f0e89cf 100644 --- a/internal/tender_approval/service.go +++ b/internal/tender_approval/service.go @@ -292,40 +292,12 @@ func (s *tenderApprovalService) GetTenderApprovalsByCompanyIDWithTender(ctx cont return nil, err } - // Convert to response with tender details + tenders := s.loadTendersByIDs(ctx, uniqueTenderIDs(tenderApprovals)) + responses := &TenderApprovalWithTenderListResponse{ - Approvals: []*TenderApprovalWithTenderResponse{}, + Approvals: mapApprovalsWithTender(tenderApprovals, tenders, listTenderDetailsFromResponse), Metadata: &response.Meta{}, } - for _, approval := range tenderApprovals { - // Get tender details - tenderResponse, err := s.tenderService.GetByID(ctx, approval.TenderID, "") - if err != nil { - s.logger.Warn("Failed to get tender details", map[string]interface{}{ - "tender_id": approval.TenderID, - "error": err.Error(), - }) - // Continue without tender details rather than failing completely - responses.Approvals = append(responses.Approvals, approval.ToResponseWithTender(nil)) - continue - } - - // Convert tender response to tender details - tenderDetails := &TenderDetails{ - ID: tenderResponse.ID, - Title: tenderResponse.Title, - Description: tenderResponse.Description, - EstimatedValue: tenderResponse.EstimatedValue, - Currency: tenderResponse.Currency, - TenderDeadline: tenderResponse.TenderDeadline, - SubmissionDeadline: tenderResponse.SubmissionDeadline, - CountryCode: tenderResponse.CountryCode, - BuyerOrganization: &OrganizationResponse{Name: tenderResponse.BuyerOrganization.Name}, - Status: string(tenderResponse.Status), - } - - responses.Approvals = append(responses.Approvals, approval.ToResponseWithTender(tenderDetails)) - } responses.Metadata = &response.Meta{ Total: int(total), @@ -780,43 +752,8 @@ func (s *tenderApprovalService) GetTenderApprovalsByStatusWithTender(ctx context return nil, 0, err } - // Convert to response with tender details - var responses []*TenderApprovalWithTenderResponse - for _, approval := range tenderApprovals { - // Get tender details - tenderResponse, err := s.tenderService.GetByID(ctx, approval.TenderID, "") - if err != nil { - s.logger.Warn("Failed to get tender details", map[string]interface{}{ - "tender_id": approval.TenderID, - "error": err.Error(), - }) - // Continue without tender details rather than failing completely - responses = append(responses, approval.ToResponseWithTender(nil)) - continue - } - - // 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.ProcurementTypeCode, - 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), - } - - responses = append(responses, approval.ToResponseWithTender(tenderDetails)) - } + tenders := s.loadTendersByIDs(ctx, uniqueTenderIDs(tenderApprovals)) + responses := mapApprovalsWithTender(tenderApprovals, tenders, fullTenderDetailsFromResponse) return responses, total, nil } @@ -828,43 +765,8 @@ func (s *tenderApprovalService) GetTenderApprovalsBySubmissionModeWithTender(ctx return nil, 0, err } - // Convert to response with tender details - var responses []*TenderApprovalWithTenderResponse - for _, approval := range tenderApprovals { - // Get tender details - tenderResponse, err := s.tenderService.GetByID(ctx, approval.TenderID, "") - if err != nil { - s.logger.Warn("Failed to get tender details", map[string]interface{}{ - "tender_id": approval.TenderID, - "error": err.Error(), - }) - // Continue without tender details rather than failing completely - responses = append(responses, approval.ToResponseWithTender(nil)) - continue - } - - // 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), - } - - responses = append(responses, approval.ToResponseWithTender(tenderDetails)) - } + tenders := s.loadTendersByIDs(ctx, uniqueTenderIDs(tenderApprovals)) + responses := mapApprovalsWithTender(tenderApprovals, tenders, fullTenderDetailsFromResponse) return responses, total, nil } @@ -926,43 +828,8 @@ func (s *tenderApprovalService) ListTenderApprovalsWithTender(ctx context.Contex total = int64(len(tenderApprovals)) // Use actual count if total count fails } - // Convert to response with tender details - var responses []*TenderApprovalWithTenderResponse - for _, approval := range tenderApprovals { - // Get tender details - tenderResponse, err := s.tenderService.GetByID(ctx, approval.TenderID, "") - if err != nil { - s.logger.Warn("Failed to get tender details", map[string]interface{}{ - "tender_id": approval.TenderID, - "error": err.Error(), - }) - // Continue without tender details rather than failing completely - responses = append(responses, approval.ToResponseWithTender(nil)) - continue - } - - // 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), - } - - responses = append(responses, approval.ToResponseWithTender(tenderDetails)) - } + tenders := s.loadTendersByIDs(ctx, uniqueTenderIDs(tenderApprovals)) + responses := mapApprovalsWithTender(tenderApprovals, tenders, fullTenderDetailsFromResponse) // Calculate pagination metadata pages := int(total) / limit diff --git a/internal/tender_approval/tender_details.go b/internal/tender_approval/tender_details.go new file mode 100644 index 0000000..2dcbf00 --- /dev/null +++ b/internal/tender_approval/tender_details.go @@ -0,0 +1,110 @@ +package tender_approval + +import ( + "context" + + "tm/internal/tender" +) + +func fullTenderDetailsFromResponse(resp *tender.TenderResponse) *TenderDetails { + if resp == nil { + return nil + } + + details := &TenderDetails{ + ID: resp.ID, + NoticePublicationID: resp.NoticePublicationID, + PublicationDate: resp.PublicationDate, + Title: resp.Title, + Description: resp.Description, + ProcurementTypeCode: resp.ProcurementTypeCode, + ProcedureCode: resp.ProcedureCode, + EstimatedValue: resp.EstimatedValue, + Currency: resp.Currency, + Duration: resp.Duration, + DurationUnit: resp.DurationUnit, + TenderDeadline: resp.TenderDeadline, + SubmissionDeadline: resp.SubmissionDeadline, + CountryCode: resp.CountryCode, + Status: string(resp.Status), + } + if resp.BuyerOrganization != nil && resp.BuyerOrganization.Name != "" { + details.BuyerOrganization = &OrganizationResponse{Name: resp.BuyerOrganization.Name} + } + return details +} + +func listTenderDetailsFromResponse(resp *tender.TenderResponse) *TenderDetails { + if resp == nil { + return nil + } + + details := &TenderDetails{ + ID: resp.ID, + Title: resp.Title, + Description: resp.Description, + EstimatedValue: resp.EstimatedValue, + Currency: resp.Currency, + TenderDeadline: resp.TenderDeadline, + SubmissionDeadline: resp.SubmissionDeadline, + CountryCode: resp.CountryCode, + Status: string(resp.Status), + } + if resp.BuyerOrganization != nil && resp.BuyerOrganization.Name != "" { + details.BuyerOrganization = &OrganizationResponse{Name: resp.BuyerOrganization.Name} + } + return details +} + +func uniqueTenderIDs(approvals []*TenderApproval) []string { + if len(approvals) == 0 { + return nil + } + + seen := make(map[string]struct{}, len(approvals)) + ids := make([]string, 0, len(approvals)) + for _, approval := range approvals { + if approval == nil || approval.TenderID == "" { + continue + } + if _, ok := seen[approval.TenderID]; ok { + continue + } + seen[approval.TenderID] = struct{}{} + ids = append(ids, approval.TenderID) + } + return ids +} + +func (s *tenderApprovalService) loadTendersByIDs(ctx context.Context, tenderIDs []string) map[string]*tender.TenderResponse { + if len(tenderIDs) == 0 { + return map[string]*tender.TenderResponse{} + } + + tenders, err := s.tenderService.GetByIDs(ctx, tenderIDs) + if err != nil { + s.logger.Warn("Failed to load tenders by IDs", map[string]interface{}{ + "count": len(tenderIDs), + "error": err.Error(), + }) + return map[string]*tender.TenderResponse{} + } + + return tenders +} + +func mapApprovalsWithTender( + approvals []*TenderApproval, + tenders map[string]*tender.TenderResponse, + toDetails func(*tender.TenderResponse) *TenderDetails, +) []*TenderApprovalWithTenderResponse { + responses := make([]*TenderApprovalWithTenderResponse, 0, len(approvals)) + for _, approval := range approvals { + var details *TenderDetails + if tenderResp, ok := tenders[approval.TenderID]; ok { + details = toDetails(tenderResp) + } + responses = append(responses, approval.ToResponseWithTender(details)) + } + return responses +}