Files
tm_back/internal/inquiry/service.go
T
n.nakhostin c22d98d4bb Integrate Notification SDK into Inquiry Service
- Updated the InquiryService to include a notification SDK for sending notifications upon inquiry creation, enhancing user engagement.
- Modified the NewInquiryService constructor to accept the notification SDK as a dependency, adhering to dependency injection principles.
- Refactored the Create method to send notifications with inquiry details, improving communication and response to inquiries.
- Cleaned up commented-out code related to duplicate inquiry checks for better readability and maintainability.
2025-09-21 12:23:26 +03:30

320 lines
9.7 KiB
Go

package inquiry
import (
"context"
"errors"
"fmt"
"tm/pkg/logger"
"tm/pkg/notification"
"tm/pkg/response"
)
// Service defines business logic for inquiry operations
type Service interface {
Create(ctx context.Context, form *CreateInquiryForm) (*InquiryResponse, error)
GetByID(ctx context.Context, id string) (*InquiryResponse, error)
UpdateStatus(ctx context.Context, id string, form *UpdateInquiryStatusForm, changedBy string) (*InquiryResponse, error)
Search(ctx context.Context, form *SearchInquiriesForm, pagination *response.Pagination) (*InquiryListResponse, error)
Delete(ctx context.Context, id string) error
}
// inquiryService implements the Service interface
type inquiryService struct {
repository Repository
sdk notification.SDK
logger logger.Logger
}
// NewInquiryService creates a new inquiry service
func NewInquiryService(repository Repository, sdk notification.SDK, logger logger.Logger) Service {
return &inquiryService{
repository: repository,
sdk: sdk,
logger: logger,
}
}
// Create creates a new inquiry
func (s *inquiryService) Create(ctx context.Context, form *CreateInquiryForm) (*InquiryResponse, error) {
s.logger.Info("Creating new inquiry", map[string]interface{}{
"work_email": form.WorkEmail,
"company_name": form.CompanyName,
"full_name": form.FullName,
})
// Check for duplicate pending inquiries by email
// existingInquiry, err := s.repository.CheckPendingInquiry(ctx, form.WorkEmail)
// if err != nil {
// s.logger.Error("Failed to check for duplicate inquiry by email", map[string]interface{}{
// "error": err.Error(),
// "work_email": form.WorkEmail,
// })
// return nil, errors.New("failed to validate inquiry")
// }
// if existingInquiry != nil {
// s.logger.Warn("Duplicate inquiry attempt by email", map[string]interface{}{
// "work_email": form.WorkEmail,
// "existing_id": existingInquiry.ID,
// "existing_status": existingInquiry.Status,
// "created_at": existingInquiry.CreatedAt,
// })
// return nil, NewDuplicateInquiryError("email", existingInquiry.ID.Hex(), existingInquiry.Status, existingInquiry.CreatedAt)
// }
// Check for duplicate pending inquiries by phone number
// existingInquiryByPhone, err := s.repository.CheckPendingInquiryByPhone(ctx, form.PhoneNumber)
// if err != nil {
// s.logger.Error("Failed to check for duplicate inquiry by phone", map[string]interface{}{
// "error": err.Error(),
// "phone_number": form.PhoneNumber,
// })
// return nil, errors.New("failed to validate inquiry")
// }
// if existingInquiryByPhone != nil {
// s.logger.Warn("Duplicate inquiry attempt by phone", map[string]interface{}{
// "phone_number": form.PhoneNumber,
// "existing_id": existingInquiryByPhone.ID,
// "existing_status": existingInquiryByPhone.Status,
// "created_at": existingInquiryByPhone.CreatedAt,
// })
// return nil, NewDuplicateInquiryError("phone", existingInquiryByPhone.ID.Hex(), existingInquiryByPhone.Status, existingInquiryByPhone.CreatedAt)
// }
// Create inquiry entity
inquiry := &Inquiry{
FullName: form.FullName,
CompanyName: form.CompanyName,
WorkEmail: form.WorkEmail,
PhoneNumber: form.PhoneNumber,
}
// Save to database
err := s.repository.Create(ctx, inquiry)
if err != nil {
s.logger.Error("Failed to create inquiry", map[string]interface{}{
"error": err.Error(),
"work_email": form.WorkEmail,
"company_name": form.CompanyName,
})
return nil, errors.New("failed to create inquiry")
}
s.sdk.SendNotification(ctx, &notification.NotificationRequest{
UserID: inquiry.ID.Hex(),
Title: fmt.Sprintf("New Inquiry from %s", inquiry.CompanyName),
Message: GenerateNewInquiryEmailTemplate(&EmailTemplateData{
FullName: inquiry.FullName,
CompanyName: inquiry.CompanyName,
WorkEmail: inquiry.WorkEmail,
PhoneNumber: inquiry.PhoneNumber,
InquiryID: inquiry.ID.Hex(),
CreatedAt: inquiry.CreatedAt,
CompanyLogo: "",
CompanyURL: "https://opplens.com",
SupportEmail: "info@opplens.com",
}),
Type: "alert",
Priority: "important",
EventType: notification.EventTypeEmail,
Methods: notification.NotificationMethods{
Email: "info@opplens.com",
},
})
// TODO: Should be removed only for testing
s.sdk.SendNotification(ctx, &notification.NotificationRequest{
UserID: inquiry.ID.Hex(),
Title: fmt.Sprintf("New Inquiry from %s", inquiry.CompanyName),
Message: GenerateNewInquiryEmailTemplate(&EmailTemplateData{
FullName: inquiry.FullName,
CompanyName: inquiry.CompanyName,
WorkEmail: inquiry.WorkEmail,
PhoneNumber: inquiry.PhoneNumber,
InquiryID: inquiry.ID.Hex(),
CreatedAt: inquiry.CreatedAt,
CompanyLogo: "",
CompanyURL: "https://opplens.com",
SupportEmail: "info@opplens.com",
}),
Type: "alert",
Priority: "important",
EventType: notification.EventTypeEmail,
Methods: notification.NotificationMethods{
Email: "nakhostin.nima1998@gmail.com",
},
})
s.logger.Info("Inquiry created successfully", map[string]interface{}{
"inquiry_id": inquiry.ID,
"work_email": inquiry.WorkEmail,
"company_name": inquiry.CompanyName,
"status": inquiry.Status,
})
return inquiry.ToResponse(), nil
}
// GetByID retrieves an inquiry by ID
func (s *inquiryService) GetByID(ctx context.Context, id string) (*InquiryResponse, error) {
s.logger.Info("Getting inquiry by ID", map[string]interface{}{
"inquiry_id": id,
})
inquiry, err := s.repository.GetByID(ctx, id)
if err != nil {
s.logger.Error("Failed to get inquiry by ID", map[string]interface{}{
"error": err.Error(),
"inquiry_id": id,
})
return nil, err
}
s.logger.Info("Inquiry retrieved successfully", map[string]interface{}{
"inquiry_id": id,
"status": inquiry.Status,
})
return inquiry.ToResponse(), nil
}
// UpdateStatus updates the status of an inquiry
func (s *inquiryService) UpdateStatus(ctx context.Context, id string, form *UpdateInquiryStatusForm, changedBy string) (*InquiryResponse, error) {
s.logger.Info("Updating inquiry status", map[string]interface{}{
"inquiry_id": id,
"new_status": form.Status,
"changed_by": changedBy,
})
// Validate status transition
err := s.validateStatusTransition(ctx, id, form.Status)
if err != nil {
s.logger.Error("Invalid status transition", map[string]interface{}{
"error": err.Error(),
"inquiry_id": id,
"new_status": form.Status,
})
return nil, err
}
// Update status in repository
err = s.repository.UpdateStatus(ctx, id, form.Status, form.Reason, changedBy, form.Description)
if err != nil {
s.logger.Error("Failed to update inquiry status", map[string]interface{}{
"error": err.Error(),
"inquiry_id": id,
"new_status": form.Status,
})
return nil, errors.New("failed to update inquiry status")
}
// Get updated inquiry
inquiry, err := s.repository.GetByID(ctx, id)
if err != nil {
s.logger.Error("Failed to get updated inquiry", map[string]interface{}{
"error": err.Error(),
"inquiry_id": id,
})
return nil, err
}
s.logger.Info("Inquiry status updated successfully", map[string]interface{}{
"inquiry_id": id,
"new_status": form.Status,
"changed_by": changedBy,
})
return inquiry.ToResponse(), nil
}
// validateStatusTransition validates if the status transition is allowed
func (s *inquiryService) validateStatusTransition(ctx context.Context, id string, newStatus string) error {
// Get current inquiry
inquiry, err := s.repository.GetByID(ctx, id)
if err != nil {
return err
}
currentStatus := inquiry.GetCurrentStatus()
// Define allowed transitions
allowedTransitions := map[string][]string{
StatusPending: {StatusReviewed, StatusRejected},
StatusReviewed: {StatusApproved, StatusRejected},
StatusApproved: {}, // No transitions from approved
StatusRejected: {}, // No transitions from rejected
}
allowedNextStatuses, exists := allowedTransitions[currentStatus]
if !exists {
return errors.New("invalid current status")
}
// Check if transition is allowed
for _, allowedStatus := range allowedNextStatuses {
if allowedStatus == newStatus {
return nil
}
}
return errors.New("invalid status transition: cannot change from " + currentStatus + " to " + newStatus)
}
// Search searches inquiries with search and filters
func (s *inquiryService) Search(ctx context.Context, form *SearchInquiriesForm, pagination *response.Pagination) (*InquiryListResponse, error) {
s.logger.Info("Searching inquiries", map[string]interface{}{
"search": form.Search,
"status": form.Status,
"limit": pagination.Limit,
"offset": pagination.Offset,
})
// Get inquiries
inquiries, total, err := s.repository.Search(ctx, form, pagination)
if err != nil {
s.logger.Error("Failed to search inquiries", map[string]interface{}{
"error": err.Error(),
})
return nil, errors.New("failed to search inquiries")
}
// Convert to responses
var inquiryResponses []*InquiryResponse
for _, inquiry := range inquiries {
inquiryResponses = append(inquiryResponses, inquiry.ToResponse())
}
s.logger.Info("Inquiries search completed successfully", map[string]interface{}{
"total_found": total,
"returned": len(inquiryResponses),
})
return &InquiryListResponse{
Inquiries: inquiryResponses,
Meta: pagination.Response(total),
}, nil
}
// Delete deletes an inquiry
func (s *inquiryService) Delete(ctx context.Context, id string) error {
s.logger.Info("Deleting inquiry", map[string]interface{}{
"inquiry_id": id,
})
err := s.repository.Delete(ctx, id)
if err != nil {
s.logger.Error("Failed to delete inquiry", map[string]interface{}{
"error": err.Error(),
"inquiry_id": id,
})
return err
}
s.logger.Info("Inquiry deleted successfully", map[string]interface{}{
"inquiry_id": id,
})
return nil
}