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>
15 lines
536 B
C#
15 lines
536 B
C#
using foodmarket.Application.Common.Tenancy;
|
|
|
|
namespace foodmarket.UnitTests.Support;
|
|
|
|
/// <summary>Подменяемый ITenantContext для тестов: значения задаются напрямую,
|
|
/// без HttpContext/JWT.</summary>
|
|
public sealed class FakeTenantContext : ITenantContext
|
|
{
|
|
public Guid? OrganizationId { get; set; }
|
|
public bool IsAuthenticated { get; set; } = true;
|
|
public bool IsSuperAdmin { get; set; }
|
|
public bool IsTenantOverride { get; set; }
|
|
public Guid? UserId { get; set; }
|
|
}
|