Files
docs/03-BACKEND/CMS/MANUAL-CLUB-MEMBERSHIP-TASKS.md
T
masoodafar-web 73e1971cc3 feat: Implement Discount Shop Completion Plan with Product Image Gallery, Admin APIs, VAT Calculation, and Sales Reports
- Added DiscountProductImage entity and related configurations for product image gallery.
- Created commands and queries for managing product images.
- Developed GetAllDiscountOrders API for admin order management with various filters.
- Implemented VAT calculation service and integrated it into order processing.
- Created Sales Reports API with support for daily, weekly, and monthly reports.
- Completed gRPC services for BackOffice.BFF to expose new APIs.
- Updated Proto files and project references accordingly.
2026-01-02 00:46:08 +03:30

408 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# عضویت دستی باشگاه مشتریان - Manual Club Membership
## 📋 خلاصه نیازمندی
ادمین بتواند برای یک کاربر **عضویت دستی باشگاه مشتریان** ایجاد کند که:
- کیف پول با **56 میلیون (Balance)** + **112 میلیون (DiscountBalance)** شارژ شود
- تراکنش و لاگ کیف پول ثبت شود
- فیلد `User.PackagePurchaseMethod = DirectPurchase` تنظیم شود
- مسیر تصویر فیش واریزی ذخیره شود
- بدون نیاز به تایید دو مرحله‌ای (ادمین ایجاد می‌کند = تایید شده)
---
## 🔢 فرمول‌های محاسبه
```
BasePackageAmount = 56,000,000 ریال (SystemConstants)
Balance (شارژ اصلی) = BasePackageAmount = 56M
DiscountBalance (تخفیف) = BasePackageAmount × 2 = 112M
مجموع شارژ = 56M + 112M = 168M ریال
```
---
## 📁 فایل‌های مورد نیاز برای تغییر
| # | فایل | نوع تغییر | اولویت |
|---|------|----------|--------|
| 1 | `ManualPayment.cs` | اضافه کردن `ImagePath` | بالا |
| 2 | `CreateManualPaymentCommand.cs` | اضافه کردن `ImagePath` | بالا |
| 3 | `manualpayment.proto` (CMS) | اضافه کردن `image_path` | بالا |
| 4 | `manualpayment.proto` (BFF) | اضافه کردن `image_path` | بالا |
| 5 | `CreateManualPaymentCommandHandler.cs` (CMS) | بازنویسی کامل | بالا |
| 6 | `CreateManualPaymentCommandHandler.cs` (BFF) | اضافه کردن `ImagePath` | متوسط |
| 7 | **جدید:** `GetManualMembershipPaymentsQuery` | Query برای لیست | کم |
---
## ✅ تسک 1: اضافه کردن ImagePath به Entity
**فایل:** `CMS/src/CMSMicroservice.Domain/Entities/Payment/ManualPayment.cs`
**تغییر:** بعد از `ReferenceNumber` اضافه شود:
```csharp
/// <summary>
/// مسیر تصویر فیش واریزی (اختیاری)
/// </summary>
public string? ImagePath { get; set; }
```
**محل دقیق:**
```csharp
/// <summary>
/// شماره مرجع یا شماره فیش (اختیاری)
/// </summary>
public string? ReferenceNumber { get; set; }
// ⬇️ اینجا اضافه شود ⬇️
/// <summary>
/// مسیر تصویر فیش واریزی (اختیاری)
/// </summary>
public string? ImagePath { get; set; }
/// <summary>
/// وضعیت تایید
/// </summary>
public ManualPaymentStatus Status { get; set; } = ManualPaymentStatus.Pending;
```
---
## ✅ تسک 2: اضافه کردن ImagePath به Command
**فایل:** `CMS/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommand.cs`
**تغییر:** بعد از `ReferenceNumber` اضافه شود:
```csharp
/// <summary>
/// مسیر تصویر فیش واریزی (اختیاری)
/// </summary>
public string? ImagePath { get; set; }
```
---
## ✅ تسک 3: آپدیت Proto - CMS
**فایل:** `CMS/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto`
**تغییر در `CreateManualPaymentRequest`:**
```protobuf
message CreateManualPaymentRequest
{
int64 user_id = 1;
int64 amount = 2;
ManualPaymentType type = 3;
string description = 4;
google.protobuf.StringValue reference_number = 5;
google.protobuf.StringValue image_path = 6; // ⬅️ اضافه شود
}
```
**تغییر در `ManualPaymentModel`:**
```protobuf
message ManualPaymentModel
{
// ... existing fields ...
google.protobuf.Timestamp created = 19;
google.protobuf.StringValue image_path = 20; // ⬅️ اضافه شود
}
```
---
## ✅ تسک 4: آپدیت Proto - BFF
**فایل:** `BackOffice.BFF/src/Protobufs/BackOffice.BFF.ManualPayment.Protobuf/Protos/manualpayment.proto`
**همان تغییرات تسک 3**
---
## ✅ تسک 5: بازنویسی Handler (CMS) - مهم‌ترین تسک
**فایل:** `CMS/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs`
**کد جدید کامل:**
```csharp
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Common;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Entities.Payment;
using CMSMicroservice.Domain.Enums;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Application.ManualPaymentCQ.Commands.CreateManualPayment;
public class CreateManualPaymentCommandHandler : IRequestHandler<CreateManualPaymentCommand, long>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
private readonly ILogger<CreateManualPaymentCommandHandler> _logger;
public CreateManualPaymentCommandHandler(
IApplicationDbContext context,
ICurrentUserService currentUser,
ILogger<CreateManualPaymentCommandHandler> logger)
{
_context = context;
_currentUser = currentUser;
_logger = logger;
}
public async Task<long> Handle(
CreateManualPaymentCommand request,
CancellationToken cancellationToken)
{
try
{
_logger.LogInformation(
"Creating manual membership payment for UserId: {UserId}, Type: {Type}",
request.UserId,
request.Type
);
// 1. بررسی Admin فعلی
var currentUserId = _currentUser.UserId;
if (string.IsNullOrEmpty(currentUserId))
{
throw new UnauthorizedAccessException("کاربر احراز هویت نشده است");
}
if (!long.TryParse(currentUserId, out var adminUserId))
{
throw new UnauthorizedAccessException("شناسه کاربر نامعتبر است");
}
// 2. بررسی وجود کاربر
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
if (user == null)
{
_logger.LogWarning("User not found: {UserId}", request.UserId);
throw new NotFoundException(nameof(User), request.UserId);
}
// 3. پیدا کردن کیف پول
var wallet = await _context.UserWallets
.FirstOrDefaultAsync(w => w.UserId == request.UserId, cancellationToken);
if (wallet == null)
{
_logger.LogError("Wallet not found for UserId: {UserId}", request.UserId);
throw new NotFoundException($"کیف پول کاربر {request.UserId} یافت نشد");
}
// 4. محاسبه مبالغ
var balanceAmount = SystemConstants.BasePackageAmount; // 56M
var discountBalanceAmount = SystemConstants.BasePackageAmount * 2; // 112M
var totalAmount = balanceAmount + discountBalanceAmount; // 168M
// 5. ثبت تراکنش
var transaction = new Transaction
{
Amount = totalAmount,
Description = $"عضویت دستی باشگاه مشتریان - {request.Description} - مرجع: {request.ReferenceNumber}",
PaymentStatus = PaymentStatus.Success,
PaymentDate = DateTime.Now,
RefId = request.ReferenceNumber,
Type = TransactionType.DepositExternal1
};
_context.Transactions.Add(transaction);
await _context.SaveChangesAsync(cancellationToken);
// 6. ایجاد ManualPayment با وضعیت Approved (بدون نیاز به تایید دو مرحله‌ای)
var manualPayment = new ManualPayment
{
UserId = request.UserId,
Amount = totalAmount,
Type = request.Type,
Description = request.Description,
ReferenceNumber = request.ReferenceNumber,
ImagePath = request.ImagePath,
Status = ManualPaymentStatus.Approved,
RequestedBy = adminUserId,
ApprovedBy = adminUserId,
ApprovedAt = DateTime.Now,
TransactionId = transaction.Id
};
_context.ManualPayments.Add(manualPayment);
// 7. اعمال تغییرات بر کیف پول
var oldBalance = wallet.Balance;
var oldDiscountBalance = wallet.DiscountBalance;
wallet.Balance += balanceAmount; // +56M
wallet.DiscountBalance += discountBalanceAmount; // +112M
// 8. ثبت لاگ کیف پول
var walletLog = new UserWalletChangeLog
{
WalletId = wallet.Id,
CurrentBalance = wallet.Balance,
ChangeValue = balanceAmount,
CurrentNetworkBalance = wallet.NetworkBalance,
ChangeNerworkValue = 0,
CurrentDiscountBalance = wallet.DiscountBalance,
ChangeDiscountValue = discountBalanceAmount,
IsIncrease = true,
RefrenceId = transaction.Id
};
await _context.UserWalletChangeLogs.AddAsync(walletLog, cancellationToken);
// 9. تنظیم روش خرید پکیج
user.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase;
// 10. ذخیره همه تغییرات
await _context.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"Manual membership payment created successfully. " +
"ManualPaymentId: {Id}, UserId: {UserId}, TransactionId: {TransactionId}, " +
"Balance: {OldBalance} -> {NewBalance}, DiscountBalance: {OldDiscount} -> {NewDiscount}",
manualPayment.Id,
request.UserId,
transaction.Id,
oldBalance,
wallet.Balance,
oldDiscountBalance,
wallet.DiscountBalance
);
return manualPayment.Id;
}
catch (Exception ex) when (ex is not NotFoundException && ex is not UnauthorizedAccessException)
{
_logger.LogError(
ex,
"Error creating manual membership payment for UserId: {UserId}",
request.UserId
);
throw;
}
}
}
```
---
## ✅ تسک 6: آپدیت Handler (BFF)
**فایل:** `BackOffice.BFF/src/BackOffice.BFF.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommandHandler.cs`
**تغییر:** اضافه کردن `ImagePath` به gRPC request:
```csharp
var grpcRequest = new CreateManualPaymentRequest
{
UserId = request.UserId,
Amount = request.Amount,
Type = (ManualPaymentType)request.Type,
Description = request.Description
};
if (!string.IsNullOrWhiteSpace(request.ReferenceNumber))
{
grpcRequest.ReferenceNumber = request.ReferenceNumber;
}
// ⬇️ اضافه شود ⬇️
if (!string.IsNullOrWhiteSpace(request.ImagePath))
{
grpcRequest.ImagePath = request.ImagePath;
}
```
**همچنین:** فایل `CreateManualPaymentCommand.cs` در BFF هم باید `ImagePath` اضافه شود.
---
## ✅ تسک 7: ایجاد Query برای لیست (اختیاری)
**فایل‌های جدید:**
- `GetManualMembershipPaymentsQuery.cs`
- `GetManualMembershipPaymentsQueryHandler.cs`
- `ManualMembershipPaymentDto.cs`
> این تسک **اختیاری** است چون در حال حاضر `GetAllManualPayments` وجود دارد که می‌تواند با فیلتر `Type` استفاده شود.
---
## 🔄 ترتیب اجرای تسک‌ها
```mermaid
graph TD
A[1. Entity - ImagePath] --> B[2. Command - ImagePath]
B --> C[3. Proto CMS - image_path]
C --> D[4. Proto BFF - image_path]
D --> E[5. CMS Handler - Full Rewrite]
E --> F[6. BFF Handler - ImagePath]
F --> G[7. Build & Test]
G --> H[8. Query - اختیاری]
```
---
## 📝 نکات مهم
### 1. تفاوت با ProcessManualMembershipPayment
| معیار | CreateManualPayment (این تسک) | ProcessManualMembershipPayment |
|-------|------------------------------|--------------------------------|
| کاربرد | ادمین ایجاد می‌کند | مشتری از طریق درگاه پرداخت می‌کند |
| Amount | از `SystemConstants` (ثابت) | از `request` (متغیر) |
| DiscountBalance | `BasePackageAmount × 2` | `Amount` (همان مبلغ) |
| ImagePath | ✅ دارد | ❌ ندارد |
### 2. مقادیر SystemConstants
```csharp
// فایل: CMSMicroservice.Domain/Common/SystemConstants.cs
public const long BasePackageAmount = 56_000_000; // 56 میلیون ریال
```
### 3. ManualPaymentType پیشنهادی
برای این کاربرد می‌توان از `CashDeposit` یا یک نوع جدید مثل `ClubMembership` استفاده کرد.
---
## ⏱️ برآورد زمانی
| تسک | زمان تقریبی |
|-----|-------------|
| تسک 1-4 (فیلدها و Proto) | ~15 دقیقه |
| تسک 5 (Handler CMS) | ~20 دقیقه |
| تسک 6 (Handler BFF) | ~10 دقیقه |
| Build & Test | ~10 دقیقه |
| **مجموع** | **~55 دقیقه** |
---
## 🧪 تست نهایی
بعد از اتمام تسک‌ها:
1. **Build:** `dotnet build` در هر دو پروژه
2. **Migration:** اگر نیاز بود برای `ImagePath`
3. **تست API:** ایجاد یک Manual Payment برای کاربر تست
4. **بررسی:** Balance و DiscountBalance کاربر
---
**تاریخ ایجاد:** 2026-01-01
**نویسنده:** GitHub Copilot
**وضعیت:** ⏳ در انتظار اجرا