Commit graph

2 commits

Author SHA1 Message Date
nns a189a5dd6e feat(audit): per-tenant журнал мутаций OrgAuditLog (P1-18)
Domain OrgAuditLog (TenantEntity) - per-org журнал create/update/delete
для Supply/SupplierReturn/RetailSale/Demand/Product/ProductPrice/
ProductBarcode/Counterparty (белый список в IsTracked).

Реализация: OrgAuditInterceptor (SaveChangesInterceptor) снимает diff на
SavingChanges (до commit), пишет в тот же DbContext в той же транзакции -
атомарно с самой мутацией. ChangesJson формата
{ "field": { "before": X, "after": Y } } - служебные поля
(OrganizationId/CreatedAt/UpdatedAt) пропускаются.

ITenantContext получил UserId (sub claim) для атрибуции событий.
AppDbContext.SkipAudit - escape-hatch для сидеров/системных операций.

Tenant-isolation: query-filter обычный TenantEntity-фильтр. B не видит
audit-строки A; SuperAdmin без override видит всё.

Контроллер GET /api/admin/audit-log с фильтрами entityType / entityId /
userId / action / from / to. Permission OrgSettingsManage.
Web: /audit-log для Admin'а - таблица с раскрывающимся JSON diff'ом,
цветные плашки create/update/delete, фильтры по типу и действию.

Миграция Phase8b_OrgAuditLog: jsonb-колонка, индексы
(OrgId+CreatedAt), (OrgId+EntityType+EntityId), (OrgId+UserId+CreatedAt).

Тесты: 3 интеграционных (create Product создаёт audit-запись;
update Counterparty - diff содержит before/after; tenant-изоляция:
B не видит записи A).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 16:26:36 +05:00
nns f3d517f257 test(unit): xUnit-проект food-market.UnitTests, 23 теста (P1-20)
Чистая логика вынесена в Application для тестируемости и используется контроллерами:
- MovingAverageCost.Compute (скользящее среднее себестоимости) ← SuppliesController.Post
- RetailPaymentValidator.IsSufficient (достаточность оплаты) ← RetailSalesController.Post

Тесты:
- MovingAverageCost: первая приёмка, средневзвешенное, округление до 4 знаков, totalQty=0.
- RetailPaymentValidator: ровно/переплата/недоплата, округление до 2 знаков.
- StockService.ApplyMovement (SQLite in-memory): материализация Stock+движение,
  инкремент, отрицательное списание, throw без tenant.
- Мультитенантный query-filter AppDbContext: tenant видит своё; чужой не видит;
  SuperAdmin без override — всё; с override — только выбранную оргу.

Все 23 зелёные. EF8 SQLite поддерживает ToJson (EmployeeRole.Permissions).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 03:01:56 +05:00