feat: Add Chatika integration and Club Features system documentation

- Implement Chatika account activation via background job
- Create IChatikaApiService interface and its implementation
- Add Club Features system documentation detailing features and entities
- Introduce ClubFeatureType enum to replace hardcoded IDs
- Update SQL scripts for Club Membership migration
- Fix various bugs in BackOffice UI and improve Products page functionality
This commit is contained in:
masoodafar-web
2025-12-24 01:07:49 +03:30
parent 002e99f6bf
commit e7d979117c
9 changed files with 1360 additions and 37 deletions
+18 -3
View File
@@ -1,9 +1,24 @@
# BackOffice Development Plan - Network & Commission System
**Date**: 2025-12-01
**Version**: 2.3
**Date**: ۳۰ آذر ۱۴۰۴ (2025-12-20)
**Version**: 2.4
**Status**: 🟢 **Production Ready - 100% Complete**
**Last Updated**: 2025-12-01
**Last Updated**: 2025-12-20
---
## 🆕 آخرین تغییرات (۳۰ آذر ۱۴۰۴)
### رفع باگ‌های Mapster:
-`CommissionProfile.cs`: اضافه شدن mapping برای `GetUserWeeklyBalancesRequest`
-`ClubMembershipProfile.cs`: بازنویسی کامل با mappings جدید
### اضافه شدن Service Override:
-`ClubMembershipService.cs`: اضافه شدن `GetClubStatistics` override
### اضافه شدن فیلد RemainingCount:
-`CreateNewProductsCommand.cs`: اضافه شدن `RemainingCount`
-`UpdateProductsCommand.cs`: اضافه شدن `RemainingCount`
---
+424
View File
@@ -0,0 +1,424 @@
# 🤖 Chatika Integration Guide
> **آخرین بروزرسانی**: ۳ دی ۱۴۰۴ (23 December 2025)
> **وضعیت**: ✅ Production Ready
---
## 📋 فهرست
1. [معرفی](#معرفی)
2. [معماری](#معماری)
3. [API چتیکا](#api-چتیکا)
4. [پیاده‌سازی](#پیاده‌سازی)
5. [تنظیمات](#تنظیمات)
6. [نحوه کار Worker](#نحوه-کار-worker)
7. [Troubleshooting](#troubleshooting)
---
## معرفی
چتیکا یک سرویس هوش مصنوعی است که به عنوان اولین فیچر باشگاه مشتریان به کاربران ارائه می‌شود. هنگام فعال‌سازی باشگاه، به صورت خودکار یک حساب در چتیکا برای کاربر ایجاد می‌شود.
### ویژگی‌ها:
- ✅ فعال‌سازی خودکار حساب
- ✅ جلوگیری از ثبت تکراری
- ✅ Retry با Exponential Backoff
- ✅ Logging کامل
---
## معماری
```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ User Activates │───▶│ ClubMembership │───▶│ UserClubFeature │
│ Club Package │ │ (IsActive=true) │ │ (Chatika, Id=1)│
└─────────────────┘ └──────────────────┘ │ Notes = NULL │
└────────┬────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Hangfire Scheduler │
│ Cron: */5 * * * * (Every 5 minutes) │
└─────────────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ ChatikaAccountActivationJob │
│ │
│ Query: SELECT * FROM UserClubFeatures │
│ WHERE ClubFeatureId = 1 (Chatika) │
│ AND ClubMembership.IsActive = true │
│ AND Notes IS NULL │
└─────────────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ ChatikaApiService │
│ POST https://api.chatika.ir/api/v1/organizations/register-user │
│ Header: X-API-Key: {ApiKey} │
│ Body: { "mobile_number": "09123456789" } │
└─────────────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Update UserClubFeature │
│ Notes = "🎉 تبریک! حساب هوش مصنوعی چتیکا شما فعال شد..." │
│ IsActive = true │
└─────────────────────────────────────────────────────────────────┘
```
---
## API چتیکا
### Endpoint
```
POST /api/v1/organizations/register-user
```
### Headers
| Header | Value |
|--------|-------|
| `X-API-Key` | Organization API Key |
| `Content-Type` | `application/json` |
### Request Body
```json
{
"mobile_number": "09123456789"
}
```
### Success Response (200 OK)
```json
{
"id": 1,
"mobile_number": "09123456789",
"organization_id": 1,
"organization_title": "FourSat",
"wallet_balance": 100.0,
"is_new_user": true,
"credit_charged": 100.0
}
```
### Error Responses
| Status | Error Code | Description |
|--------|-----------|-------------|
| 401 | `INVALID_API_KEY` | API Key نامعتبر |
| 403 | `ORGANIZATION_DISABLED` | سازمان غیرفعال شده |
| 403 | `ORGANIZATION_EXPIRED` | سازمان منقضی شده |
| 400 | `INVALID_MOBILE_FORMAT` | فرمت شماره موبایل نامعتبر |
---
## پیاده‌سازی
### 1. Interface
**فایل**: `CMSMicroservice.Application/Common/Interfaces/IChatikaApiService.cs`
```csharp
public interface IChatikaApiService
{
Task<ChatikaAccountResult> CreateAccountAsync(
string mobileNumber,
string fullName,
CancellationToken cancellationToken = default);
}
public class ChatikaAccountResult
{
public bool IsSuccess { get; set; }
public string? ErrorMessage { get; set; }
public string? ChatikaUserId { get; set; }
public string? AccessUrl { get; set; }
public static ChatikaAccountResult Success(...) => ...;
public static ChatikaAccountResult Failure(string error) => ...;
}
```
### 2. Service Implementation
**فایل**: `CMSMicroservice.Infrastructure/Services/ChatikaApiService.cs`
```csharp
public class ChatikaApiService : IChatikaApiService
{
private readonly HttpClient _httpClient;
private readonly ILogger<ChatikaApiService> _logger;
public async Task<ChatikaAccountResult> CreateAccountAsync(
string mobileNumber,
string fullName,
CancellationToken cancellationToken = default)
{
var request = new { mobile_number = mobileNumber };
var response = await _httpClient.PostAsJsonAsync(
"/api/v1/organizations/register-user",
request,
cancellationToken);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<ChatikaRegisterResponse>();
return ChatikaAccountResult.Success(result?.Id.ToString(), "https://chatika.ir");
}
return ChatikaAccountResult.Failure($"Error: {response.StatusCode}");
}
}
```
### 3. Background Job
**فایل**: `CMSMicroservice.Infrastructure/BackgroundJobs/ChatikaAccountActivationJob.cs`
```csharp
public class ChatikaAccountActivationJob
{
private const string ChatikaFeatureDescription =
"🎉 تبریک! حساب هوش مصنوعی چتیکا شما فعال شد.\n\n" +
"برای استفاده از امکانات رایگان چتیکا:\n" +
"1️⃣ به وب‌سایت chatika.ir مراجعه کنید\n" +
"2️⃣ شماره موبایل خود را وارد کنید\n" +
"3️⃣ از دستیار هوشمند چتیکا لذت ببرید!\n\n" +
"🔗 لینک ورود: https://chatika.ir";
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
{
// 1. پیدا کردن کاربران در انتظار
var pendingUsers = await _context.UserClubFeatures
.Include(ucf => ucf.User)
.Include(ucf => ucf.ClubMembership)
.Where(ucf =>
ucf.ClubFeatureId == (long)ClubFeatureType.Chatika &&
ucf.ClubMembership.IsActive &&
!ucf.IsDeleted &&
ucf.IsActive &&
(ucf.Notes == null || ucf.Notes == ""))
.ToListAsync(cancellationToken);
// 2. پردازش هر کاربر
foreach (var userFeature in pendingUsers)
{
var user = userFeature.User;
var fullName = $"{user.FirstName} {user.LastName}".Trim();
// 3. کال API با Retry
var result = await _retryPipeline.ExecuteAsync(
async ct => await _chatikaApiService.CreateAccountAsync(
user.Mobile, fullName, ct),
cancellationToken);
// 4. آپدیت فیچر
if (result.IsSuccess)
{
userFeature.Notes = ChatikaFeatureDescription;
userFeature.IsActive = true;
await _context.SaveChangesAsync(cancellationToken);
}
}
}
}
```
---
## تنظیمات
### appsettings.json
```json
{
"Chatika": {
"BaseUrl": "https://api.chatika.ir",
"ApiKey": "YOUR_ORGANIZATION_API_KEY"
}
}
```
### DI Registration
**فایل**: `ConfigureServices.cs`
```csharp
// Chatika API Service
services.AddHttpClient<IChatikaApiService, ChatikaApiService>()
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
.ConfigureHttpClient((sp, client) =>
{
client.Timeout = TimeSpan.FromSeconds(30);
});
// Background Job
services.AddScoped<ChatikaAccountActivationJob>();
```
### Hangfire Registration
**فایل**: `Program.cs`
```csharp
// Chatika Account Activation: Every 5 minutes
recurringJobManager.AddOrUpdate<ChatikaAccountActivationJob>(
recurringJobId: "chatika-account-activation",
methodCall: job => job.ExecuteAsync(CancellationToken.None),
cronExpression: "*/5 * * * *",
options: new RecurringJobOptions { TimeZone = TimeZoneInfo.Utc });
```
---
## نحوه کار Worker
### Flowchart
```
┌──────────────────────────────────────────────────────────────┐
│ START (Every 5 min) │
└──────────────────────────┬───────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Query: Users with Chatika feature & Notes = NULL │
└──────────────────────────┬───────────────────────────────────┘
┌─────────────┐
│ Any Users? │
└──────┬──────┘
┌────────────┴────────────┐
│ NO │ YES
▼ ▼
┌──────────┐ ┌───────────────┐
│ END │ │ For each user │
└──────────┘ └───────┬───────┘
┌────────────────────┐
│ Call Chatika API │
│ (with 3x Retry) │
└────────┬───────────┘
┌─────────┴─────────┐
│ SUCCESS │ FAILURE
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Update Notes │ │ Log Warning │
│ IsActive=true │ │ Continue │
└───────────────┘ └───────────────┘
┌────────────────┐
│ Next User │
└────────────────┘
```
### Retry Policy
```csharp
// Polly Retry: 3 attempts with exponential backoff
_retryPipeline = new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(30),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true
})
.Build();
```
**Retry Timeline:**
- Attempt 1: Immediate
- Attempt 2: ~30 seconds later
- Attempt 3: ~60 seconds later
---
## Troubleshooting
### 1. API Key Invalid
**خطا**: `INVALID_API_KEY`
**راه‌حل**:
1. بررسی `appsettings.json`
2. تأیید API Key در داشبورد چتیکا
3. چک کردن header name: باید `X-API-Key` باشد
### 2. Users Not Being Processed
**علت احتمالی**:
1. `ClubMembership.IsActive = false`
2. `UserClubFeature.Notes` قبلاً پر شده
3. `ClubFeatureId != 1`
**Debug Query**:
```sql
SELECT ucf.*, u.Mobile, cm.IsActive
FROM UserClubFeatures ucf
JOIN Users u ON ucf.UserId = u.Id
JOIN ClubMemberships cm ON ucf.ClubMembershipId = cm.Id
WHERE ucf.ClubFeatureId = 1
AND ucf.IsDeleted = 0
AND (ucf.Notes IS NULL OR ucf.Notes = '')
```
### 3. Hangfire Job Not Running
**راه‌حل**:
1. چک کردن Hangfire Dashboard: `/hangfire`
2. بررسی لاگ‌ها در Seq
3. تأیید ثبت Job در `Program.cs`
### 4. Network Timeout
**علت**: سرور چتیکا در دسترس نیست
**راه‌حل**:
- Retry Policy خودکار 3 بار تلاش می‌کند
- بررسی لاگ‌ها برای خطای دقیق
- تماس با پشتیبانی چتیکا
---
## 📊 Monitoring
### Logs to Watch
```
🚀 Starting Chatika account activation job
📋 Found {Count} users pending Chatika activation
🤖 Creating Chatika account for mobile: 0912***
✅ Chatika account activated for user {UserId}
⚠️ Failed to create Chatika account for user {UserId}: {Error}
❌ Network error calling Chatika API
🏁 Chatika activation job completed. Success: {X}, Failed: {Y}
```
### Seq Query
```
ApplicationName = "CMSMicroservice" AND Message LIKE "%Chatika%"
```
---
## 📚 مستندات مرتبط
- [Club Features System](./club-features-system.md)
- [Hangfire Jobs Guide](./hangfire-jobs.md)
- [Commission System](./commission-system.md)
+340
View File
@@ -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)