diff --git a/src/Fengling.Member.Application/Commands/Points/ProcessExpiredPointsCommand.cs b/src/Fengling.Member.Application/Commands/Points/ProcessExpiredPointsCommand.cs new file mode 100644 index 0000000..621728c --- /dev/null +++ b/src/Fengling.Member.Application/Commands/Points/ProcessExpiredPointsCommand.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace Fengling.Member.Application.Commands.Points; + +public record ProcessExpiredPointsCommand(int BatchSize = 1000) : IRequest; diff --git a/src/Fengling.Member.Application/Commands/Points/ProcessExpiredPointsCommandHandler.cs b/src/Fengling.Member.Application/Commands/Points/ProcessExpiredPointsCommandHandler.cs new file mode 100644 index 0000000..66d6ad7 --- /dev/null +++ b/src/Fengling.Member.Application/Commands/Points/ProcessExpiredPointsCommandHandler.cs @@ -0,0 +1,49 @@ +using MediatR; +using Fengling.Member.Domain.Aggregates.PointsModel; +using Fengling.Member.Infrastructure; +using Microsoft.EntityFrameworkCore; + +namespace Fengling.Member.Application.Commands.Points; + +public class ProcessExpiredPointsCommandHandler : IRequestHandler +{ + private readonly ApplicationDbContext _context; + + public ProcessExpiredPointsCommandHandler(ApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(ProcessExpiredPointsCommand request, CancellationToken cancellationToken) + { + var now = DateTime.UtcNow; + + var expiredTransactions = await _context.PointsTransactions + .Where(x => x.ExpireAt <= now + && x.ExpireAt > DateTime.MinValue + && x.Points > 0) + .OrderBy(x => x.ExpireAt) + .Take(request.BatchSize) + .ToListAsync(cancellationToken); + + foreach (var transaction in expiredTransactions) + { + var deduction = PointsTransaction.Create( + transaction.PointsAccountId, + transaction.MemberId, + -transaction.Points, + "Expired", + $"来源交易: {transaction.Id}", + PointsTransactionType.Expired, + $"积分过期扣除 - 来源交易: {transaction.Id}"); + + deduction.SetExpireAt(transaction.ExpireAt); + + _context.PointsTransactions.Add(deduction); + } + + var affected = await _context.SaveChangesAsync(cancellationToken); + + return affected / 2; + } +} diff --git a/src/Fengling.Member.Domain/Aggregates/PointsModel/PointsTransaction.cs b/src/Fengling.Member.Domain/Aggregates/PointsModel/PointsTransaction.cs index 31bf6e2..45273b7 100644 --- a/src/Fengling.Member.Domain/Aggregates/PointsModel/PointsTransaction.cs +++ b/src/Fengling.Member.Domain/Aggregates/PointsModel/PointsTransaction.cs @@ -10,11 +10,17 @@ public class PointsTransaction : Entity public PointsTransactionType TransactionTypeCategory { get; private set; } public string? Remark { get; private set; } public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; + public DateTime ExpireAt { get; private set; } = DateTime.MinValue; private PointsTransaction() { } + public void SetExpireAt(DateTime expireAt) + { + ExpireAt = expireAt; + } + public static PointsTransaction Create( long pointsAccountId, long memberId, @@ -41,5 +47,6 @@ public class PointsTransaction : Entity public enum PointsTransactionType { Earn = 1, - Deduct = 2 + Deduct = 2, + Expired = 3 } diff --git a/src/Fengling.Member.Infrastructure/ApplicationDbContext.cs b/src/Fengling.Member.Infrastructure/ApplicationDbContext.cs index bcc8be2..617c1d1 100644 --- a/src/Fengling.Member.Infrastructure/ApplicationDbContext.cs +++ b/src/Fengling.Member.Infrastructure/ApplicationDbContext.cs @@ -14,6 +14,7 @@ public partial class ApplicationDbContext(DbContextOptions public DbSet PointsAccounts => Set(); public DbSet PointsRules => Set(); public DbSet PointsRuleConditions => Set(); + public DbSet PointsTransactions => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) {