Регістри в K2 ERP
1. Паспорт документа
| Атрибут | Значення |
|---|---|
| Назва | Технічне завдання на розробку підсистеми «Регістри» платформи K2 ERP |
| Шифр | K2-ERP-TZ-REG-001 |
| Версія | 1.1 (чернетка на узгодження; +Додаток Д «Каталог прикладів») |
| Статус | На розгляді |
| Замовник | [підрозділ-замовник] |
| Виконавець | [команда платформи K2 ERP] |
| Тип документа | Технічне завдання (ГОСТ 34.602 адаптовано) |
| Аналог / прототип | Механізм регістрів платформи «1С:Підприємство 8.3» |
| Мова розробки | Delphi | Java] |
| СКБД | PostgreSQL 14+ (основна), MS SQL Server 2019+ (додаткова) |
1.1. Необхідні розширення MediaWiki для перегляду
Документ використовує теги діаграм. Для коректного рендерингу потрібні:
Extension:GraphViz— тег<graphviz>Extension:PlantUML— тег<plantuml>Extension:SyntaxHighlight_GeSHi— тег<syntaxhighlight>
Також використовується шаблон {{Note|текст}}. Якщо його немає у вашій вікі — створіть сторінку Template:Note з вмістом:
<div style="border-left:4px solid #36c; background:#eaf3ff; padding:8px 12px; margin:8px 0;">
'''ⓘ''' {{{1}}}
</div>
Якщо розширення недоступні — блоки діаграм відображатимуться як текстовий вихідний код; дублюючі ASCII-схеми наведені в тілі документа.
2. Терміни, визначення та скорочення
| Термін | Визначення |
|---|---|
| Регістр | Об'єкт метаданих, призначений для зберігання та накопичення структурованої облікової інформації у вигляді записів фіксованої структури, з механізмами агрегації, зрізів і транзакційного зв'язку з документами-реєстраторами. |
| РВ | Регістр відомостей (аналог «Регистр сведений»). |
| РН | Регістр накопичення (аналог «Регистр накопления»). |
| РБ | Регістр бухгалтерії (аналог «Регистр бухгалтерии»). |
| РР | Регістр розрахунку (аналог «Регистр расчёта»). |
| Вимірювання (Dimension) | Поле регістру, що визначає розріз аналітики; входить у ключ запису/підсумку. |
| Ресурс (Resource) | Поле регістру, значення якого підсумовується (для РН/РБ/РР) або зберігається як значення (для РВ). |
| Реквізит (Attribute) | Довідкове поле запису, що не бере участі в ключі та не агрегується. |
| Реєстратор (Recorder) | Документ, який породив набір записів регістру та володіє ним. |
| Рухи (Movements) | Записи регістру, підпорядковані конкретному реєстратору. |
| Набір записів (RecordSet) | Прикладний об'єкт для читання/запису групи записів регістру за заданим відбором. |
| Підсумки (Totals) | Попередньо розраховані агрегати (залишки/обороти), що зберігаються в окремих таблицях. |
| ТА | Точка актуальності підсумків — момент часу, до якого підсумки розраховані. |
| Зріз останніх (SliceLast) | Вибірка останніх за періодом записів РВ у розрізі кожної комбінації вимірювань. |
| Субконто (ExtDimension, ED) | Додатковий розріз аналітики бухгалтерського рахунку. |
| ПВР | План видів розрахунку. |
| Витіснення | Механізм РР: запис із вищим пріоритетом скорочує фактичний період дії запису з нижчим. |
| Перерахунок (Recalculation) | Підпорядкований РР об'єкт, що реєструє записи, які потребують повторного розрахунку. |
| Віртуальна таблиця | Обчислювана таблиця в мові запитів, що не має фізичного відповідника (Залишки, Обороти, ЗрізОстанніх тощо). |
3. Призначення, цілі та підстава розробки
3.1. Підстава
Поточна модель K2 ERP не має уніфікованого декларативного механізму накопичувального обліку. Регістроподібні структури реалізуються ad hoc — окремими таблицями та процедурами під кожну прикладну задачу, що спричиняє:
- дублювання коду розрахунку залишків та оборотів;
- відсутність гарантованої узгодженості «документ ↔ рухи»;
- деградацію продуктивності на великих обсягах (повний перерахунок з початку обліку);
- високу вартість введення нової аналітики (зміна схеми БД + правка десятків процедур).
3.2. Мета
Створити в K2 ERP підсистему декларативно описуваних регістрів чотирьох типів, функціонально еквівалентну механізму регістрів «1С:Підприємство 8.3», з автоматичною генерацією схеми БД, підтримкою підсумків, віртуальних таблиць у мові запитів і транзакційного проведення документів.
3.3. Цілі проєкту (вимірювані)
| № | Ціль | Критерій досягнення |
|---|---|---|
| Ц1 | Декларативне визначення регістру | Новий регістр з 5 вимірюваннями та 3 ресурсами створюється в конструкторі без написання коду; DDL генерується автоматично. |
| Ц2 | Автоматична агрегація | Залишки/обороти доступні запитом без ручних процедур перерахунку. |
| Ц3 | Продуктивність | Отримання залишків на дату за таблицею 1 млрд рухів — ≤ 200 мс (P95) на еталонному стенді. |
| Ц4 | Цілісність | Неможливість існування рухів без активного реєстратора (100 % сценаріїв у тесті цілісності). |
| Ц5 | Міграція | Перенесення ≥ 90 % наявних «регістроподібних» структур K2 на новий механізм без втрати даних. |
3.4. Що НЕ входить до обсягу (Out of scope)
- Механізм розподілених інформаційних баз / реплікації рухів (окреме ТЗ).
- Візуальний конструктор звітів поверх регістрів (окреме ТЗ, залежність).
- OLAP-куби та зовнішні аналітичні вітрини.
- Автоматичне перенесення прикладного коду замовника (тільки дані + мапінг).
4. Припущення та обмеження щодо платформи K2 ERP
| № | Припущення | Наслідок для ТЗ |
|---|---|---|
| П1 | K2 ERP має підсистему метаданих із власним репозиторієм об'єктів (документи, довідники, перерахування) і механізмом версіонування конфігурації. | Регістр вбудовується як новий рід об'єкта метаданих у наявний репозиторій (розділ 6). |
| П2 | Об'єкти-документи мають унікальний ідентифікатор посилання (GUID/UUID) та проведення як окрему операцію. | Реєстратор адресується парою «код типу + UUID» (розділ 8.2). |
| П3 | Платформа має власну мову запитів або транслятор у SQL. | Віртуальні таблиці реалізуються на рівні транслятора (розділ 7.6). Якщо мови запитів немає — потрібен окремий етап Е0 (розділ 14). |
| П4 | Цільова СКБД підтримує MERGE/INSERT ... ON CONFLICT, віконні функції, часткові та покривні індекси. |
Алгоритми підсумків (розділ 10) використовують ці можливості. |
| П5 | Прикладний код виконується на сервері застосунків із керованими транзакціями. | Проведення документа — одна транзакція БД (розділ 7.1). |
| П6 | Максимальна довжина ідентифікатора об'єкта БД — 63 символи (PostgreSQL). | Фізичні імена таблиць будуються за сурогатним числовим кодом, а не за іменем регістру (розділ 8.1). |
Обмеження:
- О1. Зміна складу вимірювань/ресурсів існуючого регістру — операція реструктуризації з простоєм; онлайн-міграція не гарантується для таблиць > 100 млн записів.
- О2. Підсистема не гарантує коректність прикладної логіки проведення — лише цілісність механізму.
- О3. Мінімальна дискретність періоду — 1 секунда (тип
timestamp(0)) [уточнити: чи потрібна мілісекундна].
5. Класифікація регістрів
5.1. Дерево типів

5.2. Порівняльна матриця типів
| Характеристика | РВ | РН (Залишки) | РН (Обороти) | РБ | РР |
|---|---|---|---|---|---|
| Ключ запису | Вимірювання (+Період) або Реєстратор+№ | Реєстратор+№ | Реєстратор+№ | Реєстратор+№ | Реєстратор+№ |
| Незалежний запис | Так | Ні | Ні | Ні | Ні |
| Періодичність | Опційна | Обов'язкова (Період) | Обов'язкова | Обов'язкова | Обов'язкова |
| Ресурси підсумовуються | Ні | Так (±) | Так (+) | Так (Дт/Кт) | Так |
| Вид руху (Прихід/Витрата) | — | Так | — | Дт/Кт | — |
| Таблиця залишків | — | Так | — | Так | — |
| Таблиця оборотів | — | Так (опц.) | Так | Так | — |
| Зв'язок із планом рахунків | — | — | — | Так | — |
| Зв'язок із ПВР | — | — | — | — | Так |
| Період дії / витіснення | — | — | — | — | Так |
| Базовий період | — | — | — | — | Так |
| Графік часу | — | — | — | — | Так |
| Субконто | — | — | — | Так | — |
| Перерахунки | — | — | — | — | Так |
| Типові приклади | Курси валют, ціни, кадрова історія, графік роботи | Товари на складах, взаєморозрахунки | Продажі, витрати за статтями | Госпрозрахунковий, управлінський, податковий облік | Нарахування ЗП, утримання |
6. Метамодель: як регістр задається
6.1. Місце в репозиторії метаданих
Конфігурація K2 ERP
├── Довідники
├── Документи
├── Плани рахунків ← використовується РБ
├── Плани видів характеристик ← використовується як тип субконто / вимірювання
├── Плани видів розрахунку ← використовується РР
└── Регістри ◄── НОВИЙ РОЗДІЛ
├── Регістри відомостей
│ └── КурсиВалют
│ ├── Вимірювання → Валюта
│ ├── Ресурси → Курс, Кратність
│ ├── Реквізити →
│ └── Форми → ФормаСписку, ФормаЗапису
├── Регістри накопичення
│ └── ТовариНаСкладах
│ ├── Вимірювання → Номенклатура, Склад, Партія
│ ├── Ресурси → Кількість, Сума
│ ├── Реквізити → Коментар
│ └── Реєстратори → [ПрихіднаНакладна, ВидатковаНакладна, ...]
├── Регістри бухгалтерії
│ └── Госпрозрахунковий
│ ├── Вимірювання → Організація, Підрозділ(небаланс.)
│ ├── Ресурси → Сума(баланс.), Кількість(небаланс.), ВалютнаСума
│ ├── Реквізити → Зміст, № журналу
│ └── Реєстратори → [...]
└── Регістри розрахунку
└── Нарахування
├── Вимірювання → Співробітник(баз.), Підрозділ
├── Ресурси → Результат, ВідпрацьованоДнів
├── Реквізити → Графік
├── Перерахунки → ПерерахунокЗаБазою
└── Реєстратори → [НарахуванняЗП]
6.2. Загальні властивості будь-якого регістру
| Властивість | Тип | Обов'язк. | Опис / допустимі значення |
|---|---|---|---|
Ім'я |
Ідентифікатор | Так | Унікальне в межах роду. Латиниця/кирилиця, ≤ 80 симв., без пробілів. |
Синонім |
Рядок | Ні | Подання для користувача. |
Коментар |
Текст | Ні | — |
ВидРегістру |
Перелік | Так | Відомостей / Накопичення / Бухгалтерії / Розрахунку. Незмінна після створення.
|
РежимЗапису |
Перелік | Так | Незалежний / ПідпорядкованийРеєстратору. Для РН/РБ/РР — примусово ПідпорядкованийРеєстратору.
|
Періодичність |
Перелік | Так | Неперіодичний, ВМежахСекунди, ВМежахДня, ВМежахМісяця, ВМежахКварталу, ВМежахРоку, ПоПозиціїРеєстратора.
|
Реєстратори |
Список посилань на типи документів | Умовно | Обов'язково для РН/РБ/РР та підпорядкованого РВ. Порожній список = регістр непридатний до запису. |
БлокуванняДаних |
Перелік | Ні | Автоматичне / Кероване (за замовчуванням Кероване).
|
ВключатиДоЗвітів |
Булево | Ні | Показувати в універсальному конструкторі звітів. |
ПравоДоступуЗаЗамовчуванням |
Перелік | Ні | Заборонено / Читання.
|
ВикористовуватиРLS |
Булево | Ні | Row-level security (розділ 7.9). |
Партиціонування |
Структура | Ні | Немає / ЗаПеріодом(RANGE, крок) / ЗаВимірюванням(HASH, n).
|
6.3. Поля регістру
Кожне поле має:
| Властивість | Опис |
|---|---|
Ім'я, Синонім |
Ідентифікація. |
Роль |
Вимірювання / Ресурс / Реквізит.
|
Тип |
Примітивний (Число(p,s), Рядок(n), Дата, Булево, УНІ), посилальний (Довідник.X, Документ.Y, Перерахування.Z, ПланРахунків.A, ПланВидівХарактеристик.B) або складений (список типів).
|
Індексувати |
Не індексувати / Індексувати / ІндексуватиЗДодаткомВимірювань.
|
Ведуче (Master) |
Тільки для вимірювань. Якщо Істина — записи регістру видаляються разом з об'єктом, на який посилається вимірювання; регістр показується у формі цього об'єкта.
|
ОсновнийВідбір |
Брати участь у стандартному відборі форми списку. |
ЗаборонитиНезаповненіЗначення |
Валідація на запис. |
Специфічні прапорці полів по типах регістрів:
| Прапорець | Застосовний до | Опис |
|---|---|---|
Балансовий |
Ресурс/Вимірювання РБ | Ресурс бере участь у контролі рівності Дт=Кт; має єдине значення на запис (а не пару Дт/Кт). Вимірювання балансове — однакове для Дт і Кт. |
Облік (AccountingFlag) |
Ресурс РБ | Посилання на ознаку обліку рахунка з ПР; ресурс заповнюється, лише якщо ознака встановлена. |
ОблікСубконто |
Ресурс РБ | Посилання на ознаку обліку субконто (напр. кількісний облік у розрізі субконто). |
Базове (BaseDimension) |
Вимірювання РР | Використовується для зіставлення записів під час отримання бази розрахунку. |
ВикористовуватиПоточніЗначення |
Ресурс/Реквізит РР | — |
6.4. Специфічні властивості за типами
6.4.1. Регістр відомостей
| Властивість | Значення | Примітка |
|---|---|---|
Періодичність |
Неперіодичний … ПоПозиціїРеєстратора | Визначає наявність поля Період і його гранулярність.
|
РежимЗапису |
Незалежний / ПідпорядкованийРеєстратору | У незалежному ключ = (Період) + Вимірювання. |
ОсновнийВідбір |
— | |
ДозволитиПідсумки |
— | У РВ підсумків немає; замість них — зрізи. |
Ключ унікальності:
- Незалежний неперіодичний:
UNIQUE(Вимірювання…) - Незалежний періодичний:
UNIQUE(Період, Вимірювання…) - Підпорядкований:
PK(Реєстратор, НомерРядка), унікальність за вимірюваннями не контролюється.
6.4.2. Регістр накопичення
| Властивість | Значення |
|---|---|
ВидРегістру |
Залишки / Обороти
|
ДозволитиПідсумкиЗалишків |
Булево (тільки для виду «Залишки») |
ДозволитиПідсумкиОборотів |
Булево |
ПеріодичністьПідсумківОборотів |
День / Місяць (тільки для виду «Обороти»)
|
Агрегати |
Список визначень агрегатів (тільки для виду «Обороти»), див. 7.4.4 |
РежимАгрегатів |
Агрегати / Підсумки
|
6.4.3. Регістр бухгалтерії
| Властивість | Значення | Примітка |
|---|---|---|
ПланРахунків |
Посилання на об'єкт «План рахунків» | Обов'язково. Незмінне після створення. |
Кореспонденція |
Булево | Істина → подвійний запис (поля РахунокДт/РахунокКт); Хиба → уніграфічний (Рахунок + ВидРуху).
|
ДозволитиРозділенняПідсумків |
Булево | |
МаксКількістьСубконто |
Число (успадковується з ПР) | Ліміт платформи: 5 [узгодити; 1С — 3 за замовчуванням, до 5] |
Пов'язаний об'єкт План рахунків повинен підтримувати:
- ієрархію рахунків,
Код,Найменування,Вид(Активний/Пасивний/Активно-пасивний); Забалансовий(булево);Ознаки обліку(набір булевих реквізитів, напр. «Кількісний», «Валютний»);- табличну частину
ВидиСубконто(ВидСубконто → ПВХ, Тільки обороти, Підсумкова сума, ознаки обліку субконто); МаксКількістьСубконто.
6.4.4. Регістр розрахунку
| Властивість | Значення | Примітка |
|---|---|---|
ПланВидівРозрахунку |
Посилання | Обов'язково. |
Періодичність |
День/Місяць/Квартал/Рік |
Період реєстрації. |
ПеріодДії |
Булево | Вмикає поля ПеріодДіїПочаток/Кінець, фактичні періоди дії та витіснення.
|
БазовийПеріод |
БезБазовогоПеріоду / ЗаПеріодДії / ЗаПеріодРеєстрації |
Режим отримання бази. |
Графік |
Посилання на РВ | Регістр-графік часу. |
ЗначенняГрафіка |
Ресурс регістру-графіка | Напр. «Значення» (1/0 — робочий день) або «Годин». |
ДатаГрафіка |
Вимірювання регістру-графіка типу Дата |
— |
Перерахунки |
Список підпорядкованих об'єктів «Перерахунок» | Див. 7.5.3. |
Об'єкт Перерахунок (підпорядкований РР):
Ім'я;Вимірювання— список, кожне зіставляється з вимірюванням основного РР (ВимірюванняРегістру);- правило: перерахунок реєструє «об'єкт перерахунку» = (Реєстратор, ВидРозрахунку, Вимірювання…).
План видів розрахунку повинен підтримувати:
ВикористовуєПеріодДії;- табличні частини
БазовіВидиРозрахунку,ВитісняючіВидиРозрахунку,ВедучіВидиРозрахунку; ЗалежністьВідБази:НеЗалежить/ЗаПеріодДії/ЗаПеріодРеєстрації.
6.5. Стандартні реквізити (генеруються автоматично)
| Реквізит | РВ | РН | РБ | РР | Тип |
|---|---|---|---|---|---|
Період |
якщо періодичний | + | + | + (період реєстрації) | Дата/час |
Реєстратор |
якщо підпорядкований | + | + | + | Складене посилання (ДокументПосилання) |
НомерРядка |
якщо підпорядкований | + | + | + | Число(9,0) |
Активність |
якщо підпорядкований | + | + | + | Булево |
ВидРуху |
— | тільки «Залишки» | — | — | Перелік {Прихід, Витрата} |
Рахунок |
— | — | + (без кореспонденції) | — | ПланРахунківПосилання |
РахунокДт, РахунокКт |
— | — | + (з кореспонденцією) | — | ПланРахунківПосилання |
ВидРухуРахунку |
— | — | + (без кореспонденції) | — | Перелік {Дебет, Кредит} |
Субконто (колекція) |
— | — | + | — | Відповідність(ВидСубконто→Значення) |
ВидРозрахунку |
— | — | — | + | ПВРПосилання |
ПеріодДіїПочаток, ПеріодДіїКінець |
— | — | — | + (якщо ПеріодДії) | Дата |
БазовийПеріодПочаток, БазовийПеріодКінець |
— | — | — | + (якщо БазовийПеріод) | Дата |
Сторно |
— | — | — | + | Булево |
6.6. Кількісні обмеження метамоделі
| Параметр | Ліміт | Примітка |
|---|---|---|
| Кількість вимірювань | 20 | Понад 8 — попередження конструктора (деградація підсумків) |
| Кількість ресурсів | 20 | |
| Кількість реквізитів | 30 | |
| Кількість типів у складеному типі поля | 32 | |
| Кількість реєстраторів | Не обмежено | |
| Максимум субконто на рахунок | 5 | |
| Довжина ключа підсумків | ≤ 900 байт (обмеження індексу MS SQL) | Валідація в конструкторі |
| Глибина періодичності підсумків | День / Місяць | Тиждень/Квартал — не підтримуються (обчислюються згортанням) |
6.7. Декларативний опис (DSL)
Регістр зберігається в репозиторії у вигляді XML/YAML-маніфесту. Приклад:
# --- Регістр накопичення (вид: Залишки) ---
register:
name: ТовариНаСкладах
kind: Accumulation
accumulationKind: Balance
synonym: "Товари на складах"
periodicity: WithinSecond
writeMode: SubordinateToRecorder
enableBalanceTotals: true
enableTurnoverTotals: true
partitioning: { mode: RangeByPeriod, step: month }
dimensions:
- { name: Номенклатура, type: "Довідник.Номенклатура", master: true, index: true }
- { name: Склад, type: "Довідник.Склади", master: true, index: true }
- { name: Партія, type: ["Документ.ПрихіднаНакладна", "Документ.ВведенняЗалишків"], index: false }
- { name: Характеристика, type: "ПланВидівХарактеристик.Характеристики", nullable: true }
resources:
- { name: Кількість, type: "Число(15,3)" }
- { name: Сума, type: "Число(15,2)" }
attributes:
- { name: Коментар, type: "Рядок(200)" }
recorders:
- Документ.ПрихіднаНакладна
- Документ.ВидатковаНакладна
- Документ.ПереміщенняТоварів
- Документ.Інвентаризація# --- Регістр відомостей (періодичний, незалежний) ---
register:
name: КурсиВалют
kind: Information
periodicity: WithinDay
writeMode: Independent
dimensions:
- { name: Валюта, type: "Довідник.Валюти", master: true }
resources:
- { name: Курс, type: "Число(15,4)" }
- { name: Кратність, type: "Число(10,0)", default: 1 }# --- Регістр бухгалтерії ---
register:
name: Госпрозрахунковий
kind: Accounting
chartOfAccounts: ПланРахунків.Госпрозрахунковий
correspondence: true
periodicity: WithinSecond
dimensions:
- { name: Організація, type: "Довідник.Організації", balance: true }
- { name: Підрозділ, type: "Довідник.Підрозділи", balance: false }
resources:
- { name: Сума, type: "Число(15,2)", balance: true }
- { name: Кількість, type: "Число(15,3)", balance: false, accountingFlag: Кількісний }
- { name: ВалютнаСума, type: "Число(15,2)", balance: false, accountingFlag: Валютний }
attributes:
- { name: Зміст, type: "Рядок(255)" }
- { name: Валюта, type: "Довідник.Валюти" }
recorders: [Документ.БухгалтерськаОперація, Документ.ПрихіднаНакладна]# --- Регістр розрахунку ---
register:
name: Нарахування
kind: Calculation
calculationTypesPlan: ПланВидівРозрахунку.ОсновніНарахування
periodicity: Month
actionPeriod: true
basePeriod: ByActionPeriod
schedule:
register: РегістрВідомостей.ГрафікРоботи
valueResource: Значення
dateDimension: ДатаГрафіка
dimensions:
- { name: Співробітник, type: "Довідник.Співробітники", baseDimension: true, master: true }
- { name: Підрозділ, type: "Довідник.Підрозділи" }
resources:
- { name: Результат, type: "Число(15,2)" }
- { name: ВідпрацьованоДнів, type: "Число(5,0)" }
- { name: НормаДнів, type: "Число(5,0)" }
recalculations:
- name: ПерерахунокЗаБазою
dimensions:
- { name: Співробітник, registerDimension: Співробітник }
recorders: [Документ.НарахуванняЗарплати]6.8. UI конструктора метаданих
6.8.1. Загальний макет
┌─ Конфігуратор K2 ERP ─────────────────────────────────────────────────────────┐ │ Файл Правка Конфігурація Адміністрування Сервіс Довідка │ ├──────────────────────┬────────────────────────────────────────────────────────┤ │ Дерево конфігурації │ Регістр накопичення: ТовариНаСкладах │ │ │ ┌──────┬─────────┬──────────┬──────────┬───────────┐ │ │ ▾ Регістри │ │Основ.│Дані │Реєстратор│Форми │Права │ │ │ ▸ відомостей │ └──────┴─────────┴──────────┴──────────┴───────────┘ │ │ ▾ накопичення │ │ │ ▾ ТовариНаСклад. │ Ім'я [ТовариНаСкладах.............] │ │ ▾ Вимірювання │ Синонім [Товари на складах...........] │ │ Номенклат. │ Коментар [.............................] │ │ Склад │ │ │ Партія │ Вид регістру ( ) Обороти (•) Залишки │ │ ▾ Ресурси │ Періодичність [ У межах секунди ▾] │ │ Кількість │ Режим запису [ Підпорядкований реєстратору ▾] │ │ Сума │ │ │ ▸ Реквізити │ ☑ Дозволити підсумки залишків │ │ ▸ Форми │ ☑ Дозволити підсумки оборотів │ │ ▸ Взаєморозрах. │ Періодичність підсумків оборотів [Місяць ▾] │ │ ▸ бухгалтерії │ ☐ Дозволити розділення підсумків │ │ ▸ розрахунку │ │ │ │ Блокування даних [Кероване ▾] │ │ │ Партиціонування [За періодом, крок: місяць ▾] │ │ │ │ │ │ [ Конструктор рухів... ] [ Перевірити ] [ Закрити ] │ ├──────────────────────┴────────────────────────────────────────────────────────┤ │ ⚠ Попередження: 4 вимірювання + місячні підсумки → оцінка таблиці підсумків │ │ ~ 12 млн записів/рік. Розгляньте виключення вимірювання «Партія» з підсумків.│ └───────────────────────────────────────────────────────────────────────────────┘
6.8.2. Палітра властивостей поля
┌─ Властивості: Вимірювання «Партія» ────────────────┐ │ Ім'я [Партія...................] │ │ Синонім [Партія...................] │ │ Тип [Складений тип........ [...]]│ │ ├ Документ.ПрихіднаНакладна │ │ └ Документ.ВведенняЗалишків │ │ Індексувати [Не індексувати ▾] │ │ ☐ Ведуче │ │ ☐ Основний відбір │ │ ☐ Заборонити незаповнені значення │ │ ☑ Використовувати в підсумках │ │ Подання │ │ Підказка [Партія товару...........] │ └────────────────────────────────────────────────────┘
6.8.3. Вкладка «Реєстратори»
┌─ ТовариНаСкладах → Реєстратори ────────────────────────────┐ │ ☑ Документ.ПрихіднаНакладна │ │ ☑ Документ.ВидатковаНакладна │ │ ☑ Документ.ПереміщенняТоварів │ │ ☑ Документ.Інвентаризація │ │ ☐ Документ.Замовлення │ │ ☐ Документ.РахунокНаОплату │ │ [Позначити всі] [Зняти]│ │ ───────────────────────────────────────────────────────── │ │ ⓘ Знімання прапорця для документа, що має рухи, вимагає │ │ очищення рухів. Знайдено рухів: 0 │ └────────────────────────────────────────────────────────────┘
6.8.4. Вимоги до конструктора
- ФТ-К-01. Валідація метаопису в реальному часі з переліком помилок/попереджень.
- ФТ-К-02. Попередній перегляд згенерованого DDL (кнопка «Показати SQL»).
- ФТ-К-03. Калькулятор оцінки обсягу таблиць підсумків (добуток кардинальностей вимірювань × кількість періодів).
- ФТ-К-04. Заборона неприпустимих комбінацій (напр. «Незалежний» + «Регістр накопичення»).
- ФТ-К-05. «Конструктор рухів» — генерація коду формування рухів за табличною частиною документа.
- ФТ-К-06. Порівняння/об'єднання конфігурацій має коректно обробляти об'єкти-регістри.
7. Функціональні вимоги
7.1. Набір записів і проведення документа
ФТ-01. Платформа надає прикладний об'єкт НабірЗаписів для кожного регістру з властивостями: Відбір, Записувати, і методами Прочитати(), Записати(Заміщати), Очистити(), ЗаблокуватиДляЗміни(), Вивантажити(), Завантажити().
ФТ-02. Для документа доступна колекція Рухи з наборами записів усіх регістрів, де документ вказано реєстратором.
ФТ-03. Сценарій проведення:
<plantuml> @startuml skinparam monochrome false skinparam shadowing false actor Користувач participant "Форма\nдокумента" as UI participant "Об'єкт\nДокумент" as Doc participant "Менеджер\nпроведення" as PM participant "Набори\nзаписів" as RS database "СКБД" as DB
Користувач -> UI: Провести UI -> Doc: Записати(РежимЗапису.Проведення) activate Doc Doc -> PM: НачатьТранзакцию() PM -> DB: BEGIN Doc -> Doc: ПередЗаписью() Doc -> DB: UPDATE doc SET Проведений=1 Doc -> Doc: ОбработкаПроведения(Отказ, Режим) note right of Doc
Прикладний код: Рухи.ТовариНаСкладах.Записувати = Істина; Рух = Рухи.ТовариНаСкладах.Добавить(); Рух.ВидРуху = ВидРухуНакопичення.Витрата; ... // контроль залишків (оперативне проведення)
end note Doc -> RS: (формування рухів у пам'яті) Doc -> PM: ЗаписатьДвижения() PM -> RS: для кожного НЗ з Записувати=Істина RS -> DB: DELETE FROM rg_a_0012 WHERE rec_ref = :doc RS -> DB: INSERT INTO rg_a_0012 (...) VALUES (...) RS -> PM: ОновитиПідсумки(delta) PM -> DB: MERGE rg_a_0012_tb (див. розд. 10.2) Doc -> Doc: ПриЗаписи() / ПослеЗаписи() alt Відмова = Хиба
PM -> DB: COMMIT PM --> UI: Успіх
else Відмова = Істина або виключення
PM -> DB: ROLLBACK PM --> UI: Помилка "Документ не проведено: ..."
end deactivate Doc @enduml </plantuml>
ФТ-04. Запис набору записів у режимі Заміщати = Істина (за замовчуванням для рухів) виконує DELETE усіх записів за відбором + INSERT нових — атомарно.
ФТ-05. Скасування проведення документа: усі набори записів за реєстратором очищуються, підсумки коригуються, документ отримує Проведений = Хиба.
ФТ-06. Видалення документа-реєстратора (у т.ч. позначеного на видалення) зобов'язане каскадно видалити всі його рухи в межах однієї транзакції. Реалізація — на рівні платформи (не FK, оскільки посилання поліморфне) + фонова перевірка цілісності (розділ 7.10).
ФТ-07. Режими проведення:
| Режим | Умова | Поведінка |
|---|---|---|
| Оперативне | Дата документа = поточна дата, документ у межах «оперативної точки» | Дозволено контроль залишків «на зараз»; дата може перепризначатися на поточну. |
| Неоперативне | Дата в минулому/майбутньому або документ перепроводиться | Контроль залишків виконується на дату документа; можливий негативний залишок за налаштуванням. |
7.2. Активність записів
ФТ-08. Реквізит Активність = Хиба означає, що запис фізично існує, але не впливає на підсумки та не потрапляє до віртуальних таблиць.
ФТ-09. Зміна активності будь-якого запису тягне коригування підсумків.
ФТ-10. Скасування проведення документа переводить усі його записи в Активність = Хиба або видаляє їх — залежно від налаштування регістру ПоведінкаПриСкасуванні (Видаляти — за замовчуванням / Деактивувати).
7.3. Віртуальні таблиці
ФТ-11. Мова запитів надає для кожного регістру набір віртуальних таблиць. Транслятор перетворює звернення до них на SQL із використанням таблиць підсумків та «живих» рухів після ТА.
| Регістр | Віртуальна таблиця | Параметри | Повертає |
|---|---|---|---|
| РВ | ЗрізОстанніх |
(Період, Умова) | Останній запис на кожну комбінацію вимірювань з періодом ≤ Період |
ЗрізПерших |
(Період, Умова) | Перший запис з періодом ≥ Період | |
| (основна) | — | Усі записи | |
| РН.Залишки | Залишки |
(Період, Умова) | Вимірювання + <Ресурс>Залишок
|
Обороти |
(Початок, Кінець, Періодичність, Умова) | <Ресурс>Прихід, <Ресурс>Витрата, <Ресурс>Оборот
| |
ЗалишкиІОбороти |
(Початок, Кінець, Періодичність, МетодДоповнення, Умова) | <Р>ПочатковийЗалишок, <Р>Прихід, <Р>Витрата, <Р>Оборот, <Р>КінцевийЗалишок
| |
| (основна) | — | Рухи | |
| РН.Обороти | Обороти |
(Початок, Кінець, Періодичність, Умова) | <Ресурс>Оборот
|
| РБ | Залишки |
(Період, УмоваРахунку, Субконто, Умова) | <Р>Залишок, <Р>ЗалишокДт, <Р>ЗалишокКт
|
Обороти |
(Початок, Кінець, Періодичність, УмоваРахунку, Субконто, Умова, УмоваКорРахунку, КорСубконто) | <Р>Оборот, <Р>ОборотДт, <Р>ОборотКт
| |
ЗалишкиІОбороти |
(…) | Повний набір | |
ОборотиДтКт |
(Початок, Кінець, Періодичність, УмоваРахункуДт, СубконтоДт, УмоваРахункуКт, СубконтоКт) | Кореспонденції | |
ДвиженияССубконто |
(Початок, Кінець, УмоваРахунку, Субконто, Умова) | Рухи з розшифровкою субконто | |
Субконто |
(Умова, Порядок…) | Значення субконто записів | |
| РР | ДаніГрафіка |
(Умова) | ЗначенняПеріодуДії, ЗначенняПеріодуРеєстрації, ЗначенняБазовогоПеріоду, ЗначенняФактичногоПеріодуДії
|
ФактичнийПеріодДії |
(Умова) | Записи з фактичними (після витіснення) межами | |
БазаРозрахунку |
(ВимірюванняБази, ВимірюванняОсновногоРегістру, Розрізи, ВидиРозрахунку, Умова) | Агреговані значення ресурсів базових видів розрахунку | |
| (основна) | — | Рухи |
ФТ-12. Приклад запиту:
ВИБРАТИ
Залишки.Номенклатура,
Залишки.Склад,
Залишки.КількістьЗалишок,
Обороти.КількістьПрихід,
Обороти.КількістьВитрата
З
РегістрНакопичення.ТовариНаСкладах.ЗалишкиІОбороти(
&ПочатокПеріоду,
&КінецьПеріоду,
Місяць,
Рух,
Склад = &Склад
І Номенклатура В ІЄРАРХІЇ (&ГрупаНоменклатури)
) ЯК ЗалишкиФТ-13. Параметри віртуальних таблиць зобов'язані транслюватися у WHERE підзапиту до звернення до підсумків (push-down), а не фільтруватися після агрегації.
7.4. Підсумки
7.4.1. Модель підсумків залишків
Таблиця залишків зберігає кумулятивний залишок на початок кожного періоду + спеціальний рядок «підсумок за всі періоди» з period = '5999-11-01' (маркер MAXPERIOD).
Таблиця підсумків (місячна) ТА
┌────┬────┬────┬────┬────┐ ▼
─────┬────┬────┬───┴────┴────┴────┴────┴────┴────────────────────┬──────────►
│ │ │ 01.10 01.11 01.12 01.01 01.02 15.02 t
│ │ │ ● ● ● ● ●
│ │ │ бал. бал. бал. бал. бал.
└─ «живі» рухи ─┘
(сканування)
Залишок на 10.02 = Підсумок(01.02) + Σ рухів (01.02 … 10.02]
Залишок на 20.02 = Підсумок(01.02) + Σ рухів (01.02 … 20.02] // після ТА — теж
ФТ-14. Регламентна операція «Перерахунок підсумків» перебудовує таблиці підсумків з нуля. ФТ-15. Регламентна операція «Встановлення періоду розрахунку підсумків» переносить ТА вперед/назад. ФТ-16. При записі рухів раніше ТА підсумки коригуються інкрементально (див. 10.2), без повного перерахунку.
7.4.2. Розділення підсумків
ФТ-17. За увімкненого ДозволитиРозділенняПідсумків у таблиці підсумків додається колонка splitter smallint. Одночасні транзакції пишуть у різні «сплітери», що усуває конкуренцію за рядок. Читання агрегує по всіх сплітерах. Регламентне завдання зливає сплітери.
7.4.3. Обороти
ФТ-18. Таблиця оборотів зберігає агрегати за календарний період (день або місяць — за налаштуванням), без кумуляції.
7.4.4. Агрегати (тільки РН виду «Обороти»)
ФТ-19. Агрегат — додаткова матеріалізована таблиця з підмножиною вимірювань і власною періодичністю (День/Місяць/Квартал/Рік). Оптимізатор запитів обирає найдешевший агрегат, що покриває запит.
ФТ-20. Режими: Реальний час (оновлюється при записі рухів) / Регламентний (оновлюється завданням; запит враховує рухи після дати актуальності агрегату).
ФТ-21. «Порадник агрегатів» аналізує статистику запитів і пропонує склад агрегатів (ВизначитиОптимальніАгрегати()).
7.5. Механіка регістру розрахунку
7.5.1. Витіснення за періодом дії
ФТ-22. Якщо у ПВР для виду розрахунку A вказано B у складі «Витісняючі види розрахунку», то запис виду B скорочує фактичний період дії запису виду A на інтервал перетину.
Період реєстрації: Січень 2026. Пріоритет витіснення: Лікарняний > Відпустка > Оклад Заявлені періоди дії: Оклад 01.01 ├══════════════════════════════════════════════┤ 31.01 Відпустка 10.01 ├════════════┤ 17.01 Лікарняний 14.01 ├════════════┤ 22.01 Фактичні періоди дії (rg_c_XXXX_ap): Оклад 01.01 ├═══════┤ 09.01 23.01 ├════════════┤ 31.01 Відпустка 10.01 ├═══┤ 13.01 Лікарняний 14.01 ├════════════┤ 22.01 ⇒ у таблиці фактичних періодів «Оклад» породжує ДВА інтервали (запис — один).
ФТ-23. Фактичні періоди дії матеріалізуються у службовій таблиці rg_c_<код>_ap під час запису набору записів.
ФТ-24. Витіснення обчислюється в межах збігу усіх вимірювань регістру та перетину періодів реєстрації, визначених ПВР.
7.5.2. Базовий період і база розрахунку
ФТ-25. Віртуальна таблиця БазаРозрахунку повертає суму ресурсів записів базових видів розрахунку, зіставлених за базовими вимірюваннями, з урахуванням режиму ЗалежністьВідБази:
ЗаПеріодДії— беруться записи, чий період дії перетинається з базовим періодом поточного запису; результат пропорційно розподіляється за часткою перетину (за графіком, якщо заданий);ЗаПеріодРеєстрації— беруться записи, чий період реєстрації потрапляє в базовий період.
7.5.3. Перерахунки
ФТ-26. При записі/зміні/видаленні запису базового виду розрахунку платформа автоматично формує записи у таблиці перерахунку для всіх залежних записів (за збігом вимірювань перерахунку та потраплянням у базовий період). ФТ-27. Прикладний код читає перерахунок як набір записів, перепроводить відповідні документи та очищує оброблені записи. ФТ-28. Перерахунок доступний як об'єкт для звіту «Документи до перерахунку».
7.5.4. Графік часу
ФТ-29. Графік — це РВ з обов'язковим вимірюванням типу Дата та числовим ресурсом. Віртуальна таблиця ДаніГрафіка повертає суму значень графіка за відповідний період (дії / реєстрації / базовий / фактичний період дії).
ФТ-30. Якщо у графіку є додаткові вимірювання (напр. ВидГрафіка), вони зіставляються з однойменними реквізитами/вимірюваннями РР.
7.6. Прикладний API
// --- Регістр відомостей: незалежний запис ---
var mgr = Registers.Information["КурсиВалют"];
// точковий запис
var rec = mgr.CreateRecordManager();
rec.Період = new DateTime(2026, 7, 17);
rec.Валюта = usd;
rec.Курс = 41.85m;
rec.Кратність = 1;
rec.Write(replace: true); // INSERT ... ON CONFLICT DO UPDATE
// зріз останніх
var slice = mgr.GetLast(DateTime.Today, new Filter { ["Валюта"] = usd });
decimal курс = slice.Курс;
// --- Регістр накопичення: рухи документа ---
protected override void OnPosting(PostingContext ctx)
{
var rs = Movements["ТовариНаСкладах"];
rs.Write = true; // набір буде записано наприкінці транзакції
rs.Clear();
foreach (var row in this.Товари)
{
var m = rs.Add();
m.Період = this.Дата;
m.MovementType = AccumulationMovementType.Expense; // Витрата
m.Номенклатура = row.Номенклатура;
m.Склад = this.Склад;
m.Партія = row.Партія;
m.Кількість = row.Кількість;
m.Сума = row.Сума;
}
// керовані блокування перед контролем залишків
var lock_ = new DataLock();
var item = lock_.Add("РегістрНакопичення.ТовариНаСкладах.Залишки");
item.Mode = DataLockMode.Exclusive;
item.DataSource = rs.Unload(new[] { "Номенклатура", "Склад" });
item.UseFromDataSource("Номенклатура", "Номенклатура");
item.UseFromDataSource("Склад", "Склад");
lock_.Lock();
rs.WriteNow(); // примусовий запис до контролю
ControlBalances(ctx); // прикладна перевірка на від'ємні залишки
}
// --- Регістр бухгалтерії ---
var rs = Movements["Госпрозрахунковий"];
var m = rs.AddAccountingEntry();
m.Період = this.Дата;
m.РахунокДт = accounts["281"];
m.РахунокКт = accounts["631"];
m.SubcontoDt[ВидиСубконто.Номенклатура] = row.Номенклатура;
m.SubcontoDt[ВидиСубконто.Склади] = this.Склад;
m.SubcontoKt[ВидиСубконто.Контрагенти] = this.Контрагент;
m.Сума = row.Сума;
m.КількістьДт = row.Кількість;
// --- Регістр розрахунку ---
var rs = Movements["Нарахування"];
var m = rs.Add();
m.ВидРозрахунку = calcTypes["Оклад"];
m.Період = this.Дата; // період реєстрації
m.ПеріодДіїПочаток = new DateTime(2026,7,1);
m.ПеріодДіїКінець = new DateTime(2026,7,31);
m.БазовийПеріодПочаток = new DateTime(2026,6,1);
m.БазовийПеріодКінець = new DateTime(2026,6,30);
m.Співробітник = row.Співробітник;
m.Результат = 0; // заповниться після розрахунку
rs.WriteNow();
// отримання даних графіка та бази — запитом7.7. Блокування та конкурентність
| № | Вимога |
|---|---|
| ФТ-31 | Режим керованих блокувань — за замовчуванням. Рівень ізоляції транзакцій: READ COMMITTED (PostgreSQL) / READ COMMITTED SNAPSHOT (MS SQL).
|
| ФТ-32 | Об'єкт DataLock дозволяє встановити розділювальне/виняткове блокування на «простір» регістру за значеннями вимірювань (реалізація — pg_advisory_xact_lock за хешем ключа або SELECT ... FOR UPDATE по таблиці підсумків).
|
| ФТ-33 | Запис рухів документа блокує тільки записи цього реєстратора (PK містить реєстратор першим). |
| ФТ-34 | Оновлення підсумків не повинно призводити до дедлоків: усі MERGE-операції в межах транзакції виконуються у детермінованому порядку сортування ключа.
|
| ФТ-35 | Гарантія: паралельне проведення двох документів з різними наборами вимірювань не конкурує (за увімкненого розділення підсумків). |
| ФТ-36 | Таймаут очікування блокування — 20 с (налаштовується); при перевищенні — керована помилка з текстом конфліктного ключа. |
7.8. Права доступу
ФТ-37. Для регістру визначаються права: Читання, Додавання, Зміна, Видалення, Перегляд, Редагування, ПереглядПідсумків.
ФТ-38. Обмеження на рівні записів (RLS) задаються шаблоном умови по вимірюваннях; транслюються в WHERE усіх звернень, включно з віртуальними таблицями.
ФТ-39. Ризик: RLS на віртуальних таблицях залишків потребує узгодженості з підсумками. Рішення — RLS дозволено тільки по вимірюваннях, що входять до ключа підсумків; конструктор валідує це правило.
7.9. Журналювання, аудит, службові сервіси
ФТ-40. Реєстрація в журналі: перерахунок підсумків, зміна ТА, реструктуризація регістру, злиття сплітерів. ФТ-41. Службовий сервіс «Перевірка цілісності регістрів»:
- «висячі» рухи (реєстратор не існує або не проведений);
- розбіжність підсумків і сум рухів (контрольний перерахунок у тіньову таблицю з порівнянням);
- некоректні фактичні періоди дії РР;
- для РБ — порушення рівності Дт = Кт за балансовим ресурсом у межах реєстратора.
ФТ-42. Звіт «Аналіз розміру регістрів»: кількість записів, розмір таблиць та індексів, дата останнього перерахунку підсумків.
8. Модель даних (як це виглядає в базі даних)
8.1. Конвенції іменування
Фізичні імена не успадковують прикладні імена (обмеження довжини ідентифікатора, кирилиця, перейменування об'єктів). Ім'я будується за сурогатним кодом об'єкта метаданих <id> (4-значний hex/dec).
| Шаблон | Призначення | Приклад |
|---|---|---|
rg_i_<id> |
Регістр відомостей — основна таблиця | rg_i_0007
|
rg_a_<id> |
Регістр накопичення — рухи | rg_a_0012
|
rg_a_<id>_tb |
РН — підсумки залишків (Totals Balance) | rg_a_0012_tb
|
rg_a_<id>_tt |
РН — підсумки оборотів (Totals Turnover) | rg_a_0012_tt
|
rg_a_<id>_ag<n> |
РН — агрегат № n | rg_a_0031_ag1
|
rg_b_<id> |
Регістр бухгалтерії — проводки | rg_b_0020
|
rg_b_<id>_ed |
РБ — субконто (якщо винесено в дочірню таблицю) | rg_b_0020_ed
|
rg_b_<id>_tb<k> |
РБ — підсумки залишків за рівнем субконто k (0…N) | rg_b_0020_tb2
|
rg_b_<id>_tt<k> |
РБ — підсумки оборотів за рівнем субконто k | rg_b_0020_tt1
|
rg_b_<id>_tc |
РБ — обороти між рахунками (кореспонденції) | rg_b_0020_tc
|
rg_c_<id> |
Регістр розрахунку — рухи | rg_c_0040
|
rg_c_<id>_ap |
РР — фактичні періоди дії | rg_c_0040_ap
|
rg_c_<id>_rc<m> |
РР — перерахунок № m | rg_c_0040_rc1
|
rg_<*>_<id>_opt |
Службова: ТА підсумків, налаштування | rg_a_0012_opt
|
md_* |
Таблиці метаданих | md_register
|
Колонки полів: d<n> — вимірювання, r<n> — ресурс, a<n> — реквізит, де n — порядковий номер поля в метаописі (стабільний, не перевикористовується після видалення поля). Мапінг «прикладне ім'я ↔ фізична колонка» зберігається в md_register_field.
8.2. Типізація та зберігання значень
8.2.1. Примітивні типи
| Тип платформи | PostgreSQL | MS SQL |
|---|---|---|
| Число(p,s) | numeric(p,s) |
numeric(p,s)
|
| Рядок(n) | varchar(n) |
nvarchar(n)
|
| Рядок(необмежена) | text |
nvarchar(max)
|
| Дата / ДатаЧас | timestamp(0) |
datetime2(0)
|
| Булево | boolean |
bit
|
| УНІ | uuid |
uniqueidentifier
|
8.2.2. Посилальні типи
- Простий (один тип): одна колонка
d<n>_r uuid NOT NULL, зовнішній ключ до таблиці об'єкта. Порожнє посилання ='00000000-0000-0000-0000-000000000000'(а не NULL — щоб зберегти семантику «пустого значення» у ключах підсумків). - Складений (кілька типів / примітив + посилання): група колонок:
| Колонка | Тип | Опис |
|---|---|---|
d<n>_t |
smallint |
Код типу: 1=Невизначено, 2=Булево, 3=Число, 4=Рядок, 5=Дата, ≥100 = md_type.id посилального типу
|
d<n>_b |
boolean |
Значення, якщо _t=2 |
d<n>_n |
numeric(38,10) |
Значення, якщо _t=3 |
d<n>_s |
varchar(N) |
Значення, якщо _t=4 |
d<n>_d |
timestamp(0) |
Значення, якщо _t=5 |
d<n>_r |
uuid |
Значення, якщо _t≥100 |
8.2.3. Реєстратор
Завжди складений (будь-який з дозволених документів):
rec_t smallint NOT NULL— код типу документа (md_type.id);rec_r uuid NOT NULL— посилання.
FK неможливий (поліморфізм) → цілісність забезпечується платформою + сервісом перевірки (ФТ-41).
8.2.4. Маркери періодів
| Константа | Значення | Використання |
|---|---|---|
MINPERIOD |
0001-01-01 00:00:00 |
Нижня межа |
MAXPERIOD |
5999-11-01 00:00:00 |
Рядок «підсумок за всі періоди» в таблицях залишків |
8.3. Таблиці метаданих
-- ============ РЕПОЗИТОРІЙ МЕТАДАНИХ ============
CREATE TABLE md_type ( -- реєстр усіх типів конфігурації
id smallint PRIMARY KEY,
kind varchar(20) NOT NULL, -- Catalog|Document|Enum|ChartOfAccounts|ChartOfCalcTypes|Register|...
name varchar(80) NOT NULL,
table_name varchar(63),
UNIQUE (kind, name)
);
CREATE TABLE md_register (
id smallint PRIMARY KEY, -- = md_type.id
name varchar(80) NOT NULL UNIQUE,
synonym varchar(255),
comment text,
kind varchar(12) NOT NULL
CHECK (kind IN ('Information','Accumulation','Accounting','Calculation')),
-- загальні
write_mode varchar(24) NOT NULL DEFAULT 'SubordinateToRecorder'
CHECK (write_mode IN ('Independent','SubordinateToRecorder')),
periodicity varchar(24) NOT NULL DEFAULT 'Nonperiodical'
CHECK (periodicity IN ('Nonperiodical','WithinSecond','WithinDay','WithinMonth',
'WithinQuarter','WithinYear','ByRecorderPosition')),
lock_mode varchar(12) NOT NULL DEFAULT 'Managed',
use_rls boolean NOT NULL DEFAULT false,
partition_mode varchar(20) NOT NULL DEFAULT 'None',
partition_step varchar(10),
-- РН
accumulation_kind varchar(10) CHECK (accumulation_kind IN ('Balance','Turnover')),
enable_bal_totals boolean NOT NULL DEFAULT true,
enable_tno_totals boolean NOT NULL DEFAULT false,
tno_totals_period varchar(6) CHECK (tno_totals_period IN ('Day','Month')),
split_totals boolean NOT NULL DEFAULT false,
split_count smallint NOT NULL DEFAULT 1,
aggregates_mode varchar(10),
-- РБ
chart_of_accounts_id smallint REFERENCES md_type(id),
correspondence boolean NOT NULL DEFAULT true,
max_extdim_count smallint NOT NULL DEFAULT 3,
-- РР
calc_types_plan_id smallint REFERENCES md_type(id),
use_action_period boolean NOT NULL DEFAULT false,
base_period_mode varchar(24)
CHECK (base_period_mode IN ('None','ByActionPeriod','ByRegistrationPeriod')),
schedule_register_id smallint REFERENCES md_register(id),
schedule_value_field smallint,
schedule_date_field smallint,
-- службове
physical_table varchar(63) NOT NULL,
struct_version int NOT NULL DEFAULT 1,
CONSTRAINT chk_kind_props CHECK (
(kind = 'Accumulation' AND accumulation_kind IS NOT NULL) OR
(kind = 'Accounting' AND chart_of_accounts_id IS NOT NULL) OR
(kind = 'Calculation' AND calc_types_plan_id IS NOT NULL) OR
(kind = 'Information')
)
);
CREATE TABLE md_register_field (
id smallint NOT NULL, -- порядковий номер поля (стабільний)
register_id smallint NOT NULL REFERENCES md_register(id) ON DELETE CASCADE,
name varchar(80) NOT NULL,
synonym varchar(255),
role varchar(12) NOT NULL CHECK (role IN ('Dimension','Resource','Attribute')),
data_type_expr text NOT NULL, -- серіалізований опис типу (може бути складеним)
is_composite boolean NOT NULL DEFAULT false,
precision_ smallint,
scale_ smallint,
length_ int,
index_mode varchar(28) NOT NULL DEFAULT 'None'
CHECK (index_mode IN ('None','Index','IndexWithAddDimensions')),
is_master boolean NOT NULL DEFAULT false, -- «Ведуче»
in_default_filter boolean NOT NULL DEFAULT false,
deny_empty boolean NOT NULL DEFAULT false,
use_in_totals boolean NOT NULL DEFAULT true,
-- РБ
is_balance boolean NOT NULL DEFAULT false,
accounting_flag varchar(80),
extdim_flag varchar(80),
-- РР
is_base_dimension boolean NOT NULL DEFAULT false,
-- фізика
column_prefix varchar(8) NOT NULL, -- 'd3','r1','a2'
PRIMARY KEY (register_id, id),
UNIQUE (register_id, name)
);
CREATE TABLE md_register_recorder (
register_id smallint NOT NULL REFERENCES md_register(id) ON DELETE CASCADE,
doc_type_id smallint NOT NULL REFERENCES md_type(id),
PRIMARY KEY (register_id, doc_type_id)
);
CREATE TABLE md_register_aggregate (
id smallint NOT NULL,
register_id smallint NOT NULL REFERENCES md_register(id) ON DELETE CASCADE,
name varchar(80) NOT NULL,
periodicity varchar(8) NOT NULL CHECK (periodicity IN ('Day','Month','Quarter','Year')),
use_mode varchar(12) NOT NULL DEFAULT 'Auto',
is_realtime boolean NOT NULL DEFAULT false,
actual_upto timestamp(0),
physical_table varchar(63) NOT NULL,
PRIMARY KEY (register_id, id)
);
CREATE TABLE md_register_aggregate_dim (
register_id smallint NOT NULL,
aggregate_id smallint NOT NULL,
field_id smallint NOT NULL,
PRIMARY KEY (register_id, aggregate_id, field_id),
FOREIGN KEY (register_id, aggregate_id) REFERENCES md_register_aggregate(register_id, id) ON DELETE CASCADE
);
CREATE TABLE md_register_recalc (
id smallint NOT NULL,
register_id smallint NOT NULL REFERENCES md_register(id) ON DELETE CASCADE,
name varchar(80) NOT NULL,
physical_table varchar(63) NOT NULL,
PRIMARY KEY (register_id, id)
);
CREATE TABLE md_register_recalc_dim (
register_id smallint NOT NULL,
recalc_id smallint NOT NULL,
name varchar(80) NOT NULL,
reg_field_id smallint NOT NULL, -- вимірювання основного регістру
column_prefix varchar(8) NOT NULL,
PRIMARY KEY (register_id, recalc_id, name),
FOREIGN KEY (register_id, recalc_id) REFERENCES md_register_recalc(register_id, id) ON DELETE CASCADE
);8.4. Регістр відомостей
8.4.1. Незалежний періодичний (приклад: КурсиВалют)
CREATE TABLE rg_i_0007 (
period timestamp(0) NOT NULL, -- Період (усічений до дня: WithinDay)
d1_r uuid NOT NULL, -- Вимірювання «Валюта»
r1 numeric(15,4) NOT NULL, -- Ресурс «Курс»
r2 numeric(10,0) NOT NULL DEFAULT 1, -- Ресурс «Кратність»
CONSTRAINT pk_rg_i_0007 PRIMARY KEY (period, d1_r)
);
-- Індекс для зрізу останніх у розрізі вимірювання
CREATE INDEX ix_rg_i_0007_d1 ON rg_i_0007 (d1_r, period DESC) INCLUDE (r1, r2);
ALTER TABLE rg_i_0007 ADD CONSTRAINT fk_rg_i_0007_d1
FOREIGN KEY (d1_r) REFERENCES cat_0003(ref); -- Довідник.Валюти8.4.2. Підпорядкований реєстратору
CREATE TABLE rg_i_0008 (
rec_t smallint NOT NULL,
rec_r uuid NOT NULL,
line_no int NOT NULL,
period timestamp(0) NOT NULL,
active boolean NOT NULL DEFAULT true,
d1_r uuid NOT NULL,
r1 numeric(15,2) NOT NULL,
CONSTRAINT pk_rg_i_0008 PRIMARY KEY (rec_t, rec_r, line_no)
);
CREATE INDEX ix_rg_i_0008_period ON rg_i_0008 (period, rec_t, rec_r, line_no);
CREATE INDEX ix_rg_i_0008_d1 ON rg_i_0008 (d1_r, period) WHERE active;8.4.3. Незалежний неперіодичний
CREATE TABLE rg_i_0009 (
d1_r uuid NOT NULL,
d2_r uuid NOT NULL,
r1 varchar(200),
CONSTRAINT pk_rg_i_0009 PRIMARY KEY (d1_r, d2_r)
);8.5. Регістр накопичення
8.5.1. Таблиця рухів
-- ТовариНаСкладах: dims = Номенклатура(d1), Склад(d2), Партія(d3, складений), Характеристика(d4)
-- res = Кількість(r1), Сума(r2); attr = Коментар(a1)
CREATE TABLE rg_a_0012 (
rec_t smallint NOT NULL,
rec_r uuid NOT NULL,
line_no int NOT NULL,
period timestamp(0) NOT NULL,
active boolean NOT NULL DEFAULT true,
mvt smallint NOT NULL CHECK (mvt IN (0,1)), -- 0=Прихід, 1=Витрата
d1_r uuid NOT NULL,
d2_r uuid NOT NULL,
d3_t smallint NOT NULL, -- складене вимірювання «Партія»
d3_r uuid NOT NULL,
d4_r uuid NOT NULL,
r1 numeric(15,3) NOT NULL DEFAULT 0,
r2 numeric(15,2) NOT NULL DEFAULT 0,
a1 varchar(200),
CONSTRAINT pk_rg_a_0012 PRIMARY KEY (rec_t, rec_r, line_no)
) PARTITION BY RANGE (period);
CREATE TABLE rg_a_0012_p2026m07 PARTITION OF rg_a_0012
FOR VALUES FROM ('2026-07-01') TO ('2026-08-01');
-- ... інші секції створюються регламентним завданням
CREATE INDEX ix_rg_a_0012_period ON rg_a_0012 (period, rec_t, rec_r, line_no);
CREATE INDEX ix_rg_a_0012_d1 ON rg_a_0012 (d1_r, period, d2_r) WHERE active; -- Номенклатура (Індексувати з дод. вимірюваннями)
CREATE INDEX ix_rg_a_0012_d2 ON rg_a_0012 (d2_r, period, d1_r) WHERE active; -- Склад8.5.2. Таблиця підсумків залишків
CREATE TABLE rg_a_0012_tb (
period timestamp(0) NOT NULL, -- початок місяця; MAXPERIOD = підсумок за всі періоди
splitter smallint NOT NULL DEFAULT 0,
d1_r uuid NOT NULL,
d2_r uuid NOT NULL,
d3_t smallint NOT NULL,
d3_r uuid NOT NULL,
d4_r uuid NOT NULL,
r1 numeric(20,3) NOT NULL, -- Кількість, знаковий залишок
r2 numeric(20,2) NOT NULL, -- Сума
CONSTRAINT pk_rg_a_0012_tb PRIMARY KEY (period, d1_r, d2_r, d3_t, d3_r, d4_r, splitter)
);
-- покривний індекс для типового відбору «залишки по складу»
CREATE INDEX ix_rg_a_0012_tb_d2 ON rg_a_0012_tb (d2_r, period, d1_r) INCLUDE (r1, r2);Семантика: рядок з period = P містить залишок на початок періоду P. Рядок з period = MAXPERIOD — поточний залишок за всі періоди (використовується для запитів «залишки на зараз», найчастіший кейс).
8.5.3. Таблиця підсумків оборотів
CREATE TABLE rg_a_0012_tt (
period timestamp(0) NOT NULL, -- початок періоду агрегації (день/місяць)
splitter smallint NOT NULL DEFAULT 0,
d1_r uuid NOT NULL,
d2_r uuid NOT NULL,
d3_t smallint NOT NULL,
d3_r uuid NOT NULL,
d4_r uuid NOT NULL,
r1_in numeric(20,3) NOT NULL DEFAULT 0, -- Кількість Прихід
r1_out numeric(20,3) NOT NULL DEFAULT 0, -- Кількість Витрата
r2_in numeric(20,2) NOT NULL DEFAULT 0,
r2_out numeric(20,2) NOT NULL DEFAULT 0,
CONSTRAINT pk_rg_a_0012_tt PRIMARY KEY (period, d1_r, d2_r, d3_t, d3_r, d4_r, splitter)
);8.5.4. Службова таблиця налаштувань
CREATE TABLE rg_a_0012_opt (
id smallint PRIMARY KEY DEFAULT 1 CHECK (id = 1),
totals_actual_to timestamp(0) NOT NULL, -- ТА: підсумки розраховані до
use_bal_totals boolean NOT NULL DEFAULT true,
use_tno_totals boolean NOT NULL DEFAULT true,
min_period timestamp(0), -- період першого руху
last_recalc_at timestamp(0),
split_merge_at timestamp(0)
);8.5.5. Агрегат (для РН виду «Обороти»)
-- Агрегат «Продажі за місяцями по номенклатурі та складу» (без Партії, без Характеристики)
CREATE TABLE rg_a_0031_ag1 (
period timestamp(0) NOT NULL,
d1_r uuid NOT NULL,
d2_r uuid NOT NULL,
r1 numeric(20,3) NOT NULL,
r2 numeric(20,2) NOT NULL,
CONSTRAINT pk_rg_a_0031_ag1 PRIMARY KEY (period, d1_r, d2_r)
);8.6. Регістр бухгалтерії
8.6.1. План рахунків (супутні таблиці)
CREATE TABLE coa_0005 ( -- ПланРахунків.Госпрозрахунковий
ref uuid PRIMARY KEY,
parent_ref uuid REFERENCES coa_0005(ref),
code varchar(20) NOT NULL,
order_code varchar(40) NOT NULL, -- впорядкований код для ієрархічних запитів
name varchar(150) NOT NULL,
acc_type smallint NOT NULL, -- 0=Активний,1=Пасивний,2=Активно-пасивний
off_balance boolean NOT NULL DEFAULT false,
ed_count smallint NOT NULL DEFAULT 0, -- фактична кількість субконто
fl_qty boolean NOT NULL DEFAULT false, -- ознака обліку «Кількісний»
fl_cur boolean NOT NULL DEFAULT false, -- ознака обліку «Валютний»
marked_del boolean NOT NULL DEFAULT false
);
CREATE UNIQUE INDEX ux_coa_0005_code ON coa_0005 (code);
CREATE TABLE coa_0005_ed ( -- ТЧ «ВидиСубконто»
ref uuid NOT NULL REFERENCES coa_0005(ref) ON DELETE CASCADE,
line_no int NOT NULL,
ed_type_r uuid NOT NULL, -- ПланВидівХарактеристик.ВидиСубконто
only_tno boolean NOT NULL DEFAULT false, -- «Тільки обороти»
sum_flag boolean NOT NULL DEFAULT true, -- «Підсумкова сума»
fl_qty boolean NOT NULL DEFAULT false, -- ознака обліку субконто «Кількісний»
PRIMARY KEY (ref, line_no)
);8.6.2. Таблиця проводок (з кореспонденцією)
-- Госпрозрахунковий: dims = Організація(d1, балансове), Підрозділ(d2, небалансове)
-- res = Сума(r1, балансовий), Кількість(r2, небалансовий), ВалютнаСума(r3, небалансовий)
-- attr = Зміст(a1), Валюта(a2); max_extdim_count = 3
CREATE TABLE rg_b_0020 (
rec_t smallint NOT NULL,
rec_r uuid NOT NULL,
line_no int NOT NULL,
period timestamp(0) NOT NULL,
active boolean NOT NULL DEFAULT true,
acc_dt_r uuid NOT NULL REFERENCES coa_0005(ref),
acc_kt_r uuid NOT NULL REFERENCES coa_0005(ref),
-- балансове вимірювання: одне значення на проводку
d1_r uuid NOT NULL,
-- небалансове вимірювання: окремо для Дт і Кт
d2_dt_r uuid NOT NULL,
d2_kt_r uuid NOT NULL,
-- субконто Дт (3 слоти, складений тип: ПВХ-значення)
ed_dt1_t smallint, ed_dt1_r uuid,
ed_dt2_t smallint, ed_dt2_r uuid,
ed_dt3_t smallint, ed_dt3_r uuid,
ed_dt_h bigint NOT NULL DEFAULT 0, -- хеш набору субконто Дт (для JOIN з підсумками)
-- субконто Кт
ed_kt1_t smallint, ed_kt1_r uuid,
ed_kt2_t smallint, ed_kt2_r uuid,
ed_kt3_t smallint, ed_kt3_r uuid,
ed_kt_h bigint NOT NULL DEFAULT 0,
-- балансовий ресурс: одне значення
r1 numeric(20,2) NOT NULL DEFAULT 0, -- Сума
-- небалансові ресурси: пара Дт/Кт
r2_dt numeric(20,3) NOT NULL DEFAULT 0, -- Кількість Дт
r2_kt numeric(20,3) NOT NULL DEFAULT 0, -- Кількість Кт
r3_dt numeric(20,2) NOT NULL DEFAULT 0, -- ВалютнаСума Дт
r3_kt numeric(20,2) NOT NULL DEFAULT 0,
a1 varchar(255),
a2_r uuid,
CONSTRAINT pk_rg_b_0020 PRIMARY KEY (rec_t, rec_r, line_no)
) PARTITION BY RANGE (period);
CREATE INDEX ix_rg_b_0020_period ON rg_b_0020 (period, rec_t, rec_r, line_no);
CREATE INDEX ix_rg_b_0020_acc_dt ON rg_b_0020 (acc_dt_r, period) WHERE active;
CREATE INDEX ix_rg_b_0020_acc_kt ON rg_b_0020 (acc_kt_r, period) WHERE active;
CREATE INDEX ix_rg_b_0020_ed_dt ON rg_b_0020 (ed_dt1_t, ed_dt1_r, period) WHERE active;8.6.3. Таблиця проводок (без кореспонденції)
CREATE TABLE rg_b_0021 (
rec_t smallint NOT NULL,
rec_r uuid NOT NULL,
line_no int NOT NULL,
period timestamp(0) NOT NULL,
active boolean NOT NULL DEFAULT true,
acc_mvt smallint NOT NULL CHECK (acc_mvt IN (0,1)), -- 0=Дебет, 1=Кредит
acc_r uuid NOT NULL REFERENCES coa_0006(ref),
d1_r uuid NOT NULL,
ed1_t smallint, ed1_r uuid,
ed2_t smallint, ed2_r uuid,
ed3_t smallint, ed3_r uuid,
ed_h bigint NOT NULL DEFAULT 0,
r1 numeric(20,2) NOT NULL DEFAULT 0,
CONSTRAINT pk_rg_b_0021 PRIMARY KEY (rec_t, rec_r, line_no)
);8.6.4. Підсумки РБ
Підсумки будуються на кількох «рівнях субконто» — щоб запит без відбору за субконто не сканував найдетальнішу таблицю.
-- Рівень 0: залишки по рахунку + балансові вимірювання (без субконто)
CREATE TABLE rg_b_0020_tb0 (
period timestamp(0) NOT NULL,
splitter smallint NOT NULL DEFAULT 0,
acc_r uuid NOT NULL,
d1_r uuid NOT NULL, -- Організація (балансове)
r1 numeric(24,2) NOT NULL, -- Сума: >0 = дебетовий, <0 = кредитовий залишок
r2 numeric(24,3) NOT NULL, -- Кількість
r3 numeric(24,2) NOT NULL,
CONSTRAINT pk_rg_b_0020_tb0 PRIMARY KEY (period, acc_r, d1_r, splitter)
);
-- Рівень 1..3: залишки з розшифровкою по субконто (тільки для рахунків з ed_count >= k)
CREATE TABLE rg_b_0020_tb1 (
period timestamp(0) NOT NULL, splitter smallint NOT NULL DEFAULT 0,
acc_r uuid NOT NULL, d1_r uuid NOT NULL,
ed1_t smallint NOT NULL, ed1_r uuid NOT NULL,
r1 numeric(24,2) NOT NULL, r2 numeric(24,3) NOT NULL, r3 numeric(24,2) NOT NULL,
CONSTRAINT pk_rg_b_0020_tb1 PRIMARY KEY (period, acc_r, d1_r, ed1_t, ed1_r, splitter)
);
CREATE TABLE rg_b_0020_tb2 (
period timestamp(0) NOT NULL, splitter smallint NOT NULL DEFAULT 0,
acc_r uuid NOT NULL, d1_r uuid NOT NULL,
ed1_t smallint NOT NULL, ed1_r uuid NOT NULL,
ed2_t smallint NOT NULL, ed2_r uuid NOT NULL,
r1 numeric(24,2) NOT NULL, r2 numeric(24,3) NOT NULL, r3 numeric(24,2) NOT NULL,
CONSTRAINT pk_rg_b_0020_tb2 PRIMARY KEY (period, acc_r, d1_r, ed1_t, ed1_r, ed2_t, ed2_r, splitter)
);
-- rg_b_0020_tb3 — аналогічно, 3 субконто
-- Обороти (аналогічні рівні): tt0..tt3
CREATE TABLE rg_b_0020_tt0 (
period timestamp(0) NOT NULL,
splitter smallint NOT NULL DEFAULT 0,
acc_r uuid NOT NULL,
d1_r uuid NOT NULL,
r1_dt numeric(24,2) NOT NULL, r1_kt numeric(24,2) NOT NULL,
r2_dt numeric(24,3) NOT NULL, r2_kt numeric(24,3) NOT NULL,
r3_dt numeric(24,2) NOT NULL, r3_kt numeric(24,2) NOT NULL,
CONSTRAINT pk_rg_b_0020_tt0 PRIMARY KEY (period, acc_r, d1_r, splitter)
);
-- Обороти між рахунками (кореспонденції)
CREATE TABLE rg_b_0020_tc (
period timestamp(0) NOT NULL,
splitter smallint NOT NULL DEFAULT 0,
acc_dt_r uuid NOT NULL,
acc_kt_r uuid NOT NULL,
d1_r uuid NOT NULL,
r1 numeric(24,2) NOT NULL,
CONSTRAINT pk_rg_b_0020_tc PRIMARY KEY (period, acc_dt_r, acc_kt_r, d1_r, splitter)
);Правило запису рівнів: для проводки з рахунком, що має ed_count = k, оновлюються таблиці tb0 … tbK (k+1 таблиць). Рахунки без субконто оновлюють лише tb0. Субконто з ознакою «Тільки обороти» не потрапляють у tb*, лише в tt*.
8.7. Регістр розрахунку
-- Нарахування: dims = Співробітник(d1, базове), Підрозділ(d2)
-- res = Результат(r1), ВідпрацьованоДнів(r2), НормаДнів(r3)
-- attr = Графік(a1)
CREATE TABLE rg_c_0040 (
rec_t smallint NOT NULL,
rec_r uuid NOT NULL,
line_no int NOT NULL,
period timestamp(0) NOT NULL, -- період реєстрації (початок місяця)
active boolean NOT NULL DEFAULT true,
reversal boolean NOT NULL DEFAULT false, -- Сторно
calc_type_r uuid NOT NULL, -- ВидРозрахунку → pct_0011(ref)
act_begin timestamp(0) NOT NULL, -- ПеріодДіїПочаток
act_end timestamp(0) NOT NULL, -- ПеріодДіїКінець
base_begin timestamp(0), -- БазовийПеріодПочаток
base_end timestamp(0), -- БазовийПеріодКінець
d1_r uuid NOT NULL,
d2_r uuid NOT NULL,
r1 numeric(15,2) NOT NULL DEFAULT 0,
r2 numeric(5,0) NOT NULL DEFAULT 0,
r3 numeric(5,0) NOT NULL DEFAULT 0,
a1_r uuid,
CONSTRAINT pk_rg_c_0040 PRIMARY KEY (rec_t, rec_r, line_no),
CONSTRAINT chk_rg_c_0040_act CHECK (act_end >= act_begin)
);
CREATE INDEX ix_rg_c_0040_period ON rg_c_0040 (period, rec_t, rec_r, line_no);
CREATE INDEX ix_rg_c_0040_d1 ON rg_c_0040 (d1_r, period, calc_type_r) WHERE active;
CREATE INDEX ix_rg_c_0040_act ON rg_c_0040 (d1_r, act_begin, act_end) WHERE active;
CREATE INDEX ix_rg_c_0040_base ON rg_c_0040 (d1_r, calc_type_r, base_begin, base_end) WHERE active;
-- Фактичні періоди дії (після витіснення). Один запис → 0..N інтервалів.
CREATE TABLE rg_c_0040_ap (
rec_t smallint NOT NULL,
rec_r uuid NOT NULL,
line_no int NOT NULL,
interval_no smallint NOT NULL,
calc_type_r uuid NOT NULL,
d1_r uuid NOT NULL, -- денормалізація базового вимірювання для швидкого JOIN
fact_begin timestamp(0) NOT NULL,
fact_end timestamp(0) NOT NULL,
CONSTRAINT pk_rg_c_0040_ap PRIMARY KEY (rec_t, rec_r, line_no, interval_no),
CONSTRAINT fk_rg_c_0040_ap FOREIGN KEY (rec_t, rec_r, line_no)
REFERENCES rg_c_0040 (rec_t, rec_r, line_no) ON DELETE CASCADE
);
CREATE INDEX ix_rg_c_0040_ap_d1 ON rg_c_0040_ap (d1_r, fact_begin, fact_end);
-- Перерахунок «ПерерахунокЗаБазою»
CREATE TABLE rg_c_0040_rc1 (
rec_t smallint NOT NULL, -- реєстратор запису, який треба перерахувати
rec_r uuid NOT NULL,
calc_type_r uuid NOT NULL, -- вид розрахунку, що потребує перерахунку
d1_r uuid NOT NULL, -- вимірювання перерахунку «Співробітник»
src_rec_t smallint NOT NULL, -- реєстратор, що спричинив перерахунок
src_rec_r uuid NOT NULL,
created_at timestamp(0) NOT NULL DEFAULT now(),
CONSTRAINT pk_rg_c_0040_rc1 PRIMARY KEY (rec_t, rec_r, calc_type_r, d1_r)
);
CREATE INDEX ix_rg_c_0040_rc1_src ON rg_c_0040_rc1 (src_rec_t, src_rec_r);8.8. Зведення: які таблиці породжує кожен тип регістру
РВ (незалежний) → rg_i_<id>
РВ (підпорядкований) → rg_i_<id>
РН (Залишки) → rg_a_<id>
rg_a_<id>_tb (якщо ДозволитиПідсумкиЗалишків)
rg_a_<id>_tt (якщо ДозволитиПідсумкиОборотів)
rg_a_<id>_opt
РН (Обороти) → rg_a_<id>
rg_a_<id>_tt
rg_a_<id>_ag1..agN (агрегати)
rg_a_<id>_opt
РБ → rg_b_<id>
rg_b_<id>_tb0 .. _tbK (K = МаксКількістьСубконто)
rg_b_<id>_tt0 .. _ttK
rg_b_<id>_tc
rg_b_<id>_opt
РР → rg_c_<id>
rg_c_<id>_ap
rg_c_<id>_rc1 .. _rcM (перерахунки)
Оцінка: для РБ з МаксКількістьСубконто = 3 генерується 10 фізичних таблиць на один регістр.
9. ER-моделі
9.1. ER-модель метаданих (репозиторій)
<plantuml> @startuml hide circle skinparam linetype ortho skinparam shadowing false
entity "md_type" as T {
* id : smallint <<PK>> -- * kind : varchar(20) * name : varchar(80) table_name : varchar(63)
}
entity "md_register" as R {
* id : smallint <<PK,FK→md_type>> -- * name : varchar(80) <> * kind : varchar(12) * write_mode : varchar(24) * periodicity : varchar(24) accumulation_kind : varchar(10) enable_bal_totals : boolean enable_tno_totals : boolean tno_totals_period : varchar(6) split_totals : boolean chart_of_accounts_id : smallint <<FK>> correspondence : boolean max_extdim_count : smallint calc_types_plan_id : smallint <<FK>> use_action_period : boolean base_period_mode : varchar(24) schedule_register_id : smallint <<FK>> * physical_table : varchar(63) * struct_version : int
}
entity "md_register_field" as F {
* register_id : smallint <<PK,FK>> * id : smallint <<PK>> -- * name : varchar(80) <> * role : Dimension|Resource|Attribute * data_type_expr : text is_composite : boolean index_mode : varchar(28) is_master : boolean is_balance : boolean is_base_dimension : boolean accounting_flag : varchar(80) * column_prefix : varchar(8)
}
entity "md_register_recorder" as RR {
* register_id : smallint <<PK,FK>> * doc_type_id : smallint <<PK,FK>>
}
entity "md_register_aggregate" as AG {
* register_id : smallint <<PK,FK>> * id : smallint <<PK>> -- * name : varchar(80) * periodicity : Day|Month|Quarter|Year is_realtime : boolean actual_upto : timestamp
}
entity "md_register_aggregate_dim" as AGD {
* register_id : smallint <<PK,FK>> * aggregate_id : smallint <<PK,FK>> * field_id : smallint <<PK,FK>>
}
entity "md_register_recalc" as RC {
* register_id : smallint <<PK,FK>> * id : smallint <<PK>> -- * name : varchar(80) * physical_table : varchar(63)
}
entity "md_register_recalc_dim" as RCD {
* register_id : smallint <<PK,FK>> * recalc_id : smallint <<PK,FK>> * name : varchar(80) <<PK>> -- * reg_field_id : smallint <<FK>>
}
T ||--o| R : "є регістром" T ||--o{ RR : "тип документа" R ||--|{ F : "має поля" R ||--o{ RR : "реєстратори" R ||--o{ AG : "агрегати" AG ||--|{ AGD : "склад вимірювань" F ||--o{ AGD : "вимірювання" R ||--o{ RC : "перерахунки" RC ||--|{ RCD : "вимірювання перерахунку" F ||--o{ RCD : "відповідає" R }o--o| T : "план рахунків" R }o--o| T : "план видів розрахунку" R }o--o| R : "графік часу" @enduml </plantuml>
9.2. ER-модель: регістр накопичення (вид «Залишки»)
<plantuml> @startuml hide circle skinparam linetype ortho skinparam shadowing false
entity "doc_0002\n«ПрихіднаНакладна»" as D1 {
* ref : uuid <<PK>> -- number : varchar date : timestamp posted : boolean
} entity "doc_0003\n«ВидатковаНакладна»" as D2 {
* ref : uuid <<PK>> -- number : varchar date : timestamp posted : boolean
} entity "cat_0001\n«Номенклатура»" as C1 {
* ref : uuid <<PK>> -- code, name
} entity "cat_0004\n«Склади»" as C2 {
* ref : uuid <<PK>> -- code, name
}
entity "rg_a_0012\n«ТовариНаСкладах» (рухи)" as M {
* rec_t : smallint <<PK>> * rec_r : uuid <<PK>> * line_no : int <<PK>> -- * period : timestamp(0) * active : boolean * mvt : smallint «0=Прихід,1=Витрата» * d1_r : uuid «Номенклатура» <<FK>> * d2_r : uuid «Склад» <<FK>> * d3_t : smallint «Партія:тип» * d3_r : uuid «Партія:посилання» * d4_r : uuid «Характеристика» <<FK>> * r1 : numeric(15,3) «Кількість» * r2 : numeric(15,2) «Сума» a1 : varchar(200) «Коментар»
}
entity "rg_a_0012_tb\nпідсумки залишків" as TB {
* period : timestamp(0) <<PK>> * d1_r : uuid <<PK>> * d2_r : uuid <<PK>> * d3_t : smallint <<PK>> * d3_r : uuid <<PK>> * d4_r : uuid <<PK>> * splitter : smallint <<PK>> -- * r1 : numeric(20,3) «Кількість (±)» * r2 : numeric(20,2) «Сума (±)»
}
entity "rg_a_0012_tt\nпідсумки оборотів" as TT {
* period : timestamp(0) <<PK>> * d1_r : uuid <<PK>> * d2_r : uuid <<PK>> * d3_t : smallint <<PK>> * d3_r : uuid <<PK>> * d4_r : uuid <<PK>> * splitter : smallint <<PK>> -- r1_in, r1_out : numeric(20,3) r2_in, r2_out : numeric(20,2)
}
entity "rg_a_0012_opt" as OPT {
* id : smallint = 1 <<PK>> -- * totals_actual_to : timestamp(0) use_bal_totals : boolean use_tno_totals : boolean min_period : timestamp(0)
}
D1 ||..o{ M : "реєстратор\n(поліморфний, без FK)" D2 ||..o{ M : "реєстратор\n(поліморфний, без FK)" C1 ||--o{ M : "d1_r" C2 ||--o{ M : "d2_r" M }o..|| TB : "агрегується в\n(підтримується платформою)" M }o..|| TT : "агрегується в" OPT ||..|| TB : "ТА підсумків"
note bottom of M
PK = (rec_t, rec_r, line_no) → усі рухи одного документа фізично суміжні Партиціонування: RANGE(period)
end note
note bottom of TB
period = MAXPERIOD ('5999-11-01')
→ рядок «залишок за всі періоди»
end note @enduml </plantuml>
9.3. ER-модель: регістр бухгалтерії
<plantuml> @startuml hide circle skinparam linetype ortho skinparam shadowing false
entity "coa_0005\n«План рахунків»" as A {
* ref : uuid <<PK>> -- parent_ref : uuid <<FK>> * code : varchar(20) <> order_code : varchar(40) * name : varchar(150) * acc_type : smallint «Акт/Пас/АП» off_balance : boolean ed_count : smallint fl_qty, fl_cur : boolean
} entity "coa_0005_ed\n«Види субконто рахунку»" as AE {
* ref : uuid <<PK,FK>> * line_no : int <<PK>> -- * ed_type_r : uuid <<FK>> only_tno : boolean sum_flag : boolean
} entity "cvt_0009\n«ПВХ.ВидиСубконто»" as VT {
* ref : uuid <<PK>> -- code, name value_type : text
}
entity "rg_b_0020\n«Госпрозрахунковий» (проводки)" as J {
* rec_t : smallint <<PK>> * rec_r : uuid <<PK>> * line_no : int <<PK>> -- * period : timestamp(0) * active : boolean * acc_dt_r : uuid <<FK>> * acc_kt_r : uuid <<FK>> * d1_r : uuid «Організація (балансове)» * d2_dt_r : uuid «Підрозділ Дт» * d2_kt_r : uuid «Підрозділ Кт» ed_dt1_t/_r .. ed_dt3_t/_r : «субконто Дт» ed_dt_h : bigint ed_kt1_t/_r .. ed_kt3_t/_r : «субконто Кт» ed_kt_h : bigint * r1 : numeric(20,2) «Сума (балансовий)» r2_dt, r2_kt : numeric(20,3) «Кількість» r3_dt, r3_kt : numeric(20,2) «ВалютнаСума» a1 : varchar(255) «Зміст» a2_r : uuid «Валюта»
}
entity "rg_b_0020_tb0\nзалишки, рівень 0" as B0 {
* period <<PK>> * acc_r <<PK>> * d1_r <<PK>> * splitter <<PK>> -- r1, r2, r3 : numeric «знакові»
} entity "rg_b_0020_tb1..tb3\nзалишки, рівні 1..3" as B1 {
* period <<PK>> * acc_r <<PK>> * d1_r <<PK>> * ed1_t, ed1_r <<PK>> * (ed2_t, ed2_r) <<PK>> * (ed3_t, ed3_r) <<PK>> * splitter <<PK>> -- r1, r2, r3 : numeric
} entity "rg_b_0020_tt0..tt3\nобороти за рівнями" as T0 {
* period <<PK>> * acc_r <<PK>> * d1_r <<PK>> * (ed…) <<PK>> -- r1_dt, r1_kt r2_dt, r2_kt r3_dt, r3_kt
} entity "rg_b_0020_tc\nобороти Дт↔Кт" as TC {
* period <<PK>> * acc_dt_r <<PK>> * acc_kt_r <<PK>> * d1_r <<PK>> -- r1 : numeric(24,2)
}
A ||--o{ AE : "види субконто" VT ||--o{ AE : "тип субконто" A ||--o{ J : "acc_dt_r" A ||--o{ J : "acc_kt_r" A ||--o{ B0 : "acc_r" A ||--o{ B1 : "acc_r" J }o..|| B0 : "агрегується (Дт: +, Кт: −)" J }o..|| B1 : "агрегується, якщо ed_count ≥ k" J }o..|| T0 : "агрегується" J }o..|| TC : "агрегується"
note right of J
Балансовий ресурс r1 — одне значення, контроль Σ(Дт) = Σ(Кт) у межах реєстратора. Небалансові ресурси — пара Дт/Кт. Субконто — слоти (див. Рішення №2).
end note @enduml </plantuml>
9.4. ER-модель: регістр розрахунку
<plantuml> @startuml hide circle skinparam linetype ortho skinparam shadowing false
entity "pct_0011\n«ПВР.ОсновніНарахування»" as P {
* ref : uuid <<PK>> -- code, name use_action_period : boolean base_dependency : varchar(24)
} entity "pct_0011_base\n«Базові види розрахунку»" as PB {
* ref <<PK,FK>> * line_no <<PK>> -- * base_type_r : uuid <<FK>>
} entity "pct_0011_displace\n«Витісняючі види розрахунку»" as PD {
* ref <<PK,FK>> * line_no <<PK>> -- * displacing_type_r : uuid <<FK>>
} entity "pct_0011_lead\n«Ведучі види розрахунку»" as PL {
* ref <<PK,FK>> * line_no <<PK>> -- * leading_type_r : uuid <<FK>>
}
entity "rg_c_0040\n«Нарахування» (рухи)" as CR {
* rec_t : smallint <<PK>> * rec_r : uuid <<PK>> * line_no : int <<PK>> -- * period : timestamp(0) «період реєстрації» * active : boolean * reversal : boolean «Сторно» * calc_type_r : uuid <<FK>> * act_begin : timestamp(0) * act_end : timestamp(0) base_begin : timestamp(0) base_end : timestamp(0) * d1_r : uuid «Співробітник (базове)» * d2_r : uuid «Підрозділ» r1 : numeric «Результат» r2 : numeric «ВідпрацьованоДнів» r3 : numeric «НормаДнів»
}
entity "rg_c_0040_ap\nфактичні періоди дії" as AP {
* rec_t <<PK,FK>> * rec_r <<PK,FK>> * line_no <<PK,FK>> * interval_no : smallint <<PK>> -- * calc_type_r : uuid * d1_r : uuid * fact_begin : timestamp(0) * fact_end : timestamp(0)
}
entity "rg_c_0040_rc1\n«ПерерахунокЗаБазою»" as RC {
* rec_t <<PK>> * rec_r <<PK>> * calc_type_r <<PK>> * d1_r <<PK>> -- src_rec_t, src_rec_r created_at
}
entity "rg_i_0015\n«ГрафікРоботи» (РВ)" as SCH {
* d1_r : uuid <<PK>> «ВидГрафіка» * d2_d : timestamp(0) <<PK>> «ДатаГрафіка» -- r1 : numeric «Значення»
}
entity "doc_0007\n«НарахуванняЗарплати»" as DOC {
* ref : uuid <<PK>> -- number, date, posted
}
P ||--o{ CR : "calc_type_r" P ||--o{ PB : "базові" P ||--o{ PD : "витісняючі" P ||--o{ PL : "ведучі" DOC ||..o{ CR : "реєстратор" CR ||--|{ AP : "1 запис → 0..N\nфактичних інтервалів" CR }o..o{ RC : "потребує перерахунку" SCH }o..o{ CR : "ДаніГрафіка\n(віртуальна таблиця)" CR }o..o{ CR : "БазаРозрахунку\n(self-join через PB\nта базові вимірювання)" @enduml </plantuml>
9.5. Зведена концептуальна ER (усі типи)
Diagrams error (with dot command): Warning: flat edge between adjacent nodes one of which has a record shape - replace records with HTML-like labels Edge MDF -> MD Error: lost MD MDF edge
Легенда:
(PK*)— ключ для незалежного РВ;(PK**)— для підпорядкованого.- Пунктир «реєстратор» — логічний зв'язок без FK (поліморфне посилання, підтримується платформою).
- Пунктир «генерує DDL» — метадані → фізична схема (не зв'язок даних).
10. Ключові алгоритми
10.1. Запис набору записів (режим «Заміщати»)
ЗАПИСАТИ_НАБІР(регістр R, відбір S, записи N[]):
1. Перевірити права (Додавання/Зміна)
2. Валідація: тип реєстратора ∈ md_register_recorder;
вимірювання з deny_empty ≠ порожнє;
для РБ — Σ(r1 де балансовий) по Дт = по Кт у межах реєстратора
3. У транзакції:
3.1. СТАРІ := SELECT * FROM rg_x WHERE <відбір S> FOR UPDATE
3.2. Δ := АГРЕГУВАТИ(N, +1) ⊕ АГРЕГУВАТИ(СТАРІ, −1) // тільки active
3.3. DELETE FROM rg_x WHERE <відбір S>
3.4. INSERT INTO rg_x SELECT ... FROM N
3.5. ОНОВИТИ_ПІДСУМКИ(R, Δ) // див. 10.2
3.6. ЯКЩО R.kind = Calculation:
ПЕРЕРАХУВАТИ_ФАКТИЧНІ_ПЕРІОДИ(R, зачеплені вимірювання) // 10.4
ЗАРЕЄСТРУВАТИ_ПЕРЕРАХУНКИ(R, N ∪ СТАРІ) // 10.510.2. Інкрементальне оновлення підсумків залишків
-- Δ (delta) — тимчасова таблиця: (period_month, d1_r, d2_r, d3_t, d3_r, d4_r, dr1, dr2)
-- де dr = Σ (CASE WHEN mvt = 0 THEN +значення ELSE -значення END)
-- Крок 1. Кумулятивні залишки: рух у місяці M впливає на підсумки всіх періодів > M
-- та на рядок MAXPERIOD.
INSERT INTO rg_a_0012_tb AS t (period, splitter, d1_r, d2_r, d3_t, d3_r, d4_r, r1, r2)
SELECT p.period, :splitter, d.d1_r, d.d2_r, d.d3_t, d.d3_r, d.d4_r, d.dr1, d.dr2
FROM delta d
CROSS JOIN LATERAL (
SELECT period FROM totals_periods
WHERE period > d.period_month
AND period <= (SELECT totals_actual_to FROM rg_a_0012_opt)
UNION ALL SELECT TIMESTAMP '5999-11-01' -- MAXPERIOD
) p
ORDER BY 1,3,4,5,6,7 -- детермінований порядок → без дедлоків
ON CONFLICT (period, d1_r, d2_r, d3_t, d3_r, d4_r, splitter)
DO UPDATE SET r1 = t.r1 + EXCLUDED.r1,
r2 = t.r2 + EXCLUDED.r2;
-- Крок 2. Прибирання нульових рядків (регламентно, не в транзакції проведення)
DELETE FROM rg_a_0012_tb WHERE r1 = 0 AND r2 = 0 AND period <> TIMESTAMP '5999-11-01';10.3. Трансляція віртуальної таблиці «Залишки»
-- РегістрНакопичення.ТовариНаСкладах.Залишки(&Період, Склад = &Склад)
WITH boundary AS (
SELECT COALESCE(MAX(period), TIMESTAMP '0001-01-01') AS p
FROM totals_periods
WHERE period <= :Період
AND period <= (SELECT totals_actual_to FROM rg_a_0012_opt)
),
base AS ( -- залишок на межу періоду з підсумків
SELECT t.d1_r, t.d2_r, t.d3_t, t.d3_r, t.d4_r,
SUM(t.r1) AS r1, SUM(t.r2) AS r2
FROM rg_a_0012_tb t, boundary b
WHERE t.period = b.p
AND t.d2_r = :Склад -- push-down параметра ВТ
GROUP BY 1,2,3,4,5
),
tail AS ( -- «живі» рухи після межі й до &Період
SELECT m.d1_r, m.d2_r, m.d3_t, m.d3_r, m.d4_r,
SUM(CASE WHEN m.mvt = 0 THEN m.r1 ELSE -m.r1 END) AS r1,
SUM(CASE WHEN m.mvt = 0 THEN m.r2 ELSE -m.r2 END) AS r2
FROM rg_a_0012 m, boundary b
WHERE m.active
AND m.period > b.p
AND m.period <= :Період
AND m.d2_r = :Склад
GROUP BY 1,2,3,4,5
)
SELECT d1_r AS "Номенклатура", d2_r AS "Склад",
d3_t, d3_r AS "Партія", d4_r AS "Характеристика",
SUM(r1) AS "КількістьЗалишок",
SUM(r2) AS "СумаЗалишок"
FROM (SELECT * FROM base UNION ALL SELECT * FROM tail) u
GROUP BY 1,2,3,4,5
HAVING SUM(r1) <> 0 OR SUM(r2) <> 0; -- нульові залишки не повертаютьсяОптимізація для найчастішого випадку: якщо :Період ≥ ТА і в регістрі немає рухів у майбутньому — base читається з рядка MAXPERIOD одним index seek, tail = ∅.
10.4. Розрахунок фактичних періодів дії (витіснення)
ПЕРЕРАХУВАТИ_ФАКТИЧНІ_ПЕРІОДИ(регістр R, множина ключів K):
ДЛЯ кожного ключа k ∈ K: // k = кортеж значень усіх вимірювань R
Z := SELECT * FROM rg_c_X
WHERE вимірювання = k AND active
ORDER BY <пріоритет виду розрахунку за ПВР>, period, line_no
// пріоритет: A витісняється B, якщо B ∈ ПВР[A].ВитісняючіВидиРозрахунку
DELETE FROM rg_c_X_ap WHERE (rec,line) ∈ Z
ДЛЯ кожного запису z ∈ Z (у порядку зростання пріоритету, тобто найслабші — першими):
I := { [z.act_begin, z.act_end] }
ДЛЯ кожного w ∈ Z, де ВидРозрахунку(w) витісняє ВидРозрахунку(z)
І перетинаються періоди реєстрації за правилами ПВР:
I := I \ [w.act_begin, w.act_end] // різниця множин інтервалів
n := 0
ДЛЯ кожного інтервалу i ∈ I (упорядковано):
INSERT INTO rg_c_X_ap
VALUES (z.rec_t, z.rec_r, z.line_no, n, z.calc_type_r, z.d1_r, i.begin, i.end)
n := n + 1Складність: O(|Z|² ) на ключ. Обмеження: якщо |Z| > 500 для одного ключа — попередження в журналі та перехід на алгоритм замітання (sweep line) O(|Z| log |Z|).
10.5. Реєстрація перерахунків
ЗАРЕЄСТРУВАТИ_ПЕРЕРАХУНКИ(регістр R, змінені записи Z):
ДЛЯ кожного перерахунку P ∈ R.Перерахунки:
ДЛЯ кожного z ∈ Z:
// знайти всі записи, для яких z є базовим
залежні := SELECT DISTINCT rec_t, rec_r, calc_type_r, <вимірювання P>
FROM rg_c_X q
JOIN pct_base b ON b.ref = q.calc_type_r
AND b.base_type_r = z.calc_type_r
WHERE q.active
AND <вимірювання P у q> = <вимірювання P у z>
AND ( (R.base_period_mode = 'ByActionPeriod'
AND [z.act_begin, z.act_end] ∩ [q.base_begin, q.base_end] ≠ ∅)
OR (R.base_period_mode = 'ByRegistrationPeriod'
AND z.period BETWEEN q.base_begin AND q.base_end) )
AND (q.rec_t, q.rec_r) ≠ (z.rec_t, z.rec_r)
INSERT INTO rg_c_X_rc<n> ... ON CONFLICT DO NOTHING10.6. Зріз останніх
-- РегістрВідомостей.КурсиВалют.ЗрізОстанніх(&Дата, Валюта В (&Список))
SELECT DISTINCT ON (r.d1_r)
r.d1_r AS "Валюта", r.period AS "Період", r.r1 AS "Курс", r.r2 AS "Кратність"
FROM rg_i_0007 r
WHERE r.period <= :Дата
AND r.d1_r = ANY (:Список)
ORDER BY r.d1_r, r.period DESC; -- використовує ix_rg_i_0007_d1 (d1_r, period DESC)
-- Для MS SQL: ROW_NUMBER() OVER (PARTITION BY d1_r ORDER BY period DESC) = 1
-- або OUTER APPLY (SELECT TOP 1 ...) для великих списків.11. Нефункціональні вимоги
11.1. Продуктивність
Еталонний стенд: 16 vCPU, 128 ГБ RAM, NVMe, PostgreSQL 15, БД 1,5 ТБ.
| № | Операція | Обсяг | Ціль (P95) |
|---|---|---|---|
| НФВ-01 | Залишки на поточну дату, відбір за 1 вимірюванням | 1 млрд рухів, 20 млн рядків підсумків | ≤ 200 мс |
| НФВ-02 | Залишки на довільну дату в межах ТА | те саме | ≤ 400 мс |
| НФВ-03 | Обороти за 12 місяців із групуванням | 1 млрд рухів | ≤ 2 с |
| НФВ-04 | Проведення документа зі 100 рядками рухів (3 регістри) | — | ≤ 300 мс |
| НФВ-05 | Оборотно-сальдова відомість по всіх рахунках за місяць | 200 млн проводок | ≤ 5 с |
| НФВ-06 | Розрахунок ЗП: 10 000 співробітників × 8 видів розрахунку | — | ≤ 10 хв |
| НФВ-07 | Зріз останніх по 5 000 елементів вимірювання | 50 млн записів РВ | ≤ 500 мс |
| НФВ-08 | Пропускна здатність проведення | — | ≥ 50 док/с (10 паралельних сеансів) |
| НФВ-09 | Перерахунок підсумків РН | 1 млрд рухів | ≤ 4 год (у вікні обслуговування) |
11.2. Масштабованість і обсяги
| Параметр | Значення |
|---|---|
| Макс. записів в одному регістрі | 1010 |
| Макс. кількість регістрів у конфігурації | 2 000 |
| Макс. одночасних сеансів проведення | 200 |
| Секціонування | Обов'язкове для таблиць > 100 млн записів |
11.3. Надійність і цілісність
- НФВ-10. Рухи та реєстратор змінюються в одній транзакції БД; часткове проведення неможливе.
- НФВ-11. Аварійне завершення сервера не залишає підсумки неузгодженими (гарантується транзакцією).
- НФВ-12. Сервіс перевірки цілісності (ФТ-41) виконується регламентно; розбіжність підсумків = інцидент P1.
11.4. Супроводжуваність
- НФВ-13. Реструктуризація регістру (додавання ресурсу/реквізиту) для таблиці ≤ 100 млн записів — ≤ 30 хв.
- НФВ-14. Додавання вимірювання вимагає перебудови підсумків; система повинна попереджати про оцінку часу до початку операції.
- НФВ-15. Усі DDL-операції — ідемпотентні та скриптовані (можливість застосувати вручну).
11.5. Сумісність
- НФВ-16. PostgreSQL 14+ та MS SQL Server 2019+ — обидві платформи з однаковою функціональністю.
- НФВ-17. Генератор DDL ізольований у шар «діалект СКБД».
12. Міграція існуючих структур K2 ERP
12.1. Етапи
| № | Етап | Результат |
|---|---|---|
| М1 | Інвентаризація | Реєстр наявних «регістроподібних» таблиць K2 з класифікацією за типом-мішенню (РВ/РН/РБ/РР). |
| М2 | Мапінг | Для кожної таблиці — метаопис цільового регістру + правила перетворення колонок. |
| М3 | Створення регістрів | Генерація метаданих і DDL у тестовому контурі. |
| М4 | Перенесення даних | ETL: історичні дані → рухи, з призначенням реєстратора (реальний документ або службовий «ВведенняЗалишків»). |
| М5 | Побудова підсумків | Повний перерахунок. |
| М6 | Звірка | Порівняння залишків/оборотів «стара система vs нова» на контрольних датах; допуск розбіжності = 0. |
| М7 | Перемикання коду | Заміна прямих SQL-звернень на віртуальні таблиці; період паралельної роботи ≥ 1 звітний період. |
| М8 | Виведення з експлуатації | Видалення старих таблиць після 2 закритих періодів. |
12.2. Проблемні випадки
| Випадок | Рішення |
|---|---|
| Історичні записи без документа-джерела | Службовий документ ВведенняЗалишків (один на період/розділ).
|
| Записи з дублями за ключем (для незалежного РВ) | Дедуплікація з протоколом; конфлікти — на ручний розбір. |
| Дробові періоди / некоректні дати (NULL, 1900-01-01) | Нормалізація до MINPERIOD; протокол. |
| Прикладний код, що пише в таблиці напряму | Заборона на рівні прав БД після М7; аудит звернень на етапі М3–М6. |
| Аналітика, відсутня в цільовій моделі | Перенесення в реквізити (не у вимірювання) або відмова від міграції з обґрунтуванням. |
13. Приймальні випробування
13.1. Функціональні тести (витяг)
| ID | Сценарій | Очікуваний результат |
|---|---|---|
| ФТ-Т-01 | Створити РВ періодичний незалежний, записати 2 записи з однаковим ключем | Другий запис заміщує перший; у таблиці 1 рядок |
| ФТ-Т-02 | Провести документ → 5 рухів РН | 5 записів у rg_a_*; підсумки MAXPERIOD змінилися на суму дельти
|
| ФТ-Т-03 | Скасувати проведення | 0 записів; підсумки повернулися до вихідних значень |
| ФТ-Т-04 | Видалити документ-реєстратор | Рухи видалені каскадно; перевірка цілісності — 0 помилок |
| ФТ-Т-05 | Записати рух заднім числом (−6 міс.) | Залишки на всі дати після руху коректні (звірка з повним перерахунком) |
| ФТ-Т-06 | Установити Активність = Хиба |
Запис не впливає на віртуальні таблиці; підсумки скориговані |
| ФТ-Т-07 | РБ: провести незбалансовану проводку (Дт ≠ Кт за балансовим ресурсом) | Помилка проведення, транзакція відкочена |
| ФТ-Т-08 | РБ: ОСВ по рахунку з 3 субконто | Підсумки за рівнями 0–3 узгоджені між собою (згортка tb3 → tb0) |
| ФТ-Т-09 | РР: сценарій «Оклад + Відпустка + Лікарняний» (розд. 7.5.1) | Фактичні періоди дії відповідають еталону; «Оклад» дає 2 інтервали |
| ФТ-Т-10 | РР: зміна базового запису | У таблиці перерахунку з'явився запис для залежного документа |
| ФТ-Т-11 | Паралельне проведення 2 документів за різними складами | Обидва проходять без очікування (розділення підсумків увімкнено) |
| ФТ-Т-12 | Паралельне проведення 2 документів за одним складом і одним товаром з контролем залишків | Один чекає, другий проходить; від'ємного залишку немає |
| ФТ-Т-13 | Перерахунок підсумків після хаотичних 106 операцій | Підсумки збігаються з повним перерахунком «з нуля» до копійки |
| ФТ-Т-14 | Реструктуризація: додати ресурс до РН із 10 млн записів | Дані збережені, підсумки перебудовані, час ≤ НФВ-13 |
| ФТ-Т-15 | RLS: користувач з обмеженням по організації | Віртуальні таблиці повертають лише дозволені дані |
13.2. Навантажувальні тести
| ID | Тест | Критерій |
|---|---|---|
| НТ-01 | Генерація 1 млрд рухів РН, заміри НФВ-01…НФВ-03 | Досягнення цілей |
| НТ-02 | Профіль «90 % рухів поточним періодом, 10 % заднім числом» — порівняння двох стратегій підсумків (розд. 10.2) | Обґрунтований вибір Рішення №3 |
| НТ-03 | Пікове проведення 50 док/с протягом 1 год | Відсутність дедлоків; P95 ≤ НФВ-04 |
| НТ-04 | РБ: субконто-слоти vs дочірня таблиця, 200 млн проводок | Обґрунтований вибір Рішення №2 |
| НТ-05 | Розрахунок ЗП на 10 000 співробітників | НФВ-06 |
| НТ-06 | Деградація за 24 год безперервної роботи | Приріст часу відгуку ≤ 15 % |
14. Етапи робіт та оцінка
| Етап | Зміст | Результат | Оцінка, чол.-міс. | Залежності |
|---|---|---|---|---|
| Е0 | Обстеження платформи, верифікація припущень П1–П6, ескізний проєкт | Затверджений архітектурний ескіз, уточнене ТЗ | 1,5 | — |
| Е1 | Метамодель + репозиторій + генератор DDL + конструктор (базовий) | Створення регістру будь-якого типу з генерацією схеми | 4 | Е0 |
| Е2 | Регістр відомостей: рантайм, набори записів, зрізи, форми | РВ у продуктиві | 2,5 | Е1 |
| Е3 | Регістр накопичення: рухи, підсумки, ТА, віртуальні таблиці | РН у продуктиві | 5 | Е2 |
| Е4 | Механізм проведення документів, блокування, оперативне/неоперативне проведення | Транзакційне проведення | 2,5 | Е3 |
| Е5 | Розширення мови запитів (віртуальні таблиці, транслятор, push-down) | Запити до ВТ | 4 | Е3 |
| Е6 | Регістр бухгалтерії + план рахунків + субконто + підсумки за рівнями | РБ у продуктиві | 6 | Е4, Е5 |
| Е7 | Регістр розрахунку + ПВР + витіснення + база + графіки + перерахунки | РР у продуктиві | 6 | Е4, Е5 |
| Е8 | Агрегати, розділення підсумків, порадник агрегатів | Оптимізація | 2,5 | Е3 |
| Е9 | Права доступу, RLS, аудит, сервіси цілісності | Безпека та адміністрування | 2 | Е6, Е7 |
| Е10 | Реструктуризація, міграція, інструменти звірки | Інструменти міграції | 2,5 | Е6, Е7 |
| Е11 | Навантажувальне тестування, оптимізація, документація | Приймальні випробування | 3 | усі |
| РАЗОМ | 41,5 | |||
Мінімальний життєздатний обсяг (MVP): Е0–Е5 (РВ + РН + проведення + запити) ≈ 19,5 чол.-міс. — покриває ~70 % прикладних сценаріїв K2 ERP.
15. Ризики
| ID | Ризик | Ймов. | Вплив | Пом'якшення |
|---|---|---|---|---|
| Р1 | У K2 ERP немає власної мови запитів → віртуальні таблиці нікуди вбудовувати | Сер. | Крит. | Верифікувати на Е0. План «Б»: віртуальні таблиці як параметризовані view/TVF + шар ORM. |
| Р2 | Конкуренція за таблицю підсумків на піках проведення | Вис. | Вис. | Розділення підсумків (сплітери), НТ-03, детермінований порядок MERGE. |
| Р3 | Рухи «заднім числом» роблять оновлення підсумків O(N періодів) | Вис. | Сер. | Рішення №3 (dirty months + регламентний перерахунок), НТ-02. |
| Р4 | «Широка» схема субконто → реструктуризація при зміні ліміту | Сер. | Сер. | Зафіксувати ліміт 5 із запасом; НТ-04; план «Б» — дочірня таблиця. |
| Р5 | Механізм витіснення РР — найскладніший і найпомилковіший вузол | Вис. | Вис. | Виділити в окремий модуль з еталонним набором з 50+ тест-кейсів; property-based тестування. |
| Р6 | Обсяг Е6+Е7 недооцінено (досвід 1С: це роки розробки) | Вис. | Вис. | MVP-підхід: спершу Е0–Е5, рішення про Е6/Е7 — після ретроспективи. |
| Р7 | Різна поведінка PostgreSQL і MS SQL (плани, MERGE, ізоляція) | Сер. | Сер. | Єдиний набір тестів на обох СКБД у CI від Е1. |
| Р8 | Міграція історичних даних без реєстраторів | Вис. | Сер. | Службовий документ «ВведенняЗалишків»; етап звірки М6 з нульовим допуском. |
| Р9 | Патентні / ліцензійні претензії щодо копіювання механізмів | Низ. | Вис. | Юридична експертиза до Е1: реалізація концепцій обліку (загальновідомих), а не коду/API; уникати дослівного копіювання найменувань API та документації. |
16. Додатки
Додаток А. Матриця відповідності «1С ↔ K2 ERP»
| Об'єкт / поняття 1С | Пропонований аналог K2 ERP | Примітка |
|---|---|---|
| РегистрСведений | Регістри.Відомостей.* |
Повний паритет |
| РегистрНакопления | Регістри.Накопичення.* |
Повний паритет |
| РегистрБухгалтерии | Регістри.Бухгалтерії.* |
Субконто зберігаються інакше (слоти) |
| РегистрРасчета | Регістри.Розрахунку.* |
Повний паритет |
| Регистратор | Реєстратор |
— |
| ВидДвижения | ВидРуху |
— |
| Итоги | Підсумки |
— |
| Точка актуальности итогов | ТА підсумків |
— |
| Разделение итогов | Розділення підсумків (splitter) |
— |
| Агрегаты | Агрегати |
— |
| Субконто / ПланВидовХарактеристик | Субконто / ПланВидівХарактеристик |
— |
| ПланВидовРасчета | ПланВидівРозрахунку |
— |
| Перерасчет | Перерахунок |
— |
| ПериодДействия / Вытеснение | ПеріодДії / Витіснення |
— |
| Границя послідовності | Не реалізується | Механізм визнано застарілим; замість нього — перепроведення за списком «брудних» документів |
| РегистрыСведений з режимом «ПоПозицииРегистратора» | ПоПозиціїРеєстратора |
Реалізується як (period, rec_t, rec_r, line_no) у ключі |
Додаток Б. Альтернатива зберігання субконто (план «Б» до Рішення №2)
-- Дочірня таблиця «ключ-значення» замість слотів
CREATE TABLE rg_b_0020_ed (
rec_t smallint NOT NULL,
rec_r uuid NOT NULL,
line_no int NOT NULL,
side smallint NOT NULL CHECK (side IN (0,1)), -- 0=Дт, 1=Кт
ed_no smallint NOT NULL, -- порядковий номер субконто рахунка
ed_type_r uuid NOT NULL, -- вид субконто
val_t smallint NOT NULL,
val_r uuid,
val_n numeric(38,10),
val_s varchar(150),
val_d timestamp(0),
PRIMARY KEY (rec_t, rec_r, line_no, side, ed_no),
FOREIGN KEY (rec_t, rec_r, line_no) REFERENCES rg_b_0020 (rec_t, rec_r, line_no) ON DELETE CASCADE
);
CREATE INDEX ix_rg_b_0020_ed_val ON rg_b_0020_ed (ed_type_r, val_t, val_r);| Критерій | Слоти (Рішення №2) | Дочірня таблиця (план «Б») |
|---|---|---|
| Відбір за 1 субконто | Index seek по колонці | JOIN + семі-з'єднання |
| Відбір за 3 субконто | 3 предикати в одному скані | 3 JOIN |
Зміна МаксКількістьСубконто |
ALTER TABLE (реструктуризація) | Без змін схеми |
| Розмір рядка проводки | +48 байт × 2 (Дт/Кт) | Базовий |
| Складність генератора DDL | Вища | Нижча |
| Рекомендація | Обрано за замовчуванням | Резерв за результатами НТ-04 |
Додаток В. Перелік вимог (реєстр трасування)
| ID | Розділ | Коротко | Пріоритет | Тест |
|---|---|---|---|---|
| ФТ-01…ФТ-07 | 7.1 | Набори записів, проведення | Must | ФТ-Т-02…04 |
| ФТ-08…ФТ-10 | 7.2 | Активність | Must | ФТ-Т-06 |
| ФТ-11…ФТ-13 | 7.3 | Віртуальні таблиці | Must | ФТ-Т-08, НТ-01 |
| ФТ-14…ФТ-18 | 7.4 | Підсумки, ТА | Must | ФТ-Т-05, ФТ-Т-13 |
| ФТ-19…ФТ-21 | 7.4.4 | Агрегати | Should | НТ-01 |
| ФТ-22…ФТ-30 | 7.5 | Механіка РР | Must (для Е7) | ФТ-Т-09, ФТ-Т-10 |
| ФТ-31…ФТ-36 | 7.7 | Блокування | Must | ФТ-Т-11, ФТ-Т-12, НТ-03 |
| ФТ-37…ФТ-39 | 7.8 | Права, RLS | Must | ФТ-Т-15 |
| ФТ-40…ФТ-42 | 7.9 | Аудит, цілісність | Should | ФТ-Т-04 |
| ФТ-К-01…06 | 6.8.4 | Конструктор | Should | — |
| НФВ-01…НФВ-09 | 11.1 | Продуктивність | Must | НТ-01…НТ-06 |
| НФВ-10…НФВ-12 | 11.3 | Цілісність | Must | ФТ-Т-13 |
| НФВ-13…НФВ-17 | 11.4–11.5 | Супровід, сумісність | Should | ФТ-Т-14 |
Додаток Г. Відкриті питання
| № | Питання | Кому | Термін |
|---|---|---|---|
| В1 | Чи існує в K2 ERP мова запитів / транслятор у SQL? (визначає Р1 та обсяг Е5) | Архітектор платформи | До Е0 |
| В2 | Формат ідентифікації об'єктів: UUID чи сурогатний int? | Архітектор платформи | До Е0 |
| В3 | Чи потрібна мілісекундна дискретність періоду? | Замовник | До Е1 |
| В4 | Максимальна кількість субконто: 3 чи 5? | Головний бухгалтер / методолог | До Е6 |
| В5 | Чи потрібен механізм «Границя послідовності»? (пропонується відмова) | Методолог | До Е3 |
| В6 | Пріоритет типів регістрів: чи достатньо MVP (РВ + РН) для першого релізу? | Замовник | До Е0 |
| В7 | Правова оцінка ризику Р9 | Юридична служба | До Е1 |
| В8 | Цільова СКБД для першої черги: тільки PostgreSQL чи одразу обидві? | Архітектор / ІТ-експлуатація | До Е1 |
| В9 | Міжпланова база розрахунку: чи потрібна можливість брати базу з іншого регістру розрахунку (Утримання ← ОсновніНарахування)? Якщо так — ФТ-25 і md_register потребують розширення полем base_register_id (див. Д.7.2) |
Методолог ЗП / архітектор | До Е7 |
| В10 | Чи прийнятна поведінка підпорядкованого РВ без контролю унікальності за (Період + Вимірювання)? (сумісність із 1С vs. захист від дублів — див. Д.3.2, антипатерн А-6) | Методолог | До Е2 |
Додаток Д. Каталог прикладів регістрів
Додаток містить прикладні приклади регістрів усіх типів і підвидів. Для кожного наведено: декларацію (DSL), пояснення проєктних рішень, приклад даних у фізичних таблицях і типовий запит. Приклади призначені для: (а) перевірки повноти метамоделі на етапі Е0; (б) формування набору приймальних тестів; (в) навчання прикладних розробників.
Д.1. Як обрати тип регістру (дерево рішень)

Контрольні питання при проєктуванні будь-якого регістру:
- Чи можна відповісти на всі прикладні питання лише полями цього регістру, без з'єднання з документами? Якщо ні — бракує реквізиту (не вимірювання!).
- Яка кардинальність таблиці підсумків = ∏(кількість значень вимірювань) × кількість періодів? Якщо > 108 — переглянути склад вимірювань.
- Чи потрібно фільтрувати/групувати за цим полем у звітах? Так → вимірювання. Ні, лише показувати → реквізит.
- Чи підсумовується поле? Так → ресурс. Ні → реквізит або вимірювання.
Д.2. Зведений каталог
| № | Регістр | Тип | Підвид / режим | Вимірювання | Ресурси | Розділ |
|---|---|---|---|---|---|---|
| 1 | КурсиВалют | РВ | Періодичний (день), незалежний | Валюта | Курс, Кратність | Д.3.1 |
| 2 | ЦіниНоменклатури | РВ | Періодичний (день), підпорядкований | Номенклатура, Характеристика, ТипЦін | Ціна | Д.3.2 |
| 3 | ШтрихкодиНоменклатури | РВ | Неперіодичний, незалежний | Штрихкод | Номенклатура, Характеристика, Упаковка | Д.3.3 |
| 4 | ГрафікРоботи | РВ | Неперіодичний, незалежний | ВидГрафіка, ДатаГрафіка | Значення, Годин | Д.3.4 |
| 5 | КадровіДані | РВ | По позиції реєстратора, підпорядкований | Співробітник | Підрозділ, Посада, Ставка, ВидЗайнятості | Д.3.5 |
| 6 | НалаштуванняКористувачів | РВ | Неперіодичний, незалежний | Користувач, Налаштування | Значення (складений) | Д.3.6 |
| 7 | ТовариНаСкладах | РН | Залишки | Номенклатура, Склад, Партія, Характеристика | Кількість, Сума | Д.4.1 |
| 8 | ВзаєморозрахункиЗКонтрагентами | РН | Залишки | Організація, Контрагент, Договір, Замовлення | Сума, СумаВал | Д.4.2 |
| 9 | ТовариВРезерві | РН | Залишки | Номенклатура, Склад, Замовлення, Характеристика | Кількість | Д.4.3 |
| 10 | ГрошовіКоштиБезготівкові | РН | Залишки | Організація, БанківськийРахунок | Сума, СумаВал | Д.4.4 |
| 11 | Продажі | РН | Обороти (місяць) + агрегати | Номенклатура, Контрагент, Договір, Менеджер, Склад | Кількість, Сума, СумаБезПДВ, Собівартість | Д.5.1 |
| 12 | ВитратиЗаСтаттями | РН | Обороти (місяць) | Організація, Підрозділ, СтаттяВитрат, Проєкт | Сума | Д.5.2 |
| 13 | Госпрозрахунковий | РБ | З кореспонденцією | Організація (бал.), Підрозділ (небал.) | Сума (бал.), Кількість, ВалютнаСума | Д.6.1 |
| 14 | ПодатковийОблік | РБ | Без кореспонденції | Організація, ВидРізниці | Сума | Д.6.2 |
| 15 | Бюджетування | РБ | З кореспонденцією | Сценарій, ЦФВ, Проєкт | Сума | Д.6.3 |
| 16 | ОсновніНарахування | РР | Період дії + база за періодом дії | Співробітник (баз.), Підрозділ | Результат, ВідпрацьованоДнів, НормаДнів | Д.7.1 |
| 17 | Утримання | РР | Без періоду дії, база за періодом реєстрації | Співробітник (баз.), Підрозділ | Результат, БазаОподаткування | Д.7.2 |
Д.3. Регістри відомостей
Д.3.1. КурсиВалют — періодичний, незалежний
Задача: зберігати історію курсів валют; отримувати курс на будь-яку дату.
register:
name: КурсиВалют
kind: Information
synonym: "Курси валют"
periodicity: WithinDay # один курс на валюту на день
writeMode: Independent # курси вводяться вручну/завантажуються з НБУ
dimensions:
- { name: Валюта, type: "Довідник.Валюти", master: true, denyEmpty: true }
resources:
- { name: Курс, type: "Число(15,4)", denyEmpty: true }
- { name: Кратність, type: "Число(10,0)", default: 1 }Проєктні рішення:
Independent— курс не породжується документом, він є зовнішнім фактом.master: trueдля Валюти — при видаленні валюти курси видаляються; регістр видно у формі елемента довідника.- Періодичність
WithinDay, а неWithinSecond— повторний запис курсу за той самий день замістить попередній, а не створить дубль.
Дані у таблиці rg_i_0007:
| period | d1_r (Валюта) | r1 (Курс) | r2 (Кратність) |
|---|---|---|---|
| 2026-07-15 00:00:00 | USD | 41.8200 | 1 |
| 2026-07-15 00:00:00 | EUR | 45.6100 | 1 |
| 2026-07-16 00:00:00 | USD | 41.8500 | 1 |
| 2026-07-17 00:00:00 | USD | 41.9000 | 1 |
| 2026-07-17 00:00:00 | EUR | 45.7300 | 1 |
Запит (курс на дату документа):
ВИБРАТИ Курси.Курс, Курси.Кратність
З РегістрВідомостей.КурсиВалют.ЗрізОстанніх(&ДатаДокумента, Валюта = &Валюта) ЯК Курси// або через менеджер
var курс = Registers.Information["КурсиВалют"]
.GetLast(doc.Дата, new Filter { ["Валюта"] = doc.Валюта });
decimal сумаГрн = doc.СумаВалюти * курс.Курс / курс.Кратність;Д.3.2. ЦіниНоменклатури — періодичний, підпорядкований реєстратору
Задача: зберігати історію цін; ціна встановлюється наказом (документом), який можна скасувати.
register:
name: ЦіниНоменклатури
kind: Information
synonym: "Ціни номенклатури"
periodicity: WithinDay
writeMode: SubordinateToRecorder # ← ключова відмінність від КурсиВалют
dimensions:
- { name: Номенклатура, type: "Довідник.Номенклатура", master: true, index: true }
- { name: Характеристика, type: "Довідник.ХарактеристикиНоменклатури", nullable: true }
- { name: ТипЦін, type: "Довідник.ТипиЦін", master: true, denyEmpty: true }
resources:
- { name: Ціна, type: "Число(15,2)", denyEmpty: true }
attributes:
- { name: Валюта, type: "Довідник.Валюти" }
- { name: Одиниця, type: "Довідник.ОдиниціВиміру" }
recorders:
- Документ.ВстановленняЦінНоменклатуриПроєктні рішення:
SubordinateToRecorder— потрібна юридична прив'язка ціни до наказу, можливість скасувати наказ і «відкотити» ціни одним рухом.ВалютаіОдиниця— реквізити, а не вимірювання: за ними не фільтрують і не будують зрізи, вони визначаються типом цін. Помилка новачка — зробити Валюту вимірюванням, що подвоїть кількість записів і зламає зріз останніх.Характеристикадозволяє порожнє значення — для номенклатури без характеристик.
Дані у таблиці rg_i_0010:
| rec_t | rec_r | line_no | period | active | d1_r (Номенкл.) | d2_r (Характ.) | d3_r (ТипЦін) | r1 (Ціна) | a1_r (Валюта) |
|---|---|---|---|---|---|---|---|---|---|
| 15 | a3f…01 | 1 | 2026-07-01 | true | Кава Lavazza 1кг | (порожня) | Роздрібна | 480.00 | UAH |
| 15 | a3f…01 | 2 | 2026-07-01 | true | Кава Lavazza 1кг | (порожня) | Оптова | 410.00 | UAH |
| 15 | a3f…01 | 3 | 2026-07-01 | true | Чай Ahmad 100г | (порожня) | Роздрібна | 95.00 | UAH |
| 15 | b7c…04 | 1 | 2026-07-15 | true | Кава Lavazza 1кг | (порожня) | Роздрібна | 495.00 | UAH |
Д.3.3. ШтрихкодиНоменклатури — неперіодичний, незалежний
Задача: за штрихкодом знайти номенклатуру. Класичний приклад «вимірювання = те, за чим шукаємо; ресурс = те, що знаходимо».
register:
name: ШтрихкодиНоменклатури
kind: Information
periodicity: Nonperiodical
writeMode: Independent
dimensions:
- { name: Штрихкод, type: "Рядок(30)", denyEmpty: true } # ← ключ пошуку
resources:
- { name: Номенклатура, type: "Довідник.Номенклатура", denyEmpty: true }
- { name: Характеристика, type: "Довідник.ХарактеристикиНоменклатури", nullable: true }
- { name: Упаковка, type: "Довідник.УпаковкиНоменклатури", nullable: true }CREATE TABLE rg_i_0011 (
d1_s varchar(30) NOT NULL, -- Штрихкод
r1_r uuid NOT NULL, -- Номенклатура
r2_r uuid NOT NULL, -- Характеристика
r3_r uuid NOT NULL, -- Упаковка
CONSTRAINT pk_rg_i_0011 PRIMARY KEY (d1_s) -- ← унікальність штрихкоду «безкоштовно»
);
CREATE INDEX ix_rg_i_0011_r1 ON rg_i_0011 (r1_r); -- зворотний пошук: штрихкоди товаруПроєктне рішення: PK за вимірюванням автоматично гарантує, що один штрихкод не може вказувати на два товари. Якби Номенклатуру зробили вимірюванням, а Штрихкод — ресурсом, ця гарантія зникла б, і довелося б писати перевірку в прикладному коді.
Д.3.4. ГрафікРоботи — неперіодичний, незалежний (використовується як графік для РР)
register:
name: ГрафікРоботи
kind: Information
periodicity: Nonperiodical
writeMode: Independent
dimensions:
- { name: ВидГрафіка, type: "Довідник.ГрафікиРоботи", master: true, denyEmpty: true }
- { name: ДатаГрафіка, type: "Дата", denyEmpty: true } # ← вимірювання типу Дата, не Період!
resources:
- { name: Значення, type: "Число(1,0)" } # 1 = робочий день, 0 = вихідний
- { name: Годин, type: "Число(4,2)" } # тривалість зміниПроєктне рішення (важливе): ДатаГрафіка — це вимірювання типу Дата, а не стандартний реквізит Період. Регістр неперіодичний. Причина: графік описує календар (кожна дата — окремий факт), а не історію зміни значення. Якби використали періодичність, зріз останніх повертав би «останній робочий день», що безглуздо. Механізм РР (ДаніГрафіка) очікує саме таку структуру.
Дані у таблиці rg_i_0015:
| d1_r (ВидГрафіка) | d2_d (ДатаГрафіка) | r1 (Значення) | r2 (Годин) |
|---|---|---|---|
| П'ятиденка | 2026-07-01 | 1 | 8.00 |
| П'ятиденка | 2026-07-04 | 0 | 0.00 |
| П'ятиденка | 2026-07-05 | 0 | 0.00 |
| П'ятиденка | 2026-07-06 | 1 | 8.00 |
| Змінний 2/2 | 2026-07-01 | 1 | 12.00 |
| Змінний 2/2 | 2026-07-03 | 0 | 0.00 |
Д.3.5. КадровіДані — по позиції реєстратора
Задача: зберігати актуальні кадрові дані співробітника з історією; підтримати кілька наказів однією датою в правильному порядку.
register:
name: КадровіДані
kind: Information
periodicity: ByRecorderPosition # ← упорядкування за позицією документа, не лише за датою
writeMode: SubordinateToRecorder
dimensions:
- { name: Співробітник, type: "Довідник.Співробітники", master: true, denyEmpty: true }
resources:
- { name: Підрозділ, type: "Довідник.Підрозділи" }
- { name: Посада, type: "Довідник.Посади" }
- { name: Ставка, type: "Число(5,2)" }
- { name: ВидЗайнятості, type: "Перерахування.ВидиЗайнятості" }
- { name: ГрафікРоботи, type: "Довідник.ГрафікиРоботи" }
recorders:
- Документ.ПрийомНаРоботу
- Документ.КадровеПереміщення
- Документ.ЗвільненняПроєктне рішення: ByRecorderPosition — коли два накази оформлені однією датою (переміщення + зміна ставки), звичайна періодичність «у межах дня» дала б недетермінований результат зрізу. Позиція реєстратора додає до ключа (rec_t, rec_r, line_no) і впорядковує за моментом часу документа.
Фізична схема:
CREATE TABLE rg_i_0016 (
period timestamp(0) NOT NULL,
rec_t smallint NOT NULL,
rec_r uuid NOT NULL,
line_no int NOT NULL,
active boolean NOT NULL DEFAULT true,
d1_r uuid NOT NULL, -- Співробітник
r1_r uuid NOT NULL, r2_r uuid NOT NULL,
r3 numeric(5,2) NOT NULL,
r4 smallint NOT NULL, -- Перерахування зберігається як порядковий номер
r5_r uuid NOT NULL,
CONSTRAINT pk_rg_i_0016 PRIMARY KEY (period, rec_t, rec_r, line_no) -- ← Період у ключі ПЕРШИЙ
);
CREATE INDEX ix_rg_i_0016_d1 ON rg_i_0016 (d1_r, period DESC, rec_t, rec_r, line_no DESC)
WHERE active;Д.3.6. НалаштуванняКористувачів — складений тип ресурсу
register:
name: НалаштуванняКористувачів
kind: Information
periodicity: Nonperiodical
writeMode: Independent
dimensions:
- { name: Користувач, type: "Довідник.Користувачі", master: true }
- { name: Налаштування, type: "ПланВидівХарактеристик.НалаштуванняКористувачів", master: true }
resources:
- name: Значення
type: ["Рядок(500)", "Число(15,2)", "Дата", "Булево",
"Довідник.Організації", "Довідник.Склади"] # ← складений типCREATE TABLE rg_i_0018 (
d1_r uuid NOT NULL,
d2_r uuid NOT NULL,
r1_t smallint NOT NULL, -- код типу значення
r1_b boolean,
r1_n numeric(15,2),
r1_s varchar(500),
r1_d timestamp(0),
r1_r uuid,
CONSTRAINT pk_rg_i_0018 PRIMARY KEY (d1_r, d2_r)
);| d1_r | d2_r | r1_t | r1_b | r1_n | r1_s | r1_d | r1_r |
|---|---|---|---|---|---|---|---|
| Іваненко | ОсновнаОрганізація | 101 | ТОВ «Альфа» | ||||
| Іваненко | ОсновнийСклад | 104 | Центральний | ||||
| Іваненко | ПоказуватиПідказки | 2 | true | ||||
| Петренко | КількістьРядківСписку | 3 | 50 |
Д.4. Регістри накопичення, вид «Залишки»
Д.4.1. ТовариНаСкладах
Декларація — розділ 6.7, фізична схема — розділ 8.5, ER — розділ 9.2. Тут — дані.
Рухи (rg_a_0012) після проведення трьох документів:
| rec_t | rec_r | line_no | period | mvt | d1_r (Номенкл.) | d2_r (Склад) | r1 (К-сть) | r2 (Сума) |
|---|---|---|---|---|---|---|---|---|
| 2 (ПрихНакл) | p1 | 1 | 2026-06-10 | 0 Прихід | Кава Lavazza | Центральний | 100.000 | 35 000.00 |
| 2 (ПрихНакл) | p1 | 2 | 2026-06-10 | 0 Прихід | Чай Ahmad | Центральний | 200.000 | 12 000.00 |
| 3 (ВидНакл) | v1 | 1 | 2026-07-05 | 1 Витрата | Кава Lavazza | Центральний | 30.000 | 10 500.00 |
| 4 (Переміщ) | m1 | 1 | 2026-07-12 | 1 Витрата | Кава Lavazza | Центральний | 20.000 | 7 000.00 |
| 4 (Переміщ) | m1 | 2 | 2026-07-12 | 0 Прихід | Кава Lavazza | Роздрібний | 20.000 | 7 000.00 |
Підсумки залишків (rg_a_0012_tb), ТА = 2026-07-31:
| period | d1_r | d2_r | r1 (К-сть ±) | r2 (Сума ±) | Коментар |
|---|---|---|---|---|---|
| 2026-07-01 | Кава Lavazza | Центральний | 100.000 | 35 000.00 | залишок на початок липня |
| 2026-07-01 | Чай Ahmad | Центральний | 200.000 | 12 000.00 | |
| 2026-08-01 | Кава Lavazza | Центральний | 50.000 | 17 500.00 | 100 − 30 − 20 |
| 2026-08-01 | Кава Lavazza | Роздрібний | 20.000 | 7 000.00 | |
| 2026-08-01 | Чай Ahmad | Центральний | 200.000 | 12 000.00 | |
| 5999-11-01 | Кава Lavazza | Центральний | 50.000 | 17 500.00 | MAXPERIOD — поточний залишок |
| 5999-11-01 | Кава Lavazza | Роздрібний | 20.000 | 7 000.00 | MAXPERIOD |
| 5999-11-01 | Чай Ahmad | Центральний | 200.000 | 12 000.00 | MAXPERIOD |
(Вимірювання Партія і Характеристика для стислості опущені.)
Д.4.2. ВзаєморозрахункиЗКонтрагентами
Задача: облік дебіторської/кредиторської заборгованості в розрізі договорів і замовлень.
register:
name: ВзаєморозрахункиЗКонтрагентами
kind: Accumulation
accumulationKind: Balance
periodicity: WithinSecond
enableBalanceTotals: true
enableTurnoverTotals: true
partitioning: { mode: RangeByPeriod, step: month }
dimensions:
- { name: Організація, type: "Довідник.Організації", denyEmpty: true, index: true }
- { name: Контрагент, type: "Довідник.Контрагенти", master: true, index: true }
- { name: Договір, type: "Довідник.ДоговориКонтрагентів", master: true, index: true }
- { name: Замовлення, type: ["Документ.ЗамовленняПокупця", "Документ.ЗамовленняПостачальнику"],
nullable: true }
resources:
- { name: Сума, type: "Число(15,2)" } # у валюті регламентованого обліку
- { name: СумаВал, type: "Число(15,2)" } # у валюті договору
attributes:
- { name: Коментар, type: "Рядок(200)" }
recorders:
- Документ.ВидатковаНакладна
- Документ.ПрихіднаНакладна
- Документ.ПлатіжнеДоручення
- Документ.КасовийОрдер
- Документ.КоригуванняБоргуУгода про знак (обов'язково фіксується в методології!):
| Вид руху | Сенс | Приклад документа |
|---|---|---|
| Прихід | Борг контрагента перед нами зростає (дебіторка +) | Видаткова накладна |
| Витрата | Борг контрагента перед нами зменшується (оплата) | Платіжне доручення вхідне |
Кредиторська заборгованість = від'ємний залишок. Альтернатива — два окремих ресурси (СумаДт/СумаКт) або два регістри; обраний варіант — один знаковий ресурс (простіше, менший обсяг підсумків).
Приклад даних:
| Документ | period | mvt | Контрагент | Договір | r1 (Сума) | Залишок після |
|---|---|---|---|---|---|---|
| ВидатковаНакладна №12 | 2026-07-05 | Прихід | ТОВ «Бета» | Основний | 12 000.00 | +12 000 (винні нам) |
| ПлатіжнеДоручення №88 | 2026-07-09 | Витрата | ТОВ «Бета» | Основний | 8 000.00 | +4 000 |
| ПлатіжнеДоручення №91 | 2026-07-16 | Витрата | ТОВ «Бета» | Основний | 6 000.00 | −2 000 (передоплата) |
Запит (акт звірки):
ВИБРАТИ
Дані.Контрагент, Дані.Договір,
Дані.СумаПочатковийЗалишок, Дані.СумаПрихід,
Дані.СумаВитрата, Дані.СумаКінцевийЗалишок
З РегістрНакопичення.ВзаєморозрахункиЗКонтрагентами.ЗалишкиІОбороти(
&Початок, &Кінець, Авто, Рух,
Організація = &Організація І Контрагент = &Контрагент
) ЯК ДаніД.4.3. ТовариВРезерві — регістр «супутник»
register:
name: ТовариВРезерві
kind: Accumulation
accumulationKind: Balance
periodicity: WithinSecond
dimensions:
- { name: Номенклатура, type: "Довідник.Номенклатура", master: true, index: true }
- { name: Характеристика, type: "Довідник.ХарактеристикиНоменклатури", nullable: true }
- { name: Склад, type: "Довідник.Склади", master: true, index: true }
- { name: Замовлення, type: "Документ.ЗамовленняПокупця", master: true, denyEmpty: true }
resources:
- { name: Кількість, type: "Число(15,3)" }
recorders:
- Документ.ЗамовленняПокупця
- Документ.ВидатковаНакладна
- Документ.ЗакриттяЗамовленьПатерн «доступний залишок» — з'єднання двох регістрів:
ВИБРАТИ
ЄСТЬNULL(Зал.Номенклатура, Рез.Номенклатура) ЯК Номенклатура,
ЄСТЬNULL(Зал.Склад, Рез.Склад) ЯК Склад,
ЄСТЬNULL(Зал.КількістьЗалишок, 0) ЯК ВСкладі,
ЄСТЬNULL(Рез.КількістьЗалишок, 0) ЯК ВРезерві,
ЄСТЬNULL(Зал.КількістьЗалишок, 0) - ЄСТЬNULL(Рез.КількістьЗалишок, 0) ЯК Доступно
З РегістрНакопичення.ТовариНаСкладах.Залишки(&Період, Склад = &Склад) ЯК Зал
ПОВНЕ З'ЄДНАННЯ РегістрНакопичення.ТовариВРезерві.Залишки(&Період, Склад = &Склад) ЯК Рез
ЗА Зал.Номенклатура = Рез.Номенклатура І Зал.Склад = Рез.СкладД.4.4. ГрошовіКоштиБезготівкові
register:
name: ГрошовіКоштиБезготівкові
kind: Accumulation
accumulationKind: Balance
periodicity: WithinDay # достатньо дня: банківська виписка — денна
dimensions:
- { name: Організація, type: "Довідник.Організації", denyEmpty: true }
- { name: БанківськийРахунок, type: "Довідник.БанківськіРахунки", master: true, denyEmpty: true }
resources:
- { name: Сума, type: "Число(15,2)" }
- { name: СумаВал, type: "Число(15,2)" }
recorders: [Документ.ПлатіжнеДоручення, Документ.БанківськаВиписка, Документ.ПереміщенняКоштів]Проєктне рішення: Валюта — не вимірювання, тому що вона однозначно визначається банківським рахунком (функціональна залежність). Її додавання як вимірювання створило б надлишковість і ризик неузгоджених даних. Валюта отримується з'єднанням із довідником.
Д.5. Регістри накопичення, вид «Обороти»
Д.5.1. Продажі — з агрегатами
register:
name: Продажі
kind: Accumulation
accumulationKind: Turnover # ← залишків не буває: «залишок продажів» безглуздий
periodicity: WithinSecond
turnoverTotalsPeriod: Month
aggregatesMode: Aggregates
partitioning: { mode: RangeByPeriod, step: month }
dimensions:
- { name: Номенклатура, type: "Довідник.Номенклатура", index: true }
- { name: Характеристика, type: "Довідник.ХарактеристикиНоменклатури", nullable: true }
- { name: Контрагент, type: "Довідник.Контрагенти", index: true }
- { name: Договір, type: "Довідник.ДоговориКонтрагентів" }
- { name: Менеджер, type: "Довідник.Користувачі", index: true }
- { name: Склад, type: "Довідник.Склади" }
resources:
- { name: Кількість, type: "Число(15,3)" }
- { name: Сума, type: "Число(15,2)" }
- { name: СумаБезПДВ, type: "Число(15,2)" }
- { name: Собівартість, type: "Число(15,2)" }
recorders: [Документ.ВидатковаНакладна, Документ.ПовернненняВідПокупця, Документ.ЗвітПроРоздрібніПродажі]
aggregates:
- name: ПродажіПоМісяцяхІТоварах
periodicity: Month
dimensions: [Номенклатура, Склад]
realtime: true
- name: ПродажіПоМенеджерах
periodicity: Day
dimensions: [Менеджер]
realtime: false # оновлюється регламентно
- name: ПродажіПоКонтрагентах
periodicity: Quarter
dimensions: [Контрагент, Договір]
realtime: falseПроєктні рішення:
Turnover, а неBalance: немаєВидРуху, немає таблиці залишків → удвічі менше підсумків і немає кумулятивних оновлень. Повернення відображається від'ємною сумою, а не «витратою».- 6 вимірювань → таблиця підсумків надто широка для швидких звітів → компенсуємо агрегатами.
Ефект агрегатів:
| Звіт | Без агрегатів (rg_a_0031_tt) |
З агрегатом | Виграш |
|---|---|---|---|
| Продажі по товарах за рік | скан 8 млн рядків підсумків | ag1: 40 тис. рядків |
×200 |
| Рейтинг менеджерів за квартал | скан 8 млн рядків | ag2: 1,8 тис. рядків |
×4000 |
| Продажі по контрагенту за 3 роки | скан 8 млн рядків | ag3: 60 тис. рядків |
×130 |
| Продажі по товару + контрагенту | агрегат не покриває → підсумки | — | — |
CREATE TABLE rg_a_0031_ag1 ( -- Місяць × (Номенклатура, Склад)
period timestamp(0) NOT NULL, d1_r uuid NOT NULL, d6_r uuid NOT NULL,
r1 numeric(20,3) NOT NULL, r2 numeric(20,2) NOT NULL,
r3 numeric(20,2) NOT NULL, r4 numeric(20,2) NOT NULL,
CONSTRAINT pk_rg_a_0031_ag1 PRIMARY KEY (period, d1_r, d6_r)
);
CREATE TABLE rg_a_0031_ag2 ( -- День × (Менеджер)
period timestamp(0) NOT NULL, d5_r uuid NOT NULL,
r1 numeric(20,3) NOT NULL, r2 numeric(20,2) NOT NULL,
r3 numeric(20,2) NOT NULL, r4 numeric(20,2) NOT NULL,
CONSTRAINT pk_rg_a_0031_ag2 PRIMARY KEY (period, d5_r)
);Д.5.2. ВитратиЗаСтаттями
register:
name: ВитратиЗаСтаттями
kind: Accumulation
accumulationKind: Turnover
periodicity: WithinDay
turnoverTotalsPeriod: Month
dimensions:
- { name: Організація, type: "Довідник.Організації", denyEmpty: true }
- { name: Підрозділ, type: "Довідник.Підрозділи", index: true }
- { name: СтаттяВитрат, type: "Довідник.СтаттіВитрат", master: true, denyEmpty: true, index: true }
- { name: Проєкт, type: "Довідник.Проєкти", nullable: true }
resources:
- { name: Сума, type: "Число(15,2)" }
recorders: [Документ.АвансовийЗвіт, Документ.НадходженняПослуг, Документ.НарахуванняЗарплати, Документ.Амортизація]Приклад даних (rg_a_0032_tt, підсумки оборотів за місяць):
| period | Підрозділ | СтаттяВитрат | Проєкт | r1 (Сума) |
|---|---|---|---|---|
| 2026-07-01 | Відділ продажів | Оренда | (порожній) | 45 000.00 |
| 2026-07-01 | Відділ продажів | ЗП і нарахування | (порожній) | 320 000.00 |
| 2026-07-01 | Відділ продажів | Реклама | Запуск «Альфа» | 78 000.00 |
| 2026-07-01 | ІТ-відділ | ЗП і нарахування | (порожній) | 410 000.00 |
| 2026-07-01 | ІТ-відділ | Ліцензії ПЗ | (порожній) | 62 000.00 |
Д.6. Регістри бухгалтерії
Д.6.1. Госпрозрахунковий — з кореспонденцією
Декларація — розділ 6.7, схема — 8.6, ER — 9.3. Тут — приклад проводок.
Господарська операція: реалізація товару на 12 000 грн (у т.ч. ПДВ 2 000), собівартість 7 000 грн.
Проводки (rg_b_0020, реєстратор = ВидатковаНакладна №12):
| line | acc_dt | ed_dt1 | ed_dt2 | acc_kt | ed_kt1 | ed_kt2 | r1 (Сума) | r2_dt | r2_kt |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 361 Розрах. з покупцями | ТОВ «Бета» | Договір №5 | 702 Дохід від реалізації | Кава Lavazza | — | 12 000.00 | 30.000 | |
| 2 | 702 Дохід від реалізації | Кава Lavazza | — | 641 Розрах. за податками | ПДВ | — | 2 000.00 | ||
| 3 | 902 Собівартість реалізації | Кава Lavazza | — | 281 Товари на складі | Кава Lavazza | Центральний | 7 000.00 | 30.000 | 30.000 |
Контроль: Σ(Сума) по Дт = Σ(Сума) по Кт у межах реєстратора → 12 000 + 2 000 + 7 000 = 21 000 з обох боків. ✔
Підсумки, рівень 0 (rg_b_0020_tb0), на 5999-11-01:
| acc_r | d1_r (Організація) | r1 (Сума ±) | Інтерпретація |
|---|---|---|---|
| 281 | ТОВ «Альфа» | +28 000.00 | дебетовий залишок (товари є) |
| 361 | ТОВ «Альфа» | +12 000.00 | дебетовий (дебіторка) |
| 641 | ТОВ «Альфа» | −2 000.00 | кредитовий (винні бюджету) |
| 702 | ТОВ «Альфа» | −10 000.00 | кредитовий (дохід) |
| 902 | ТОВ «Альфа» | +7 000.00 | дебетовий (собівартість) |
Підсумки, рівень 2 (rg_b_0020_tb2) — для рахунку 281 (2 субконто: Номенклатура, Склад):
| acc_r | ed1_r (Номенклатура) | ed2_r (Склад) | r1 (Сума) | r2 (Кількість) |
|---|---|---|---|---|
| 281 | Кава Lavazza | Центральний | 17 500.00 | 50.000 |
| 281 | Кава Lavazza | Роздрібний | 7 000.00 | 20.000 |
| 281 | Чай Ahmad | Центральний | 12 000.00 | 200.000 |
Згортка tb2 → tb0: 17 500 + 7 000 + 12 000 = 36 500… (різниця з таблицею вище — інші операції; у тесті ФТ-Т-08 перевіряється точна рівність).
Запит (ОСВ по рахунку):
ВИБРАТИ
Дані.Субконто1 ЯК Номенклатура, Дані.Субконто2 ЯК Склад,
Дані.СумаПочатковийЗалишокДт, Дані.КількістьПочатковийЗалишокДт,
Дані.СумаОборотДт, Дані.СумаОборотКт,
Дані.СумаКінцевийЗалишокДт, Дані.КількістьКінцевийЗалишокДт
З РегістрБухгалтерії.Госпрозрахунковий.ЗалишкиІОбороти(
&Початок, &Кінець, Місяць,
Рахунок = &Рахунок281,
, Організація = &Організація,
,
) ЯК ДаніД.6.2. ПодатковийОблік — без кореспонденції
Задача: податковий облік із розділенням на постійні/тимчасові різниці, де подвійний запис не потрібен (кожен факт реєструється окремо по Дт або по Кт).
register:
name: ПодатковийОблік
kind: Accounting
chartOfAccounts: ПланРахунків.Податковий
correspondence: false # ← уніграфічний облік
periodicity: WithinSecond
dimensions:
- { name: Організація, type: "Довідник.Організації", balance: true, denyEmpty: true }
- { name: ВидРізниці, type: "Перерахування.ВидиРізниць", balance: true } # БУ|ПУ|ПР|ТР
resources:
- { name: Сума, type: "Число(15,2)", balance: true }
attributes:
- { name: Зміст, type: "Рядок(255)" }
recorders: [Документ.БухгалтерськаОперація, Документ.ВидатковаНакладна, Документ.НадходженняПослуг]Проводки (rg_b_0021):
| line | acc_mvt | acc_r | ed1 (Номенкл.) | d1 (Організація) | d2 (ВидРізниці) | r1 (Сума) |
|---|---|---|---|---|---|---|
| 1 | 0 Дебет | 902.НУ | Кава Lavazza | ТОВ «Альфа» | ПУ податковий | 7 000.00 |
| 2 | 1 Кредит | 281.НУ | Кава Lavazza | ТОВ «Альфа» | ПУ | 7 000.00 |
| 3 | 0 Дебет | 902.НУ | Кава Lavazza | ТОВ «Альфа» | ПР постійна різниця | 350.00 |
| 4 | 1 Кредит | 281.НУ | Кава Lavazza | ТОВ «Альфа» | ПР | 350.00 |
Відмінності від Д.6.1:
| З кореспонденцією | Без кореспонденції | |
|---|---|---|
| Один факт = | 1 запис (Дт+Кт) | 2 записи (окремо Дт, окремо Кт) |
| Поля рахунків | acc_dt_r, acc_kt_r |
acc_r + acc_mvt
|
| Субконто | 2 набори (Дт, Кт) | 1 набір |
| Кількість записів | N | 2N |
Таблиця _tc (кореспонденції) |
Є | Немає — «шахова відомість» неможлива |
| Контроль Дт=Кт | У межах реєстратора | Опційно (може бути незбалансовано за задумом) |
| Коли обирати | Класичний облік, потрібен аналіз кореспонденцій | Облік без потреби в кореспонденціях, забалансовий, статистичний |
Д.6.3. Бюджетування
register:
name: Бюджетування
kind: Accounting
chartOfAccounts: ПланРахунків.Бюджетування
correspondence: true
periodicity: WithinMonth # бюджет — помісячний, до секунди не потрібно
dimensions:
- { name: Сценарій, type: "Довідник.СценаріїБюджетування", balance: true, denyEmpty: true }
- { name: ЦФВ, type: "Довідник.ЦентриФінансовоїВідповідальності", balance: true }
- { name: Проєкт, type: "Довідник.Проєкти", balance: true, nullable: true }
resources:
- { name: Сума, type: "Число(15,2)", balance: true }
recorders: [Документ.БюджетнаОперація, Документ.ВведенняБюджету]Проєктне рішення: Сценарій — вимірювання, а не окремий регістр на кожен сценарій (План/Факт/Прогноз). Це дозволяє план-фактний аналіз одним запитом. Усі три вимірювання балансові — бюджетна проводка не змінює сценарій/ЦФВ між дебетом і кредитом.
Д.7. Регістри розрахунку
Д.7.1. ОсновніНарахування — період дії + витіснення
План видів розрахунку ОсновніНарахування:
| Вид розрахунку | Період дії | Витісняючі | Базові | Залежність від бази |
|---|---|---|---|---|
| Оклад | Так | Відпустка, Лікарняний | — | Не залежить |
| Відпустка | Так | Лікарняний | Оклад, Премія | За періодом дії |
| Лікарняний | Так | — | Оклад | За періодом дії |
| Премія | Ні | — | Оклад | За періодом реєстрації |
Декларація регістру — розділ 6.7.
Записи (rg_c_0040), період реєстрації = липень 2026:
| line | calc_type | period | act_begin | act_end | base_begin | base_end | d1 (Співробітник) | r1 (Результат) |
|---|---|---|---|---|---|---|---|---|
| 1 | Оклад | 2026-07-01 | 2026-07-01 | 2026-07-31 | Іваненко | 18 000.00 | ||
| 2 | Відпустка | 2026-07-01 | 2026-07-10 | 2026-07-17 | 2026-01-01 | 2026-06-30 | Іваненко | 6 420.00 |
| 3 | Лікарняний | 2026-07-01 | 2026-07-14 | 2026-07-22 | 2026-01-01 | 2026-06-30 | Іваненко | 5 180.00 |
| 4 | Оклад | 2026-07-01 | 2026-07-01 | 2026-07-31 | Петренко | 22 000.00 |
Фактичні періоди дії (rg_c_0040_ap) — результат витіснення:
| rec/line | interval_no | calc_type | d1 | fact_begin | fact_end | Коментар |
|---|---|---|---|---|---|---|
| n1 / 1 | 0 | Оклад | Іваненко | 2026-07-01 | 2026-07-09 | до відпустки |
| n1 / 1 | 1 | Оклад | Іваненко | 2026-07-23 | 2026-07-31 | після лікарняного |
| n1 / 2 | 0 | Відпустка | Іваненко | 2026-07-10 | 2026-07-13 | витіснена лікарняним з 14-го |
| n1 / 3 | 0 | Лікарняний | Іваненко | 2026-07-14 | 2026-07-22 | не витісняється нічим |
| n1 / 4 | 0 | Оклад | Петренко | 2026-07-01 | 2026-07-31 | витіснення відсутнє |
Один запис «Оклад» Іваненка → два інтервали — ілюстрація зв'язку 1:N у розділі 9.4.
Запит (дані графіка для розрахунку окладу):
ВИБРАТИ
Дані.Співробітник, Дані.НомерРядка,
Дані.ЗначенняФактичногоПеріодуДії ЯК ВідпрацьованоДнів,
Дані.ЗначенняПеріодуДії ЯК НормаДнів
З РегістрРозрахунку.ОсновніНарахування.ДаніГрафіка(
ВидРозрахунку = ЗНАЧЕННЯ(ПланВидівРозрахунку.ОсновніНарахування.Оклад)
І Реєстратор = &Реєстратор
) ЯК ДаніРезультат: Іваненко — ВідпрацьованоДнів = 13 (робочі дні у 01–09 та 23–31), НормаДнів = 23 → Оклад = 18 000 × 13 / 23 = 10 173,91.
Запит (база для відпустки):
ВИБРАТИ База.Співробітник, База.НомерРядка, База.РезультатБаза ЯК Заробіток
З РегістрРозрахунку.ОсновніНарахування.БазаРозрахунку(
, /* вимірювання бази */
, /* вимірювання основного регістру */
Співробітник, /* розрізи */
ВидРозрахунку = ЗНАЧЕННЯ(ПланВидівРозрахунку.ОсновніНарахування.Відпустка)
) ЯК БазаД.7.2. Утримання — без періоду дії, база за періодом реєстрації
План видів розрахунку Утримання:
| Вид розрахунку | Період дії | Базові види розрахунку | Залежність |
|---|---|---|---|
| ПДФО | Ні | (усі з ПВР ОсновніНарахування) | За періодом реєстрації |
| Військовий збір | Ні | (усі з ПВР ОсновніНарахування) | За періодом реєстрації |
| Аліменти | Ні | Оклад, Премія, Відпустка | За періодом реєстрації |
| Профспілковий внесок | Ні | Оклад | За періодом реєстрації |
register:
name: Утримання
kind: Calculation
calculationTypesPlan: ПланВидівРозрахунку.Утримання
periodicity: Month
actionPeriod: false # ← утримання не має періоду дії
basePeriod: ByRegistrationPeriod # ← база береться за періодом реєстрації
# schedule: не задається — графік не потрібен
dimensions:
- { name: Співробітник, type: "Довідник.Співробітники", baseDimension: true, master: true, denyEmpty: true }
- { name: Підрозділ, type: "Довідник.Підрозділи" }
resources:
- { name: Результат, type: "Число(15,2)" }
- { name: БазаОподаткування, type: "Число(15,2)" }
recalculations:
- name: ПерерахунокУтримань
dimensions:
- { name: Співробітник, registerDimension: Співробітник }
recorders: [Документ.НарахуванняЗарплати]Фізична схема (спрощена — немає полів періоду дії):
CREATE TABLE rg_c_0041 (
rec_t smallint NOT NULL,
rec_r uuid NOT NULL,
line_no int NOT NULL,
period timestamp(0) NOT NULL, -- період реєстрації
active boolean NOT NULL DEFAULT true,
reversal boolean NOT NULL DEFAULT false,
calc_type_r uuid NOT NULL,
base_begin timestamp(0) NOT NULL, -- ← є, бо basePeriod ≠ None
base_end timestamp(0) NOT NULL,
-- act_begin / act_end ВІДСУТНІ (actionPeriod: false)
d1_r uuid NOT NULL,
d2_r uuid NOT NULL,
r1 numeric(15,2) NOT NULL DEFAULT 0,
r2 numeric(15,2) NOT NULL DEFAULT 0,
CONSTRAINT pk_rg_c_0041 PRIMARY KEY (rec_t, rec_r, line_no)
);
-- Таблиця rg_c_0041_ap НЕ створюється (немає періоду дії → немає витіснення)
CREATE TABLE rg_c_0041_rc1 ( -- ПерерахунокУтримань
rec_t smallint NOT NULL, rec_r uuid NOT NULL,
calc_type_r uuid NOT NULL, d1_r uuid NOT NULL,
src_rec_t smallint NOT NULL, src_rec_r uuid NOT NULL,
created_at timestamp(0) NOT NULL DEFAULT now(),
CONSTRAINT pk_rg_c_0041_rc1 PRIMARY KEY (rec_t, rec_r, calc_type_r, d1_r)
);Приклад даних:
| line | calc_type | period | base_begin | base_end | Співробітник | r2 (База) | r1 (Результат) |
|---|---|---|---|---|---|---|---|
| 1 | ПДФО | 2026-07-01 | 2026-07-01 | 2026-07-31 | Іваненко | 21 773.91 | 3 919.30 |
| 2 | Військовий збір | 2026-07-01 | 2026-07-01 | 2026-07-31 | Іваненко | 21 773.91 | 326.61 |
| 3 | Аліменти | 2026-07-01 | 2026-07-01 | 2026-07-31 | Іваненко | 17 528.00 | 4 382.00 |
Д.8. Наскрізний приклад: проведення видаткової накладної
Документ: Видаткова накладна №12 від 05.07.2026, ТОВ «Бета», 30 кг кави за 400 грн (12 000 грн з ПДВ), собівартість 7 000 грн.

protected override void OnPosting(PostingContext ctx)
{
// --- 0. Блокування перед контролем залишків ---
var dl = new DataLock();
var li = dl.Add("РегістрНакопичення.ТовариНаСкладах.Залишки");
li.Mode = DataLockMode.Exclusive;
li.DataSource = this.Товари.Unload(new[] { "Номенклатура", "Характеристика" });
li.UseFromDataSource("Номенклатура", "Номенклатура");
li.UseFromDataSource("Характеристика", "Характеристика");
li.SetValue("Склад", this.Склад);
dl.Lock();
// --- 1. РН ТовариНаСкладах: списання ---
var rsGoods = Movements["ТовариНаСкладах"]; rsGoods.Write = true; rsGoods.Clear();
foreach (var row in this.Товари) {
var m = rsGoods.Add();
m.Період = this.Дата;
m.MovementType = AccumulationMovementType.Expense;
m.Номенклатура = row.Номенклатура; m.Характеристика = row.Характеристика;
m.Склад = this.Склад; m.Партія = row.Партія;
m.Кількість = row.Кількість; m.Сума = row.Собівартість;
}
// --- 2. РН Взаєморозрахунки: дебіторка зростає ---
var rsAR = Movements["ВзаєморозрахункиЗКонтрагентами"]; rsAR.Write = true; rsAR.Clear();
var ar = rsAR.Add();
ar.Період = this.Дата;
ar.MovementType = AccumulationMovementType.Receipt; // Прихід = борг зростає
ar.Організація = this.Організація; ar.Контрагент = this.Контрагент;
ar.Договір = this.Договір; ar.Замовлення = this.ЗамовленняПокупця;
ar.Сума = this.СумаДокумента; ar.СумаВал = this.СумаДокументаВал;
// --- 3. РН Продажі: оборот (без ВидРуху!) ---
var rsSales = Movements["Продажі"]; rsSales.Write = true; rsSales.Clear();
foreach (var row in this.Товари) {
var s = rsSales.Add();
s.Період = this.Дата;
s.Номенклатура = row.Номенклатура; s.Контрагент = this.Контрагент;
s.Договір = this.Договір; s.Менеджер = this.Менеджер;
s.Склад = this.Склад;
s.Кількість = row.Кількість; s.Сума = row.Сума;
s.СумаБезПДВ = row.СумаБезПДВ; s.Собівартість = row.Собівартість;
}
// --- 4. РН ТовариВРезерві: зняття резерву ---
if (!this.ЗамовленняПокупця.IsEmpty()) { /* ... Витрата ... */ }
// --- 5. РБ Госпрозрахунковий: проводки ---
var rsAcc = Movements["Госпрозрахунковий"]; rsAcc.Write = true; rsAcc.Clear();
foreach (var row in this.Товари) {
var e1 = rsAcc.AddAccountingEntry(); // Дт 361 / Кт 702
e1.Період = this.Дата;
e1.РахунокДт = Accounts["361"]; e1.РахунокКт = Accounts["702"];
e1.SubcontoDt[ВидиСубконто.Контрагенти] = this.Контрагент;
e1.SubcontoDt[ВидиСубконто.Договори] = this.Договір;
e1.SubcontoKt[ВидиСубконто.Номенклатура] = row.Номенклатура;
e1.Організація = this.Організація;
e1.Сума = row.Сума; e1.КількістьКт = row.Кількість;
var e2 = rsAcc.AddAccountingEntry(); // Дт 702 / Кт 641
/* ... ПДВ ... */
var e3 = rsAcc.AddAccountingEntry(); // Дт 902 / Кт 281
/* ... собівартість ... */
}
rsGoods.WriteNow(); // примусовий запис до контролю
ControlNegativeBalances(ctx); // прикладна перевірка
}Що відбувається у БД (одна транзакція):
| Крок | Таблиця | Операція | Рядків |
|---|---|---|---|
| 1 | doc_0003 |
UPDATE posted = true | 1 |
| 2 | rg_a_0012 (ТовариНаСкладах) |
DELETE + INSERT | 1 |
| 3 | rg_a_0012_tb |
MERGE (MAXPERIOD + періоди > 07.2026) | 2 |
| 4 | rg_a_0013 (Взаєморозрахунки) |
DELETE + INSERT | 1 |
| 5 | rg_a_0013_tb |
MERGE | 2 |
| 6 | rg_a_0031 (Продажі) |
DELETE + INSERT | 1 |
| 7 | rg_a_0031_tt |
MERGE (тільки період 2026-07) | 1 |
| 8 | rg_a_0031_ag1 |
MERGE (realtime-агрегат) | 1 |
| 9 | rg_a_0014 (Резерв) |
DELETE + INSERT | 1 |
| 10 | rg_b_0020 (проводки) |
DELETE + INSERT | 3 |
| 11 | rg_b_0020_tb0 |
MERGE | 10 |
| 12 | rg_b_0020_tb1/tb2 |
MERGE | 8 |
| 13 | rg_b_0020_tt0..tt2 |
MERGE | 12 |
| 14 | rg_b_0020_tc |
MERGE | 3 |
| Разом | ≈ 47 рядків, 1 COMMIT | ||
Д.9. Наскрізний приклад: розрахунок зарплати
КРОК 1. Документ «Нарахування зарплати» за липень 2026, Іваненко.
Записуємо рухи в РР ОсновніНарахування (Результат = 0).
КРОК 2. Платформа автоматично будує фактичні періоди дії (rg_c_0040_ap):
Оклад → [01.07–09.07], [23.07–31.07]
Відпустка → [10.07–13.07]
Лікарняний → [14.07–22.07]
КРОК 3. Розрахунок «первинних» видів (без бази) — Оклад.
Запит до ДаніГрафіка:
НормаДнів = 23 (робочих днів у липні за П'ятиденкою)
ВідпрацьованоДнів = 13 (робочі дні у фактичних періодах дії)
Результат = 18 000 × 13 / 23 = 10 173,91
→ UPDATE rg_c_0040 SET r1 = 10173.91, r2 = 13, r3 = 23 WHERE line_no = 1
КРОК 4. Розрахунок видів із базою — Відпустка, Лікарняний.
Запит до БазаРозрахунку (базовий період 01.01–30.06, база = Оклад+Премія):
Заробіток за 6 міс. = 115 560,00
Календарних днів = 181
Середньоденний = 638,45
Відпустка: 638,45 × 4 дні (10–13.07) = 2 553,80
Лікарняний: 638,45 × 9 днів × 100 % = 5 746,05
→ UPDATE rg_c_0040 SET r1 = ... WHERE line_no IN (2, 3)
КРОК 5. Записуємо рухи в РР Утримання (базовий період = період реєстрації, липень).
База ПДФО = 10 173,91 + 2 553,80 + 5 746,05 = 18 473,76
ПДФО = 18 473,76 × 18 % = 3 325,28
ВЗ = 18 473,76 × 1,5 % = 277,11
КРОК 6. Проведення документа → рухи в:
РН ВитратиЗаСтаттями (Обороти): Стаття «ЗП і нарахування» = 18 473,76
РБ Госпрозрахунковий: Дт 92 / Кт 661 = 18 473,76
Дт 661 / Кт 641.ПДФО = 3 325,28
Дт 661 / Кт 642.ВЗ = 277,11
КРОК 7. Через тиждень бухгалтер змінює Оклад Іваненка за червень (заднім числом).
→ Платформа реєструє в rg_c_0040_rc1 (ПерерахунокЗаБазою):
(Реєстратор = НарахуванняЗП липня, ВидРозрахунку = Відпустка, Співробітник = Іваненко)
(Реєстратор = НарахуванняЗП липня, ВидРозрахунку = Лікарняний, Співробітник = Іваненко)
→ Звіт «Документи до перерахунку» показує липневий документ.
→ Бухгалтер перепроводить → база перераховується → записи перерахунку очищаються.
Д.10. Антипатерни проєктування регістрів
| ID | Антипатерн | Чому погано | Як правильно |
|---|---|---|---|
| А-1 | Дата як вимірювання у РН (замість стандартного Період) |
Таблиця підсумків множиться на кількість дат і зростає нескінченно; віртуальні таблиці Залишки/Обороти ігнорують це вимірювання і дають безглузді результати |
Використовувати стандартний реквізит Період. Виняток — календарі (Д.3.4), де це РВ, а не РН
|
| А-2 | РН «Залишки» там, де потрібна лише історія значення | Надлишкові таблиці підсумків, кумулятивні оновлення, вид руху без сенсу | РВ періодичний |
| А-3 | РН «Залишки» там, де залишок безглуздий (Продажі, Витрати) | Удвічі більший обсяг підсумків, кумулятивні MERGE на всі майбутні періоди | РН «Обороти» + агрегати (Д.5.1) |
| А-4 | Реквізит документа продубльовано у вимірювання «щоб фільтрувати у звіті» | Кардинальність підсумків × N; типовий випадок — Менеджер/Валюта/Коментар | Реквізит регістру + з'єднання з документом у звіті; або окремий регістр «Обороти» |
| А-5 | > 8 вимірювань в одному РН | Таблиця підсумків стає більшою за таблицю рухів; проведення сповільнюється в рази | Розділити на регістри за призначенням; частину аналітики — в «Обороти» з агрегатами |
| А-6 | Незалежний РВ «у межах секунди» як журнал подій | Два записи в ту саму секунду → другий мовчки замістить перший; втрата даних | Підпорядкований РВ (реєстратор у ключі) або додати вимірювання-дискримінатор |
| А-7 | Ресурс типу Рядок/Посилання у РН |
Підсумовування неможливе; підсумки не побудуються | Реквізит (не агрегується) або вимірювання (якщо це розріз) |
| А-8 | Прямий UPDATE таблиці підсумків із прикладного коду |
Розсинхронізація підсумків і рухів; діагностика — тижні | Тільки через набори записів. Заборона на рівні прав БД (розділ 12.2) |
| А-9 | Один «універсальний» регістр із вимірюванням ВидОбліку (БУ/ПУ/УО) замість трьох |
Втричі більша таблиця; конкуренція за блокування між незалежними обліками; неможливо окремо перерахувати підсумки | Окремі регістри. Виняток — РБ ПодатковийОблік (Д.6.2), де ВидРізниці — це справді розріз одного обліку
|
| А-10 | Функціонально залежне поле як вимірювання (Валюта при БанківськомуРахунку) | Надлишковість; ризик неузгоджених даних; зайвий розмір ключа підсумків | З'єднання з довідником у запиті (Д.4.4) |
| А-11 | РБ там, де достатньо РН | Замість 1 таблиці — 10; ~70 % вартості проведення (Д.8); складність субконто без потреби | РН. РБ — лише коли потрібні план рахунків, подвійний запис і кореспонденції |
| А-12 | Вимірювання з nullable без потреби |
Порожнє посилання — окреме значення ключа; підсумки «розмазуються» на два рядки замість одного | denyEmpty: true скрізь, де порожнє значення не має сенсу
|
| А-13 | Контроль залишків до запису рухів | Класична гонитва: два сеанси проходять контроль і обидва списують у мінус | Спершу DataLock → WriteNow() → потім контроль (Д.8, ФТ-Т-12)
|
| А-14 | periodicity: WithinSecond «про всяк випадок» скрізь |
Для РВ — неможливість замістити запис за день; для РН — марна точність, більші індекси | Обирати мінімально достатню: день для цін/курсів/виписок, секунда — для складських рухів |
Д.11. Чек-лист рев'ю нового регістру
□ Тип регістру обґрунтований за деревом Д.1 □ Кожне вимірювання використовується у фільтрі АБО групуванні звітів □ Кожен ресурс має сенс при підсумовуванні □ Немає полів з антипатернів А-1, А-4, А-7, А-10, А-12 □ Оцінка кардинальності підсумків ≤ 10^8 (калькулятор конструктора, ФТ-К-03) □ Для вимірювань, де порожнє значення безглузде, стоїть denyEmpty □ Ведучі (master) вимірювання визначені — інакше «висячі» записи при видаленні об'єктів □ Склад реєстраторів повний; зайвих типів документів немає □ Угода про знак / вид руху зафіксована в методологічній документації □ Періодичність мінімально достатня (А-14) □ Для РН «Обороти» з >4 вимірюваннями визначено ≥1 агрегат □ Для РБ: балансові/небалансові вимірювання та ресурси розставлені свідомо □ Для РР: витісняючі та базові види розрахунку в ПВР заповнені; перерахунки визначені □ Партиціонування ввімкнене, якщо очікується >100 млн записів □ Написано тест на проведення + скасування + перерахунок підсумків
Кінець документа. Версія 1.1. Підлягає узгодженню з архітектором платформи K2 ERP до затвердження.