a342da193e
- Updated feedback repository and service methods to use a consistent naming convention, changing `NewTenderRepository` and similar methods to `NewRepository`. - Refactored feedback handler methods to improve clarity by renaming methods such as `ListFeedback` to `Search` and `GetFeedback` to `Get`. - Enhanced feedback response structures to include detailed company and tender information, improving the clarity of feedback data returned to API consumers. - Updated Swagger documentation to reflect changes in feedback response structures and endpoint paths, ensuring accurate representation of the API for consumers.
320 lines
9.7 KiB
Go
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
|
|
}
|
|
|
|
// NewService creates a new inquiry service
|
|
func NewService(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, ¬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",
|
|
}),
|
|
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",
|
|
}),
|
|
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
|
|
}
|