Files
docs/archive/03-BACKEND/CMS/club-features-system.md
T
masoodafar-web 5965b98728 update
2026-01-03 18:27:49 +03:30

12 KiB

🎁 Club Features System

آخرین بروزرسانی: ۳ دی ۱۴۰۴ (23 December 2025)
وضعیت: Production Ready


📋 فهرست

  1. معرفی
  2. فیچرهای باشگاه
  3. Entity ها
  4. Enum ClubFeatureType
  5. فرآیند فعال‌سازی
  6. API ها

معرفی

سیستم فیچرهای باشگاه مشتریان، امکانات ویژه‌ای را برای اعضای باشگاه فراهم می‌کند. هر کاربر با فعال‌سازی باشگاه، به تمام 4 فیچر دسترسی پیدا می‌کند.


فیچرهای باشگاه

Id نام عنوان فارسی توضیح
1 Chatika چتیکا دستیار هوش مصنوعی - حساب خودکار ایجاد می‌شود
2 Bime بیمه خدمات بیمه‌ای
3 Trip تریپ خدمات سفر و گردشگری
4 Learn لرن آموزش و یادگیری

Entity ها

ClubFeature (تعریف فیچرها)

public class ClubFeature : BaseAuditableEntity
{
    public string Title { get; set; }
    public string? Description { get; set; }
    public bool IsActive { get; set; }
    public int SortOrder { get; set; }
    
    public virtual ICollection<UserClubFeature>? UserClubFeatures { get; set; }
}

UserClubFeature (فیچرهای کاربر)

public class UserClubFeature : BaseAuditableEntity
{
    public long UserId { get; set; }
    public virtual User User { get; set; }
    
    public long ClubMembershipId { get; set; }
    public virtual ClubMembership ClubMembership { get; set; }
    
    public long ClubFeatureId { get; set; }
    public virtual ClubFeature ClubFeature { get; set; }
    
    public DateTime GrantedAt { get; set; }
    public bool IsActive { get; set; } = true;
    public string? Notes { get; set; }  // توضیحات اختیاری یا وضعیت فعال‌سازی
}

Database Schema

CREATE TABLE ClubFeatures (
    Id BIGINT PRIMARY KEY IDENTITY,
    Title NVARCHAR(200) NOT NULL,
    Description NVARCHAR(MAX),
    IsActive BIT DEFAULT 1,
    SortOrder INT DEFAULT 0,
    -- BaseAuditableEntity fields
    Created DATETIME2,
    CreatedBy NVARCHAR(100),
    LastModified DATETIME2,
    LastModifiedBy NVARCHAR(100),
    IsDeleted BIT DEFAULT 0
);

CREATE TABLE UserClubFeatures (
    Id BIGINT PRIMARY KEY IDENTITY,
    UserId BIGINT NOT NULL FOREIGN KEY REFERENCES Users(Id),
    ClubMembershipId BIGINT NOT NULL FOREIGN KEY REFERENCES ClubMemberships(Id),
    ClubFeatureId BIGINT NOT NULL FOREIGN KEY REFERENCES ClubFeatures(Id),
    GrantedAt DATETIME2 NOT NULL,
    IsActive BIT DEFAULT 1,
    Notes NVARCHAR(MAX),
    -- BaseAuditableEntity fields
    Created DATETIME2,
    CreatedBy NVARCHAR(100),
    LastModified DATETIME2,
    LastModifiedBy NVARCHAR(100),
    IsDeleted BIT DEFAULT 0
);

-- Seed Data
INSERT INTO ClubFeatures (Id, Title, Description, IsActive, SortOrder)
VALUES 
    (1, N'چتیکا', N'دستیار هوش مصنوعی', 1, 1),
    (2, N'بیمه', N'خدمات بیمه‌ای', 1, 2),
    (3, N'تریپ', N'خدمات سفر و گردشگری', 1, 3),
    (4, N'لرن', N'آموزش و یادگیری', 1, 4);

Enum ClubFeatureType

برای جلوگیری از hardcoded IDs، از Enum استفاده می‌شود:

فایل: CMSMicroservice.Domain/Enums/ClubFeatureType.cs

namespace CMSMicroservice.Domain.Enums;

/// <summary>
/// انواع ویژگی‌های باشگاه مشتریان
/// </summary>
public enum ClubFeatureType
{
    /// <summary>
    /// چتیکا - دستیار هوش مصنوعی
    /// </summary>
    Chatika = 1,

    /// <summary>
    /// بیمه - خدمات بیمه‌ای
    /// </summary>
    Bime = 2,

    /// <summary>
    /// تریپ - خدمات سفر و گردشگری
    /// </summary>
    Trip = 3,

    /// <summary>
    /// لرن - آموزش و یادگیری
    /// </summary>
    Learn = 4
}

/// <summary>
/// Extension methods برای ClubFeatureType
/// </summary>
public static class ClubFeatureTypeExtensions
{
    /// <summary>
    /// دریافت تمام مقادیر ClubFeatureType به صورت آرایه long
    /// </summary>
    public static long[] GetAllFeatureIds()
    {
        return Enum.GetValues<ClubFeatureType>()
            .Select(f => (long)f)
            .ToArray();
    }

    /// <summary>
    /// دریافت عنوان فارسی ویژگی
    /// </summary>
    public static string GetPersianTitle(this ClubFeatureType featureType)
    {
        return featureType switch
        {
            ClubFeatureType.Chatika => "چتیکا",
            ClubFeatureType.Bime => "بیمه",
            ClubFeatureType.Trip => "تور و سفر",
            ClubFeatureType.Learn => "آموزش",
            _ => featureType.ToString()
        };
    }
}

استفاده در کد

// ❌ قبل - Hardcoded
var featureIds = new long[] { 1, 2, 3, 4 };

// ✅ بعد - با Enum
var featureIds = ClubFeatureTypeExtensions.GetAllFeatureIds();

// دسترسی به یک فیچر خاص
var chatikaId = (long)ClubFeatureType.Chatika; // = 1
var title = ClubFeatureType.Bime.GetPersianTitle(); // = "بیمه"

فرآیند فعال‌سازی

هنگام فعال‌سازی باشگاه مشتریان، فیچرها به این ترتیب اختصاص داده می‌شوند:

┌─────────────────────────────────────────────────────────────────┐
│              ActivateClubMembershipCommandHandler               │
│                            یا                                    │
│           AcceptClubMembershipContractCommandHandler            │
└─────────────────────────────┬───────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  // 8. اختصاص فیچرهای باشگاه                                     │
│  var featureIds = ClubFeatureTypeExtensions.GetAllFeatureIds(); │
│  foreach (var featureId in featureIds)                          │
│  {                                                              │
│      _context.UserClubFeatures.Add(new UserClubFeature          │
│      {                                                          │
│          UserId = user.Id,                                      │
│          ClubMembershipId = membership.Id,                      │
│          ClubFeatureId = featureId,                             │
│          GrantedAt = DateTime.Now,                              │
│          IsActive = true,                                       │
│          Notes = null  // برای چتیکا بعداً توسط Worker پر میشود │
│      });                                                        │
│  }                                                              │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                  4 UserClubFeature Records                       │
│  ┌─────────────┬──────────────┬────────────┬─────────────┐      │
│  │ ClubFeatureId │ GrantedAt   │ IsActive │ Notes       │      │
│  ├─────────────┼──────────────┼────────────┼─────────────┤      │
│  │ 1 (Chatika)  │ 2025-12-23  │ true      │ NULL → پر   │      │
│  │ 2 (Bime)     │ 2025-12-23  │ true      │ NULL        │      │
│  │ 3 (Trip)     │ 2025-12-23  │ true      │ NULL        │      │
│  │ 4 (Learn)    │ 2025-12-23  │ true      │ NULL        │      │
│  └─────────────┴──────────────┴────────────┴─────────────┘      │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼ (برای چتیکا)
┌─────────────────────────────────────────────────────────────────┐
│             ChatikaAccountActivationJob (Worker)                 │
│  - هر 5 دقیقه اجرا می‌شود                                        │
│  - کاربران با Notes = NULL و ClubFeatureId = 1 را پیدا می‌کند   │
│  - API چتیکا را کال می‌کند                                       │
│  - Notes را با توضیحات فارسی پر می‌کند                          │
└─────────────────────────────────────────────────────────────────┘

API ها

GetUserClubFeatures

دریافت لیست فیچرهای فعال کاربر:

rpc GetUserClubFeatures (GetUserClubFeaturesRequest) returns (GetUserClubFeaturesResponse);

message GetUserClubFeaturesRequest {
    int64 user_id = 1;
}

message GetUserClubFeaturesResponse {
    repeated UserClubFeatureModel features = 1;
}

message UserClubFeatureModel {
    int64 id = 1;
    int64 club_feature_id = 2;
    string feature_title = 3;
    string feature_description = 4;
    google.protobuf.Timestamp granted_at = 5;
    bool is_active = 6;
    string notes = 7;
}

ToggleUserClubFeature

فعال/غیرفعال کردن فیچر توسط ادمین:

rpc ToggleUserClubFeature (ToggleUserClubFeatureRequest) returns (ToggleUserClubFeatureResponse);

message ToggleUserClubFeatureRequest {
    int64 user_club_feature_id = 1;
    bool is_active = 2;
}

📊 Query های مفید

تعداد فیچرهای فعال هر کاربر

SELECT u.Mobile, COUNT(ucf.Id) as FeatureCount
FROM Users u
JOIN UserClubFeatures ucf ON u.Id = ucf.UserId
WHERE ucf.IsActive = 1 AND ucf.IsDeleted = 0
GROUP BY u.Mobile

کاربران بدون فیچر چتیکا فعال

SELECT u.Id, u.Mobile
FROM Users u
JOIN ClubMemberships cm ON u.Id = cm.UserId
WHERE cm.IsActive = 1
  AND NOT EXISTS (
      SELECT 1 FROM UserClubFeatures ucf 
      WHERE ucf.UserId = u.Id 
        AND ucf.ClubFeatureId = 1 
        AND ucf.IsActive = 1
  )

وضعیت فعال‌سازی چتیکا

SELECT 
    CASE WHEN Notes IS NOT NULL THEN 'Activated' ELSE 'Pending' END as Status,
    COUNT(*) as Count
FROM UserClubFeatures
WHERE ClubFeatureId = 1 AND IsDeleted = 0
GROUP BY CASE WHEN Notes IS NOT NULL THEN 'Activated' ELSE 'Pending' END

📚 مستندات مرتبط