diff --git a/src/CMSMicroservice.Application/Common/Extensions/SortByExtensions.cs b/src/CMSMicroservice.Application/Common/Extensions/SortByExtensions.cs
index 439e9a5..ebb6a06 100644
--- a/src/CMSMicroservice.Application/Common/Extensions/SortByExtensions.cs
+++ b/src/CMSMicroservice.Application/Common/Extensions/SortByExtensions.cs
@@ -1,3 +1,4 @@
+using System.Linq.Expressions;
using System.Linq.Dynamic.Core;
using CMSMicroservice.Domain.Common;
@@ -16,6 +17,21 @@ public static class SortByExtensions
return source.OrderBy(NormalizeSortBy(sortBy));
}
+ ///
+ /// موجودیدارها اول، سپس سورت درخواستی کاربر (یا Created desc اگر SortBy خالی باشد).
+ ///
+ public static IQueryable ApplyOrderWithInStockPriority(
+ this IQueryable source,
+ string? sortBy,
+ Expression> isInStock) where TSource : BaseAuditableEntity
+ {
+ var ordered = source.OrderByDescending(isInStock);
+ if (string.IsNullOrWhiteSpace(sortBy))
+ return ordered.ThenByDescending(p => p.Created);
+
+ return ordered.ThenBy(NormalizeSortBy(sortBy));
+ }
+
///
/// Dynamic LINQ treats "-Created" as unary minus on DateTime (invalid).
/// Project convention: leading '-' means descending sort.
diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryHandler.cs
index f7e5c9b..5edacaf 100644
--- a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryHandler.cs
+++ b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetDiscountProducts/GetDiscountProductsQueryHandler.cs
@@ -57,10 +57,8 @@ public class GetDiscountProductsQueryHandler : IRequestHandler p.Price <= request.MaxPrice.Value);
}
- if (!string.IsNullOrEmpty(request.SortBy))
- query = query.ApplyOrder(request.SortBy);
- else
- query = query.OrderByDescending(p => p.Created);
+ // موجودیدارها اول، سپس سورت درخواستی
+ query = query.ApplyOrderWithInStockPriority(request.SortBy, p => p.RemainingCount > 0);
var totalCount = await query.CountAsync(cancellationToken);
diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQueryHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQueryHandler.cs
index 669c9d4..e468628 100644
--- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQueryHandler.cs
+++ b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQueryHandler.cs
@@ -67,11 +67,8 @@ public class GetCustomerProductsByFilterQueryHandler : IRequestHandler x.IsDeleted != request.IsActive.Value);
- // Apply sorting
- if (!string.IsNullOrEmpty(request.SortBy))
- query = query.ApplyOrder(request.SortBy);
- else
- query = query.OrderByDescending(x => x.Created);
+ // موجودیدارها اول، سپس سورت درخواستی
+ query = query.ApplyOrderWithInStockPriority(request.SortBy, x => x.RemainingCount > 0);
// Pagination
var totalCount = await query.CountAsync(cancellationToken);
diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/AdminManualCreditCharge/AdminManualCreditChargeCommand.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/AdminManualCreditCharge/AdminManualCreditChargeCommand.cs
new file mode 100644
index 0000000..3baa57a
--- /dev/null
+++ b/src/CMSMicroservice.Application/WalletCQ/Commands/AdminManualCreditCharge/AdminManualCreditChargeCommand.cs
@@ -0,0 +1,19 @@
+using MediatR;
+
+namespace CMSMicroservice.Application.WalletCQ.Commands.AdminManualCreditCharge;
+
+public class AdminManualCreditChargeCommand : IRequest
+{
+ public long UserId { get; set; }
+ public long Amount { get; set; }
+ public string? Note { get; set; }
+ public string? PerformedByAdminId { get; set; }
+}
+
+public class AdminManualCreditChargeResult
+{
+ public bool Success { get; set; }
+ public string Message { get; set; } = string.Empty;
+ public long TransactionId { get; set; }
+ public long NewBalance { get; set; }
+}
diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/AdminManualCreditCharge/AdminManualCreditChargeCommandHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/AdminManualCreditCharge/AdminManualCreditChargeCommandHandler.cs
new file mode 100644
index 0000000..b9e5039
--- /dev/null
+++ b/src/CMSMicroservice.Application/WalletCQ/Commands/AdminManualCreditCharge/AdminManualCreditChargeCommandHandler.cs
@@ -0,0 +1,105 @@
+using CMSMicroservice.Application.Common;
+using CMSMicroservice.Application.Common.Exceptions;
+using CMSMicroservice.Application.Common.Interfaces;
+using CMSMicroservice.Domain.Entities;
+using CMSMicroservice.Domain.Enums;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+
+namespace CMSMicroservice.Application.WalletCQ.Commands.AdminManualCreditCharge;
+
+public class AdminManualCreditChargeCommandHandler
+ : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+ private readonly ILogger _logger;
+ private readonly IUserPaymentLock _paymentLock;
+
+ public AdminManualCreditChargeCommandHandler(
+ IApplicationDbContext context,
+ ILogger logger,
+ IUserPaymentLock paymentLock)
+ {
+ _context = context;
+ _logger = logger;
+ _paymentLock = paymentLock;
+ }
+
+ public Task Handle(
+ AdminManualCreditChargeCommand request,
+ CancellationToken cancellationToken) =>
+ _paymentLock.ExecuteAsync(
+ PaymentLockScopes.Initiate(request.UserId),
+ PaymentLockStrategy.FailFast,
+ ct => HandleCore(request, ct),
+ cancellationToken);
+
+ private async Task HandleCore(
+ AdminManualCreditChargeCommand request,
+ CancellationToken cancellationToken)
+ {
+ var user = await _context.Users
+ .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken)
+ ?? throw new NotFoundException(nameof(User), request.UserId);
+
+ var wallet = await _context.UserWallets
+ .FirstOrDefaultAsync(w => w.UserId == user.Id, cancellationToken)
+ ?? throw new NotFoundException($"کیف پول کاربر با شناسه {request.UserId} یافت نشد");
+
+ var oldBalance = wallet.Balance;
+ wallet.Balance += request.Amount;
+
+ var refId = $"MANUAL-{Guid.NewGuid():N}";
+ var notePart = string.IsNullOrWhiteSpace(request.Note)
+ ? string.Empty
+ : $" — {request.Note.Trim()}";
+ var adminPart = string.IsNullOrWhiteSpace(request.PerformedByAdminId)
+ ? string.Empty
+ : $" (ادمین: {request.PerformedByAdminId})";
+
+ var transaction = new Transaction
+ {
+ Amount = request.Amount,
+ Description = $"شارژ دستی کیف پول اصلی - کاربر {user.Id}{notePart}{adminPart}",
+ PaymentStatus = PaymentStatus.Success,
+ PaymentDate = DateTime.Now,
+ RefId = refId,
+ Type = TransactionType.ManualCreditWalletCharge
+ };
+
+ _context.Transactions.Add(transaction);
+ await _context.SaveChangesAsync(cancellationToken);
+
+ _context.UserWalletHistories.Add(new UserWalletHistory
+ {
+ WalletId = wallet.Id,
+ CurrentBalance = wallet.Balance,
+ ChangeValue = request.Amount,
+ CurrentNetworkBalance = wallet.NetworkBalance,
+ ChangeNerworkValue = 0,
+ CurrentDiscountBalance = wallet.DiscountBalance,
+ ChangeDiscountValue = 0,
+ IsIncrease = true,
+ RefrenceId = transaction.Id
+ });
+ await _context.SaveChangesAsync(cancellationToken);
+
+ _logger.LogInformation(
+ "Manual credit charge: UserId={UserId}, Amount={Amount}, OldBalance={Old}, NewBalance={New}, TxId={TxId}, Admin={Admin}",
+ user.Id,
+ request.Amount,
+ oldBalance,
+ wallet.Balance,
+ transaction.Id,
+ request.PerformedByAdminId);
+
+ return new AdminManualCreditChargeResult
+ {
+ Success = true,
+ Message = "شارژ دستی کیف پول اصلی با موفقیت انجام شد",
+ TransactionId = transaction.Id,
+ NewBalance = wallet.Balance
+ };
+ }
+}
diff --git a/src/CMSMicroservice.Application/WalletCQ/Commands/AdminManualCreditCharge/AdminManualCreditChargeCommandValidator.cs b/src/CMSMicroservice.Application/WalletCQ/Commands/AdminManualCreditCharge/AdminManualCreditChargeCommandValidator.cs
new file mode 100644
index 0000000..9b2c9f1
--- /dev/null
+++ b/src/CMSMicroservice.Application/WalletCQ/Commands/AdminManualCreditCharge/AdminManualCreditChargeCommandValidator.cs
@@ -0,0 +1,20 @@
+using CMSMicroservice.Domain.Common;
+using FluentValidation;
+
+namespace CMSMicroservice.Application.WalletCQ.Commands.AdminManualCreditCharge;
+
+public class AdminManualCreditChargeCommandValidator : AbstractValidator
+{
+ public AdminManualCreditChargeCommandValidator()
+ {
+ RuleFor(x => x.UserId)
+ .GreaterThan(0)
+ .WithMessage("شناسه کاربر باید بزرگتر از صفر باشد");
+
+ RuleFor(x => x.Amount)
+ .GreaterThanOrEqualTo(SystemConstants.DiscountWalletMinCharge)
+ .WithMessage("حداقل مبلغ شارژ ۱۰,۰۰۰ تومان است")
+ .LessThanOrEqualTo(SystemConstants.WalletMaxSafeAmount)
+ .WithMessage("مبلغ وارد شده بیش از حد مجاز است");
+ }
+}
diff --git a/src/CMSMicroservice.Application/WalletCQ/Queries/GetManualCreditCharges/GetManualCreditChargesQuery.cs b/src/CMSMicroservice.Application/WalletCQ/Queries/GetManualCreditCharges/GetManualCreditChargesQuery.cs
new file mode 100644
index 0000000..ad30dfe
--- /dev/null
+++ b/src/CMSMicroservice.Application/WalletCQ/Queries/GetManualCreditCharges/GetManualCreditChargesQuery.cs
@@ -0,0 +1,39 @@
+using CMSMicroservice.Application.Common.Models;
+using MediatR;
+
+namespace CMSMicroservice.Application.WalletCQ.Queries.GetManualCreditCharges;
+
+public class GetManualCreditChargesQuery : IRequest
+{
+ public PaginationState? PaginationState { get; set; }
+ public string? SortBy { get; set; }
+ public GetManualCreditChargesFilter? Filter { get; set; }
+}
+
+public class GetManualCreditChargesFilter
+{
+ 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 GetManualCreditChargesResponseDto
+{
+ public MetaData MetaData { get; set; } = new();
+ public List Models { get; set; } = new();
+}
+
+public class ManualCreditChargeModelDto
+{
+ public long TransactionId { get; set; }
+ public long UserId { get; set; }
+ public string UserName { get; set; } = string.Empty;
+ public long Amount { get; set; }
+ public string Description { get; set; } = string.Empty;
+ 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/GetManualCreditCharges/GetManualCreditChargesQueryHandler.cs b/src/CMSMicroservice.Application/WalletCQ/Queries/GetManualCreditCharges/GetManualCreditChargesQueryHandler.cs
new file mode 100644
index 0000000..1478eda
--- /dev/null
+++ b/src/CMSMicroservice.Application/WalletCQ/Queries/GetManualCreditCharges/GetManualCreditChargesQueryHandler.cs
@@ -0,0 +1,69 @@
+using CMSMicroservice.Application.Common.Extensions;
+using CMSMicroservice.Application.Common.Interfaces;
+using CMSMicroservice.Domain.Enums;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace CMSMicroservice.Application.WalletCQ.Queries.GetManualCreditCharges;
+
+public class GetManualCreditChargesQueryHandler
+ : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+
+ public GetManualCreditChargesQueryHandler(IApplicationDbContext context)
+ {
+ _context = context;
+ }
+
+ public async Task Handle(
+ GetManualCreditChargesQuery request,
+ CancellationToken cancellationToken)
+ {
+ var filter = request.Filter;
+
+ var query =
+ from t in _context.Transactions.AsNoTracking()
+ where t.Type == TransactionType.ManualCreditWalletCharge
+ 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 ManualCreditChargeModelDto
+ {
+ TransactionId = t.Id,
+ UserId = w.UserId,
+ UserName = ((u.FirstName ?? "") + " " + (u.LastName ?? "")).Trim(),
+ Amount = t.Amount,
+ Description = t.Description ?? string.Empty,
+ 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);
+
+ return new GetManualCreditChargesResponseDto
+ {
+ MetaData = await query.GetMetaData(request.PaginationState, cancellationToken),
+ Models = await query
+ .PaginatedListAsync(paginationState: request.PaginationState)
+ .ToListAsync(cancellationToken)
+ };
+ }
+}
diff --git a/src/CMSMicroservice.Domain/Enums/TransactionType.cs b/src/CMSMicroservice.Domain/Enums/TransactionType.cs
index 7b32d15..51564ad 100644
--- a/src/CMSMicroservice.Domain/Enums/TransactionType.cs
+++ b/src/CMSMicroservice.Domain/Enums/TransactionType.cs
@@ -41,4 +41,9 @@ public enum TransactionType
/// شارژ کیف پول اصلی از درگاه
///
CreditWalletCharge = 16,
+
+ ///
+ /// شارژ دستی کیف پول اصلی توسط ادمین
+ ///
+ ManualCreditWalletCharge = 17,
}
diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj
index cc8bf9d..d205872 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.204
+ 0.0.205
None
False
False
diff --git a/src/CMSMicroservice.Protobuf/Protos/public_messages.proto b/src/CMSMicroservice.Protobuf/Protos/public_messages.proto
index a3c55c6..99fd260 100644
--- a/src/CMSMicroservice.Protobuf/Protos/public_messages.proto
+++ b/src/CMSMicroservice.Protobuf/Protos/public_messages.proto
@@ -116,6 +116,7 @@ enum TransactionType
MagicWalletDeposit = 14;
MagicWalletBonus = 15;
CreditWalletCharge = 16;
+ ManualCreditWalletCharge = 17;
}
enum ContractType
{
diff --git a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto
index ab9a1a8..9dd87a8 100644
--- a/src/CMSMicroservice.Protobuf/Protos/userwallet.proto
+++ b/src/CMSMicroservice.Protobuf/Protos/userwallet.proto
@@ -125,6 +125,20 @@ service UserWalletContract
body: "*"
};
};
+
+ // ============= Admin Manual Credit Charge =============
+
+ rpc AdminManualCreditCharge(AdminManualCreditChargeRequest) returns (AdminManualCreditChargeResponse){
+ option (google.api.http) = {
+ post: "/Admin/ManualCreditCharge"
+ body: "*"
+ };
+ };
+ rpc GetManualCreditCharges(GetManualCreditChargesRequest) returns (GetManualCreditChargesResponse){
+ option (google.api.http) = {
+ get: "/Admin/GetManualCreditCharges"
+ };
+ };
}
message CreateNewUserWalletRequest
{
@@ -324,4 +338,56 @@ message VerifyWalletChargeResponse
{
bool success = 1;
string message = 2;
+}
+
+// ============= Admin Manual Credit Charge Messages =============
+
+message AdminManualCreditChargeRequest
+{
+ int64 user_id = 1;
+ int64 amount = 2; // تومان
+ google.protobuf.StringValue note = 3;
+}
+
+message AdminManualCreditChargeResponse
+{
+ bool success = 1;
+ string message = 2;
+ int64 transaction_id = 3;
+ int64 new_balance = 4;
+}
+
+message GetManualCreditChargesRequest
+{
+ messages.PaginationState pagination_state = 1;
+ google.protobuf.StringValue sort_by = 2;
+ GetManualCreditChargesFilter filter = 3;
+}
+
+message GetManualCreditChargesFilter
+{
+ 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 GetManualCreditChargesResponse
+{
+ messages.MetaData meta_data = 1;
+ repeated ManualCreditChargeModel models = 2;
+}
+
+message ManualCreditChargeModel
+{
+ int64 transaction_id = 1;
+ int64 user_id = 2;
+ string user_name = 3;
+ int64 amount = 4;
+ string description = 5;
+ string ref_id = 6;
+ int64 new_balance = 7;
+ google.protobuf.Timestamp created = 8;
+ google.protobuf.Timestamp payment_date = 9;
}
\ No newline at end of file
diff --git a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs
index 72338f0..e8b8ff4 100644
--- a/src/CMSMicroservice.WebApi/Services/UserWalletService.cs
+++ b/src/CMSMicroservice.WebApi/Services/UserWalletService.cs
@@ -9,6 +9,9 @@ using CMSMicroservice.Application.WalletCQ.Commands.ChargeCreditWallet;
using CMSMicroservice.Application.WalletCQ.Commands.VerifyMagicWalletCharge;
using CMSMicroservice.Application.WalletCQ.Commands.VerifyDiscountWalletCharge;
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.UserWalletCQ.Queries.GetUserWallet;
using CMSMicroservice.Application.UserWalletCQ.Queries.GetAllUserWalletByFilter;
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory;
@@ -518,4 +521,116 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
MinWithdrawalAmount = settings.MinWithdrawalAmount
};
}
+
+ // ============= Admin Manual Credit Charge =============
+
+ public override async Task AdminManualCreditCharge(
+ AdminManualCreditChargeRequest request, ServerCallContext context)
+ {
+ try
+ {
+ var result = await _sender.Send(new AdminManualCreditChargeCommand
+ {
+ UserId = request.UserId,
+ Amount = request.Amount,
+ Note = request.Note,
+ PerformedByAdminId = _currentUserService.GetPerformedBy()
+ }, context.CancellationToken);
+
+ return new AdminManualCreditChargeResponse
+ {
+ Success = result.Success,
+ Message = result.Message ?? string.Empty,
+ TransactionId = result.TransactionId,
+ NewBalance = result.NewBalance
+ };
+ }
+ catch (PaymentInProgressException ex)
+ {
+ return new AdminManualCreditChargeResponse
+ {
+ Success = false,
+ Message = ex.Message
+ };
+ }
+ catch (NotFoundException ex)
+ {
+ return new AdminManualCreditChargeResponse
+ {
+ Success = false,
+ Message = ex.Message
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "AdminManualCreditCharge failed for UserId={UserId}", request.UserId);
+ return new AdminManualCreditChargeResponse
+ {
+ Success = false,
+ Message = ex.Message
+ };
+ }
+ }
+
+ public override async Task GetManualCreditCharges(
+ GetManualCreditChargesRequest request, ServerCallContext context)
+ {
+ var result = await _sender.Send(new GetManualCreditChargesQuery
+ {
+ 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 AppManualChargeFilter
+ {
+ 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 GetManualCreditChargesResponse
+ {
+ 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 ManualCreditChargeModel
+ {
+ TransactionId = m.TransactionId,
+ UserId = m.UserId,
+ UserName = m.UserName ?? string.Empty,
+ Amount = m.Amount,
+ Description = m.Description ?? string.Empty,
+ RefId = m.RefId ?? string.Empty,
+ NewBalance = m.NewBalance,
+ Created = Timestamp.FromDateTime(DateTime.SpecifyKind(m.Created, DateTimeKind.Utc))
+ };
+
+ if (m.PaymentDate.HasValue)
+ {
+ model.PaymentDate = Timestamp.FromDateTime(
+ DateTime.SpecifyKind(m.PaymentDate.Value, DateTimeKind.Utc));
+ }
+
+ response.Models.Add(model);
+ }
+
+ return response;
+ }
}