Manual JournalEntry — ручні проводки¶
Статус: ✅ Реалізовано (MVP, 2026-04-22)
Бухгалтер / фінансовий контролер може вручну зробити будь-яку валідну проводку Дт/Кт без створення документа-джерела. Використовується для коригуючих проводок, вступних залишків, місячних/річних закриттів, переоцінок, переносів між рахунками.
Призначення¶
Документ-джерело (Invoice, GoodsReceipt, …) автоматично генерує PostingGroup через BusinessOperation-шаблон. Але є випадки, коли проводка потрібна поза цим flow:
- помилка минулого періоду — потрібна сторнуюча проводка;
- вступні залишки при міграції з іншої системи;
- місячні корекції резервів, амортизації, нарахувань;
- переоцінка валютних активів;
- розподіл загальногосподарських витрат за фінансовими вимірами;
- закриття періоду / року;
- проводки, що не мають первинного документа в системі (наприклад, страхове відшкодування, рішення суду);
- зарплата без модуля HRM — згорнутими проводками (нарахування + утримання + виплата) без per-employee розкладки. Див. розділ нижче.
JournalEntry дозволяє створити таку проводку напряму, з валідацією Σ Дт = Σ Кт перед проведенням.
Модель даних¶
JournalEntry (TransactionModel)¶
| Поле | Тип | Опис |
|---|---|---|
number |
str | Номер документа (як у решти TransactionModel) |
date |
date | Дата проводки (потрапляє у PostingGroup.date) |
state |
enum | draft / posted / marked |
organization |
FK Organization | Опціонально, для поділу по юр. особах |
currency |
FK Currency | Валюта групи (record-keeping; реальні суми в Decimal) |
description |
text | Опис документа (потрапляє у PostingGroup.description) |
JournalEntryLine (TenantAwareModel)¶
| Поле | Тип | Опис |
|---|---|---|
entry |
FK JournalEntry | Посилання на header |
account |
FK ChartOfAccounts | Рахунок (PROTECT — не видалити, якщо є посилання) |
debit |
Decimal | Сума Дт (взаємовиключно з credit) |
credit |
Decimal | Сума Кт |
description |
str | Опис рядка (опціонально) |
partner |
FK Client | Аналітика — контрагент |
project |
FK BusinessDirection | Аналітика — напрямок |
department |
FK Department | Аналітика — підрозділ |
expense_item |
FK ExpenseItem | Аналітика — стаття витрат |
При проведенні всі аналітики копіюються 1:1 у відповідні PostingEntry.
Сервіси¶
essentials/services/journal_entry.py
post_journal_entry(je) → PostingGroup¶
- Завантажує всі
JournalEntryLineзselect_relatedдля аналітик. - Перевіряє
Σ debit == Σ credit— інакше підіймаєJournalEntryNotBalanced. - Idempotent: видаляє попередню
PostingGroupзdocument_type='JournalEntry', document_id=je.pk, щоб повторне проведення не дублювало. - Створює нову
PostingGroup(amount=Σ debit,manual_edit=True,document_type='JournalEntry'). - Створює один
PostingEntryна коженJournalEntryLineз усіма аналітиками.
unpost_journal_entry(je) → int¶
Видаляє PostingGroup (та всі PostingEntry через CASCADE). Повертає кількість видалених рядків. Виклик автоматично з ViewSet при unpost_document.
get_posting_group(je) → Optional[PostingGroup]¶
Повертає поточну PostingGroup, якщо JE проведений.
API¶
| Метод | URL | Опис |
|---|---|---|
GET/POST |
/api/v1/essentials/journal-entries/ |
CRUD header |
PATCH/DELETE |
/api/v1/essentials/journal-entries/{id}/ |
|
GET/POST/PATCH/DELETE |
/api/v1/essentials/journal-entry-lines/ |
CRUD рядків |
POST |
/api/v1/essentials/journal-entries/{id}/post_document/ |
Провести (валідує + створює PostingGroup; 400 якщо unbalanced) |
POST |
/api/v1/essentials/journal-entries/{id}/unpost_document/ |
Відмінити проведення |
Серіалізатор повертає додаткові поля: total_debit, total_credit, is_balanced, account_code/name, partner_name, project_name тощо.
Frontend¶
components/Essentials/JournalEntry/JournalEntryPage.tsx
Дві view:
Список (без :id у URL):
- Таблиця: Number · Date · Description · Amount · State · Balanced (✓/⚠).
- Клік на рядок → відкриває form.
- Кнопка «Нова проводка».
Форма (/essentials/.../journalEntries/{id} або new):
- Header: Number · Date · Organization · Currency · Description.
- Таблиця рядків: Account · Debit · Credit · Description · ✕ (видалити).
- Footer: Σ Debit, Σ Credit, дисбаланс (підсвічено червоним якщо ≠ 0).
- Кнопки:
- Save — зберегти header + sync рядків (PATCH/POST/DELETE кожного рядка проти існуючих).
- Post — увімкнено лише якщо balanced; викликає /post_document/.
- Unpost — для posted-документа; викликає /unpost_document/ і повертає в draft.
- Posted-стан робить усі поля read-only (треба unpost щоб редагувати).
Зареєстровано в config/essentials.ts як journalEntries (ItemType.TRANSACTIONDATA, plugin: "accounting"), у групі Operations & Postings поряд з documentOperations.
Workflow¶
[draft] редагується вільно, рядки додаються/видаляються
│
│ POST /post_document/
│ ─ валідація Σ Дт = Σ Кт
│ ─ створення PostingGroup + PostingEntry × N
▼
[posted] read-only; PostingGroup присутній у регістрах і звітах
│
│ POST /unpost_document/
│ ─ видалення PostingGroup
▼
[draft] знову редагується
Покриття тестами¶
backend/essentials/tests/test_journal_entry_and_fixed_asset.py — TestPostJournalEntry, TestJournalEntryEndpoint:
- balanced entry створює
PostingGroupзamount=Σ debitі двомаPostingEntry; - unbalanced entry підіймає
JournalEntryNotBalanced; - repost замінює попередню групу (idempotent);
- unpost видаляє групу;
- API
/post_document/повертає 200 для balanced, 400 для unbalanced; - після post —
je.state == 'posted'.
Відмінність від DocumentOperation¶
| Аспект | DocumentOperation |
JournalEntry |
|---|---|---|
| Призначення | Шаблонна групова проводка з заздалегідь визначеними BusinessOperation-парами |
Вільна одиночна проводка без шаблону |
| Лінії | Кожна лінія — посилання на BusinessOperation (Дт/Кт визначаються шаблоном) |
Кожна лінія — явний account + debit/credit |
| Use-case | "Видача готівки касою": завжди Дт 5310 / Кт 5311 | "Сторно дублюючої накладної №42 від 2026-01-15" |
| Аналітики | Залежать від BusinessOperation |
Вказуються вручну на кожному рядку |
Обидва записують у PostingGroup + PostingEntry, тому в звітах виглядають однаково.
Use-case: зарплата без модуля HRM¶
Якщо HRM-модуль не встановлено (community-збірка / ранній клієнт без потреби в per-employee обліку), зарплата відбивається згорнутими проводками через JournalEntry або шаблонний DocumentOperation — за аналогією з 1С «Бухгалтерська довідка» / SAP «FI Document» / Odoo «Miscellaneous Operation».
Типовий flow за місяць (UA, спрощено):
| # | Проводка | Дт | Кт | Сума |
|---|---|---|---|---|
| 1 | Нарахування ЗП | 92 / 93 / 23 (за центром витрат) | 661 «Розрахунки з оплати праці» | gross |
| 2 | Утримання ПДФО (18%) | 661 | 6411 «ПДФО» | gross × 0.18 |
| 3 | Утримання військового збору (1.5%) | 661 | 6412 «Військовий збір» | gross × 0.015 |
| 4 | Нарахування ЄСВ роботодавцем (22%) | 92 / 93 / 23 | 6510 «ЄСВ» | gross × 0.22 |
| 5 | Виплата net на картку | 661 | 311 «Поточний рахунок» | net |
| 6 | Сплата ПДФО / ВЗ / ЄСВ | 6411 / 6412 / 6510 | 311 | відповідні суми |
Кроки 1-4 — один JournalEntry (4 рядки). Кроки 5-6 — окремі OutgoingPayment (касовий/банківський flow), або теж згорнутий JE, якщо банк-виписка ще не імпортована.
Аналітики на рядках: department, expense_item, project — для P&L drill-down. Контрагент (partner=Person) можна не заповнювати — це і є «згорнутість».
Що втрачається vs HRM: - ❌ Per-employee облік → залишок на 661 не розкладається по співробітниках. - ❌ Розрахункові листи / друкована форма для працівника. - ❌ Авто-розрахунок ставок (треба самому рахувати gross/tax/net). - ❌ Bulk-операції (один JE на місяць замість N slip-документів).
Що залишається доступним:
- ✅ P&L / BalanceSheet / TrialBalance — коректно показують 661/6411/6412/6510 та витрати на 92/93/23.
- ✅ CashFlow Direct Method — виплати потрапляють у «Грошові потоки від операційної діяльності».
- ✅ PartyLedger — можна вести по Person (контрагент-фізособа), якщо заповнити partner на рядку.
Коли переходити на HRM: перший клієнт, що вимагає (a) per-employee розкладки залишку 661, (b) автоматичного 4-leg розрахунку UA-податків, (c) друкованої форми розрахункового листа, (d) інтеграції з табелем робочого часу. Див. hrm-payroll/README.md.
🔮 Deferred / Ideas¶
Шаблони ручних проводок¶
Мотивація: часто повторювані ручні проводки (напр. місячне нарахування резерву) дублюються вручну.
Чому відкладено: для типових сценаріїв є DocumentOperation з BusinessOperation — він закриває цю потребу.
Trigger: запит від користувача з конкретним сценарієм, який не вписується у DocumentOperation-flow.
Multi-currency lines¶
Мотивація: проводка з валютними рядками (наприклад, переоцінка USD-залишку). Чому відкладено: MVP оперує однією валютою на JE; складніше — окрема ітерація разом із dual-currency PostingEntry. Trigger: перший випадок переоцінки валютного активу клієнтом.
Reverse-on-date scheduling¶
Мотивація: автоматичний контр-сторно через N місяців (для нарахувань резервів). Чому відкладено: потребує task-scheduler (Celery/Cron). MVP — лише ручне сторнування. Trigger: поява потреби у регулярних reverse-проводках.