Add Inquiry Management Functionality and Update API Documentation
- Introduced a new inquiry management feature, including CRUD operations for inquiries in both admin and public API endpoints. - Implemented inquiry entity, service, repository, and handler layers following the clean architecture principles. - Added new API routes for searching, retrieving, updating, and deleting inquiries, enhancing the overall functionality of the system. - Updated Swagger documentation to include detailed descriptions and examples for the new inquiry endpoints, ensuring clarity for API consumers. - Enhanced error handling for duplicate inquiries and validation, improving the robustness of the inquiry management process. - Updated existing routes to integrate inquiry handling, ensuring a seamless user experience across the application.
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
package inquiry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"tm/pkg/logger"
|
||||
"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
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewInquiryService creates a new inquiry service
|
||||
func NewInquiryService(repository Repository, logger logger.Logger) Service {
|
||||
return &inquiryService{
|
||||
repository: repository,
|
||||
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.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
|
||||
}
|
||||
Reference in New Issue
Block a user