using foodmarket.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
namespace foodmarket.Api.Background;
/// Раз в сутки переписывает ReferencePrice = Cost для товаров,
/// у которых LastSupplyAt старше 30 дней и Cost > 0. Цель: устаревшая
/// «эталонная» цена не остаётся годами — её сравнивают с актуальной
/// себестоимостью. Если пользователь редактировал ReferencePrice вручную
/// (через PUT /api/catalog/products/...), ReferencePriceUpdatedAt уходит
/// в now, и таймер начинает 30 дней заново.
public class ReferencePriceRefreshJob : BackgroundService
{
private readonly IServiceProvider _services;
private readonly ILogger _log;
// Запускаем каждые 24 часа. Промах между сутками не критичен — цена
// не падает, а лишь догоняет текущую Cost.
private static readonly TimeSpan Period = TimeSpan.FromHours(24);
public ReferencePriceRefreshJob(IServiceProvider services, ILogger log)
{
_services = services;
_log = log;
}
protected override async Task ExecuteAsync(CancellationToken ct)
{
// Ждём 5 минут после старта чтобы не упасть на ещё не применённой миграции.
try { await Task.Delay(TimeSpan.FromMinutes(5), ct); } catch (TaskCanceledException) { return; }
using var timer = new PeriodicTimer(Period);
do
{
try { await RunOnceAsync(ct); }
catch (Exception ex) { _log.LogError(ex, "ReferencePriceRefreshJob: iteration failed"); }
}
while (await timer.WaitForNextTickAsync(ct));
}
public async Task RunOnceAsync(CancellationToken ct)
{
using var scope = _services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService();
var threshold = DateTime.UtcNow.AddDays(-30);
// IgnoreQueryFilters — фоновый job работает над всеми organizations.
var stale = await db.Products
.IgnoreQueryFilters()
.Where(p => p.LastSupplyAt != null && p.LastSupplyAt < threshold && p.Cost > 0m)
.ToListAsync(ct);
if (stale.Count == 0) return;
var now = DateTime.UtcNow;
foreach (var p in stale)
{
p.ReferencePrice = p.Cost;
p.ReferencePriceUpdatedAt = now;
}
await db.SaveChangesAsync(ct);
_log.LogInformation("ReferencePriceRefreshJob: refreshed {Count} stale products", stale.Count);
}
}