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, }) page, 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, err } var inquiryResponses []*InquiryResponse for _, inquiry := range page.Items { inquiryResponses = append(inquiryResponses, inquiry.ToResponse()) } s.logger.Info("Inquiries search completed successfully", map[string]interface{}{ "total_found": page.TotalCount, "returned": len(inquiryResponses), }) return &InquiryListResponse{ Inquiries: inquiryResponses, Meta: pagination.ListMeta(page.TotalCount, page.NextCursor, page.HasMore, page.PageOffset), }, 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 }