feat: Add ClearCart command and response, implement CancelOrder command with validation, and enhance DeliveryStatus and User models
This commit is contained in:
@@ -1,22 +1,15 @@
|
||||
using CMSMicroservice.Application.Common.Models;
|
||||
using MediatR;
|
||||
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Commands.ActivateClubMembership;
|
||||
|
||||
/// <summary>
|
||||
/// Command برای فعالسازی عضویت باشگاه مشتریان یک کاربر
|
||||
/// </summary>
|
||||
public record ActivateClubMembershipCommand : IRequest<long>
|
||||
public record ActivateClubMembershipCommand : IRequest<bool>
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه کاربر
|
||||
/// </summary>
|
||||
public long UserId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// تاریخ فعالسازی (اختیاری - پیشفرض: الان)
|
||||
/// </summary>
|
||||
public DateTimeOffset? ActivationDate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// دلیل فعالسازی (برای History)
|
||||
/// </summary>
|
||||
public string? Reason { get; init; }
|
||||
}
|
||||
|
||||
@@ -1,98 +1,220 @@
|
||||
using CMSMicroservice.Application.Common.Exceptions;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Application.Common.Models;
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using CMSMicroservice.Domain.Entities.Club;
|
||||
using CMSMicroservice.Domain.Entities.History;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Commands.ActivateClubMembership;
|
||||
|
||||
public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClubMembershipCommand, long>
|
||||
public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClubMembershipCommand, bool>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUser;
|
||||
private readonly ILogger<ActivateClubMembershipCommandHandler> _logger;
|
||||
|
||||
public ActivateClubMembershipCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUser)
|
||||
ICurrentUserService currentUser,
|
||||
ILogger<ActivateClubMembershipCommandHandler> logger)
|
||||
{
|
||||
_context = context;
|
||||
_currentUser = currentUser;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<long> Handle(ActivateClubMembershipCommand request, CancellationToken cancellationToken)
|
||||
public async Task<bool> Handle(
|
||||
ActivateClubMembershipCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// بررسی وجود کاربر
|
||||
var userExists = await _context.Users
|
||||
.AnyAsync(x => x.Id == request.UserId, cancellationToken);
|
||||
|
||||
if (!userExists)
|
||||
try
|
||||
{
|
||||
throw new NotFoundException(nameof(User), request.UserId);
|
||||
}
|
||||
_logger.LogInformation(
|
||||
"Activating club membership for UserId: {UserId}",
|
||||
request.UserId
|
||||
);
|
||||
|
||||
// دریافت مبلغ عضویت از Configuration
|
||||
var activationFeeConfig = await _context.SystemConfigurations
|
||||
.Where(x => x.Key == "Club.ActivationFee" && x.IsActive)
|
||||
.Select(x => x.Value)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
long initialContribution = long.Parse(activationFeeConfig ?? "25000000"); // Default: 25 million Rials
|
||||
// 1. بررسی کاربر
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
|
||||
|
||||
// بررسی عضویت فعلی
|
||||
var existingMembership = await _context.ClubMemberships
|
||||
.FirstOrDefaultAsync(x => x.UserId == request.UserId, cancellationToken);
|
||||
|
||||
ClubMembership entity;
|
||||
bool isNewMembership = existingMembership == null;
|
||||
var activationDate = request.ActivationDate ?? DateTimeOffset.Now; // استفاده از Local Time
|
||||
|
||||
if (isNewMembership)
|
||||
{
|
||||
// ایجاد عضویت جدید
|
||||
// توجه: InitialContribution فقط ثبت میشود، از کیف پول کسر نمیشود!
|
||||
// کاربر قبلاً باید کیف پول خود را شارژ کرده باشد
|
||||
entity = new ClubMembership
|
||||
if (user == null)
|
||||
{
|
||||
UserId = request.UserId,
|
||||
IsActive = true,
|
||||
ActivatedAt = activationDate.DateTime,
|
||||
InitialContribution = initialContribution, // مبلغ عضویت از Configuration
|
||||
TotalEarned = 0
|
||||
};
|
||||
_logger.LogWarning("User not found: {UserId}", request.UserId);
|
||||
throw new NotFoundException(nameof(User), request.UserId);
|
||||
}
|
||||
|
||||
await _context.ClubMemberships.AddAsync(entity, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
// فعالسازی مجدد عضویت موجود
|
||||
entity = existingMembership;
|
||||
|
||||
if (entity.IsActive)
|
||||
// 2. بررسی اینکه پکیج خریده باشد
|
||||
if (user.PackagePurchaseMethod == PackagePurchaseMethod.None)
|
||||
{
|
||||
// اگر از قبل فعال است، فقط تاریخ را بهروز میکنیم
|
||||
entity.ActivatedAt = activationDate.DateTime;
|
||||
_logger.LogWarning(
|
||||
"User {UserId} has not purchased golden package yet",
|
||||
request.UserId
|
||||
);
|
||||
throw new BadRequestException(
|
||||
"برای فعالسازی باشگاه مشتریان ابتدا باید پکیج طلایی خریداری کنید"
|
||||
);
|
||||
}
|
||||
|
||||
// 3. بررسی موجودی کیف پول
|
||||
var wallet = await _context.UserWallets
|
||||
.FirstOrDefaultAsync(w => w.UserId == user.Id, cancellationToken);
|
||||
|
||||
if (wallet == null)
|
||||
{
|
||||
_logger.LogError("Wallet not found for UserId: {UserId}", request.UserId);
|
||||
throw new NotFoundException("کیف پول کاربر یافت نشد");
|
||||
}
|
||||
|
||||
if (wallet.Balance < 56_000_000)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"User {UserId} has insufficient balance: {Balance}",
|
||||
request.UserId,
|
||||
wallet.Balance
|
||||
);
|
||||
throw new BadRequestException(
|
||||
"برای فعالسازی باشگاه مشتریان باید حداقل 56 میلیون تومان موجودی اصلی داشته باشید"
|
||||
);
|
||||
}
|
||||
|
||||
// 4. پیدا کردن UserOrder با PackageId
|
||||
var packageOrder = await _context.UserOrders
|
||||
.Include(o => o.Transaction)
|
||||
.Where(o =>
|
||||
o.UserId == user.Id &&
|
||||
o.PackageId != null &&
|
||||
o.PaymentStatus == PaymentStatus.Success)
|
||||
.OrderByDescending(o => o.Created)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (packageOrder == null)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"No successful package order found for UserId: {UserId}",
|
||||
request.UserId
|
||||
);
|
||||
throw new NotFoundException("سفارش پکیج طلایی یافت نشد");
|
||||
}
|
||||
|
||||
// 5. بررسی Transaction
|
||||
if (packageOrder.Transaction == null)
|
||||
{
|
||||
_logger.LogError(
|
||||
"Transaction not found for OrderId: {OrderId}",
|
||||
packageOrder.Id
|
||||
);
|
||||
throw new NotFoundException("تراکنش مربوط به سفارش یافت نشد");
|
||||
}
|
||||
|
||||
var transaction = packageOrder.Transaction;
|
||||
|
||||
if (transaction.Type != TransactionType.DepositIpg &&
|
||||
transaction.Type != TransactionType.DepositExternal1)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Invalid transaction type for OrderId {OrderId}: {Type}",
|
||||
packageOrder.Id,
|
||||
transaction.Type
|
||||
);
|
||||
throw new BadRequestException(
|
||||
"تراکنش معتبر برای فعالسازی باشگاه یافت نشد"
|
||||
);
|
||||
}
|
||||
|
||||
// 6. بررسی عضویت فعلی
|
||||
var existingMembership = await _context.ClubMemberships
|
||||
.FirstOrDefaultAsync(c => c.UserId == user.Id, cancellationToken);
|
||||
|
||||
ClubMembership entity;
|
||||
bool isNewMembership = existingMembership == null;
|
||||
var activationDate = DateTime.UtcNow;
|
||||
|
||||
if (isNewMembership)
|
||||
{
|
||||
// ایجاد عضویت جدید
|
||||
entity = new ClubMembership
|
||||
{
|
||||
UserId = user.Id,
|
||||
IsActive = true,
|
||||
ActivatedAt = activationDate,
|
||||
InitialContribution = 56_000_000,
|
||||
TotalEarned = 0,
|
||||
PurchaseMethod = user.PackagePurchaseMethod
|
||||
};
|
||||
|
||||
_context.ClubMemberships.Add(entity);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Created new club membership for UserId {UserId} via {Method}",
|
||||
user.Id,
|
||||
user.PackagePurchaseMethod
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// فعالسازی عضویت غیرفعال
|
||||
if (existingMembership.IsActive)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"User {UserId} is already an active club member",
|
||||
user.Id
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// فعالسازی مجدد
|
||||
entity = existingMembership;
|
||||
entity.IsActive = true;
|
||||
entity.ActivatedAt = activationDate.DateTime;
|
||||
entity.ActivatedAt = activationDate;
|
||||
entity.PurchaseMethod = user.PackagePurchaseMethod;
|
||||
|
||||
_context.ClubMemberships.Update(entity);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Reactivated club membership for UserId {UserId}",
|
||||
user.Id
|
||||
);
|
||||
}
|
||||
|
||||
_context.ClubMemberships.Update(entity);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 7. ثبت تاریخچه
|
||||
var history = new ClubMembershipHistory
|
||||
{
|
||||
ClubMembershipId = entity.Id,
|
||||
UserId = entity.UserId,
|
||||
OldIsActive = !isNewMembership && !existingMembership!.IsActive,
|
||||
NewIsActive = true,
|
||||
Action = ClubMembershipAction.Activated,
|
||||
Reason = isNewMembership
|
||||
? $"Initial activation via {user.PackagePurchaseMethod}"
|
||||
: $"Reactivated via {user.PackagePurchaseMethod}",
|
||||
PerformedBy = _currentUser.GetPerformedBy()
|
||||
};
|
||||
|
||||
_context.ClubMembershipHistories.Add(history);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Club membership activated successfully. UserId: {UserId}, MembershipId: {MembershipId}",
|
||||
user.Id,
|
||||
entity.Id
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// ثبت تاریخچه
|
||||
var history = new ClubMembershipHistory
|
||||
catch (Exception ex)
|
||||
{
|
||||
ClubMembershipId = entity.Id,
|
||||
UserId = entity.UserId,
|
||||
OldIsActive = !isNewMembership && !existingMembership!.IsActive,
|
||||
NewIsActive = true,
|
||||
Action = ClubMembershipAction.Activated,
|
||||
Reason = request.Reason ?? (isNewMembership ? "Initial activation" : "Reactivated"),
|
||||
PerformedBy = _currentUser.GetPerformedBy()
|
||||
};
|
||||
|
||||
await _context.ClubMembershipHistories.AddAsync(history, cancellationToken);
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return entity.Id;
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Error in ActivateClubMembershipCommand for UserId: {UserId}",
|
||||
request.UserId
|
||||
);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,6 @@ public class ActivateClubMembershipCommandValidator : AbstractValidator<Activate
|
||||
RuleFor(x => x.UserId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر معتبر نیست");
|
||||
|
||||
RuleFor(x => x.ActivationDate)
|
||||
.LessThanOrEqualTo(DateTimeOffset.UtcNow.AddDays(1))
|
||||
.WithMessage("تاریخ فعالسازی نمیتواند در آینده باشد")
|
||||
.When(x => x.ActivationDate.HasValue);
|
||||
|
||||
RuleFor(x => x.Reason)
|
||||
.MaximumLength(500)
|
||||
.WithMessage("دلیل فعالسازی نمیتواند بیشتر از 500 کاراکتر باشد")
|
||||
.When(x => !string.IsNullOrEmpty(x.Reason));
|
||||
}
|
||||
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
|
||||
Reference in New Issue
Block a user