323 lines
9.7 KiB
Go
323 lines
9.7 KiB
Go
package inquiry
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"tm/pkg/logger"
|
|
"tm/pkg/notification"
|
|
"tm/pkg/response"
|
|
"tm/pkg/security"
|
|
)
|
|
|
|
// 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
|
|
xssPolicy *security.XSSPolicy
|
|
}
|
|
|
|
// NewService creates a new inquiry service
|
|
func NewService(repository Repository, sdk notification.SDK, logger logger.Logger, xssPolicy *security.XSSPolicy) Service {
|
|
return &inquiryService{
|
|
repository: repository,
|
|
sdk: sdk,
|
|
logger: logger,
|
|
xssPolicy: xssPolicy,
|
|
}
|
|
}
|
|
|
|
// 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, ¬ification.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",
|
|
}, s.xssPolicy),
|
|
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, ¬ification.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",
|
|
}, s.xssPolicy),
|
|
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
|
|
}
|