feat: Add ClubMembershipCQ - Phase 3 Application Layer
- Implemented 3 Commands with handlers and validators: * ActivateClubMembership: Create/reactivate membership * DeactivateClubMembership: Deactivate existing membership * AssignClubFeature: Assign features to club members - Implemented 3 Queries with handlers, validators, and DTOs: * GetClubMembership: Retrieve single membership by UserId * GetAllClubMemberships: List with filtering and pagination * GetClubMembershipHistory: Audit trail with full history - All operations include history tracking via ClubMembershipHistory - Property names aligned with Domain entity definitions - 21 new files, ~600 lines of code - Build successful with 0 errors
This commit is contained in:
+45
@@ -0,0 +1,45 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetAllClubMemberships;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای دریافت لیست عضویتهای باشگاه
|
||||
/// </summary>
|
||||
public record GetAllClubMembershipsQuery : IRequest<GetAllClubMembershipsResponseDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// موقعیت صفحهبندی
|
||||
/// </summary>
|
||||
public PaginationState? PaginationState { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// مرتبسازی بر اساس
|
||||
/// </summary>
|
||||
public string? SortBy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// فیلتر
|
||||
/// </summary>
|
||||
public GetAllClubMembershipsFilter? Filter { get; init; }
|
||||
}
|
||||
|
||||
public class GetAllClubMembershipsFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// فیلتر بر اساس شناسه کاربر
|
||||
/// </summary>
|
||||
public long? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// فقط عضویتهای فعال
|
||||
/// </summary>
|
||||
public bool? IsActive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// فیلتر بر اساس تاریخ فعالسازی (از)
|
||||
/// </summary>
|
||||
public DateTimeOffset? ActivationDateFrom { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// فیلتر بر اساس تاریخ فعالسازی (تا)
|
||||
/// </summary>
|
||||
public DateTimeOffset? ActivationDateTo { get; set; }
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetAllClubMemberships;
|
||||
|
||||
public class GetAllClubMembershipsQueryHandler : IRequestHandler<GetAllClubMembershipsQuery, GetAllClubMembershipsResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetAllClubMembershipsQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<GetAllClubMembershipsResponseDto> Handle(GetAllClubMembershipsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.ClubMemberships
|
||||
.ApplyOrder(sortBy: request.SortBy)
|
||||
.AsNoTracking()
|
||||
.AsQueryable();
|
||||
|
||||
if (request.Filter is not null)
|
||||
{
|
||||
query = query
|
||||
.Where(x => request.Filter.UserId == null || x.UserId == request.Filter.UserId)
|
||||
.Where(x => request.Filter.IsActive == null || x.IsActive == request.Filter.IsActive)
|
||||
.Where(x => request.Filter.ActivationDateFrom == null || x.ActivatedAt >= request.Filter.ActivationDateFrom)
|
||||
.Where(x => request.Filter.ActivationDateTo == null || x.ActivatedAt <= request.Filter.ActivationDateTo);
|
||||
}
|
||||
|
||||
var meta = await query.GetMetaData(request.PaginationState, cancellationToken);
|
||||
|
||||
var models = await query
|
||||
.PaginatedListAsync(paginationState: request.PaginationState)
|
||||
.Select(x => new GetAllClubMembershipsResponseModel
|
||||
{
|
||||
Id = x.Id,
|
||||
UserId = x.UserId,
|
||||
IsActive = x.IsActive,
|
||||
ActivatedAt = x.ActivatedAt,
|
||||
InitialContribution = x.InitialContribution,
|
||||
TotalEarned = x.TotalEarned,
|
||||
Created = x.Created,
|
||||
LastModified = x.LastModified
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return new GetAllClubMembershipsResponseDto
|
||||
{
|
||||
MetaData = meta,
|
||||
Models = models
|
||||
};
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetAllClubMemberships;
|
||||
|
||||
public class GetAllClubMembershipsQueryValidator : AbstractValidator<GetAllClubMembershipsQuery>
|
||||
{
|
||||
public GetAllClubMembershipsQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.Filter.UserId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر معتبر نیست")
|
||||
.When(x => x.Filter?.UserId != null);
|
||||
|
||||
RuleFor(x => x.Filter.ActivationDateTo)
|
||||
.GreaterThanOrEqualTo(x => x.Filter.ActivationDateFrom)
|
||||
.WithMessage("تاریخ پایان باید بعد از تاریخ شروع باشد")
|
||||
.When(x => x.Filter?.ActivationDateFrom != null && x.Filter?.ActivationDateTo != null);
|
||||
}
|
||||
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(
|
||||
ValidationContext<GetAllClubMembershipsQuery>.CreateWithOptions(
|
||||
(GetAllClubMembershipsQuery)model,
|
||||
x => x.IncludeProperties(propertyName)));
|
||||
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetAllClubMemberships;
|
||||
|
||||
public class GetAllClubMembershipsResponseDto
|
||||
{
|
||||
public MetaData MetaData { get; set; }
|
||||
public List<GetAllClubMembershipsResponseModel> Models { get; set; }
|
||||
}
|
||||
|
||||
public class GetAllClubMembershipsResponseModel
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public DateTime? ActivatedAt { get; set; }
|
||||
public long InitialContribution { get; set; }
|
||||
public long TotalEarned { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
public DateTimeOffset? LastModified { get; set; }
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembership;
|
||||
|
||||
/// <summary>
|
||||
/// DTO برای نمایش اطلاعات عضویت باشگاه
|
||||
/// </summary>
|
||||
public class ClubMembershipDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public DateTime? ActivatedAt { get; set; }
|
||||
public long InitialContribution { get; set; }
|
||||
public long TotalEarned { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
public DateTimeOffset? LastModified { get; set; }
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembership;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای دریافت عضویت باشگاه یک کاربر
|
||||
/// </summary>
|
||||
public record GetClubMembershipQuery : IRequest<ClubMembershipDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه کاربر
|
||||
/// </summary>
|
||||
public long UserId { get; init; }
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembership;
|
||||
|
||||
public class GetClubMembershipQueryHandler : IRequestHandler<GetClubMembershipQuery, ClubMembershipDto?>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetClubMembershipQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<ClubMembershipDto?> Handle(GetClubMembershipQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var membership = await _context.ClubMemberships
|
||||
.AsNoTracking()
|
||||
.Where(x => x.UserId == request.UserId)
|
||||
.Select(x => new ClubMembershipDto
|
||||
{
|
||||
Id = x.Id,
|
||||
UserId = x.UserId,
|
||||
IsActive = x.IsActive,
|
||||
ActivatedAt = x.ActivatedAt,
|
||||
InitialContribution = x.InitialContribution,
|
||||
TotalEarned = x.TotalEarned,
|
||||
Created = x.Created,
|
||||
LastModified = x.LastModified
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
return membership;
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembership;
|
||||
|
||||
public class GetClubMembershipQueryValidator : AbstractValidator<GetClubMembershipQuery>
|
||||
{
|
||||
public GetClubMembershipQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.UserId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر معتبر نیست");
|
||||
}
|
||||
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(
|
||||
ValidationContext<GetClubMembershipQuery>.CreateWithOptions(
|
||||
(GetClubMembershipQuery)model,
|
||||
x => x.IncludeProperties(propertyName)));
|
||||
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembershipHistory;
|
||||
|
||||
/// <summary>
|
||||
/// Query برای دریافت تاریخچه تغییرات عضویت باشگاه
|
||||
/// </summary>
|
||||
public record GetClubMembershipHistoryQuery : IRequest<GetClubMembershipHistoryResponseDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه عضویت (اختیاری)
|
||||
/// </summary>
|
||||
public long? MembershipId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه کاربر (اختیاری)
|
||||
/// </summary>
|
||||
public long? UserId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// موقعیت صفحهبندی
|
||||
/// </summary>
|
||||
public PaginationState? PaginationState { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// مرتبسازی بر اساس
|
||||
/// </summary>
|
||||
public string? SortBy { get; init; }
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembershipHistory;
|
||||
|
||||
public class GetClubMembershipHistoryQueryHandler : IRequestHandler<GetClubMembershipHistoryQuery, GetClubMembershipHistoryResponseDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public GetClubMembershipHistoryQueryHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<GetClubMembershipHistoryResponseDto> Handle(GetClubMembershipHistoryQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = _context.ClubMembershipHistories
|
||||
.AsNoTracking()
|
||||
.AsQueryable();
|
||||
|
||||
if (request.MembershipId.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.ClubMembershipId == request.MembershipId.Value);
|
||||
}
|
||||
|
||||
if (request.UserId.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.UserId == request.UserId.Value);
|
||||
}
|
||||
|
||||
query = query.ApplyOrder(sortBy: request.SortBy ?? "-Created"); // پیشفرض: جدیدترین اول
|
||||
|
||||
var meta = await query.GetMetaData(request.PaginationState, cancellationToken);
|
||||
|
||||
var models = await query
|
||||
.PaginatedListAsync(paginationState: request.PaginationState)
|
||||
.Select(x => new GetClubMembershipHistoryResponseModel
|
||||
{
|
||||
Id = x.Id,
|
||||
ClubMembershipId = x.ClubMembershipId,
|
||||
UserId = x.UserId,
|
||||
OldIsActive = x.OldIsActive,
|
||||
NewIsActive = x.NewIsActive,
|
||||
OldInitialContribution = x.OldInitialContribution,
|
||||
NewInitialContribution = x.NewInitialContribution,
|
||||
Action = x.Action,
|
||||
Reason = x.Reason,
|
||||
PerformedBy = x.PerformedBy,
|
||||
Created = x.Created
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return new GetClubMembershipHistoryResponseDto
|
||||
{
|
||||
MetaData = meta,
|
||||
Models = models
|
||||
};
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembershipHistory;
|
||||
|
||||
public class GetClubMembershipHistoryQueryValidator : AbstractValidator<GetClubMembershipHistoryQuery>
|
||||
{
|
||||
public GetClubMembershipHistoryQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.MembershipId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه عضویت معتبر نیست")
|
||||
.When(x => x.MembershipId.HasValue);
|
||||
|
||||
RuleFor(x => x.UserId)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر معتبر نیست")
|
||||
.When(x => x.UserId.HasValue);
|
||||
|
||||
RuleFor(x => x)
|
||||
.Must(x => x.MembershipId.HasValue || x.UserId.HasValue)
|
||||
.WithMessage("حداقل یکی از MembershipId یا UserId باید مقداردهی شود");
|
||||
}
|
||||
|
||||
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
|
||||
{
|
||||
var result = await ValidateAsync(
|
||||
ValidationContext<GetClubMembershipHistoryQuery>.CreateWithOptions(
|
||||
(GetClubMembershipHistoryQuery)model,
|
||||
x => x.IncludeProperties(propertyName)));
|
||||
|
||||
if (result.IsValid)
|
||||
return Array.Empty<string>();
|
||||
|
||||
return result.Errors.Select(e => e.ErrorMessage);
|
||||
};
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
namespace CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubMembershipHistory;
|
||||
|
||||
public class GetClubMembershipHistoryResponseDto
|
||||
{
|
||||
public MetaData MetaData { get; set; }
|
||||
public List<GetClubMembershipHistoryResponseModel> Models { get; set; }
|
||||
}
|
||||
|
||||
public class GetClubMembershipHistoryResponseModel
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long ClubMembershipId { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public bool OldIsActive { get; set; }
|
||||
public bool NewIsActive { get; set; }
|
||||
public long? OldInitialContribution { get; set; }
|
||||
public long? NewInitialContribution { get; set; }
|
||||
public ClubMembershipAction Action { get; set; }
|
||||
public string? Reason { get; set; }
|
||||
public string? PerformedBy { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user