From 305b8d3fd87df79a436c9de6440484005d62d2eb Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Sun, 22 Feb 2026 21:14:09 +0330 Subject: [PATCH] =?UTF-8?q?feat:=20add=20MagicWalletCycleSeedService=20?= =?UTF-8?q?=E2=80=94=20one-time=20BackgroundService=20to=20seed=20ClubMemb?= =?UTF-8?q?ershipCycle=20for=20existing=20active=20memberships,=20controll?= =?UTF-8?q?ed=20via=20appsettings=20SeedWorkers:MagicWalletCycleSeed:Enabl?= =?UTF-8?q?ed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MagicWalletCycleSeedService.cs | 126 ++++++++++++++++++ .../ConfigureServices.cs | 4 + .../appsettings.Staging.json | 5 + src/CMSMicroservice.WebApi/appsettings.json | 5 + 4 files changed, 140 insertions(+) create mode 100644 src/CMSMicroservice.Infrastructure/BackgroundServices/MagicWalletCycleSeedService.cs diff --git a/src/CMSMicroservice.Infrastructure/BackgroundServices/MagicWalletCycleSeedService.cs b/src/CMSMicroservice.Infrastructure/BackgroundServices/MagicWalletCycleSeedService.cs new file mode 100644 index 0000000..72cdd87 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/BackgroundServices/MagicWalletCycleSeedService.cs @@ -0,0 +1,126 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Club; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Infrastructure.BackgroundServices; + +/// +/// سرویس یکباره برای Seed کردن ClubMembershipCycle برای عضویت‌های فعال موجود. +/// عضویت‌هایی که قبل از اضافه شدن سیستم Cycle ایجاد شده‌اند رکورد ندارند. +/// این Worker در استارتاپ اجرا شده، برای آنها CycleNumber=1 می‌سازد و سپس متوقف می‌شود. +/// +/// فعال/غیرفعال از appsettings: +/// "SeedWorkers": { "MagicWalletCycleSeed": { "Enabled": true } } +/// +/// بعد از اجرای موفق، Enabled را false کنید. +/// +public class MagicWalletCycleSeedService : BackgroundService +{ + private readonly IServiceScopeFactory _scopeFactory; + private readonly ILogger _logger; + + public MagicWalletCycleSeedService( + IServiceScopeFactory scopeFactory, + ILogger logger) + { + _scopeFactory = scopeFactory; + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + // صبر کوتاه برای اطمینان از آماده شدن دیتابیس + await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken); + + _logger.LogInformation("MagicWalletCycleSeedService started — scanning active memberships without cycles..."); + + try + { + using var scope = _scopeFactory.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + + var seededCount = await SeedMissingCycles(context, stoppingToken); + + _logger.LogInformation( + "MagicWalletCycleSeedService completed — seeded {Count} ClubMembershipCycle records", + seededCount); + } + catch (Exception ex) + { + _logger.LogError(ex, "MagicWalletCycleSeedService encountered an error"); + } + + _logger.LogInformation( + "MagicWalletCycleSeedService finished — shutting down (one-time execution). " + + "Set SeedWorkers:MagicWalletCycleSeed:Enabled to false in appsettings."); + } + + private async Task SeedMissingCycles(IApplicationDbContext context, CancellationToken ct) + { + // پیدا کردن عضویت‌های فعالی که هنوز Cycle ندارند + var membershipIdsWithCycle = await context.ClubMembershipCycles + .Where(c => !c.IsDeleted) + .Select(c => c.ClubMembershipId) + .Distinct() + .ToListAsync(ct); + + var membershipsWithoutCycle = await context.ClubMemberships + .Where(cm => !cm.IsDeleted && cm.IsActive && !membershipIdsWithCycle.Contains(cm.Id)) + .Select(cm => new + { + cm.Id, + cm.UserId, + cm.ActivatedAt + }) + .ToListAsync(ct); + + if (membershipsWithoutCycle.Count == 0) + { + _logger.LogInformation("No active memberships without cycles found — nothing to seed"); + return 0; + } + + _logger.LogInformation("Found {Count} active memberships without ClubMembershipCycle — seeding...", + membershipsWithoutCycle.Count); + + var seeded = 0; + foreach (var cm in membershipsWithoutCycle) + { + if (ct.IsCancellationRequested) break; + + try + { + var cycle = new ClubMembershipCycle + { + UserId = cm.UserId, + ClubMembershipId = cm.Id, + CycleNumber = 1, + PackagePurchasedAt = cm.ActivatedAt ?? DateTime.UtcNow, + IsCurrentCycle = true, + PurchaseMethod = 0, + PackageAmount = 0 + }; + + context.ClubMembershipCycles.Add(cycle); + seeded++; + + _logger.LogDebug( + "Seeded ClubMembershipCycle for UserId={UserId}, MembershipId={MembershipId}, ActivatedAt={ActivatedAt}", + cm.UserId, cm.Id, cm.ActivatedAt); + } + catch (Exception ex) + { + _logger.LogWarning(ex, + "Failed to seed cycle for MembershipId={MembershipId}, UserId={UserId}", + cm.Id, cm.UserId); + } + } + + await context.SaveChangesAsync(ct); + + return seeded; + } +} diff --git a/src/CMSMicroservice.Infrastructure/ConfigureServices.cs b/src/CMSMicroservice.Infrastructure/ConfigureServices.cs index bb47e93..016fb9d 100644 --- a/src/CMSMicroservice.Infrastructure/ConfigureServices.cs +++ b/src/CMSMicroservice.Infrastructure/ConfigureServices.cs @@ -126,6 +126,10 @@ public static class ConfigureServices // One-time: Initialize inventory records for existing products services.AddHostedService(); + // One-time: Seed ClubMembershipCycle for existing active memberships + if (configuration.GetValue("SeedWorkers:MagicWalletCycleSeed:Enabled")) + services.AddHostedService(); + if (configuration.GetValue("UseInMemoryDatabase")) { services.AddDbContext(options => diff --git a/src/CMSMicroservice.WebApi/appsettings.Staging.json b/src/CMSMicroservice.WebApi/appsettings.Staging.json index c0f39e7..608c072 100644 --- a/src/CMSMicroservice.WebApi/appsettings.Staging.json +++ b/src/CMSMicroservice.WebApi/appsettings.Staging.json @@ -68,6 +68,11 @@ "CronExpression": "5 0 * * 0" } }, + "SeedWorkers": { + "MagicWalletCycleSeed": { + "Enabled": true + } + }, "AllowedHosts": "*", "Kestrel": { "EndpointDefaults": { diff --git a/src/CMSMicroservice.WebApi/appsettings.json b/src/CMSMicroservice.WebApi/appsettings.json index d8263d5..424513b 100644 --- a/src/CMSMicroservice.WebApi/appsettings.json +++ b/src/CMSMicroservice.WebApi/appsettings.json @@ -75,6 +75,11 @@ "CronExpression": "5 0 * * 0" } }, + "SeedWorkers": { + "MagicWalletCycleSeed": { + "Enabled": true + } + }, "AllowedHosts": "*", "Authentication": { "Authority": "https://ids.domain.com/",