diff --git a/cmd/web/main.go b/cmd/web/main.go index 7529e75..150353a 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -124,7 +124,7 @@ func main() { customerService := customer.NewCustomerService(customerRepository, logger, customerAuthService, companyService) tenderService := tender.NewTenderService(tenderRepository, companyService, logger) feedbackService := feedback.NewFeedbackService(feedbackRepo, logger) - tenderApprovalService := tender_approval.NewTenderApprovalService(tenderApprovalRepository, logger) + tenderApprovalService := tender_approval.NewTenderApprovalService(tenderApprovalRepository, tenderService, logger) logger.Info("Services initialized successfully", map[string]interface{}{ "services": []string{"customer", "user", "company", "tender", "feedback", "tender_approval"}, }) diff --git a/internal/tender_approval/entity.go b/internal/tender_approval/entity.go index 8fcfd3f..1f1ad27 100644 --- a/internal/tender_approval/entity.go +++ b/internal/tender_approval/entity.go @@ -52,6 +52,42 @@ type TenderApprovalResponse struct { CreatedAt int64 `json:"created_at"` } +// TenderApprovalWithTenderResponse represents the response data for tender approval with tender details +type TenderApprovalWithTenderResponse struct { + ID string `json:"id"` + TenderID string `json:"tender_id"` + CompanyID string `json:"company_id"` + SubmissionMode string `json:"submission_mode"` + Status string `json:"status"` + CreatedAt int64 `json:"created_at"` + Tender *TenderDetails `json:"tender,omitempty"` +} + +// TenderDetails represents essential tender information for the response +type ( + TenderDetails struct { + ID string `json:"id"` + NoticePublicationID string `json:"notice_publication_id"` + PublicationDate int64 `json:"publication_date"` + Title string `json:"title"` + Description string `json:"description"` + ProcurementTypeCode string `json:"procurement_type_code"` + ProcedureCode string `json:"procedure_code"` + EstimatedValue float64 `json:"estimated_value"` + Currency string `json:"currency"` + Duration string `json:"duration"` + DurationUnit string `json:"duration_unit"` + TenderDeadline int64 `json:"tender_deadline"` + SubmissionDeadline int64 `json:"submission_deadline"` + CountryCode string `json:"country_code"` + BuyerOrganization *OrganizationResponse `json:"buyer_organization"` + Status string `json:"status"` + } + OrganizationResponse struct { + Name string `json:"name"` + } +) + // TenderApprovalSearchCriteria represents search criteria for tender approvals type TenderApprovalSearchCriteria struct { TenderID *string `json:"tender_id,omitempty"` @@ -64,6 +100,16 @@ type TenderApprovalSearchCriteria struct { Offset int `json:"offset,omitempty"` } +// TenderApprovalListResponseWithTender represents a paginated list of tender approvals with tender details +type TenderApprovalListResponseWithTender struct { + TenderApprovals []*TenderApprovalWithTenderResponse `json:"tender_approvals"` + Total int64 `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` + Page int `json:"page"` + Pages int `json:"pages"` +} + // TenderApprovalStatistics represents statistics for tender approvals type TenderApprovalStatistics struct { TotalApprovals int64 `json:"total_approvals"` @@ -123,3 +169,16 @@ func (ta *TenderApproval) ToResponse() *TenderApprovalResponse { CreatedAt: ta.CreatedAt, } } + +// ToResponseWithTender converts TenderApproval to TenderApprovalWithTenderResponse +func (ta *TenderApproval) ToResponseWithTender(tenderDetails *TenderDetails) *TenderApprovalWithTenderResponse { + return &TenderApprovalWithTenderResponse{ + ID: ta.ID.Hex(), + TenderID: ta.TenderID, + CompanyID: ta.CompanyID, + SubmissionMode: string(ta.SubmissionMode), + Status: string(ta.Status), + CreatedAt: ta.CreatedAt, + Tender: tenderDetails, + } +} diff --git a/internal/tender_approval/handler.go b/internal/tender_approval/handler.go index edf8e68..2852b49 100644 --- a/internal/tender_approval/handler.go +++ b/internal/tender_approval/handler.go @@ -56,7 +56,13 @@ func (h *Handler) CreateTenderApproval(c echo.Context) error { return response.InternalServerError(c, "Failed to create tender approval") } - return response.Created(c, tenderApproval.ToResponse(), "Tender approval created successfully") + // Get the created approval with tender details + tenderApprovalWithTender, err := h.service.GetTenderApprovalByIDWithTender(c.Request().Context(), tenderApproval.GetID()) + if err != nil { + return response.InternalServerError(c, "Failed to get tender approval details") + } + + return response.Created(c, tenderApprovalWithTender, "Tender approval created successfully") } // UpdateTenderApproval updates a tender approval @@ -81,7 +87,7 @@ func (h *Handler) UpdateTenderApproval(c echo.Context) error { return response.ValidationError(c, "Invalid request data", err.Error()) } - tenderApproval, err := h.service.UpdateTenderApproval(c.Request().Context(), id, form) + _, err = h.service.UpdateTenderApproval(c.Request().Context(), id, form) if err != nil { if err.Error() == "tender approval not found" { return response.NotFound(c, "Tender approval not found") @@ -89,7 +95,13 @@ func (h *Handler) UpdateTenderApproval(c echo.Context) error { return response.InternalServerError(c, "Failed to update tender approval") } - return response.Success(c, tenderApproval.ToResponse(), "Tender approval updated successfully") + // Get the updated approval with tender details + tenderApprovalWithTender, err := h.service.GetTenderApprovalByIDWithTender(c.Request().Context(), id) + if err != nil { + return response.InternalServerError(c, "Failed to get tender approval details") + } + + return response.Success(c, tenderApprovalWithTender, "Tender approval updated successfully") } // GetTenderApproval retrieves a tender approval by ID @@ -108,7 +120,8 @@ func (h *Handler) UpdateTenderApproval(c echo.Context) error { func (h *Handler) GetPublicTenderApproval(c echo.Context) error { id := c.Param("id") - tenderApproval, err := h.service.GetTenderApprovalByID(c.Request().Context(), id) + // Get the approval with tender details + tenderApprovalWithTender, err := h.service.GetTenderApprovalByIDWithTender(c.Request().Context(), id) if err != nil { if err.Error() == "tender approval not found" { return response.NotFound(c, "Tender approval not found") @@ -116,7 +129,7 @@ func (h *Handler) GetPublicTenderApproval(c echo.Context) error { return response.InternalServerError(c, "Failed to get tender approval") } - return response.Success(c, tenderApproval.ToResponse(), "Tender approval retrieved successfully") + return response.Success(c, tenderApprovalWithTender, "Tender approval retrieved successfully") } // GetTenderApprovalByTenderAndCompany retrieves a tender approval by tender ID and company ID @@ -144,7 +157,7 @@ func (h *Handler) GetTenderApprovalByTenderAndCompany(c echo.Context) error { return response.BadRequest(c, "Missing required parameters", "tender_id and company_id are required") } - tenderApproval, err := h.service.GetTenderApprovalByTenderAndCompany(c.Request().Context(), tenderID, companyID) + tenderApprovalWithTender, err := h.service.GetTenderApprovalByTenderAndCompanyWithTender(c.Request().Context(), tenderID, companyID) if err != nil { if err.Error() == "tender approval not found" { return response.NotFound(c, "Tender approval not found") @@ -152,7 +165,7 @@ func (h *Handler) GetTenderApprovalByTenderAndCompany(c echo.Context) error { return response.InternalServerError(c, "Failed to get tender approval") } - return response.Success(c, tenderApproval.ToResponse(), "Tender approval retrieved successfully") + return response.Success(c, tenderApprovalWithTender, "Tender approval retrieved successfully") } // DeleteTenderApproval deletes a tender approval @@ -192,7 +205,7 @@ func (h *Handler) DeleteTenderApproval(c echo.Context) error { // @Produce json // @Param submission_mode query string false "Filter by submission mode Enums(self-apply, partnership)" // @Param status query string false "Filter by status Enums(approved, rejected)" -// @Success 200 {object} response.APIResponse{data=[]TenderApprovalResponse} "Tender approvals retrieved successfully" +// @Success 200 {object} response.APIResponse{data=[]TenderApprovalWithTenderResponse} "Tender approvals retrieved successfully" // @Failure 400 {object} response.APIResponse "Bad request - Invalid company ID" // @Failure 500 {object} response.APIResponse "Internal server error" // @Security BearerAuth @@ -227,17 +240,12 @@ func (h *Handler) PublicGetTenderApprovals(c echo.Context) error { status = &statusVal } - tenderApprovals, err := h.service.GetTenderApprovalsByCompanyID(c.Request().Context(), companyID, submissionMode, status) + tenderApprovalsWithTender, err := h.service.GetTenderApprovalsByCompanyIDWithTender(c.Request().Context(), companyID, submissionMode, status) if err != nil { return response.InternalServerError(c, "Failed to get tender approvals") } - var responses []*TenderApprovalResponse - for _, approval := range tenderApprovals { - responses = append(responses, approval.ToResponse()) - } - - return response.Success(c, responses, "Tender approvals retrieved successfully") + return response.Success(c, tenderApprovalsWithTender, "Tender approvals retrieved successfully") } // **************** Admin Routes **************** @@ -270,7 +278,7 @@ func (h *Handler) ListTenderApprovals(c echo.Context) error { return response.ValidationError(c, "Invalid request data", err.Error()) } - result, err := h.service.ListTenderApprovals(c.Request().Context(), form) + result, err := h.service.ListTenderApprovalsWithTender(c.Request().Context(), form) if err != nil { return response.InternalServerError(c, "Failed to list tender approvals") } @@ -334,23 +342,18 @@ func (h *Handler) GetTenderApprovalsByStatus(c echo.Context) error { } } - tenderApprovals, total, err := h.service.GetTenderApprovalsByStatus(c.Request().Context(), status, limit, offset) + tenderApprovalsWithTender, total, err := h.service.GetTenderApprovalsByStatusWithTender(c.Request().Context(), status, limit, offset) if err != nil { return response.InternalServerError(c, "Failed to retrieve tender approvals by status") } - var responses []*TenderApprovalResponse - for _, approval := range tenderApprovals { - responses = append(responses, approval.ToResponse()) - } - meta := &response.Meta{ Total: int(total), Limit: limit, Offset: offset, } - return response.SuccessWithMeta(c, responses, meta, "Tender approvals retrieved successfully") + return response.SuccessWithMeta(c, tenderApprovalsWithTender, meta, "Tender approvals retrieved successfully") } // GetTenderApprovalsBySubmissionMode retrieves tender approvals by submission mode @@ -390,23 +393,18 @@ func (h *Handler) GetTenderApprovalsBySubmissionMode(c echo.Context) error { } } - tenderApprovals, total, err := h.service.GetTenderApprovalsBySubmissionMode(c.Request().Context(), submissionMode, limit, offset) + tenderApprovalsWithTender, total, err := h.service.GetTenderApprovalsBySubmissionModeWithTender(c.Request().Context(), submissionMode, limit, offset) if err != nil { return response.InternalServerError(c, "Failed to retrieve tender approvals by submission mode") } - var responses []*TenderApprovalResponse - for _, approval := range tenderApprovals { - responses = append(responses, approval.ToResponse()) - } - meta := &response.Meta{ Total: int(total), Limit: limit, Offset: offset, } - return response.SuccessWithMeta(c, responses, meta, "Tender approvals retrieved successfully") + return response.SuccessWithMeta(c, tenderApprovalsWithTender, meta, "Tender approvals retrieved successfully") } // GetTenderApproval retrieves a tender approval by ID @@ -425,7 +423,8 @@ func (h *Handler) GetTenderApprovalsBySubmissionMode(c echo.Context) error { func (h *Handler) GetTenderApproval(c echo.Context) error { id := c.Param("id") - tenderApproval, err := h.service.GetTenderApprovalByID(c.Request().Context(), id) + // Get the approval with tender details + tenderApprovalWithTender, err := h.service.GetTenderApprovalByIDWithTender(c.Request().Context(), id) if err != nil { if err.Error() == "tender approval not found" { return response.NotFound(c, "Tender approval not found") @@ -433,5 +432,5 @@ func (h *Handler) GetTenderApproval(c echo.Context) error { return response.InternalServerError(c, "Failed to get tender approval") } - return response.Success(c, tenderApproval.ToResponse(), "Tender approval retrieved successfully") + return response.Success(c, tenderApprovalWithTender, "Tender approval retrieved successfully") } diff --git a/internal/tender_approval/service.go b/internal/tender_approval/service.go index 76ad470..2aac8c7 100644 --- a/internal/tender_approval/service.go +++ b/internal/tender_approval/service.go @@ -4,7 +4,7 @@ import ( "context" "errors" "time" - + "tm/internal/tender" "tm/pkg/logger" ) @@ -13,15 +13,20 @@ 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) + GetTenderApprovalByIDWithTender(ctx context.Context, id string) (*TenderApprovalWithTenderResponse, error) GetTenderApprovalByTenderAndCompany(ctx context.Context, tenderID, companyID string) (*TenderApproval, error) + GetTenderApprovalByTenderAndCompanyWithTender(ctx context.Context, tenderID, companyID string) (*TenderApprovalWithTenderResponse, error) GetTenderApprovalsByTenderID(ctx context.Context, tenderID string) ([]*TenderApproval, error) GetTenderApprovalsByCompanyID(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus) ([]*TenderApproval, error) + GetTenderApprovalsByCompanyIDWithTender(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus) ([]*TenderApprovalWithTenderResponse, error) GetTenderApprovalsByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error) UpdateTenderApproval(ctx context.Context, id string, form *UpdateTenderApprovalForm) (*TenderApproval, error) + UpdateTenderApprovalWithTender(ctx context.Context, id string, form *UpdateTenderApprovalForm) (*TenderApprovalWithTenderResponse, error) DeleteTenderApproval(ctx context.Context, id string) error // Tender approval listing and search ListTenderApprovals(ctx context.Context, form *ListTenderApprovalsForm) (*TenderApprovalListResponse, error) + ListTenderApprovalsWithTender(ctx context.Context, form *ListTenderApprovalsForm) (*TenderApprovalListResponseWithTender, error) SearchTenderApprovals(ctx context.Context, form *TenderApprovalSearchForm) (*TenderApprovalListResponse, error) SearchTenderApprovalsByCriteria(ctx context.Context, criteria *TenderApprovalSearchCriteria) ([]*TenderApproval, error) @@ -34,20 +39,24 @@ type Service interface { // Search and filtering helpers GetTenderApprovalsByStatus(ctx context.Context, status ApprovalStatus, limit, offset int) ([]*TenderApproval, int64, error) + GetTenderApprovalsByStatusWithTender(ctx context.Context, status ApprovalStatus, limit, offset int) ([]*TenderApprovalWithTenderResponse, int64, error) GetTenderApprovalsBySubmissionMode(ctx context.Context, submissionMode SubmissionMode, limit, offset int) ([]*TenderApproval, int64, error) + GetTenderApprovalsBySubmissionModeWithTender(ctx context.Context, submissionMode SubmissionMode, limit, offset int) ([]*TenderApprovalWithTenderResponse, int64, error) } // tenderApprovalService implements the Service interface type tenderApprovalService struct { - repository Repository - logger logger.Logger + repository Repository + tenderService tender.TenderService + logger logger.Logger } // NewTenderApprovalService creates a new tender approval service -func NewTenderApprovalService(repository Repository, logger logger.Logger) Service { +func NewTenderApprovalService(repository Repository, tenderService tender.TenderService, logger logger.Logger) Service { return &tenderApprovalService{ - repository: repository, - logger: logger, + repository: repository, + tenderService: tenderService, + logger: logger, } } @@ -109,6 +118,55 @@ func (s *tenderApprovalService) GetTenderApprovalByID(ctx context.Context, id st return tenderApproval, nil } +// GetTenderApprovalByIDWithTender retrieves a tender approval by ID with tender details +func (s *tenderApprovalService) GetTenderApprovalByIDWithTender(ctx context.Context, id string) (*TenderApprovalWithTenderResponse, 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 + } + + // Get tender details + tenderResponse, err := s.tenderService.GetTenderByID(ctx, tenderApproval.TenderID) + if err != nil { + s.logger.Warn("Failed to get tender details", map[string]interface{}{ + "tender_id": tenderApproval.TenderID, + "error": err.Error(), + }) + // Return approval without tender details rather than failing completely + return tenderApproval.ToResponseWithTender(nil), nil + } + + // 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), + } + + s.logger.Info("Tender approval with tender details retrieved successfully", map[string]interface{}{ + "tender_approval_id": id, + }) + + return tenderApproval.ToResponseWithTender(tenderDetails), 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) @@ -130,6 +188,57 @@ func (s *tenderApprovalService) GetTenderApprovalByTenderAndCompany(ctx context. return tenderApproval, nil } +// GetTenderApprovalByTenderAndCompanyWithTender retrieves a tender approval by tender ID and company ID with tender details +func (s *tenderApprovalService) GetTenderApprovalByTenderAndCompanyWithTender(ctx context.Context, tenderID, companyID string) (*TenderApprovalWithTenderResponse, 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 + } + + // Get tender details + tenderResponse, err := s.tenderService.GetTenderByID(ctx, tenderApproval.TenderID) + if err != nil { + s.logger.Warn("Failed to get tender details", map[string]interface{}{ + "tender_id": tenderApproval.TenderID, + }) + // Return approval without tender details rather than failing completely + return tenderApproval.ToResponseWithTender(nil), nil + } + + // 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), + } + + s.logger.Info("Tender approval with tender details retrieved successfully", map[string]interface{}{ + "tender_approval_id": tenderApproval.ID, + "tender_id": tenderApproval.TenderID, + "company_id": tenderApproval.CompanyID, + }) + + return tenderApproval.ToResponseWithTender(tenderDetails), 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) @@ -168,6 +277,57 @@ func (s *tenderApprovalService) GetTenderApprovalsByCompanyID(ctx context.Contex return tenderApprovals, nil } +// GetTenderApprovalsByCompanyIDWithTender retrieves all tender approvals for a specific company with tender details +func (s *tenderApprovalService) GetTenderApprovalsByCompanyIDWithTender(ctx context.Context, companyID string, submissionMode *SubmissionMode, status *ApprovalStatus) ([]*TenderApprovalWithTenderResponse, 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 + } + + // Convert to response with tender details + var responses []*TenderApprovalWithTenderResponse + for _, approval := range tenderApprovals { + // Get tender details + tenderResponse, err := s.tenderService.GetTenderByID(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, + 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 = append(responses, approval.ToResponseWithTender(tenderDetails)) + } + + s.logger.Info("Tender approvals with tender details retrieved successfully", map[string]interface{}{ + "company_id": companyID, + "count": len(responses), + }) + + return responses, nil +} + // GetTenderApprovalsByIDs retrieves multiple tender approvals by their IDs func (s *tenderApprovalService) GetTenderApprovalsByIDs(ctx context.Context, ids []string) ([]*TenderApproval, error) { if len(ids) == 0 { @@ -234,6 +394,80 @@ func (s *tenderApprovalService) UpdateTenderApproval(ctx context.Context, id str return tenderApproval, nil } +// UpdateTenderApprovalWithTender updates a tender approval and returns it with tender details +func (s *tenderApprovalService) UpdateTenderApprovalWithTender(ctx context.Context, id string, form *UpdateTenderApprovalForm) (*TenderApprovalWithTenderResponse, 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") + } + + // Get tender details + tenderResponse, err := s.tenderService.GetTenderByID(ctx, tenderApproval.TenderID) + if err != nil { + s.logger.Warn("Failed to get tender details", map[string]interface{}{ + "tender_id": tenderApproval.TenderID, + "error": err.Error(), + }) + // Return approval without tender details rather than failing completely + return tenderApproval.ToResponseWithTender(nil), nil + } + + // 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), + } + + 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.ToResponseWithTender(tenderDetails), nil +} + // DeleteTenderApproval deletes a tender approval func (s *tenderApprovalService) DeleteTenderApproval(ctx context.Context, id string) error { // Get tender approval to check if exists @@ -525,3 +759,213 @@ func (s *tenderApprovalService) GetTenderApprovalsBySubmissionMode(ctx context.C return tenderApprovals, total, nil } + +// GetTenderApprovalsByStatusWithTender retrieves tender approvals by status with tender details +func (s *tenderApprovalService) GetTenderApprovalsByStatusWithTender(ctx context.Context, status ApprovalStatus, limit, offset int) ([]*TenderApprovalWithTenderResponse, int64, error) { + tenderApprovals, total, err := s.GetTenderApprovalsByStatus(ctx, status, limit, offset) + if err != nil { + return nil, 0, err + } + + // Convert to response with tender details + var responses []*TenderApprovalWithTenderResponse + for _, approval := range tenderApprovals { + // Get tender details + tenderResponse, err := s.tenderService.GetTenderByID(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)) + } + + return responses, total, nil +} + +// GetTenderApprovalsBySubmissionModeWithTender retrieves tender approvals by submission mode with tender details +func (s *tenderApprovalService) GetTenderApprovalsBySubmissionModeWithTender(ctx context.Context, submissionMode SubmissionMode, limit, offset int) ([]*TenderApprovalWithTenderResponse, int64, error) { + tenderApprovals, total, err := s.GetTenderApprovalsBySubmissionMode(ctx, submissionMode, limit, offset) + if err != nil { + return nil, 0, err + } + + // Convert to response with tender details + var responses []*TenderApprovalWithTenderResponse + for _, approval := range tenderApprovals { + // Get tender details + tenderResponse, err := s.tenderService.GetTenderByID(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)) + } + + return responses, total, nil +} + +// ListTenderApprovalsWithTender lists tender approvals with search and filters, including tender details +func (s *tenderApprovalService) ListTenderApprovalsWithTender(ctx context.Context, form *ListTenderApprovalsForm) (*TenderApprovalListResponseWithTender, 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 { + for _, s := range form.Status { + status = append(status, ApprovalStatus(s)) + } + } + + // Convert submission mode strings to SubmissionMode + var submissionMode []SubmissionMode + if form.SubmissionMode != nil { + for _, sm := range form.SubmissionMode { + submissionMode = append(submissionMode, SubmissionMode(sm)) + } + } + + // Search 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 search tender approvals", map[string]interface{}{ + "error": err.Error(), + }) + return nil, errors.New("failed to search tender approvals") + } + + // Get total count for pagination + total, err := s.repository.Count(ctx) + if err != nil { + s.logger.Error("Failed to count tender approvals", map[string]interface{}{ + "error": err.Error(), + }) + 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.GetTenderByID(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)) + } + + // Calculate pagination metadata + pages := int(total) / limit + if int(total)%limit > 0 { + pages++ + } + page := (offset / limit) + 1 + + meta := &TenderApprovalListResponseWithTender{ + TenderApprovals: responses, + Total: total, + Limit: limit, + Offset: offset, + Page: page, + Pages: pages, + } + + return meta, nil +}