using foodmarket.Application.Common.Tenancy; using foodmarket.Application.Inventory; using foodmarket.Domain.Inventory; using foodmarket.Infrastructure.Persistence; using Microsoft.EntityFrameworkCore; namespace foodmarket.Infrastructure.Inventory; /// Создаёт/закрывает при изменении остатка. /// Сущности только трекаются в текущем DbContext — SaveChanges делает /// вызывающий документ (атомарно с проводкой). public sealed class NegativeStockAlertService : INegativeStockAlertService { private readonly AppDbContext _db; private readonly ITenantContext _tenant; public NegativeStockAlertService(AppDbContext db, ITenantContext tenant) { _db = db; _tenant = tenant; } public async Task OnStockLevelAsync( Guid storeId, Guid productId, decimal newQty, string? causeDocType, Guid? causeDocId, string? causeDocNumber, Guid? causeUserId, DateTime? causeAt, CancellationToken ct = default) { var orgId = _tenant.OrganizationId; if (orgId is null) return; var now = DateTime.UtcNow; // Ищем открытый алерт по (store, product). Учитываем и ещё не сохранённые // (Local) — на случай нескольких движений одного товара в одном документе. var open = _db.NegativeStockAlerts.Local .FirstOrDefault(a => a.StoreId == storeId && a.ProductId == productId && a.ResolvedAt == null) ?? await _db.NegativeStockAlerts .FirstOrDefaultAsync(a => a.StoreId == storeId && a.ProductId == productId && a.ResolvedAt == null, ct); if (newQty < 0) { if (open is null) { _db.NegativeStockAlerts.Add(new NegativeStockAlert { OrganizationId = orgId.Value, StoreId = storeId, ProductId = productId, Quantity = newQty, CauseDocType = causeDocType, CauseDocId = causeDocId, CauseDocNumber = causeDocNumber, CauseUserId = causeUserId, CauseAt = causeAt ?? now, FirstDetectedAt = now, LastUpdatedAt = now, }); } else { open.Quantity = newQty; open.CauseDocType = causeDocType; open.CauseDocId = causeDocId; open.CauseDocNumber = causeDocNumber; open.CauseUserId = causeUserId; open.CauseAt = causeAt ?? now; open.LastUpdatedAt = now; } } else if (open is not null) { // Остаток вернулся ≥ 0 — закрываем. open.Quantity = newQty; open.ResolvedAt = now; open.LastUpdatedAt = now; } } }