This commit is contained in:
masoodafar-web
2026-01-03 18:27:49 +03:30
parent 0369292d7f
commit 5965b98728
156 changed files with 16082 additions and 0 deletions
@@ -0,0 +1,340 @@
# 🎁 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<UserClubFeature>? 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;
/// <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()
};
}
}
```
### استفاده در کد
```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)