package feedback import ( "context" "tm/internal/company" "tm/internal/tender" "tm/pkg/logger" "tm/pkg/response" ) // FeedbackService interface defines feedback business logic methods type Service interface { Toggle(ctx context.Context, req ToggleFeedbackForm, customerID *string, companyID *string) (*FeedbackResponse, error) Search(ctx context.Context, criteria SearchForm, pagination *response.Pagination) (*FeedbackListResponse, error) Get(ctx context.Context, id string) (*FeedbackResponse, error) GetByTenderID(ctx context.Context, tenderID, companyID string) (*FeedbackResponse, error) Delete(ctx context.Context, id string) error CompanyStats(ctx context.Context, companyID string) (*StatsResponse, error) getDetails(ctx context.Context, tenderID, companyID string) (*tenderResponse, *companyResponse) } // feedbackService implements FeedbackService interface type feedbackService struct { feedbackRepo Repository tenderService tender.Service companyService company.Service logger logger.Logger } // NewService creates a new feedback service func NewService(feedbackRepo Repository, tenderService tender.Service, companyService company.Service, logger logger.Logger) Service { return &feedbackService{ feedbackRepo: feedbackRepo, tenderService: tenderService, companyService: companyService, logger: logger, } } func (s *feedbackService) Toggle(ctx context.Context, req ToggleFeedbackForm, customerID *string, companyID *string) (*FeedbackResponse, error) { existing, err := s.feedbackRepo.GetByCompanyID(ctx, *companyID, req.Tender) if err != nil { feedback := &Feedback{ TenderID: req.Tender, FeedbackType: req.Type, CompanyID: companyID, CustomerID: customerID, } err = s.feedbackRepo.Create(ctx, feedback) if err != nil { s.logger.Error("Failed to create new feedback", map[string]interface{}{ "tender_id": req.Tender, "company_id": *companyID, "error": err.Error(), }) return nil, err } tender, company := s.getDetails(ctx, req.Tender, *companyID) return feedback.ToResponse(tender, company), nil } if existing.FeedbackType == req.Type { err = s.feedbackRepo.Delete(ctx, existing.GetID()) if err != nil { s.logger.Error("Failed to update existing feedback", map[string]interface{}{ "feedback_id": existing.GetID(), "tender_id": req.Tender, "company_id": *companyID, "error": err.Error(), }) return nil, err } if req.Type == FeedbackTypeLike { existing.FeedbackType = "unlike" } else { existing.FeedbackType = "undislike" } tender, company := s.getDetails(ctx, req.Tender, *companyID) return existing.ToResponse(tender, company), nil } if existing.FeedbackType == FeedbackTypeLike { existing.FeedbackType = FeedbackTypeDislike } else { existing.FeedbackType = FeedbackTypeLike } err = s.feedbackRepo.Update(ctx, existing) if err != nil { s.logger.Error("Failed to update existing feedback", map[string]interface{}{ "feedback_id": existing.GetID(), "tender_id": req.Tender, "company_id": *companyID, "error": err.Error(), }) return nil, err } tender, company := s.getDetails(ctx, req.Tender, *companyID) return existing.ToResponse(tender, company), nil } func (s *feedbackService) Get(ctx context.Context, id string) (*FeedbackResponse, error) { feedback, err := s.feedbackRepo.GetByID(ctx, id) if err != nil { s.logger.Error("Failed to get feedback", map[string]interface{}{ "id": id, "error": err.Error(), }) return nil, err } tender, company := s.getDetails(ctx, feedback.TenderID, feedbackCompanyID(*feedback)) return feedback.ToResponse(tender, company), nil } func (s *feedbackService) Search(ctx context.Context, criteria SearchForm, pagination *response.Pagination) (*FeedbackListResponse, error) { page, err := s.feedbackRepo.Search(ctx, criteria, pagination) if err != nil { s.logger.Error("Failed to list feedback", map[string]interface{}{ "criteria": criteria, "limit": pagination.Limit, "offset": pagination.Offset, "error": err.Error(), }) return nil, err } meta := pagination.ListMeta(page.TotalCount, page.NextCursor, page.HasMore, page.PageOffset) result := make([]*FeedbackResponse, len(page.Items)) for i, feedback := range page.Items { tender, company := s.getDetails(ctx, feedback.TenderID, feedbackCompanyID(feedback)) result[i] = feedback.ToResponse(tender, company) } return &FeedbackListResponse{ Feedback: result, Meta: meta, }, nil } func (s *feedbackService) Delete(ctx context.Context, id string) error { return s.feedbackRepo.Delete(ctx, id) } func (s *feedbackService) GetByTenderID(ctx context.Context, tenderID, companyID string) (*FeedbackResponse, error) { feedback, err := s.feedbackRepo.GetByTenderID(ctx, tenderID, companyID) if err != nil { s.logger.Error("Failed to get feedback by tender ID", map[string]interface{}{ "tender_id": tenderID, "error": err.Error(), }) return nil, err } tender, company := s.getDetails(ctx, feedback.TenderID, feedbackCompanyID(*feedback)) return feedback.ToResponse(tender, company), nil } func feedbackCompanyID(f Feedback) string { if f.CompanyID == nil { return "" } return *f.CompanyID } func tenderToFeedbackResponse(t *tender.TenderResponse) *tenderResponse { if t == nil { return nil } var buyer *organizationResponse if t.BuyerOrganization != nil { buyer = &organizationResponse{Name: t.BuyerOrganization.Name} } return &tenderResponse{ ID: t.ID, NoticePublicationID: t.NoticePublicationID, PublicationDate: t.PublicationDate, Title: t.Title, Description: t.Description, ProcurementTypeCode: t.ProcurementTypeCode, ProcedureCode: t.ProcedureCode, EstimatedValue: t.EstimatedValue, Currency: t.Currency, Duration: t.Duration, DurationUnit: t.DurationUnit, TenderDeadline: t.TenderDeadline, SubmissionDeadline: t.SubmissionDeadline, CountryCode: t.CountryCode, BuyerOrganization: buyer, Status: string(t.Status), } } func companyToFeedbackResponse(c *company.CompanyResponse) *companyResponse { if c == nil { return nil } return &companyResponse{ ID: c.ID, Name: c.Name, } } func (s *feedbackService) getDetails(ctx context.Context, tenderID, companyID string) (*tenderResponse, *companyResponse) { var tenderResp *tenderResponse tender, err := s.tenderService.GetByID(ctx, tenderID, "") if err != nil { s.logger.Error("Failed to get tender", map[string]interface{}{ "tender_id": tenderID, "error": err.Error(), }) } else { tenderResp = tenderToFeedbackResponse(tender) } var companyResp *companyResponse if companyID != "" { company, err := s.companyService.GetByID(ctx, companyID) if err != nil { s.logger.Error("Failed to get company", map[string]interface{}{ "company_id": companyID, "error": err.Error(), }) } else { companyResp = companyToFeedbackResponse(company) } } return tenderResp, companyResp } func (s *feedbackService) CompanyStats(ctx context.Context, companyID string) (*StatsResponse, error) { s.logger.Info("Getting company feedback statistics", map[string]interface{}{ "company_id": companyID, }) stats, err := s.feedbackRepo.CompanyStats(ctx, companyID) if err != nil { s.logger.Error("Failed to get company stats", map[string]interface{}{ "company_id": companyID, "error": err.Error(), }) return nil, err } s.logger.Info("Company stats retrieved successfully", map[string]interface{}{ "company_id": companyID, "total_feedback": stats.TotalFeedback, "total_likes": stats.TotalLikes, "total_dislikes": stats.TotalDislikes, }) return stats, nil }