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, error) } // 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, err := s.getDetails(ctx, req.Tender, *companyID) if err != nil { s.logger.Error("Failed to get details", map[string]interface{}{ "tender_id": req.Tender, "company_id": *companyID, "error": err.Error(), }) } 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, err := s.getDetails(ctx, req.Tender, *companyID) if err != nil { s.logger.Error("Failed to get details", map[string]interface{}{ "tender_id": req.Tender, "company_id": *companyID, "error": err.Error(), }) } 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, err := s.getDetails(ctx, req.Tender, *companyID) if err != nil { s.logger.Error("Failed to get details", map[string]interface{}{ "tender_id": req.Tender, "company_id": *companyID, "error": err.Error(), }) } 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, err := s.getDetails(ctx, feedback.TenderID, *feedback.CompanyID) if err != nil { s.logger.Error("Failed to get details", map[string]interface{}{ "tender_id": feedback.TenderID, "company_id": *feedback.CompanyID, "error": err.Error(), }) } return feedback.ToResponse(tender, company), nil } func (s *feedbackService) Search(ctx context.Context, criteria SearchForm, pagination *response.Pagination) (*FeedbackListResponse, error) { feedbacks, total, 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 := &response.Meta{ Total: int(total), Limit: pagination.Limit, Offset: pagination.Offset, Page: (pagination.Offset / pagination.Limit) + 1, Pages: int((total + int64(pagination.Limit) - 1) / int64(pagination.Limit)), } result := make([]*FeedbackResponse, len(feedbacks)) for i, feedback := range feedbacks { tender, company, _ := s.getDetails(ctx, feedback.TenderID, *feedback.CompanyID) 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, err := s.tenderService.GetByID(ctx, feedback.TenderID) if err != nil { s.logger.Error("Failed to get tender", map[string]interface{}{ "tender_id": feedback.TenderID, "error": err.Error(), }) } company, err := s.companyService.GetByID(ctx, *feedback.CompanyID) if err != nil { s.logger.Error("Failed to get company", map[string]interface{}{ "company_id": feedback.CompanyID, "error": err.Error(), }) } return feedback.ToResponse( &tenderResponse{ ID: tender.ID, NoticePublicationID: tender.NoticePublicationID, PublicationDate: tender.PublicationDate, Title: tender.Title, Description: tender.Description, ProcurementTypeCode: tender.ProcurementTypeCode, ProcedureCode: tender.ProcedureCode, EstimatedValue: tender.EstimatedValue, Currency: tender.Currency, Duration: tender.Duration, DurationUnit: tender.DurationUnit, TenderDeadline: tender.TenderDeadline, SubmissionDeadline: tender.SubmissionDeadline, CountryCode: tender.CountryCode, BuyerOrganization: &organizationResponse{Name: tender.BuyerOrganization.Name}, Status: string(tender.Status), }, &companyResponse{ ID: company.ID, Name: company.Name, }, ), nil } func (s *feedbackService) getDetails(ctx context.Context, tenderID, companyID string) (*tenderResponse, *companyResponse, error) { 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(), }) } 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(), }) } return &tenderResponse{ ID: tender.ID, NoticePublicationID: tender.NoticePublicationID, PublicationDate: tender.PublicationDate, Title: tender.Title, Description: tender.Description, ProcurementTypeCode: tender.ProcurementTypeCode, ProcedureCode: tender.ProcedureCode, EstimatedValue: tender.EstimatedValue, Currency: tender.Currency, Duration: tender.Duration, DurationUnit: tender.DurationUnit, TenderDeadline: tender.TenderDeadline, SubmissionDeadline: tender.SubmissionDeadline, CountryCode: tender.CountryCode, BuyerOrganization: &organizationResponse{Name: tender.BuyerOrganization.Name}, Status: string(tender.Status), }, &companyResponse{ ID: company.ID, Name: company.Name, }, nil } 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 }