diff --git a/README.md b/README.md index 9ef67cc..9fd7663 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ # CMS Microservice Documentation moved to [totalDoc/INDEX.md](../totalDoc/INDEX.md). - \ No newline at end of file + \ No newline at end of file diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs index 0735b07..3d361e5 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/ActivateClubMembership/ActivateClubMembershipCommandHandler.cs @@ -163,10 +163,10 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler +{ + public PaginationState? PaginationState { get; init; } + public GetCustomerPackagePurchaseRollupFilter? Filter { get; init; } +} + +public class GetCustomerPackagePurchaseRollupFilter +{ + public long? UserId { get; set; } + public PackagePurchaseMethod? PurchaseMethod { get; set; } + public DateTime? PurchasedFrom { get; set; } + public DateTime? PurchasedTo { get; set; } +} + +public class GetCustomerPackagePurchaseRollupResponseDto +{ + public MetaData MetaData { get; set; } = new(); + public List Models { get; set; } = new(); +} + +public class CustomerPackagePurchaseRollupModel +{ + public long UserId { get; set; } + public string UserName { get; set; } = string.Empty; + public string UserMobile { get; set; } = string.Empty; + + public long FirstPackageId { get; set; } + public string FirstPackageName { get; set; } = string.Empty; + public long FirstAmount { get; set; } + public DateTime FirstPurchasedAt { get; set; } + + public long LastPackageId { get; set; } + public string LastPackageName { get; set; } = string.Empty; + public long LastAmount { get; set; } + public DateTime LastPurchasedAt { get; set; } + public PackagePurchaseMethod LastPurchaseMethod { get; set; } + + public int PurchaseCount { get; set; } + public long TotalAmount { get; set; } + + public int DayaCount { get; set; } + public long DayaAmount { get; set; } + public int ManualCount { get; set; } + public long ManualAmount { get; set; } + public int GatewayCount { get; set; } + public long GatewayAmount { get; set; } +} diff --git a/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetCustomerPackagePurchaseRollup/GetCustomerPackagePurchaseRollupQueryHandler.cs b/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetCustomerPackagePurchaseRollup/GetCustomerPackagePurchaseRollupQueryHandler.cs new file mode 100644 index 0000000..5fc0e91 --- /dev/null +++ b/src/CMSMicroservice.Application/UserPackagePurchaseCQ/Queries/GetCustomerPackagePurchaseRollup/GetCustomerPackagePurchaseRollupQueryHandler.cs @@ -0,0 +1,130 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetCustomerPackagePurchaseRollup; + +public class GetCustomerPackagePurchaseRollupQueryHandler + : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetCustomerPackagePurchaseRollupQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle( + GetCustomerPackagePurchaseRollupQuery request, + CancellationToken cancellationToken) + { + var filter = request.Filter; + var pagination = request.PaginationState ?? new PaginationState { PageNumber = 1, PageSize = 20 }; + if (pagination.PageNumber < 1) pagination.PageNumber = 1; + if (pagination.PageSize < 1) pagination.PageSize = 20; + + // خرید معتبر: بدون تراکنش یا تراکنش موفق + var purchases = _context.UserPackagePurchases + .AsNoTracking() + .Include(x => x.User) + .Include(x => x.Package) + .Include(x => x.Transaction) + .Where(x => x.TransactionId == null + || (x.Transaction != null && x.Transaction.PaymentStatus == PaymentStatus.Success)); + + if (filter?.UserId is > 0) + purchases = purchases.Where(x => x.UserId == filter.UserId.Value); + + if (filter?.PurchaseMethod != null) + purchases = purchases.Where(x => x.PurchaseMethod == filter.PurchaseMethod.Value); + + if (filter?.PurchasedFrom != null) + purchases = purchases.Where(x => x.PurchasedAt >= filter.PurchasedFrom.Value); + + if (filter?.PurchasedTo != null) + purchases = purchases.Where(x => x.PurchasedAt <= filter.PurchasedTo.Value); + + var rows = await purchases + .Select(x => new + { + x.UserId, + FirstName = x.User != null ? x.User.FirstName : null, + LastName = x.User != null ? x.User.LastName : null, + Mobile = x.User != null ? x.User.Mobile : null, + x.PackageId, + PackageName = x.Package != null ? x.Package.Title : null, + x.Amount, + x.PurchasedAt, + x.PurchaseMethod + }) + .ToListAsync(cancellationToken); + + var grouped = rows + .GroupBy(x => x.UserId) + .Select(g => + { + var ordered = g.OrderBy(x => x.PurchasedAt).ThenBy(x => x.PackageId).ToList(); + var first = ordered.First(); + var last = ordered.Last(); + var userName = string.Join(" ", + new[] { first.FirstName, first.LastName }.Where(s => !string.IsNullOrWhiteSpace(s))).Trim(); + + return new CustomerPackagePurchaseRollupModel + { + UserId = g.Key, + UserName = string.IsNullOrWhiteSpace(userName) + ? (!string.IsNullOrWhiteSpace(first.Mobile) ? first.Mobile! : $"کاربر {g.Key}") + : userName, + UserMobile = first.Mobile ?? string.Empty, + + FirstPackageId = first.PackageId, + FirstPackageName = first.PackageName ?? string.Empty, + FirstAmount = first.Amount, + FirstPurchasedAt = first.PurchasedAt, + + LastPackageId = last.PackageId, + LastPackageName = last.PackageName ?? string.Empty, + LastAmount = last.Amount, + LastPurchasedAt = last.PurchasedAt, + LastPurchaseMethod = last.PurchaseMethod, + + PurchaseCount = ordered.Count, + TotalAmount = ordered.Sum(x => x.Amount), + + DayaCount = ordered.Count(x => x.PurchaseMethod == PackagePurchaseMethod.DayaLoan), + DayaAmount = ordered.Where(x => x.PurchaseMethod == PackagePurchaseMethod.DayaLoan).Sum(x => x.Amount), + ManualCount = ordered.Count(x => x.PurchaseMethod == PackagePurchaseMethod.Manual), + ManualAmount = ordered.Where(x => x.PurchaseMethod == PackagePurchaseMethod.Manual).Sum(x => x.Amount), + GatewayCount = ordered.Count(x => x.PurchaseMethod == PackagePurchaseMethod.DirectPurchase), + GatewayAmount = ordered.Where(x => x.PurchaseMethod == PackagePurchaseMethod.DirectPurchase).Sum(x => x.Amount) + }; + }) + .OrderByDescending(x => x.LastPurchasedAt) + .ToList(); + + var totalCount = grouped.Count; + var pageSize = pagination.PageSize; + var pageNumber = pagination.PageNumber; + var totalPage = pageSize > 0 ? (int)Math.Ceiling(totalCount / (double)pageSize) : 0; + + var pageModels = grouped + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .ToList(); + + return new GetCustomerPackagePurchaseRollupResponseDto + { + MetaData = new MetaData + { + CurrentPage = pageNumber, + PageSize = pageSize, + TotalCount = totalCount, + TotalPage = totalPage, + HasPrevious = pageNumber > 1, + HasNext = pageNumber < totalPage + }, + Models = pageModels + }; + } +} diff --git a/src/CMSMicroservice.Domain/Enums/PackagePurchaseMethod.cs b/src/CMSMicroservice.Domain/Enums/PackagePurchaseMethod.cs index 8ff177f..748f3ac 100644 --- a/src/CMSMicroservice.Domain/Enums/PackagePurchaseMethod.cs +++ b/src/CMSMicroservice.Domain/Enums/PackagePurchaseMethod.cs @@ -18,5 +18,10 @@ public enum PackagePurchaseMethod /// /// از طریق پرداخت مستقیم درگاه بانکی /// - DirectPurchase = 2 + DirectPurchase = 2, + + /// + /// ثبت دستی توسط ادمین + /// + Manual = 3 } diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index 80ec01a..6d7c475 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -3,7 +3,7 @@ net9.0 enable enable - 0.0.207 + 0.0.208 None False False diff --git a/src/CMSMicroservice.Protobuf/Protos/userpackagepurchase.proto b/src/CMSMicroservice.Protobuf/Protos/userpackagepurchase.proto index 8a52aa4..ef35d9b 100644 --- a/src/CMSMicroservice.Protobuf/Protos/userpackagepurchase.proto +++ b/src/CMSMicroservice.Protobuf/Protos/userpackagepurchase.proto @@ -21,6 +21,11 @@ service UserPackagePurchaseContract get: "/UserPackagePurchase/GetSummary" }; }; + rpc GetCustomerPackagePurchaseRollup(GetCustomerPackagePurchaseRollupRequest) returns (GetCustomerPackagePurchaseRollupResponse){ + option (google.api.http) = { + get: "/UserPackagePurchase/GetCustomerRollup" + }; + }; } message GetAllUserPackagePurchaseByFilterRequest @@ -73,3 +78,51 @@ message GetUserPackagePurchaseSummaryResponse int64 total_count = 1; int64 total_amount = 2; } + +message GetCustomerPackagePurchaseRollupRequest +{ + messages.PaginationState pagination_state = 1; + GetCustomerPackagePurchaseRollupFilter filter = 2; +} + +message GetCustomerPackagePurchaseRollupFilter +{ + google.protobuf.Int64Value user_id = 1; + google.protobuf.Int32Value purchase_method = 2; + google.protobuf.Timestamp purchased_from = 3; + google.protobuf.Timestamp purchased_to = 4; +} + +message GetCustomerPackagePurchaseRollupResponse +{ + messages.MetaData meta_data = 1; + repeated CustomerPackagePurchaseRollupModel models = 2; +} + +message CustomerPackagePurchaseRollupModel +{ + int64 user_id = 1; + string user_name = 2; + string user_mobile = 3; + + int64 first_package_id = 4; + string first_package_name = 5; + int64 first_amount = 6; + google.protobuf.Timestamp first_purchased_at = 7; + + int64 last_package_id = 8; + string last_package_name = 9; + int64 last_amount = 10; + google.protobuf.Timestamp last_purchased_at = 11; + int32 last_purchase_method = 12; + + int32 purchase_count = 13; + int64 total_amount = 14; + + int32 daya_count = 15; + int64 daya_amount = 16; + int32 manual_count = 17; + int64 manual_amount = 18; + int32 gateway_count = 19; + int64 gateway_amount = 20; +} diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/UserPackagePurchaseProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/UserPackagePurchaseProfile.cs index ba02b15..a825884 100644 --- a/src/CMSMicroservice.WebApi/Common/Mappings/UserPackagePurchaseProfile.cs +++ b/src/CMSMicroservice.WebApi/Common/Mappings/UserPackagePurchaseProfile.cs @@ -3,6 +3,7 @@ using CMSMicroservice.Domain.Enums; using Mapster; using AppUp = CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetAllUserPackagePurchaseByFilter; using AppUpSummary = CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetUserPackagePurchaseSummary; +using AppRollup = CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetCustomerPackagePurchaseRollup; using ProtoUp = CMSMicroservice.Protobuf.Protos.UserPackagePurchase; namespace CMSMicroservice.WebApi.Common.Mappings; @@ -49,5 +50,41 @@ public class UserPackagePurchaseProfile : IRegister config.NewConfig() .Map(dest => dest.TotalCount, src => src.TotalCount) .Map(dest => dest.TotalAmount, src => src.TotalAmount); + + config.NewConfig() + .Map(dest => dest.PaginationState, src => src.PaginationState) + .Map(dest => dest.Filter, src => src.Filter); + + config.NewConfig() + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.PurchaseMethod, src => src.PurchaseMethod == null ? null : (PackagePurchaseMethod?)src.PurchaseMethod) + .Map(dest => dest.PurchasedFrom, src => src.PurchasedFrom != null ? src.PurchasedFrom.ToDateTime() : (DateTime?)null) + .Map(dest => dest.PurchasedTo, src => src.PurchasedTo != null ? src.PurchasedTo.ToDateTime() : (DateTime?)null); + + config.NewConfig() + .Map(dest => dest.MetaData, src => src.MetaData) + .Map(dest => dest.Models, src => src.Models); + + config.NewConfig() + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest.UserName, src => src.UserName) + .Map(dest => dest.UserMobile, src => src.UserMobile) + .Map(dest => dest.FirstPackageId, src => src.FirstPackageId) + .Map(dest => dest.FirstPackageName, src => src.FirstPackageName) + .Map(dest => dest.FirstAmount, src => src.FirstAmount) + .Map(dest => dest.FirstPurchasedAt, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.FirstPurchasedAt, DateTimeKind.Utc))) + .Map(dest => dest.LastPackageId, src => src.LastPackageId) + .Map(dest => dest.LastPackageName, src => src.LastPackageName) + .Map(dest => dest.LastAmount, src => src.LastAmount) + .Map(dest => dest.LastPurchasedAt, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.LastPurchasedAt, DateTimeKind.Utc))) + .Map(dest => dest.LastPurchaseMethod, src => (int)src.LastPurchaseMethod) + .Map(dest => dest.PurchaseCount, src => src.PurchaseCount) + .Map(dest => dest.TotalAmount, src => src.TotalAmount) + .Map(dest => dest.DayaCount, src => src.DayaCount) + .Map(dest => dest.DayaAmount, src => src.DayaAmount) + .Map(dest => dest.ManualCount, src => src.ManualCount) + .Map(dest => dest.ManualAmount, src => src.ManualAmount) + .Map(dest => dest.GatewayCount, src => src.GatewayCount) + .Map(dest => dest.GatewayAmount, src => src.GatewayAmount); } } diff --git a/src/CMSMicroservice.WebApi/Services/UserPackagePurchaseService.cs b/src/CMSMicroservice.WebApi/Services/UserPackagePurchaseService.cs index 239d7c8..2581b4c 100644 --- a/src/CMSMicroservice.WebApi/Services/UserPackagePurchaseService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserPackagePurchaseService.cs @@ -2,6 +2,7 @@ using CMSMicroservice.Protobuf.Protos.UserPackagePurchase; using CMSMicroservice.WebApi.Common.Services; using CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetAllUserPackagePurchaseByFilter; using CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetUserPackagePurchaseSummary; +using CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetCustomerPackagePurchaseRollup; namespace CMSMicroservice.WebApi.Services; @@ -18,4 +19,8 @@ public class UserPackagePurchaseService : UserPackagePurchaseContract.UserPackag public override Task GetUserPackagePurchaseSummary( GetUserPackagePurchaseSummaryRequest request, ServerCallContext context) => _dispatch.Handle(request, context); + + public override Task GetCustomerPackagePurchaseRollup( + GetCustomerPackagePurchaseRollupRequest request, ServerCallContext context) => + _dispatch.Handle(request, context); }