Files
tm_back/internal/inquiry/repository.go
T
Mazyar eeafe2a625
continuous-integration/drone/push Build is passing
Add inquiry status transition error handling and validation improvements
- Introduced InvalidStatusTransitionError to handle invalid status transitions for inquiries, providing clearer error messages.
- Updated UpdateInquiryStatusForm to make the Reason field optional and added logic to set a default reason if not provided.
- Enhanced form validation tests to cover new status transition error scenarios and validation messages.
- Refactored handler methods to utilize new error handling functions for improved response management.

This update improves the robustness of inquiry status management by ensuring proper error handling and validation, enhancing user experience during status updates.
2026-07-01 22:50:44 +03:30

281 lines
7.8 KiB
Go

package inquiry
import (
"context"
"errors"
"time"
"tm/pkg/logger"
orm "tm/pkg/mongo"
"tm/pkg/response"
"go.mongodb.org/mongo-driver/v2/bson"
)
// Repository defines methods for inquiry data access
type Repository interface {
Create(ctx context.Context, inquiry *Inquiry) error
GetByID(ctx context.Context, id string) (*Inquiry, error)
UpdateStatus(ctx context.Context, id string, status, reason, changedBy, description string) error
Search(ctx context.Context, form *SearchInquiriesForm, pagination *response.Pagination) (*orm.PaginatedResult[Inquiry], error)
Delete(ctx context.Context, id string) error
CheckPendingInquiry(ctx context.Context, email string) (*Inquiry, error)
CheckPendingInquiryByPhone(ctx context.Context, phone string) (*Inquiry, error)
}
// inquiryRepository implements the Repository interface using the MongoDB ORM
type inquiryRepository struct {
ormRepo orm.Repository[Inquiry]
logger logger.Logger
}
// NewInquiryRepository creates a new inquiry repository
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
// Create indexes using the ORM's index management
indexes := []orm.Index{
*orm.CreateTextIndex("search_idx", "full_name", "company_name", "work_email"),
*orm.NewIndex("company_name_idx", bson.D{bson.E{Key: "company_name", Value: 1}}),
*orm.NewIndex("work_email_idx", bson.D{bson.E{Key: "work_email", Value: 1}}),
*orm.NewIndex("phone_number_idx", bson.D{bson.E{Key: "phone_number", Value: 1}}),
*orm.NewIndex("status_idx", bson.D{bson.E{Key: "status", Value: 1}}),
*orm.NewIndex("created_at_idx", bson.D{bson.E{Key: "created_at", Value: -1}}),
// Compound index for duplicate prevention
*orm.NewIndex("email_status_idx", bson.D{
bson.E{Key: "work_email", Value: 1},
bson.E{Key: "status", Value: 1},
}),
*orm.NewIndex("phone_status_idx", bson.D{
bson.E{Key: "phone_number", Value: 1},
bson.E{Key: "status", Value: 1},
}),
}
// Create indexes
err := mongoManager.CreateIndexes("inquiries", indexes)
if err != nil {
logger.Warn("Failed to create inquiry indexes", map[string]interface{}{
"error": err.Error(),
})
}
// Create ORM repository
ormRepo := orm.NewRepository[Inquiry](mongoManager.GetCollection("inquiries"), logger)
return &inquiryRepository{
ormRepo: ormRepo,
logger: logger,
}
}
// Create creates a new inquiry
func (r *inquiryRepository) Create(ctx context.Context, inquiry *Inquiry) error {
// Set created timestamp using Unix timestamp
now := time.Now().Unix()
inquiry.SetCreatedAt(now)
inquiry.SetUpdatedAt(now)
// Set initial status to pending
inquiry.Status = StatusPending
// Add initial status change to history
inquiry.AddStatusChange(StatusPending, "Inquiry submitted", "system", "Initial inquiry submission")
// Use ORM to create inquiry
err := r.ormRepo.Create(ctx, inquiry)
if err != nil {
r.logger.Error("Failed to create inquiry", map[string]interface{}{
"error": err.Error(),
"work_email": inquiry.WorkEmail,
"company_name": inquiry.CompanyName,
})
return err
}
r.logger.Info("Inquiry created successfully", map[string]interface{}{
"inquiry_id": inquiry.ID,
"work_email": inquiry.WorkEmail,
"company_name": inquiry.CompanyName,
"status": inquiry.Status,
})
return nil
}
// GetByID retrieves an inquiry by ID
func (r *inquiryRepository) GetByID(ctx context.Context, id string) (*Inquiry, error) {
inquiry, err := r.ormRepo.FindByID(ctx, id)
if err != nil {
if errors.Is(err, orm.ErrDocumentNotFound) {
return nil, ErrInquiryNotFound
}
r.logger.Error("Failed to get inquiry by ID", map[string]interface{}{
"error": err.Error(),
"inquiry_id": id,
})
return nil, err
}
return inquiry, nil
}
// UpdateStatus updates the status of an inquiry
func (r *inquiryRepository) UpdateStatus(ctx context.Context, id string, status, reason, changedBy, description string) error {
// Get inquiry first
inquiry, err := r.GetByID(ctx, id)
if err != nil {
return err
}
// Add status change to history
inquiry.AddStatusChange(status, reason, changedBy, description)
// Update in database - use the correct Update method signature
err = r.ormRepo.Update(ctx, inquiry)
if err != nil {
r.logger.Error("Failed to update inquiry status", map[string]interface{}{
"error": err.Error(),
"inquiry_id": id,
"new_status": status,
})
return err
}
r.logger.Info("Inquiry status updated successfully", map[string]interface{}{
"inquiry_id": id,
"old_status": inquiry.StatusHistory[len(inquiry.StatusHistory)-2].Status, // Previous status
"new_status": status,
"changed_by": changedBy,
})
return nil
}
// CheckPendingInquiry checks if there's a pending inquiry for the given email
func (r *inquiryRepository) CheckPendingInquiry(ctx context.Context, email string) (*Inquiry, error) {
filter := bson.M{
"work_email": email,
"status": bson.M{"$in": []string{StatusPending, StatusReviewed}},
}
pagination := orm.NewPaginationBuilder().
Limit(1).
SortBy("created_at", "desc").
Build()
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
if err != nil {
r.logger.Error("Failed to check pending inquiry by email", map[string]interface{}{
"error": err.Error(),
"work_email": email,
})
return nil, err
}
if len(result.Items) > 0 {
return &result.Items[0], nil
}
return nil, nil
}
// CheckPendingInquiryByPhone checks if there's a pending inquiry for the given phone number
func (r *inquiryRepository) CheckPendingInquiryByPhone(ctx context.Context, phone string) (*Inquiry, error) {
filter := bson.M{
"phone_number": phone,
"status": bson.M{"$in": []string{StatusPending, StatusReviewed}},
}
pagination := orm.NewPaginationBuilder().
Limit(1).
SortBy("created_at", "desc").
Build()
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
if err != nil {
r.logger.Error("Failed to check pending inquiry by phone", map[string]interface{}{
"error": err.Error(),
"phone_number": phone,
})
return nil, err
}
if len(result.Items) > 0 {
return &result.Items[0], nil
}
return nil, nil
}
// Search retrieves inquiries with pagination and filters
func (r *inquiryRepository) Search(ctx context.Context, form *SearchInquiriesForm, pagination *response.Pagination) (*orm.PaginatedResult[Inquiry], error) {
filter := bson.M{}
// Add search filter
if form.Search != nil {
filter["$or"] = bson.A{
bson.M{"full_name": bson.M{"$regex": form.Search, "$options": "i"}},
bson.M{"company_name": bson.M{"$regex": form.Search, "$options": "i"}},
bson.M{"work_email": bson.M{"$regex": form.Search, "$options": "i"}},
}
}
// Add status filter
if form.Status != nil {
filter["status"] = *form.Status
}
mongoPagination, err := orm.BuildListPagination(
pagination.Limit,
pagination.Offset,
pagination.Cursor,
pagination.SortBy,
pagination.SortOrder,
filter,
orm.ListPaginationOptions{},
)
if err != nil {
return nil, err
}
result, err := r.ormRepo.FindAll(ctx, filter, mongoPagination)
if err != nil {
r.logger.Error("Failed to search inquiries", map[string]interface{}{
"error": err.Error(),
})
return nil, err
}
return result, nil
}
// Delete deletes an inquiry
func (r *inquiryRepository) Delete(ctx context.Context, id string) error {
// Get inquiry first to ensure it exists
_, err := r.GetByID(ctx, id)
if err != nil {
r.logger.Error("Failed to delete inquiry", map[string]interface{}{
"error": err.Error(),
"inquiry_id": id,
})
if errors.Is(err, orm.ErrDocumentNotFound) {
return errors.New("inquiry not found")
}
return err
}
// Delete in database
err = r.ormRepo.Delete(ctx, id)
if err != nil {
r.logger.Error("Failed to delete inquiry", map[string]interface{}{
"error": err.Error(),
"inquiry_id": id,
})
return err
}
r.logger.Info("Inquiry deleted successfully", map[string]interface{}{
"inquiry_id": id,
})
return nil
}