package customer import ( "context" "errors" "time" "tm/pkg/logger" mongopkg "tm/pkg/mongo" "go.mongodb.org/mongo-driver/bson" ) // Repository defines the interface for customer data operations type Repository interface { Create(ctx context.Context, customer *Customer) error GetByID(ctx context.Context, id string) (*Customer, error) GetByEmail(ctx context.Context, email string) (*Customer, error) GetByUsername(ctx context.Context, username string) (*Customer, error) GetByCompanyName(ctx context.Context, companyName string) (*Customer, error) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error) GetByTaxID(ctx context.Context, taxID string) (*Customer, error) GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, error) Update(ctx context.Context, customer *Customer) error Delete(ctx context.Context, id string) error List(ctx context.Context, limit, offset int) ([]*Customer, error) Search(ctx context.Context, search string, customerType *string, status *string, companyID *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error) CountByCompanyID(ctx context.Context, companyID string) (int64, error) CountByType(ctx context.Context, customerType CustomerType) (int64, error) CountByStatus(ctx context.Context, status CustomerStatus) (int64, error) UpdateStatus(ctx context.Context, id string, status CustomerStatus) error UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error } // customerRepository implements the Repository interface using the MongoDB ORM type customerRepository struct { ormRepo mongopkg.Repository[Customer] logger logger.Logger } // NewCustomerRepository creates a new customer repository func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logger.Logger) Repository { // Create indexes using the ORM's index management indexes := []mongopkg.Index{ *mongopkg.CreateUniqueIndex("email_idx", bson.D{{Key: "email", Value: 1}}), *mongopkg.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}), *mongopkg.NewIndex("company_name_idx", bson.D{{Key: "company_name", Value: 1}}), *mongopkg.NewIndex("registration_number_idx", bson.D{{Key: "registration_number", Value: 1}}), *mongopkg.NewIndex("tax_id_idx", bson.D{{Key: "tax_id", Value: 1}}), *mongopkg.NewIndex("company_id_idx", bson.D{{Key: "company_id", Value: 1}}), *mongopkg.NewIndex("type_idx", bson.D{{Key: "type", Value: 1}}), *mongopkg.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}), *mongopkg.NewIndex("industry_idx", bson.D{{Key: "industry", Value: 1}}), *mongopkg.NewIndex("is_verified_idx", bson.D{{Key: "is_verified", Value: 1}}), *mongopkg.NewIndex("is_compliant_idx", bson.D{{Key: "is_compliant", Value: 1}}), *mongopkg.NewIndex("language_idx", bson.D{{Key: "language", Value: 1}}), *mongopkg.NewIndex("currency_idx", bson.D{{Key: "currency", Value: 1}}), *mongopkg.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}), *mongopkg.CreateTextIndex("search_idx", "first_name", "last_name", "full_name", "company_name", "email", "username"), } // Create indexes err := mongoManager.CreateIndexes("customers", indexes) if err != nil { logger.Warn("Failed to create customer indexes", map[string]interface{}{ "error": err.Error(), }) } // Create ORM repository ormRepo := mongopkg.NewRepository[Customer](mongoManager.GetCollection("customers"), logger) return &customerRepository{ ormRepo: ormRepo, logger: logger, } } // Create creates a new customer func (r *customerRepository) Create(ctx context.Context, customer *Customer) error { // Set created/updated timestamps using Unix timestamps now := time.Now().Unix() customer.SetCreatedAt(now) customer.SetUpdatedAt(now) // Use ORM to create customer err := r.ormRepo.Create(ctx, customer) if err != nil { r.logger.Error("Failed to create customer", map[string]interface{}{ "error": err.Error(), "email": customer.Email, "username": customer.Username, }) return err } r.logger.Info("Customer created successfully", map[string]interface{}{ "customer_id": customer.ID, "email": customer.Email, "type": customer.Type, }) return nil } // GetByID retrieves a customer by ID func (r *customerRepository) GetByID(ctx context.Context, id string) (*Customer, error) { customer, err := r.ormRepo.FindByID(ctx, id) if err != nil { if errors.Is(err, mongopkg.ErrDocumentNotFound) { return nil, errors.New("customer not found") } r.logger.Error("Failed to get customer by ID", map[string]interface{}{ "error": err.Error(), "customer_id": id, }) return nil, err } return customer, nil } // GetByEmail retrieves a customer by email func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Customer, error) { filter := bson.M{"email": email} customer, err := r.ormRepo.FindOne(ctx, filter) if err != nil { if errors.Is(err, mongopkg.ErrDocumentNotFound) { return nil, errors.New("customer not found") } r.logger.Error("Failed to get customer by email", map[string]interface{}{ "error": err.Error(), "email": email, }) return nil, err } return customer, nil } // GetByUsername retrieves a customer by username func (r *customerRepository) GetByUsername(ctx context.Context, username string) (*Customer, error) { filter := bson.M{"username": username} customer, err := r.ormRepo.FindOne(ctx, filter) if err != nil { if errors.Is(err, mongopkg.ErrDocumentNotFound) { return nil, errors.New("customer not found") } r.logger.Error("Failed to get customer by username", map[string]interface{}{ "error": err.Error(), "username": username, }) return nil, err } return customer, nil } // GetByCompanyName retrieves a customer by company name func (r *customerRepository) GetByCompanyName(ctx context.Context, companyName string) (*Customer, error) { filter := bson.M{"company_name": companyName} customer, err := r.ormRepo.FindOne(ctx, filter) if err != nil { if errors.Is(err, mongopkg.ErrDocumentNotFound) { return nil, errors.New("customer not found") } r.logger.Error("Failed to get customer by company name", map[string]interface{}{ "error": err.Error(), "company_name": companyName, }) return nil, err } return customer, nil } // GetByRegistrationNumber retrieves a customer by registration number func (r *customerRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error) { filter := bson.M{"registration_number": registrationNumber} customer, err := r.ormRepo.FindOne(ctx, filter) if err != nil { if errors.Is(err, mongopkg.ErrDocumentNotFound) { return nil, errors.New("customer not found") } r.logger.Error("Failed to get customer by registration number", map[string]interface{}{ "error": err.Error(), "registration_number": registrationNumber, }) return nil, err } return customer, nil } // GetByTaxID retrieves a customer by tax ID func (r *customerRepository) GetByTaxID(ctx context.Context, taxID string) (*Customer, error) { filter := bson.M{"tax_id": taxID} customer, err := r.ormRepo.FindOne(ctx, filter) if err != nil { if errors.Is(err, mongopkg.ErrDocumentNotFound) { return nil, errors.New("customer not found") } r.logger.Error("Failed to get customer by tax ID", map[string]interface{}{ "error": err.Error(), "tax_id": taxID, }) return nil, err } return customer, nil } // GetByCompanyID retrieves customers by company ID with pagination func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, error) { // Build pagination pagination := mongopkg.NewPaginationBuilder(). Limit(limit). Skip(offset). SortDesc("created_at"). Build() // Filter by company ID and active status filter := bson.M{ "company_id": companyID, "status": bson.M{"$ne": CustomerStatusInactive}, } // Use ORM to find customers result, err := r.ormRepo.FindAll(ctx, filter, pagination) if err != nil { r.logger.Error("Failed to get customers by company ID", map[string]interface{}{ "error": err.Error(), "company_id": companyID, "limit": limit, "offset": offset, }) return nil, err } // Convert []Customer to []*Customer customers := make([]*Customer, len(result.Items)) for i := range result.Items { customers[i] = &result.Items[i] } return customers, nil } // Update updates a customer func (r *customerRepository) Update(ctx context.Context, customer *Customer) error { // Set updated timestamp using Unix timestamp customer.SetUpdatedAt(time.Now().Unix()) // Use ORM to update customer err := r.ormRepo.Update(ctx, customer) if err != nil { r.logger.Error("Failed to update customer", map[string]interface{}{ "error": err.Error(), "customer_id": customer.ID, }) return err } r.logger.Info("Customer updated successfully", map[string]interface{}{ "customer_id": customer.ID, "email": customer.Email, }) return nil } // Delete deletes a customer (soft delete by setting status to inactive) func (r *customerRepository) Delete(ctx context.Context, id string) error { // Get customer first customer, err := r.GetByID(ctx, id) if err != nil { return err } // Update status to inactive customer.Status = CustomerStatusInactive customer.SetUpdatedAt(time.Now().Unix()) // Update in database err = r.ormRepo.Update(ctx, customer) if err != nil { r.logger.Error("Failed to delete customer", map[string]interface{}{ "error": err.Error(), "customer_id": id, }) return err } r.logger.Info("Customer deleted successfully", map[string]interface{}{ "customer_id": id, }) return nil } // List retrieves customers with pagination func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*Customer, error) { // Build pagination pagination := mongopkg.NewPaginationBuilder(). Limit(limit). Skip(offset). SortDesc("created_at"). Build() // Only active customers by default filter := bson.M{"status": bson.M{"$ne": CustomerStatusInactive}} // Use ORM to find customers result, err := r.ormRepo.FindAll(ctx, filter, pagination) if err != nil { r.logger.Error("Failed to list customers", map[string]interface{}{ "error": err.Error(), "limit": limit, "offset": offset, }) return nil, err } // Convert []Customer to []*Customer customers := make([]*Customer, len(result.Items)) for i := range result.Items { customers[i] = &result.Items[i] } return customers, nil } // Search retrieves customers with search and filters func (r *customerRepository) Search(ctx context.Context, search string, customerType *string, status *string, companyID *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error) { // Build filter filter := bson.M{} if search != "" { filter["$text"] = bson.M{"$search": search} } if customerType != nil { filter["type"] = *customerType } if status != nil { filter["status"] = *status } if companyID != nil { filter["company_id"] = *companyID } if industry != nil { filter["industry"] = *industry } if isVerified != nil { filter["is_verified"] = *isVerified } if isCompliant != nil { filter["is_compliant"] = *isCompliant } if language != nil { filter["language"] = *language } if currency != nil { filter["currency"] = *currency } // Build pagination pagination := mongopkg.NewPaginationBuilder(). Limit(limit). Skip(offset) // Set sort if sortBy != "" { if sortOrder == "desc" { pagination.SortDesc(sortBy) } else { pagination.SortAsc(sortBy) } } else { pagination.SortDesc("created_at") // Default sort } // Use ORM to find customers result, err := r.ormRepo.FindAll(ctx, filter, pagination.Build()) if err != nil { r.logger.Error("Failed to search customers", map[string]interface{}{ "error": err.Error(), "search": search, }) return nil, err } // Convert []Customer to []*Customer customers := make([]*Customer, len(result.Items)) for i := range result.Items { customers[i] = &result.Items[i] } return customers, nil } // CountByCompanyID counts customers by company ID func (r *customerRepository) CountByCompanyID(ctx context.Context, companyID string) (int64, error) { filter := bson.M{ "company_id": companyID, "status": bson.M{"$ne": CustomerStatusInactive}, } count, err := r.ormRepo.Count(ctx, filter) if err != nil { r.logger.Error("Failed to count customers by company ID", map[string]interface{}{ "error": err.Error(), "company_id": companyID, }) return 0, err } return count, nil } // CountByType counts customers by type func (r *customerRepository) CountByType(ctx context.Context, customerType CustomerType) (int64, error) { filter := bson.M{ "type": customerType, "status": bson.M{"$ne": CustomerStatusInactive}, } count, err := r.ormRepo.Count(ctx, filter) if err != nil { r.logger.Error("Failed to count customers by type", map[string]interface{}{ "error": err.Error(), "customer_type": customerType, }) return 0, err } return count, nil } // CountByStatus counts customers by status func (r *customerRepository) CountByStatus(ctx context.Context, status CustomerStatus) (int64, error) { filter := bson.M{"status": status} count, err := r.ormRepo.Count(ctx, filter) if err != nil { r.logger.Error("Failed to count customers by status", map[string]interface{}{ "error": err.Error(), "status": status, }) return 0, err } return count, nil } // UpdateStatus updates customer status func (r *customerRepository) UpdateStatus(ctx context.Context, id string, status CustomerStatus) error { // Get customer first customer, err := r.GetByID(ctx, id) if err != nil { return err } // Update status customer.Status = status customer.SetUpdatedAt(time.Now().Unix()) // Update in database err = r.ormRepo.Update(ctx, customer) if err != nil { r.logger.Error("Failed to update customer status", map[string]interface{}{ "error": err.Error(), "customer_id": id, "status": status, }) return err } r.logger.Info("Customer status updated successfully", map[string]interface{}{ "customer_id": id, "status": status, }) return nil } // UpdateVerification updates customer verification status func (r *customerRepository) UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error { // Get customer first customer, err := r.GetByID(ctx, id) if err != nil { return err } // Update verification fields customer.IsVerified = isVerified customer.IsCompliant = isCompliant customer.ComplianceNotes = complianceNotes customer.SetUpdatedAt(time.Now().Unix()) // Update in database err = r.ormRepo.Update(ctx, customer) if err != nil { r.logger.Error("Failed to update customer verification", map[string]interface{}{ "error": err.Error(), "customer_id": id, }) return err } r.logger.Info("Customer verification updated successfully", map[string]interface{}{ "customer_id": id, "is_verified": isVerified, "is_compliant": isCompliant, }) return nil }