ESWF · Data Model Overview¶
Для кого: новий розробник або AI-агент, що вперше відкриває репо. Мета — за 15 хвилин отримати картину, як влаштовані дані ESWF/DOP, без читання
backend/*/models/(130+ ORM-класів у 23 аппах).Що ВНУТРІ: базові абстракції, multi-tenancy на рівні моделей, категорії моделей, карта крос-аппових FK, posting bridge (документ → бух. проводка), реєстри, multi-ledger, app-індекс.
Що ЗОВНІ: API contract, RBAC/security, frontend-↔-backend конвенції, deployment — це окремі гепи, які закриватимуться іншими доками (див. docs/audit-2026-04-22.md).
1. Базові класи¶
Уся бізнес-модель росте з 5 абстрактних класів у backend/core/models/base.py:
TenantAwareModel (uuid, tenant, created/updated_at/by)
│
┌───────────────┼─────────────────┬──────────────────┐
│ │ │ │
MasterDataModel TransactionModel RegisterModel (прямі нащадки —
│ (number, date, (date, document_ PostingEntry,
│ state, status, type, document_id, Batch, ...)
│ description) manual_edit)
│
HierarchicalMasterDataModel
(parent, is_group)
| Клас | Призначення | Ключові поля | Приклади моделей |
|---|---|---|---|
| TenantAwareModel | Корінь усіх таблиць даних. Прив'язка до тенанта + аудит. | uuid, tenant, created_at/by, updated_at/by |
напряму використовується нечасто; всі інші наслідують |
| MasterDataModel | Довідники (каталоги). Логічне видалення через deletion_mark. |
code, name, name_ua, is_active, deletion_mark, description |
Item, Client, Organization, Warehouse, Driver, Employee |
| HierarchicalMasterDataModel | Деревовидні довідники. | + parent (self-FK), is_group |
ChartOfAccounts, ExpenseItem, Vehicle, Container |
| TransactionModel | Бізнес-документи. Дві незалежні осі: state (життєвий цикл) і status (бізнес-воркфлоу). |
number, date, state ∈ {draft, posted, marked}, status, description |
Invoice, GoodsReceipt, Waybill, PayrollSlip, WorkOrder, Deal |
| RegisterModel | Регістри/журнали накопичення. Завжди мають backref на документ. | date (indexed), document_type, document_id, manual_edit |
PostingGroup, CashJournal, InventoryJournal, IncomeExpenseJournal |
Чому 4 категорії, а не загальний BaseModel? Кожен клас несе свою семантику доступу і поведінку:
- Master → CRUD з deletion_mark, не видаляється фізично.
- Transaction → workflow (draft → posted), посилається з регістрів.
- Register → write-once журнал, переписується тільки через анпост-репост документа (інваріант, див. §7).
2. Multi-tenancy на рівні даних¶
Принцип: одна БД, всі таблиці спільні, ізоляція через tenant FK. Schema-per-tenant НЕ використовується.
Request → TenantMiddleware → request.tenant
│
▼
ViewSet (TenantFilterMixin)
│
▼
queryset.filter(tenant=request.tenant)
│
▼
Serializer / Response
| Шар | Файл | Що робить |
|---|---|---|
| Модель | backend/core/models/base.py |
TenantAwareModel.tenant FK на core.Tenant (cascade) — успадковується всіма |
| Middleware | backend/core/middleware/tenant.py |
Витягує тенанта з JWT-токена, кладе у request.tenant |
| ViewSet | backend/core/views.py (TenantFilterMixin) |
Авто-фільтр queryset за request.tenant |
| Cascade | DB-рівень | on_delete=CASCADE на tenant FK → видалення тенанта чистить усі його дані |
Винятки (моделі БЕЗ tenant)¶
| App | Моделі | Чому |
|---|---|---|
news |
Article, Category, Tag |
Глобальний блог, спільний для всіх тенантів |
eswf_chat |
ChatSession, Message, ChatRoom, ... |
Standalone (legacy, не мігровано на TenantAware) |
shop (більшість) |
Product, License, ActivationCode, ... |
SaaS-магазин — продукти спільні, тенанти купують ліцензії |
⚠️ Gotcha з backend/CLAUDE.md:
TenantFilterMixinпідміняє серіалізатор дляlist— у новому ViewSet одразу перевизначайтеget_serializer_class, інакше зламаються детальні відповіді.
3. Категорії моделей (зведення)¶
Загалом по проекту: ~130 ORM-класів + ~50 inline-subtables ≈ 180 таблиць.
| Категорія | К-сть | Базовий клас | Приклади |
|---|---|---|---|
| Master Data | ~65 | MasterDataModel |
Item, Client, Warehouse, Driver, Employee, FuelType, Position |
| Hierarchical Master | ~6 | HierarchicalMasterDataModel |
ChartOfAccounts, ExpenseItem, Vehicle, Container |
| Transactions | ~35 | TransactionModel |
Invoice, GoodsReceipt, Waybill, PayrollSlip, WorkOrder, Deal, Quotation |
| Registers / Ledgers | ~22 | RegisterModel |
PostingGroup, CashJournal, InventoryJournal, IncomeExpenseJournal, ContainerLedger |
| Reference / Enum-as-Table | ~30 | MasterDataModel (легкі) |
Unit, Currency, TaxRate, BusinessOperation, WorkSchedule |
| Config / Settings | ~15 | звичайні models.Model (singleton-стиль) |
EssentialsModuleSettings, FleetModuleSettings, GpsAddonSettings |
| Inline Subtables | ~50+ | звичайні models.Model з FK на parent |
InvoiceLine, GoodsReceiptLine, WaybillTask, BOMLine, PayrollSlipLine |
Subtable-патерн: документ оголошує атрибут _subtables = [...], це використовується EntityRegistry для метадрівен-форм у фронтенді. Деталі — eswf/infrastructure/backend.md.
4. Карта модулів (high-level)¶
essentials — ядро. Усі вертикалі (fleet, production, hrm, crm) посилаються в нього за shared masters (Organization, ChartOfAccounts, Currency, Warehouse, Client, Department).
┌──────────────┐
│ medoc_exchange│ (УКТЗЕД sync)
└──────┬────────┘
│
┌──────────────┐ ┌───────┴────────┐ ┌──────────────┐
│ crm │───►│ │◄───│ hrm │
└──────────────┘ │ │ └──────────────┘
┌──────────────┐ │ │ ┌──────────────┐
│ production │───►│ essentials │◄───│ fleet │
└──────────────┘ │ │ └──────────────┘
┌──────────────┐ │ (ядро ERP: │ ┌──────────────┐
│ budgeting │───►│ docs, CoA, │◄───│ containerhub │
└──────────────┘ │ posting, │ │ (plugin) │
┌──────────────┐ │ registers) │ └──────────────┘
│consolidation │───►│ │ ┌──────────────┐
└──────────────┘ │ │◄───│ ess_quality │
│ │ │ (plugin) │
└──┬─────────┬───┘ └──────────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│registers │ │ shop │
│ (12 jrnl)│ │(SaaS) │
└──────────┘ └──────────┘
┌──────────────┐ ┌──────────────┐
│sales_mobile_ │── tenant docs ───────► │ baf_sync │── external API
│ api │ (через essentials) │ (singleton) │
└──────────────┘ └──────────────┘
Легенда: стрілка A → B = моделі A мають FK на моделі B. Глобальні (без tenant) — news, eswf_chat, частина shop — на діаграмі не показані.
5. Cross-app FK реєстр¶
Шаблон: vertical-моделі тримають тонкі бізнес-поля, а спільні довідники беруть з essentials.
| З моделі | На модель (інший app) | Кратність | Призначення |
|---|---|---|---|
hrm.Position |
essentials.Department |
N:1 | Посада належить підрозділу |
hrm.Employee |
hrm.Position, essentials.Department |
N:1, N:1 | Працівник прив'язаний до посади/підрозділу |
hrm.PayrollSlip |
essentials.Organization, essentials.ChartOfAccounts (×3) |
N:1 | Рахунки для проводок ЗП (expense / payable / tax) |
production.WorkOrder |
essentials.Organization, essentials.Warehouse, essentials.ChartOfAccounts (×4) |
N:1 | Рахунки inventory / cogs / output / variance |
production.BOM |
essentials.Item (output), essentials.Unit |
N:1 | Готовий продукт + од. виміру |
fleet.Waybill |
essentials.Organization, essentials.Department |
N:1 | Транспорт прив'язаний до орг./підрозділу |
fleet.TransportInvoice |
essentials.Organization, essentials.Client, essentials.ChartOfAccounts |
N:1 | Рахунок-фактура за транспортні послуги |
fleet.DriverSalaryAccrual |
essentials.Organization |
N:1 | Нарахування зарплати водія |
crm.Deal, crm.Quotation |
essentials.Currency, essentials.Client |
N:1 | Сума угоди у валюті, клієнт |
consolidation.ConsolidationGroupMember |
essentials.Organization |
N:1 | Учасник холдингу |
budgeting.BudgetLine |
essentials.ChartOfAccounts, essentials.Currency, essentials.BusinessDirection |
N:1 | План по рахунку/проекту |
containerhub.GateTransaction, TrainArrival |
essentials.Organization, essentials.Warehouse |
N:1 | Логістична операція в орг./складі |
essentials_quality.QualityInspection |
essentials.Item, essentials.Warehouse, essentials_quality.DefectReason |
N:1 | Контроль якості при прийманні |
sales_mobile_api.SalesReturn |
essentials.Client, essentials.Item, essentials.Warehouse |
N:1 | Повернення товару з мобільного |
medoc_exchange.UKTZEDDirectory |
(немає крос-FK) | — | Глобальний довідник УКТЗЕД, читається essentials.Item як lookup |
Анти-патерн, якого тут НЕМАЄ: circular import між apps. Усі стрілки односпрямовані → essentials ні на кого не посилається.
6. Posting bridge (документ ↔ облік)¶
Найважливіший шов архітектури. Усі бухгалтерські проводки створюються через одну пару моделей, незалежно від типу документа.
┌─────────────────────────┐
│ TransactionModel │ (Invoice, Waybill, PayrollSlip,
│ state = "posted" │ WorkOrder, GoodsReceipt, ...)
│ business_operation FK ├──┐
└───────────┬─────────────┘ │ шаблон проводки
│ │ (Sales / Purchase / Salary / ...)
│ post_xxx() ▼
│ ┌─────────────────────────┐
│ │ BusinessOperation │
│ │ (template: rules для │
│ │ Дт/Кт + analytics) │
│ └────────────┬────────────┘
▼ │
┌─────────────────────────┐ │
│ PostingGroup │◄─────┘
│ (RegisterModel) │
│ • document_type/id │ ← backref на документ
│ • organization, ledger│
│ • amount, currency │
│ • source_signature │ ← SHA-256, ловить ручні правки
└───────────┬─────────────┘
│ 1 : N
▼
┌─────────────────────────────────────────────────┐
│ PostingEntry │
│ • account (ChartOfAccounts) │
│ • debit / credit / quantity │
│ ─── Financial Dimensions (D365-style): ─────── │
│ • partner • product • warehouse • contract │
│ • department • project • expense_item • person │
└─────────────────────────────────────────────────┘
Файли:
| Що | Файл |
|---|---|
Моделі PostingGroup + PostingEntry |
backend/essentials/models/posting.py |
Per-document пост-сервіси (post_invoice, post_payroll_slip, …) |
backend/essentials/services/posting.py |
Універсальний build з BusinessOperation шаблону |
backend/essentials/services/accounting_service.py |
| Інваріант посту/анпосту (READ THIS) | .claude/rules/document-posting.md |
Інваріант (з .claude/rules/document-posting.md)¶
Якщо
document.state == "posted", то для пари(document_type, document_id)повинна існувати щонайменше однаPostingGroup(і відповідніPostingEntry), сума яких збігається з документом.Будь-яке розходження = баг. Анпост документа = видалення
PostingGroup(cascade → entries). Репост = новийPostingGroup, не правка існуючого.
source_signature (SHA-256 від ключових полів документа на момент посту) дозволяє швидко детектувати ручне редагування проведеного документа без анпосту — кейс, який інакше веде до тихого розходження.
Financial Dimensions замість субконто¶
На відміну від класичного 1С/SAP-підходу зі стрімкими "субконто №1, №2, №3", ESWF тримає усі аналітики як окремі FK-колонки в PostingEntry. Документ при пості заповнює ті, що релевантні (наприклад, для Sales: partner + product + warehouse; для Salary: person + department + expense_item).
Деталі підходу — dop/modules/horizontal/accounting-tax/architecture.md.
7. Реєстри (Registers)¶
PostingGroup — це бухгалтерський регістр (Дт/Кт). Поряд із ним існують операційні регістри — для швидких звітів і контролю залишків без агрегації проводок.
| Регістр | App | Що зберігає | Хто пише |
|---|---|---|---|
CashJournal / CashLedger |
essentials / registers | Рух грошей по касах і рахунках | IncomingPayment, OutgoingPayment, CashTransfer |
InventoryJournal / StockTransaction |
essentials | Рух товарів по складах + партії | GoodsReceipt, GoodsShipment, GoodsWriteoff, Manufacturing |
IncomeExpenseJournal |
essentials | Доходи/витрати з аналітикою (для P&L) | усі документи з виручкою/собівартістю |
PartyLedger |
registers | Розрахунки з контрагентами | PurchaseInvoice, SalesInvoice, payments |
PlannedPaymentJournal |
essentials | План платежів (Cash Flow forecast) | PlannedPayment |
Ledger |
essentials | Загальна Главна книга (агрегати по рахунку × періоду) | derived з PostingGroup |
ItemWarehouseStock |
essentials | Поточні залишки (snapshot, не журнал) | trigger з StockTransaction |
FuelJournal / FuelLedger |
registers | Рух пального | Waybill, VehicleRefueling, FuelDrain |
DriverSalaryLedger |
registers | Нарахування ЗП водіїв | DriverSalaryAccrual |
ActivityLedger |
registers | Активності CRM (подзвоники, зустрічі) | crm.Activity |
CustomerOrderLedger |
registers | Залишки по замовленнях | crm.SalesOrder |
BlockedStockLedger |
essentials_quality | Заблокований по якості stock | QualityInspection |
ContainerLedger / StorageChargeLedger |
containerhub | Контейнери на терміналі + збори за зберігання | ContainerMovement, DemurrageCalculation |
Правило: регістри ніколи не редагуються вручну (manual_edit=False за замовчуванням). Зміна — тільки через анпост-репост документа-джерела. Виняток — поле manual_edit=True для legacy-міграцій.
8. Multi-Ledger та Consolidation¶
Multi-Ledger (multiple Charts of Accounts)¶
PostingGroup.ledger (FK на essentials.Ledger) дозволяє вести кілька паралельних книг на одну операцію:
Document (Sales Invoice)
│
post_invoice()
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
PostingGroup PostingGroup PostingGroup
ledger=NSBO ledger=IFRS ledger=Tax
(укр. стандарт) (міжнародний) (податковий)
Один документ → N груп проводок → N паралельних звітів (Trial Balance, P&L, Balance Sheet) з різних точок зору. Деталі реалізації — planning/multi-coa-implementation.md.
Consolidation (холдинг)¶
App consolidation агрегує IncomeExpenseJournal через групи компаній з еліминацією внутрішньогрупових операцій:
| Що | Файл |
|---|---|
Моделі ConsolidationGroup, ConsolidationGroupMember, IntercompanyMap |
backend/consolidation/models.py |
Сервіс compute_consolidated_pnl() |
backend/consolidation/services.py |
| Endpoint | GET /api/v1/consolidation/groups/{id}/pnl/?from&to |
MVP-обмеження (Phase F-5): 100% володіння, одна валюта, тільки транзакційна еліминація.
9. App-індекс (23 backend-апи)¶
| App | К-сть моделей | Категорія | Роль |
|---|---|---|---|
| core | infra | infrastructure | User, Tenant, AuditLog, базові класи, EntityRegistry, UniversalViewSet, TenantMiddleware |
| essentials | ~46 | horizontal · ядро | Master data + транзакції + проводки + регістри + звіти. Серце системи. |
| registers | 12 | horizontal · регістри | CashJournal, InventoryJournal, FuelJournal, PartyLedger, CustomerOrderLedger, ActivityLedger, ... |
| crm | 9 | horizontal | Lead → Deal → Quotation → SalesOrder + analytics (funnel, win-loss) |
| hrm | 4 | horizontal · F-1 | Position, Employee, PayrollPeriod, PayrollSlip |
| production | 3 | horizontal · F-2 | BOM + BOMLine + WorkOrder; idempotent complete_work_order() |
| baf_sync | 3 | horizontal · F-3 | Singleton settings + pluggable BAFTransport Protocol |
| budgeting | 2 | horizontal · F-4 | Budget + BudgetLine; compute_variance() vs IncomeExpenseJournal |
| consolidation | 3 | horizontal · F-5 | ConsolidationGroup + Member + IntercompanyMap; compute_consolidated_pnl() |
| fleet | 33 | vertical | Vehicle/Driver/Route + Waybill + Refueling + GPS + eTTN (Ed25519) |
| transport | 3 | vertical · ref | RailwayRoad, RailwayStation, Wagon |
| eswf_chat | 5 | feature | AI chat (OpenRouter) + messenger (rooms) |
| news | 3 | feature | Article, Category, Tag (глобальні) |
| shop | 8 | feature · SaaS | Product, ActivationCode, ShopOrder, License, EmailSmtpSettings |
| medoc_exchange | 2 | integration | UKTZEDDirectory, TaxInvoice (M.E.Doc) |
| sales_mobile_api | 9 | mobile | SalesVisit, CallLog, DayPlan, SalesReturn — для мобільного Sales Rep |
| mobile_api | 0 | mobile · API only | DRF endpoints для WatermelonDB sync (driver app) |
| sales_field | 0 | API only | KPI, day plans, GPS compliance — supervisor endpoints |
| media | 0 | infra | File storage config |
| eswf | 0 | infra | Project settings, root urls.py |
| essentials_quality | 5 | plugin | DefectReason, StorageLocation, QualityInspection, ReceiptFlow, BlockedStockLedger |
| containerhub | 15+ | plugin · vertical | Container, Terminal, GateTransaction, TrainArrival, DemurrageCalculation, EDI |
| logistic | — | plugin (frontend-only) | Backend-app відсутній — еталонний exclusion-case для plugin system |
Нумерація фаз (F-1..F-5) відповідає Phase F roadmap (2026-04-22). Шаблон додавання нового модуля — skill
.claude/skills/new-module/.
10. Подальше читання¶
| Тема | Документ |
|---|---|
| Backend tech stack, URL-карта, gotchas | eswf/infrastructure/backend.md |
| Архітектурний аудит (ризики, не "що є") | eswf/architecture/audit.md |
| Concept deep-dive: 15 принципів обліку | audit-concepts-2026-04-22.md |
| Financial Dimensions (D365-style) | dop/modules/horizontal/accounting-tax/architecture.md |
| План рахунків (European PCG) | dop/modules/horizontal/accounting-tax/accounting-plan.md |
| Document Operations (BusinessOperation шаблони) | dop/modules/horizontal/essentials/document-operations.md |
| Multi-CoA / Multi-Ledger дизайн | planning/multi-coa-implementation.md |
| Inventory ↔ Finance резонанс | dop/modules/horizontal/essentials/inventory-finance.md |
| Інваріанти посту/анпосту (для редагування коду) | .claude/rules/document-posting.md |
| Шаблон нового модуля | .claude/skills/new-module/SKILL.md |