# 🎁 Club Features System > **آخرین بروزرسانی**: ۳ دی ۱۴۰۴ (23 December 2025) > **وضعیت**: ✅ Production Ready --- ## 📋 فهرست 1. [معرفی](#معرفی) 2. [فیچرهای باشگاه](#فیچرهای-باشگاه) 3. [Entity ها](#entity-ها) 4. [Enum ClubFeatureType](#enum-clubfeaturetype) 5. [فرآیند فعال‌سازی](#فرآیند-فعال‌سازی) 6. [API ها](#api-ها) --- ## معرفی سیستم فیچرهای باشگاه مشتریان، امکانات ویژه‌ای را برای اعضای باشگاه فراهم می‌کند. هر کاربر با فعال‌سازی باشگاه، به تمام 4 فیچر دسترسی پیدا می‌کند. --- ## فیچرهای باشگاه | Id | نام | عنوان فارسی | توضیح | |----|-----|-------------|-------| | 1 | **Chatika** | چتیکا | دستیار هوش مصنوعی - حساب خودکار ایجاد می‌شود | | 2 | **Bime** | بیمه | خدمات بیمه‌ای | | 3 | **Trip** | تریپ | خدمات سفر و گردشگری | | 4 | **Learn** | لرن | آموزش و یادگیری | --- ## Entity ها ### ClubFeature (تعریف فیچرها) ```csharp 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? UserClubFeatures { get; set; } } ``` ### UserClubFeature (فیچرهای کاربر) ```csharp 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 ```sql 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` ```csharp namespace CMSMicroservice.Domain.Enums; /// /// انواع ویژگی‌های باشگاه مشتریان /// public enum ClubFeatureType { /// /// چتیکا - دستیار هوش مصنوعی /// Chatika = 1, /// /// بیمه - خدمات بیمه‌ای /// Bime = 2, /// /// تریپ - خدمات سفر و گردشگری /// Trip = 3, /// /// لرن - آموزش و یادگیری /// Learn = 4 } /// /// Extension methods برای ClubFeatureType /// public static class ClubFeatureTypeExtensions { /// /// دریافت تمام مقادیر ClubFeatureType به صورت آرایه long /// public static long[] GetAllFeatureIds() { return Enum.GetValues() .Select(f => (long)f) .ToArray(); } /// /// دریافت عنوان فارسی ویژگی /// public static string GetPersianTitle(this ClubFeatureType featureType) { return featureType switch { ClubFeatureType.Chatika => "چتیکا", ClubFeatureType.Bime => "بیمه", ClubFeatureType.Trip => "تور و سفر", ClubFeatureType.Learn => "آموزش", _ => featureType.ToString() }; } } ``` ### استفاده در کد ```csharp // ❌ قبل - 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 دریافت لیست فیچرهای فعال کاربر: ```protobuf 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 فعال/غیرفعال کردن فیچر توسط ادمین: ```protobuf rpc ToggleUserClubFeature (ToggleUserClubFeatureRequest) returns (ToggleUserClubFeatureResponse); message ToggleUserClubFeatureRequest { int64 user_club_feature_id = 1; bool is_active = 2; } ``` --- ## 📊 Query های مفید ### تعداد فیچرهای فعال هر کاربر ```sql 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 ``` ### کاربران بدون فیچر چتیکا فعال ```sql 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 ) ``` ### وضعیت فعال‌سازی چتیکا ```sql 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 ``` --- ## 📚 مستندات مرتبط - [Chatika Integration](./chatika-integration.md) - [Club Membership Migration](./club-membership-migration.md) - [Commission System](./commission-system.md)