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

Концептуальний аудит — наскрізні принципи обліку

Жанр: не gap-аналіз окремих фіч (це у audit-2026-04-22 §7-§8), а архітектурні концепції верхнього рівня, які пронизують усі модулі і визначають саму модель обліку.

Для кого: для переосмислення підходу. Якщо щось здається дивним — можливо це 1С-звичка, яку SAP/Dynamics давно подолали (або навпаки — 1С-зручність, якої їм бракує).

Як читати: не для імплементації. Для розуміння, чому колись доведеться вийти за рамки «модель + ViewSet + post_document». Кожна секція — окремий концепт, можна зупинитись на будь-якому.


Зміст

  1. Open Items та Clearing — чому SAP не довіряє paid_amount
  2. Period Close — ритуал замість одного прапорця
  3. Document Splitting — коли одна проводка стає десятьма
  4. Financial Dimensions з validation rules — чому не достатньо FK
  5. Material Master views — один Item, багато облікових облич
  6. Multi-Ledger Accounting — IFRS + UA + Tax окремо
  7. Cost Centers + Cost Allocation — звідки беруться накладні витрати на товарах
  8. Standard Cost vs Actual Cost variance — у чому помилився виробничий план
  9. Withholding Tax engine — чому одного tax_rate_pct мало
  10. Posting Rules engine — як SAP знає, на який рахунок проводити
  11. Document Workflow / Approval — engine замість прапорця status='approved'
  12. Inter-Company та Multi-Org — коли одна транзакція стосується двох юр. осіб
  13. Storno vs negative entries — два підходи до сторнування
  14. Drill-down audit trail — чому регістри без UI назад до документа марні
  15. Summary: 1С-mindset vs SAP/Dynamics-mindset

1. Open Items та Clearing

Метафора: платіж — це ключ, який «відмикає» конкретний інвойс. У SAP вони лежать у різних шухлядах, поки клерк руками не покладе ключ у замок.

Як це у SAP/Dynamics

В SAP FI кожен рядок проводки на партнерському рахунку (4100 для AR) має статус Open або Cleared. Платіж не «зменшує суму на рахунку клієнта». Він створює окремий проводочний рядок зі статусом Open + Clearing Document, який «зв'язує» цей платіжний рядок з інвойсним рядком — обидва переходять у статус Cleared.

Invoice INV-001 amount=10000  →  PostingEntry: Dt 4100 / Ct 7000   STATUS=Open
Payment PAY-001 amount=6000   →  PostingEntry: Dt 5310 / Ct 4100   STATUS=Open
Clearing Doc CLR-001 links INV-001 row + PAY-001 row → both STATUS=Cleared,
   залишок INV-001 = 4000 (Open Item)

Що це дає: - Можеш сказати «оце саме той платіж закрив саме той інвойс». - Якщо платіж надійшов «знеособлений», він лежить як Unclearted → Bank Reconciliation flow вимагає від клерка allocate його до інвойсу. - AR aging report = list of Open Items з date_overdue буцет.

Як це у 1С (і у DOP зараз)

Invoice.paid_amount — агрегатне поле. Платіж «зменшує» цю суму. Якщо клієнт переплатив — це окремий тип проводки. Якщо платіж надійшов без референсу — IncomingPayment створюється з порожнім invoice FK і живе як хитрий «advance payment».

# DOP
class Invoice(TransactionModel):
    paid_amount = Decimal(...)  # агрегат, не open items

class IncomingPayment(TransactionModel):
    invoice = FK(Invoice, null=True, blank=True)  # один FK, не many-to-many через clearing

Жодної концепції OpenItem / ClearingDocument не існує.

Що ламається без Open Items

  • Bank Reconciliation — без Open Items неможливо коректно: «у виписці прийшов платіж 6000, до якого з 5 інвойсів його прив'язати?». DOP зараз пропонує IncomingPayment.invoice як один FK = одна payment може закрити лише один інвойс. Реальність: один платіж 50 000 ₴ може закрити 7 інвойсів по 6000 + аванс 8000.
  • AR aging buckets — без Open Items aging показує сумарну заборгованість контрагента, а не конкретні overdue інвойси. Менеджер не знає «який саме інвойс минув 90 днів».
  • Partial payments — у DOP paid_amount = 6000 приховує: чи це частковий платіж по INV-001, чи повний по INV-002. SAP бачить рядки.

Як можна перейти

Замість Invoice.paid_amount (агрегат): 1. Додати payment_allocation модель: (payment_entry, invoice_entry, amount). 2. Invoice.open_amount обчислюється на льоту через total - sum(payment_allocations.amount). 3. UI: при створенні IncomingPayment без явного інвойсу — клерк бачить список Open Items контрагента і allocate платіж порядково. 4. AR aging — WHERE invoice.id NOT IN (SELECT invoice_id FROM payment_allocation WHERE amount=invoice.total).

Trigger: перший клієнт зі складною AR (B2B з рамковими договорами + advance payments + partial payments).


2. Period Close як ритуал

Метафора: закриття місяця — не «натиснули прапорець», а процедура з 12 кроків, кожен з яких має бути виконаний у правильному порядку.

Як це у SAP / Dynamics

Місячне закриття — окрема Period Close Cockpit сторінка зі списком задач:

1. Stop posting in Sub-Ledgers (AR, AP, Asset) for prior period
2. Run FX Revaluation для валютних рахунків
3. Run Goods Receipt/Invoice Receipt clearing (GR/IR analysis)
4. Run Depreciation Run для всіх активів
5. Run Recurring Entries (orders, accruals)
6. Run Allocations (cost-center → product allocation)
7. Run Settlement (внутрішні замовлення → постоянні рахунки)
8. Reconcile Sub-Ledger to General Ledger (balance check)
9. Close period in Sub-Ledgers
10. Run Trial Balance for review
11. Close period in General Ledger
12. Open new period

Кожен крок — окрема UI-сторінка зі статусом (queued / running / completed / failed) + log. Якщо крок 4 впав — крок 5 заблокований до виправлення. Усе це idempotent — повторний запуск чистить попередній результат.

Як це у DOP

Один прапорець EssentialsModuleSettings.posting_lock_date. При спробі провести документ з датою <= lock_date — exception. Жодного closing-flow не існує:

  • Депреціація — окремий DepreciationRunner UI-кнопка, не пов'язана з закриттям.
  • FX revaluation — взагалі ❌ NONE.
  • GR/IR clearing — ❌ NONE (тригерить через 3-way matching, але не на момент закриття).
  • Sub-Ledger reconciliation — ❌ NONE (PartyLedger ↔ ChartOfAccounts sync робиться signal-функціями миттєво, не як reconciliation step).
  • Recurring entries — ❌ NONE.

Що ламається без period close-flow

  • Аудит за квартал. Аудитору треба «знімок системи на 31 березня». DOP не зберігає snapshot — кожен звіт обчислюється «зараз», з урахуванням пізніших проводок (наприклад, корекційних, що були заведені у квітні з датою у березні до lock_date встановлення).
  • Фінансовий контроль. Без recurring entries (наприклад, місячне нарахування резервів сумнівних боргів) кожен бухгалтер робить це руками + забуває.
  • Multi-step coordination. Якщо депреціація залежить від FX revaluation (валютний актив) — у DOP це не enforced. Можна провести депреціацію перед FX, отримати неправильні цифри.

Як можна перейти

  1. Модель PeriodCloseRun(year, month, status) + PeriodCloseStep(run, step_code, status, log).
  2. Catalog of step-handlers — кожен step = функція з name, dependencies, idempotent.
  3. UI: list of steps + run-button per step + sequential auto-run (з блокуванням наступного, якщо попередній failed).
  4. Перший набір steps:
  5. fx_revaluation
  6. depreciation_run
  7. recurring_entries (поки порожньо)
  8. partyledger_recheck (диф з ChartOfAccounts)
  9. trial_balance_export
  10. lock_period

Trigger: перший клієнт з multiple legal entities, перший аудит, або перший фінансовий контролер на повну ставку.


3. Document Splitting

Метафора: «У ВАС одна проводка на рахунок 6240 на 100 000 ₴? Покажіть мені 100 000 ₴ окремо для проєкту А, проєкту Б, департамента Москва і Київ — інакше я не зможу побудувати P&L per project».

Як це у SAP

В SAP New GL працює Document Splitting — при кожній проводці система дублює її по фінансовим вимірам. Простий приклад:

Інвойс на 100 000 ₴ за послугу (один рахунок, один партнер). Аналітика: проєкт А — 60 000, проєкт Б — 40 000.

Як це проводиться:

Без splitting:                 Зі splitting:
Dt 6240 / Ct 4000  100 000     Dt 6240 / Ct 4000  60 000  project=A
                               Dt 6240 / Ct 4000  40 000  project=B

Це навіть якщо проводка на 4000 (партнерський рахунок!) з одним постачальником. SAP розщеплює партнерську проводку, щоб у звіті по project=A залишок на 4000 був 60 000 (за умови що вся ця сума оплачена окремо).

Як це у DOP

DOP PostingEntry має поля partner / project / department / expense_item / person — це financial dimensions. Ми могли б записувати кілька entries на той самий рахунок з різними dimensions. Але:

  1. Інтерфейс цього не пропонує: Invoice.lines має одне поле expense_item на лінію — для розщеплення треба ламати лінію на кілька.
  2. Document Operation flow не розщеплює партнерську сторону: одна проводка Dt 4100 / Ct 7000 за весь Invoice, незалежно від кількості ліній.
  3. PartyLedger має одну позицію за весь Invoice (не за лінію).

Що ламається

  • P&L by project не показує реальну картину — бо проводки на партнерські рахунки (5310, 4000, 4100) не розщеплено.
  • Балансу по проектам скласти неможливо. Можна побудувати лише P&L (по 6/7 рахункам).
  • Cost-center accounting — теж недосяжне без splitting.

Як можна перейти

Простіший підхід (без класичного SAP splitting): - При post-постингу проводки на партнерський рахунок — створювати кілька PostingEntry, по одному на dimension-комбінацію, проportional до сум на P&L-стороні. - Це робиться в posting.py як post-processing крок після стандартної проводки.

Складніший (як SAP): - Окремий DocumentSplittingRule engine з конфігураціями: для якого рахунку розщеплювати по якому виміру з якою логікою.

Trigger: запит CFO на «P&L per project including partner balances» або повний balance per legal entity.


4. Financial Dimensions з validation rules

Метафора: не «всі виміри незалежні», а «у нас є дозволені комбінації».

Як це у Dynamics 365

В Dynamics 365 F&O Financial Dimensions — окрема концепція з 3 типами: 1. Custom dimensions — як у DOP (BusinessDirection, Department). 2. Entity-backed dimensions — кожна довідкова сутність може стати виміром (Customer, Vendor, Item). 3. Account structure — визначає які виміри і в якому порядку обов'язкові для рахунків.

Account Structure "Operations":
  Account (mandatory) → Department (mandatory) → Project (optional) → Activity (optional)

Account Structure "Balance Sheet":
  Account (mandatory) → no dimensions

Validation rules:
- Account 6240 → потребує Department + Project
- Department 'IT' → допускає Project лише з категорії 'Internal'
- Customer 'Acme' → не може йти разом з Project 'Marketing-Q1'

При проведенні проводки — система перевіряє валідну комбінацію через Account Structure. Якщо клерк ввів Account=6240, Department=NULL — посту блокується.

Як це у DOP

Усі виміри незалежні і всі опціональні:

# PostingEntry
partner = FK(Client, null=True, blank=True)
project = FK(BusinessDirection, null=True, blank=True)
department = FK(Department, null=True, blank=True)
expense_item = FK(ExpenseItem, null=True, blank=True)

Validation rules — ❌ NONE. Можна провести 6240 без department, потім дивуватись чому P&L по департаментам не сходиться.

Що ламається

  • Звіти по dimensions показують «без значення» категорію, що часто більша за всі названі разом.
  • Контролінг неможливий: будь-яку проводку можна зробити «без проекту».
  • Compliance — для UA податкового обліку деякі рахунки мають мати department/cost-center.

Як можна перейти

  1. AccountDimensionRule(account_pattern, dimension_field, requirement) — мапіт regex на рахунок → which dimensions mandatory.
  2. Validation у PostingEntry.clean() (перевірка перед save).
  3. UI: при виборі account у формі — підсвічуються які виміри стають обов'язковими.

Trigger: перший фінансовий контролер скаржиться на «дірявий P&L per project».


5. Material Master views

Метафора: товар — не одна табличка, а анкета з 8 розділами, кожен з яких заповнює свій департамент.

Як це у SAP MM

Material Master — одна сутність з ~10 views (areas):

View Хто заповнює Що містить
Basic Data Master Data team name, weight, dimensions, base UoM
Sales: Sales Org Data Sales sales price, customer-group discount, delivery plant
Purchasing Purchasing purchasing group, lead time, default vendor, MOQ
MRP 1/2/3/4 Planning MRP type, reorder point, planning horizon, lot size, safety stock
Storage Warehouse storage conditions, storage location, max stock level
Forecasting Planning forecast model, history weights, alpha/beta
Quality QC team inspection setup, frequency, sampling rules
Accounting Finance valuation class (→ posting account!), price control (Standard/Moving avg)
Costing 1/2 Costing costing variant, overhead group, profit margin %, target prices
Plant data / Storage per plant/warehouse per-warehouse settings

Кожен view має свій набір полів за організацію + завод + warehouse + sales-org. Тобто Item «вода» в Києві має один цикл MRP, а в Львові — інший.

Як це у DOP

Item — одна модель з ~20 полями. Усі дані «глобальні» по tenant — немає per-organization, per-warehouse, per-sales-org views.

class Item(MasterDataModel):
    name, code, description, unit, item_type, ...
    # all fields global per tenant

Цінова політика винесена в ItemPrice (per price_type), що частково аналогічно Sales view. Але: - Немає Purchasing view (default vendor, MOQ, lead time). - Немає MRP view (reorder point, safety stock). - Немає per-organization Accounting view (valuation class → account mapping). - Немає per-warehouse storage settings.

Що ламається

  • Multi-organization scenarios: одна юр.особа купує товар у валюті, інша — в гривні. Без per-org views це неможливо коректно вести.
  • Warehouse-specific settings: основний склад тримає 100 одиниць, регіональний — 10. Без per-warehouse settings немає reorder logic.
  • Posting account derivation: SAP визначає account автоматично через Valuation Class на Item × Account Determination tables. У DOP — рахунок задається у BusinessOperation, єдиний для всіх товарів.

Як можна перейти

Поетапно (без SAP-overkill): 1. ItemAccountingView(item, organization, valuation_class, account_inventory, account_cogs, account_revenue) — мінімум для multi-org. 2. ItemPurchasingView(item, default_vendor, lead_time_days, moq) — для MRP. 3. ItemWarehouseSettings(item, warehouse, reorder_point, safety_stock, max_level) — для inventory.

Поки modular, без єдиного шторму.

Trigger: другий tenant з multi-organization (різні юр.особи з різними рахунками).


6. Multi-Ledger Accounting

Метафора: одна транзакція → 3 окремі бухгалтерії: «для податкової», «для акціонерів (IFRS)», «для управлінців».

Як це у SAP S/4HANA

В SAP S/4HANA з'явилась концепція Ledger — окремий рахунковий план + правила проводок:

Ledger 0L (Leading) — IFRS, основний для consolidation
Ledger 2L           — UA-tax, ставки амортизації за податковим кодексом
Ledger 3L           — Local GAAP (для статистики)
Ledger 4L           — Management (внутрішня модель витрат)

Кожна проводка створюється одночасно у всіх ledgers, але з різними правилами: - Ledger 0L: депреціація лінійна за IFRS, 5 років. - Ledger 2L: депреціація прискорена за UA-податковим, 3 роки. - Ledger 3L: депреціація лінійна за UA-ПСБО, 7 років.

Звіт «P&L for ledger 2L» побудує податковий результат, для 0L — IFRS-результат, з тих самих первинних документів.

Як це у DOP

❌ NONE. Один ChartOfAccounts, один PostingGroup. Якщо нам треба IFRS і UA-tax одночасно — треба буде або: 1. Вести два DOP-tenants (дубль усіх документів). 2. Або робити коригуючі JournalEntry руками щомісяця.

Жодного автоматичного "опс-у-нас-три-копії" немає.

Що ламається

  • Multi-GAAP reporting: міжнародний холдинг звітує IFRS аудиторам + UA податковій + керівництву (management accounting). DOP не надає цього.
  • Tax depreciation — повторно з §7.6/Fixed Assets (F-8). Якщо для bookkeeping ставка одна, для податку — інша, без multi-ledger це костилі.
  • Currency translation для consolidation — без leading-ledger у звітній валюті теж проблематично.

Як можна перейти

  1. Додати Ledger модель: (code, name, currency, is_leading).
  2. До PostingGroup додати FK на Ledger.
  3. BusinessOperation — окремі шаблони per ledger.
  4. Звіти приймають ?ledger=2L параметр.

Робота важка, але не «переписати все»: достатньо додати окремий рівень над існуючою механікою.

Trigger: запит на UA податкову окремо (тобто реалізація другого ledger під tax) або клієнт з IFRS-вимогами.


7. Cost Allocation

Метафора: «оренда офісу 30 000 ₴ на рахунку 6130 — це не витрати компанії, це витрати проектів, у пропорції до людино-годин».

Як це у SAP CO / Dynamics F&O

Cost Allocation — окремий процес у Controlling:

Step 1: Збираємо реальні витрати на cost-center "Office":
  Dt 6130 (Rent) Cost-Center=Office  30 000

Step 2: Визначаємо ключ розподілу — "проекти, людино-години за місяць"
  Project A: 120 hours
  Project B: 60 hours
  Project C: 40 hours
  Total:    220 hours

Step 3: Запускаємо Allocation Cycle:
  Project A отримує: 30000 × 120/220 = 16363.64
  Project B:                30000 × 60/220 = 8181.82
  Project C:                30000 × 40/220 = 5454.55

Step 4: Створюються алокаційні проводки:
  Cost-center Office → Project A: 16363.64
  Cost-center Office → Project B: 8181.82
  Cost-center Office → Project C: 5454.55

В балансі/P&L виглядає так: основна проводка зменшується (з allocation-cycle), а суми «летять» по проєктам.

Як це у DOP

❌ NONE. Якщо ми проводимо оренду — вона залишається на одному cost-центрі. Per-project P&L не може вивести «частку оренди».

Що ламається

  • Реальна project margin. Менеджер проєкту бачить пряму собівартість (зарплата команди + матеріали), але не накладні (оренда, керівництво, IT). Реальна маржа спотворена.
  • ABC costing — взагалі неможливе.
  • Service-based business (агенція, консалтинг): без allocation cost-of-services не порахуєш.

Як можна перейти

  1. AllocationCycle модель: (name, source_account, source_dimension, target_dimension_field, allocation_basis).
  2. AllocationBasis — окрема модель: «hours-by-project», «square-meters-by-department», «headcount-by-cost-center».
  3. Service run_allocation(cycle, period) — обчислює, створює PostingGroup з reverse + redistribute.

Trigger: service business з кількома project lines + бажання знати реальну project margin.


8. Standard vs Actual Variance

Метафора: план каже «1 кг борошна = 10 ₴», виробництво списує по факту 12 ₴, і різниця 2 ₴ — це окрема проводка, яку треба пояснити.

Як це у SAP CO-PA / 1С:ERP

У виробництві з нормативною собівартістю (Standard Cost): - BOM каже: «1 торт = 2 кг борошна × 10 ₴ + 0.5 кг цукру × 20 ₴ = 30 ₴» - Фактично списано: 2 кг × 12 + 0.5 × 22 = 35 ₴ - Variance = 5 ₴ → окрема проводка Dt Variance / Ct Cost-of-Production

Variance ділиться на: - Price variance — куплено дорожче ніж планували. - Quantity variance — використали більше ніж BOM каже. - Mix variance — замінили борошно №1 на борошно №2 з іншою ціною.

Кожен variance — окремий рядок у звіті. Виробничий контролер бачить «торт собівартість 35 ₴ замість планових 30 ₴, з них 3 ₴ через якіcну зміну сировини, 2 ₴ через перевитрату».

Як це у DOP

DOP Production MVP взагалі без costing (unit_cost=1 placeholder). Навіть якщо ми реалізуємо real FIFO costing — variance окремо не виділяється. Усе йде в єдину проводку.

Що ламається

  • Виробничий контролінг. Без variance-проводок неможливо проаналізувати чому собівартість зросла.
  • Pricing decisions. Якщо собівартість тортика плавно росте від 30 до 50 — без variance-розкладки не зрозуміти, де косячиться.

Як можна перейти

  1. BOM має поле standard_cost_per_unit (фіксується при створенні версії).
  2. complete_work_order обчислює:
  3. planned_cost = standard × qty
  4. actual_cost = sum через FIFO
  5. variance = planned − actual
  6. Створює окремий PostingEntry Dt VarianceAccount / Ct WIP для variance суми.

Trigger: перший виробничий клієнт з регулярним аналізом маржі.


9. Withholding Tax engine

Метафора: одна виплата = 3-4 окремі податки з різними ставками, базами і одержувачами. Кожен має проводку.

Як це у Dynamics F&O

Withholding Tax engine:

Tax Code: PDFL_18
   Calculation base: gross
   Rate: 18%
   Account payable: 4421
   Account expense: NULL (це утримання, не нарахування)
   Receiver: ДПС

Tax Code: MILITARY_15
   Calculation base: gross
   Rate: 1.5%
   Account payable: 4422
   ...

Tax Code: ESV_22_EMP
   Calculation base: gross
   Rate: 22%
   Account payable: 4427
   Account expense: 6450 ← ЦЕ нарахування, не утримання
   ...

При нарахуванні зарплати engine: 1. Бере gross. 2. Йде по всіх tax codes, applicable до payroll. 3. Для кожного: tax_amount = base × rate, створює окремий PostingEntry. 4. Net = gross − sum(withholdings).

Один payroll slip → 8-12 PostingEntry, не 4. Кожен tax — окремий рядок у звіті.

Як це у DOP

PayrollSlip.tax_rate_pct — один прапорець. У §2.3 я це позначив як placeholder. Withholding tax engine — ❌ NONE.

Те ж саме для: - VAT — є TaxRate модель і поле в InvoiceLine, але engine окремий від загальної концепції. - WHT при виплаті дивідендів / роялті / процентів нерезидентам — ❌ NONE.

Що ламається

  • UA payroll — з одним прапорцем неможливий (повторно).
  • WHT для нерезидентів (15% з роялті, 5% з дивідендів — UA-tax) — без engine ці сценарії робляться руками через JournalEntry.

Як можна перейти

TaxCode модель з: - code (PDFL_18, ESV_22_EMP, ...) - calculation_base (gross / net / specific_field) - rate - payable_account, expense_account (опціонально) - applies_to (payroll, payment_to_nonresident, sales_invoice, ...) - is_withholding (зменшує net) vs is_charge (нарахування)

compute_taxes(document) — generic функція, що проходить по всіх таx codes applicable і будує PostingEntry-список.

Trigger: UA-production payroll або перший платіж нерезиденту.


10. Posting Rules engine

Метафора: «не пиши в коді який рахунок дебетувати — нехай engine знайде».

Як це у SAP / 1С:ERP

В SAP Account Determination — конфігураційна таблиця:

GL Account Determination для матеріальних рухів:
Movement Type | Valuation Class | Plant | Account
WRX (GR/IR)   | 3000 (Trading)  |  *    | 2400000 (GR/IR)
BSX (Stock)   | 3000            |  *    | 1100000 (Inventory)
GBB-VBR       | 3000 (cons.)    |  *    | 6010000 (COGS)

Тобто: «коли робимо goods receipt на матеріал з valuation class 3000 — кредитуй 2400000». Кожна проводка визначається через ці condition tables.

В 1С аналог — СпособОтраженияВУчете для основних засобів, БизнесОперация для матеріальних рухів. Уся ця логіка — в адміністративних довідниках, не в коді.

Як це у DOP

BusinessOperation — це наш аналог. Але працює спрощено: - Один BusinessOperation = пара рахунків Дт/Кт + кілька правил. - Документ обирає BusinessOperation з dropdown — нема condition table «для класу матеріалів X — авто-обери BO Y». - Default BusinessOperation через EssentialsModuleSettings.default_*_operation (Phase A) — теж глобальний один на весь tenant, не condition-based.

Що ламається

  • Multi-product portfolios (різні класи товарів → різні рахунки COGS) — у DOP всі через однаковий BO.
  • Multi-location (склад A → 1100, склад B → 1101) — у DOP не диференціюється.
  • Product-specific revenue accounts (товари → 7070, послуги → 7060) — у DOP мусиш робити 2 BO і вручну обирати.

Як можна перейти

BusinessOperationRule(condition_field, condition_value, business_operation) — простий keyValue-based rules engine:

WHERE invoice.line.item.category='service' → BO_SALE_SERVICE
WHERE invoice.line.item.category='product' → BO_SALE_PRODUCT
WHERE invoice.line.item.category='trading' → BO_SALE_TRADING

Service resolve_business_operation(document_or_line) обходить rules в порядку specificity і повертає коректну BO.

Trigger: клієнт з широкою номенклатурою з різних бухгалтерських категорій.


11. Document Workflow

Метафора: не одне поле status='approved', а послідовність ступенів з умовами і ролями.

Як це у Dynamics / SAP

Workflow Engine: - Document Type визначає workflow (PurchaseInvoice → workflow "PI_Approval_v3"). - Workflow має N steps (review, approve_<5000, approve_>5000, finance_review). - Кожен step: - condition (field-based: amount > 5000) - assigned_to (role: manager / cfo / specific_user) - actions (approve / reject / request_info) - escalation (через X днів — підняти на наступний рівень) - Lifecycle: draft → submitted → step_1 → step_2 → ... → approved.

UI: Inbox для approver-а, email/push notification, history з commentaries.

Як це у DOP

TransactionModel.status = 'draft' / 'approved' / 'cancelled'єдина enum-колонка. Approval ролі немає, conditions немає, history немає (окрім AuditLog).

# DOP — це і весь "workflow"
DEFAULT_STATUS_CHOICES = [
    ('draft', 'Draft'),
    ('approved', 'Approved'),
    ('cancelled', 'Cancelled'),
]

Що ламається

  • Compliance — без затвердженого аудиторського workflow інвойс може бути «approved» одною людиною, що оплачує.
  • Multi-stakeholder approval: «PurchaseInvoice >5000 — ДВА затвердження від CFO + GM» — не реалізовано.
  • Notification + escalation — немає механізму повідомлення approver-у.

Як можна перейти

Окремий додаток workflow/ з: - WorkflowDefinition(document_type, steps_config). - WorkflowInstance(document_id, current_step, history). - WorkflowAssignment(instance, step, assigned_to, status). - API: GET /workflow/inbox/, POST /workflow/{id}/approve/.

Це окрема серйозна робота — перш ніж робити, потрібен конкретний клієнт зі складним flow.

Trigger: SOX compliance, або клієнт з 5+ ролями у фінансовому процесі.


12. Inter-Company

Метафора: одна юр.особа купує у іншої — і це створює документ-дзеркало в обох автоматично.

Як це у SAP / Dynamics

Inter-Company sales: - Створюється Sales Order у компанії A продати компанії B. - Автоматично створюється Purchase Order у компанії B купити у компанії A. - При відвантаженні: GoodsShipment у A + GoodsReceipt у B (синхронізовано). - При інвойсуванні: Invoice у A + PurchaseInvoice у B. - При consolidation — обидві сторони elimi-ються (intercompany elimination).

Налаштування: Inter-Company Trading Partners — таблиця: «Компанія A продає Компанії B → відповідні Customer/Vendor records → який Pricelist → які рахунки elimination».

Як це у DOP

❌ NONE. У DOP Organization — це наша юр.особа (одна або кілька), Client — зовнішній клієнт. Якщо одна Organization тенанту хоче «продати» іншій — треба або: - Завести other Organization як Client (втрачаємо ідентичність юр.особи). - Зробити два окремі документи руками без зв'язку.

Жодного intercompany flow / elimination не існує.

Що ламається

  • Холдинг із 3+ юр.осіб не може коректно вести облік взаємних продажів.
  • Consolidator (з backlog'у) без intercompany elimination — лише сумарний звіт без виключення взаємооперацій.
  • Transfer pricing — окрема тема, теж недосяжна без intercompany.

Як можна перейти

  1. Дозволити Client мати FK на Organization (it's a sister org, not external).
  2. При post Invoice до такого Client — автоматично створити PurchaseInvoice у sister Organization.
  3. Окремий IntercompanyElimination процес для consolidator.

Trigger: холдинг з 2+ організаціями + консолідована звітність.


13. Storno vs negative entries

Метафора: повертаючи помилку — два способи: «стерти» (negative posting) або «червона проводка» (storno).

Два підходи

Negative posting (Odoo, частково 1С): - Якщо PostingEntry на 100 ₴ помилкова, створити окрему PostingEntry з amount=-100 ₴ на ті самі рахунки. - У звіті — суми складаються до 0. - Plus: простота. - Minus: на звітах це виглядає як «оборот по рахунку 200 ₴, при тому що по факту нічого не було». Аудитор бачить шум.

Storno / Red entry (SAP, 1С класичний): - Створюється PostingEntry з тією ж сумою + прапорцем is_storno=True. - При підрахунку обороту — storno віднімається з обороту (не додається), не дублюючись як negative. - У звіті: оборот 0, але видно що була компенсаційна проводка. - Plus: чистіша картина для аудиту. - Minus: складніша реалізація aggregations.

Як це у DOP

В коді є _reverse_postings (additional_expense_service.py:404) — це видалення PostingGroup, не storno. Тобто ми просто стираємо проводку при unpost.

Проблема: після unpost немає сліду в основних регістрах. AuditLog зберігає action='unpost', але інші системи (PartyLedger, BalanceSheet) не показують «була проводка → знята». У SAP-моделі обидва обидва обороти видно у звіті.

Як можна перейти

Поетапно: 1. Залишити unpost як видалення для draft/just-created документів. 2. Для давно посту-нутих — створювати compensation PostingGroup з is_storno=True замість видалення. 3. Reports фільтрують: total_debit = SUM(debit WHERE NOT is_storno) - SUM(debit WHERE is_storno).

Trigger: аудитор скаржиться на «зникання проводок». Або вимога UA законодавства зберігати весь історичний trail.


14. Drill-down audit trail

Метафора: «бачу у звіті прибуток 1 млн — клік → бачу 12 інвойсів → клік → бачу окремий інвойс → клік → бачу всі його проводки → клік → бачу регістр PartyLedger».

Як це у SAP

Кожен звіт має Drill-Down: - P&L → клік на рядок «Sales 1 000 000» → відкривається list of revenue postings. - Клік на одну проводку → відкривається Document (FB03 у SAP). - Клік на партнера в проводці → відкривається Customer Master з усіма його балансами. - Клік на product у проводці → відкривається Material Master.

Кожна точка має навігаційне меню «Go to → ...». Це наскрізна audit trail через UI.

Як це у DOP

PostingGroup.document_type + document_id — є backref на джерело. Але: - UI navigation немає. З звіту P&L неможливо натиснути на рядок і побачити проводки. - PartyLedgerReport має drill-down до документа — це єдиний приклад правильної навігації (Phase 3). - Більшість frontend reports (TrialBalance, ПСБО Form 1) — статичні таблиці без UI-навігації.

Що ламається

  • Аудит — аудитор має сам пам'ятати «з якого документа цей рядок».
  • Контроль — неможливо швидко перевірити «чому такий великий борг у клієнта Х».
  • Demo-показ — без drill-down навіть в demo важко показати «осі вся історія цієї суми».

Як можна перейти

Не велика робота, але систематична: 1. Кожен report_view має генерувати meta з вказівкою «цей рядок з PostingGroup IDs [1,2,3]». 2. Frontend — клік на рядок → modal з list of source documents → клік на документ → перехід у TransactionPage. 3. PartyLedgerReport — це reference-implementation.

Trigger: перший аудит з реальним аудитором, або скарга користувача «не розумію звідки ця цифра».


15. Summary: 1С-mindset vs SAP/Dynamics-mindset

Якщо ти давно «застряг у 1С-підході», ось 5 ключових ментальних зсувів:

15.1 «Документ виконує проводку» → «Document → Engine → Postings»

1C/DOP: документ при проведенні викликає функцію, що пише проводки.

SAP/D365: документ містить факти бізнес-події. Engine (Account Determination + Document Splitting + Posting Rules + Tax Calculation) виводить проводки з фактів. Документ нічого не «знає» про конкретні рахунки.

Чому важливо: змінив правило обліку — змінив конфігурацію engine, не код документа. Документ незмінний.

15.2 «Поле status='approved'» → «WorkflowInstance як окрема сутність»

1C/DOP: документ має статус як ENUM.

SAP/D365: статус — це поточний step у Workflow Instance. Можна додавати ступені, ролі, conditions без зміни моделі документа.

Чому важливо: compliance + multi-stakeholder процеси.

15.3 «Один tenant = одна бухгалтерія» → «Multi-Ledger»

1C/DOP: один план рахунків, одна модель обліку.

SAP/D365: одночасно ведеться 3-5 «бухгалтерій» з тих самих первинних документів — IFRS, Tax, Local GAAP, Management.

Чому важливо: як тільки клієнт виходить за UA-кордон або робить аудит — без multi-ledger треба дублювати все.

15.4 «Платіж = зменшення paid_amount» → «Open Items + Clearing»

1C/DOP: агрегатне поле з кешем.

SAP/D365: окрема Many-to-Many таблиця з прив'язкою платежного рядка до інвойсного рядка.

Чому важливо: AR aging, partial payments, advances — всі ці сценарії розпадаються без Open Items.

15.5 «posting_lock_date» → «Period Close як ритуал»

1C/DOP: один прапорець «не дай змінити документи раніше дати».

SAP/D365: це послідовність 12 кроків: revaluation → depreciation → recurring → allocation → settlement → reconciliation → trial balance → close.

Чому важливо: без ритуалу багато речей робиться руками + забувається. Period close engine — це гарантія консистентності наприкінці місяця.


16. Що з цим робити

Це не план робіт. Це інструмент переосмислення.

Прочитавши це на ніч, можна:

  1. Усвідомити обмеження поточної архітектури. Не «у нас немає фічі X», а «у нас немає підходу X». Підхід — глибший за фічу.

  2. При наступному gap-listing — питати себе: «Це я граю в 1С-фічу, чи це SAP-концепція, яку я ще не реалізував?». Іноді те, що здається «дрібною фічею», насправді просить велику архітектуру.

  3. Не імплементувати все. Більшість DOP-tenant-ів ніколи не вийде за рамки «маленька торгівля + UA-tax». Для них Open Items + Multi-Ledger + Workflow Engine — overkill. Але треба знати межу: коли стає overkill, коли — must-have.

  4. Готуватись до архітектурних розвилок. Якщо першим серйозним клієнтом буде холдинг — ми будемо змушені або переписати ½ моделей, або робити костилі. Краще — наперед розуміти карту.


Список концептів, рекомендованих для першого впровадження (коли треба)

Якщо вирішиш почати освоєння, починай не з самих складних, а з тих, що закривають конкретний больовий момент:

# Концепт Складність Перший trigger Closes pain
1 Drill-down audit trail (§14) 🟢 1 тиждень Перший «не розумію звідки ця цифра» UI debt
2 Open Items + Clearing (§1) 🟡 2-3 тижні Bank Reconciliation MVP AR aging, partial payments
3 Period Close ритуал (§2) 🟡 2-3 тижні Перший фін.контролер у штаті Місячний хаос
4 Withholding Tax engine (§9) 🟡 2 тижні UA payroll або платіж нерезиденту Withholding ad-hoc
5 Document Splitting (§3) 🟠 3-4 тижні P&L per project з партнерськими сумами Project margin
6 Multi-Ledger (§6) 🔴 1-2 місяці Tax depreciation окремо від bookkeeping Multi-GAAP reporting
7 Posting Rules engine (§10) 🟠 3 тижні Multi-product portfolio Manual BO selection
8 Cost Allocation (§7) 🟠 3-4 тижні Service business з accurate margin Project margin (level 2)
9 Inter-Company (§12) 🔴 1-2 місяці Холдинг з 2+ юр.осіб Multi-org consolidation
10 Workflow Engine (§11) 🔴 1-2 місяці SOX-compliance клієнт Approval ad-hoc

Останні 4 (🔴) — це архітектурні стрибки. Перш ніж — серйозний клієнт + 1-2 місяці виділеного часу на переписування. Не торкайся, поки не з'явилась критична потреба.


Пов'язане


Це не план — це карта. Карта — щоб орієнтуватись, а не щоб їхати по всіх дорогах одразу.