Регістри в K2 ERP: відмінності між версіями

Створена сторінка: {{DISPLAYTITLE:ТЗ. Підсистема регістрів K2 ERP}} __TOC__ = 1. Паспорт документа = {| class="wikitable" ! Атрибут !! Значення |- | Назва || Технічне завдання на розробку підсистеми «Регістри» платформи K2 ERP |- | Шифр || K2-ERP-TZ-REG-001 |- | Версія || 1.0 (чернетка на узгодження) |- | Статус || На роз...
 
Немає опису редагування
Рядок 11: Рядок 11:
| Шифр || K2-ERP-TZ-REG-001
| Шифр || K2-ERP-TZ-REG-001
|-
|-
| Версія || 1.0 (чернетка на узгодження)
| Версія || 1.1 (чернетка на узгодження; +Додаток Д «Каталог прикладів»)
|-
|-
| Статус || На розгляді
| Статус || На розгляді
Рядок 571: Рядок 571:
   recorders: [Документ.НарахуванняЗарплати]
   recorders: [Документ.НарахуванняЗарплати]
</syntaxhighlight>
</syntaxhighlight>
{{Note|Розширений каталог із 17 прикладів регістрів усіх типів і підвидів — з деклараціями, даними у фізичних таблицях, запитами, наскрізними сценаріями та антипатернами — див. '''Додаток Д'''.}}


== 6.8. UI конструктора метаданих ==
== 6.8. UI конструктора метаданих ==
Рядок 2584: Рядок 2586:
|-
|-
| В8 || Цільова СКБД для першої черги: тільки PostgreSQL чи одразу обидві? || Архітектор / ІТ-експлуатація || До Е1
| В8 || Цільова СКБД для першої черги: тільки PostgreSQL чи одразу обидві? || Архітектор / ІТ-експлуатація || До Е1
|-
| В9 || '''Міжпланова база розрахунку:''' чи потрібна можливість брати базу з ''іншого'' регістру розрахунку (Утримання ← ОсновніНарахування)? Якщо так — ФТ-25 і <code>md_register</code> потребують розширення полем <code>base_register_id</code> (див. Д.7.2) || Методолог ЗП / архітектор || До Е7
|-
| В10 || Чи прийнятна поведінка підпорядкованого РВ без контролю унікальності за (Період + Вимірювання)? (сумісність із 1С vs. захист від дублів — див. Д.3.2, антипатерн А-6) || Методолог || До Е2
|}
|}
== Додаток Д. Каталог прикладів регістрів ==
Додаток містить прикладні приклади регістрів усіх типів і підвидів. Для кожного наведено: декларацію (DSL), пояснення проєктних рішень, приклад даних у фізичних таблицях і типовий запит. Приклади призначені для: (а) перевірки повноти метамоделі на етапі Е0; (б) формування набору приймальних тестів; (в) навчання прикладних розробників.
=== Д.1. Як обрати тип регістру (дерево рішень) ===
<graphviz>
digraph Choose {
  rankdir=TB; bgcolor="transparent";
  node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=9];
  edge [fontname="Helvetica", fontsize=8];
  q1 [label="Що зберігаємо?", shape=diamond, fillcolor="#ffe0b2"];
  q2 [label="Потрібні залишки\n(скільки Є зараз)?", shape=diamond, fillcolor="#ffe0b2"];
  q3 [label="Подвійний запис,\nплан рахунків,\nсубконто?", shape=diamond, fillcolor="#ffe0b2"];
  q4 [label="Періоди дії,\nвитіснення,\nбаза розрахунку?", shape=diamond, fillcolor="#ffe0b2"];
  q5 [label="Значення прив'язане\nдо документа?", shape=diamond, fillcolor="#ffe0b2"];
  ri_i [label="РВ незалежний\n(КурсиВалют,\nШтрихкоди)", fillcolor="#e8f5e9"];
  ri_s [label="РВ підпорядкований\n(ЦіниНоменклатури,\nКадровіДані)", fillcolor="#e8f5e9"];
  ra_b [label="РН «Залишки»\n(ТовариНаСкладах,\nВзаєморозрахунки)", fillcolor="#fff8e1"];
  ra_t [label="РН «Обороти»\n(Продажі,\nВитрати)", fillcolor="#fff8e1"];
  rb  [label="РБ\n(Госпрозрахунковий)", fillcolor="#fce4ec"];
  rc  [label="РР\n(Нарахування,\nУтримання)", fillcolor="#ede7f6"];
  q1 -> q3 [label="облікові проводки"];
  q3 -> rb [label="так"];
  q3 -> q4 [label="ні"];
  q4 -> rc [label="так"];
  q4 -> q2 [label="ні"];
  q1 -> q2 [label="кількісні/сумові\nрухи"];
  q2 -> ra_b [label="так"];
  q2 -> ra_t [label="ні,\nлише обороти\nза період"];
  q1 -> q5 [label="значення\nатрибута\nв часі"];
  q5 -> ri_s [label="так"];
  q5 -> ri_i [label="ні"];
}
</graphviz>
'''Контрольні питання при проєктуванні будь-якого регістру:'''
# Чи можна відповісти на всі прикладні питання ''лише'' полями цього регістру, без з'єднання з документами? Якщо ні — бракує реквізиту (не вимірювання!).
# Яка ''кардинальність'' таблиці підсумків = ∏(кількість значень вимірювань) × кількість періодів? Якщо > 10<sup>8</sup> — переглянути склад вимірювань.
# Чи потрібно фільтрувати/групувати за цим полем у звітах? Так → вимірювання. Ні, лише показувати → реквізит.
# Чи підсумовується поле? Так → ресурс. Ні → реквізит або вимірювання.
=== Д.2. Зведений каталог ===
{| class="wikitable sortable"
! № !! Регістр !! Тип !! Підвид / режим !! Вимірювання !! Ресурси !! Розділ
|-
| 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. КурсиВалют — періодичний, незалежний ====
'''Задача:''' зберігати історію курсів валют; отримувати курс на будь-яку дату.
<syntaxhighlight lang="yaml">
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 }
</syntaxhighlight>
'''Проєктні рішення:'''
* <code>Independent</code> — курс не породжується документом, він є зовнішнім фактом.
* <code>master: true</code> для Валюти — при видаленні валюти курси видаляються; регістр видно у формі елемента довідника.
* Періодичність <code>WithinDay</code>, а не <code>WithinSecond</code> — повторний запис курсу за той самий день ''замістить'' попередній, а не створить дубль.
'''Дані у таблиці <code>rg_i_0007</code>:'''
{| class="wikitable"
! 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
|}
'''Запит (курс на дату документа):'''
<syntaxhighlight lang="sql">
ВИБРАТИ Курси.Курс, Курси.Кратність
З РегістрВідомостей.КурсиВалют.ЗрізОстанніх(&ДатаДокумента, Валюта = &Валюта) ЯК Курси
</syntaxhighlight>
<syntaxhighlight lang="csharp">
// або через менеджер
var курс = Registers.Information["КурсиВалют"]
                    .GetLast(doc.Дата, new Filter { ["Валюта"] = doc.Валюта });
decimal сумаГрн = doc.СумаВалюти * курс.Курс / курс.Кратність;
</syntaxhighlight>
==== Д.3.2. ЦіниНоменклатури — періодичний, підпорядкований реєстратору ====
'''Задача:''' зберігати історію цін; ціна встановлюється наказом (документом), який можна скасувати.
<syntaxhighlight lang="yaml">
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:
    - Документ.ВстановленняЦінНоменклатури
</syntaxhighlight>
'''Проєктні рішення:'''
* <code>SubordinateToRecorder</code> — потрібна юридична прив'язка ціни до наказу, можливість скасувати наказ і «відкотити» ціни одним рухом.
* <code>Валюта</code> і <code>Одиниця</code> — '''реквізити''', а не вимірювання: за ними не фільтрують і не будують зрізи, вони визначаються типом цін. ''Помилка новачка'' — зробити Валюту вимірюванням, що подвоїть кількість записів і зламає зріз останніх.
* <code>Характеристика</code> дозволяє порожнє значення — для номенклатури без характеристик.
'''Дані у таблиці <code>rg_i_0010</code>:'''
{| class="wikitable"
! 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
|}
{{Note|Зверніть увагу: у підпорядкованому РВ '''немає''' унікальності за (Період + Вимірювання). Два різні документи можуть встановити ціну на ту саму дату — платформа це дозволить, зріз останніх поверне запис із більшим <code>line_no</code> у межах реєстратора з пізнішою позицією. Це '''свідома''' поведінка (сумісність із 1С), але прикладний розробник має контролювати дублі самостійно — див. антипатерн А-6 (Д.10).}}
==== Д.3.3. ШтрихкодиНоменклатури — неперіодичний, незалежний ====
'''Задача:''' за штрихкодом знайти номенклатуру. Класичний приклад «вимірювання = те, ''за чим'' шукаємо; ресурс = те, що ''знаходимо''».
<syntaxhighlight lang="yaml">
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 }
</syntaxhighlight>
<syntaxhighlight lang="sql">
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);  -- зворотний пошук: штрихкоди товару
</syntaxhighlight>
'''Проєктне рішення:''' PK за вимірюванням автоматично гарантує, що один штрихкод не може вказувати на два товари. Якби Номенклатуру зробили вимірюванням, а Штрихкод — ресурсом, ця гарантія зникла б, і довелося б писати перевірку в прикладному коді.
==== Д.3.4. ГрафікРоботи — неперіодичний, незалежний (використовується як графік для РР) ====
<syntaxhighlight lang="yaml">
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)" }      # тривалість зміни
</syntaxhighlight>
'''Проєктне рішення (важливе):''' <code>ДатаГрафіка</code> — це '''вимірювання''' типу <code>Дата</code>, а не стандартний реквізит <code>Період</code>. Регістр '''неперіодичний'''. Причина: графік описує ''календар'' (кожна дата — окремий факт), а не ''історію зміни значення''. Якби використали періодичність, зріз останніх повертав би «останній робочий день», що безглуздо. Механізм РР (<code>ДаніГрафіка</code>) очікує саме таку структуру.
'''Дані у таблиці <code>rg_i_0015</code>:'''
{| class="wikitable"
! 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. КадровіДані — по позиції реєстратора ====
'''Задача:''' зберігати актуальні кадрові дані співробітника з історією; підтримати кілька наказів однією датою в правильному порядку.
<syntaxhighlight lang="yaml">
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:
    - Документ.ПрийомНаРоботу
    - Документ.КадровеПереміщення
    - Документ.Звільнення
</syntaxhighlight>
'''Проєктне рішення:''' <code>ByRecorderPosition</code> — коли два накази оформлені однією датою (переміщення + зміна ставки), звичайна періодичність «у межах дня» дала б недетермінований результат зрізу. Позиція реєстратора додає до ключа <code>(rec_t, rec_r, line_no)</code> і впорядковує за моментом часу документа.
'''Фізична схема:'''
<syntaxhighlight lang="sql">
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;
</syntaxhighlight>
==== Д.3.6. НалаштуванняКористувачів — складений тип ресурсу ====
<syntaxhighlight lang="yaml">
register:
  name: НалаштуванняКористувачів
  kind: Information
  periodicity: Nonperiodical
  writeMode: Independent
  dimensions:
    - { name: Користувач,  type: "Довідник.Користувачі", master: true }
    - { name: Налаштування, type: "ПланВидівХарактеристик.НалаштуванняКористувачів", master: true }
  resources:
    - name: Значення
      type: ["Рядок(500)", "Число(15,2)", "Дата", "Булево",
            "Довідник.Організації", "Довідник.Склади"]    # ← складений тип
</syntaxhighlight>
<syntaxhighlight lang="sql">
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)
);
</syntaxhighlight>
{| class="wikitable"
! 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. Тут — дані.
'''Рухи (<code>rg_a_0012</code>) після проведення трьох документів:'''
{| class="wikitable"
! 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
|}
'''Підсумки залишків (<code>rg_a_0012_tb</code>), ТА = 2026-07-31:'''
{| class="wikitable"
! 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. ВзаєморозрахункиЗКонтрагентами ====
'''Задача:''' облік дебіторської/кредиторської заборгованості в розрізі договорів і замовлень.
<syntaxhighlight lang="yaml">
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:
    - Документ.ВидатковаНакладна
    - Документ.ПрихіднаНакладна
    - Документ.ПлатіжнеДоручення
    - Документ.КасовийОрдер
    - Документ.КоригуванняБоргу
</syntaxhighlight>
'''Угода про знак (обов'язково фіксується в методології!):'''
{| class="wikitable"
! Вид руху !! Сенс !! Приклад документа
|-
| '''Прихід''' || Борг контрагента перед нами '''зростає''' (дебіторка +) || Видаткова накладна
|-
| '''Витрата''' || Борг контрагента перед нами '''зменшується''' (оплата) || Платіжне доручення вхідне
|}
Кредиторська заборгованість = від'ємний залишок. ''Альтернатива'' — два окремих ресурси (СумаДт/СумаКт) або два регістри; обраний варіант — один знаковий ресурс (простіше, менший обсяг підсумків).
'''Приклад даних:'''
{| class="wikitable"
! Документ !! 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 ''(передоплата)''
|}
'''Запит (акт звірки):'''
<syntaxhighlight lang="sql">
ВИБРАТИ
    Дані.Контрагент, Дані.Договір,
    Дані.СумаПочатковийЗалишок, Дані.СумаПрихід,
    Дані.СумаВитрата,          Дані.СумаКінцевийЗалишок
З РегістрНакопичення.ВзаєморозрахункиЗКонтрагентами.ЗалишкиІОбороти(
      &Початок, &Кінець, Авто, Рух,
      Організація = &Організація І Контрагент = &Контрагент
  ) ЯК Дані
</syntaxhighlight>
==== Д.4.3. ТовариВРезерві — регістр «супутник» ====
<syntaxhighlight lang="yaml">
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:
    - Документ.ЗамовленняПокупця
    - Документ.ВидатковаНакладна
    - Документ.ЗакриттяЗамовлень
</syntaxhighlight>
'''Патерн «доступний залишок»''' — з'єднання двох регістрів:
<syntaxhighlight lang="sql">
ВИБРАТИ
    ЄСТЬNULL(Зал.Номенклатура, Рез.Номенклатура) ЯК Номенклатура,
    ЄСТЬNULL(Зал.Склад, Рез.Склад) ЯК Склад,
    ЄСТЬNULL(Зал.КількістьЗалишок, 0) ЯК ВСкладі,
    ЄСТЬNULL(Рез.КількістьЗалишок, 0) ЯК ВРезерві,
    ЄСТЬNULL(Зал.КількістьЗалишок, 0) - ЄСТЬNULL(Рез.КількістьЗалишок, 0) ЯК Доступно
З РегістрНакопичення.ТовариНаСкладах.Залишки(&Період, Склад = &Склад) ЯК Зал
ПОВНЕ З'ЄДНАННЯ РегістрНакопичення.ТовариВРезерві.Залишки(&Період, Склад = &Склад) ЯК Рез
    ЗА Зал.Номенклатура = Рез.Номенклатура І Зал.Склад = Рез.Склад
</syntaxhighlight>
{{Note|Чому окремий регістр, а не ресурс «Резерв» у <code>ТовариНаСкладах</code>? Тому що резерв ведеться в розрізі '''Замовлення''', а залишок — у розрізі '''Партії'''. Додавання Замовлення як вимірювання до <code>ТовариНаСкладах</code> помножило б таблицю підсумків на кардинальність замовлень (сотні тисяч рядків на товар). Це ілюстрація контрольного питання №2 з Д.1.}}
==== Д.4.4. ГрошовіКоштиБезготівкові ====
<syntaxhighlight lang="yaml">
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: [Документ.ПлатіжнеДоручення, Документ.БанківськаВиписка, Документ.ПереміщенняКоштів]
</syntaxhighlight>
'''Проєктне рішення:''' Валюта — '''не''' вимірювання, тому що вона однозначно визначається банківським рахунком (функціональна залежність). Її додавання як вимірювання створило б надлишковість і ризик неузгоджених даних. Валюта отримується з'єднанням із довідником.
=== Д.5. Регістри накопичення, вид «Обороти» ===
==== Д.5.1. Продажі — з агрегатами ====
<syntaxhighlight lang="yaml">
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
</syntaxhighlight>
'''Проєктні рішення:'''
* <code>Turnover</code>, а не <code>Balance</code>: немає <code>ВидРуху</code>, немає таблиці залишків → удвічі менше підсумків і немає кумулятивних оновлень. Повернення відображається '''від'ємною''' сумою, а не «витратою».
* 6 вимірювань → таблиця підсумків надто широка для швидких звітів → компенсуємо агрегатами.
'''Ефект агрегатів:'''
{| class="wikitable"
! Звіт !! Без агрегатів (<code>rg_a_0031_tt</code>) !! З агрегатом !! Виграш
|-
| Продажі по товарах за рік || скан 8 млн рядків підсумків || <code>ag1</code>: 40 тис. рядків || ×200
|-
| Рейтинг менеджерів за квартал || скан 8 млн рядків || <code>ag2</code>: 1,8 тис. рядків || ×4000
|-
| Продажі по контрагенту за 3 роки || скан 8 млн рядків || <code>ag3</code>: 60 тис. рядків || ×130
|-
| Продажі по товару + контрагенту || '''агрегат не покриває''' → підсумки || — || —
|}
<syntaxhighlight lang="sql">
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)
);
</syntaxhighlight>
==== Д.5.2. ВитратиЗаСтаттями ====
<syntaxhighlight lang="yaml">
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: [Документ.АвансовийЗвіт, Документ.НадходженняПослуг, Документ.НарахуванняЗарплати, Документ.Амортизація]
</syntaxhighlight>
'''Приклад даних (<code>rg_a_0032_tt</code>, підсумки оборотів за місяць):'''
{| class="wikitable"
! 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 грн.
'''Проводки (<code>rg_b_0020</code>, реєстратор = ВидатковаНакладна №12):'''
{| class="wikitable"
! 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 (<code>rg_b_0020_tb0</code>), на 5999-11-01:'''
{| class="wikitable"
! 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 (<code>rg_b_0020_tb2</code>) — для рахунку 281 (2 субконто: Номенклатура, Склад):'''
{| class="wikitable"
! 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
|}
Згортка <code>tb2</code> → <code>tb0</code>: 17 500 + 7 000 + 12 000 = '''36 500'''… ''(різниця з таблицею вище — інші операції; у тесті ФТ-Т-08 перевіряється точна рівність)''.
'''Запит (ОСВ по рахунку):'''
<syntaxhighlight lang="sql">
ВИБРАТИ
    Дані.Субконто1 ЯК Номенклатура, Дані.Субконто2 ЯК Склад,
    Дані.СумаПочатковийЗалишокДт, Дані.КількістьПочатковийЗалишокДт,
    Дані.СумаОборотДт, Дані.СумаОборотКт,
    Дані.СумаКінцевийЗалишокДт, Дані.КількістьКінцевийЗалишокДт
З РегістрБухгалтерії.Госпрозрахунковий.ЗалишкиІОбороти(
      &Початок, &Кінець, Місяць,
      Рахунок = &Рахунок281,
      , Організація = &Організація,
      ,
  ) ЯК Дані
</syntaxhighlight>
==== Д.6.2. ПодатковийОблік — без кореспонденції ====
'''Задача:''' податковий облік із розділенням на постійні/тимчасові різниці, де подвійний запис не потрібен (кожен факт реєструється окремо по Дт або по Кт).
<syntaxhighlight lang="yaml">
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: [Документ.БухгалтерськаОперація, Документ.ВидатковаНакладна, Документ.НадходженняПослуг]
</syntaxhighlight>
'''Проводки (<code>rg_b_0021</code>):'''
{| class="wikitable"
! 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:'''
{| class="wikitable"
! !! З кореспонденцією !! Без кореспонденції
|-
| Один факт = || 1 запис (Дт+Кт) || 2 записи (окремо Дт, окремо Кт)
|-
| Поля рахунків || <code>acc_dt_r</code>, <code>acc_kt_r</code> || <code>acc_r</code> + <code>acc_mvt</code>
|-
| Субконто || 2 набори (Дт, Кт) || 1 набір
|-
| Кількість записів || N || 2N
|-
| Таблиця <code>_tc</code> (кореспонденції) || Є || '''Немає''' — «шахова відомість» неможлива
|-
| Контроль Дт=Кт || У межах реєстратора || Опційно (може бути незбалансовано за задумом)
|-
| Коли обирати || Класичний облік, потрібен аналіз кореспонденцій || Облік без потреби в кореспонденціях, забалансовий, статистичний
|}
==== Д.6.3. Бюджетування ====
<syntaxhighlight lang="yaml">
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: [Документ.БюджетнаОперація, Документ.ВведенняБюджету]
</syntaxhighlight>
'''Проєктне рішення:''' <code>Сценарій</code> — вимірювання, а не окремий регістр на кожен сценарій (План/Факт/Прогноз). Це дозволяє план-фактний аналіз одним запитом. Усі три вимірювання '''балансові''' — бюджетна проводка не змінює сценарій/ЦФВ між дебетом і кредитом.
=== Д.7. Регістри розрахунку ===
==== Д.7.1. ОсновніНарахування — період дії + витіснення ====
'''План видів розрахунку <code>ОсновніНарахування</code>:'''
{| class="wikitable"
! Вид розрахунку !! Період дії !! Витісняючі !! Базові !! Залежність від бази
|-
| Оклад || Так || Відпустка, Лікарняний || — || Не залежить
|-
| Відпустка || Так || Лікарняний || Оклад, Премія || За періодом дії
|-
| Лікарняний || Так || — || Оклад || За періодом дії
|-
| Премія || Ні || — || Оклад || За періодом реєстрації
|}
Декларація регістру — розділ 6.7.
'''Записи (<code>rg_c_0040</code>), період реєстрації = липень 2026:'''
{| class="wikitable"
! 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
|}
'''Фактичні періоди дії (<code>rg_c_0040_ap</code>) — результат витіснення:'''
{| class="wikitable"
! 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.
'''Запит (дані графіка для розрахунку окладу):'''
<syntaxhighlight lang="sql">
ВИБРАТИ
    Дані.Співробітник, Дані.НомерРядка,
    Дані.ЗначенняФактичногоПеріодуДії ЯК ВідпрацьованоДнів,
    Дані.ЗначенняПеріодуДії          ЯК НормаДнів
З РегістрРозрахунку.ОсновніНарахування.ДаніГрафіка(
      ВидРозрахунку = ЗНАЧЕННЯ(ПланВидівРозрахунку.ОсновніНарахування.Оклад)
      І Реєстратор = &Реєстратор
  ) ЯК Дані
</syntaxhighlight>
'''Результат:''' Іваненко — <code>ВідпрацьованоДнів = 13</code> (робочі дні у 01–09 та 23–31), <code>НормаДнів = 23</code> → Оклад = 18 000 × 13 / 23 = '''10 173,91'''.
'''Запит (база для відпустки):'''
<syntaxhighlight lang="sql">
ВИБРАТИ База.Співробітник, База.НомерРядка, База.РезультатБаза ЯК Заробіток
З РегістрРозрахунку.ОсновніНарахування.БазаРозрахунку(
      , /* вимірювання бази */
      , /* вимірювання основного регістру */
      Співробітник,                          /* розрізи */
      ВидРозрахунку = ЗНАЧЕННЯ(ПланВидівРозрахунку.ОсновніНарахування.Відпустка)
  ) ЯК База
</syntaxhighlight>
==== Д.7.2. Утримання — без періоду дії, база за періодом реєстрації ====
'''План видів розрахунку <code>Утримання</code>:'''
{| class="wikitable"
! Вид розрахунку !! Період дії !! Базові види розрахунку !! Залежність
|-
| ПДФО || Ні || ''(усі з ПВР ОсновніНарахування)'' || За періодом реєстрації
|-
| Військовий збір || Ні || ''(усі з ПВР ОсновніНарахування)'' || За періодом реєстрації
|-
| Аліменти || Ні || Оклад, Премія, Відпустка || За періодом реєстрації
|-
| Профспілковий внесок || Ні || Оклад || За періодом реєстрації
|}
<syntaxhighlight lang="yaml">
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: [Документ.НарахуванняЗарплати]
</syntaxhighlight>
'''Фізична схема (спрощена — немає полів періоду дії):'''
<syntaxhighlight lang="sql">
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)
);
</syntaxhighlight>
{{Note|'''Міжпланова база.''' Базові види розрахунку для ПВР <code>Утримання</code> належать до ''іншого'' ПВР — <code>ОсновніНарахування</code>. Це означає, що віртуальна таблиця <code>БазаРозрахунку</code> регістру <code>Утримання</code> має читати записи регістру <code>ОсновніНарахування</code>. '''Вимога ФТ-25 має бути розширена:''' метамодель повинна дозволяти вказати ''регістр-джерело бази''. Це '''відкрите питання В9''' (див. оновлений Додаток Г) — у 1С механізм працює через відповідність ПВР, а не регістрів, що обмежує сценарії. Пропозиція: додати до <code>md_register</code> поле <code>base_register_id</code>.}}
'''Приклад даних:'''
{| class="wikitable"
! 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 грн.
<graphviz>
digraph Posting {
  rankdir=LR; bgcolor="transparent";
  node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=9];
  D [label="Документ\nВидаткова накладна №12\n05.07.2026", fillcolor="#fff8e1", shape=note];
  R1 [label="РН ТовариНаСкладах\n(Залишки)\nВитрата: 30 кг / 7 000 грн", fillcolor="#fff3e0"];
  R2 [label="РН Взаєморозрахунки\n(Залишки)\nПрихід: 12 000 грн", fillcolor="#fff3e0"];
  R3 [label="РН Продажі\n(Обороти)\n30 кг / 12 000 / 10 000 / 7 000", fillcolor="#fff3e0"];
  R4 [label="РН ТовариВРезерві\n(Залишки)\nВитрата: 30 кг", fillcolor="#fff3e0"];
  R5 [label="РБ Госпрозрахунковий\n3 проводки на 21 000", fillcolor="#fce4ec"];
  T1 [label="rg_a_0012_tb\nMAXPERIOD: 80 → 50 кг", fillcolor="#ffe0b2", shape=cylinder];
  T2 [label="rg_a_0013_tb\nMAXPERIOD: 0 → +12 000", fillcolor="#ffe0b2", shape=cylinder];
  T3 [label="rg_a_0031_tt\n2026-07: +30 / +12 000", fillcolor="#ffe0b2", shape=cylinder];
  T5 [label="rg_b_0020_tb0/tb2/tt0/tc", fillcolor="#f8bbd0", shape=cylinder];
  D -> R1; D -> R2; D -> R3; D -> R4; D -> R5;
  R1 -> T1; R2 -> T2; R3 -> T3; R5 -> T5;
  label="ОДНА транзакція БД: усі 5 регістрів + підсумки + прапорець Проведений";
  labelloc=b; fontname="Helvetica"; fontsize=10;
}
</graphviz>
<syntaxhighlight lang="csharp">
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);                  // прикладна перевірка
}
</syntaxhighlight>
'''Що відбувається у БД (одна транзакція):'''
{| class="wikitable"
! Крок !! Таблиця !! Операція !! Рядків
|-
| 1 || <code>doc_0003</code> || UPDATE posted = true || 1
|-
| 2 || <code>rg_a_0012</code> (ТовариНаСкладах) || DELETE + INSERT || 1
|-
| 3 || <code>rg_a_0012_tb</code> || MERGE (MAXPERIOD + періоди > 07.2026) || 2
|-
| 4 || <code>rg_a_0013</code> (Взаєморозрахунки) || DELETE + INSERT || 1
|-
| 5 || <code>rg_a_0013_tb</code> || MERGE || 2
|-
| 6 || <code>rg_a_0031</code> (Продажі) || DELETE + INSERT || 1
|-
| 7 || <code>rg_a_0031_tt</code> || MERGE (тільки період 2026-07) || 1
|-
| 8 || <code>rg_a_0031_ag1</code> || MERGE (realtime-агрегат) || 1
|-
| 9 || <code>rg_a_0014</code> (Резерв) || DELETE + INSERT || 1
|-
| 10 || <code>rg_b_0020</code> (проводки) || DELETE + INSERT || 3
|-
| 11 || <code>rg_b_0020_tb0</code> || MERGE || 10
|-
| 12 || <code>rg_b_0020_tb1/tb2</code> || MERGE || 8
|-
| 13 || <code>rg_b_0020_tt0..tt2</code> || MERGE || 12
|-
| 14 || <code>rg_b_0020_tc</code> || MERGE || 3
|-
| colspan="3" | '''Разом''' || '''≈ 47 рядків, 1 COMMIT'''
|}
{{Note|'''Спостереження для НФВ-04.''' Один рядок товару в накладній породжує ~47 модифікацій рядків у 14 таблицях. Для накладної зі 100 рядків це ~2 000–4 000 модифікацій. Саме тут — основне навантаження проведення, і саме тому РБ (кроки 10–14) дає ~70 % вартості транзакції. Це аргумент на користь MVP без РБ (розділ 14) і предмет вимірювання в НТ-03.}}
=== Д.9. Наскрізний приклад: розрахунок зарплати ===
<pre>
КРОК 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 (ПерерахунокЗаБазою):
            (Реєстратор = НарахуванняЗП липня, ВидРозрахунку = Відпустка,  Співробітник = Іваненко)
            (Реєстратор = НарахуванняЗП липня, ВидРозрахунку = Лікарняний, Співробітник = Іваненко)
        → Звіт «Документи до перерахунку» показує липневий документ.
        → Бухгалтер перепроводить → база перераховується → записи перерахунку очищаються.
</pre>
=== Д.10. Антипатерни проєктування регістрів ===
{| class="wikitable"
! ID !! Антипатерн !! Чому погано !! Як правильно
|-
| '''А-1''' || Дата як '''вимірювання''' у РН (замість стандартного <code>Період</code>) || Таблиця підсумків множиться на кількість дат і зростає нескінченно; віртуальні таблиці <code>Залишки</code>/<code>Обороти</code> ігнорують це вимірювання і дають безглузді результати || Використовувати стандартний реквізит <code>Період</code>. Виняток — календарі (Д.3.4), де це РВ, а не РН
|-
| '''А-2''' || РН «Залишки» там, де потрібна лише історія значення || Надлишкові таблиці підсумків, кумулятивні оновлення, вид руху без сенсу || РВ періодичний
|-
| '''А-3''' || РН «Залишки» там, де залишок безглуздий (Продажі, Витрати) || Удвічі більший обсяг підсумків, кумулятивні MERGE на всі майбутні періоди || РН «Обороти» + агрегати (Д.5.1)
|-
| '''А-4''' || Реквізит документа продубльовано у вимірювання «щоб фільтрувати у звіті» || Кардинальність підсумків × N; типовий випадок — Менеджер/Валюта/Коментар || Реквізит регістру + з'єднання з документом у звіті; або окремий регістр «Обороти»
|-
| '''А-5''' || > 8 вимірювань в одному РН || Таблиця підсумків стає '''більшою''' за таблицю рухів; проведення сповільнюється в рази || Розділити на регістри за призначенням; частину аналітики — в «Обороти» з агрегатами
|-
| '''А-6''' || Незалежний РВ «у межах секунди» як журнал подій || Два записи в ту саму секунду → другий '''мовчки замістить''' перший; втрата даних || Підпорядкований РВ (реєстратор у ключі) або додати вимірювання-дискримінатор
|-
| '''А-7''' || Ресурс типу <code>Рядок</code>/<code>Посилання</code> у РН || Підсумовування неможливе; підсумки не побудуються || Реквізит (не агрегується) або вимірювання (якщо це розріз)
|-
| '''А-8''' || Прямий <code>UPDATE</code> таблиці підсумків із прикладного коду || Розсинхронізація підсумків і рухів; діагностика — тижні || Тільки через набори записів. Заборона на рівні прав БД (розділ 12.2)
|-
| '''А-9''' || Один «універсальний» регістр із вимірюванням <code>ВидОбліку</code> (БУ/ПУ/УО) замість трьох || Втричі більша таблиця; конкуренція за блокування між незалежними обліками; неможливо окремо перерахувати підсумки || Окремі регістри. Виняток — РБ <code>ПодатковийОблік</code> (Д.6.2), де ВидРізниці — це справді розріз одного обліку
|-
| '''А-10''' || Функціонально залежне поле як вимірювання (Валюта при БанківськомуРахунку) || Надлишковість; ризик неузгоджених даних; зайвий розмір ключа підсумків || З'єднання з довідником у запиті (Д.4.4)
|-
| '''А-11''' || РБ там, де достатньо РН || Замість 1 таблиці — 10; ~70 % вартості проведення (Д.8); складність субконто без потреби || РН. РБ — лише коли потрібні план рахунків, подвійний запис і кореспонденції
|-
| '''А-12''' || Вимірювання з <code>nullable</code> без потреби || Порожнє посилання — окреме значення ключа; підсумки «розмазуються» на два рядки замість одного || <code>denyEmpty: true</code> скрізь, де порожнє значення не має сенсу
|-
| '''А-13''' || Контроль залишків '''до''' запису рухів || Класична гонитва: два сеанси проходять контроль і обидва списують у мінус || Спершу <code>DataLock</code> → <code>WriteNow()</code> → потім контроль (Д.8, ФТ-Т-12)
|-
| '''А-14''' || <code>periodicity: WithinSecond</code> «про всяк випадок» скрізь || Для РВ — неможливість замістити запис за день; для РН — марна точність, більші індекси || Обирати мінімально достатню: день для цін/курсів/виписок, секунда — для складських рухів
|}
=== Д.11. Чек-лист рев'ю нового регістру ===
<pre>
□ Тип регістру обґрунтований за деревом Д.1
□ Кожне вимірювання використовується у фільтрі АБО групуванні звітів
□ Кожен ресурс має сенс при підсумовуванні
□ Немає полів з антипатернів А-1, А-4, А-7, А-10, А-12
□ Оцінка кардинальності підсумків ≤ 10^8 (калькулятор конструктора, ФТ-К-03)
□ Для вимірювань, де порожнє значення безглузде, стоїть denyEmpty
□ Ведучі (master) вимірювання визначені — інакше «висячі» записи при видаленні об'єктів
□ Склад реєстраторів повний; зайвих типів документів немає
□ Угода про знак / вид руху зафіксована в методологічній документації
□ Періодичність мінімально достатня (А-14)
□ Для РН «Обороти» з >4 вимірюваннями визначено ≥1 агрегат
□ Для РБ: балансові/небалансові вимірювання та ресурси розставлені свідомо
□ Для РР: витісняючі та базові види розрахунку в ПВР заповнені; перерахунки визначені
□ Партиціонування ввімкнене, якщо очікується >100 млн записів
□ Написано тест на проведення + скасування + перерахунок підсумків
</pre>


----
----
''Кінець документа. Версія 1.0. Підлягає узгодженню з архітектором платформи K2 ERP до затвердження.''
''Кінець документа. Версія 1.1. Підлягає узгодженню з архітектором платформи K2 ERP до затвердження.''