Files
docs/archive/03-BACKEND/CMS/MANUAL-CLUB-MEMBERSHIP-TASKS.md
T
masoodafar-web 5965b98728 update
2026-01-03 18:27:49 +03:30

14 KiB
Raw Blame History

عضویت دستی باشگاه مشتریان - 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.cs
  • GetManualMembershipPaymentsQueryHandler.cs
  • ManualMembershipPaymentDto.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 دقیقه

🧪 تست نهایی

بعد از اتمام تسک‌ها:

  1. Build: dotnet build در هر دو پروژه
  2. Migration: اگر نیاز بود برای ImagePath
  3. تست API: ایجاد یک Manual Payment برای کاربر تست
  4. بررسی: Balance و DiscountBalance کاربر

تاریخ ایجاد: 2026-01-01
نویسنده: GitHub Copilot
وضعیت: در انتظار اجرا