Implement Persian Date Conversion and Enhance User Network Information Service
- Added PersianDateTimeService for converting Gregorian dates to Persian format in the BackOffice frontend. - Updated multiple frontend pages (Dashboard, UserPayouts, WorkerControl, UserNetworkInfo) to utilize the new Persian date service. - Enhanced GetUserNetworkPositionDto with 28+ new fields for comprehensive user network data. - Updated GetUserNetworkPositionQueryHandler to include new methods for calculating network statistics. - Modified Protobuf messages to accommodate the new fields, increasing from 14 to 42. - Refined week number calculation algorithm to ensure consistency across C# and SQL implementations. - Created new CSV and Excel files for binary plan calculations. - Ensured all changes are tested and validated for accuracy and performance.
This commit is contained in:
+122
-3
@@ -1,9 +1,122 @@
|
||||
# 🎯 اسپرینت جاری (Current Sprint)
|
||||
|
||||
**تاریخ شروع**: ۱۴ آذر ۱۴۰۴
|
||||
**آخرین بروزرسانی**: ۱۵ آذر ۱۴۰۴
|
||||
**آخرین بروزرسانی**: ۲۸ آذر ۱۴۰۴
|
||||
**مدت**: 2 هفته
|
||||
**هدف**: تکمیل FrontOffice UI و یکپارچهسازی BFF
|
||||
**هدف**: تکمیل BackOffice UI و رفع Anti-Patterns معماری
|
||||
|
||||
---
|
||||
|
||||
## 🔧 تغییرات اخیر (۲۸ آذر) - Session 2
|
||||
|
||||
### ✅ سیستم مدیریت موجودی محصولات
|
||||
**CMS Handler**: چک موجودی در `SubmitShopBuyOrderCommandHandler`
|
||||
- چک موجودی قبل از تأیید سفارش
|
||||
- کاهش `RemainingCount` و افزایش `SaleCount` بعد از پرداخت
|
||||
- پیام خطای فارسی با جزئیات محصول و تعداد
|
||||
|
||||
**FrontOffice ProductDetail**:
|
||||
- `MaxQty` داینامیک بر اساس موجودی واقعی
|
||||
- نمایش Chip موجودی (سبز/قرمز) با تعداد
|
||||
- غیرفعال کردن دکمه افزودن وقتی ناموجود
|
||||
|
||||
### ✅ ویژگیهای باشگاه (ClubFeatures) - اصلاح معماری
|
||||
**حذف فیلدها از UserClubFeature**:
|
||||
- `DetailedDescriptionHtml`, `Icon`, `Color` فقط در `ClubFeature` (جدول قالب)
|
||||
- `UserClubFeature` فقط: `IsActive`, `GrantedAt`, `Notes`
|
||||
|
||||
**فایلهای اصلاحشده**:
|
||||
- CMS: `UserClubFeatureDto`, `clubmembership.proto`, `ClubFeatureProfile`
|
||||
- BFF: `GetClubFeaturesQueryHandler`, `GetClubFeaturesResponseDto`, `configuration.proto`, `ConfigurationProfile`
|
||||
- FrontOffice: `ClubConfigurationService`, `FeaturesPage`
|
||||
|
||||
**MembershipPage - مزایای عضویت**:
|
||||
1. شارژ ۵۶ میلیون تومان کیف پول فروشگاه تخفیفی
|
||||
2. عضویت در شبکه بازاریابی و دریافت پورسانت
|
||||
3. امکان جذب زیرمجموعه
|
||||
|
||||
### ✅ رفع باگ مدال آدرسها
|
||||
- **Snackbar تکراری**: حذف inject از code-behind (global در `_Imports.razor`)
|
||||
- **NullReferenceException**: null check برای `dialog.Result`
|
||||
|
||||
### ✅ VAT و Cart Services
|
||||
- **VATService**: نرخ پیشفرض 9.99% برای debug
|
||||
- **CartService**: `EnsureInitializedAsync` + `IsAuthenticatedAsync` برای لود فقط برای کاربران لاگینشده
|
||||
|
||||
---
|
||||
|
||||
## 🔧 تغییرات قبلی (۲۸ آذر) - Session 1
|
||||
|
||||
### ✅ نمودار درختی شبکه در FrontOffice با d3-org-chart
|
||||
**کتابخانهها**: d3-org-chart v3 + d3.js v7 + d3-flextree v2.1.2
|
||||
**ویژگیها**:
|
||||
- نمایش درختی باینری شبکه
|
||||
- کلیک روی نود → نمایش درخت زیرمجموعه
|
||||
- دکمه بازگشت به نود قبلی
|
||||
- دکمه "درخت من" برای بازگشت به درخت کاربر
|
||||
- انتخاب عمق درخت با MudSelect (2-10 سطح)
|
||||
- FitToScreen برای تناسب با صفحه
|
||||
- Responsive با MudBlazor components
|
||||
|
||||
### ✅ API جدید GetSubordinateTree
|
||||
**Proto**: `GetSubordinateTreeRequest` با `target_user_id` و `max_depth`
|
||||
**BFF Handler**: `GetSubordinateTreeQueryHandler.cs`
|
||||
**Frontend Service**: `GetSubordinateTreeAsync(targetUserId, maxDepth)`
|
||||
**امنیت**: JWT Authentication (بدون check سنگین subordinate)
|
||||
|
||||
### ✅ رفع مشکل Encoding فارسی در Geography
|
||||
**Entity های تغییر یافته**: Country, State, City
|
||||
**تغییرات Configuration**: `NVARCHAR` با `Persian_100_CI_AI` collation
|
||||
**Migration**: `FixPersianCollation_Geography`
|
||||
|
||||
---
|
||||
|
||||
## 🔧 تغییرات قبلی (۱۷ آذر)
|
||||
|
||||
### ✅ رفع Anti-Pattern معماری در BackOffice.BFF
|
||||
**مشکل**: BackOffice.BFF.WebApi از پکیجهای Protobuf مربوط به CMS استفاده میکرد
|
||||
**راهحل**:
|
||||
- ساخت Protobuf های اختصاصی BackOffice.BFF (ClubMembership, Commission, Configuration, NetworkMembership)
|
||||
- تغییر namespace از `CMSMicroservice` به `Foursat.BackOffice.BFF.*`
|
||||
- تغییر GrpcServices از "Client" به "Both" (برای پشتیبانی هم از Server و هم Client)
|
||||
- نسخههای منتشر شده: 0.0.6 (ClubMembership, Commission, NetworkMembership), 1.0.6 (Configuration)
|
||||
|
||||
### ✅ اضافه شدن HTTP Annotations به Protobuf
|
||||
- افزودن `google/api/annotations.proto` به 4 پروژه Protobuf
|
||||
- پیادهسازی HTTP endpoints برای Swagger: 33 endpoint
|
||||
- ClubMembership: 7 endpoints
|
||||
- Commission: 14 endpoints
|
||||
- Configuration: 5 endpoints
|
||||
- NetworkMembership: 7 endpoints
|
||||
- نصب `Google.Api.CommonProtos v2.10.0`
|
||||
|
||||
### ✅ رفع مشکل Mapster با Immutable Types
|
||||
- ساخت `NetworkMembershipProfile.cs` با استفاده از `MapWith()`
|
||||
- مپینگ دستی برای `RepeatedField` و `Timestamp`
|
||||
- رفع خطای "Cannot convert immutable type"
|
||||
|
||||
### ✅ پشتیبانی از Multi-Role Authorization
|
||||
- تغییر `AuthorizationService` برای خواندن چندین رول از JWT
|
||||
- اضافه شدن متد `GetUserRolesAsync()`
|
||||
- استفاده از `user.FindAll(ClaimTypes.Role)` بجای `FindFirst`
|
||||
- پشتیبانی از رولهای آرایهای در `ApiAuthenticationStateProvider`
|
||||
|
||||
### ✅ نمایش درختی شبکه (Network Tree Visualization)
|
||||
- پیادهسازی درخت تعاملی با D3.js v7
|
||||
- ویژگیهای درخت:
|
||||
- Zoom & Pan با mouse/touch
|
||||
- دکمه Reset برای بازگشت به حالت اولیه
|
||||
- رنگبندی: سبز (فعال), قرمز (غیرفعال), سبز (چپ), نارنجی (راست)
|
||||
- کلیک روی node برای بارگذاری درخت آن کاربر
|
||||
- Responsive با viewBox و preserveAspectRatio
|
||||
- اضافه شدن `UserAutoComplete` برای جستجوی کاربر
|
||||
- جستجوی همزمان در Mobile, FirstName, LastName, NationalCode
|
||||
- نمایش نام + موبایل در لیست
|
||||
|
||||
### ✅ رفع مشکلات UI
|
||||
- رفع NullReferenceException در `NetworkTreeViewer` (JoinedAt null check)
|
||||
- رفع timing issue در render درخت (StateHasChanged + Task.Delay)
|
||||
- رفع خطای JSInterop با استفاده از `setDotNetReference`
|
||||
|
||||
---
|
||||
|
||||
@@ -42,10 +155,16 @@ GitLab Registry: `https://git.afrino.co/api/packages/FourSat/nuget/index.json`
|
||||
- ✅ **CMS Microservice**: ~98% Complete
|
||||
- ✅ **Daya Loan Integration**: 100% Complete (Real API implemented - Dec 6, 2025)
|
||||
- ✅ **BackOffice.BFF**: 100% Complete (35+ Handlers)
|
||||
- ✅ **Architecture Fixed**: Anti-pattern با CMS Protobuf رفع شد
|
||||
- ✅ **HTTP Annotations**: 33 endpoint با Swagger support
|
||||
- ✅ **Mapster Profiles**: NetworkMembership, Products با MapWith()
|
||||
- ✅ **FrontOffice.BFF**: 98% Complete (همه سرویسهای مورد نیاز مشتری پیادهسازی شده)
|
||||
|
||||
### Frontend:
|
||||
- ✅ **BackOffice UI**: ~95% Complete (60+ صفحه)
|
||||
- ✅ **BackOffice UI**: ~97% Complete (65+ صفحه)
|
||||
- ✅ **Multi-Role Authorization**: پشتیبانی از چندین نقش همزمان
|
||||
- ✅ **Network Tree Visualization**: نمایش درختی تعاملی با D3.js
|
||||
- ✅ **User AutoComplete**: جستجوی پیشرفته کاربران
|
||||
- ✅ **FrontOffice UI**: **98% Complete** (تمام صفحات مورد نیاز مشتری پیادهسازی شده)
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,617 @@
|
||||
# 📋 Task List - توضیحات جدید بیزینس 2025-12-08
|
||||
|
||||
**تاریخ ایجاد**: 2025-12-08
|
||||
**آخرین بهروزرسانی**: 2025-12-09
|
||||
**منبع**: تحلیل توضیحات شفاهی جدید بیزینس
|
||||
**وضعیت**: ✅ Task #0 Complete, بقیه آماده برای اجرا
|
||||
|
||||
---
|
||||
|
||||
## ✅ Completed Tasks
|
||||
|
||||
### ~~Task #0: اصلاح محاسبات تعادل و فلش~~ ✅
|
||||
|
||||
**شرح**:
|
||||
کد محاسبه تعادل با توضیحات دقیق بیزینس تطبیق داده شد.
|
||||
|
||||
**انجام شده**:
|
||||
- ✅ ترتیب محاسبات اصلاح شد (تعادل → باقیمانده → سقف → فلش)
|
||||
- ✅ فلش از هر دو طرف محاسبه میشود
|
||||
- ✅ باقیمانده جداگانه ذخیره میشود (چپ و راست)
|
||||
- ✅ Documentation بهروزرسانی شد
|
||||
- ✅ مثالهای 5 لول عمقی اضافه شد
|
||||
|
||||
**فایلهای تغییر یافته**:
|
||||
```
|
||||
CMS/src/.../CalculateWeeklyBalancesCommandHandler.cs (اصلاح شد)
|
||||
totalDoc/01-BUSINESS/balance-calculation-rules.md (بهروزرسانی شد)
|
||||
totalDoc/01-BUSINESS/balance-calculation-examples-5-levels.md (جدید)
|
||||
```
|
||||
|
||||
**تاریخ اتمام**: 2025-12-09
|
||||
|
||||
---
|
||||
|
||||
## 🔥 Priority 1: Critical Tasks
|
||||
|
||||
### Task #1: پیادهسازی Worker حذف خودکار کاربران غیرفعال
|
||||
|
||||
**شرح**:
|
||||
کاربرانی که تا 2 هفته بعد از ثبت نام هیچکدام از موارد زیر را انجام ندادند باید به صورت خودکار حذف شوند:
|
||||
- وام دایا نگرفتند
|
||||
- پرداخت مستقیم 56 میلیون نکردند
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] Worker روزانه یک بار اجرا شود (مثلاً ساعت 3 صبح)
|
||||
- [ ] کاربرانی با `CreatedAt < Now - 14 days` و `IsActive = false` و `ClubMembershipId = null` شناسایی شوند
|
||||
- [ ] کاربر به صورت Soft Delete حذف شود (یا Hard Delete بر اساس تصمیم)
|
||||
- [ ] جایگاه شبکه (Network Position) آزاد شود
|
||||
- [ ] معرف (Parent) بتواند دوباره کاربر جدید جذب کند
|
||||
- [ ] Log کامل عملیات حذف ثبت شود
|
||||
|
||||
**فایلهای نیاز به ایجاد/تغییر**:
|
||||
```
|
||||
CMS/src/CMSMicroservice.WebApi/BackgroundWorkers/
|
||||
└── DeleteInactiveUsersJob.cs (جدید)
|
||||
|
||||
CMS/src/CMSMicroservice.Application/UserCQ/Commands/
|
||||
└── DeleteInactiveUser/
|
||||
├── DeleteInactiveUserCommand.cs (جدید)
|
||||
└── DeleteInactiveUserCommandHandler.cs (جدید)
|
||||
|
||||
CMS/src/CMSMicroservice.WebApi/Program.cs
|
||||
└── services.AddHostedService<DeleteInactiveUsersJob>();
|
||||
```
|
||||
|
||||
**کد پیشنهادی**:
|
||||
```csharp
|
||||
public class DeleteInactiveUsersJob : BackgroundService
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger<DeleteInactiveUsersJob> _logger;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
// محاسبه زمان اجرا (3 صبح)
|
||||
var now = DateTime.Now;
|
||||
var next3AM = now.Date.AddDays(1).AddHours(3);
|
||||
var delay = next3AM - now;
|
||||
|
||||
await Task.Delay(delay, stoppingToken);
|
||||
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var context = scope.ServiceProvider.GetRequiredService<IApplicationDbContext>();
|
||||
|
||||
var twoWeeksAgo = DateTime.Now.AddDays(-14);
|
||||
|
||||
var inactiveUsers = await context.Users
|
||||
.Where(u => u.Created < twoWeeksAgo
|
||||
&& u.ClubMembershipId == null
|
||||
&& !u.IsActive)
|
||||
.ToListAsync(stoppingToken);
|
||||
|
||||
_logger.LogInformation($"🧹 حذف {inactiveUsers.Count} کاربر غیرفعال بیش از 2 هفته");
|
||||
|
||||
foreach (var user in inactiveUsers)
|
||||
{
|
||||
// حذف کاربر
|
||||
user.IsDeleted = true; // Soft Delete
|
||||
user.DeletedAt = DateTime.Now;
|
||||
|
||||
// آزادسازی جایگاه شبکه
|
||||
// (NetworkParentId را null نکنید چون تاریخچه نیاز دارد)
|
||||
|
||||
_logger.LogWarning($"❌ حذف کاربر: {user.Id} - {user.UserName}");
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync(stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**تست**:
|
||||
1. کاربر جدید با `CreatedAt = DateTime.Now.AddDays(-15)` ایجاد کنید
|
||||
2. `IsActive = false`, `ClubMembershipId = null`
|
||||
3. Worker را مجبور به اجرا کنید (یا زمان را تغییر دهید)
|
||||
4. چک کنید: `user.IsDeleted = true`
|
||||
|
||||
**تخمین زمان**: 4-6 ساعت
|
||||
|
||||
---
|
||||
|
||||
### Task #2: الزامی کردن دیالوگ باشگاه مشتریان
|
||||
|
||||
**شرح**:
|
||||
بعد از پرداخت موفق 56 میلیون تومان، کاربر **باید** قرارداد باشگاه مشتریان را امضا کند. تا زمانی که امضا نکند، نمیتواند به سایر بخشهای سیستم دسترسی داشته باشد و لینک معرفی خود را ببیند.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] بعد از تأیید پرداخت، Modal/Dialog باشگاه مشتریان باز شود
|
||||
- [ ] دکمه Close غیرفعال باشد (یا Modal با `disableBackdropClick` باز شود)
|
||||
- [ ] کاربر نتواند از دیالوگ خارج شود (ESC هم کار نکند)
|
||||
- [ ] بعد از امضای قرارداد:
|
||||
- `ClubMembership` record ایجاد شود
|
||||
- `User.ClubMembershipId` Set شود
|
||||
- 25 میلیون تومان به `WeeklyCommissionPool` اضافه شود
|
||||
- [ ] بعد از امضا، redirect به Dashboard
|
||||
- [ ] در Dashboard لینک معرفی نمایش داده شود
|
||||
|
||||
**فایلهای نیاز به تغییر**:
|
||||
```
|
||||
FrontOffice/src/FrontOffice.Main/Pages/
|
||||
└── Payment/PaymentSuccess.razor
|
||||
└── Payment/PaymentSuccess.razor.cs
|
||||
|
||||
FrontOffice/src/FrontOffice.Main/Components/
|
||||
└── ClubMembershipDialog.razor (جدید یا اصلاح)
|
||||
|
||||
CMS/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/
|
||||
└── CreateClubMembership/
|
||||
├── CreateClubMembershipCommand.cs
|
||||
└── CreateClubMembershipCommandHandler.cs
|
||||
```
|
||||
|
||||
**کد پیشنهادی (Frontend)**:
|
||||
```razor
|
||||
@* PaymentSuccess.razor *@
|
||||
@if (_showClubDialog)
|
||||
{
|
||||
<MudDialog @bind-IsVisible="_showClubDialog"
|
||||
Options="@(new DialogOptions {
|
||||
DisableBackdropClick = true,
|
||||
CloseButton = false
|
||||
})">
|
||||
<DialogContent>
|
||||
<h3>عضویت در باشگاه مشتریان</h3>
|
||||
<p>برای ادامه، لطفاً قرارداد باشگاه مشتریان را مطالعه و امضا کنید.</p>
|
||||
|
||||
<MudPaper Class="pa-4 my-4" Elevation="2">
|
||||
<p>متن قرارداد...</p>
|
||||
</MudPaper>
|
||||
|
||||
<MudCheckBox @bind-Checked="_agreedToTerms">
|
||||
متن قرارداد را مطالعه کردم و با آن موافقم
|
||||
</MudCheckBox>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
Disabled="!_agreedToTerms"
|
||||
OnClick="SignContract">
|
||||
امضای قرارداد
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// PaymentSuccess.razor.cs
|
||||
private bool _showClubDialog = false;
|
||||
private bool _agreedToTerms = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// بعد از تأیید پرداخت
|
||||
if (PaymentConfirmed && !User.ClubMembershipId.HasValue)
|
||||
{
|
||||
_showClubDialog = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SignContract()
|
||||
{
|
||||
var request = new CreateClubMembershipRequest
|
||||
{
|
||||
UserId = User.Id,
|
||||
InitialContribution = 25000000
|
||||
};
|
||||
|
||||
await ClubMembershipContract.CreateClubMembershipAsync(request);
|
||||
|
||||
_showClubDialog = false;
|
||||
NavigationManager.NavigateTo("/dashboard");
|
||||
}
|
||||
```
|
||||
|
||||
**تست**:
|
||||
1. پرداخت 56M انجام دهید
|
||||
2. بعد از موفقیت، باید Dialog باز شود
|
||||
3. سعی کنید Close کنید → نشود
|
||||
4. بدون tick نزدن → دکمه غیرفعال باشد
|
||||
5. tick بزنید و امضا کنید → redirect به Dashboard
|
||||
6. لینک معرفی نمایش داده شود
|
||||
|
||||
**تخمین زمان**: 6-8 ساعت
|
||||
|
||||
---
|
||||
|
||||
### Task #3: شرط نمایش لینک معرفی
|
||||
|
||||
**شرح**:
|
||||
لینک معرفی فقط باید برای کاربرانی نمایش داده شود که:
|
||||
1. پرداخت کردهاند (`IsActive = true`)
|
||||
2. عضو باشگاه مشتریان شدهاند (`ClubMembershipId != null`)
|
||||
3. عضویت باشگاه فعال است (`ClubMembership.IsActive = true`)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] در صفحه Dashboard یا Profile، شرط بالا چک شود
|
||||
- [ ] اگر شرایط برقرار نیست:
|
||||
- پیغام: "برای دریافت لینک معرفی، ابتدا عضو باشگاه مشتریان شوید"
|
||||
- دکمه "عضویت در باشگاه" (در صورت عدم عضویت)
|
||||
- [ ] اگر شرایط برقرار است:
|
||||
- لینک معرفی نمایش داده شود
|
||||
- دکمه کپی
|
||||
- QR Code (اختیاری)
|
||||
|
||||
**فایلهای نیاز به تغییر**:
|
||||
```
|
||||
FrontOffice/src/FrontOffice.Main/Pages/
|
||||
└── Dashboard/Dashboard.razor
|
||||
└── Dashboard/Dashboard.razor.cs
|
||||
|
||||
یا
|
||||
|
||||
FrontOffice/src/FrontOffice.Main/Pages/
|
||||
└── Profile/MyProfile.razor
|
||||
```
|
||||
|
||||
**کد پیشنهادی**:
|
||||
```razor
|
||||
@if (CanShowReferralLink)
|
||||
{
|
||||
<MudCard Class="my-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">🔗 لینک معرفی شما</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudTextField @bind-Value="_referralLink"
|
||||
ReadOnly="true"
|
||||
Adornment="Adornment.End"
|
||||
AdornmentIcon="@Icons.Material.Filled.ContentCopy"
|
||||
OnAdornmentClick="CopyLink"/>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning" Class="my-4">
|
||||
برای دریافت لینک معرفی، ابتدا عضو باشگاه مشتریان شوید.
|
||||
@if (!User.ClubMembershipId.HasValue)
|
||||
{
|
||||
<MudButton Color="Color.Primary"
|
||||
Variant="Variant.Filled"
|
||||
Class="mt-2"
|
||||
OnClick="OpenClubDialog">
|
||||
عضویت در باشگاه
|
||||
</MudButton>
|
||||
}
|
||||
</MudAlert>
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
private bool CanShowReferralLink =>
|
||||
User.IsActive
|
||||
&& User.ClubMembershipId.HasValue
|
||||
&& User.ClubMembership?.IsActive == true;
|
||||
```
|
||||
|
||||
**تست**:
|
||||
1. کاربر بدون `ClubMembership` → Alert نمایش داده شود
|
||||
2. کاربر با `ClubMembership` فعال → لینک نمایش داده شود
|
||||
3. دکمه کپی کار کند
|
||||
|
||||
**تخمین زمان**: 3-4 ساعت
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Priority 2: Medium Tasks
|
||||
|
||||
### Task #4: Validation دقیقتر محدودیت 2 فرزند فعال
|
||||
|
||||
**شرح**:
|
||||
در هنگام ثبت نام با کد معرف، باید بررسی شود که آیا Parent حداکثر **2 فرزند فعال** دارد یا نه (نه فقط 2 فرزند).
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] Validation در `CreateUserCommandHandler` یا `NetworkPlacementService`
|
||||
- [ ] شمارش فرزندان با شرط:
|
||||
```csharp
|
||||
u.NetworkParentId == parentId
|
||||
&& u.IsActive
|
||||
&& u.ClubMembershipId != null
|
||||
```
|
||||
- [ ] اگر `activeChildCount >= 2`:
|
||||
- Exception: "این کاربر تعداد زیرمجموعههاش پر شده و شما نمیتونید جزو زیرمجموعه این آدم بشید"
|
||||
- یا Auto-Placement (بر اساس تصمیم)
|
||||
|
||||
**فایلهای نیاز به تغییر**:
|
||||
```
|
||||
CMS/src/CMSMicroservice.Application/Services/
|
||||
└── NetworkPlacementService.cs
|
||||
|
||||
CMS/src/CMSMicroservice.Application/UserCQ/Commands/CreateUser/
|
||||
└── CreateUserCommandHandler.cs
|
||||
└── CreateUserCommandValidator.cs
|
||||
```
|
||||
|
||||
**کد پیشنهادی**:
|
||||
```csharp
|
||||
public async Task<NetworkLeg?> CalculateLegPositionAsync(long parentId, CancellationToken cancellationToken)
|
||||
{
|
||||
var activeChildrenCount = await _context.Users
|
||||
.CountAsync(u => u.NetworkParentId == parentId
|
||||
&& u.IsActive
|
||||
&& u.ClubMembershipId != null,
|
||||
cancellationToken);
|
||||
|
||||
if (activeChildrenCount >= 2)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"این کاربر تعداد زیرمجموعههاش پر شده و شما نمیتونید جزو زیرمجموعه این آدم بشید");
|
||||
}
|
||||
|
||||
// بررسی Left و Right
|
||||
var hasLeft = await _context.Users
|
||||
.AnyAsync(u => u.NetworkParentId == parentId
|
||||
&& u.LegPosition == NetworkLeg.Left
|
||||
&& u.IsActive
|
||||
&& u.ClubMembershipId != null,
|
||||
cancellationToken);
|
||||
|
||||
if (!hasLeft) return NetworkLeg.Left;
|
||||
|
||||
var hasRight = await _context.Users
|
||||
.AnyAsync(u => u.NetworkParentId == parentId
|
||||
&& u.LegPosition == NetworkLeg.Right
|
||||
&& u.IsActive
|
||||
&& u.ClubMembershipId != null,
|
||||
cancellationToken);
|
||||
|
||||
if (!hasRight) return NetworkLeg.Right;
|
||||
|
||||
return null; // هر دو پر است
|
||||
}
|
||||
```
|
||||
|
||||
**تست**:
|
||||
1. Parent با 2 فرزند فعال
|
||||
2. ثبت نام با کد این Parent
|
||||
3. باید Exception بیاید
|
||||
|
||||
**تخمین زمان**: 3-4 ساعت
|
||||
|
||||
---
|
||||
|
||||
### Task #5: بهبود پیغام خطای کد معرف پر
|
||||
|
||||
**شرح**:
|
||||
در صفحه ثبت نام، اگر کاربر کد معرفی وارد کند که ظرفیتش پر است، باید پیغام خطای واضح و فارسی نمایش داده شود.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] در Frontend، بعد از وارد کردن کد معرف، validation شود
|
||||
- [ ] اگر کد پر بود، پیغام:
|
||||
> "این کد معرف ظرفیتش پر شده، لطفا از کد معرف دیگری استفاده کنید"
|
||||
- [ ] Snackbar یا Alert با Severity.Warning
|
||||
- [ ] فیلد کد معرف هایلایت شود (قرمز)
|
||||
|
||||
**فایلهای نیاز به تغییر**:
|
||||
```
|
||||
FrontOffice/src/FrontOffice.Main/Pages/
|
||||
└── Register.razor
|
||||
└── Register.razor.cs
|
||||
```
|
||||
|
||||
**کد پیشنهادی**:
|
||||
```csharp
|
||||
private async Task ValidateReferralCode()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_referralCode))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var request = new ValidateReferralCodeRequest
|
||||
{
|
||||
ReferralCode = _referralCode
|
||||
};
|
||||
|
||||
var response = await UserContract.ValidateReferralCodeAsync(request);
|
||||
|
||||
if (!response.IsValid)
|
||||
{
|
||||
_referralCodeError = "کد معرف نامعتبر است";
|
||||
}
|
||||
else if (response.IsFull)
|
||||
{
|
||||
_referralCodeError = "این کد معرف ظرفیتش پر شده، لطفا از کد معرف دیگری استفاده کنید";
|
||||
Snackbar.Add(_referralCodeError, Severity.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_referralCodeError = "خطا در بررسی کد معرف";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**تست**:
|
||||
1. Parent پر را پیدا کنید
|
||||
2. کد معرف او را در Register وارد کنید
|
||||
3. پیغام واضح نمایش داده شود
|
||||
|
||||
**تخمین زمان**: 2-3 ساعت
|
||||
|
||||
---
|
||||
|
||||
## 📝 Priority 3: Documentation Tasks
|
||||
|
||||
### Task #6: بهروزرسانی مستندات
|
||||
|
||||
**شرح**:
|
||||
با توجه به توضیحات جدید، داکیومنتهای زیر باید Update شوند.
|
||||
|
||||
**فایلهای نیاز به تغییر**:
|
||||
|
||||
#### 1. `totalDoc/01-BUSINESS/network-commission-system.md`
|
||||
```markdown
|
||||
# اضافه کردن بخش جدید:
|
||||
|
||||
## ۱۰. حذف خودکار کاربران غیرفعال
|
||||
|
||||
کاربرانی که تا 2 هفته بعد از ثبت نام:
|
||||
- وام دایا نگرفتهاند
|
||||
- پرداخت مستقیم 56 میلیون نکردهاند
|
||||
|
||||
به صورت خودکار حذف میشوند.
|
||||
|
||||
**Worker**: `DeleteInactiveUsersJob`
|
||||
**زمان اجرا**: روزانه ساعت 3 صبح
|
||||
**منطق**: `CreatedAt < Now - 14 days && !IsActive && ClubMembershipId == null`
|
||||
|
||||
---
|
||||
|
||||
## ۱۱. شرایط نمایش لینک معرفی
|
||||
|
||||
لینک معرفی فقط برای کاربرانی نمایش داده میشود که:
|
||||
1. پرداخت کردهاند (IsActive = true)
|
||||
2. عضو باشگاه مشتریان شدهاند (ClubMembershipId != null)
|
||||
3. عضویت باشگاه فعال است (ClubMembership.IsActive = true)
|
||||
|
||||
**تا زمانی که این شرایط برقرار نباشد، کاربر نمیتواند لینک معرفی خود را ببیند.**
|
||||
|
||||
---
|
||||
|
||||
## ۱۲. الزامی بودن دیالوگ باشگاه مشتریان
|
||||
|
||||
بعد از پرداخت موفق 56 میلیون تومان، کاربر **باید** قرارداد باشگاه مشتریان را امضا کند.
|
||||
|
||||
**فرآیند**:
|
||||
1. پرداخت موفق
|
||||
2. Dialog باشگاه مشتریان باز میشود
|
||||
3. کاربر نمیتواند Dialog را ببندد
|
||||
4. باید قرارداد را بخواند و امضا کند
|
||||
5. بعد از امضا → redirect به Dashboard
|
||||
6. لینک معرفی نمایش داده میشود
|
||||
```
|
||||
|
||||
#### 2. `totalDoc/01-BUSINESS/binary-tree-guide.md`
|
||||
```markdown
|
||||
# اصلاح بخش Validation:
|
||||
|
||||
### محدودیت 2 فرزند **فعال**
|
||||
|
||||
هر Parent فقط میتواند **2 فرزند فعال** داشته باشد.
|
||||
|
||||
**تعریف فعال**:
|
||||
- IsActive = true
|
||||
- ClubMembershipId != null
|
||||
- عضویت باشگاه فعال است
|
||||
|
||||
**نکته مهم**: کاربرانی که ثبت نام کردهاند اما هنوز فعال نشدهاند، در شمارش 2 فرزند محسوب نمیشوند.
|
||||
```
|
||||
|
||||
#### 3. `totalDoc/03-BACKEND/CMS/implementation-status.md`
|
||||
```markdown
|
||||
# افزودن به بخش Background Workers:
|
||||
|
||||
### ✅ DeleteInactiveUsersWorker (NEW - 2025-12-08)
|
||||
|
||||
**وضعیت**: 🔴 نیاز به پیادهسازی
|
||||
|
||||
**شرح**: حذف خودکار کاربران غیرفعال بعد از 2 هفته
|
||||
|
||||
**منطق**:
|
||||
- روزانه ساعت 3 صبح اجرا میشود
|
||||
- کاربرانی که `CreatedAt < Now - 14 days`
|
||||
- و `IsActive = false`
|
||||
- و `ClubMembershipId = null`
|
||||
- به صورت Soft Delete حذف میشوند
|
||||
|
||||
**فایل**: `CMS/BackgroundWorkers/DeleteInactiveUsersJob.cs`
|
||||
|
||||
**Dependencies**:
|
||||
- IApplicationDbContext
|
||||
- ILogger
|
||||
```
|
||||
|
||||
#### 4. `totalDoc/05-TASKS/BACKLOG.md`
|
||||
```markdown
|
||||
# اضافه کردن این 5 Task به Backlog
|
||||
|
||||
## 🔥 Critical
|
||||
|
||||
- [ ] Task #1: پیادهسازی DeleteInactiveUsersWorker (6h)
|
||||
- [ ] Task #2: الزامی کردن دیالوگ باشگاه (8h)
|
||||
- [ ] Task #3: شرط نمایش لینک معرفی (4h)
|
||||
|
||||
## ⚠️ Medium
|
||||
|
||||
- [ ] Task #4: Validation 2 فرزند فعال (4h)
|
||||
- [ ] Task #5: پیغام خطای کد معرف پر (3h)
|
||||
|
||||
## 📝 Low
|
||||
|
||||
- [ ] Task #6: Update Documentation (2h)
|
||||
|
||||
**زمان کل**: 27 ساعت (~4 روز کاری)
|
||||
```
|
||||
|
||||
**تخمین زمان**: 2-3 ساعت
|
||||
|
||||
---
|
||||
|
||||
## 📊 خلاصه Task ها
|
||||
|
||||
| # | عنوان | Priority | زمان | وضعیت |
|
||||
|---|--------|----------|------|--------|
|
||||
| 1 | DeleteInactiveUsersWorker | 🔥 Critical | 6h | ⬜ Todo |
|
||||
| 2 | الزامی دیالوگ باشگاه | 🔥 Critical | 8h | ⬜ Todo |
|
||||
| 3 | شرط لینک معرفی | 🔥 Critical | 4h | ⬜ Todo |
|
||||
| 4 | Validation 2 فرزند فعال | ⚠️ Medium | 4h | ⬜ Todo |
|
||||
| 5 | پیغام کد معرف پر | ⚠️ Medium | 3h | ⬜ Todo |
|
||||
| 6 | Update Documentation | 📝 Low | 3h | ⬜ Todo |
|
||||
|
||||
**مجموع زمان**: 28 ساعت (~4 روز کاری)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 پلان اجرا (پیشنهادی)
|
||||
|
||||
### روز 1 (8 ساعت):
|
||||
- [ ] Task #1: DeleteInactiveUsersWorker (6h)
|
||||
- [ ] شروع Task #2 (2h)
|
||||
|
||||
### روز 2 (8 ساعت):
|
||||
- [ ] ادامه Task #2: Dialog الزامی (6h)
|
||||
- [ ] شروع Task #3 (2h)
|
||||
|
||||
### روز 3 (8 ساعت):
|
||||
- [ ] ادامه Task #3: شرط لینک (2h)
|
||||
- [ ] Task #4: Validation (4h)
|
||||
- [ ] شروع Task #5 (2h)
|
||||
|
||||
### روز 4 (4 ساعت):
|
||||
- [ ] ادامه Task #5 (1h)
|
||||
- [ ] Task #6: Documentation (3h)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Definition of Done
|
||||
|
||||
هر Task زمانی Complete حساب میشود که:
|
||||
1. ✅ کد نوشته شده و Build موفق
|
||||
2. ✅ Unit Test / Manual Test انجام شده
|
||||
3. ✅ Code Review شده
|
||||
4. ✅ Documentation بهروز شده
|
||||
5. ✅ Merge به Main Branch
|
||||
|
||||
---
|
||||
|
||||
**تهیهکننده**: AI Assistant
|
||||
**تاریخ**: 2025-12-08
|
||||
**نسخه**: 1.0
|
||||
Reference in New Issue
Block a user