14 KiB
عضویت دستی باشگاه مشتریان - 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 اضافه شود:
/// <summary>
/// مسیر تصویر فیش واریزی (اختیاری)
/// </summary>
public string? ImagePath { get; set; }
محل دقیق:
/// <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 اضافه شود:
/// <summary>
/// مسیر تصویر فیش واریزی (اختیاری)
/// </summary>
public string? ImagePath { get; set; }
✅ تسک 3: آپدیت Proto - CMS
فایل: CMS/src/CMSMicroservice.Protobuf/Protos/manualpayment.proto
تغییر در CreateManualPaymentRequest:
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:
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
کد جدید کامل:
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:
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.csGetManualMembershipPaymentsQueryHandler.csManualMembershipPaymentDto.cs
این تسک اختیاری است چون در حال حاضر
GetAllManualPaymentsوجود دارد که میتواند با فیلترTypeاستفاده شود.
🔄 ترتیب اجرای تسکها
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
// فایل: 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 دقیقه |
🧪 تست نهایی
بعد از اتمام تسکها:
- Build:
dotnet buildدر هر دو پروژه - Migration: اگر نیاز بود برای
ImagePath - تست API: ایجاد یک Manual Payment برای کاربر تست
- بررسی: Balance و DiscountBalance کاربر
تاریخ ایجاد: 2026-01-01
نویسنده: GitHub Copilot
وضعیت: ⏳ در انتظار اجرا