feat: Implement Approve and Reject Withdrawal commands with handlers

This commit is contained in:
masoodafar-web
2025-12-01 16:48:07 +03:30
parent 8d31a8c026
commit 4aaf2247ff
27 changed files with 989 additions and 1 deletions
@@ -0,0 +1,32 @@
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools;
/// <summary>
/// Query برای دریافت لیست تمام استخرهای کمیسیون هفتگی
/// </summary>
public record GetAllWeeklyPoolsQuery : IRequest<GetAllWeeklyPoolsResponseDto>
{
/// <summary>
/// از هفته (فیلتر اختیاری)
/// </summary>
public string? FromWeek { get; init; }
/// <summary>
/// تا هفته (فیلتر اختیاری)
/// </summary>
public string? ToWeek { get; init; }
/// <summary>
/// فقط Pool های محاسبه شده
/// </summary>
public bool? OnlyCalculated { get; init; }
/// <summary>
/// شماره صفحه
/// </summary>
public int PageIndex { get; init; } = 1;
/// <summary>
/// تعداد در صفحه
/// </summary>
public int PageSize { get; init; } = 10;
}
@@ -0,0 +1,67 @@
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools;
public class GetAllWeeklyPoolsQueryHandler : IRequestHandler<GetAllWeeklyPoolsQuery, GetAllWeeklyPoolsResponseDto>
{
private readonly IApplicationDbContext _context;
public GetAllWeeklyPoolsQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<GetAllWeeklyPoolsResponseDto> Handle(GetAllWeeklyPoolsQuery request, CancellationToken cancellationToken)
{
var query = _context.WeeklyCommissionPools.AsNoTracking();
// Apply filters
if (!string.IsNullOrWhiteSpace(request.FromWeek))
{
query = query.Where(x => string.Compare(x.WeekNumber, request.FromWeek) >= 0);
}
if (!string.IsNullOrWhiteSpace(request.ToWeek))
{
query = query.Where(x => string.Compare(x.WeekNumber, request.ToWeek) <= 0);
}
if (request.OnlyCalculated.HasValue && request.OnlyCalculated.Value)
{
query = query.Where(x => x.IsCalculated);
}
// Order by week number descending (newest first)
query = query.OrderByDescending(x => x.WeekNumber);
// Count total
var totalCount = await query.CountAsync(cancellationToken);
// Paginate
var pools = await query
.Skip((request.PageIndex - 1) * request.PageSize)
.Take(request.PageSize)
.Select(x => new WeeklyCommissionPoolDto
{
Id = x.Id,
WeekNumber = x.WeekNumber,
TotalPoolAmount = x.TotalPoolAmount,
TotalBalances = x.TotalBalances,
ValuePerBalance = x.ValuePerBalance,
IsCalculated = x.IsCalculated,
CalculatedAt = x.CalculatedAt,
Created = x.Created
})
.ToListAsync(cancellationToken);
return new GetAllWeeklyPoolsResponseDto
{
MetaData = new MetaDataDto
{
TotalCount = totalCount,
PageSize = request.PageSize,
CurrentPage = request.PageIndex,
TotalPages = (int)Math.Ceiling(totalCount / (double)request.PageSize)
},
Models = pools
};
}
}
@@ -0,0 +1,27 @@
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools;
public record GetAllWeeklyPoolsResponseDto
{
public MetaDataDto MetaData { get; init; } = new();
public List<WeeklyCommissionPoolDto> Models { get; init; } = new();
}
public record WeeklyCommissionPoolDto
{
public long Id { get; init; }
public string WeekNumber { get; init; } = string.Empty;
public long TotalPoolAmount { get; init; }
public int TotalBalances { get; init; }
public long ValuePerBalance { get; init; }
public bool IsCalculated { get; init; }
public DateTime? CalculatedAt { get; init; }
public DateTime Created { get; init; }
}
public record MetaDataDto
{
public int TotalCount { get; init; }
public int PageSize { get; init; }
public int CurrentPage { get; init; }
public int TotalPages { get; init; }
}
@@ -0,0 +1,10 @@
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests;
public class GetWithdrawalRequestsQuery : IRequest<GetWithdrawalRequestsResponseDto>
{
public int? Status { get; set; } // CommissionPayoutStatus enum
public long? UserId { get; set; }
public string? WeekNumber { get; set; }
public PaginationState? PaginationState { get; set; }
public string? SortBy { get; set; }
}
@@ -0,0 +1,67 @@
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests;
public class GetWithdrawalRequestsQueryHandler : IRequestHandler<GetWithdrawalRequestsQuery, GetWithdrawalRequestsResponseDto>
{
private readonly IApplicationDbContext _context;
public GetWithdrawalRequestsQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<GetWithdrawalRequestsResponseDto> Handle(GetWithdrawalRequestsQuery request, CancellationToken cancellationToken)
{
var query = _context.UserCommissionPayouts
.AsNoTracking()
.Include(x => x.User)
.Where(x => x.WithdrawalMethod != null) // Only requests with withdrawal method
.AsQueryable();
// Filters
if (request.Status.HasValue)
{
query = query.Where(x => (int)x.Status == request.Status.Value);
}
if (request.UserId.HasValue)
{
query = query.Where(x => x.UserId == request.UserId.Value);
}
if (!string.IsNullOrEmpty(request.WeekNumber))
{
query = query.Where(x => x.WeekNumber == request.WeekNumber);
}
query = query.ApplyOrder(sortBy: request.SortBy ?? "-Created");
var meta = await query.GetMetaData(request.PaginationState, cancellationToken);
var models = await query
.PaginatedListAsync(paginationState: request.PaginationState)
.ToListAsync(cancellationToken);
var result = models.Select(x => new WithdrawalRequestModel
{
Id = x.Id,
UserId = x.UserId,
UserName = x.User != null ? (x.User.FirstName + " " + x.User.LastName).Trim() : x.User?.Mobile ?? "N/A",
WeekNumber = x.WeekNumber,
Amount = x.TotalAmount,
Status = (int)x.Status,
WithdrawalMethod = x.WithdrawalMethod.HasValue ? (int)x.WithdrawalMethod.Value : 0,
IbanNumber = x.IbanNumber,
RequestedAt = x.WithdrawnAt ?? x.Created,
ProcessedAt = x.LastModified,
ProcessedBy = null, // TODO: Add admin user tracking
Reason = null, // TODO: Add rejection reason field
Created = x.Created
}).ToList();
return new GetWithdrawalRequestsResponseDto
{
MetaData = meta,
Models = result
};
}
}
@@ -0,0 +1,24 @@
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWithdrawalRequests;
public class GetWithdrawalRequestsResponseDto
{
public MetaData? MetaData { get; set; }
public List<WithdrawalRequestModel> Models { get; set; } = new();
}
public class WithdrawalRequestModel
{
public long Id { get; set; }
public long UserId { get; set; }
public string UserName { get; set; } = string.Empty;
public string WeekNumber { get; set; } = string.Empty;
public long Amount { get; set; }
public int Status { get; set; } // CommissionPayoutStatus enum
public int? WithdrawalMethod { get; set; }
public string? IbanNumber { get; set; }
public DateTime? RequestedAt { get; set; }
public DateTime? ProcessedAt { get; set; }
public string? ProcessedBy { get; set; }
public string? Reason { get; set; }
public DateTime Created { get; set; }
}
@@ -0,0 +1,13 @@
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLogs;
public record GetWorkerExecutionLogsQuery : IRequest<GetWorkerExecutionLogsResponseDto>
{
public string? WeekNumber { get; init; }
public string? ExecutionId { get; init; }
public bool? SuccessOnly { get; init; }
public bool? FailedOnly { get; init; }
public string? SortBy { get; init; }
public PaginationState? PaginationState { get; init; }
}
@@ -0,0 +1,106 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLogs;
public class GetWorkerExecutionLogsQueryHandler : IRequestHandler<GetWorkerExecutionLogsQuery, GetWorkerExecutionLogsResponseDto>
{
private readonly IApplicationDbContext _context;
public GetWorkerExecutionLogsQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<GetWorkerExecutionLogsResponseDto> Handle(
GetWorkerExecutionLogsQuery request,
CancellationToken cancellationToken)
{
// TODO: این باید از یک entity واقعی لاگ‌ها را بگیرد
// فعلاً mock data برمی‌گرداند
await Task.CompletedTask;
var mockLogs = new List<WorkerExecutionLogModel>
{
new WorkerExecutionLogModel
{
ExecutionId = Guid.NewGuid().ToString(),
WeekNumber = "2025-W48",
Step = "Full",
Success = true,
ErrorMessage = null,
StartedAt = DateTime.UtcNow.AddHours(-24),
CompletedAt = DateTime.UtcNow.AddHours(-24).AddMinutes(15),
DurationMs = 900000, // 15 minutes
RecordsProcessed = 1523,
Details = "محاسبات کامل هفته 2025-W48 با موفقیت انجام شد"
},
new WorkerExecutionLogModel
{
ExecutionId = Guid.NewGuid().ToString(),
WeekNumber = "2025-W47",
Step = "Full",
Success = true,
ErrorMessage = null,
StartedAt = DateTime.UtcNow.AddDays(-7),
CompletedAt = DateTime.UtcNow.AddDays(-7).AddMinutes(12),
DurationMs = 720000,
RecordsProcessed = 1489,
Details = "محاسبات کامل هفته 2025-W47 با موفقیت انجام شد"
},
new WorkerExecutionLogModel
{
ExecutionId = Guid.NewGuid().ToString(),
WeekNumber = "2025-W46",
Step = "Pool",
Success = false,
ErrorMessage = "خطا در محاسبه استخر کمیسیون",
StartedAt = DateTime.UtcNow.AddDays(-14),
CompletedAt = DateTime.UtcNow.AddDays(-14).AddSeconds(30),
DurationMs = 30000,
RecordsProcessed = 0,
Details = "محاسبه استخر با خطا مواجه شد"
}
};
// Apply filters
if (!string.IsNullOrEmpty(request.WeekNumber))
{
mockLogs = mockLogs.Where(x => x.WeekNumber == request.WeekNumber).ToList();
}
if (request.SuccessOnly == true)
{
mockLogs = mockLogs.Where(x => x.Success).ToList();
}
if (request.FailedOnly == true)
{
mockLogs = mockLogs.Where(x => !x.Success).ToList();
}
var totalCount = mockLogs.Count;
var pageSize = request.PaginationState?.PageSize ?? 10;
var pageNumber = request.PaginationState?.PageNumber ?? 1;
var pagedLogs = mockLogs
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToList();
return new GetWorkerExecutionLogsResponseDto
{
MetaData = new MetaData
{
CurrentPage = pageNumber,
TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize),
PageSize = pageSize,
TotalCount = totalCount,
HasPrevious = pageNumber > 1,
HasNext = pageNumber < (int)Math.Ceiling(totalCount / (double)pageSize)
},
Models = pagedLogs
};
}
}
@@ -0,0 +1,23 @@
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerExecutionLogs;
public class GetWorkerExecutionLogsResponseDto
{
public MetaData? MetaData { get; set; }
public List<WorkerExecutionLogModel> Models { get; set; } = new();
}
public class WorkerExecutionLogModel
{
public string ExecutionId { get; set; } = string.Empty;
public string WeekNumber { get; set; } = string.Empty;
public string Step { get; set; } = string.Empty; // "Balances" | "Pool" | "Payouts" | "Full"
public bool Success { get; set; }
public string? ErrorMessage { get; set; }
public DateTime StartedAt { get; set; }
public DateTime? CompletedAt { get; set; }
public long DurationMs { get; set; }
public int RecordsProcessed { get; set; }
public string? Details { get; set; }
}
@@ -0,0 +1,6 @@
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus;
public record GetWorkerStatusQuery : IRequest<GetWorkerStatusResponseDto>
{
// Empty - returns current worker status
}
@@ -0,0 +1,37 @@
using CMSMicroservice.Application.Common.Interfaces;
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus;
public class GetWorkerStatusQueryHandler : IRequestHandler<GetWorkerStatusQuery, GetWorkerStatusResponseDto>
{
private readonly IApplicationDbContext _context;
public GetWorkerStatusQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<GetWorkerStatusResponseDto> Handle(
GetWorkerStatusQuery request,
CancellationToken cancellationToken)
{
// TODO: این باید از یک service یا cache واقعی worker status را بگیرد
// فعلاً mock data برمی‌گرداند
await Task.CompletedTask;
return new GetWorkerStatusResponseDto
{
IsRunning = false,
IsEnabled = true,
CurrentExecutionId = null,
CurrentWeekNumber = null,
CurrentStep = "Idle",
LastRunAt = DateTime.UtcNow.AddHours(-24),
NextScheduledRun = DateTime.UtcNow.AddDays(7),
TotalExecutions = 48,
SuccessfulExecutions = 47,
FailedExecutions = 1
};
}
}
@@ -0,0 +1,15 @@
namespace CMSMicroservice.Application.CommissionCQ.Queries.GetWorkerStatus;
public class GetWorkerStatusResponseDto
{
public bool IsRunning { get; set; }
public bool IsEnabled { get; set; }
public string? CurrentExecutionId { get; set; }
public string? CurrentWeekNumber { get; set; }
public string? CurrentStep { get; set; } // "Balances" | "Pool" | "Payouts" | "Idle"
public DateTime? LastRunAt { get; set; }
public DateTime? NextScheduledRun { get; set; }
public int TotalExecutions { get; set; }
public int SuccessfulExecutions { get; set; }
public int FailedExecutions { get; set; }
}