Consolidation — Holding-level Financial Consolidation¶
Тип: horizontal (community, Phase F-5, 2026-04-23)
Backend app: consolidation
Frontend config: frontend/erp/src/config/consolidation.ts
API prefix: /api/v1/consolidation/
Призначення¶
Відповідає на два питання одночасно:
- Валовий фінрезультат по окремій компанії — стендалон P&L для кожної
Organization. Брав зIncomeExpenseJournalз фільтромorganization. - Чистий фінрезультат по холдингу — consolidated P&L для групи
Organization'ів, з елімінацією внутрішньогрупових оборотів (продажі між компаніями холдингу не впливають на груповий результат).
Звіт повертається однією відповіддю: колонки per-company, колонка elimination, колонка consolidated.
MVP Scope (Phase 1)¶
Обмеження, прийняті свідомо для MVP:
- 100% володіння — мінори (minority interest) не обчислюються
- Одна валюта — члени групи в одній валюті. Мультивалютність на пізніше
- Транзакційна елімінація лише — unrealized profit in inventory (якщо A продала B з націнкою, а B ще не продала зовні) НЕ елімінується. Цей кейс — окремий наступний етап
- Період = date range — довільний діапазон дат (зазвичай місяць/квартал/рік)
Deferred кейси — в ## 🔮 Deferred нижче.
Моделі¶
ConsolidationGroup (MasterData)¶
Визначення холдингу. Набір Organization'ів, які консолідуються разом.
Поля: name, code, base_currency (reporting currency), parent_organization (informational top-level entity).
ConsolidationGroupMember¶
Належність Organization до Group з часовими межами (from_date / to_date) і часткою володіння (ownership_pct — MVP ігнорує, завжди 100).
Унікальність: (tenant, group, organization).
IntercompanyMap¶
Мапа Client ↔ внутрішня Organization. Коли інвойс виставлено на цього клієнта, і клієнт мапиться на іншу організацію з того ж холдингу — оборот елімінується.
Один Client → одна Organization (OneToOne). Ідентифікація через мапу, не через EDRPOU (явно надійніше).
Endpoints¶
Standard CRUD¶
/groups/—ConsolidationGroup+ дочірніmembersinline/group-members/— окремий ресурс для редагування належності/intercompany-maps/— окремий ресурс для мапи
Report¶
GET /groups/{id}/pnl/?from=YYYY-MM-DD&to=YYYY-MM-DDОбчислює:{ "group": {"id": 1, "name": "My Holding", "base_currency_code": "UAH"}, "period": {"from": "2026-01-01", "to": "2026-12-31"}, "per_company": [ {"organization_id": 1, "organization_name": "Company A", "income": 100000, "expense": 60000, "profit": 40000}, {"organization_id": 2, "organization_name": "Company B", "income": 80000, "expense": 50000, "profit": 30000} ], "elimination": { "income": 30000, "expense": 30000, "profit_impact": 0, "pairs": [{"from_organization_id": 1, "from_organization_name": "A", "to_organization_id": 2, "to_organization_name": "B", "operation_type": "income", "amount": 30000}] }, "consolidated": {"income": 150000, "expense": 80000, "profit": 70000} }
Helper (advisory)¶
GET /intercompany-maps/candidates/— пропонує париClient ↔ Organizationза збігом EDRPOU. Користувач вирішує, чи створювати мапінг. Не використовується автоматично.
Алгоритм compute_consolidated_pnl¶
- Знайти
membersгрупи, активні наdate_to(фільтр поfrom_date/to_date) - Завантажити всі
IncomeExpenseJournalentries зtenant + organization ∈ members + date range - Сагрегувати по
organization + operation_type: income = sum(credit)−sum(debit), expense = sum(debit)−sum(credit) - Виявити внутрішньогрупові рядки:
partner(Client) маєIntercompanyMap → Organization, де та Organization також у цій групі - Сагрегувати суму елімінації по напрямках (seller → buyer)
- Consolidated = Σ(per-company) − elimination
Сервіс — чиста функція, легко тестується.
Frontend¶
Section code: consolidation (в Sidebar)
appCode: appConsolidation
Підрозділи:
- Налаштування → Структура — CRUD на consolidationGroups, consolidationGroupMembers, intercompanyMaps (через стандартні MasterDataPage)
- Аналітика → Звіти → Чистий P&L холдингу — ConsolidatedPnlReport React-компонент (components/Essentials/Consolidation/ConsolidatedPnlReport.tsx)
Типовий сценарій використання¶
- Створити
ConsolidationGroup(наприклад "ACME Holding") - Додати
ConsolidationGroupMemberзаписи — одна на кожну внутрішню Organization - Для кожної пари "внутрішня організація A" ↔ "відповідний Client у картотеці B" — створити
IntercompanyMap - Підказка: endpoint
/intercompany-maps/candidates/пропонує пари за EDRPOU - Відкрити звіт
Чистий P&L холдингу, обрати групу і період → одразу видно валовий по кожній компанії + чистий по холдингу
🔮 Deferred¶
- Unrealized profit in inventory — якщо A продала Б товар з націнкою, а Б ще не продав зовнішньому покупцю, прибуток A — фіктивний на рівні холдингу. Потребує трекінгу intercompany-markup у запасах
- Minority interest — якщо холдинг володіє <100% дочірньої компанії, залишок результату приписується міноритарним акціонерам. Потребує окремого рядка в consolidated звіті
- Мультивалютність — члени в різних валютах. Треба
ExchangeRateна дату звіту (average для P&L, closing для Balance Sheet) - Consolidated Balance Sheet — аналогічно P&L, але для балансу. Елімінуються внутрішньогрупові заборгованості (Дт Receivable vs Кт Payable)
- Proportional consolidation — joint ventures (часто 50%)
- Equity method — асоційовані компанії (20-50%, без консолідації рядків, лише доля в прибутку через equity)
- Intercompany reconciliation report — показує непарні рядки (A виставила на B, але B ще не провів)
Status¶
MVP / Phase 1 — завершено 2026-04-23. Моделі, сервіс, endpoints, frontend компонент працюють. Міграція 0001 застосована. Swagger endpoints видимі. Тестові дані seed'ом — ще не додано.