From 9dc8f6e0448208e6a03934e99602688117fb0e87 Mon Sep 17 00:00:00 2001 From: nns Date: Fri, 9 Oct 2026 09:52:33 +0500 Subject: [PATCH] =?UTF-8?q?fix(inventory):=20=D0=B7=D0=B0=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D1=8B=D0=B2=D0=B0=D1=82=D1=8C=20=D0=B0=D0=B2=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B0=20=D0=B4=D0=B2=D0=B8=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20(CauseUserId)=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=81=D0=B5?= =?UTF-8?q?=D1=85=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CauseUserId в уведомлениях о минусах был null: StockService писал автора только если вызывающий явно передал CreatedByUserId в черновике, а большинство документов (касса/списание/перемещение/приёмка/…) его не передавали. Теперь автор берётся из черновика, иначе — из текущего пользователя (ITenantContext.UserId) в единой точке ApplyMovementAsync, поэтому пишется и в StockMovement, и в алерт для всех типов документов. Тест: минус от списания → causeUserId = текущий юзер. Co-Authored-By: Claude Opus 4.8 --- src/food-market.infrastructure/Inventory/StockService.cs | 8 ++++++-- tests/food-market.IntegrationTests/NegativeStockTests.cs | 6 ++++++ 2 files changed, 12 insertions(+), 2 deletions(-) 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 });