diff --git a/src/CMSMicroservice.Application/WalletCQ/Queries/GetMagicWalletCharges/GetMagicWalletChargesQuery.cs b/src/CMSMicroservice.Application/WalletCQ/Queries/GetMagicWalletCharges/GetMagicWalletChargesQuery.cs new file mode 100644 index 0000000..a3c7967 --- /dev/null +++ b/src/CMSMicroservice.Application/WalletCQ/Queries/GetMagicWalletCharges/GetMagicWalletChargesQuery.cs @@ -0,0 +1,41 @@ +using CMSMicroservice.Application.Common.Models; +using MediatR; + +namespace CMSMicroservice.Application.WalletCQ.Queries.GetMagicWalletCharges; + +public class GetMagicWalletChargesQuery : IRequest +{ + public PaginationState? PaginationState { get; set; } + public string? SortBy { get; set; } + public GetMagicWalletChargesFilter? Filter { get; set; } +} + +public class GetMagicWalletChargesFilter +{ + public long? UserId { get; set; } + public long? TransactionId { get; set; } + public string? RefId { get; set; } + public DateTime? CreatedFrom { get; set; } + public DateTime? CreatedTo { get; set; } +} + +public class GetMagicWalletChargesResponseDto +{ + public MetaData MetaData { get; set; } = new(); + public List Models { get; set; } = new(); +} + +public class MagicWalletChargeModelDto +{ + public long TransactionId { get; set; } + public long UserId { get; set; } + public string UserName { get; set; } = string.Empty; + public long DepositAmount { get; set; } + public double Multiplier { get; set; } + public long CreditedAmount { get; set; } + public long? RemainingCapacity { get; set; } + public string? RefId { get; set; } + public long NewBalance { get; set; } + public DateTime Created { get; set; } + public DateTime? PaymentDate { get; set; } +} diff --git a/src/CMSMicroservice.Application/WalletCQ/Queries/GetMagicWalletCharges/GetMagicWalletChargesQueryHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Queries/GetMagicWalletCharges/GetMagicWalletChargesQueryHandler.cs new file mode 100644 index 0000000..20cddef --- /dev/null +++ b/src/CMSMicroservice.Application/WalletCQ/Queries/GetMagicWalletCharges/GetMagicWalletChargesQueryHandler.cs @@ -0,0 +1,171 @@ +using CMSMicroservice.Application.Common.Extensions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.WalletCQ.Queries.GetMagicWalletCharges; + +public class GetMagicWalletChargesQueryHandler + : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetMagicWalletChargesQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle( + GetMagicWalletChargesQuery request, + CancellationToken cancellationToken) + { + var filter = request.Filter; + + var query = + from t in _context.Transactions.AsNoTracking() + where t.Type == TransactionType.MagicWalletDeposit + join h in _context.UserWalletHistories.AsNoTracking() on t.Id equals h.RefrenceId + join w in _context.UserWallets.AsNoTracking() on h.WalletId equals w.Id + join u in _context.Users.AsNoTracking() on w.UserId equals u.Id + select new MagicWalletChargeModelDto + { + TransactionId = t.Id, + UserId = w.UserId, + UserName = ((u.FirstName ?? "") + " " + (u.LastName ?? "")).Trim(), + DepositAmount = t.Amount, + CreditedAmount = h.ChangeValue, + RefId = t.RefId, + NewBalance = h.CurrentBalance, + Created = t.Created, + PaymentDate = t.PaymentDate + }; + + if (filter?.UserId is long userId) + query = query.Where(x => x.UserId == userId); + + if (filter?.TransactionId is long txId) + query = query.Where(x => x.TransactionId == txId); + + if (!string.IsNullOrWhiteSpace(filter?.RefId)) + query = query.Where(x => x.RefId != null && x.RefId.Contains(filter.RefId)); + + if (filter?.CreatedFrom is DateTime from) + query = query.Where(x => x.Created >= from); + + if (filter?.CreatedTo is DateTime to) + query = query.Where(x => x.Created <= to); + + query = query.OrderByDescending(x => x.Created); + + var meta = await query.GetMetaData(request.PaginationState, cancellationToken); + var models = await query + .PaginatedListAsync(paginationState: request.PaginationState) + .ToListAsync(cancellationToken); + + foreach (var model in models) + { + model.Multiplier = model.DepositAmount == 0 + ? 0 + : (double)model.CreditedAmount / model.DepositAmount; + } + + await PopulateRemainingCapacityAsync(models, cancellationToken); + + return new GetMagicWalletChargesResponseDto + { + MetaData = meta, + Models = models + }; + } + + private async Task PopulateRemainingCapacityAsync( + List models, + CancellationToken cancellationToken) + { + if (models.Count == 0) + return; + + var userIds = models.Select(m => m.UserId).Distinct().ToList(); + + var wallets = await _context.UserWallets.AsNoTracking() + .Where(w => userIds.Contains(w.UserId)) + .Select(w => new { w.UserId, w.MagicActivatedAt }) + .ToListAsync(cancellationToken); + + var walletByUser = wallets.ToDictionary(w => w.UserId, w => w.MagicActivatedAt); + + var cycles = await _context.ClubMembershipCycles.AsNoTracking() + .Where(c => userIds.Contains(c.UserId) && c.IsCurrentCycle) + .Select(c => new { c.UserId, c.PackageId }) + .ToListAsync(cancellationToken); + + var packageIds = cycles.Select(c => c.PackageId).Distinct().ToList(); + var packages = await _context.Packages.AsNoTracking() + .Where(p => packageIds.Contains(p.Id) || (p.IsBasePackage && !p.IsDeleted)) + .Select(p => new { p.Id, p.IsBasePackage, p.IsDeleted, p.MagicWalletMaxDeposit }) + .ToListAsync(cancellationToken); + + var basePackageMax = packages + .Where(p => p.IsBasePackage && !p.IsDeleted) + .Select(p => (long?)p.MagicWalletMaxDeposit) + .FirstOrDefault(); + + var packageIdByUser = cycles.ToDictionary(c => c.UserId, c => c.PackageId); + var maxByPackageId = packages.ToDictionary(p => p.Id, p => p.MagicWalletMaxDeposit); + + // All magic deposits for these users (for cumulative sum within magic cycle) + var depositRows = await ( + from t in _context.Transactions.AsNoTracking() + where t.Type == TransactionType.MagicWalletDeposit + join h in _context.UserWalletHistories.AsNoTracking() on t.Id equals h.RefrenceId + join w in _context.UserWallets.AsNoTracking() on h.WalletId equals w.Id + where userIds.Contains(w.UserId) + select new { w.UserId, t.Id, t.Amount, t.Created } + ).ToListAsync(cancellationToken); + + var depositsByUser = depositRows + .GroupBy(d => d.UserId) + .ToDictionary(g => g.Key, g => g.OrderBy(x => x.Created).ThenBy(x => x.Id).ToList()); + + foreach (var model in models) + { + if (!walletByUser.TryGetValue(model.UserId, out var magicActivatedAt) || !magicActivatedAt.HasValue) + { + model.RemainingCapacity = null; + continue; + } + + long? maxDeposit = null; + if (packageIdByUser.TryGetValue(model.UserId, out var pkgId) && + maxByPackageId.TryGetValue(pkgId, out var pkgMax)) + { + maxDeposit = pkgMax; + } + else + { + maxDeposit = basePackageMax; + } + + if (maxDeposit is null) + { + model.RemainingCapacity = null; + continue; + } + + if (!depositsByUser.TryGetValue(model.UserId, out var userDeposits)) + { + model.RemainingCapacity = maxDeposit; + continue; + } + + var depositedThroughThis = userDeposits + .Where(d => d.Created >= magicActivatedAt.Value && + (d.Created < model.Created || + (d.Created == model.Created && d.Id <= model.TransactionId))) + .Sum(d => d.Amount); + + model.RemainingCapacity = Math.Max(0, maxDeposit.Value - depositedThroughThis); + } + } +} diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index d7341af..0de0808 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.214 + 0.0.215 None False False diff --git a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto index 9dd87a8..b439690 100644 --- a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto +++ b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto @@ -139,6 +139,14 @@ service UserWalletContract get: "/Admin/GetManualCreditCharges" }; }; + + // ============= Admin Magic Wallet Charges ============= + + rpc GetMagicWalletCharges(GetMagicWalletChargesRequest) returns (GetMagicWalletChargesResponse){ + option (google.api.http) = { + get: "/Admin/GetMagicWalletCharges" + }; + }; } message CreateNewUserWalletRequest { @@ -390,4 +398,41 @@ message ManualCreditChargeModel int64 new_balance = 7; google.protobuf.Timestamp created = 8; google.protobuf.Timestamp payment_date = 9; +} + +message GetMagicWalletChargesRequest +{ + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetMagicWalletChargesFilter filter = 3; +} + +message GetMagicWalletChargesFilter +{ + google.protobuf.Int64Value user_id = 1; + google.protobuf.Int64Value transaction_id = 2; + google.protobuf.StringValue ref_id = 3; + google.protobuf.Timestamp created_from = 4; + google.protobuf.Timestamp created_to = 5; +} + +message GetMagicWalletChargesResponse +{ + messages.MetaData meta_data = 1; + repeated MagicWalletChargeModel models = 2; +} + +message MagicWalletChargeModel +{ + int64 transaction_id = 1; + int64 user_id = 2; + string user_name = 3; + int64 deposit_amount = 4; + double multiplier = 5; + int64 credited_amount = 6; + google.protobuf.Int64Value remaining_capacity = 7; + string ref_id = 8; + int64 new_balance = 9; + google.protobuf.Timestamp created = 10; + google.protobuf.Timestamp payment_date = 11; } \ No newline at end of file diff --git a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs index 8c92e58..91fc6d7 100644 --- a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs @@ -12,6 +12,8 @@ using CMSMicroservice.Application.WalletCQ.Commands.VerifyCreditWalletCharge; using CMSMicroservice.Application.WalletCQ.Commands.AdminManualCreditCharge; using CMSMicroservice.Application.WalletCQ.Queries.GetManualCreditCharges; using AppManualChargeFilter = CMSMicroservice.Application.WalletCQ.Queries.GetManualCreditCharges.GetManualCreditChargesFilter; +using CMSMicroservice.Application.WalletCQ.Queries.GetMagicWalletCharges; +using AppMagicChargeFilter = CMSMicroservice.Application.WalletCQ.Queries.GetMagicWalletCharges.GetMagicWalletChargesFilter; using CMSMicroservice.Application.UserWalletCQ.Queries.GetUserWallet; using CMSMicroservice.Application.UserWalletCQ.Queries.GetAllUserWalletByFilter; using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory; @@ -653,4 +655,70 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase return response; } + + public override async Task GetMagicWalletCharges( + GetMagicWalletChargesRequest request, ServerCallContext context) + { + var result = await _sender.Send(new GetMagicWalletChargesQuery + { + SortBy = request.SortBy, + PaginationState = request.PaginationState != null + ? new Application.Common.Models.PaginationState + { + PageNumber = request.PaginationState.PageNumber, + PageSize = request.PaginationState.PageSize + } + : new Application.Common.Models.PaginationState { PageNumber = 1, PageSize = 20 }, + Filter = new AppMagicChargeFilter + { + UserId = request.Filter?.UserId, + TransactionId = request.Filter?.TransactionId, + RefId = request.Filter?.RefId, + CreatedFrom = request.Filter?.CreatedFrom?.ToDateTime(), + CreatedTo = request.Filter?.CreatedTo?.ToDateTime() + } + }, context.CancellationToken); + + var response = new GetMagicWalletChargesResponse + { + MetaData = new CMSMicroservice.Protobuf.Protos.MetaData + { + CurrentPage = result.MetaData.CurrentPage, + TotalPage = result.MetaData.TotalPage, + PageSize = result.MetaData.PageSize, + TotalCount = result.MetaData.TotalCount, + HasPrevious = result.MetaData.HasPrevious, + HasNext = result.MetaData.HasNext + } + }; + + foreach (var m in result.Models) + { + var model = new MagicWalletChargeModel + { + TransactionId = m.TransactionId, + UserId = m.UserId, + UserName = m.UserName ?? string.Empty, + DepositAmount = m.DepositAmount, + Multiplier = m.Multiplier, + CreditedAmount = m.CreditedAmount, + RefId = m.RefId ?? string.Empty, + NewBalance = m.NewBalance, + Created = Timestamp.FromDateTime(DateTime.SpecifyKind(m.Created, DateTimeKind.Utc)) + }; + + if (m.RemainingCapacity.HasValue) + model.RemainingCapacity = m.RemainingCapacity.Value; + + if (m.PaymentDate.HasValue) + { + model.PaymentDate = Timestamp.FromDateTime( + DateTime.SpecifyKind(m.PaymentDate.Value, DateTimeKind.Utc)); + } + + response.Models.Add(model); + } + + return response; + } }