using foodmarket.Application.Common.Tenancy; using foodmarket.Application.Inventory; using foodmarket.Domain.Inventory; using foodmarket.Infrastructure.Persistence; using Microsoft.EntityFrameworkCore; namespace foodmarket.Infrastructure.Inventory; public class StockService : IStockService { private readonly AppDbContext _db; private readonly ITenantContext _tenant; private readonly INegativeStockAlertService _negAlerts; public StockService(AppDbContext db, ITenantContext tenant, INegativeStockAlertService negAlerts) { _db = db; _tenant = tenant; _negAlerts = negAlerts; } public async Task ApplyMovementAsync(StockMovementDraft d, CancellationToken ct = default) { var orgId = _tenant.OrganizationId ?? throw new InvalidOperationException("Tenant not set."); _db.StockMovements.Add(new StockMovement { OrganizationId = orgId, ProductId = d.ProductId, StoreId = d.StoreId, Quantity = d.Quantity, UnitCost = d.UnitCost, Type = d.Type, DocumentType = d.DocumentType, DocumentId = d.DocumentId, DocumentNumber = d.DocumentNumber, OccurredAt = d.OccurredAt ?? DateTime.UtcNow, CreatedByUserId = d.CreatedByUserId, Notes = d.Notes, }); var stock = await _db.Stocks.FirstOrDefaultAsync( s => s.ProductId == d.ProductId && s.StoreId == d.StoreId, ct); if (stock is null) { stock = new Stock { OrganizationId = orgId, ProductId = d.ProductId, StoreId = d.StoreId, Quantity = d.Quantity, }; _db.Stocks.Add(stock); } else { stock.Quantity += d.Quantity; } // Предупреждение об отрицательном остатке: единая точка детекции для всех // документов. Остаток < 0 → открыть/обновить алерт; ≥ 0 → закрыть. await _negAlerts.OnStockLevelAsync(d.StoreId, d.ProductId, stock.Quantity, d.DocumentType, d.DocumentId, d.DocumentNumber, d.CreatedByUserId, d.OccurredAt, ct); return stock.Quantity; } public async Task ApplyMovementsAsync(IEnumerable drafts, CancellationToken ct = default) { var last = 0m; foreach (var d in drafts) last = await ApplyMovementAsync(d, ct); return last; } }