From 4a064de96b7a1207a723827ef25292f3a07bf442 Mon Sep 17 00:00:00 2001 From: nns Date: Wed, 7 Oct 2026 22:40:25 +0500 Subject: [PATCH] =?UTF-8?q?fix(money):=20=D1=81=D0=BF=D0=B8=D1=81=D0=BE?= =?UTF-8?q?=D0=BA=20=D1=81=D1=87=D0=B5=D1=82=D0=BE=D0=B2=20500=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B5=D0=BF=D1=83=D1=81=D1=82=D1=8B=D1=85=20?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20(EF.Property=20xmin=20?= =?UTF-8?q?=D0=B2=20=D0=BF=D0=B0=D0=BC=D1=8F=D1=82=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MoneyAccountsController.List проецировал xmin через EF.Property ПОСЛЕ ToListAsync на AsNoTracking-сущности → InvalidOperationException (shadow- свойство вне трекера). С пустым списком не всплывало (нет строк), с данными — 500, и ломались ВСЕ money-страницы, дёргающие useMoneyAccounts (счета/смены/ платежи) — поймал menu-walk на stage. Переносим xmin в server-side Select. Регресс закрыт: date-only тест теперь ещё и GET-ит список с данными. Co-Authored-By: Claude Opus 4.8 --- .../Controllers/Money/MoneyAccountsController.cs | 13 +++++++++++-- .../MoneyModuleTests.cs | 11 ++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/food-market.api/Controllers/Money/MoneyAccountsController.cs b/src/food-market.api/Controllers/Money/MoneyAccountsController.cs index 254539a..7ef936c 100644 --- a/src/food-market.api/Controllers/Money/MoneyAccountsController.cs +++ b/src/food-market.api/Controllers/Money/MoneyAccountsController.cs @@ -103,7 +103,16 @@ public record MoneyAccountInput( var items = await q .OrderBy(x => x.a.Kind).ThenBy(x => x.a.Name) .Skip(req.Skip).Take(req.Take) - .Select(x => new { x.a, x.cu, StoreName = x.st != null ? x.st.Name : null }) + // xmin проецируем ВНУТРИ запроса (через SQL) — иначе EF.Property на + // AsNoTracking-сущности в памяти кидает (shadow-свойство не хранится + // вне трекера). С пустым списком не всплывало (нет строк); с данными + // — 500 на каждой money-странице, использующей список счетов. + .Select(x => new + { + x.a, x.cu, + StoreName = x.st != null ? x.st.Name : null, + Xmin = Microsoft.EntityFrameworkCore.EF.Property(x.a, "xmin"), + }) .ToListAsync(ct); var rows = items.Select(x => @@ -116,7 +125,7 @@ public record MoneyAccountInput( x.cu.Id, x.cu.Code, x.a.OpeningBalance, x.a.OpeningDate, current, x.a.IsActive, x.a.IsDefault, - Microsoft.EntityFrameworkCore.EF.Property(x.a, "xmin")); + x.Xmin); }).ToList(); return new PagedResult diff --git a/tests/food-market.IntegrationTests/MoneyModuleTests.cs b/tests/food-market.IntegrationTests/MoneyModuleTests.cs index 11c2fb1..44268de 100644 --- a/tests/food-market.IntegrationTests/MoneyModuleTests.cs +++ b/tests/food-market.IntegrationTests/MoneyModuleTests.cs @@ -59,7 +59,16 @@ public async Task Account_create_accepts_date_only_opening_date_from_ui() isActive = true, isDefault = false, }); resp.IsSuccessStatusCode.Should().BeTrue($"date-only openingDate должен приниматься: {(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}"); - (await resp.Content.ReadFromJsonAsync()).GetProperty("currentBalance").GetDecimal().Should().Be(50_000m); + var created = await resp.Content.ReadFromJsonAsync(); + created.GetProperty("currentBalance").GetDecimal().Should().Be(50_000m); + + // Регресс: список счетов С ДАННЫМИ не должен падать (xmin проецировался + // в памяти на AsNoTracking → 500; с пустым списком не всплывало). + using var list = await api.Http.GetAsync("/api/money/accounts?pageSize=50"); + list.IsSuccessStatusCode.Should().BeTrue($"список счетов с данными должен отдаваться: {(int)list.StatusCode}"); + var listed = await api.ListAsync("/api/money/accounts?pageSize=50"); + listed.Select(x => x.GetProperty("id").GetString()) + .Should().Contain(created.GetProperty("id").GetString()); } [Fact]