feat: Implement withdrawal reports query and service integration
This commit is contained in:
@@ -1,6 +1,11 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Exceptions;
|
||||||
using CMSMicroservice.Application.Common.Interfaces;
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Entities;
|
||||||
using CMSMicroservice.Domain.Enums;
|
using CMSMicroservice.Domain.Enums;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ValidationException = FluentValidation.ValidationException;
|
||||||
|
|
||||||
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
||||||
|
|
||||||
@@ -22,67 +27,135 @@ public class PurchaseGoldenPackageCommandHandler : IRequestHandler<PurchaseGolde
|
|||||||
|
|
||||||
public async Task<PurchaseGoldenPackageResponseDto> Handle(PurchaseGoldenPackageCommand request, CancellationToken cancellationToken)
|
public async Task<PurchaseGoldenPackageResponseDto> Handle(PurchaseGoldenPackageCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// TODO: پیادهسازی خرید پکیج طلایی
|
try
|
||||||
//
|
{
|
||||||
// 1. پیدا کردن کاربر و بررسی شرایط:
|
_logger.LogInformation(
|
||||||
// - var user = await _context.Users
|
"Starting golden package purchase for UserId: {UserId}, PackageId: {PackageId}",
|
||||||
// .Include(u => u.UserOrders)
|
request.UserId,
|
||||||
// .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken)
|
request.PackageId);
|
||||||
// - if (user == null) throw new NotFoundException("کاربر یافت نشد")
|
|
||||||
// - if (user.PackagePurchaseMethod != PackagePurchaseMethod.None)
|
|
||||||
// throw new InvalidOperationException("شما قبلاً پکیج طلایی خریداری کردهاید")
|
|
||||||
//
|
|
||||||
// 2. پیدا کردن پکیج:
|
|
||||||
// - var package = await _context.Packages
|
|
||||||
// .FirstOrDefaultAsync(p => p.Id == request.PackageId && p.IsAvailable, cancellationToken)
|
|
||||||
// - if (package == null) throw new NotFoundException("پکیج یافت نشد")
|
|
||||||
// - if (package.Name != "طلایی")
|
|
||||||
// throw new InvalidOperationException("فقط پکیج طلایی قابل خرید است")
|
|
||||||
//
|
|
||||||
// 3. ایجاد سفارش:
|
|
||||||
// - var order = new UserOrder {
|
|
||||||
// UserId = user.Id,
|
|
||||||
// OrderNumber = GenerateOrderNumber(), // مثلاً "ORD" + DateTime.UtcNow.Ticks
|
|
||||||
// TotalPrice = package.Price, // 56,000,000
|
|
||||||
// Status = OrderStatus.Pending,
|
|
||||||
// PaymentMethod = PaymentMethod.IPG,
|
|
||||||
// OrderType = OrderType.PackagePurchase, // enum جدید
|
|
||||||
// PackageId = package.Id
|
|
||||||
// }
|
|
||||||
// - _context.UserOrders.Add(order)
|
|
||||||
// - await _context.SaveChangesAsync(cancellationToken)
|
|
||||||
//
|
|
||||||
// 4. شروع پرداخت با درگاه:
|
|
||||||
// - var paymentResult = await _paymentGateway.InitiatePaymentAsync(
|
|
||||||
// orderId: order.Id,
|
|
||||||
// amount: order.TotalPrice,
|
|
||||||
// description: $"خرید پکیج {package.Name}",
|
|
||||||
// returnUrl: request.ReturnUrl,
|
|
||||||
// cancellationToken: cancellationToken
|
|
||||||
// )
|
|
||||||
// - if (!paymentResult.Success)
|
|
||||||
// throw new InvalidOperationException($"خطا در اتصال به درگاه: {paymentResult.ErrorMessage}")
|
|
||||||
//
|
|
||||||
// 5. ذخیره اطلاعات پرداخت:
|
|
||||||
// - order.TrackingCode = paymentResult.TrackingCode
|
|
||||||
// - order.PaymentGatewayToken = paymentResult.Token
|
|
||||||
// - await _context.SaveChangesAsync(cancellationToken)
|
|
||||||
//
|
|
||||||
// 6. برگشت نتیجه:
|
|
||||||
// - _logger.LogInformation("Golden package purchase initiated for user {UserId}, order {OrderId}", user.Id, order.Id)
|
|
||||||
// - return new PurchaseGoldenPackageResponseDto {
|
|
||||||
// Success = true,
|
|
||||||
// Message = "لطفاً به درگاه پرداخت منتقل شوید",
|
|
||||||
// OrderId = order.Id,
|
|
||||||
// PaymentGatewayUrl = paymentResult.PaymentUrl,
|
|
||||||
// TrackingCode = paymentResult.TrackingCode
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// نکته 1: OrderType.PackagePurchase را به OrderType enum اضافه کنید
|
|
||||||
// نکته 2: PackageId nullable است در UserOrder - مطمئن شوید میتوانید set کنید
|
|
||||||
// نکته 3: کاربر به صفحه paymentResult.PaymentUrl redirect میشود
|
|
||||||
// نکته 4: پس از پرداخت موفق، VerifyGoldenPackagePurchase فراخوانی میشود
|
|
||||||
|
|
||||||
throw new NotImplementedException("PurchaseGoldenPackage needs implementation");
|
// 1. پیدا کردن کاربر
|
||||||
|
var user = await _context.Users
|
||||||
|
.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("User not found for golden package purchase. UserId: {UserId}", request.UserId);
|
||||||
|
throw new NotFoundException(nameof(User), request.UserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. جلوگیری از خرید مجدد پکیج طلایی
|
||||||
|
if (user.PackagePurchaseMethod != PackagePurchaseMethod.None)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"User {UserId} has already purchased golden package via {Method}",
|
||||||
|
request.UserId,
|
||||||
|
user.PackagePurchaseMethod);
|
||||||
|
|
||||||
|
throw new ValidationException("شما قبلاً پکیج طلایی را خریداری کردهاید.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. پیدا کردن پکیج
|
||||||
|
var package = await _context.Packages
|
||||||
|
.FirstOrDefaultAsync(p => p.Id == request.PackageId, cancellationToken);
|
||||||
|
|
||||||
|
if (package == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Golden package not found. PackageId: {PackageId}", request.PackageId);
|
||||||
|
throw new NotFoundException(nameof(Package), request.PackageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// اطمینان از اینکه این همان پکیج طلایی است
|
||||||
|
if (!package.Title.Contains("طلایی", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!package.Title.Contains("golden", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"PackageId {PackageId} is not a golden package. Title: {Title}",
|
||||||
|
request.PackageId,
|
||||||
|
package.Title);
|
||||||
|
|
||||||
|
throw new ValidationException("فقط پکیج طلایی قابل خرید است.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. پیدا کردن آدرس پیشفرض کاربر (الزامی برای UserOrder)
|
||||||
|
var defaultAddress = await _context.UserAddresses
|
||||||
|
.Where(a => a.UserId == request.UserId)
|
||||||
|
.OrderByDescending(a => a.Created)
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (defaultAddress == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("No address found for user {UserId} in golden package purchase", request.UserId);
|
||||||
|
throw new ValidationException("لطفاً ابتدا یک آدرس برای خود ثبت کنید.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. ایجاد سفارش
|
||||||
|
var order = new UserOrder
|
||||||
|
{
|
||||||
|
UserId = user.Id,
|
||||||
|
PackageId = package.Id,
|
||||||
|
Amount = package.Price,
|
||||||
|
PaymentStatus = PaymentStatus.Pending,
|
||||||
|
DeliveryStatus = DeliveryStatus.None,
|
||||||
|
UserAddressId = defaultAddress.Id,
|
||||||
|
PaymentMethod = PaymentMethod.IPG
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.UserOrders.Add(order);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Created golden package UserOrder {OrderId} for UserId {UserId}, Amount: {Amount}",
|
||||||
|
order.Id,
|
||||||
|
request.UserId,
|
||||||
|
order.Amount);
|
||||||
|
|
||||||
|
// 6. شروع پرداخت با درگاه
|
||||||
|
var paymentRequest = new PaymentRequest
|
||||||
|
{
|
||||||
|
Amount = order.Amount,
|
||||||
|
UserId = user.Id,
|
||||||
|
Mobile = user.Mobile ?? string.Empty,
|
||||||
|
CallbackUrl = request.ReturnUrl,
|
||||||
|
Description = $"خرید پکیج طلایی - سفارش #{order.Id}"
|
||||||
|
};
|
||||||
|
|
||||||
|
var paymentResult = await _paymentGateway.InitiatePaymentAsync(paymentRequest, cancellationToken);
|
||||||
|
|
||||||
|
if (!paymentResult.IsSuccess)
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
"Payment gateway initiation failed for golden package. OrderId {OrderId}: {ErrorMessage}",
|
||||||
|
order.Id,
|
||||||
|
paymentResult.ErrorMessage);
|
||||||
|
|
||||||
|
order.PaymentStatus = PaymentStatus.Reject;
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
throw new Exception($"خطا در ارتباط با درگاه پرداخت: {paymentResult.ErrorMessage}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Golden package payment initiated successfully. OrderId: {OrderId}, RefId: {RefId}",
|
||||||
|
order.Id,
|
||||||
|
paymentResult.RefId);
|
||||||
|
|
||||||
|
return new PurchaseGoldenPackageResponseDto
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "لطفاً به درگاه پرداخت منتقل شوید.",
|
||||||
|
OrderId = order.Id,
|
||||||
|
PaymentGatewayUrl = paymentResult.GatewayUrl ?? string.Empty,
|
||||||
|
TrackingCode = paymentResult.RefId ?? string.Empty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
ex,
|
||||||
|
"Error in PurchaseGoldenPackageCommand for UserId: {UserId}",
|
||||||
|
request.UserId);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Exceptions;
|
||||||
using CMSMicroservice.Application.Common.Interfaces;
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Entities;
|
||||||
using CMSMicroservice.Domain.Enums;
|
using CMSMicroservice.Domain.Enums;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ValidationException = FluentValidation.ValidationException;
|
||||||
|
|
||||||
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
|
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
|
||||||
|
|
||||||
@@ -22,94 +27,161 @@ public class VerifyGoldenPackagePurchaseCommandHandler : IRequestHandler<VerifyG
|
|||||||
|
|
||||||
public async Task<VerifyGoldenPackagePurchaseResponseDto> Handle(VerifyGoldenPackagePurchaseCommand request, CancellationToken cancellationToken)
|
public async Task<VerifyGoldenPackagePurchaseResponseDto> Handle(VerifyGoldenPackagePurchaseCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// TODO: پیادهسازی تایید پرداخت پکیج طلایی
|
try
|
||||||
//
|
{
|
||||||
// 1. بررسی Status از درگاه:
|
_logger.LogInformation(
|
||||||
// - if (request.Status != "OK")
|
"Verifying golden package purchase. OrderId: {OrderId}, Authority: {Authority}, Status: {Status}",
|
||||||
// throw new InvalidOperationException("پرداخت توسط کاربر لغو شد")
|
request.OrderId,
|
||||||
//
|
request.Authority,
|
||||||
// 2. پیدا کردن سفارش:
|
request.Status);
|
||||||
// - var order = await _context.UserOrders
|
|
||||||
// .Include(o => o.User)
|
|
||||||
// .ThenInclude(u => u.UserWallet)
|
|
||||||
// .FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken)
|
|
||||||
// - if (order == null) throw new NotFoundException("سفارش یافت نشد")
|
|
||||||
// - if (order.Status != OrderStatus.Pending)
|
|
||||||
// throw new InvalidOperationException("این سفارش قبلاً پردازش شده است")
|
|
||||||
//
|
|
||||||
// 3. Verify با درگاه پرداخت:
|
|
||||||
// - var verifyResult = await _paymentGateway.VerifyPaymentAsync(
|
|
||||||
// authority: request.Authority,
|
|
||||||
// amount: order.TotalPrice,
|
|
||||||
// cancellationToken: cancellationToken
|
|
||||||
// )
|
|
||||||
// - if (!verifyResult.Success) {
|
|
||||||
// order.Status = OrderStatus.PaymentFailed
|
|
||||||
// order.PaymentFailureReason = verifyResult.ErrorMessage
|
|
||||||
// await _context.SaveChangesAsync(cancellationToken)
|
|
||||||
// throw new InvalidOperationException($"تایید پرداخت ناموفق: {verifyResult.ErrorMessage}")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// 4. شارژ کیف پول:
|
|
||||||
// - var wallet = order.User.UserWallet
|
|
||||||
// - if (wallet == null) {
|
|
||||||
// wallet = new UserWallet { UserId = order.UserId, Balance = 0, DiscountBalance = 0 }
|
|
||||||
// _context.UserWallets.Add(wallet)
|
|
||||||
// }
|
|
||||||
// - wallet.Balance += order.TotalPrice // اضافه شدن 56,000,000
|
|
||||||
//
|
|
||||||
// 5. ثبت Transaction:
|
|
||||||
// - var transaction = new Transaction {
|
|
||||||
// UserId = order.UserId,
|
|
||||||
// Amount = order.TotalPrice,
|
|
||||||
// Type = TransactionType.DepositIpg,
|
|
||||||
// Status = TransactionStatus.Completed,
|
|
||||||
// Description = $"شارژ کیف پول از خرید پکیج طلایی - سفارش {order.OrderNumber}",
|
|
||||||
// ReferenceCode = verifyResult.ReferenceCode,
|
|
||||||
// OrderId = order.Id
|
|
||||||
// }
|
|
||||||
// - _context.Transactions.Add(transaction)
|
|
||||||
//
|
|
||||||
// 6. ثبت UserWalletChangeLog:
|
|
||||||
// - var changeLog = new UserWalletChangeLog {
|
|
||||||
// UserId = order.UserId,
|
|
||||||
// ChangeValue = order.TotalPrice,
|
|
||||||
// ChangeType = ChangeType.Deposit,
|
|
||||||
// CurrentBalance = wallet.Balance,
|
|
||||||
// Description = $"شارژ از خرید پکیج طلایی",
|
|
||||||
// TransactionId = transaction.Id
|
|
||||||
// }
|
|
||||||
// - _context.UserWalletChangeLogs.Add(changeLog)
|
|
||||||
//
|
|
||||||
// 7. Set PackagePurchaseMethod:
|
|
||||||
// - order.User.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase
|
|
||||||
//
|
|
||||||
// 8. بهروزرسانی سفارش:
|
|
||||||
// - order.Status = OrderStatus.Processing // یا Completed
|
|
||||||
// - order.PaymentStatus = PaymentStatus.Paid
|
|
||||||
// - order.PaidAt = DateTime.UtcNow
|
|
||||||
// - order.BankReferenceId = verifyResult.ReferenceCode
|
|
||||||
//
|
|
||||||
// 9. ذخیره همه تغییرات:
|
|
||||||
// - await _context.SaveChangesAsync(cancellationToken)
|
|
||||||
//
|
|
||||||
// 10. Log و برگشت:
|
|
||||||
// - _logger.LogInformation("Golden package verified for user {UserId}, order {OrderId}, wallet charged {Amount}",
|
|
||||||
// order.UserId, order.Id, order.TotalPrice)
|
|
||||||
// - return new VerifyGoldenPackagePurchaseResponseDto {
|
|
||||||
// Success = true,
|
|
||||||
// Message = "پرداخت با موفقیت تایید شد. کیف پول شما شارژ گردید",
|
|
||||||
// OrderId = order.Id,
|
|
||||||
// TransactionId = transaction.Id,
|
|
||||||
// ReferenceCode = verifyResult.ReferenceCode,
|
|
||||||
// WalletBalance = wallet.Balance
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// نکته 1: کاربر هنوز عضو باشگاه نشده - باید ActivateClubMembership صدا بزند
|
|
||||||
// نکته 2: TransactionType.DepositIpg را بررسی کنید موجود باشد
|
|
||||||
// نکته 3: در صورت خطا، سفارش به PaymentFailed تغییر وضعیت میدهد
|
|
||||||
// نکته 4: این فرآیند idempotent نیست - باید بررسی شود سفارش Pending باشد
|
|
||||||
|
|
||||||
throw new NotImplementedException("VerifyGoldenPackagePurchase needs implementation");
|
// 1. اگر پرداخت از سمت درگاه موفق گزارش نشده باشد
|
||||||
|
if (!string.Equals(request.Status, "OK", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var pendingOrder = await _context.UserOrders
|
||||||
|
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
|
||||||
|
|
||||||
|
if (pendingOrder != null && pendingOrder.PaymentStatus == PaymentStatus.Pending)
|
||||||
|
{
|
||||||
|
pendingOrder.PaymentStatus = PaymentStatus.Reject;
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ValidationException("پرداخت توسط کاربر لغو شد.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. پیدا کردن سفارش به همراه کاربر
|
||||||
|
var order = await _context.UserOrders
|
||||||
|
.Include(o => o.User)
|
||||||
|
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
|
||||||
|
|
||||||
|
if (order == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Golden package order not found. OrderId: {OrderId}", request.OrderId);
|
||||||
|
throw new NotFoundException(nameof(UserOrder), request.OrderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// اگر قبلاً با موفقیت پرداخت شده، پاسخ idempotent برگردانیم
|
||||||
|
if (order.PaymentStatus == PaymentStatus.Success && order.TransactionId.HasValue)
|
||||||
|
{
|
||||||
|
var existingWallet = await _context.UserWallets
|
||||||
|
.FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken);
|
||||||
|
|
||||||
|
var existingTransaction = await _context.Transactions
|
||||||
|
.FirstOrDefaultAsync(t => t.Id == order.TransactionId.Value, cancellationToken);
|
||||||
|
|
||||||
|
return new VerifyGoldenPackagePurchaseResponseDto
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "پرداخت قبلاً با موفقیت تایید شده است.",
|
||||||
|
OrderId = order.Id,
|
||||||
|
TransactionId = existingTransaction?.Id ?? order.TransactionId.Value,
|
||||||
|
ReferenceCode = existingTransaction?.RefId ?? string.Empty,
|
||||||
|
WalletBalance = existingWallet?.Balance ?? 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Verify با درگاه پرداخت
|
||||||
|
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
|
||||||
|
request.Authority,
|
||||||
|
request.Authority,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (!verifyResult.IsSuccess)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Golden package payment verification failed. OrderId: {OrderId}, Message: {Message}",
|
||||||
|
request.OrderId,
|
||||||
|
verifyResult.Message);
|
||||||
|
|
||||||
|
order.PaymentStatus = PaymentStatus.Reject;
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
throw new ValidationException($"تراکنش ناموفق: {verifyResult.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. شارژ کیف پول (Balance فقط طبق سناریوی پکیج)
|
||||||
|
var wallet = await _context.UserWallets
|
||||||
|
.FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken);
|
||||||
|
|
||||||
|
if (wallet == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Wallet not found for UserId: {UserId}", order.UserId);
|
||||||
|
throw new NotFoundException($"کیف پول کاربر با شناسه {order.UserId} یافت نشد");
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldBalance = wallet.Balance;
|
||||||
|
wallet.Balance += order.Amount;
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Charging wallet Balance for user {UserId} from {OldBalance} to {NewBalance}",
|
||||||
|
order.UserId,
|
||||||
|
oldBalance,
|
||||||
|
wallet.Balance);
|
||||||
|
|
||||||
|
// 5. ثبت Transaction
|
||||||
|
var transaction = new Transaction
|
||||||
|
{
|
||||||
|
Amount = order.Amount,
|
||||||
|
Description = $"خرید پکیج طلایی از درگاه - سفارش #{order.Id}",
|
||||||
|
PaymentStatus = PaymentStatus.Success,
|
||||||
|
PaymentDate = DateTime.UtcNow,
|
||||||
|
RefId = verifyResult.RefId,
|
||||||
|
Type = TransactionType.DepositIpg
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.Transactions.Add(transaction);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 6. ثبت لاگ تغییر کیف پول
|
||||||
|
var changeLog = new UserWalletChangeLog
|
||||||
|
{
|
||||||
|
WalletId = wallet.Id,
|
||||||
|
CurrentBalance = wallet.Balance,
|
||||||
|
ChangeValue = order.Amount,
|
||||||
|
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||||
|
ChangeNerworkValue = 0,
|
||||||
|
CurrentDiscountBalance = wallet.DiscountBalance,
|
||||||
|
ChangeDiscountValue = 0,
|
||||||
|
IsIncrease = true,
|
||||||
|
RefrenceId = transaction.Id
|
||||||
|
};
|
||||||
|
|
||||||
|
await _context.UserWalletChangeLogs.AddAsync(changeLog, cancellationToken);
|
||||||
|
|
||||||
|
// 7. بهروزرسانی سفارش و کاربر
|
||||||
|
order.TransactionId = transaction.Id;
|
||||||
|
order.PaymentStatus = PaymentStatus.Success;
|
||||||
|
order.PaymentDate = DateTime.UtcNow;
|
||||||
|
order.PaymentMethod = PaymentMethod.IPG;
|
||||||
|
order.User.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Golden package purchase verified successfully. OrderId: {OrderId}, UserId: {UserId}, TransactionId: {TransactionId}, RefId: {RefId}",
|
||||||
|
order.Id,
|
||||||
|
order.UserId,
|
||||||
|
transaction.Id,
|
||||||
|
verifyResult.RefId);
|
||||||
|
|
||||||
|
return new VerifyGoldenPackagePurchaseResponseDto
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "پرداخت با موفقیت تایید شد. کیف پول شما شارژ گردید.",
|
||||||
|
OrderId = order.Id,
|
||||||
|
TransactionId = transaction.Id,
|
||||||
|
ReferenceCode = verifyResult.RefId,
|
||||||
|
WalletBalance = wallet.Balance
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
ex,
|
||||||
|
"Error in VerifyGoldenPackagePurchaseCommand. OrderId: {OrderId}",
|
||||||
|
request.OrderId);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using CMSMicroservice.Application.Common.Interfaces;
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
using CMSMicroservice.Domain.Enums;
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using ValidationException = FluentValidation.ValidationException;
|
||||||
|
|
||||||
namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus;
|
namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus;
|
||||||
|
|
||||||
@@ -18,36 +20,37 @@ public class UpdateOrderStatusCommandHandler : IRequestHandler<UpdateOrderStatus
|
|||||||
|
|
||||||
public async Task<UpdateOrderStatusResponseDto> Handle(UpdateOrderStatusCommand request, CancellationToken cancellationToken)
|
public async Task<UpdateOrderStatusResponseDto> Handle(UpdateOrderStatusCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// TODO: پیادهسازی تغییر وضعیت سفارش
|
var order = await _context.UserOrders
|
||||||
// 1. پیدا کردن سفارش:
|
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
|
||||||
// - await _context.UserOrders.FirstOrDefaultAsync(o => o.Id == request.OrderId)
|
|
||||||
// - بررسی null و پرتاب NotFoundException
|
|
||||||
//
|
|
||||||
// 2. بررسیهای انتقال وضعیت (State Transition Validation):
|
|
||||||
// - نمیتوان از Delivered به Cancelled رفت
|
|
||||||
// - نمیتوان از Cancelled به سایر وضعیتها رفت
|
|
||||||
// - الگوی معمول: Pending → Processing → Shipped → Delivered
|
|
||||||
// - Cancelled میتواند از Pending, Processing, Shipped باشد
|
|
||||||
//
|
|
||||||
// 3. تغییر وضعیت:
|
|
||||||
// - order.DeliveryStatus = request.NewStatus
|
|
||||||
// - اگر Description داریم: order.DeliveryDescription = request.Description
|
|
||||||
// - تنظیم تاریخهای مربوطه:
|
|
||||||
// * اگر NewStatus == Delivered → order.DeliveredAt = DateTime.UtcNow
|
|
||||||
// * اگر NewStatus == Shipped → order.ShippedAt = DateTime.UtcNow
|
|
||||||
// * اگر NewStatus == Processing → order.ProcessedAt = DateTime.UtcNow
|
|
||||||
//
|
|
||||||
// 4. ذخیره و Log:
|
|
||||||
// - await _context.SaveChangesAsync(cancellationToken)
|
|
||||||
// - _logger.LogInformation("Order {OrderId} status changed to {NewStatus}", request.OrderId, request.NewStatus)
|
|
||||||
//
|
|
||||||
// 5. برگشت Response:
|
|
||||||
// - Success = true
|
|
||||||
// - Message = "وضعیت سفارش با موفقیت تغییر کرد"
|
|
||||||
// - CurrentStatus = order.DeliveryStatus
|
|
||||||
//
|
|
||||||
// نکته: برای validation دقیقتر، میتوان یک State Machine برای انتقالهای مجاز تعریف کرد
|
|
||||||
|
|
||||||
throw new NotImplementedException("UpdateOrderStatus needs implementation");
|
if (order == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException(nameof(order), request.OrderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldStatus = order.DeliveryStatus;
|
||||||
|
|
||||||
|
// قوانین ساده انتقال وضعیت: از Cancelled نمیتوان خارج شد
|
||||||
|
if (oldStatus == DeliveryStatus.Cancelled)
|
||||||
|
{
|
||||||
|
throw new ValidationException("امکان تغییر وضعیت سفارش لغو شده وجود ندارد");
|
||||||
|
}
|
||||||
|
|
||||||
|
order.DeliveryStatus = request.NewStatus;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Order {OrderId} status changed from {OldStatus} to {NewStatus}",
|
||||||
|
request.OrderId,
|
||||||
|
oldStatus,
|
||||||
|
request.NewStatus);
|
||||||
|
|
||||||
|
return new UpdateOrderStatusResponseDto
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "وضعیت سفارش با موفقیت تغییر کرد",
|
||||||
|
CurrentStatus = order.DeliveryStatus
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Exceptions;
|
||||||
using CMSMicroservice.Application.Common.Interfaces;
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV;
|
namespace CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV;
|
||||||
|
|
||||||
@@ -7,6 +9,12 @@ public class CalculateOrderPVQueryHandler : IRequestHandler<CalculateOrderPVQuer
|
|||||||
private readonly IApplicationDbContext _context;
|
private readonly IApplicationDbContext _context;
|
||||||
private readonly ILogger<CalculateOrderPVQueryHandler> _logger;
|
private readonly ILogger<CalculateOrderPVQueryHandler> _logger;
|
||||||
|
|
||||||
|
// نسبت PV به قیمت بر اساس مثالهای بیزینسی:
|
||||||
|
// محصول ۱: قیمت 100,000 → PV = 50
|
||||||
|
// محصول ۲: قیمت 200,000 → PV = 100
|
||||||
|
// یعنی: PV = Price / 2000
|
||||||
|
private const decimal PvPerRial = 1m / 2000m;
|
||||||
|
|
||||||
public CalculateOrderPVQueryHandler(
|
public CalculateOrderPVQueryHandler(
|
||||||
IApplicationDbContext context,
|
IApplicationDbContext context,
|
||||||
ILogger<CalculateOrderPVQueryHandler> logger)
|
ILogger<CalculateOrderPVQueryHandler> logger)
|
||||||
@@ -17,46 +25,56 @@ public class CalculateOrderPVQueryHandler : IRequestHandler<CalculateOrderPVQuer
|
|||||||
|
|
||||||
public async Task<CalculateOrderPVResponseDto> Handle(CalculateOrderPVQuery request, CancellationToken cancellationToken)
|
public async Task<CalculateOrderPVResponseDto> Handle(CalculateOrderPVQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// TODO: پیادهسازی محاسبه PV سفارش
|
var order = await _context.UserOrders
|
||||||
// 1. پیدا کردن سفارش و جزئیات:
|
.Include(o => o.FactorDetails)
|
||||||
// - var order = await _context.UserOrders
|
.ThenInclude(fd => fd.Product)
|
||||||
// .Include(o => o.FactorDetails)
|
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
|
||||||
// .ThenInclude(fd => fd.Product)
|
|
||||||
// .FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken)
|
|
||||||
// - بررسی null و پرتاب NotFoundException
|
|
||||||
//
|
|
||||||
// 2. محاسبه PV هر محصول:
|
|
||||||
// - var productPVs = new List<ProductPVDto>()
|
|
||||||
// - decimal totalPV = 0
|
|
||||||
// - foreach (var detail in order.FactorDetails):
|
|
||||||
// * محاسبه PV واحد محصول (فرض: قیمت / 10000 یا از فیلد Product.PV اگر وجود دارد):
|
|
||||||
// decimal unitPV = detail.Product.PV ?? (detail.Product.Price / 10000m)
|
|
||||||
// * محاسبه PV کل این آیتم:
|
|
||||||
// decimal itemTotalPV = unitPV * detail.Count
|
|
||||||
// * اضافه به لیست:
|
|
||||||
// productPVs.Add(new ProductPVDto {
|
|
||||||
// ProductId = detail.ProductId,
|
|
||||||
// ProductTitle = detail.Product.Title,
|
|
||||||
// Quantity = detail.Count,
|
|
||||||
// UnitPV = unitPV,
|
|
||||||
// TotalPV = itemTotalPV,
|
|
||||||
// UnitPrice = detail.Product.Price
|
|
||||||
// })
|
|
||||||
// * اضافه به مجموع:
|
|
||||||
// totalPV += itemTotalPV
|
|
||||||
//
|
|
||||||
// 3. برگشت Response:
|
|
||||||
// - _logger.LogInformation("Calculated PV for order {OrderId}: {TotalPV}", request.OrderId, totalPV)
|
|
||||||
// - return new CalculateOrderPVResponseDto {
|
|
||||||
// TotalPV = totalPV,
|
|
||||||
// ProductPVs = productPVs,
|
|
||||||
// PayableAmount = order.DiscountedPrice
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// نکته: PV (Point Value) معمولاً برای سیستمهای MLM و کمیسیون شبکه استفاده میشود
|
|
||||||
// نکته: فرمول محاسبه PV باید بر اساس business logic شما باشد (قیمت/10000 فقط مثال است)
|
|
||||||
// نکته: اگر entity Product فیلد PV ندارد، باید اضافه شود یا از Configuration استفاده کنید
|
|
||||||
|
|
||||||
throw new NotImplementedException("CalculateOrderPV needs implementation");
|
if (order == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException(nameof(order), request.OrderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var productPVs = new List<ProductPVDto>();
|
||||||
|
decimal totalPV = 0;
|
||||||
|
|
||||||
|
foreach (var detail in order.FactorDetails)
|
||||||
|
{
|
||||||
|
if (detail.Product == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var unitPrice = detail.Product.Price;
|
||||||
|
var unitPV = Math.Round(unitPrice * PvPerRial, 2, MidpointRounding.AwayFromZero);
|
||||||
|
var itemTotalPV = unitPV * detail.Count;
|
||||||
|
|
||||||
|
productPVs.Add(new ProductPVDto
|
||||||
|
{
|
||||||
|
ProductId = detail.ProductId,
|
||||||
|
ProductTitle = detail.Product.Title,
|
||||||
|
Quantity = detail.Count,
|
||||||
|
UnitPV = unitPV,
|
||||||
|
TotalPV = itemTotalPV,
|
||||||
|
UnitPrice = unitPrice
|
||||||
|
});
|
||||||
|
|
||||||
|
totalPV += itemTotalPV;
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = new CalculateOrderPVResponseDto
|
||||||
|
{
|
||||||
|
TotalPV = totalPV,
|
||||||
|
ProductPVs = productPVs,
|
||||||
|
// فعلاً مبلغ قابل پرداخت همان Amount است؛ در آینده میتوان تخفیف را هم اعمال کرد
|
||||||
|
PayableAmount = order.Amount
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Calculated PV for order {OrderId}: {TotalPV}",
|
||||||
|
request.OrderId,
|
||||||
|
totalPV);
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using CMSMicroservice.Application.Common.Interfaces;
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
using CMSMicroservice.Application.Common.Models;
|
using CMSMicroservice.Application.Common.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange;
|
namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange;
|
||||||
|
|
||||||
@@ -18,63 +19,78 @@ public class GetOrdersByDateRangeQueryHandler : IRequestHandler<GetOrdersByDateR
|
|||||||
|
|
||||||
public async Task<GetOrdersByDateRangeResponseDto> Handle(GetOrdersByDateRangeQuery request, CancellationToken cancellationToken)
|
public async Task<GetOrdersByDateRangeResponseDto> Handle(GetOrdersByDateRangeQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// TODO: پیادهسازی دریافت سفارشات بر اساس بازه زمانی
|
var query = _context.UserOrders
|
||||||
// 1. شروع Query:
|
.AsNoTracking()
|
||||||
// - var query = _context.UserOrders.AsQueryable()
|
.Include(o => o.User)
|
||||||
// - Include User برای نام کاربر: .Include(o => o.User)
|
.Include(o => o.FactorDetails)
|
||||||
// - Include FactorDetails برای شمارش تعداد آیتمها: .Include(o => o.FactorDetails)
|
.AsQueryable();
|
||||||
//
|
|
||||||
// 2. اعمال فیلتر بازه زمانی:
|
|
||||||
// - query = query.Where(o => o.Created >= request.StartDate && o.Created <= request.EndDate)
|
|
||||||
//
|
|
||||||
// 3. اعمال فیلترهای اختیاری:
|
|
||||||
// - اگر request.Status.HasValue:
|
|
||||||
// query = query.Where(o => o.DeliveryStatus == request.Status.Value)
|
|
||||||
// - اگر request.UserId.HasValue:
|
|
||||||
// query = query.Where(o => o.UserId == request.UserId.Value)
|
|
||||||
//
|
|
||||||
// 4. محاسبه تعداد کل:
|
|
||||||
// - var totalCount = await query.CountAsync(cancellationToken)
|
|
||||||
//
|
|
||||||
// 5. مرتبسازی و Pagination:
|
|
||||||
// - query = query.OrderByDescending(o => o.Created)
|
|
||||||
// - query = query.Skip((request.PageIndex - 1) * request.PageSize)
|
|
||||||
// - query = query.Take(request.PageSize)
|
|
||||||
//
|
|
||||||
// 6. دریافت دادهها:
|
|
||||||
// - var orders = await query.ToListAsync(cancellationToken)
|
|
||||||
//
|
|
||||||
// 7. Mapping به DTO:
|
|
||||||
// - var orderDtos = orders.Select(o => new OrderSummaryDto {
|
|
||||||
// Id = o.Id,
|
|
||||||
// UserId = o.UserId,
|
|
||||||
// UserFullName = $"{o.User.Firstname} {o.User.Lastname}",
|
|
||||||
// Amount = o.Amount,
|
|
||||||
// DiscountedPrice = o.DiscountedPrice,
|
|
||||||
// Status = o.DeliveryStatus,
|
|
||||||
// Created = o.Created,
|
|
||||||
// ShippedAt = o.ShippedAt,
|
|
||||||
// DeliveredAt = o.DeliveredAt,
|
|
||||||
// ItemsCount = o.FactorDetails.Count
|
|
||||||
// }).ToList()
|
|
||||||
//
|
|
||||||
// 8. ساخت MetaData:
|
|
||||||
// - var totalPages = (int)Math.Ceiling(totalCount / (double)request.PageSize)
|
|
||||||
// - MetaData = new MetaData {
|
|
||||||
// CurrentPage = request.PageIndex,
|
|
||||||
// TotalPage = totalPages,
|
|
||||||
// PageSize = request.PageSize,
|
|
||||||
// TotalCount = totalCount,
|
|
||||||
// HasNext = request.PageIndex < totalPages,
|
|
||||||
// HasPrevious = request.PageIndex > 1
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// 9. Log و برگشت:
|
|
||||||
// - _logger.LogInformation("Retrieved {Count} orders for date range {Start} to {End}", orders.Count, request.StartDate, request.EndDate)
|
|
||||||
// - return new GetOrdersByDateRangeResponseDto { MetaData = metaData, Orders = orderDtos }
|
|
||||||
//
|
|
||||||
// نکته: برای performance بهتر، میتوان از AsNoTracking() استفاده کرد
|
|
||||||
|
|
||||||
throw new NotImplementedException("GetOrdersByDateRange needs implementation");
|
query = query.Where(o => o.Created >= request.StartDate && o.Created <= request.EndDate);
|
||||||
|
|
||||||
|
if (request.Status.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(o => o.DeliveryStatus == request.Status.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.UserId.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(o => o.UserId == request.UserId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalCount = await query.CountAsync(cancellationToken);
|
||||||
|
|
||||||
|
var response = new GetOrdersByDateRangeResponseDto
|
||||||
|
{
|
||||||
|
MetaData = new MetaData
|
||||||
|
{
|
||||||
|
CurrentPage = request.PageIndex,
|
||||||
|
TotalPage = totalCount == 0 ? 0 : (int)Math.Ceiling(totalCount / (double)request.PageSize),
|
||||||
|
PageSize = request.PageSize,
|
||||||
|
TotalCount = totalCount,
|
||||||
|
HasNext = totalCount > 0 && request.PageIndex * request.PageSize < totalCount,
|
||||||
|
HasPrevious = request.PageIndex > 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (totalCount == 0)
|
||||||
|
{
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
var orders = await query
|
||||||
|
.OrderByDescending(o => o.Created)
|
||||||
|
.Skip((request.PageIndex - 1) * request.PageSize)
|
||||||
|
.Take(request.PageSize)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
response.Orders = orders.Select(o =>
|
||||||
|
{
|
||||||
|
var firstName = o.User?.FirstName ?? string.Empty;
|
||||||
|
var lastName = o.User?.LastName ?? string.Empty;
|
||||||
|
var fullName = $"{firstName} {lastName}".Trim();
|
||||||
|
|
||||||
|
return new OrderSummaryDto
|
||||||
|
{
|
||||||
|
Id = o.Id,
|
||||||
|
UserId = o.UserId,
|
||||||
|
UserFullName = fullName,
|
||||||
|
Amount = o.Amount,
|
||||||
|
// در حال حاضر فیلد DiscountedPrice در UserOrder وجود ندارد، پس همان Amount برگردانده میشود
|
||||||
|
DiscountedPrice = o.Amount,
|
||||||
|
Status = o.DeliveryStatus,
|
||||||
|
Created = o.Created,
|
||||||
|
ShippedAt = null,
|
||||||
|
DeliveredAt = null,
|
||||||
|
ItemsCount = o.FactorDetails?.Count ?? 0
|
||||||
|
};
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Retrieved {Count} orders for date range {Start} to {End}",
|
||||||
|
response.Orders.Count,
|
||||||
|
request.StartDate,
|
||||||
|
request.EndDate);
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,13 @@ service CommissionContract
|
|||||||
get: "/Commission/GetWorkerLogs"
|
get: "/Commission/GetWorkerLogs"
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Financial Reports
|
||||||
|
rpc GetWithdrawalReports(GetWithdrawalReportsRequest) returns (GetWithdrawalReportsResponse){
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/Commission/GetWithdrawalReports"
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ Commands ============
|
// ============ Commands ============
|
||||||
@@ -388,6 +395,50 @@ message WorkerExecutionLogModel
|
|||||||
google.protobuf.StringValue details = 10; // JSON or text details
|
google.protobuf.StringValue details = 10; // JSON or text details
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWithdrawalReports Query
|
||||||
|
message GetWithdrawalReportsRequest
|
||||||
|
{
|
||||||
|
google.protobuf.Timestamp start_date = 1; // Optional - default: 30 days ago
|
||||||
|
google.protobuf.Timestamp end_date = 2; // Optional - default: today
|
||||||
|
int32 period_type = 3; // ReportPeriodType: Daily=1, Weekly=2, Monthly=3
|
||||||
|
google.protobuf.Int32Value status = 4; // CommissionPayoutStatus enum (optional)
|
||||||
|
google.protobuf.Int64Value user_id = 5; // Optional user filter
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetWithdrawalReportsResponse
|
||||||
|
{
|
||||||
|
repeated PeriodReport period_reports = 1;
|
||||||
|
WithdrawalSummary summary = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PeriodReport
|
||||||
|
{
|
||||||
|
string period_label = 1; // e.g., "2025-01-15", "هفته 3", "فروردین 1404"
|
||||||
|
google.protobuf.Timestamp start_date = 2;
|
||||||
|
google.protobuf.Timestamp end_date = 3;
|
||||||
|
int32 total_requests = 4;
|
||||||
|
int32 pending_count = 5;
|
||||||
|
int32 approved_count = 6;
|
||||||
|
int32 rejected_count = 7;
|
||||||
|
int32 completed_count = 8;
|
||||||
|
int32 failed_count = 9;
|
||||||
|
int64 total_amount = 10;
|
||||||
|
int64 paid_amount = 11;
|
||||||
|
int64 pending_amount = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WithdrawalSummary
|
||||||
|
{
|
||||||
|
int32 total_requests = 1;
|
||||||
|
int64 total_amount = 2;
|
||||||
|
int64 total_paid = 3;
|
||||||
|
int64 total_pending = 4;
|
||||||
|
int64 total_rejected = 5;
|
||||||
|
int64 average_amount = 6;
|
||||||
|
int32 unique_users = 7;
|
||||||
|
float success_rate = 8; // Percentage (0-100)
|
||||||
|
}
|
||||||
|
|
||||||
message WithdrawalRequestModel
|
message WithdrawalRequestModel
|
||||||
{
|
{
|
||||||
int64 id = 1;
|
int64 id = 1;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ using CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools;
|
|||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests;
|
||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus;
|
||||||
using CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLogs;
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLogs;
|
||||||
|
using CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalReports;
|
||||||
|
|
||||||
namespace CMSMicroservice.WebApi.Services;
|
namespace CMSMicroservice.WebApi.Services;
|
||||||
|
|
||||||
@@ -110,4 +111,9 @@ public class CommissionService : CommissionContract.CommissionContractBase
|
|||||||
{
|
{
|
||||||
return await _dispatchRequestToCQRS.Handle<GetWorkerExecutionLogsRequest, GetWorkerExecutionLogsQuery, GetWorkerExecutionLogsResponse>(request, context);
|
return await _dispatchRequestToCQRS.Handle<GetWorkerExecutionLogsRequest, GetWorkerExecutionLogsQuery, GetWorkerExecutionLogsResponse>(request, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<GetWithdrawalReportsResponse> GetWithdrawalReports(GetWithdrawalReportsRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return await _dispatchRequestToCQRS.Handle<GetWithdrawalReportsRequest, GetWithdrawalReportsQuery, GetWithdrawalReportsResponse>(request, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user