# Глоссарий food-market Доменные термины, которые используются в коде, документации и общении с пользователями. Один термин — одно определение. Ссылки на код через `file:line` или namespace.path. ## Базовые сущности ### Organization (Организация, tenant) **Корневая сущность мульти-tenancy.** Один процесс API обслуживает много организаций; каждая видит только свои данные через query-filter по `OrganizationId`. Не tenant-scoped сама по себе (отношение «один-ко-многим» с TenantEntity). Code: `foodmarket.Domain.Organizations.Organization` (`src/food-market.domain/Organizations/Organization.cs`). См. [MULTI-TENANCY.md](MULTI-TENANCY.md). ### TenantEntity / ITenantEntity Базовый класс/интерфейс для всех domain-сущностей с `OrganizationId`. `AppDbContext` автоматически применяет query-filter по reflection. Code: `foodmarket.Domain.Common.TenantEntity` + `ITenantEntity`. ### IOptionalTenantEntity Двухуровневые справочники: либо системная запись (OrganizationId=null, видна всем, мутирует только SuperAdmin), либо tenant'овская. Пример: `UnitOfMeasure`, `ProductGroup` — есть глобальные «штука», есть кастомные. ### User Учётная запись для логина (ASP.NET Identity). НЕ привязан к одной org — один email может работать в нескольких организациях через Employee. Code: `foodmarket.Domain.Identity.User`. ### Employee (Сотрудник) Запись о работнике конкретной org. Может иметь User (для логина) или быть «без аккаунта» (только в чеках/документах). Связан с EmployeeRole. Code: `foodmarket.Domain.Organizations.Employee`. ### Owner / AccountOwnerUserId Первый пользователь, создавший org через signup. Хранится в `Organization.AccountOwnerUserId`. Не удаляется (кроме как через SuperAdmin reassign). ### Role / EmployeeRole / RolePermissions - **Identity Role** (ASP.NET) — системная: `SuperAdmin`, `Admin`, `Cashier`, `Storekeeper`, `Manager`. - **EmployeeRole** — per-org кастомная роль (например, «Старший кассир»), привязана к сотруднику. Имеет `RolePermissions` (флаги типа `ProductsEdit`, `RetailSalesOperate`). - **Permission** — атрибут `[RequiresPermission("Name")]` на endpoint'е. Проверяет `RolePermissions` сотрудника текущего юзера. ### Store (Склад) Физическое место хранения остатков. У org может быть несколько; первый после signup — «MAIN» store. Code: `foodmarket.Domain.Organizations.Store`. ### RetailPoint (Касса / торговая точка) Привязана к Store, к ней привязывается RetailSale. Может иметь фискальные поля (FiscalSerial, FiscalRegNumber). Code: `foodmarket.Domain.Organizations.RetailPoint`. ## Каталог ### Product (Товар) Единица каталога. Имеет несколько Prices (по типам), Barcodes, Images, принадлежит ProductGroup. Поля Sprint 19: `IsArchived`, `IsAvailableForSale`. Code: `foodmarket.Domain.Catalog.Product`. ### ProductGroup (Группа товаров) Иерархическая (через `ParentId` + `Path`). Корень — «Все товары». Может быть системной (OrganizationId=null) или per-org. Code: `foodmarket.Domain.Catalog.ProductGroup`. ### ProductPrice (Цена) Один товар × один PriceType = одна цена. Тип может быть «системным» (IsSystem — основная розничная) или «обязательным» (IsRequired — без неё нельзя сохранить товар). Code: `foodmarket.Domain.Catalog.ProductPrice`. ### PriceType (Тип цены) Розничная / Закупочная / Базовая / Себестоимость и т.д. Per-org. Sprint 1. Code: `foodmarket.Domain.Catalog.PriceType`. ### ProductBarcode (Штрихкод) Уникальный (составной UNIQUE: Code + Organization). Один товар может иметь несколько штрихкодов; один из них — `IsPrimary` (показывается на этикетке). Code: `foodmarket.Domain.Catalog.ProductBarcode`. ### UnitOfMeasure (Единица измерения) шт / кг / л / м / упак. Системные (OrganizationId=null) + org-кастомные. `OrgUnitOfMeasure` — таблица per-org enable/disable. Code: `foodmarket.Domain.Catalog.UnitOfMeasure`. ### Counterparty (Контрагент) Поставщик (Supplier) / Покупатель-юрлицо (LegalEntity) / Покупатель-физлицо (Individual). Имеет БИН/ИИН, банковские реквизиты, контакты. Code: `foodmarket.Domain.Catalog.Counterparty`. ## Остатки и движения ### Stock (Остаток) Кеш `SUM(StockMovement.Quantity)` для пары `(Store, Product)`. Поддерживается транзакционно в каждом posting'е документа. Code: `foodmarket.Domain.Inventory.Stock`. ### StockMovement (Движение остатка) Имматериальная запись об изменении остатка. Source: документ (Supply.Post / RetailSale.Post / Enter.Post / Loss.Post / Transfer.Post / Inventory.Post / SupplierReturn.Post / CustomerReturn.Post). **Инвариант**: `Stock.Quantity ≡ Σ StockMovement.Quantity` для каждой пары (Store, Product). Проверяется property-test'ом (Sprint 15). Code: `foodmarket.Domain.Inventory.StockMovement`. ## Документы (Documents) Все имеют поля: `Number`, `Date`, `Status` (Draft/Posted), `PostedAt`. Имеют `IVersionedEntity` для `xmin` concurrency check. ### Supply (Приёмка) От поставщика. Увеличивает остаток + пересчитывает скользящую себестоимость (Product.Cost). Code: `foodmarket.Domain.Purchases.Supply`. ### Enter (Оприходование) Внутреннее. Увеличивает остаток без поставщика. Для коррекций инвентаризации. Code: `foodmarket.Domain.Inventory.Enter`. ### Loss (Списание) Уменьшает остаток. Причина: порча, кража, тестовое использование. Code: `foodmarket.Domain.Inventory.Loss`. ### Transfer (Перемещение) Между складами. Уменьшает на исходном, увеличивает на целевом. Code: `foodmarket.Domain.Inventory.Transfer`. ### Inventory (Инвентаризация) Списки фактических остатков. Расхождение → автоматические Enter/Loss строки при post. Code: `foodmarket.Domain.Inventory.InventoryDoc` (имя класса не Inventory из-за конфликта с namespace). ### RetailSale (Розничный чек) Продажа через POS / админку. После Post → уменьшает остаток, пишет ОФД снапшот (FiscalNumber etc., Sprint 11), уведомляет SignalR. Code: `foodmarket.Domain.Sales.RetailSale`. ### Demand (Оптовая отгрузка) Продажа юрлицу. Аналогично RetailSale, но с накладной (печатной формой). Code: `foodmarket.Domain.Sales.Demand`. ### SupplierReturn (Возврат поставщику) Sprint 5. Уменьшает остаток + возвращает деньги поставщику. Code: `foodmarket.Domain.Purchases.SupplierReturn`. ### CustomerReturn / RetailSale.IsReturn=true Возврат от покупателя. Реализован через флаг `IsReturn` на RetailSale + ReferenceSaleId. Восстанавливает остаток. ## Деньги ### Cost (Себестоимость) Скользящее среднее `(qty_old × cost_old + qty_in × price_in) / (qty_old + qty_in)`. Пересчитывается на каждой проведённой Supply. `Decimal(18,4)`. ### ReferencePrice (Эталонная цена закупа) Опциональная. Заполняется автоматически unit-price'ом первой Supply; после 30 дней без новых Supply → Hangfire-job переписывает на Cost. ### VAT (НДС) - `Product.Vat` (default из `Country.VatRate`, в РК — 12%). - `Product.VatEnabled` — управляет видимостью поля на UI. - На документах: `VatMode` (включается «в том числе» / «сверху»). ### AllowFractionalPrices (Дробные цены) Org-настройка. Если false → все цены округляются до целых при сохранении. Sprint 23 bug-004: round-then-validate чтобы избежать «0 цена прошла required-check». ## Доступ и безопасность ### Tenant context `ITenantContext` (resolved per request) выдаёт `OrganizationId` из JWT claim `org_id`. NULL для unauthenticated / SuperAdmin-без-override. ### SuperAdmin Системная роль. Видит все organizations + может «открыть как…» через `X-Org-Override` header (включает Admin claim для этой org'и). Все действия SuperAdmin'a в override-режиме пишутся в `super_admin_audit_log`. ### OrgAuditLog Per-tenant журнал каждой mutate-операции (CREATE/UPDATE/DELETE на любую TenantEntity). Пишется автоматически через `OrgAuditInterceptor` на SaveChanges. ### Permission Атрибут `[RequiresPermission("ProductsEdit")]` на endpoint'е. Проверяет флаг `RolePermissions` сотрудника текущего юзера. Если у юзера нет Employee в этой org — 403. ## Фоновые операции ### Hangfire job .NET background job framework. `recurring-job` (по cron) и `background-job` (одноразовый). Хранятся в схеме `hangfire` той же БД. ### advisory lock (Sprint 18) PostgreSQL `pg_advisory_xact_lock(int, int)` — кооперативная блокировка per-(org, doctype). Используется для сериализации генерации номера документа. ### Serializable transaction PostgreSQL Isolation Level. Используется в posting'ах документов (`RetailSale.Post`, `Supply.Post`, etc.) для защиты от race на остатках. На конфликте → 40001, теперь мапится в 409 (Sprint 23). ## Внешние интеграции ### ОФД (OFD) Оператор Фискальных Данных. В РК: Webkassa, Kassa24, ОФД-Соло. RetailSale.Post после успеха отправляет фискальный документ → получает `FiscalNumber`, `FiscalQrCode`. Sprint 11 scaffolding. ### МойСклад Сторонняя SaaS-система учёта. Импорт товаров/контрагентов/остатков по OAuth-token (per-org в `Organization.MoySkladToken`). ### POS (касса) WPF-приложение под Windows 10+. Локальный SQLite-буфер, синк через `/api/pos/v1/*` с idempotency-ключом (см. `pos_batch_acks`). ### Telegram bot Один platform-bot (token в env). Owner'ы org'и привязывают свой chat-id (`Organization.OwnerTelegramChatId`) для получения daily-сводки. ## Тестирование ### Stage `https://test.admin.food-market.kz`. Контейнеры на prod-vm `192.168.1.190`, deploy через `~/deploy-stage.sh`. ### Smoke / Regression / Verify - **Smoke** — быстрый sanity-check (5 шагов signup → login → bootstrap). - **Regression** — полный e2e через Playwright (44 spec'a в Sprint 23). - **Verify** — спринт-специфичные post-feature тесты. ## Сокращения | Сокр | Что | |---|---| | **AT** | Access Token (JWT, TTL 1h) | | **RT** | Refresh Token (для получения нового AT) | | **PoS** | Point of Sale (касса) | | **ОФД** | Оператор Фискальных Данных | | **БИН** | 12-цифровой номер юрлица в РК | | **ИИН** | 12-цифровой номер физлица в РК | | **RPO** | Recovery Point Objective (макс. потеря данных при backup-restore) | | **RTO** | Recovery Time Objective (время восстановления) | | **CSP** | Content Security Policy (HTTP-header) | | **SA** | SuperAdmin |