392 lines
11 KiB
Go
392 lines
11 KiB
Go
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)
|
|
GetByTenderIDForCustomer(ctx context.Context, tenderID, customerID string) (*FeedbackResponse, error)
|
|
Delete(ctx context.Context, id string) error
|
|
CompanyStats(ctx context.Context, companyID string) (*StatsResponse, error)
|
|
CustomerStats(ctx context.Context, customerID string) (*CustomerStatsResponse, 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.GetByCustomerID(ctx, *customerID, 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,
|
|
"customer_id": *customerID,
|
|
"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 delete existing feedback", map[string]interface{}{
|
|
"feedback_id": existing.GetID(),
|
|
"tender_id": req.Tender,
|
|
"customer_id": *customerID,
|
|
"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,
|
|
"customer_id": *customerID,
|
|
"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)
|
|
|
|
tenderByID, companyByID := s.loadListDetails(ctx, page.Items)
|
|
|
|
result := make([]*FeedbackResponse, len(page.Items))
|
|
for i, feedback := range page.Items {
|
|
var tenderResp *tenderResponse
|
|
if t, ok := tenderByID[feedback.TenderID]; ok {
|
|
tenderResp = t
|
|
}
|
|
var companyResp *companyResponse
|
|
if cid := feedbackCompanyID(feedback); cid != "" {
|
|
companyResp = companyByID[cid]
|
|
}
|
|
result[i] = feedback.ToResponse(tenderResp, companyResp)
|
|
}
|
|
|
|
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 (s *feedbackService) GetByTenderIDForCustomer(ctx context.Context, tenderID, customerID string) (*FeedbackResponse, error) {
|
|
feedback, err := s.feedbackRepo.GetByTenderIDForCustomer(ctx, tenderID, customerID)
|
|
if err != nil {
|
|
s.logger.Error("Failed to get feedback by tender ID for customer", map[string]interface{}{
|
|
"tender_id": tenderID,
|
|
"customer_id": customerID,
|
|
"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 uniqueNonEmptyStrings(values []string) []string {
|
|
seen := make(map[string]struct{}, len(values))
|
|
out := make([]string, 0, len(values))
|
|
for _, v := range values {
|
|
if v == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[v]; ok {
|
|
continue
|
|
}
|
|
seen[v] = struct{}{}
|
|
out = append(out, v)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func collectFeedbackListIDs(items []Feedback) (tenderIDs, companyIDs []string) {
|
|
tenderIDs = make([]string, 0, len(items))
|
|
companyIDs = make([]string, 0, len(items))
|
|
for _, item := range items {
|
|
tenderIDs = append(tenderIDs, item.TenderID)
|
|
companyIDs = append(companyIDs, feedbackCompanyID(item))
|
|
}
|
|
return uniqueNonEmptyStrings(tenderIDs), uniqueNonEmptyStrings(companyIDs)
|
|
}
|
|
|
|
// loadListDetails batch-loads tender and company data for feedback list responses.
|
|
func (s *feedbackService) loadListDetails(ctx context.Context, items []Feedback) (
|
|
map[string]*tenderResponse,
|
|
map[string]*companyResponse,
|
|
) {
|
|
tenderByID := make(map[string]*tenderResponse)
|
|
companyByID := make(map[string]*companyResponse)
|
|
|
|
if len(items) == 0 {
|
|
return tenderByID, companyByID
|
|
}
|
|
|
|
tenderIDs, companyIDs := collectFeedbackListIDs(items)
|
|
|
|
if len(tenderIDs) > 0 {
|
|
tenders, err := s.tenderService.GetByIDs(ctx, tenderIDs)
|
|
if err != nil {
|
|
s.logger.Error("Failed to batch load tenders for feedback list", map[string]interface{}{
|
|
"count": len(tenderIDs),
|
|
"error": err.Error(),
|
|
})
|
|
} else {
|
|
for id, t := range tenders {
|
|
tenderByID[id] = tenderToFeedbackResponse(t)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(companyIDs) > 0 {
|
|
companies, err := s.companyService.GetNamesByIDs(ctx, companyIDs)
|
|
if err != nil {
|
|
s.logger.Error("Failed to batch load companies for feedback list", map[string]interface{}{
|
|
"count": len(companyIDs),
|
|
"error": err.Error(),
|
|
})
|
|
} else {
|
|
for id, c := range companies {
|
|
companyByID[id] = companyToFeedbackResponse(c)
|
|
}
|
|
}
|
|
}
|
|
|
|
return tenderByID, companyByID
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (s *feedbackService) CustomerStats(ctx context.Context, customerID string) (*CustomerStatsResponse, error) {
|
|
s.logger.Info("Getting customer feedback statistics", map[string]interface{}{
|
|
"customer_id": customerID,
|
|
})
|
|
|
|
stats, err := s.feedbackRepo.CustomerStats(ctx, customerID)
|
|
if err != nil {
|
|
s.logger.Error("Failed to get customer stats", map[string]interface{}{
|
|
"customer_id": customerID,
|
|
"error": err.Error(),
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
s.logger.Info("Customer stats retrieved successfully", map[string]interface{}{
|
|
"customer_id": customerID,
|
|
"total_feedback": stats.TotalFeedback,
|
|
"total_likes": stats.TotalLikes,
|
|
"total_dislikes": stats.TotalDislikes,
|
|
})
|
|
|
|
return stats, nil
|
|
}
|