Перейти до змісту

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/

Призначення

Відповідає на два питання одночасно:

  1. Валовий фінрезультат по окремій компанії — стендалон P&L для кожної Organization. Брав з IncomeExpenseJournal з фільтром organization.
  2. Чистий фінрезультат по холдингу — 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 + дочірні members inline
  • /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

  1. Знайти members групи, активні на date_to (фільтр по from_date/to_date)
  2. Завантажити всі IncomeExpenseJournal entries з tenant + organization ∈ members + date range
  3. Сагрегувати по organization + operation_type: income = sum(credit)−sum(debit), expense = sum(debit)−sum(credit)
  4. Виявити внутрішньогрупові рядки: partner (Client) має IntercompanyMap → Organization, де та Organization також у цій групі
  5. Сагрегувати суму елімінації по напрямках (seller → buyer)
  6. 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)

Типовий сценарій використання

  1. Створити ConsolidationGroup (наприклад "ACME Holding")
  2. Додати ConsolidationGroupMember записи — одна на кожну внутрішню Organization
  3. Для кожної пари "внутрішня організація A" ↔ "відповідний Client у картотеці B" — створити IntercompanyMap
  4. Підказка: endpoint /intercompany-maps/candidates/ пропонує пари за EDRPOU
  5. Відкрити звіт Чистий 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'ом — ще не додано.