diff --git a/src/food-market.infrastructure/Inventory/StockService.cs b/src/food-market.infrastructure/Inventory/StockService.cs index 0b1c228..99e85c8 100644 --- a/src/food-market.infrastructure/Inventory/StockService.cs +++ b/src/food-market.infrastructure/Inventory/StockService.cs @@ -22,6 +22,10 @@ public StockService(AppDbContext db, ITenantContext tenant, INegativeStockAlertS public async Task ApplyMovementAsync(StockMovementDraft d, CancellationToken ct = default) { var orgId = _tenant.OrganizationId ?? throw new InvalidOperationException("Tenant not set."); + // Автор движения: берём из черновика, иначе — текущий пользователь (JWT). Так + // автор пишется для ВСЕХ документов (касса/списание/перемещение/приёмка/…), + // даже если вызывающий его явно не указал — иначе CauseUserId в алертах был null. + var userId = d.CreatedByUserId ?? _tenant.UserId; _db.StockMovements.Add(new StockMovement { @@ -35,7 +39,7 @@ public async Task ApplyMovementAsync(StockMovementDraft d, Cancellation DocumentId = d.DocumentId, DocumentNumber = d.DocumentNumber, OccurredAt = d.OccurredAt ?? DateTime.UtcNow, - CreatedByUserId = d.CreatedByUserId, + CreatedByUserId = userId, Notes = d.Notes, }); @@ -61,7 +65,7 @@ public async Task ApplyMovementAsync(StockMovementDraft d, Cancellation // Предупреждение об отрицательном остатке: единая точка детекции для всех // документов. Остаток < 0 → открыть/обновить алерт; ≥ 0 → закрыть. await _negAlerts.OnStockLevelAsync(d.StoreId, d.ProductId, stock.Quantity, - d.DocumentType, d.DocumentId, d.DocumentNumber, d.CreatedByUserId, d.OccurredAt, ct); + d.DocumentType, d.DocumentId, d.DocumentNumber, userId, d.OccurredAt, ct); return stock.Quantity; } diff --git a/tests/food-market.IntegrationTests/NegativeStockTests.cs b/tests/food-market.IntegrationTests/NegativeStockTests.cs index 4793e87..2137b99 100644 --- a/tests/food-market.IntegrationTests/NegativeStockTests.cs +++ b/tests/food-market.IntegrationTests/NegativeStockTests.cs @@ -64,6 +64,12 @@ public async Task Loss_into_negative_is_allowed_creates_alert_recount_and_resolv alerts[0].GetProperty("quantity").GetDecimal().Should().Be(-3m); alerts[0].GetProperty("productId").GetString().Should().Be(p); alerts[0].GetProperty("causeDocType").GetString().Should().Be("loss"); + // Автор документа записан (раньше CauseUserId был null). Сверяем с /api/me. + var me = await api.GetJsonAsync("/api/me"); + var myId = me.GetProperty("sub").GetString(); + alerts[0].TryGetProperty("causeUserId", out var cu).Should().BeTrue(); + cu.GetString().Should().NotBeNullOrEmpty("автор минусового документа должен быть записан"); + cu.GetString().Should().Be(myId, "автор — текущий пользователь"); // «Пересчитать эти товары» → черновик инвентаризации с этим товаром. var rc = await api.Http.PostAsJsonAsync("/api/negative-stock/recount", new { storeId = refs.StoreId });