Add validators and services for Product Galleries and Product Tags
- Implemented Create, Delete, Get, and Update validators for Product Galleries. - Added Create, Delete, Get, and Update validators for Product Tags. - Created service classes for handling Discount Categories, Discount Orders, Discount Products, Discount Shopping Cart, Product Categories, Product Galleries, and Product Tags. - Each service class integrates with CQRS for command and query handling. - Established mapping profiles for Product Galleries.
This commit is contained in:
@@ -13,7 +13,7 @@ public class CreateNewCategoryCommandHandler : IRequestHandler<CreateNewCategory
|
|||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var entity = request.Adapt<Category>();
|
var entity = request.Adapt<Category>();
|
||||||
await _context.Categorys.AddAsync(entity, cancellationToken);
|
await _context.Categories.AddAsync(entity, cancellationToken);
|
||||||
entity.AddDomainEvent(new CreateNewCategoryEvent(entity));
|
entity.AddDomainEvent(new CreateNewCategoryEvent(entity));
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
return entity.Adapt<CreateNewCategoryResponseDto>();
|
return entity.Adapt<CreateNewCategoryResponseDto>();
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ public class DeleteCategoryCommandHandler : IRequestHandler<DeleteCategoryComman
|
|||||||
|
|
||||||
public async Task<Unit> Handle(DeleteCategoryCommand request, CancellationToken cancellationToken)
|
public async Task<Unit> Handle(DeleteCategoryCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var entity = await _context.Categorys
|
var entity = await _context.Categories
|
||||||
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(Category), request.Id);
|
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(Category), request.Id);
|
||||||
entity.IsDeleted = true;
|
entity.IsDeleted = true;
|
||||||
_context.Categorys.Update(entity);
|
_context.Categories.Update(entity);
|
||||||
entity.AddDomainEvent(new DeleteCategoryEvent(entity));
|
entity.AddDomainEvent(new DeleteCategoryEvent(entity));
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
return Unit.Value;
|
return Unit.Value;
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ public class UpdateCategoryCommandHandler : IRequestHandler<UpdateCategoryComman
|
|||||||
|
|
||||||
public async Task<Unit> Handle(UpdateCategoryCommand request, CancellationToken cancellationToken)
|
public async Task<Unit> Handle(UpdateCategoryCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var entity = await _context.Categorys
|
var entity = await _context.Categories
|
||||||
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(Category), request.Id);
|
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(Category), request.Id);
|
||||||
request.Adapt(entity);
|
request.Adapt(entity);
|
||||||
_context.Categorys.Update(entity);
|
_context.Categories.Update(entity);
|
||||||
entity.AddDomainEvent(new UpdateCategoryEvent(entity));
|
entity.AddDomainEvent(new UpdateCategoryEvent(entity));
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
return Unit.Value;
|
return Unit.Value;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public class GetAllCategoryByFilterQueryHandler : IRequestHandler<GetAllCategory
|
|||||||
|
|
||||||
public async Task<GetAllCategoryByFilterResponseDto> Handle(GetAllCategoryByFilterQuery request, CancellationToken cancellationToken)
|
public async Task<GetAllCategoryByFilterResponseDto> Handle(GetAllCategoryByFilterQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var query = _context.Categorys
|
var query = _context.Categories
|
||||||
.ApplyOrder(sortBy: request.SortBy)
|
.ApplyOrder(sortBy: request.SortBy)
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.AsQueryable();
|
.AsQueryable();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class GetCategoryQueryHandler : IRequestHandler<GetCategoryQuery, GetCate
|
|||||||
public async Task<GetCategoryResponseDto> Handle(GetCategoryQuery request,
|
public async Task<GetCategoryResponseDto> Handle(GetCategoryQuery request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var response = await _context.Categorys
|
var response = await _context.Categories
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(x => x.Id == request.Id)
|
.Where(x => x.Id == request.Id)
|
||||||
.ProjectToType<GetCategoryResponseDto>()
|
.ProjectToType<GetCategoryResponseDto>()
|
||||||
|
|||||||
@@ -130,6 +130,30 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
|||||||
var existingMembership = await _context.ClubMemberships
|
var existingMembership = await _context.ClubMemberships
|
||||||
.FirstOrDefaultAsync(c => c.UserId == user.Id, cancellationToken);
|
.FirstOrDefaultAsync(c => c.UserId == user.Id, cancellationToken);
|
||||||
|
|
||||||
|
// 6.1. دریافت مبلغ هدیه از تنظیمات
|
||||||
|
var giftValueConfig = await _context.SystemConfigurations
|
||||||
|
.FirstOrDefaultAsync(
|
||||||
|
c => c.Key == "Club.MembershipGiftValue" && c.IsActive,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
long giftValue = 25_200_000; // مقدار پیشفرض
|
||||||
|
if (giftValueConfig != null && long.TryParse(giftValueConfig.Value, out var configValue))
|
||||||
|
{
|
||||||
|
giftValue = configValue;
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Using Club.MembershipGiftValue from configuration: {GiftValue}",
|
||||||
|
giftValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Club.MembershipGiftValue not found in configuration, using default: {GiftValue}",
|
||||||
|
giftValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ClubMembership entity;
|
ClubMembership entity;
|
||||||
bool isNewMembership = existingMembership == null;
|
bool isNewMembership = existingMembership == null;
|
||||||
var activationDate = DateTime.UtcNow;
|
var activationDate = DateTime.UtcNow;
|
||||||
@@ -143,6 +167,7 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
|||||||
IsActive = true,
|
IsActive = true,
|
||||||
ActivatedAt = activationDate,
|
ActivatedAt = activationDate,
|
||||||
InitialContribution = 56_000_000,
|
InitialContribution = 56_000_000,
|
||||||
|
GiftValue = giftValue, // مقدار از تنظیمات
|
||||||
TotalEarned = 0,
|
TotalEarned = 0,
|
||||||
PurchaseMethod = user.PackagePurchaseMethod
|
PurchaseMethod = user.PackagePurchaseMethod
|
||||||
};
|
};
|
||||||
@@ -150,9 +175,10 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
|||||||
_context.ClubMemberships.Add(entity);
|
_context.ClubMemberships.Add(entity);
|
||||||
|
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"Created new club membership for UserId {UserId} via {Method}",
|
"Created new club membership for UserId {UserId} via {Method}, GiftValue: {GiftValue}",
|
||||||
user.Id,
|
user.Id,
|
||||||
user.PackagePurchaseMethod
|
user.PackagePurchaseMethod,
|
||||||
|
giftValue
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -86,11 +86,17 @@ public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWe
|
|||||||
// اعمال محدودیت سقف تعادل هفتگی (مثلاً 300)
|
// اعمال محدودیت سقف تعادل هفتگی (مثلاً 300)
|
||||||
var cappedBalances = Math.Min(totalBalances, maxWeeklyBalances);
|
var cappedBalances = Math.Min(totalBalances, maxWeeklyBalances);
|
||||||
|
|
||||||
// محاسبه باقیمانده برای هفته بعد
|
// محاسبه باقیمانده برای هفته بعد (Flash Out Logic)
|
||||||
// اگر تعادل بیش از سقف بود، مازاد هم به remainder اضافه میشود
|
// مثال: چپ=350، راست=450، سقف=300
|
||||||
var excessBalances = totalBalances - cappedBalances;
|
// تعادل پرداختی = MIN(MIN(350, 450), 300) = 300
|
||||||
var leftRemainder = (leftTotal - totalBalances) + (leftTotal >= rightTotal ? excessBalances : 0);
|
// از هر طرف نصف تعادل پرداختی کسر میشود: 300÷2 = 150
|
||||||
var rightRemainder = (rightTotal - totalBalances) + (rightTotal >= leftTotal ? excessBalances : 0);
|
// باقیمانده چپ: 350 - 150 = 200
|
||||||
|
// باقیمانده راست: 450 - 150 = 300
|
||||||
|
var balancesToPay = cappedBalances; // تعادل نهایی قابل پرداخت
|
||||||
|
var balancesConsumedPerSide = balancesToPay / 2; // هر طرف نصف تعادل را مصرف میکند
|
||||||
|
|
||||||
|
var leftRemainder = leftTotal - balancesConsumedPerSide;
|
||||||
|
var rightRemainder = rightTotal - balancesConsumedPerSide;
|
||||||
|
|
||||||
// محاسبه سهم استخر (20% از مجموع فعالسازیهای جدید کل شبکه)
|
// محاسبه سهم استخر (20% از مجموع فعالسازیهای جدید کل شبکه)
|
||||||
// طبق گفته دکتر: کل افراد جدید در شبکه × هزینه فعالسازی × 20%
|
// طبق گفته دکتر: کل افراد جدید در شبکه × هزینه فعالسازی × 20%
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalReports;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query برای دریافت گزارش برداشتها
|
||||||
|
/// </summary>
|
||||||
|
public record GetWithdrawalReportsQuery : IRequest<WithdrawalReportsDto>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// تاریخ شروع
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? StartDate { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تاریخ پایان
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? EndDate { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نوع بازه زمانی (روزانه، هفتگی، ماهانه)
|
||||||
|
/// </summary>
|
||||||
|
public ReportPeriodType PeriodType { get; init; } = ReportPeriodType.Daily;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// فیلتر بر اساس وضعیت
|
||||||
|
/// </summary>
|
||||||
|
public CommissionPayoutStatus? Status { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه کاربر (برای فیلتر کردن بر اساس کاربر خاص)
|
||||||
|
/// </summary>
|
||||||
|
public long? UserId { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نوع بازه زمانی گزارش
|
||||||
|
/// </summary>
|
||||||
|
public enum ReportPeriodType
|
||||||
|
{
|
||||||
|
Daily = 1,
|
||||||
|
Weekly = 2,
|
||||||
|
Monthly = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO گزارش برداشتها
|
||||||
|
/// </summary>
|
||||||
|
public class WithdrawalReportsDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// گزارشهای بازههای زمانی
|
||||||
|
/// </summary>
|
||||||
|
public List<PeriodReportDto> PeriodReports { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// خلاصه کلی
|
||||||
|
/// </summary>
|
||||||
|
public WithdrawalSummaryDto Summary { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// گزارش یک بازه زمانی
|
||||||
|
/// </summary>
|
||||||
|
public class PeriodReportDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// عنوان بازه (مثلاً "هفته 1" یا "دی ماه")
|
||||||
|
/// </summary>
|
||||||
|
public string PeriodLabel { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تاریخ شروع بازه
|
||||||
|
/// </summary>
|
||||||
|
public DateTime StartDate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تاریخ پایان بازه
|
||||||
|
/// </summary>
|
||||||
|
public DateTime EndDate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تعداد کل درخواستها
|
||||||
|
/// </summary>
|
||||||
|
public int TotalRequests { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تعداد درخواستهای در انتظار
|
||||||
|
/// </summary>
|
||||||
|
public int PendingCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تعداد درخواستهای تأیید شده
|
||||||
|
/// </summary>
|
||||||
|
public int ApprovedCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تعداد درخواستهای رد شده
|
||||||
|
/// </summary>
|
||||||
|
public int RejectedCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تعداد درخواستهای موفق
|
||||||
|
/// </summary>
|
||||||
|
public int CompletedCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تعداد درخواستهای ناموفق
|
||||||
|
/// </summary>
|
||||||
|
public int FailedCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// مجموع مبلغ درخواستها
|
||||||
|
/// </summary>
|
||||||
|
public long TotalAmount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// مجموع مبلغ پرداخت شده
|
||||||
|
/// </summary>
|
||||||
|
public long PaidAmount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// مجموع مبلغ در انتظار
|
||||||
|
/// </summary>
|
||||||
|
public long PendingAmount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// خلاصه کلی برداشتها
|
||||||
|
/// </summary>
|
||||||
|
public class WithdrawalSummaryDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// تعداد کل درخواستها
|
||||||
|
/// </summary>
|
||||||
|
public int TotalRequests { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// مجموع کل مبالغ
|
||||||
|
/// </summary>
|
||||||
|
public long TotalAmount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// مجموع مبلغ پرداخت شده
|
||||||
|
/// </summary>
|
||||||
|
public long TotalPaid { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// مجموع مبلغ در انتظار
|
||||||
|
/// </summary>
|
||||||
|
public long TotalPending { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// مجموع مبلغ رد شده
|
||||||
|
/// </summary>
|
||||||
|
public long TotalRejected { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// میانگین مبلغ هر درخواست
|
||||||
|
/// </summary>
|
||||||
|
public long AverageAmount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تعداد کاربران منحصر به فرد
|
||||||
|
/// </summary>
|
||||||
|
public int UniqueUsers { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// درصد موفقیت (Completed / Total)
|
||||||
|
/// </summary>
|
||||||
|
public decimal SuccessRate { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalReports;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handler برای دریافت گزارش برداشتها
|
||||||
|
/// </summary>
|
||||||
|
public class GetWithdrawalReportsQueryHandler : IRequestHandler<GetWithdrawalReportsQuery, WithdrawalReportsDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public GetWithdrawalReportsQueryHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<WithdrawalReportsDto> Handle(GetWithdrawalReportsQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// تعیین بازه زمانی پیشفرض (30 روز گذشته)
|
||||||
|
var endDate = request.EndDate ?? DateTime.UtcNow;
|
||||||
|
var startDate = request.StartDate ?? endDate.AddDays(-30);
|
||||||
|
|
||||||
|
// Query پایه
|
||||||
|
var query = _context.UserCommissionPayouts
|
||||||
|
.Where(p => p.Created >= startDate && p.Created <= endDate);
|
||||||
|
|
||||||
|
// فیلتر بر اساس وضعیت
|
||||||
|
if (request.Status.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(p => p.Status == request.Status.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// فیلتر بر اساس کاربر
|
||||||
|
if (request.UserId.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(p => p.UserId == request.UserId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var payouts = await query
|
||||||
|
.OrderBy(p => p.Created)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
// گروهبندی بر اساس نوع بازه
|
||||||
|
var periodReports = request.PeriodType switch
|
||||||
|
{
|
||||||
|
ReportPeriodType.Daily => GroupByDay(payouts, startDate, endDate),
|
||||||
|
ReportPeriodType.Weekly => GroupByWeek(payouts, startDate, endDate),
|
||||||
|
ReportPeriodType.Monthly => GroupByMonth(payouts, startDate, endDate),
|
||||||
|
_ => GroupByDay(payouts, startDate, endDate)
|
||||||
|
};
|
||||||
|
|
||||||
|
// محاسبه خلاصه کلی
|
||||||
|
var summary = CalculateSummary(payouts);
|
||||||
|
|
||||||
|
return new WithdrawalReportsDto
|
||||||
|
{
|
||||||
|
PeriodReports = periodReports,
|
||||||
|
Summary = summary
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PeriodReportDto> GroupByDay(List<Domain.Entities.Commission.UserCommissionPayout> payouts, DateTime startDate, DateTime endDate)
|
||||||
|
{
|
||||||
|
var reports = new List<PeriodReportDto>();
|
||||||
|
var currentDate = startDate.Date;
|
||||||
|
|
||||||
|
while (currentDate <= endDate.Date)
|
||||||
|
{
|
||||||
|
var dayPayouts = payouts.Where(p => p.Created.Date == currentDate).ToList();
|
||||||
|
|
||||||
|
reports.Add(new PeriodReportDto
|
||||||
|
{
|
||||||
|
PeriodLabel = currentDate.ToString("yyyy-MM-dd"),
|
||||||
|
StartDate = currentDate,
|
||||||
|
EndDate = currentDate.AddDays(1).AddSeconds(-1),
|
||||||
|
TotalRequests = dayPayouts.Count,
|
||||||
|
PendingCount = dayPayouts.Count(p => p.Status == CommissionPayoutStatus.Pending ||
|
||||||
|
p.Status == CommissionPayoutStatus.WithdrawRequested),
|
||||||
|
ApprovedCount = 0, // تایید جداگانه نداریم
|
||||||
|
RejectedCount = dayPayouts.Count(p => p.Status == CommissionPayoutStatus.Cancelled),
|
||||||
|
CompletedCount = dayPayouts.Count(p => p.Status == CommissionPayoutStatus.Withdrawn),
|
||||||
|
FailedCount = dayPayouts.Count(p => p.Status == CommissionPayoutStatus.PaymentFailed),
|
||||||
|
TotalAmount = dayPayouts.Sum(p => p.TotalAmount),
|
||||||
|
PaidAmount = dayPayouts.Where(p => p.Status == CommissionPayoutStatus.Withdrawn).Sum(p => p.TotalAmount),
|
||||||
|
PendingAmount = dayPayouts.Where(p => p.Status == CommissionPayoutStatus.Pending ||
|
||||||
|
p.Status == CommissionPayoutStatus.WithdrawRequested).Sum(p => p.TotalAmount)
|
||||||
|
});
|
||||||
|
|
||||||
|
currentDate = currentDate.AddDays(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PeriodReportDto> GroupByWeek(List<Domain.Entities.Commission.UserCommissionPayout> payouts, DateTime startDate, DateTime endDate)
|
||||||
|
{
|
||||||
|
var reports = new List<PeriodReportDto>();
|
||||||
|
var currentWeekStart = startDate.Date;
|
||||||
|
|
||||||
|
int weekNumber = 1;
|
||||||
|
while (currentWeekStart <= endDate)
|
||||||
|
{
|
||||||
|
var weekEnd = currentWeekStart.AddDays(7).AddSeconds(-1);
|
||||||
|
if (weekEnd > endDate)
|
||||||
|
weekEnd = endDate;
|
||||||
|
|
||||||
|
var weekPayouts = payouts.Where(p => p.Created >= currentWeekStart && p.Created <= weekEnd).ToList();
|
||||||
|
|
||||||
|
reports.Add(new PeriodReportDto
|
||||||
|
{
|
||||||
|
PeriodLabel = $"هفته {weekNumber}",
|
||||||
|
StartDate = currentWeekStart,
|
||||||
|
EndDate = weekEnd,
|
||||||
|
TotalRequests = weekPayouts.Count,
|
||||||
|
PendingCount = weekPayouts.Count(p => p.Status == CommissionPayoutStatus.Pending ||
|
||||||
|
p.Status == CommissionPayoutStatus.WithdrawRequested),
|
||||||
|
ApprovedCount = 0, // تایید جداگانه نداریم
|
||||||
|
RejectedCount = weekPayouts.Count(p => p.Status == CommissionPayoutStatus.Cancelled),
|
||||||
|
CompletedCount = weekPayouts.Count(p => p.Status == CommissionPayoutStatus.Withdrawn),
|
||||||
|
FailedCount = weekPayouts.Count(p => p.Status == CommissionPayoutStatus.PaymentFailed),
|
||||||
|
TotalAmount = weekPayouts.Sum(p => p.TotalAmount),
|
||||||
|
PaidAmount = weekPayouts.Where(p => p.Status == CommissionPayoutStatus.Withdrawn).Sum(p => p.TotalAmount),
|
||||||
|
PendingAmount = weekPayouts.Where(p => p.Status == CommissionPayoutStatus.Pending ||
|
||||||
|
p.Status == CommissionPayoutStatus.WithdrawRequested).Sum(p => p.TotalAmount)
|
||||||
|
});
|
||||||
|
|
||||||
|
currentWeekStart = currentWeekStart.AddDays(7);
|
||||||
|
weekNumber++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PeriodReportDto> GroupByMonth(List<Domain.Entities.Commission.UserCommissionPayout> payouts, DateTime startDate, DateTime endDate)
|
||||||
|
{
|
||||||
|
var reports = new List<PeriodReportDto>();
|
||||||
|
var currentMonthStart = new DateTime(startDate.Year, startDate.Month, 1);
|
||||||
|
|
||||||
|
while (currentMonthStart <= endDate)
|
||||||
|
{
|
||||||
|
var monthEnd = currentMonthStart.AddMonths(1).AddSeconds(-1);
|
||||||
|
if (monthEnd > endDate)
|
||||||
|
monthEnd = endDate;
|
||||||
|
|
||||||
|
var monthPayouts = payouts.Where(p => p.Created >= currentMonthStart && p.Created <= monthEnd).ToList();
|
||||||
|
|
||||||
|
var persianMonthName = GetPersianMonthName(currentMonthStart.Month);
|
||||||
|
|
||||||
|
reports.Add(new PeriodReportDto
|
||||||
|
{
|
||||||
|
PeriodLabel = $"{persianMonthName} {currentMonthStart.Year}",
|
||||||
|
StartDate = currentMonthStart,
|
||||||
|
EndDate = monthEnd,
|
||||||
|
TotalRequests = monthPayouts.Count,
|
||||||
|
PendingCount = monthPayouts.Count(p => p.Status == CommissionPayoutStatus.Pending ||
|
||||||
|
p.Status == CommissionPayoutStatus.WithdrawRequested),
|
||||||
|
ApprovedCount = 0, // تایید جداگانه نداریم
|
||||||
|
RejectedCount = monthPayouts.Count(p => p.Status == CommissionPayoutStatus.Cancelled),
|
||||||
|
CompletedCount = monthPayouts.Count(p => p.Status == CommissionPayoutStatus.Withdrawn),
|
||||||
|
FailedCount = monthPayouts.Count(p => p.Status == CommissionPayoutStatus.PaymentFailed),
|
||||||
|
TotalAmount = monthPayouts.Sum(p => p.TotalAmount),
|
||||||
|
PaidAmount = monthPayouts.Where(p => p.Status == CommissionPayoutStatus.Withdrawn).Sum(p => p.TotalAmount),
|
||||||
|
PendingAmount = monthPayouts.Where(p => p.Status == CommissionPayoutStatus.Pending ||
|
||||||
|
p.Status == CommissionPayoutStatus.WithdrawRequested).Sum(p => p.TotalAmount)
|
||||||
|
});
|
||||||
|
|
||||||
|
currentMonthStart = currentMonthStart.AddMonths(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WithdrawalSummaryDto CalculateSummary(List<Domain.Entities.Commission.UserCommissionPayout> payouts)
|
||||||
|
{
|
||||||
|
var totalRequests = payouts.Count;
|
||||||
|
var completedCount = payouts.Count(p => p.Status == CommissionPayoutStatus.Withdrawn);
|
||||||
|
|
||||||
|
return new WithdrawalSummaryDto
|
||||||
|
{
|
||||||
|
TotalRequests = totalRequests,
|
||||||
|
TotalAmount = payouts.Sum(p => p.TotalAmount),
|
||||||
|
TotalPaid = payouts.Where(p => p.Status == CommissionPayoutStatus.Withdrawn).Sum(p => p.TotalAmount),
|
||||||
|
TotalPending = payouts.Where(p => p.Status == CommissionPayoutStatus.Pending ||
|
||||||
|
p.Status == CommissionPayoutStatus.WithdrawRequested).Sum(p => p.TotalAmount),
|
||||||
|
TotalRejected = payouts.Where(p => p.Status == CommissionPayoutStatus.Cancelled).Sum(p => p.TotalAmount),
|
||||||
|
AverageAmount = totalRequests > 0 ? payouts.Sum(p => p.TotalAmount) / totalRequests : 0,
|
||||||
|
UniqueUsers = payouts.Select(p => p.UserId).Distinct().Count(),
|
||||||
|
SuccessRate = totalRequests > 0 ? (decimal)completedCount / totalRequests * 100 : 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetPersianMonthName(int month)
|
||||||
|
{
|
||||||
|
return month switch
|
||||||
|
{
|
||||||
|
1 => "فروردین",
|
||||||
|
2 => "اردیبهشت",
|
||||||
|
3 => "خرداد",
|
||||||
|
4 => "تیر",
|
||||||
|
5 => "مرداد",
|
||||||
|
6 => "شهریور",
|
||||||
|
7 => "مهر",
|
||||||
|
8 => "آبان",
|
||||||
|
9 => "آذر",
|
||||||
|
10 => "دی",
|
||||||
|
11 => "بهمن",
|
||||||
|
12 => "اسفند",
|
||||||
|
_ => month.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,39 @@
|
|||||||
|
using CMSMicroservice.Domain.Entities.Payment;
|
||||||
|
using CMSMicroservice.Domain.Entities.Message;
|
||||||
|
using CMSMicroservice.Domain.Entities.Order;
|
||||||
|
using CMSMicroservice.Domain.Entities.DiscountShop;
|
||||||
|
|
||||||
namespace CMSMicroservice.Application.Common.Interfaces;
|
namespace CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
|
||||||
public interface IApplicationDbContext
|
public interface IApplicationDbContext
|
||||||
{
|
{
|
||||||
DbSet<UserAddress> UserAddresss { get; }
|
DbSet<UserAddress> UserAddresses { get; }
|
||||||
DbSet<Package> Packages { get; }
|
DbSet<Package> Packages { get; }
|
||||||
DbSet<Role> Roles { get; }
|
DbSet<Role> Roles { get; }
|
||||||
DbSet<Category> Categorys { get; }
|
DbSet<Category> Categories { get; }
|
||||||
DbSet<UserRole> UserRoles { get; }
|
DbSet<UserRole> UserRoles { get; }
|
||||||
DbSet<UserCarts> UserCartss { get; }
|
DbSet<UserCart> UserCarts { get; }
|
||||||
DbSet<ProductGallerys> ProductGalleryss { get; }
|
DbSet<ProductGallery> ProductGalleries { get; }
|
||||||
DbSet<FactorDetails> FactorDetailss { get; }
|
DbSet<FactorDetails> FactorDetails { get; }
|
||||||
DbSet<Products> Productss { get; }
|
DbSet<Product> Products { get; }
|
||||||
DbSet<ProductImages> ProductImagess { get; }
|
DbSet<ProductImage> ProductImages { get; }
|
||||||
DbSet<User> Users { get; }
|
DbSet<User> Users { get; }
|
||||||
DbSet<OtpToken> OtpTokens { get; }
|
DbSet<OtpToken> OtpTokens { get; }
|
||||||
DbSet<Contract> Contracts { get; }
|
DbSet<Contract> Contracts { get; }
|
||||||
DbSet<UserContract> UserContracts { get; }
|
DbSet<UserContract> UserContracts { get; }
|
||||||
DbSet<Tag> Tags { get; }
|
DbSet<Tag> Tags { get; }
|
||||||
DbSet<PruductCategory> PruductCategorys { get; }
|
DbSet<ProductCategory> ProductCategories { get; }
|
||||||
DbSet<PruductTag> PruductTags { get; }
|
DbSet<ProductTag> ProductTags { get; }
|
||||||
DbSet<Transactions> Transactionss { get; }
|
DbSet<Transaction> Transactions { get; }
|
||||||
DbSet<UserOrder> UserOrders { get; }
|
DbSet<UserOrder> UserOrders { get; }
|
||||||
|
DbSet<OrderVAT> OrderVATs { get; }
|
||||||
|
DbSet<UserPackagePurchase> UserPackagePurchases { get; }
|
||||||
DbSet<UserWallet> UserWallets { get; }
|
DbSet<UserWallet> UserWallets { get; }
|
||||||
DbSet<UserWalletChangeLog> UserWalletChangeLogs { get; }
|
DbSet<UserWalletChangeLog> UserWalletChangeLogs { get; }
|
||||||
DbSet<SystemConfiguration> SystemConfigurations { get; }
|
DbSet<SystemConfiguration> SystemConfigurations { get; }
|
||||||
DbSet<SystemConfigurationHistory> SystemConfigurationHistories { get; }
|
DbSet<SystemConfigurationHistory> SystemConfigurationHistories { get; }
|
||||||
|
DbSet<ManualPayment> ManualPayments { get; }
|
||||||
|
DbSet<PublicMessage> PublicMessages { get; }
|
||||||
DbSet<ClubMembership> ClubMemberships { get; }
|
DbSet<ClubMembership> ClubMemberships { get; }
|
||||||
DbSet<ClubMembershipHistory> ClubMembershipHistories { get; }
|
DbSet<ClubMembershipHistory> ClubMembershipHistories { get; }
|
||||||
DbSet<ClubFeature> ClubFeatures { get; }
|
DbSet<ClubFeature> ClubFeatures { get; }
|
||||||
@@ -36,5 +45,14 @@ public interface IApplicationDbContext
|
|||||||
DbSet<CommissionPayoutHistory> CommissionPayoutHistories { get; }
|
DbSet<CommissionPayoutHistory> CommissionPayoutHistories { get; }
|
||||||
DbSet<WorkerExecutionLog> WorkerExecutionLogs { get; }
|
DbSet<WorkerExecutionLog> WorkerExecutionLogs { get; }
|
||||||
DbSet<DayaLoanContract> DayaLoanContracts { get; }
|
DbSet<DayaLoanContract> DayaLoanContracts { get; }
|
||||||
|
|
||||||
|
// ============= Discount Shop =============
|
||||||
|
DbSet<DiscountProduct> DiscountProducts { get; }
|
||||||
|
DbSet<DiscountCategory> DiscountCategories { get; }
|
||||||
|
DbSet<DiscountProductCategory> DiscountProductCategories { get; }
|
||||||
|
DbSet<DiscountShoppingCart> DiscountShoppingCarts { get; }
|
||||||
|
DbSet<DiscountOrder> DiscountOrders { get; }
|
||||||
|
DbSet<DiscountOrderDetail> DiscountOrderDetails { get; }
|
||||||
|
|
||||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ public class UserCartsProfile : IRegister
|
|||||||
{
|
{
|
||||||
void IRegister.Register(TypeAdapterConfig config)
|
void IRegister.Register(TypeAdapterConfig config)
|
||||||
{
|
{
|
||||||
config.NewConfig<UserCarts,GetAllUserCartsByFilterResponseModel>()
|
config.NewConfig<UserCart,GetAllUserCartsByFilterResponseModel>()
|
||||||
.Map(dest => dest.Id, src => src.Id)
|
.Map(dest => dest.Id, src => src.Id)
|
||||||
.Map(dest => dest.Count, src => src.Count)
|
.Map(dest => dest.Count, src => src.Count)
|
||||||
.Map(dest => dest.ProductId, src => src.ProductId)
|
.Map(dest => dest.ProductId, src => src.ProductId)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class UserOrderProfile : IRegister
|
|||||||
.Map(dest => dest.UserAddressId, src => src.UserAddressId)
|
.Map(dest => dest.UserAddressId, src => src.UserAddressId)
|
||||||
.Map(dest => dest.PaymentMethod, src => src.PaymentMethod)
|
.Map(dest => dest.PaymentMethod, src => src.PaymentMethod)
|
||||||
.Map(dest => dest.UserAddressText, src => src.UserAddress.Address)
|
.Map(dest => dest.UserAddressText, src => src.UserAddress.Address)
|
||||||
.Map(dest => dest.FactorDetails, src => src.FactorDetailss.Select(s=>s.Adapt<GetUserOrderResponseFactorDetail>()))
|
.Map(dest => dest.FactorDetails, src => src.FactorDetails.Select(s=>s.Adapt<GetUserOrderResponseFactorDetail>()))
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ public class UserOrderProfile : IRegister
|
|||||||
.Map(dest => dest.UserAddressId, src => src.UserAddressId)
|
.Map(dest => dest.UserAddressId, src => src.UserAddressId)
|
||||||
.Map(dest => dest.PaymentMethod, src => src.PaymentMethod)
|
.Map(dest => dest.PaymentMethod, src => src.PaymentMethod)
|
||||||
.Map(dest => dest.UserAddressText, src => src.UserAddress.Address)
|
.Map(dest => dest.UserAddressText, src => src.UserAddress.Address)
|
||||||
.Map(dest => dest.FactorDetails, src => src.FactorDetailss.Select(s=>s.Adapt<GetUserOrderResponseFactorDetail>()))
|
.Map(dest => dest.FactorDetails, src => src.FactorDetails.Select(s=>s.Adapt<GetUserOrderResponseFactorDetail>()))
|
||||||
;
|
;
|
||||||
|
|
||||||
config.NewConfig<FactorDetails,GetUserOrderResponseFactorDetail>()
|
config.NewConfig<FactorDetails,GetUserOrderResponseFactorDetail>()
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.ConfigurationCQ.Commands.SeedVATConfiguration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Seed initial VAT configuration
|
||||||
|
/// نرخ مالیات پیشفرض ۹٪
|
||||||
|
/// </summary>
|
||||||
|
public class SeedVATConfigurationCommand : IRequest<Unit>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SeedVATConfigurationCommandHandler : IRequestHandler<SeedVATConfigurationCommand, Unit>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
private readonly ILogger<SeedVATConfigurationCommandHandler> _logger;
|
||||||
|
|
||||||
|
public SeedVATConfigurationCommandHandler(
|
||||||
|
IApplicationDbContext context,
|
||||||
|
ILogger<SeedVATConfigurationCommandHandler> logger)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> Handle(SeedVATConfigurationCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var configs = new[]
|
||||||
|
{
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Scope = ConfigurationScope.VAT,
|
||||||
|
Key = "Rate",
|
||||||
|
Value = "0.09",
|
||||||
|
Description = "نرخ مالیات بر ارزش افزوده (۹٪)"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Scope = ConfigurationScope.VAT,
|
||||||
|
Key = "IsEnabled",
|
||||||
|
Value = "true",
|
||||||
|
Description = "فعال/غیرفعال بودن محاسبه مالیات"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var config in configs)
|
||||||
|
{
|
||||||
|
var exists = _context.SystemConfigurations
|
||||||
|
.Any(x => x.Scope == config.Scope && x.Key == config.Key);
|
||||||
|
|
||||||
|
if (!exists)
|
||||||
|
{
|
||||||
|
_context.SystemConfigurations.Add(new Domain.Entities.Configuration.SystemConfiguration
|
||||||
|
{
|
||||||
|
Scope = config.Scope,
|
||||||
|
Key = config.Key,
|
||||||
|
Value = config.Value,
|
||||||
|
Description = config.Description
|
||||||
|
});
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"VAT configuration seeded: {Scope}.{Key} = {Value}",
|
||||||
|
config.Scope,
|
||||||
|
config.Key,
|
||||||
|
config.Value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ public class ProcessDayaLoanApprovalCommandHandler : IRequestHandler<ProcessDaya
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ایجاد تراکنش با RefId = شماره قرارداد دایا
|
// ایجاد تراکنش با RefId = شماره قرارداد دایا
|
||||||
var transaction = new Transactions
|
var transaction = new Transaction
|
||||||
{
|
{
|
||||||
Amount = request.WalletAmount + request.LockedWalletAmount + request.DiscountWalletAmount, // 168 میلیون
|
Amount = request.WalletAmount + request.LockedWalletAmount + request.DiscountWalletAmount, // 168 میلیون
|
||||||
Description = $"دریافت اعتبار دایا - قرارداد {request.ContractNumber}",
|
Description = $"دریافت اعتبار دایا - قرارداد {request.ContractNumber}",
|
||||||
@@ -41,7 +41,7 @@ public class ProcessDayaLoanApprovalCommandHandler : IRequestHandler<ProcessDaya
|
|||||||
Type = TransactionType.DepositExternal1
|
Type = TransactionType.DepositExternal1
|
||||||
};
|
};
|
||||||
|
|
||||||
await _context.Transactionss.AddAsync(transaction, cancellationToken);
|
await _context.Transactions.AddAsync(transaction, cancellationToken);
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
// یافتن یا ایجاد کیف پول کاربر
|
// یافتن یا ایجاد کیف پول کاربر
|
||||||
@@ -119,14 +119,14 @@ public class ProcessDayaLoanApprovalCommandHandler : IRequestHandler<ProcessDaya
|
|||||||
// تنظیم نحوه خرید پکیج به DayaLoan
|
// تنظیم نحوه خرید پکیج به DayaLoan
|
||||||
user.PackagePurchaseMethod = PackagePurchaseMethod.DayaLoan;
|
user.PackagePurchaseMethod = PackagePurchaseMethod.DayaLoan;
|
||||||
|
|
||||||
// ثبت سفارش پکیج طلایی
|
// ثبت سفارش پکیج (فعلاً پکیج طلایی)
|
||||||
var goldenPackage = await _context.Packages
|
var goldenPackage = await _context.Packages
|
||||||
.FirstOrDefaultAsync(p => p.Title.Contains("طلایی") || p.Title.Contains("Golden"), cancellationToken);
|
.FirstOrDefaultAsync(p => p.Title.Contains("طلایی") || p.Title.Contains("Golden"), cancellationToken);
|
||||||
|
|
||||||
if (goldenPackage != null)
|
if (goldenPackage != null)
|
||||||
{
|
{
|
||||||
// پیدا کردن آدرس پیشفرض کاربر
|
// پیدا کردن آدرس پیشفرض کاربر
|
||||||
var defaultAddress = await _context.UserAddresss
|
var defaultAddress = await _context.UserAddresses
|
||||||
.Where(a => a.UserId == request.UserId)
|
.Where(a => a.UserId == request.UserId)
|
||||||
.OrderByDescending(a => a.Created)
|
.OrderByDescending(a => a.Created)
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.AddToCart;
|
||||||
|
|
||||||
|
public class AddToCartCommand : IRequest<AddToCartResponseDto>
|
||||||
|
{
|
||||||
|
public long UserId { get; set; }
|
||||||
|
public long ProductId { get; set; }
|
||||||
|
public int Count { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddToCartResponseDto
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public long CartItemId { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Entities.DiscountShop;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.AddToCart;
|
||||||
|
|
||||||
|
public class AddToCartCommandHandler : IRequestHandler<AddToCartCommand, AddToCartResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public AddToCartCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AddToCartResponseDto> Handle(AddToCartCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Check if product exists and is active
|
||||||
|
var product = await _context.DiscountProducts
|
||||||
|
.FirstOrDefaultAsync(p => p.Id == request.ProductId && p.IsActive, cancellationToken);
|
||||||
|
|
||||||
|
if (product == null)
|
||||||
|
{
|
||||||
|
return new AddToCartResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "محصول یافت نشد یا غیرفعال است"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check stock availability
|
||||||
|
if (product.RemainingCount < request.Count)
|
||||||
|
{
|
||||||
|
return new AddToCartResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = $"موجودی کافی نیست. موجودی فعلی: {product.RemainingCount}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if item already exists in cart
|
||||||
|
var existingCartItem = await _context.DiscountShoppingCarts
|
||||||
|
.FirstOrDefaultAsync(c => c.UserId == request.UserId && c.ProductId == request.ProductId, cancellationToken);
|
||||||
|
|
||||||
|
if (existingCartItem != null)
|
||||||
|
{
|
||||||
|
// Update quantity
|
||||||
|
var newCount = existingCartItem.Count + request.Count;
|
||||||
|
|
||||||
|
if (product.RemainingCount < newCount)
|
||||||
|
{
|
||||||
|
return new AddToCartResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = $"موجودی کافی نیست. موجودی فعلی: {product.RemainingCount}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
existingCartItem.Count = newCount;
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new AddToCartResponseDto
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "تعداد محصول در سبد خرید بهروزرسانی شد",
|
||||||
|
CartItemId = existingCartItem.Id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new item to cart
|
||||||
|
var cartItem = new DiscountShoppingCart
|
||||||
|
{
|
||||||
|
UserId = request.UserId,
|
||||||
|
ProductId = request.ProductId,
|
||||||
|
Count = request.Count
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.DiscountShoppingCarts.Add(cartItem);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new AddToCartResponseDto
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "محصول به سبد خرید اضافه شد",
|
||||||
|
CartItemId = cartItem.Id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.AddToCart;
|
||||||
|
|
||||||
|
public class AddToCartCommandValidator : AbstractValidator<AddToCartCommand>
|
||||||
|
{
|
||||||
|
public AddToCartCommandValidator()
|
||||||
|
{
|
||||||
|
RuleFor(v => v.UserId)
|
||||||
|
.GreaterThan(0).WithMessage("شناسه کاربر نامعتبر است");
|
||||||
|
|
||||||
|
RuleFor(v => v.ProductId)
|
||||||
|
.GreaterThan(0).WithMessage("شناسه محصول نامعتبر است");
|
||||||
|
|
||||||
|
RuleFor(v => v.Count)
|
||||||
|
.GreaterThan(0).WithMessage("تعداد باید بیشتر از صفر باشد");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.ClearCart;
|
||||||
|
|
||||||
|
public class ClearCartCommand : IRequest<bool>
|
||||||
|
{
|
||||||
|
public long UserId { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Entities;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.ClearCart;
|
||||||
|
|
||||||
|
public class ClearCartCommandHandler : IRequestHandler<ClearCartCommand, bool>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public ClearCartCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Handle(ClearCartCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var cartItems = await _context.DiscountShoppingCarts
|
||||||
|
.Where(c => c.UserId == request.UserId)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!cartItems.Any())
|
||||||
|
{
|
||||||
|
return true; // Cart already empty
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.DiscountShoppingCarts.RemoveRange(cartItems);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment;
|
||||||
|
|
||||||
|
public class CompleteOrderPaymentCommand : IRequest<CompleteOrderPaymentResponseDto>
|
||||||
|
{
|
||||||
|
public long OrderId { get; set; }
|
||||||
|
public long TransactionId { get; set; }
|
||||||
|
public bool PaymentSuccess { get; set; }
|
||||||
|
public string? RefId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CompleteOrderPaymentResponseDto
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public long? OrderId { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment;
|
||||||
|
|
||||||
|
public class CompleteOrderPaymentCommandHandler : IRequestHandler<CompleteOrderPaymentCommand, CompleteOrderPaymentResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public CompleteOrderPaymentCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CompleteOrderPaymentResponseDto> Handle(CompleteOrderPaymentCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var order = await _context.DiscountOrders
|
||||||
|
.Include(o => o.OrderDetails)
|
||||||
|
.ThenInclude(od => od.Product)
|
||||||
|
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
|
||||||
|
|
||||||
|
if (order == null)
|
||||||
|
{
|
||||||
|
return new CompleteOrderPaymentResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "سفارش یافت نشد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var transaction = await _context.Transactions
|
||||||
|
.FirstOrDefaultAsync(t => t.Id == request.TransactionId, cancellationToken);
|
||||||
|
|
||||||
|
if (transaction == null)
|
||||||
|
{
|
||||||
|
return new CompleteOrderPaymentResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "تراکنش یافت نشد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.PaymentSuccess)
|
||||||
|
{
|
||||||
|
// Update transaction
|
||||||
|
transaction.PaymentStatus = PaymentStatus.Success;
|
||||||
|
transaction.PaymentDate = DateTime.UtcNow;
|
||||||
|
transaction.RefId = request.RefId;
|
||||||
|
|
||||||
|
// Update order
|
||||||
|
order.PaymentStatus = PaymentStatus.Success;
|
||||||
|
order.PaymentDate = DateTime.UtcNow;
|
||||||
|
order.DeliveryStatus = DeliveryStatus.InTransit;
|
||||||
|
|
||||||
|
// Deduct discount balance from user wallet
|
||||||
|
var userWallet = await _context.UserWallets
|
||||||
|
.FirstOrDefaultAsync(w => w.UserId == order.UserId, cancellationToken);
|
||||||
|
|
||||||
|
if (userWallet != null)
|
||||||
|
{
|
||||||
|
userWallet.DiscountBalance -= order.DiscountBalanceUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update product stock and sale count
|
||||||
|
foreach (var orderDetail in order.OrderDetails)
|
||||||
|
{
|
||||||
|
var product = orderDetail.Product;
|
||||||
|
product.RemainingCount -= orderDetail.Count;
|
||||||
|
product.SaleCount += orderDetail.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new CompleteOrderPaymentResponseDto
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "پرداخت با موفقیت انجام شد",
|
||||||
|
OrderId = order.Id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Payment failed
|
||||||
|
transaction.PaymentStatus = PaymentStatus.Reject;
|
||||||
|
order.PaymentStatus = PaymentStatus.Reject;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new CompleteOrderPaymentResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "پرداخت ناموفق بود",
|
||||||
|
OrderId = order.Id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountCategory;
|
||||||
|
|
||||||
|
public class CreateDiscountCategoryCommand : IRequest<long>
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public string? ImagePath { get; set; }
|
||||||
|
public long? ParentCategoryId { get; set; }
|
||||||
|
public int SortOrder { get; set; }
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Entities;
|
||||||
|
using CMSMicroservice.Domain.Entities.DiscountShop;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountCategory;
|
||||||
|
|
||||||
|
public class CreateDiscountCategoryCommandHandler : IRequestHandler<CreateDiscountCategoryCommand, long>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public CreateDiscountCategoryCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> Handle(CreateDiscountCategoryCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// بررسی وجود دستهبندی با همین نام
|
||||||
|
var existingCategory = await _context.DiscountCategories
|
||||||
|
.FirstOrDefaultAsync(c => c.Name == request.Name, cancellationToken);
|
||||||
|
|
||||||
|
if (existingCategory != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("دستهبندی با این نام قبلاً ثبت شده است");
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی وجود دستهبندی والد
|
||||||
|
if (request.ParentCategoryId.HasValue)
|
||||||
|
{
|
||||||
|
var parentExists = await _context.DiscountCategories
|
||||||
|
.AnyAsync(c => c.Id == request.ParentCategoryId.Value, cancellationToken);
|
||||||
|
|
||||||
|
if (!parentExists)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("دستهبندی والد یافت نشد");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var category = new DiscountCategory
|
||||||
|
{
|
||||||
|
Name = request.Name,
|
||||||
|
Title = request.Title,
|
||||||
|
Description = request.Description,
|
||||||
|
ImagePath = request.ImagePath,
|
||||||
|
ParentCategoryId = request.ParentCategoryId,
|
||||||
|
SortOrder = request.SortOrder,
|
||||||
|
IsActive = request.IsActive,
|
||||||
|
Created = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.DiscountCategories.Add(category);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return category.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountCategory;
|
||||||
|
|
||||||
|
public class CreateDiscountCategoryCommandValidator : AbstractValidator<CreateDiscountCategoryCommand>
|
||||||
|
{
|
||||||
|
public CreateDiscountCategoryCommandValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Name)
|
||||||
|
.NotEmpty().WithMessage("نام دستهبندی الزامی است")
|
||||||
|
.MaximumLength(100).WithMessage("نام دستهبندی نباید بیشتر از 100 کاراکتر باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.Title)
|
||||||
|
.NotEmpty().WithMessage("عنوان دستهبندی الزامی است")
|
||||||
|
.MaximumLength(200).WithMessage("عنوان دستهبندی نباید بیشتر از 200 کاراکتر باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.Description)
|
||||||
|
.MaximumLength(1000).WithMessage("توضیحات نباید بیشتر از 1000 کاراکتر باشد")
|
||||||
|
.When(x => !string.IsNullOrEmpty(x.Description));
|
||||||
|
|
||||||
|
RuleFor(x => x.ImagePath)
|
||||||
|
.MaximumLength(500).WithMessage("مسیر تصویر نباید بیشتر از 500 کاراکتر باشد")
|
||||||
|
.When(x => !string.IsNullOrEmpty(x.ImagePath));
|
||||||
|
|
||||||
|
RuleFor(x => x.ParentCategoryId)
|
||||||
|
.GreaterThan(0).WithMessage("شناسه دستهبندی والد باید مثبت باشد")
|
||||||
|
.When(x => x.ParentCategoryId.HasValue);
|
||||||
|
|
||||||
|
RuleFor(x => x.SortOrder)
|
||||||
|
.GreaterThanOrEqualTo(0).WithMessage("ترتیب نمایش نمیتواند منفی باشد");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountProduct;
|
||||||
|
|
||||||
|
public class CreateDiscountProductCommand : IRequest<long>
|
||||||
|
{
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string ShortInfomation { get; set; }
|
||||||
|
public string FullInformation { get; set; }
|
||||||
|
public long Price { get; set; }
|
||||||
|
public int MaxDiscountPercent { get; set; }
|
||||||
|
public string ImagePath { get; set; }
|
||||||
|
public string ThumbnailPath { get; set; }
|
||||||
|
public int RemainingCount { get; set; }
|
||||||
|
public List<long> CategoryIds { get; set; } = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Entities.DiscountShop;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountProduct;
|
||||||
|
|
||||||
|
public class CreateDiscountProductCommandHandler : IRequestHandler<CreateDiscountProductCommand, long>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public CreateDiscountProductCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> Handle(CreateDiscountProductCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var product = new DiscountProduct
|
||||||
|
{
|
||||||
|
Title = request.Title,
|
||||||
|
ShortInfomation = request.ShortInfomation,
|
||||||
|
FullInformation = request.FullInformation,
|
||||||
|
Price = request.Price,
|
||||||
|
MaxDiscountPercent = request.MaxDiscountPercent,
|
||||||
|
ImagePath = request.ImagePath,
|
||||||
|
ThumbnailPath = request.ThumbnailPath,
|
||||||
|
RemainingCount = request.RemainingCount,
|
||||||
|
Rate = 0,
|
||||||
|
SaleCount = 0,
|
||||||
|
ViewCount = 0,
|
||||||
|
IsActive = true
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.DiscountProducts.Add(product);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// Add product categories
|
||||||
|
if (request.CategoryIds.Any())
|
||||||
|
{
|
||||||
|
var productCategories = request.CategoryIds.Select(categoryId => new DiscountProductCategory
|
||||||
|
{
|
||||||
|
ProductId = product.Id,
|
||||||
|
CategoryId = categoryId
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
_context.DiscountProductCategories.AddRange(productCategories);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return product.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountProduct;
|
||||||
|
|
||||||
|
public class CreateDiscountProductCommandValidator : AbstractValidator<CreateDiscountProductCommand>
|
||||||
|
{
|
||||||
|
public CreateDiscountProductCommandValidator()
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Title)
|
||||||
|
.NotEmpty().WithMessage("عنوان محصول الزامی است")
|
||||||
|
.MaximumLength(200).WithMessage("عنوان محصول نمیتواند بیشتر از 200 کاراکتر باشد");
|
||||||
|
|
||||||
|
RuleFor(v => v.ShortInfomation)
|
||||||
|
.NotEmpty().WithMessage("توضیحات کوتاه الزامی است")
|
||||||
|
.MaximumLength(500).WithMessage("توضیحات کوتاه نمیتواند بیشتر از 500 کاراکتر باشد");
|
||||||
|
|
||||||
|
RuleFor(v => v.FullInformation)
|
||||||
|
.NotEmpty().WithMessage("توضیحات کامل الزامی است")
|
||||||
|
.MaximumLength(2000).WithMessage("توضیحات کامل نمیتواند بیشتر از 2000 کاراکتر باشد");
|
||||||
|
|
||||||
|
RuleFor(v => v.Price)
|
||||||
|
.GreaterThan(0).WithMessage("قیمت باید بیشتر از صفر باشد");
|
||||||
|
|
||||||
|
RuleFor(v => v.MaxDiscountPercent)
|
||||||
|
.InclusiveBetween(0, 100).WithMessage("درصد تخفیف باید بین 0 تا 100 باشد");
|
||||||
|
|
||||||
|
RuleFor(v => v.RemainingCount)
|
||||||
|
.GreaterThanOrEqualTo(0).WithMessage("موجودی نمیتواند منفی باشد");
|
||||||
|
|
||||||
|
RuleFor(v => v.ImagePath)
|
||||||
|
.NotEmpty().WithMessage("تصویر محصول الزامی است");
|
||||||
|
|
||||||
|
RuleFor(v => v.ThumbnailPath)
|
||||||
|
.NotEmpty().WithMessage("تصویر بندانگشتی الزامی است");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountCategory;
|
||||||
|
|
||||||
|
public class DeleteDiscountCategoryCommand : IRequest<bool>
|
||||||
|
{
|
||||||
|
public long CategoryId { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Entities;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountCategory;
|
||||||
|
|
||||||
|
public class DeleteDiscountCategoryCommandHandler : IRequestHandler<DeleteDiscountCategoryCommand, bool>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public DeleteDiscountCategoryCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Handle(DeleteDiscountCategoryCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var category = await _context.DiscountCategories
|
||||||
|
.Include(c => c.ChildCategories)
|
||||||
|
.Include(c => c.ProductCategories)
|
||||||
|
.FirstOrDefaultAsync(c => c.Id == request.CategoryId, cancellationToken);
|
||||||
|
|
||||||
|
if (category == null)
|
||||||
|
{
|
||||||
|
throw new Exception($"Discount category with ID {request.CategoryId} not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if category has child categories
|
||||||
|
if (category.ChildCategories.Any())
|
||||||
|
{
|
||||||
|
throw new Exception($"Cannot delete category. It has {category.ChildCategories.Count} child categories. Please delete child categories first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if category has products
|
||||||
|
if (category.ProductCategories.Any())
|
||||||
|
{
|
||||||
|
throw new Exception($"Cannot delete category. It has {category.ProductCategories.Count} products. Please move or delete products first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.DiscountCategories.Remove(category);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountProduct;
|
||||||
|
|
||||||
|
public class DeleteDiscountProductCommand : IRequest<bool>
|
||||||
|
{
|
||||||
|
public long ProductId { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.DeleteDiscountProduct;
|
||||||
|
|
||||||
|
public class DeleteDiscountProductCommandHandler : IRequestHandler<DeleteDiscountProductCommand, bool>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public DeleteDiscountProductCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Handle(DeleteDiscountProductCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var product = await _context.DiscountProducts
|
||||||
|
.FirstOrDefaultAsync(p => p.Id == request.ProductId, cancellationToken);
|
||||||
|
|
||||||
|
if (product == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// حذف رابطههای دستهبندی
|
||||||
|
var productCategories = await _context.DiscountProductCategories
|
||||||
|
.Where(pc => pc.ProductId == request.ProductId)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
_context.DiscountProductCategories.RemoveRange(productCategories);
|
||||||
|
|
||||||
|
// حذف محصول
|
||||||
|
_context.DiscountProducts.Remove(product);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.PlaceOrder;
|
||||||
|
|
||||||
|
public class PlaceOrderCommand : IRequest<PlaceOrderResponseDto>
|
||||||
|
{
|
||||||
|
public long UserId { get; set; }
|
||||||
|
public long UserAddressId { get; set; }
|
||||||
|
public long DiscountBalanceToUse { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PlaceOrderResponseDto
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public long? OrderId { get; set; }
|
||||||
|
public long? TransactionId { get; set; }
|
||||||
|
public long TotalAmount { get; set; }
|
||||||
|
public long DiscountBalanceUsed { get; set; }
|
||||||
|
public long GatewayAmountRequired { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Entities.DiscountShop;
|
||||||
|
using CMSMicroservice.Domain.Entities.Payment;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.PlaceOrder;
|
||||||
|
|
||||||
|
public class PlaceOrderCommandHandler : IRequestHandler<PlaceOrderCommand, PlaceOrderResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public PlaceOrderCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PlaceOrderResponseDto> Handle(PlaceOrderCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Get user wallet
|
||||||
|
var userWallet = await _context.UserWallets
|
||||||
|
.FirstOrDefaultAsync(w => w.UserId == request.UserId, cancellationToken);
|
||||||
|
|
||||||
|
if (userWallet == null)
|
||||||
|
{
|
||||||
|
return new PlaceOrderResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "کیف پول کاربر یافت نشد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get cart items with products
|
||||||
|
var cartItems = await _context.DiscountShoppingCarts
|
||||||
|
.Where(c => c.UserId == request.UserId)
|
||||||
|
.Include(c => c.Product)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!cartItems.Any())
|
||||||
|
{
|
||||||
|
return new PlaceOrderResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "سبد خرید خالی است"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate stock and calculate totals
|
||||||
|
long totalAmount = 0;
|
||||||
|
long totalDiscountAmount = 0;
|
||||||
|
var orderDetails = new List<DiscountOrderDetail>();
|
||||||
|
|
||||||
|
foreach (var cartItem in cartItems)
|
||||||
|
{
|
||||||
|
var product = cartItem.Product;
|
||||||
|
|
||||||
|
// Check stock
|
||||||
|
if (product.RemainingCount < cartItem.Count)
|
||||||
|
{
|
||||||
|
return new PlaceOrderResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = $"موجودی محصول '{product.Title}' کافی نیست"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if product is active
|
||||||
|
if (!product.IsActive)
|
||||||
|
{
|
||||||
|
return new PlaceOrderResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = $"محصول '{product.Title}' غیرفعال است"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate discount for this product
|
||||||
|
var itemTotal = product.Price * cartItem.Count;
|
||||||
|
var maxDiscountForItem = (itemTotal * product.MaxDiscountPercent) / 100;
|
||||||
|
|
||||||
|
totalAmount += itemTotal;
|
||||||
|
totalDiscountAmount += maxDiscountForItem;
|
||||||
|
|
||||||
|
orderDetails.Add(new DiscountOrderDetail
|
||||||
|
{
|
||||||
|
ProductId = product.Id,
|
||||||
|
Count = cartItem.Count,
|
||||||
|
UnitPrice = product.Price,
|
||||||
|
DiscountPercentUsed = product.MaxDiscountPercent,
|
||||||
|
DiscountAmount = maxDiscountForItem,
|
||||||
|
FinalPrice = itemTotal - maxDiscountForItem
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate discount balance usage
|
||||||
|
var maxDiscountBalanceUsable = totalDiscountAmount;
|
||||||
|
var actualDiscountBalanceUsed = Math.Min(request.DiscountBalanceToUse, maxDiscountBalanceUsable);
|
||||||
|
actualDiscountBalanceUsed = Math.Min(actualDiscountBalanceUsed, userWallet.DiscountBalance);
|
||||||
|
|
||||||
|
if (actualDiscountBalanceUsed < request.DiscountBalanceToUse)
|
||||||
|
{
|
||||||
|
return new PlaceOrderResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = $"موجودی تخفیف کافی نیست. حداکثر قابل استفاده: {maxDiscountBalanceUsable:N0} تومان، موجودی شما: {userWallet.DiscountBalance:N0} تومان"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var gatewayAmountRequired = totalAmount - actualDiscountBalanceUsed;
|
||||||
|
|
||||||
|
// Calculate VAT (9%)
|
||||||
|
var vatAmount = (gatewayAmountRequired * 9) / 100;
|
||||||
|
var finalGatewayAmount = gatewayAmountRequired + vatAmount;
|
||||||
|
|
||||||
|
// Create transaction for gateway payment
|
||||||
|
var transaction = new Transaction
|
||||||
|
{
|
||||||
|
Amount = finalGatewayAmount,
|
||||||
|
Description = $"خرید از فروشگاه تخفیف - مبلغ کل: {totalAmount:N0}، اعتبار تخفیف: {actualDiscountBalanceUsed:N0}",
|
||||||
|
PaymentStatus = PaymentStatus.Pending,
|
||||||
|
Type = TransactionType.DiscountShopPurchase
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.Transactions.Add(transaction);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// Create order
|
||||||
|
var order = new DiscountOrder
|
||||||
|
{
|
||||||
|
UserId = request.UserId,
|
||||||
|
TotalAmount = totalAmount,
|
||||||
|
DiscountBalanceUsed = actualDiscountBalanceUsed,
|
||||||
|
GatewayAmountPaid = finalGatewayAmount,
|
||||||
|
VatAmount = vatAmount,
|
||||||
|
PaymentStatus = PaymentStatus.Pending,
|
||||||
|
TransactionId = transaction.Id,
|
||||||
|
UserAddressId = request.UserAddressId,
|
||||||
|
DeliveryStatus = DeliveryStatus.Pending
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.DiscountOrders.Add(order);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// Add order details
|
||||||
|
foreach (var detail in orderDetails)
|
||||||
|
{
|
||||||
|
detail.DiscountOrderId = order.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.DiscountOrderDetails.AddRange(orderDetails);
|
||||||
|
|
||||||
|
// Clear cart
|
||||||
|
_context.DiscountShoppingCarts.RemoveRange(cartItems);
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new PlaceOrderResponseDto
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "سفارش ایجاد شد. لطفا پرداخت را تکمیل کنید",
|
||||||
|
OrderId = order.Id,
|
||||||
|
TransactionId = transaction.Id,
|
||||||
|
TotalAmount = totalAmount,
|
||||||
|
DiscountBalanceUsed = actualDiscountBalanceUsed,
|
||||||
|
GatewayAmountRequired = finalGatewayAmount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.PlaceOrder;
|
||||||
|
|
||||||
|
public class PlaceOrderCommandValidator : AbstractValidator<PlaceOrderCommand>
|
||||||
|
{
|
||||||
|
public PlaceOrderCommandValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.UserId)
|
||||||
|
.GreaterThan(0).WithMessage("شناسه کاربر باید مثبت باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.UserAddressId)
|
||||||
|
.GreaterThan(0).WithMessage("آدرس تحویل باید انتخاب شود");
|
||||||
|
|
||||||
|
RuleFor(x => x.DiscountBalanceToUse)
|
||||||
|
.GreaterThanOrEqualTo(0).WithMessage("مبلغ استفاده از موجودی تخفیف نمیتواند منفی باشد");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.RemoveFromCart;
|
||||||
|
|
||||||
|
public class RemoveFromCartCommand : IRequest<RemoveFromCartResponseDto>
|
||||||
|
{
|
||||||
|
public long UserId { get; set; }
|
||||||
|
public long ProductId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RemoveFromCartResponseDto
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.RemoveFromCart;
|
||||||
|
|
||||||
|
public class RemoveFromCartCommandHandler : IRequestHandler<RemoveFromCartCommand, RemoveFromCartResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public RemoveFromCartCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RemoveFromCartResponseDto> Handle(RemoveFromCartCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var cartItem = await _context.DiscountShoppingCarts
|
||||||
|
.FirstOrDefaultAsync(c => c.UserId == request.UserId && c.ProductId == request.ProductId, cancellationToken);
|
||||||
|
|
||||||
|
if (cartItem == null)
|
||||||
|
{
|
||||||
|
return new RemoveFromCartResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "محصول در سبد خرید یافت نشد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.DiscountShoppingCarts.Remove(cartItem);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new RemoveFromCartResponseDto
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "محصول از سبد خرید حذف شد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.RemoveFromCart;
|
||||||
|
|
||||||
|
public class RemoveFromCartCommandValidator : AbstractValidator<RemoveFromCartCommand>
|
||||||
|
{
|
||||||
|
public RemoveFromCartCommandValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.UserId)
|
||||||
|
.GreaterThan(0).WithMessage("شناسه کاربر باید مثبت باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.ProductId)
|
||||||
|
.GreaterThan(0).WithMessage("شناسه محصول باید مثبت باشد");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateCartItemCount;
|
||||||
|
|
||||||
|
public class UpdateCartItemCountCommand : IRequest<UpdateCartItemCountResponseDto>
|
||||||
|
{
|
||||||
|
public long UserId { get; set; }
|
||||||
|
public long ProductId { get; set; }
|
||||||
|
public int NewCount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UpdateCartItemCountResponseDto
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateCartItemCount;
|
||||||
|
|
||||||
|
public class UpdateCartItemCountCommandHandler : IRequestHandler<UpdateCartItemCountCommand, UpdateCartItemCountResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public UpdateCartItemCountCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UpdateCartItemCountResponseDto> Handle(UpdateCartItemCountCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// پیدا کردن آیتم سبد خرید
|
||||||
|
var cartItem = await _context.DiscountShoppingCarts
|
||||||
|
.Include(c => c.Product)
|
||||||
|
.FirstOrDefaultAsync(c => c.UserId == request.UserId && c.ProductId == request.ProductId, cancellationToken);
|
||||||
|
|
||||||
|
if (cartItem == null)
|
||||||
|
{
|
||||||
|
return new UpdateCartItemCountResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "آیتم در سبد خرید یافت نشد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی موجودی محصول
|
||||||
|
if (request.NewCount > cartItem.Product.RemainingCount)
|
||||||
|
{
|
||||||
|
return new UpdateCartItemCountResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = $"موجودی محصول کافی نیست. موجودی فعلی: {cartItem.Product.RemainingCount}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// اگر تعداد جدید صفر یا منفی باشد، آیتم را حذف کن
|
||||||
|
if (request.NewCount <= 0)
|
||||||
|
{
|
||||||
|
_context.DiscountShoppingCarts.Remove(cartItem);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new UpdateCartItemCountResponseDto
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "محصول از سبد خرید حذف شد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// بهروزرسانی تعداد
|
||||||
|
cartItem.Count = request.NewCount;
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new UpdateCartItemCountResponseDto
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "تعداد محصول بهروزرسانی شد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateCartItemCount;
|
||||||
|
|
||||||
|
public class UpdateCartItemCountCommandValidator : AbstractValidator<UpdateCartItemCountCommand>
|
||||||
|
{
|
||||||
|
public UpdateCartItemCountCommandValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.UserId)
|
||||||
|
.GreaterThan(0).WithMessage("شناسه کاربر باید مثبت باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.ProductId)
|
||||||
|
.GreaterThan(0).WithMessage("شناسه محصول باید مثبت باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.NewCount)
|
||||||
|
.GreaterThanOrEqualTo(0).WithMessage("تعداد نمیتواند منفی باشد");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountCategory;
|
||||||
|
|
||||||
|
public class UpdateDiscountCategoryCommand : IRequest<bool>
|
||||||
|
{
|
||||||
|
public long CategoryId { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public string? ImagePath { get; set; }
|
||||||
|
public long? ParentCategoryId { get; set; }
|
||||||
|
public int SortOrder { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountCategory;
|
||||||
|
|
||||||
|
public class UpdateDiscountCategoryCommandHandler : IRequestHandler<UpdateDiscountCategoryCommand, bool>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public UpdateDiscountCategoryCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Handle(UpdateDiscountCategoryCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var category = await _context.DiscountCategories
|
||||||
|
.FirstOrDefaultAsync(c => c.Id == request.CategoryId, cancellationToken);
|
||||||
|
|
||||||
|
if (category == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی وجود دستهبندی دیگری با همین نام (به جز خودش)
|
||||||
|
var duplicateName = await _context.DiscountCategories
|
||||||
|
.AnyAsync(c => c.Name == request.Name && c.Id != request.CategoryId, cancellationToken);
|
||||||
|
|
||||||
|
if (duplicateName)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("دستهبندی دیگری با این نام وجود دارد");
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی عدم ایجاد حلقه در سلسله مراتب
|
||||||
|
if (request.ParentCategoryId.HasValue)
|
||||||
|
{
|
||||||
|
if (request.ParentCategoryId.Value == request.CategoryId)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("دستهبندی نمیتواند والد خودش باشد");
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی وجود دستهبندی والد
|
||||||
|
var parentExists = await _context.DiscountCategories
|
||||||
|
.AnyAsync(c => c.Id == request.ParentCategoryId.Value, cancellationToken);
|
||||||
|
|
||||||
|
if (!parentExists)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("دستهبندی والد یافت نشد");
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی اینکه والد جدید زیرمجموعه این دستهبندی نباشد
|
||||||
|
var isDescendant = await IsDescendant(request.ParentCategoryId.Value, request.CategoryId, cancellationToken);
|
||||||
|
if (isDescendant)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("دستهبندی والد نمیتواند زیرمجموعه این دستهبندی باشد");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
category.Name = request.Name;
|
||||||
|
category.Title = request.Title;
|
||||||
|
category.Description = request.Description;
|
||||||
|
category.ImagePath = request.ImagePath;
|
||||||
|
category.ParentCategoryId = request.ParentCategoryId;
|
||||||
|
category.SortOrder = request.SortOrder;
|
||||||
|
category.IsActive = request.IsActive;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> IsDescendant(long potentialDescendantId, long ancestorId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var category = await _context.DiscountCategories
|
||||||
|
.FirstOrDefaultAsync(c => c.Id == potentialDescendantId, cancellationToken);
|
||||||
|
|
||||||
|
if (category == null || !category.ParentCategoryId.HasValue)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category.ParentCategoryId.Value == ancestorId)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await IsDescendant(category.ParentCategoryId.Value, ancestorId, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountCategory;
|
||||||
|
|
||||||
|
public class UpdateDiscountCategoryCommandValidator : AbstractValidator<UpdateDiscountCategoryCommand>
|
||||||
|
{
|
||||||
|
public UpdateDiscountCategoryCommandValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.CategoryId)
|
||||||
|
.GreaterThan(0).WithMessage("شناسه دستهبندی باید مثبت باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.Name)
|
||||||
|
.NotEmpty().WithMessage("نام دستهبندی الزامی است")
|
||||||
|
.MaximumLength(100).WithMessage("نام دستهبندی نباید بیشتر از 100 کاراکتر باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.Title)
|
||||||
|
.NotEmpty().WithMessage("عنوان دستهبندی الزامی است")
|
||||||
|
.MaximumLength(200).WithMessage("عنوان دستهبندی نباید بیشتر از 200 کاراکتر باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.Description)
|
||||||
|
.MaximumLength(1000).WithMessage("توضیحات نباید بیشتر از 1000 کاراکتر باشد")
|
||||||
|
.When(x => !string.IsNullOrEmpty(x.Description));
|
||||||
|
|
||||||
|
RuleFor(x => x.ImagePath)
|
||||||
|
.MaximumLength(500).WithMessage("مسیر تصویر نباید بیشتر از 500 کاراکتر باشد")
|
||||||
|
.When(x => !string.IsNullOrEmpty(x.ImagePath));
|
||||||
|
|
||||||
|
RuleFor(x => x.ParentCategoryId)
|
||||||
|
.GreaterThan(0).WithMessage("شناسه دستهبندی والد باید مثبت باشد")
|
||||||
|
.When(x => x.ParentCategoryId.HasValue);
|
||||||
|
|
||||||
|
RuleFor(x => x.SortOrder)
|
||||||
|
.GreaterThanOrEqualTo(0).WithMessage("ترتیب نمایش نمیتواند منفی باشد");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProduct;
|
||||||
|
|
||||||
|
public class UpdateDiscountProductCommand : IRequest<Unit>
|
||||||
|
{
|
||||||
|
public long ProductId { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string ShortInfomation { get; set; }
|
||||||
|
public string FullInformation { get; set; }
|
||||||
|
public long Price { get; set; }
|
||||||
|
public int MaxDiscountPercent { get; set; }
|
||||||
|
public string ImagePath { get; set; }
|
||||||
|
public string ThumbnailPath { get; set; }
|
||||||
|
public int RemainingCount { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
public List<long> CategoryIds { get; set; } = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Entities.DiscountShop;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProduct;
|
||||||
|
|
||||||
|
public class UpdateDiscountProductCommandHandler : IRequestHandler<UpdateDiscountProductCommand, Unit>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public UpdateDiscountProductCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> Handle(UpdateDiscountProductCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var product = await _context.DiscountProducts
|
||||||
|
.FirstOrDefaultAsync(p => p.Id == request.ProductId, cancellationToken);
|
||||||
|
|
||||||
|
if (product == null)
|
||||||
|
throw new Exception("محصول یافت نشد");
|
||||||
|
|
||||||
|
product.Title = request.Title;
|
||||||
|
product.ShortInfomation = request.ShortInfomation;
|
||||||
|
product.FullInformation = request.FullInformation;
|
||||||
|
product.Price = request.Price;
|
||||||
|
product.MaxDiscountPercent = request.MaxDiscountPercent;
|
||||||
|
product.ImagePath = request.ImagePath;
|
||||||
|
product.ThumbnailPath = request.ThumbnailPath;
|
||||||
|
product.RemainingCount = request.RemainingCount;
|
||||||
|
product.IsActive = request.IsActive;
|
||||||
|
|
||||||
|
// Update categories
|
||||||
|
var existingCategories = await _context.DiscountProductCategories
|
||||||
|
.Where(pc => pc.ProductId == request.ProductId)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
_context.DiscountProductCategories.RemoveRange(existingCategories);
|
||||||
|
|
||||||
|
if (request.CategoryIds.Any())
|
||||||
|
{
|
||||||
|
var newCategories = request.CategoryIds.Select(categoryId => new DiscountProductCategory
|
||||||
|
{
|
||||||
|
ProductId = product.Id,
|
||||||
|
CategoryId = categoryId
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
_context.DiscountProductCategories.AddRange(newCategories);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProduct;
|
||||||
|
|
||||||
|
public class UpdateDiscountProductCommandValidator : AbstractValidator<UpdateDiscountProductCommand>
|
||||||
|
{
|
||||||
|
public UpdateDiscountProductCommandValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.ProductId)
|
||||||
|
.GreaterThan(0).WithMessage("شناسه محصول باید مثبت باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.Title)
|
||||||
|
.NotEmpty().WithMessage("عنوان محصول الزامی است")
|
||||||
|
.MaximumLength(200).WithMessage("عنوان محصول نباید بیشتر از 200 کاراکتر باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.ShortInfomation)
|
||||||
|
.NotEmpty().WithMessage("توضیحات کوتاه الزامی است")
|
||||||
|
.MaximumLength(500).WithMessage("توضیحات کوتاه نباید بیشتر از 500 کاراکتر باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.FullInformation)
|
||||||
|
.NotEmpty().WithMessage("توضیحات کامل الزامی است")
|
||||||
|
.MaximumLength(5000).WithMessage("توضیحات کامل نباید بیشتر از 5000 کاراکتر باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.Price)
|
||||||
|
.GreaterThan(0).WithMessage("قیمت باید بزرگتر از صفر باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.MaxDiscountPercent)
|
||||||
|
.InclusiveBetween(0, 100).WithMessage("درصد تخفیف باید بین 0 تا 100 باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.ImagePath)
|
||||||
|
.NotEmpty().WithMessage("مسیر تصویر اصلی الزامی است")
|
||||||
|
.MaximumLength(500).WithMessage("مسیر تصویر نباید بیشتر از 500 کاراکتر باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.ThumbnailPath)
|
||||||
|
.NotEmpty().WithMessage("مسیر تصویر بندانگشتی الزامی است")
|
||||||
|
.MaximumLength(500).WithMessage("مسیر تصویر نباید بیشتر از 500 کاراکتر باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.RemainingCount)
|
||||||
|
.GreaterThanOrEqualTo(0).WithMessage("موجودی نمیتواند منفی باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.CategoryIds)
|
||||||
|
.NotEmpty().WithMessage("حداقل یک دستهبندی باید انتخاب شود")
|
||||||
|
.Must(ids => ids.All(id => id > 0)).WithMessage("شناسه دستهبندیها باید مثبت باشند");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateOrderStatus;
|
||||||
|
|
||||||
|
public class UpdateOrderStatusCommand : IRequest<UpdateOrderStatusResponseDto>
|
||||||
|
{
|
||||||
|
public long OrderId { get; set; }
|
||||||
|
public DeliveryStatus DeliveryStatus { get; set; }
|
||||||
|
public string? TrackingCode { get; set; }
|
||||||
|
public string? AdminNotes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UpdateOrderStatusResponseDto
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateOrderStatus;
|
||||||
|
|
||||||
|
public class UpdateOrderStatusCommandHandler : IRequestHandler<UpdateOrderStatusCommand, UpdateOrderStatusResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public UpdateOrderStatusCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UpdateOrderStatusResponseDto> Handle(UpdateOrderStatusCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var order = await _context.DiscountOrders
|
||||||
|
.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken);
|
||||||
|
|
||||||
|
if (order == null)
|
||||||
|
{
|
||||||
|
return new UpdateOrderStatusResponseDto
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "سفارش یافت نشد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// بهروزرسانی وضعیت
|
||||||
|
order.DeliveryStatus = request.DeliveryStatus;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(request.TrackingCode))
|
||||||
|
{
|
||||||
|
order.TrackingCode = request.TrackingCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new UpdateOrderStatusResponseDto
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "وضعیت سفارش بهروزرسانی شد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateOrderStatus;
|
||||||
|
|
||||||
|
public class UpdateOrderStatusCommandValidator : AbstractValidator<UpdateOrderStatusCommand>
|
||||||
|
{
|
||||||
|
public UpdateOrderStatusCommandValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.OrderId)
|
||||||
|
.GreaterThan(0).WithMessage("شناسه سفارش باید مثبت باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.DeliveryStatus)
|
||||||
|
.IsInEnum().WithMessage("وضعیت ارسال نامعتبر است");
|
||||||
|
|
||||||
|
RuleFor(x => x.TrackingCode)
|
||||||
|
.MaximumLength(50).WithMessage("کد رهگیری نباید بیشتر از 50 کاراکتر باشد")
|
||||||
|
.When(x => !string.IsNullOrEmpty(x.TrackingCode));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountCategories;
|
||||||
|
|
||||||
|
public class GetDiscountCategoriesQuery : IRequest<GetDiscountCategoriesResponseDto>
|
||||||
|
{
|
||||||
|
public long? ParentCategoryId { get; set; }
|
||||||
|
public bool? IsActive { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GetDiscountCategoriesResponseDto
|
||||||
|
{
|
||||||
|
public List<DiscountCategoryDto> Categories { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DiscountCategoryDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public string? ImagePath { get; set; }
|
||||||
|
public long? ParentCategoryId { get; set; }
|
||||||
|
public int SortOrder { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
public int ProductCount { get; set; }
|
||||||
|
public List<DiscountCategoryDto>? Children { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountCategories;
|
||||||
|
|
||||||
|
public class GetDiscountCategoriesQueryHandler : IRequestHandler<GetDiscountCategoriesQuery, GetDiscountCategoriesResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public GetDiscountCategoriesQueryHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GetDiscountCategoriesResponseDto> Handle(GetDiscountCategoriesQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var query = _context.DiscountCategories.AsQueryable();
|
||||||
|
|
||||||
|
// فیلتر بر اساس ParentCategoryId
|
||||||
|
if (request.ParentCategoryId.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(c => c.ParentCategoryId == request.ParentCategoryId.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// اگر ParentCategoryId مشخص نشده، فقط دستههای اصلی (بدون والد) را برگردان
|
||||||
|
query = query.Where(c => c.ParentCategoryId == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// فیلتر بر اساس وضعیت فعال
|
||||||
|
if (request.IsActive.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(c => c.IsActive == request.IsActive.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var categories = await query
|
||||||
|
.OrderBy(c => c.SortOrder)
|
||||||
|
.ThenBy(c => c.Title)
|
||||||
|
.Select(c => new DiscountCategoryDto
|
||||||
|
{
|
||||||
|
Id = c.Id,
|
||||||
|
Name = c.Name,
|
||||||
|
Title = c.Title,
|
||||||
|
Description = c.Description,
|
||||||
|
ImagePath = c.ImagePath,
|
||||||
|
ParentCategoryId = c.ParentCategoryId,
|
||||||
|
SortOrder = c.SortOrder,
|
||||||
|
IsActive = c.IsActive,
|
||||||
|
ProductCount = _context.DiscountProductCategories.Count(pc => pc.CategoryId == c.Id)
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
// بارگذاری زیرمجموعهها به صورت بازگشتی
|
||||||
|
foreach (var category in categories)
|
||||||
|
{
|
||||||
|
category.Children = await LoadChildren(category.Id, request.IsActive, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GetDiscountCategoriesResponseDto
|
||||||
|
{
|
||||||
|
Categories = categories
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<DiscountCategoryDto>> LoadChildren(long parentId, bool? isActive, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var query = _context.DiscountCategories.Where(c => c.ParentCategoryId == parentId);
|
||||||
|
|
||||||
|
if (isActive.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(c => c.IsActive == isActive.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var children = await query
|
||||||
|
.OrderBy(c => c.SortOrder)
|
||||||
|
.ThenBy(c => c.Title)
|
||||||
|
.Select(c => new DiscountCategoryDto
|
||||||
|
{
|
||||||
|
Id = c.Id,
|
||||||
|
Name = c.Name,
|
||||||
|
Title = c.Title,
|
||||||
|
Description = c.Description,
|
||||||
|
ImagePath = c.ImagePath,
|
||||||
|
ParentCategoryId = c.ParentCategoryId,
|
||||||
|
SortOrder = c.SortOrder,
|
||||||
|
IsActive = c.IsActive,
|
||||||
|
ProductCount = _context.DiscountProductCategories.Count(pc => pc.CategoryId == c.Id)
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
foreach (var child in children)
|
||||||
|
{
|
||||||
|
child.Children = await LoadChildren(child.Id, isActive, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProductById;
|
||||||
|
|
||||||
|
public class GetDiscountProductByIdQuery : IRequest<DiscountProductDetailDto?>
|
||||||
|
{
|
||||||
|
public long ProductId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DiscountProductDetailDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string ShortInfomation { get; set; }
|
||||||
|
public string FullInformation { get; set; }
|
||||||
|
public long Price { get; set; }
|
||||||
|
public int MaxDiscountPercent { get; set; }
|
||||||
|
public int Rate { get; set; }
|
||||||
|
public string ImagePath { get; set; }
|
||||||
|
public string ThumbnailPath { get; set; }
|
||||||
|
public int SaleCount { get; set; }
|
||||||
|
public int ViewCount { get; set; }
|
||||||
|
public int RemainingCount { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
public List<CategoryDto> Categories { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CategoryDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProductById;
|
||||||
|
|
||||||
|
public class GetDiscountProductByIdQueryHandler : IRequestHandler<GetDiscountProductByIdQuery, DiscountProductDetailDto?>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public GetDiscountProductByIdQueryHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DiscountProductDetailDto?> Handle(GetDiscountProductByIdQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var product = await _context.DiscountProducts
|
||||||
|
.Where(p => p.Id == request.ProductId)
|
||||||
|
.Select(p => new DiscountProductDetailDto
|
||||||
|
{
|
||||||
|
Id = p.Id,
|
||||||
|
Title = p.Title,
|
||||||
|
ShortInfomation = p.ShortInfomation,
|
||||||
|
FullInformation = p.FullInformation,
|
||||||
|
Price = p.Price,
|
||||||
|
MaxDiscountPercent = p.MaxDiscountPercent,
|
||||||
|
Rate = p.Rate,
|
||||||
|
ImagePath = p.ImagePath,
|
||||||
|
ThumbnailPath = p.ThumbnailPath,
|
||||||
|
SaleCount = p.SaleCount,
|
||||||
|
ViewCount = p.ViewCount,
|
||||||
|
RemainingCount = p.RemainingCount,
|
||||||
|
IsActive = p.IsActive
|
||||||
|
})
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (product == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Get categories
|
||||||
|
var categories = await _context.DiscountProductCategories
|
||||||
|
.Where(pc => pc.ProductId == request.ProductId)
|
||||||
|
.Select(pc => new CategoryDto
|
||||||
|
{
|
||||||
|
Id = pc.Category.Id,
|
||||||
|
Name = pc.Category.Name,
|
||||||
|
Title = pc.Category.Title
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
product.Categories = categories;
|
||||||
|
|
||||||
|
// Increment view count
|
||||||
|
var productEntity = await _context.DiscountProducts.FindAsync(new object[] { request.ProductId }, cancellationToken);
|
||||||
|
if (productEntity != null)
|
||||||
|
{
|
||||||
|
productEntity.ViewCount++;
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return product;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Models;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProducts;
|
||||||
|
|
||||||
|
public class GetDiscountProductsQuery : IRequest<GetDiscountProductsResponseDto>
|
||||||
|
{
|
||||||
|
public PaginationState? PaginationQuery { get; set; }
|
||||||
|
public long? CategoryId { get; set; }
|
||||||
|
public string? SearchTerm { get; set; }
|
||||||
|
public bool? IsActive { get; set; }
|
||||||
|
public int? MinPrice { get; set; }
|
||||||
|
public int? MaxPrice { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GetDiscountProductsResponseDto
|
||||||
|
{
|
||||||
|
public MetaData MetaData { get; set; }
|
||||||
|
public List<DiscountProductDto> Models { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DiscountProductDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string ShortInfomation { get; set; }
|
||||||
|
public long Price { get; set; }
|
||||||
|
public int MaxDiscountPercent { get; set; }
|
||||||
|
public int Rate { get; set; }
|
||||||
|
public string ImagePath { get; set; }
|
||||||
|
public string ThumbnailPath { get; set; }
|
||||||
|
public int SaleCount { get; set; }
|
||||||
|
public int ViewCount { get; set; }
|
||||||
|
public int RemainingCount { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Application.Common.Models;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProducts;
|
||||||
|
|
||||||
|
public class GetDiscountProductsQueryHandler : IRequestHandler<GetDiscountProductsQuery, GetDiscountProductsResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public GetDiscountProductsQueryHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GetDiscountProductsResponseDto> Handle(GetDiscountProductsQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var query = _context.DiscountProducts.AsQueryable();
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
if (request.CategoryId.HasValue)
|
||||||
|
{
|
||||||
|
var productIds = await _context.DiscountProductCategories
|
||||||
|
.Where(pc => pc.CategoryId == request.CategoryId.Value)
|
||||||
|
.Select(pc => pc.ProductId)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
query = query.Where(p => productIds.Contains(p.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.SearchTerm))
|
||||||
|
{
|
||||||
|
query = query.Where(p =>
|
||||||
|
p.Title.Contains(request.SearchTerm) ||
|
||||||
|
p.ShortInfomation.Contains(request.SearchTerm));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.IsActive.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(p => p.IsActive == request.IsActive.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.MinPrice.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(p => p.Price >= request.MinPrice.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.MaxPrice.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(p => p.Price <= request.MaxPrice.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalCount = await query.CountAsync(cancellationToken);
|
||||||
|
|
||||||
|
// Apply pagination
|
||||||
|
var pagination = request.PaginationQuery ?? new PaginationState { PageNumber = 1, PageSize = 10 };
|
||||||
|
|
||||||
|
var products = await query
|
||||||
|
.OrderByDescending(p => p.Created)
|
||||||
|
.Skip((pagination.PageNumber - 1) * pagination.PageSize)
|
||||||
|
.Take(pagination.PageSize)
|
||||||
|
.Select(p => new DiscountProductDto
|
||||||
|
{
|
||||||
|
Id = p.Id,
|
||||||
|
Title = p.Title,
|
||||||
|
ShortInfomation = p.ShortInfomation,
|
||||||
|
Price = p.Price,
|
||||||
|
MaxDiscountPercent = p.MaxDiscountPercent,
|
||||||
|
Rate = p.Rate,
|
||||||
|
ImagePath = p.ImagePath,
|
||||||
|
ThumbnailPath = p.ThumbnailPath,
|
||||||
|
SaleCount = p.SaleCount,
|
||||||
|
ViewCount = p.ViewCount,
|
||||||
|
RemainingCount = p.RemainingCount,
|
||||||
|
IsActive = p.IsActive
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new GetDiscountProductsResponseDto
|
||||||
|
{
|
||||||
|
MetaData = new MetaData
|
||||||
|
{
|
||||||
|
TotalCount = totalCount,
|
||||||
|
PageSize = pagination.PageSize,
|
||||||
|
CurrentPage = pagination.PageNumber,
|
||||||
|
TotalPage = (int)Math.Ceiling(totalCount / (double)pagination.PageSize)
|
||||||
|
},
|
||||||
|
Models = products
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetOrderById;
|
||||||
|
|
||||||
|
public class GetOrderByIdQuery : IRequest<OrderDetailDto?>
|
||||||
|
{
|
||||||
|
public long OrderId { get; set; }
|
||||||
|
public long UserId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OrderDetailDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public long UserId { get; set; }
|
||||||
|
public long TotalAmount { get; set; }
|
||||||
|
public long DiscountBalanceUsed { get; set; }
|
||||||
|
public long GatewayAmountPaid { get; set; }
|
||||||
|
public long VatAmount { get; set; }
|
||||||
|
public PaymentStatus PaymentStatus { get; set; }
|
||||||
|
public DateTime? PaymentDate { get; set; }
|
||||||
|
public DeliveryStatus DeliveryStatus { get; set; }
|
||||||
|
public string? TrackingCode { get; set; }
|
||||||
|
public string? DeliveryDescription { get; set; }
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
public UserAddressDto Address { get; set; }
|
||||||
|
public List<OrderItemDto> Items { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserAddressDto
|
||||||
|
{
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Address { get; set; }
|
||||||
|
public string PostalCode { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OrderItemDto
|
||||||
|
{
|
||||||
|
public long ProductId { get; set; }
|
||||||
|
public string ProductTitle { get; set; }
|
||||||
|
public int Count { get; set; }
|
||||||
|
public long UnitPrice { get; set; }
|
||||||
|
public int DiscountPercentUsed { get; set; }
|
||||||
|
public long DiscountAmount { get; set; }
|
||||||
|
public long FinalPrice { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetOrderById;
|
||||||
|
|
||||||
|
public class GetOrderByIdQueryHandler : IRequestHandler<GetOrderByIdQuery, OrderDetailDto?>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public GetOrderByIdQueryHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<OrderDetailDto?> Handle(GetOrderByIdQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var order = await _context.DiscountOrders
|
||||||
|
.Where(o => o.Id == request.OrderId && o.UserId == request.UserId)
|
||||||
|
.Include(o => o.UserAddress)
|
||||||
|
.Include(o => o.OrderDetails)
|
||||||
|
.ThenInclude(od => od.Product)
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (order == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new OrderDetailDto
|
||||||
|
{
|
||||||
|
Id = order.Id,
|
||||||
|
UserId = order.UserId,
|
||||||
|
TotalAmount = order.TotalAmount,
|
||||||
|
DiscountBalanceUsed = order.DiscountBalanceUsed,
|
||||||
|
GatewayAmountPaid = order.GatewayAmountPaid,
|
||||||
|
VatAmount = order.VatAmount,
|
||||||
|
PaymentStatus = order.PaymentStatus,
|
||||||
|
PaymentDate = order.PaymentDate,
|
||||||
|
DeliveryStatus = order.DeliveryStatus,
|
||||||
|
TrackingCode = order.TrackingCode,
|
||||||
|
DeliveryDescription = order.DeliveryDescription,
|
||||||
|
Created = order.Created,
|
||||||
|
Address = new UserAddressDto
|
||||||
|
{
|
||||||
|
Title = order.UserAddress.Title,
|
||||||
|
Address = order.UserAddress.Address,
|
||||||
|
PostalCode = order.UserAddress.PostalCode
|
||||||
|
},
|
||||||
|
Items = order.OrderDetails.Select(od => new OrderItemDto
|
||||||
|
{
|
||||||
|
ProductId = od.ProductId,
|
||||||
|
ProductTitle = od.Product.Title,
|
||||||
|
Count = od.Count,
|
||||||
|
UnitPrice = od.UnitPrice,
|
||||||
|
DiscountPercentUsed = od.DiscountPercentUsed,
|
||||||
|
DiscountAmount = od.DiscountAmount,
|
||||||
|
FinalPrice = od.FinalPrice
|
||||||
|
}).ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetUserCart;
|
||||||
|
|
||||||
|
public class GetUserCartQuery : IRequest<UserCartDto>
|
||||||
|
{
|
||||||
|
public long UserId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserCartDto
|
||||||
|
{
|
||||||
|
public List<CartItemDto> Items { get; set; } = new();
|
||||||
|
public long TotalAmount { get; set; }
|
||||||
|
public long MaxDiscountAmount { get; set; }
|
||||||
|
public long MinPayableAmount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CartItemDto
|
||||||
|
{
|
||||||
|
public long CartItemId { get; set; }
|
||||||
|
public long ProductId { get; set; }
|
||||||
|
public string ProductTitle { get; set; }
|
||||||
|
public string ProductImagePath { get; set; }
|
||||||
|
public long UnitPrice { get; set; }
|
||||||
|
public int Count { get; set; }
|
||||||
|
public long SubTotal { get; set; }
|
||||||
|
public int MaxDiscountPercent { get; set; }
|
||||||
|
public long MaxDiscountAmount { get; set; }
|
||||||
|
public long MinPayable { get; set; }
|
||||||
|
public int RemainingStock { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetUserCart;
|
||||||
|
|
||||||
|
public class GetUserCartQueryHandler : IRequestHandler<GetUserCartQuery, UserCartDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public GetUserCartQueryHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UserCartDto> Handle(GetUserCartQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var cartItems = await _context.DiscountShoppingCarts
|
||||||
|
.Where(c => c.UserId == request.UserId)
|
||||||
|
.Include(c => c.Product)
|
||||||
|
.Select(c => new CartItemDto
|
||||||
|
{
|
||||||
|
CartItemId = c.Id,
|
||||||
|
ProductId = c.ProductId,
|
||||||
|
ProductTitle = c.Product.Title,
|
||||||
|
ProductImagePath = c.Product.ThumbnailPath,
|
||||||
|
UnitPrice = c.Product.Price,
|
||||||
|
Count = c.Count,
|
||||||
|
SubTotal = c.Product.Price * c.Count,
|
||||||
|
MaxDiscountPercent = c.Product.MaxDiscountPercent,
|
||||||
|
MaxDiscountAmount = (c.Product.Price * c.Count * c.Product.MaxDiscountPercent) / 100,
|
||||||
|
MinPayable = c.Product.Price * c.Count - ((c.Product.Price * c.Count * c.Product.MaxDiscountPercent) / 100),
|
||||||
|
RemainingStock = c.Product.RemainingCount,
|
||||||
|
IsActive = c.Product.IsActive
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var totalAmount = cartItems.Sum(i => i.SubTotal);
|
||||||
|
var maxDiscountAmount = cartItems.Sum(i => i.MaxDiscountAmount);
|
||||||
|
var minPayableAmount = cartItems.Sum(i => i.MinPayable);
|
||||||
|
|
||||||
|
return new UserCartDto
|
||||||
|
{
|
||||||
|
Items = cartItems,
|
||||||
|
TotalAmount = totalAmount,
|
||||||
|
MaxDiscountAmount = maxDiscountAmount,
|
||||||
|
MinPayableAmount = minPayableAmount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Models;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetUserOrders;
|
||||||
|
|
||||||
|
public class GetUserOrdersQuery : IRequest<GetUserOrdersResponseDto>
|
||||||
|
{
|
||||||
|
public long UserId { get; set; }
|
||||||
|
public PaginationState? PaginationQuery { get; set; }
|
||||||
|
public PaymentStatus? PaymentStatus { get; set; }
|
||||||
|
public DeliveryStatus? DeliveryStatus { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GetUserOrdersResponseDto
|
||||||
|
{
|
||||||
|
public MetaData MetaData { get; set; }
|
||||||
|
public List<OrderSummaryDto> Models { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OrderSummaryDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public long TotalAmount { get; set; }
|
||||||
|
public long DiscountBalanceUsed { get; set; }
|
||||||
|
public long GatewayAmountPaid { get; set; }
|
||||||
|
public long VatAmount { get; set; }
|
||||||
|
public PaymentStatus PaymentStatus { get; set; }
|
||||||
|
public DateTime? PaymentDate { get; set; }
|
||||||
|
public DeliveryStatus DeliveryStatus { get; set; }
|
||||||
|
public string? TrackingCode { get; set; }
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
public int ItemsCount { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Application.Common.Models;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.DiscountShopCQ.Queries.GetUserOrders;
|
||||||
|
|
||||||
|
public class GetUserOrdersQueryHandler : IRequestHandler<GetUserOrdersQuery, GetUserOrdersResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public GetUserOrdersQueryHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GetUserOrdersResponseDto> Handle(GetUserOrdersQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var query = _context.DiscountOrders
|
||||||
|
.Where(o => o.UserId == request.UserId);
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
if (request.PaymentStatus.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(o => o.PaymentStatus == request.PaymentStatus.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.DeliveryStatus.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(o => o.DeliveryStatus == request.DeliveryStatus.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalCount = await query.CountAsync(cancellationToken);
|
||||||
|
|
||||||
|
// Apply pagination
|
||||||
|
var pagination = request.PaginationQuery ?? new PaginationState { PageNumber = 1, PageSize = 10 };
|
||||||
|
|
||||||
|
var orders = await query
|
||||||
|
.OrderByDescending(o => o.Created)
|
||||||
|
.Skip((pagination.PageNumber - 1) * pagination.PageSize)
|
||||||
|
.Take(pagination.PageSize)
|
||||||
|
.Select(o => new OrderSummaryDto
|
||||||
|
{
|
||||||
|
Id = o.Id,
|
||||||
|
TotalAmount = o.TotalAmount,
|
||||||
|
DiscountBalanceUsed = o.DiscountBalanceUsed,
|
||||||
|
GatewayAmountPaid = o.GatewayAmountPaid,
|
||||||
|
VatAmount = o.VatAmount,
|
||||||
|
PaymentStatus = o.PaymentStatus,
|
||||||
|
PaymentDate = o.PaymentDate,
|
||||||
|
DeliveryStatus = o.DeliveryStatus,
|
||||||
|
TrackingCode = o.TrackingCode,
|
||||||
|
Created = o.Created,
|
||||||
|
ItemsCount = o.OrderDetails.Count
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new GetUserOrdersResponseDto
|
||||||
|
{
|
||||||
|
MetaData = new MetaData
|
||||||
|
{
|
||||||
|
TotalCount = totalCount,
|
||||||
|
PageSize = pagination.PageSize,
|
||||||
|
CurrentPage = pagination.PageNumber,
|
||||||
|
TotalPage = (int)Math.Ceiling(totalCount / (double)pagination.PageSize)
|
||||||
|
},
|
||||||
|
Models = orders
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ public class CreateNewFactorDetailsCommandHandler : IRequestHandler<CreateNewFac
|
|||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var entity = request.Adapt<FactorDetails>();
|
var entity = request.Adapt<FactorDetails>();
|
||||||
await _context.FactorDetailss.AddAsync(entity, cancellationToken);
|
await _context.FactorDetails.AddAsync(entity, cancellationToken);
|
||||||
entity.AddDomainEvent(new CreateNewFactorDetailsEvent(entity));
|
entity.AddDomainEvent(new CreateNewFactorDetailsEvent(entity));
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
return entity.Adapt<CreateNewFactorDetailsResponseDto>();
|
return entity.Adapt<CreateNewFactorDetailsResponseDto>();
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ public class DeleteFactorDetailsCommandHandler : IRequestHandler<DeleteFactorDet
|
|||||||
|
|
||||||
public async Task<Unit> Handle(DeleteFactorDetailsCommand request, CancellationToken cancellationToken)
|
public async Task<Unit> Handle(DeleteFactorDetailsCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var entity = await _context.FactorDetailss
|
var entity = await _context.FactorDetails
|
||||||
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(FactorDetails), request.Id);
|
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(FactorDetails), request.Id);
|
||||||
entity.IsDeleted = true;
|
entity.IsDeleted = true;
|
||||||
_context.FactorDetailss.Update(entity);
|
_context.FactorDetails.Update(entity);
|
||||||
entity.AddDomainEvent(new DeleteFactorDetailsEvent(entity));
|
entity.AddDomainEvent(new DeleteFactorDetailsEvent(entity));
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
return Unit.Value;
|
return Unit.Value;
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ public class UpdateFactorDetailsCommandHandler : IRequestHandler<UpdateFactorDet
|
|||||||
|
|
||||||
public async Task<Unit> Handle(UpdateFactorDetailsCommand request, CancellationToken cancellationToken)
|
public async Task<Unit> Handle(UpdateFactorDetailsCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var entity = await _context.FactorDetailss
|
var entity = await _context.FactorDetails
|
||||||
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(FactorDetails), request.Id);
|
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(FactorDetails), request.Id);
|
||||||
request.Adapt(entity);
|
request.Adapt(entity);
|
||||||
_context.FactorDetailss.Update(entity);
|
_context.FactorDetails.Update(entity);
|
||||||
entity.AddDomainEvent(new UpdateFactorDetailsEvent(entity));
|
entity.AddDomainEvent(new UpdateFactorDetailsEvent(entity));
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
return Unit.Value;
|
return Unit.Value;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public class GetAllFactorDetailsByFilterQueryHandler : IRequestHandler<GetAllFac
|
|||||||
|
|
||||||
public async Task<GetAllFactorDetailsByFilterResponseDto> Handle(GetAllFactorDetailsByFilterQuery request, CancellationToken cancellationToken)
|
public async Task<GetAllFactorDetailsByFilterResponseDto> Handle(GetAllFactorDetailsByFilterQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var query = _context.FactorDetailss
|
var query = _context.FactorDetails
|
||||||
.ApplyOrder(sortBy: request.SortBy)
|
.ApplyOrder(sortBy: request.SortBy)
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.AsQueryable();
|
.AsQueryable();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class GetFactorDetailsQueryHandler : IRequestHandler<GetFactorDetailsQuer
|
|||||||
public async Task<GetFactorDetailsResponseDto> Handle(GetFactorDetailsQuery request,
|
public async Task<GetFactorDetailsResponseDto> Handle(GetFactorDetailsQuery request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var response = await _context.FactorDetailss
|
var response = await _context.FactorDetails
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(x => x.Id == request.Id)
|
.Where(x => x.Id == request.Id)
|
||||||
.ProjectToType<GetFactorDetailsResponseDto>()
|
.ProjectToType<GetFactorDetailsResponseDto>()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
global using MediatR;
|
global using MediatR;
|
||||||
global using FluentValidation;
|
global using FluentValidation;
|
||||||
global using Mapster;
|
global using Mapster;
|
||||||
|
global using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
global using CMSMicroservice.Domain.Entities;
|
global using CMSMicroservice.Domain.Entities;
|
||||||
global using CMSMicroservice.Domain.Entities.Club;
|
global using CMSMicroservice.Domain.Entities.Club;
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.ManualPaymentCQ.Commands.ApproveManualPayment;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دستور تایید پرداخت دستی توسط SuperAdmin
|
||||||
|
/// </summary>
|
||||||
|
public class ApproveManualPaymentCommand : IRequest<bool>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه ManualPayment
|
||||||
|
/// </summary>
|
||||||
|
public long ManualPaymentId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// یادداشت تایید (اختیاری)
|
||||||
|
/// </summary>
|
||||||
|
public string? ApprovalNote { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Exceptions;
|
||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
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.ApproveManualPayment;
|
||||||
|
|
||||||
|
public class ApproveManualPaymentCommandHandler : IRequestHandler<ApproveManualPaymentCommand, bool>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
private readonly ICurrentUserService _currentUser;
|
||||||
|
private readonly ILogger<ApproveManualPaymentCommandHandler> _logger;
|
||||||
|
|
||||||
|
public ApproveManualPaymentCommandHandler(
|
||||||
|
IApplicationDbContext context,
|
||||||
|
ICurrentUserService currentUser,
|
||||||
|
ILogger<ApproveManualPaymentCommandHandler> logger)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_currentUser = currentUser;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Handle(
|
||||||
|
ApproveManualPaymentCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Approving manual payment: {ManualPaymentId}",
|
||||||
|
request.ManualPaymentId
|
||||||
|
);
|
||||||
|
|
||||||
|
// 1. پیدا کردن ManualPayment
|
||||||
|
var manualPayment = await _context.ManualPayments
|
||||||
|
.Include(m => m.User)
|
||||||
|
.FirstOrDefaultAsync(m => m.Id == request.ManualPaymentId, cancellationToken);
|
||||||
|
|
||||||
|
if (manualPayment == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("ManualPayment not found: {Id}", request.ManualPaymentId);
|
||||||
|
throw new NotFoundException(nameof(ManualPayment), request.ManualPaymentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. بررسی وضعیت
|
||||||
|
if (manualPayment.Status != ManualPaymentStatus.Pending)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"ManualPayment {Id} is not in Pending status: {Status}",
|
||||||
|
request.ManualPaymentId,
|
||||||
|
manualPayment.Status
|
||||||
|
);
|
||||||
|
throw new BadRequestException($"فقط درخواستهای در وضعیت Pending قابل تایید هستند. وضعیت فعلی: {manualPayment.Status}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. بررسی SuperAdmin
|
||||||
|
var currentUserId = _currentUser.UserId;
|
||||||
|
if (string.IsNullOrEmpty(currentUserId))
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("کاربر احراز هویت نشده است");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!long.TryParse(currentUserId, out var approvedById))
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("شناسه کاربر نامعتبر است");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. پیدا کردن Wallet کاربر
|
||||||
|
var wallet = await _context.UserWallets
|
||||||
|
.FirstOrDefaultAsync(w => w.UserId == manualPayment.UserId, cancellationToken);
|
||||||
|
|
||||||
|
if (wallet == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Wallet not found for UserId: {UserId}", manualPayment.UserId);
|
||||||
|
throw new NotFoundException($"کیف پول کاربر {manualPayment.UserId} یافت نشد");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. ایجاد Transaction
|
||||||
|
var transaction = new Transaction
|
||||||
|
{
|
||||||
|
Amount = manualPayment.Amount,
|
||||||
|
Description = $"پرداخت دستی - {manualPayment.Type} - {manualPayment.Description}",
|
||||||
|
PaymentStatus = PaymentStatus.Success,
|
||||||
|
PaymentDate = DateTime.UtcNow,
|
||||||
|
RefId = manualPayment.ReferenceNumber,
|
||||||
|
Type = MapToTransactionType(manualPayment.Type)
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.Transactions.Add(transaction);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 6. اعمال تغییرات بر کیف پول
|
||||||
|
var oldBalance = wallet.Balance;
|
||||||
|
var oldDiscountBalance = wallet.DiscountBalance;
|
||||||
|
var oldNetworkBalance = wallet.NetworkBalance;
|
||||||
|
|
||||||
|
switch (manualPayment.Type)
|
||||||
|
{
|
||||||
|
case ManualPaymentType.CashDeposit:
|
||||||
|
case ManualPaymentType.Settlement:
|
||||||
|
case ManualPaymentType.ErrorCorrection:
|
||||||
|
wallet.Balance += manualPayment.Amount;
|
||||||
|
wallet.DiscountBalance += manualPayment.Amount;
|
||||||
|
|
||||||
|
// لاگ Balance
|
||||||
|
await _context.UserWalletChangeLogs.AddAsync(new UserWalletChangeLog
|
||||||
|
{
|
||||||
|
WalletId = wallet.Id,
|
||||||
|
CurrentBalance = wallet.Balance,
|
||||||
|
ChangeValue = manualPayment.Amount,
|
||||||
|
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||||
|
ChangeNerworkValue = 0,
|
||||||
|
CurrentDiscountBalance = oldDiscountBalance,
|
||||||
|
ChangeDiscountValue = 0,
|
||||||
|
IsIncrease = true,
|
||||||
|
RefrenceId = transaction.Id
|
||||||
|
}, cancellationToken);
|
||||||
|
|
||||||
|
// لاگ DiscountBalance
|
||||||
|
await _context.UserWalletChangeLogs.AddAsync(new UserWalletChangeLog
|
||||||
|
{
|
||||||
|
WalletId = wallet.Id,
|
||||||
|
CurrentBalance = wallet.Balance,
|
||||||
|
ChangeValue = 0,
|
||||||
|
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||||
|
ChangeNerworkValue = 0,
|
||||||
|
CurrentDiscountBalance = wallet.DiscountBalance,
|
||||||
|
ChangeDiscountValue = manualPayment.Amount,
|
||||||
|
IsIncrease = true,
|
||||||
|
RefrenceId = transaction.Id
|
||||||
|
}, cancellationToken);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ManualPaymentType.DiscountWalletCharge:
|
||||||
|
wallet.DiscountBalance += manualPayment.Amount;
|
||||||
|
|
||||||
|
await _context.UserWalletChangeLogs.AddAsync(new UserWalletChangeLog
|
||||||
|
{
|
||||||
|
WalletId = wallet.Id,
|
||||||
|
CurrentBalance = wallet.Balance,
|
||||||
|
ChangeValue = 0,
|
||||||
|
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||||
|
ChangeNerworkValue = 0,
|
||||||
|
CurrentDiscountBalance = wallet.DiscountBalance,
|
||||||
|
ChangeDiscountValue = manualPayment.Amount,
|
||||||
|
IsIncrease = true,
|
||||||
|
RefrenceId = transaction.Id
|
||||||
|
}, cancellationToken);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ManualPaymentType.NetworkWalletCharge:
|
||||||
|
wallet.NetworkBalance += manualPayment.Amount;
|
||||||
|
|
||||||
|
await _context.UserWalletChangeLogs.AddAsync(new UserWalletChangeLog
|
||||||
|
{
|
||||||
|
WalletId = wallet.Id,
|
||||||
|
CurrentBalance = wallet.Balance,
|
||||||
|
ChangeValue = 0,
|
||||||
|
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||||
|
ChangeNerworkValue = manualPayment.Amount,
|
||||||
|
CurrentDiscountBalance = wallet.DiscountBalance,
|
||||||
|
ChangeDiscountValue = 0,
|
||||||
|
IsIncrease = true,
|
||||||
|
RefrenceId = transaction.Id
|
||||||
|
}, cancellationToken);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ManualPaymentType.Refund:
|
||||||
|
// بازگشت وجه - کم کردن از Balance و DiscountBalance
|
||||||
|
if (wallet.Balance < manualPayment.Amount)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("موجودی کیف پول برای بازگشت وجه کافی نیست");
|
||||||
|
}
|
||||||
|
|
||||||
|
wallet.Balance -= manualPayment.Amount;
|
||||||
|
if (wallet.DiscountBalance >= manualPayment.Amount)
|
||||||
|
{
|
||||||
|
wallet.DiscountBalance -= manualPayment.Amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.UserWalletChangeLogs.AddAsync(new UserWalletChangeLog
|
||||||
|
{
|
||||||
|
WalletId = wallet.Id,
|
||||||
|
CurrentBalance = wallet.Balance,
|
||||||
|
ChangeValue = manualPayment.Amount,
|
||||||
|
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||||
|
ChangeNerworkValue = 0,
|
||||||
|
CurrentDiscountBalance = wallet.DiscountBalance,
|
||||||
|
ChangeDiscountValue = wallet.DiscountBalance < oldDiscountBalance ? manualPayment.Amount : 0,
|
||||||
|
IsIncrease = false,
|
||||||
|
RefrenceId = transaction.Id
|
||||||
|
}, cancellationToken);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Other یا سایر موارد - فقط Balance
|
||||||
|
wallet.Balance += manualPayment.Amount;
|
||||||
|
|
||||||
|
await _context.UserWalletChangeLogs.AddAsync(new UserWalletChangeLog
|
||||||
|
{
|
||||||
|
WalletId = wallet.Id,
|
||||||
|
CurrentBalance = wallet.Balance,
|
||||||
|
ChangeValue = manualPayment.Amount,
|
||||||
|
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||||
|
ChangeNerworkValue = 0,
|
||||||
|
CurrentDiscountBalance = wallet.DiscountBalance,
|
||||||
|
ChangeDiscountValue = 0,
|
||||||
|
IsIncrease = true,
|
||||||
|
RefrenceId = transaction.Id
|
||||||
|
}, cancellationToken);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. بهروزرسانی ManualPayment
|
||||||
|
manualPayment.Status = ManualPaymentStatus.Approved;
|
||||||
|
manualPayment.ApprovedBy = approvedById;
|
||||||
|
manualPayment.ApprovedAt = DateTime.UtcNow;
|
||||||
|
manualPayment.TransactionId = transaction.Id;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Manual payment approved successfully. Id: {Id}, UserId: {UserId}, Amount: {Amount}, ApprovedBy: {ApprovedBy}",
|
||||||
|
manualPayment.Id,
|
||||||
|
manualPayment.UserId,
|
||||||
|
manualPayment.Amount,
|
||||||
|
approvedById
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
ex,
|
||||||
|
"Error approving manual payment: {ManualPaymentId}",
|
||||||
|
request.ManualPaymentId
|
||||||
|
);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TransactionType MapToTransactionType(ManualPaymentType type)
|
||||||
|
{
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
ManualPaymentType.CashDeposit => TransactionType.DepositExternal1,
|
||||||
|
ManualPaymentType.DiscountWalletCharge => TransactionType.DiscountWalletCharge,
|
||||||
|
ManualPaymentType.NetworkWalletCharge => TransactionType.NetworkCommission,
|
||||||
|
ManualPaymentType.Settlement => TransactionType.DepositExternal1,
|
||||||
|
ManualPaymentType.ErrorCorrection => TransactionType.DepositExternal1,
|
||||||
|
ManualPaymentType.Refund => TransactionType.Withdraw,
|
||||||
|
_ => TransactionType.DepositExternal1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.ManualPaymentCQ.Commands.CreateManualPayment;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دستور ثبت پرداخت دستی توسط Admin
|
||||||
|
/// </summary>
|
||||||
|
public class CreateManualPaymentCommand : IRequest<long>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه کاربری که پرداخت برای او ثبت میشود
|
||||||
|
/// </summary>
|
||||||
|
public long UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// مبلغ تراکنش (ریال)
|
||||||
|
/// </summary>
|
||||||
|
public long Amount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نوع تراکنش دستی
|
||||||
|
/// </summary>
|
||||||
|
public ManualPaymentType Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// توضیحات (اجباری)
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شماره مرجع یا شماره فیش (اختیاری)
|
||||||
|
/// </summary>
|
||||||
|
public string? ReferenceNumber { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Exceptions;
|
||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
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 payment for UserId: {UserId}, Amount: {Amount}, Type: {Type}",
|
||||||
|
request.UserId,
|
||||||
|
request.Amount,
|
||||||
|
request.Type
|
||||||
|
);
|
||||||
|
|
||||||
|
// 1. بررسی وجود کاربر
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. بررسی Admin فعلی
|
||||||
|
var currentUserId = _currentUser.UserId;
|
||||||
|
if (string.IsNullOrEmpty(currentUserId))
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("کاربر احراز هویت نشده است");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!long.TryParse(currentUserId, out var requestedById))
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("شناسه کاربر نامعتبر است");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. ایجاد ManualPayment
|
||||||
|
var manualPayment = new ManualPayment
|
||||||
|
{
|
||||||
|
UserId = request.UserId,
|
||||||
|
Amount = request.Amount,
|
||||||
|
Type = request.Type,
|
||||||
|
Description = request.Description,
|
||||||
|
ReferenceNumber = request.ReferenceNumber,
|
||||||
|
Status = ManualPaymentStatus.Pending,
|
||||||
|
RequestedBy = requestedById
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.ManualPayments.Add(manualPayment);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Manual payment created successfully. Id: {Id}, UserId: {UserId}, RequestedBy: {RequestedBy}",
|
||||||
|
manualPayment.Id,
|
||||||
|
request.UserId,
|
||||||
|
requestedById
|
||||||
|
);
|
||||||
|
|
||||||
|
return manualPayment.Id;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
ex,
|
||||||
|
"Error creating manual payment for UserId: {UserId}",
|
||||||
|
request.UserId
|
||||||
|
);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.ManualPaymentCQ.Commands.CreateManualPayment;
|
||||||
|
|
||||||
|
public class CreateManualPaymentCommandValidator : AbstractValidator<CreateManualPaymentCommand>
|
||||||
|
{
|
||||||
|
public CreateManualPaymentCommandValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.UserId)
|
||||||
|
.GreaterThan(0)
|
||||||
|
.WithMessage("شناسه کاربر باید بزرگتر از صفر باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.Amount)
|
||||||
|
.GreaterThan(0)
|
||||||
|
.WithMessage("مبلغ باید بزرگتر از صفر باشد")
|
||||||
|
.LessThanOrEqualTo(1_000_000_000)
|
||||||
|
.WithMessage("مبلغ نمیتواند بیشتر از 1 میلیارد ریال باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.Type)
|
||||||
|
.IsInEnum()
|
||||||
|
.WithMessage("نوع تراکنش نامعتبر است");
|
||||||
|
|
||||||
|
RuleFor(x => x.Description)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage("توضیحات الزامی است")
|
||||||
|
.MaximumLength(1000)
|
||||||
|
.WithMessage("توضیحات نمیتواند بیشتر از 1000 کاراکتر باشد");
|
||||||
|
|
||||||
|
RuleFor(x => x.ReferenceNumber)
|
||||||
|
.MaximumLength(100)
|
||||||
|
.WithMessage("شماره مرجع نمیتواند بیشتر از 100 کاراکتر باشد")
|
||||||
|
.When(x => !string.IsNullOrEmpty(x.ReferenceNumber));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.ManualPaymentCQ.Commands.RejectManualPayment;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دستور رد پرداخت دستی توسط SuperAdmin
|
||||||
|
/// </summary>
|
||||||
|
public class RejectManualPaymentCommand : IRequest<bool>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه ManualPayment
|
||||||
|
/// </summary>
|
||||||
|
public long ManualPaymentId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دلیل رد (الزامی)
|
||||||
|
/// </summary>
|
||||||
|
public string RejectionReason { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Exceptions;
|
||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Entities.Payment;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.ManualPaymentCQ.Commands.RejectManualPayment;
|
||||||
|
|
||||||
|
public class RejectManualPaymentCommandHandler : IRequestHandler<RejectManualPaymentCommand, bool>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
private readonly ICurrentUserService _currentUser;
|
||||||
|
private readonly ILogger<RejectManualPaymentCommandHandler> _logger;
|
||||||
|
|
||||||
|
public RejectManualPaymentCommandHandler(
|
||||||
|
IApplicationDbContext context,
|
||||||
|
ICurrentUserService currentUser,
|
||||||
|
ILogger<RejectManualPaymentCommandHandler> logger)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_currentUser = currentUser;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Handle(
|
||||||
|
RejectManualPaymentCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Rejecting manual payment: {ManualPaymentId}",
|
||||||
|
request.ManualPaymentId
|
||||||
|
);
|
||||||
|
|
||||||
|
// 1. پیدا کردن ManualPayment
|
||||||
|
var manualPayment = await _context.ManualPayments
|
||||||
|
.FirstOrDefaultAsync(m => m.Id == request.ManualPaymentId, cancellationToken);
|
||||||
|
|
||||||
|
if (manualPayment == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("ManualPayment not found: {Id}", request.ManualPaymentId);
|
||||||
|
throw new NotFoundException(nameof(ManualPayment), request.ManualPaymentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. بررسی وضعیت
|
||||||
|
if (manualPayment.Status != ManualPaymentStatus.Pending)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"ManualPayment {Id} is not in Pending status: {Status}",
|
||||||
|
request.ManualPaymentId,
|
||||||
|
manualPayment.Status
|
||||||
|
);
|
||||||
|
throw new BadRequestException($"فقط درخواستهای در وضعیت Pending قابل رد هستند. وضعیت فعلی: {manualPayment.Status}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. بررسی SuperAdmin
|
||||||
|
var currentUserId = _currentUser.UserId;
|
||||||
|
if (string.IsNullOrEmpty(currentUserId))
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("کاربر احراز هویت نشده است");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!long.TryParse(currentUserId, out var rejectedById))
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("شناسه کاربر نامعتبر است");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. رد درخواست
|
||||||
|
manualPayment.Status = ManualPaymentStatus.Rejected;
|
||||||
|
manualPayment.ApprovedBy = rejectedById;
|
||||||
|
manualPayment.ApprovedAt = DateTime.UtcNow;
|
||||||
|
manualPayment.RejectionReason = request.RejectionReason;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Manual payment rejected successfully. Id: {Id}, RejectedBy: {RejectedBy}, Reason: {Reason}",
|
||||||
|
manualPayment.Id,
|
||||||
|
rejectedById,
|
||||||
|
request.RejectionReason
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
ex,
|
||||||
|
"Error rejecting manual payment: {ManualPaymentId}",
|
||||||
|
request.ManualPaymentId
|
||||||
|
);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Models;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.ManualPaymentCQ.Queries.GetAllManualPayments;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کوئری دریافت لیست پرداختهای دستی با فیلتر
|
||||||
|
/// </summary>
|
||||||
|
public class GetAllManualPaymentsQuery : IRequest<GetAllManualPaymentsResponseDto>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// شماره صفحه
|
||||||
|
/// </summary>
|
||||||
|
public int PageNumber { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تعداد رکورد در هر صفحه
|
||||||
|
/// </summary>
|
||||||
|
public int PageSize { get; set; } = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// فیلتر بر اساس UserId (اختیاری)
|
||||||
|
/// </summary>
|
||||||
|
public long? UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// فیلتر بر اساس وضعیت (اختیاری)
|
||||||
|
/// </summary>
|
||||||
|
public ManualPaymentStatus? Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// فیلتر بر اساس نوع (اختیاری)
|
||||||
|
/// </summary>
|
||||||
|
public ManualPaymentType? Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// فیلتر بر اساس RequestedBy (اختیاری)
|
||||||
|
/// </summary>
|
||||||
|
public long? RequestedBy { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// مرتبسازی بر اساس تاریخ ایجاد (نزولی: true, صعودی: false)
|
||||||
|
/// </summary>
|
||||||
|
public bool OrderByDescending { get; set; } = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Application.Common.Models;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.ManualPaymentCQ.Queries.GetAllManualPayments;
|
||||||
|
|
||||||
|
public class GetAllManualPaymentsQueryHandler
|
||||||
|
: IRequestHandler<GetAllManualPaymentsQuery, GetAllManualPaymentsResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
private readonly ILogger<GetAllManualPaymentsQueryHandler> _logger;
|
||||||
|
|
||||||
|
public GetAllManualPaymentsQueryHandler(
|
||||||
|
IApplicationDbContext context,
|
||||||
|
ILogger<GetAllManualPaymentsQueryHandler> logger)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GetAllManualPaymentsResponseDto> Handle(
|
||||||
|
GetAllManualPaymentsQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Getting manual payments. Page: {Page}, PageSize: {PageSize}",
|
||||||
|
request.PageNumber,
|
||||||
|
request.PageSize
|
||||||
|
);
|
||||||
|
|
||||||
|
// ساخت Query با فیلترها
|
||||||
|
var query = _context.ManualPayments
|
||||||
|
.Include(m => m.User)
|
||||||
|
.AsQueryable();
|
||||||
|
|
||||||
|
// فیلتر UserId
|
||||||
|
if (request.UserId.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(m => m.UserId == request.UserId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// فیلتر Status
|
||||||
|
if (request.Status.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(m => m.Status == request.Status.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// فیلتر Type
|
||||||
|
if (request.Type.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(m => m.Type == request.Type.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// فیلتر RequestedBy
|
||||||
|
if (request.RequestedBy.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(m => m.RequestedBy == request.RequestedBy.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// شمارش کل
|
||||||
|
var totalCount = await query.CountAsync(cancellationToken);
|
||||||
|
|
||||||
|
// مرتبسازی
|
||||||
|
query = request.OrderByDescending
|
||||||
|
? query.OrderByDescending(m => m.Created)
|
||||||
|
: query.OrderBy(m => m.Created);
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
var skip = (request.PageNumber - 1) * request.PageSize;
|
||||||
|
var data = await query
|
||||||
|
.Skip(skip)
|
||||||
|
.Take(request.PageSize)
|
||||||
|
.Select(m => new ManualPaymentDto
|
||||||
|
{
|
||||||
|
Id = m.Id,
|
||||||
|
UserId = m.UserId,
|
||||||
|
UserFullName = m.User.FirstName + " " + m.User.LastName,
|
||||||
|
UserMobile = m.User.Mobile ?? "",
|
||||||
|
Amount = m.Amount,
|
||||||
|
Type = m.Type,
|
||||||
|
TypeDisplay = m.Type.ToString(),
|
||||||
|
Description = m.Description,
|
||||||
|
ReferenceNumber = m.ReferenceNumber,
|
||||||
|
Status = m.Status,
|
||||||
|
StatusDisplay = m.Status.ToString(),
|
||||||
|
RequestedBy = m.RequestedBy,
|
||||||
|
RequestedByName = "", // باید از جدول User گرفته شود
|
||||||
|
ApprovedBy = m.ApprovedBy,
|
||||||
|
ApprovedByName = null,
|
||||||
|
ApprovedAt = m.ApprovedAt,
|
||||||
|
RejectionReason = m.RejectionReason,
|
||||||
|
TransactionId = m.TransactionId,
|
||||||
|
Created = m.Created
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var metaData = new MetaData
|
||||||
|
{
|
||||||
|
TotalCount = totalCount,
|
||||||
|
PageSize = request.PageSize,
|
||||||
|
CurrentPage = request.PageNumber,
|
||||||
|
TotalPage = (int)Math.Ceiling(totalCount / (double)request.PageSize),
|
||||||
|
HasNext = request.PageNumber < (int)Math.Ceiling(totalCount / (double)request.PageSize),
|
||||||
|
HasPrevious = request.PageNumber > 1
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Retrieved {Count} manual payments. Total: {Total}",
|
||||||
|
data.Count,
|
||||||
|
totalCount
|
||||||
|
);
|
||||||
|
|
||||||
|
return new GetAllManualPaymentsResponseDto
|
||||||
|
{
|
||||||
|
MetaData = metaData,
|
||||||
|
Models = data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Models;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.ManualPaymentCQ.Queries.GetAllManualPayments;
|
||||||
|
|
||||||
|
public class GetAllManualPaymentsResponseDto
|
||||||
|
{
|
||||||
|
public MetaData? MetaData { get; set; }
|
||||||
|
public List<ManualPaymentDto>? Models { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ManualPaymentDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public long UserId { get; set; }
|
||||||
|
public string UserFullName { get; set; } = string.Empty;
|
||||||
|
public string UserMobile { get; set; } = string.Empty;
|
||||||
|
public long Amount { get; set; }
|
||||||
|
public ManualPaymentType Type { get; set; }
|
||||||
|
public string TypeDisplay { get; set; } = string.Empty;
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public string? ReferenceNumber { get; set; }
|
||||||
|
public ManualPaymentStatus Status { get; set; }
|
||||||
|
public string StatusDisplay { get; set; } = string.Empty;
|
||||||
|
public long RequestedBy { get; set; }
|
||||||
|
public string RequestedByName { get; set; } = string.Empty;
|
||||||
|
public long? ApprovedBy { get; set; }
|
||||||
|
public string? ApprovedByName { get; set; }
|
||||||
|
public DateTime? ApprovedAt { get; set; }
|
||||||
|
public string? RejectionReason { get; set; }
|
||||||
|
public long? TransactionId { get; set; }
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.OrderManagementCQ.Commands.CancelOrderByAdmin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دستور لغو سفارش توسط Admin
|
||||||
|
/// </summary>
|
||||||
|
public class CancelOrderByAdminCommand : IRequest<Unit>
|
||||||
|
{
|
||||||
|
public long OrderId { get; set; }
|
||||||
|
public string CancelReason { get; set; } = string.Empty;
|
||||||
|
public bool RefundToWallet { get; set; } = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.OrderManagementCQ.Commands.CancelOrderByAdmin;
|
||||||
|
|
||||||
|
public class CancelOrderByAdminCommandHandler : IRequestHandler<CancelOrderByAdminCommand, Unit>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
private readonly ICurrentUserService _currentUser;
|
||||||
|
private readonly ILogger<CancelOrderByAdminCommandHandler> _logger;
|
||||||
|
|
||||||
|
public CancelOrderByAdminCommandHandler(
|
||||||
|
IApplicationDbContext context,
|
||||||
|
ICurrentUserService currentUser,
|
||||||
|
ILogger<CancelOrderByAdminCommandHandler> logger)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_currentUser = currentUser;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> Handle(CancelOrderByAdminCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// بررسی Admin
|
||||||
|
if (string.IsNullOrEmpty(_currentUser.UserId))
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("کاربر احراز هویت نشده است");
|
||||||
|
}
|
||||||
|
|
||||||
|
var order = await _context.UserOrders
|
||||||
|
.Include(x => x.User)
|
||||||
|
.ThenInclude(x => x.UserWallets)
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == request.OrderId && !x.IsDeleted, cancellationToken);
|
||||||
|
|
||||||
|
if (order == null)
|
||||||
|
{
|
||||||
|
throw new KeyNotFoundException($"سفارش با شناسه {request.OrderId} یافت نشد");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.DeliveryStatus == DeliveryStatus.Cancelled)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("این سفارش قبلاً لغو شده است");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.DeliveryStatus == DeliveryStatus.Delivered)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("سفارش تحویل داده شده را نمیتوان لغو کرد");
|
||||||
|
}
|
||||||
|
|
||||||
|
// تغییر وضعیت به لغو شده
|
||||||
|
order.DeliveryStatus = DeliveryStatus.Cancelled;
|
||||||
|
order.DeliveryDescription = $"لغو توسط Admin: {request.CancelReason}";
|
||||||
|
|
||||||
|
// بازگشت وجه به کیف پول
|
||||||
|
if (request.RefundToWallet && order.PaymentMethod == PaymentMethod.Wallet)
|
||||||
|
{
|
||||||
|
var wallet = order.User.UserWallets.FirstOrDefault();
|
||||||
|
if (wallet != null)
|
||||||
|
{
|
||||||
|
var walletLog = new UserWalletChangeLog
|
||||||
|
{
|
||||||
|
WalletId = wallet.Id,
|
||||||
|
CurrentBalance = wallet.Balance,
|
||||||
|
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||||
|
CurrentDiscountBalance = wallet.DiscountBalance,
|
||||||
|
ChangeValue = order.Amount,
|
||||||
|
ChangeDiscountValue = 0,
|
||||||
|
IsIncrease = true,
|
||||||
|
RefrenceId = order.Id
|
||||||
|
};
|
||||||
|
|
||||||
|
wallet.Balance += order.Amount;
|
||||||
|
|
||||||
|
await _context.UserWalletChangeLogs.AddAsync(walletLog, cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Refund processed. OrderId: {OrderId}, Amount: {Amount}, UserId: {UserId}",
|
||||||
|
order.Id,
|
||||||
|
order.Amount,
|
||||||
|
order.UserId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Order cancelled by admin. OrderId: {OrderId}, Reason: {Reason}, Refunded: {Refunded}, Admin: {AdminId}",
|
||||||
|
order.Id,
|
||||||
|
request.CancelReason,
|
||||||
|
request.RefundToWallet,
|
||||||
|
_currentUser.UserId
|
||||||
|
);
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.OrderManagementCQ.Commands.CancelOrderByAdmin;
|
||||||
|
|
||||||
|
public class CancelOrderByAdminCommandValidator : AbstractValidator<CancelOrderByAdminCommand>
|
||||||
|
{
|
||||||
|
public CancelOrderByAdminCommandValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.OrderId)
|
||||||
|
.GreaterThan(0).WithMessage("شناسه سفارش نامعتبر است");
|
||||||
|
|
||||||
|
RuleFor(x => x.CancelReason)
|
||||||
|
.NotEmpty().WithMessage("دلیل لغو الزامی است")
|
||||||
|
.MaximumLength(500).WithMessage("دلیل لغو نمیتواند بیشتر از 500 کاراکتر باشد");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.OrderManagementCQ.Commands.UpdateOrderStatus;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دستور تغییر وضعیت ارسال سفارش (Admin)
|
||||||
|
/// </summary>
|
||||||
|
public class UpdateOrderStatusCommand : IRequest<Unit>
|
||||||
|
{
|
||||||
|
public long OrderId { get; set; }
|
||||||
|
public DeliveryStatus NewStatus { get; set; }
|
||||||
|
public string? TrackingCode { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.OrderManagementCQ.Commands.UpdateOrderStatus;
|
||||||
|
|
||||||
|
public class UpdateOrderStatusCommandHandler : IRequestHandler<UpdateOrderStatusCommand, Unit>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
private readonly ICurrentUserService _currentUser;
|
||||||
|
private readonly ILogger<UpdateOrderStatusCommandHandler> _logger;
|
||||||
|
|
||||||
|
public UpdateOrderStatusCommandHandler(
|
||||||
|
IApplicationDbContext context,
|
||||||
|
ICurrentUserService currentUser,
|
||||||
|
ILogger<UpdateOrderStatusCommandHandler> logger)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_currentUser = currentUser;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> Handle(UpdateOrderStatusCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// بررسی Admin
|
||||||
|
if (string.IsNullOrEmpty(_currentUser.UserId))
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("کاربر احراز هویت نشده است");
|
||||||
|
}
|
||||||
|
|
||||||
|
var order = await _context.UserOrders
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == request.OrderId && !x.IsDeleted, cancellationToken);
|
||||||
|
|
||||||
|
if (order == null)
|
||||||
|
{
|
||||||
|
throw new KeyNotFoundException($"سفارش با شناسه {request.OrderId} یافت نشد");
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldStatus = order.DeliveryStatus;
|
||||||
|
|
||||||
|
order.DeliveryStatus = request.NewStatus;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(request.TrackingCode))
|
||||||
|
{
|
||||||
|
order.TrackingCode = request.TrackingCode.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(request.Description))
|
||||||
|
{
|
||||||
|
order.DeliveryDescription = request.Description.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Order status updated by admin. OrderId: {OrderId}, OldStatus: {OldStatus}, NewStatus: {NewStatus}, Admin: {AdminId}",
|
||||||
|
order.Id,
|
||||||
|
oldStatus,
|
||||||
|
request.NewStatus,
|
||||||
|
_currentUser.UserId
|
||||||
|
);
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.OrderManagementCQ.Commands.UpdateOrderStatus;
|
||||||
|
|
||||||
|
public class UpdateOrderStatusCommandValidator : AbstractValidator<UpdateOrderStatusCommand>
|
||||||
|
{
|
||||||
|
public UpdateOrderStatusCommandValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.OrderId)
|
||||||
|
.GreaterThan(0).WithMessage("شناسه سفارش نامعتبر است");
|
||||||
|
|
||||||
|
RuleFor(x => x.NewStatus)
|
||||||
|
.IsInEnum().WithMessage("وضعیت ارسال نامعتبر است");
|
||||||
|
|
||||||
|
RuleFor(x => x.TrackingCode)
|
||||||
|
.MaximumLength(50).WithMessage("کد رهگیری نمیتواند بیشتر از 50 کاراکتر باشد")
|
||||||
|
.When(x => !string.IsNullOrEmpty(x.TrackingCode));
|
||||||
|
|
||||||
|
RuleFor(x => x.Description)
|
||||||
|
.MaximumLength(500).WithMessage("توضیحات نمیتواند بیشتر از 500 کاراکتر باشد")
|
||||||
|
.When(x => !string.IsNullOrEmpty(x.Description));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.OrderVATCQ.Queries.GetOrderVAT;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کوئری دریافت اطلاعات مالیات سفارش
|
||||||
|
/// </summary>
|
||||||
|
public class GetOrderVATQuery : IRequest<OrderVATDto?>
|
||||||
|
{
|
||||||
|
public long OrderId { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.OrderVATCQ.Queries.GetOrderVAT;
|
||||||
|
|
||||||
|
public class GetOrderVATQueryHandler : IRequestHandler<GetOrderVATQuery, OrderVATDto?>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
private readonly ILogger<GetOrderVATQueryHandler> _logger;
|
||||||
|
|
||||||
|
public GetOrderVATQueryHandler(
|
||||||
|
IApplicationDbContext context,
|
||||||
|
ILogger<GetOrderVATQueryHandler> logger)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<OrderVATDto?> Handle(GetOrderVATQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var orderVAT = await _context.OrderVATs
|
||||||
|
.Where(x => x.OrderId == request.OrderId && !x.IsDeleted)
|
||||||
|
.Select(x => new OrderVATDto
|
||||||
|
{
|
||||||
|
Id = x.Id,
|
||||||
|
OrderId = x.OrderId,
|
||||||
|
VATRate = x.VATRate,
|
||||||
|
VATRatePercentage = $"{x.VATRate * 100:F1}%",
|
||||||
|
BaseAmount = x.BaseAmount,
|
||||||
|
VATAmount = x.VATAmount,
|
||||||
|
TotalAmount = x.TotalAmount,
|
||||||
|
IsPaid = x.IsPaid,
|
||||||
|
PaidAt = x.PaidAt,
|
||||||
|
Note = x.Note,
|
||||||
|
Created = x.Created
|
||||||
|
})
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (orderVAT != null)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Retrieved VAT for order {OrderId}. VAT Amount: {VATAmount}",
|
||||||
|
request.OrderId,
|
||||||
|
orderVAT.VATAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return orderVAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace CMSMicroservice.Application.OrderVATCQ.Queries.GetOrderVAT;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO اطلاعات مالیات سفارش
|
||||||
|
/// </summary>
|
||||||
|
public class OrderVATDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public long OrderId { get; set; }
|
||||||
|
public decimal VATRate { get; set; }
|
||||||
|
public string VATRatePercentage { get; set; } = string.Empty;
|
||||||
|
public long BaseAmount { get; set; }
|
||||||
|
public long VATAmount { get; set; }
|
||||||
|
public long TotalAmount { get; set; }
|
||||||
|
public bool IsPaid { get; set; }
|
||||||
|
public DateTime? PaidAt { get; set; }
|
||||||
|
public string? Note { get; set; }
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
}
|
||||||
@@ -62,7 +62,7 @@ public class VerifyOtpTokenCommandHandler : IRequestHandler<VerifyOtpTokenComman
|
|||||||
if (parent == null)
|
if (parent == null)
|
||||||
return new VerifyOtpTokenResponseDto() { Success = false, Message = "معرف وجود ندارد." };
|
return new VerifyOtpTokenResponseDto() { Success = false, Message = "معرف وجود ندارد." };
|
||||||
|
|
||||||
if (await _context.Users.CountAsync(x => x.ParentId == parent.Id, cancellationToken: cancellationToken) > 1)
|
if (await _context.Users.CountAsync(x => x.NetworkParentId == parent.Id, cancellationToken: cancellationToken) > 1)
|
||||||
return new VerifyOtpTokenResponseDto() { Success = false, Message = "ظرفیت معرف تکمیل است!!" };
|
return new VerifyOtpTokenResponseDto() { Success = false, Message = "ظرفیت معرف تکمیل است!!" };
|
||||||
|
|
||||||
user = new User
|
user = new User
|
||||||
@@ -73,7 +73,7 @@ public class VerifyOtpTokenCommandHandler : IRequestHandler<VerifyOtpTokenComman
|
|||||||
MobileVerifiedAt = now,
|
MobileVerifiedAt = now,
|
||||||
IsRulesAccepted = true,
|
IsRulesAccepted = true,
|
||||||
RulesAcceptedAt = now,
|
RulesAcceptedAt = now,
|
||||||
ParentId = parent.Id
|
NetworkParentId = parent.Id
|
||||||
};
|
};
|
||||||
await _context.Users.AddAsync(user, cancellationToken);
|
await _context.Users.AddAsync(user, cancellationToken);
|
||||||
user.AddDomainEvent(new CreateNewUserEvent(user));
|
user.AddDomainEvent(new CreateNewUserEvent(user));
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ using CMSMicroservice.Domain.Enums;
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchasePackage;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// دستور خرید پکیج طلایی از طریق درگاه بانکی
|
/// دستور خرید پکیج از طریق درگاه بانکی
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PurchaseGoldenPackageCommand : IRequest<PaymentInitiateResult>
|
public class PurchasePackageCommand : IRequest<PaymentInitiateResult>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// شناسه کاربر
|
/// شناسه کاربر
|
||||||
@@ -8,19 +8,19 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using ValidationException = FluentValidation.ValidationException;
|
using ValidationException = FluentValidation.ValidationException;
|
||||||
|
|
||||||
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchasePackage;
|
||||||
|
|
||||||
public class PurchaseGoldenPackageCommandHandler
|
public class PurchasePackageCommandHandler
|
||||||
: IRequestHandler<PurchaseGoldenPackageCommand, PaymentInitiateResult>
|
: IRequestHandler<PurchasePackageCommand, PaymentInitiateResult>
|
||||||
{
|
{
|
||||||
private readonly IApplicationDbContext _context;
|
private readonly IApplicationDbContext _context;
|
||||||
private readonly IPaymentGatewayService _paymentGateway;
|
private readonly IPaymentGatewayService _paymentGateway;
|
||||||
private readonly ILogger<PurchaseGoldenPackageCommandHandler> _logger;
|
private readonly ILogger<PurchasePackageCommandHandler> _logger;
|
||||||
|
|
||||||
public PurchaseGoldenPackageCommandHandler(
|
public PurchasePackageCommandHandler(
|
||||||
IApplicationDbContext context,
|
IApplicationDbContext context,
|
||||||
IPaymentGatewayService paymentGateway,
|
IPaymentGatewayService paymentGateway,
|
||||||
ILogger<PurchaseGoldenPackageCommandHandler> logger)
|
ILogger<PurchasePackageCommandHandler> logger)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_paymentGateway = paymentGateway;
|
_paymentGateway = paymentGateway;
|
||||||
@@ -28,13 +28,13 @@ public class PurchaseGoldenPackageCommandHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PaymentInitiateResult> Handle(
|
public async Task<PaymentInitiateResult> Handle(
|
||||||
PurchaseGoldenPackageCommand request,
|
PurchasePackageCommand request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"Starting golden package purchase for UserId: {UserId}",
|
"Starting package purchase for UserId: {UserId}",
|
||||||
request.UserId
|
request.UserId
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -57,11 +57,11 @@ public class PurchaseGoldenPackageCommandHandler
|
|||||||
user.PackagePurchaseMethod
|
user.PackagePurchaseMethod
|
||||||
);
|
);
|
||||||
throw new ValidationException(
|
throw new ValidationException(
|
||||||
"شما قبلاً پکیج طلایی را خریداری کردهاید"
|
"شما قبلاً پکیج را خریداری کردهاید"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. پیدا کردن پکیج طلایی
|
// 3. پیدا کردن پکیج (فعلاً پکیج طلایی)
|
||||||
var goldenPackage = await _context.Packages
|
var goldenPackage = await _context.Packages
|
||||||
.FirstOrDefaultAsync(
|
.FirstOrDefaultAsync(
|
||||||
p => p.Title.Contains("طلایی") || p.Title.Contains("Golden"),
|
p => p.Title.Contains("طلایی") || p.Title.Contains("Golden"),
|
||||||
@@ -70,12 +70,12 @@ public class PurchaseGoldenPackageCommandHandler
|
|||||||
|
|
||||||
if (goldenPackage == null)
|
if (goldenPackage == null)
|
||||||
{
|
{
|
||||||
_logger.LogError("Golden package not found in database");
|
_logger.LogError("Package not found in database");
|
||||||
throw new NotFoundException("پکیج طلایی یافت نشد");
|
throw new NotFoundException("پکیج یافت نشد");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. پیدا کردن آدرس پیشفرض کاربر (برای فیلد اجباری)
|
// 4. پیدا کردن آدرس پیشفرض کاربر (برای فیلد اجباری)
|
||||||
var defaultAddress = await _context.UserAddresss
|
var defaultAddress = await _context.UserAddresses
|
||||||
.Where(a => a.UserId == request.UserId)
|
.Where(a => a.UserId == request.UserId)
|
||||||
.OrderByDescending(a => a.Created)
|
.OrderByDescending(a => a.Created)
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
@@ -116,8 +116,8 @@ public class PurchaseGoldenPackageCommandHandler
|
|||||||
Amount = order.Amount,
|
Amount = order.Amount,
|
||||||
UserId = user.Id,
|
UserId = user.Id,
|
||||||
Mobile = user.Mobile ?? "",
|
Mobile = user.Mobile ?? "",
|
||||||
CallbackUrl = $"https://yourdomain.com/api/package/verify-golden-package",
|
CallbackUrl = $"https://yourdomain.com/api/package/verify-package",
|
||||||
Description = $"خرید پکیج طلایی - سفارش #{order.Id}"
|
Description = $"خرید پکیج - سفارش #{order.Id}"
|
||||||
};
|
};
|
||||||
|
|
||||||
var paymentResult = await _paymentGateway.InitiatePaymentAsync(paymentRequest);
|
var paymentResult = await _paymentGateway.InitiatePaymentAsync(paymentRequest);
|
||||||
@@ -151,7 +151,7 @@ public class PurchaseGoldenPackageCommandHandler
|
|||||||
{
|
{
|
||||||
_logger.LogError(
|
_logger.LogError(
|
||||||
ex,
|
ex,
|
||||||
"Error in PurchaseGoldenPackageCommand for UserId: {UserId}",
|
"Error in PurchasePackageCommand for UserId: {UserId}",
|
||||||
request.UserId
|
request.UserId
|
||||||
);
|
);
|
||||||
throw;
|
throw;
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
|
||||||
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
|
namespace CMSMicroservice.Application.PackageCQ.Commands.PurchasePackage;
|
||||||
|
|
||||||
public class PurchaseGoldenPackageCommandValidator : AbstractValidator<PurchaseGoldenPackageCommand>
|
public class PurchasePackageCommandValidator : AbstractValidator<PurchasePackageCommand>
|
||||||
{
|
{
|
||||||
public PurchaseGoldenPackageCommandValidator()
|
public PurchasePackageCommandValidator()
|
||||||
{
|
{
|
||||||
RuleFor(x => x.UserId)
|
RuleFor(x => x.UserId)
|
||||||
.GreaterThan(0)
|
.GreaterThan(0)
|
||||||
@@ -2,12 +2,12 @@ using CMSMicroservice.Application.Common.Interfaces;
|
|||||||
using CMSMicroservice.Application.Common.Models;
|
using CMSMicroservice.Application.Common.Models;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
|
||||||
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
|
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyPackagePurchase;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// دستور تأیید پرداخت پکیج طلایی و شارژ کیف پول
|
/// دستور تأیید پرداخت پکیج و شارژ کیف پول
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class VerifyGoldenPackagePurchaseCommand : IRequest<bool>
|
public class VerifyPackagePurchaseCommand : IRequest<bool>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// شناسه سفارش
|
/// شناسه سفارش
|
||||||
@@ -8,19 +8,19 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using ValidationException = FluentValidation.ValidationException;
|
using ValidationException = FluentValidation.ValidationException;
|
||||||
|
|
||||||
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
|
namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyPackagePurchase;
|
||||||
|
|
||||||
public class VerifyGoldenPackagePurchaseCommandHandler
|
public class VerifyPackagePurchaseCommandHandler
|
||||||
: IRequestHandler<VerifyGoldenPackagePurchaseCommand, bool>
|
: IRequestHandler<VerifyPackagePurchaseCommand, bool>
|
||||||
{
|
{
|
||||||
private readonly IApplicationDbContext _context;
|
private readonly IApplicationDbContext _context;
|
||||||
private readonly IPaymentGatewayService _paymentGateway;
|
private readonly IPaymentGatewayService _paymentGateway;
|
||||||
private readonly ILogger<VerifyGoldenPackagePurchaseCommandHandler> _logger;
|
private readonly ILogger<VerifyPackagePurchaseCommandHandler> _logger;
|
||||||
|
|
||||||
public VerifyGoldenPackagePurchaseCommandHandler(
|
public VerifyPackagePurchaseCommandHandler(
|
||||||
IApplicationDbContext context,
|
IApplicationDbContext context,
|
||||||
IPaymentGatewayService paymentGateway,
|
IPaymentGatewayService paymentGateway,
|
||||||
ILogger<VerifyGoldenPackagePurchaseCommandHandler> logger)
|
ILogger<VerifyPackagePurchaseCommandHandler> logger)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_paymentGateway = paymentGateway;
|
_paymentGateway = paymentGateway;
|
||||||
@@ -28,13 +28,13 @@ public class VerifyGoldenPackagePurchaseCommandHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> Handle(
|
public async Task<bool> Handle(
|
||||||
VerifyGoldenPackagePurchaseCommand request,
|
VerifyPackagePurchaseCommand request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"Verifying golden package purchase. OrderId: {OrderId}, Authority: {Authority}",
|
"Verifying package purchase. OrderId: {OrderId}, Authority: {Authority}",
|
||||||
request.OrderId,
|
request.OrderId,
|
||||||
request.Authority
|
request.Authority
|
||||||
);
|
);
|
||||||
@@ -111,17 +111,17 @@ public class VerifyGoldenPackagePurchaseCommandHandler
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 5. ثبت Transaction
|
// 5. ثبت Transaction
|
||||||
var transaction = new Transactions
|
var transaction = new Transaction
|
||||||
{
|
{
|
||||||
Amount = order.Amount,
|
Amount = order.Amount,
|
||||||
Description = $"خرید پکیج طلایی از درگاه - سفارش #{order.Id}",
|
Description = $"خرید پکیج از درگاه - سفارش #{order.Id}",
|
||||||
PaymentStatus = PaymentStatus.Success,
|
PaymentStatus = PaymentStatus.Success,
|
||||||
PaymentDate = DateTime.UtcNow,
|
PaymentDate = DateTime.UtcNow,
|
||||||
RefId = verifyResult.RefId,
|
RefId = verifyResult.RefId,
|
||||||
Type = TransactionType.DepositIpg
|
Type = TransactionType.DepositIpg
|
||||||
};
|
};
|
||||||
|
|
||||||
_context.Transactionss.Add(transaction);
|
_context.Transactions.Add(transaction);
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
// 6. ثبت لاگ تغییر Balance
|
// 6. ثبت لاگ تغییر Balance
|
||||||
@@ -166,7 +166,7 @@ public class VerifyGoldenPackagePurchaseCommandHandler
|
|||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"Golden package purchase verified successfully. " +
|
"Package purchase verified successfully. " +
|
||||||
"OrderId: {OrderId}, UserId: {UserId}, TransactionId: {TransactionId}, RefId: {RefId}",
|
"OrderId: {OrderId}, UserId: {UserId}, TransactionId: {TransactionId}, RefId: {RefId}",
|
||||||
order.Id,
|
order.Id,
|
||||||
order.UserId,
|
order.UserId,
|
||||||
@@ -180,7 +180,7 @@ public class VerifyGoldenPackagePurchaseCommandHandler
|
|||||||
{
|
{
|
||||||
_logger.LogError(
|
_logger.LogError(
|
||||||
ex,
|
ex,
|
||||||
"Error in VerifyGoldenPackagePurchaseCommand. OrderId: {OrderId}",
|
"Error in VerifyPackagePurchaseCommand. OrderId: {OrderId}",
|
||||||
request.OrderId
|
request.OrderId
|
||||||
);
|
);
|
||||||
throw;
|
throw;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace CMSMicroservice.Application.ProductCategoryCQ.Commands.CreateNewProductCategory;
|
||||||
|
public record CreateNewProductCategoryCommand : IRequest<CreateNewProductCategoryResponseDto>
|
||||||
|
{
|
||||||
|
//شناسه محصول
|
||||||
|
public long ProductId { get; init; }
|
||||||
|
//شناسه دسته بندی
|
||||||
|
public long CategoryId { get; init; }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using CMSMicroservice.Domain.Events;
|
||||||
|
namespace CMSMicroservice.Application.ProductCategoryCQ.Commands.CreateNewProductCategory;
|
||||||
|
public class CreateNewProductCategoryCommandHandler : IRequestHandler<CreateNewProductCategoryCommand, CreateNewProductCategoryResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public CreateNewProductCategoryCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CreateNewProductCategoryResponseDto> Handle(CreateNewProductCategoryCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = request.Adapt<ProductCategory>();
|
||||||
|
await _context.ProductCategories.AddAsync(entity, cancellationToken);
|
||||||
|
entity.AddDomainEvent(new CreateNewProductCategoryEvent(entity));
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
return entity.Adapt<CreateNewProductCategoryResponseDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
namespace CMSMicroservice.Application.PruductCategoryCQ.Commands.CreateNewPruductCategory;
|
namespace CMSMicroservice.Application.ProductCategoryCQ.Commands.CreateNewProductCategory;
|
||||||
public class CreateNewPruductCategoryCommandValidator : AbstractValidator<CreateNewPruductCategoryCommand>
|
public class CreateNewProductCategoryCommandValidator : AbstractValidator<CreateNewProductCategoryCommand>
|
||||||
{
|
{
|
||||||
public CreateNewPruductCategoryCommandValidator()
|
public CreateNewProductCategoryCommandValidator()
|
||||||
{
|
{
|
||||||
RuleFor(model => model.ProductId)
|
RuleFor(model => model.ProductId)
|
||||||
.NotNull();
|
.NotNull();
|
||||||
@@ -10,7 +10,7 @@ public class CreateNewPruductCategoryCommandValidator : AbstractValidator<Create
|
|||||||
}
|
}
|
||||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||||
{
|
{
|
||||||
var result = await ValidateAsync(ValidationContext<CreateNewPruductCategoryCommand>.CreateWithOptions((CreateNewPruductCategoryCommand)model, x => x.IncludeProperties(propertyName)));
|
var result = await ValidateAsync(ValidationContext<CreateNewProductCategoryCommand>.CreateWithOptions((CreateNewProductCategoryCommand)model, x => x.IncludeProperties(propertyName)));
|
||||||
if (result.IsValid)
|
if (result.IsValid)
|
||||||
return Array.Empty<string>();
|
return Array.Empty<string>();
|
||||||
return result.Errors.Select(e => e.ErrorMessage);
|
return result.Errors.Select(e => e.ErrorMessage);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace CMSMicroservice.Application.ProductCategoryCQ.Commands.CreateNewProductCategory;
|
||||||
|
public class CreateNewProductCategoryResponseDto
|
||||||
|
{
|
||||||
|
//شناسه
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace CMSMicroservice.Application.ProductCategoryCQ.Commands.DeleteProductCategory;
|
||||||
|
public record DeleteProductCategoryCommand : IRequest<Unit>
|
||||||
|
{
|
||||||
|
//شناسه
|
||||||
|
public long Id { get; init; }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using CMSMicroservice.Domain.Events;
|
||||||
|
namespace CMSMicroservice.Application.ProductCategoryCQ.Commands.DeleteProductCategory;
|
||||||
|
public class DeleteProductCategoryCommandHandler : IRequestHandler<DeleteProductCategoryCommand, Unit>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public DeleteProductCategoryCommandHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> Handle(DeleteProductCategoryCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _context.ProductCategories
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken) ?? throw new NotFoundException(nameof(ProductCategory), request.Id);
|
||||||
|
entity.IsDeleted = true;
|
||||||
|
_context.ProductCategories.Update(entity);
|
||||||
|
entity.AddDomainEvent(new DeleteProductCategoryEvent(entity));
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
namespace CMSMicroservice.Application.PruductCategoryCQ.Commands.DeletePruductCategory;
|
namespace CMSMicroservice.Application.ProductCategoryCQ.Commands.DeleteProductCategory;
|
||||||
public class DeletePruductCategoryCommandValidator : AbstractValidator<DeletePruductCategoryCommand>
|
public class DeleteProductCategoryCommandValidator : AbstractValidator<DeleteProductCategoryCommand>
|
||||||
{
|
{
|
||||||
public DeletePruductCategoryCommandValidator()
|
public DeleteProductCategoryCommandValidator()
|
||||||
{
|
{
|
||||||
RuleFor(model => model.Id)
|
RuleFor(model => model.Id)
|
||||||
.NotNull();
|
.NotNull();
|
||||||
}
|
}
|
||||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||||
{
|
{
|
||||||
var result = await ValidateAsync(ValidationContext<DeletePruductCategoryCommand>.CreateWithOptions((DeletePruductCategoryCommand)model, x => x.IncludeProperties(propertyName)));
|
var result = await ValidateAsync(ValidationContext<DeleteProductCategoryCommand>.CreateWithOptions((DeleteProductCategoryCommand)model, x => x.IncludeProperties(propertyName)));
|
||||||
if (result.IsValid)
|
if (result.IsValid)
|
||||||
return Array.Empty<string>();
|
return Array.Empty<string>();
|
||||||
return result.Errors.Select(e => e.ErrorMessage);
|
return result.Errors.Select(e => e.ErrorMessage);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user