using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Enums; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace CMSMicroservice.Infrastructure.BackgroundServices; /// /// سرویس پس‌زمینه برای منقضی کردن سفارشات تخفیفی که بیش از ۳۰ دقیقه در وضعیت Pending مانده‌اند. /// موجودی رزرو شده آزاد و وضعیت پرداخت به Reject تغییر می‌کند. /// public class ExpirePendingOrdersService : BackgroundService { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; /// /// مدت زمان انقضا (۳۰ دقیقه) /// private static readonly TimeSpan ExpirationTime = TimeSpan.FromMinutes(30); /// /// هر ۵ دقیقه چک می‌شود /// private static readonly TimeSpan CheckInterval = TimeSpan.FromMinutes(5); public ExpirePendingOrdersService( IServiceScopeFactory scopeFactory, ILogger logger) { _scopeFactory = scopeFactory; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("ExpirePendingOrdersService started — checking every {Interval} min, expiring after {Expiry} min", CheckInterval.TotalMinutes, ExpirationTime.TotalMinutes); while (!stoppingToken.IsCancellationRequested) { try { await ExpireOldPendingOrders(stoppingToken); } catch (Exception ex) { _logger.LogError(ex, "Error in ExpirePendingOrdersService"); } await Task.Delay(CheckInterval, stoppingToken); } } private async Task ExpireOldPendingOrders(CancellationToken cancellationToken) { using var scope = _scopeFactory.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var inventoryService = scope.ServiceProvider.GetRequiredService(); var cutoff = DateTime.UtcNow - ExpirationTime; // پیدا کردن سفارشات Pending قدیمی var expiredOrders = await context.DiscountOrders .Include(o => o.OrderDetails) .Where(o => o.PaymentStatus == PaymentStatus.Pending && o.Created < cutoff) .ToListAsync(cancellationToken); if (!expiredOrders.Any()) return; _logger.LogInformation("Found {Count} expired pending orders to clean up", expiredOrders.Count); foreach (var order in expiredOrders) { try { // آزادسازی رزرو موجودی foreach (var detail in order.OrderDetails) { await inventoryService.ReleaseReservationAsync( detail.ProductId, ProductType.DiscountProduct, detail.Count, order.Id, cancellationToken); } // تغییر وضعیت به Reject order.PaymentStatus = PaymentStatus.Reject; // آپدیت تراکنش مربوطه if (order.TransactionId.HasValue) { var transaction = await context.Transactions .FirstOrDefaultAsync(t => t.Id == order.TransactionId.Value, cancellationToken); if (transaction != null) { transaction.PaymentStatus = PaymentStatus.Reject; } } _logger.LogInformation( "Expired order #{OrderId} (created {Created:u}) — inventory released, status set to Reject", order.Id, order.Created); } catch (Exception ex) { _logger.LogError(ex, "Failed to expire order #{OrderId}", order.Id); } } await context.SaveChangesAsync(cancellationToken); } }