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) GetByCompanyIDs(ctx context.Context, companyIDs []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) CountByCompanyIDs(ctx context.Context, companyIDs []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_ids_idx", bson.D{{Key: "company_ids", Value: 1}}), // Index for CompanyIDs array *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 } // GetByCompanyIDs retrieves customers by multiple company IDs with pagination func (r *customerRepository) GetByCompanyIDs(ctx context.Context, companyIDs []string, limit, offset int) ([]*Customer, error) { if len(companyIDs) == 0 { return nil, errors.New("no company IDs provided") } // Build pagination pagination := mongopkg.NewPaginationBuilder(). Limit(limit). Skip(offset). SortDesc("created_at"). Build() // Filter by company IDs and active status filter := bson.M{ "company_ids": bson.M{"$in": companyIDs}, "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 multiple company IDs", map[string]interface{}{ "error": err.Error(), "company_ids": companyIDs, "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 } // CountByCompanyIDs counts customers by multiple company IDs func (r *customerRepository) CountByCompanyIDs(ctx context.Context, companyIDs []string) (int64, error) { if len(companyIDs) == 0 { return 0, errors.New("no company IDs provided") } filter := bson.M{ "company_ids": bson.M{"$in": companyIDs}, "status": bson.M{"$ne": CustomerStatusInactive}, } count, err := r.ormRepo.Count(ctx, filter) if err != nil { r.logger.Error("Failed to count customers by multiple company IDs", map[string]interface{}{ "error": err.Error(), "company_ids": companyIDs, }) 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 }