# عضویت دستی باشگاه مشتریان - 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 /// /// مسیر تصویر فیش واریزی (اختیاری) /// public string? ImagePath { get; set; } ``` **محل دقیق:** ```csharp /// /// شماره مرجع یا شماره فیش (اختیاری) /// public string? ReferenceNumber { get; set; } // ⬇️ اینجا اضافه شود ⬇️ /// /// مسیر تصویر فیش واریزی (اختیاری) /// public string? ImagePath { get; set; } /// /// وضعیت تایید /// public ManualPaymentStatus Status { get; set; } = ManualPaymentStatus.Pending; ``` --- ## ✅ تسک 2: اضافه کردن ImagePath به Command **فایل:** `CMS/src/CMSMicroservice.Application/ManualPaymentCQ/Commands/CreateManualPayment/CreateManualPaymentCommand.cs` **تغییر:** بعد از `ReferenceNumber` اضافه شود: ```csharp /// /// مسیر تصویر فیش واریزی (اختیاری) /// 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 { private readonly IApplicationDbContext _context; private readonly ICurrentUserService _currentUser; private readonly ILogger _logger; public CreateManualPaymentCommandHandler( IApplicationDbContext context, ICurrentUserService currentUser, ILogger logger) { _context = context; _currentUser = currentUser; _logger = logger; } public async Task 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 **وضعیت:** ⏳ در انتظار اجرا