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

Документообіг та узгодження (Approvals + Internal Documents)

Тип: horizontal-модуль (community) Backend: backend/approvals/ (движок) + backend/internal_documents/ (документи-консументи) Frontend: секція «Внутрішні документи» (frontend/dop/internal-documents/) Статус: ✅ реалізовано (2026-05-31) Архітектура / план: planning/internal-documents-approval-2026-05-31.md · AI-bundle: docs/ai/domains/{approvals,internal_documents}/


Призначення

Внутрішні документи компанії — службові записки, рапорти, накази, договори — проходять складну процедуру узгодження та підписання між підрозділами (автор → начальник → суміжний відділ → директор → канцелярія). Модуль веде сам документ, проганяє його через налаштовуваний маршрут, фіксує підписи/рішення кожного підписанта і дає кожному виконавцю чергу «мені на узгодження».

Ключове архітектурне рішення: движок узгодження (approvals) — generic і polymorphic (працює над будь-яким документом через GenericFK), а конкретні документи (InternalDocument, ContractDocument) — його перші консументи. Завтра на узгодження можна пустити будь-який інший документ без переробки движка.

Дві групи сутностей

Движок approvals

Модель Роль
ApprovalRoute + ApprovalRouteStep Шаблон маршруту: впорядковані стадії. Кроки з однаковим order — паралельні (всі мусять погодити); різні order — послідовні.
ApprovalProcess + ApprovalTask Запущений екземпляр на конкретному документі (снапшот кроків — редагування маршруту не зачіпає процеси в роботі).
ApprovalSigningKey Person-bound Ed25519-ключ (Tier-2 self-sign, TEST MODE).
ApprovalDelegation Делегування узгоджень заступнику на період.

Документи-консументи internal_documents

Модель Роль
InternalDocumentKind Налаштовуваний вид (службова / рапорт / наказ) + маршрут за замовчуванням.
InternalDocument Generic внутрішній документ (kind / title / body / route).
ContractDocument Договір (наша юрособа / контрагент / сума / строк / route) — підписується від імені юрособи.

ContractDocumentessentials.Contract. Останній — обліковий довідник (тарифи, прив'язка до клієнта, вимір для накладних). ContractDocument — документ узгодження й підписання. Це різні життєві цикли (D7). Зв'язок між ними (підписаний договір → обліковий контракт) — опційний follow-up, не реалізовано.

Життєвий цикл документа

draft ──send-for-approval──▶ on_approval ──(усі кроки погоджено)──▶ approved
                                  ├── відхилено ───────────────────▶ rejected
                                  ├── повернено на доопрацювання ──▶ draft (редагувати + переслати)
                                  └── відкликано автором ──────────▶ draft

InternalDocument/ContractDocumentno-posting документи (не пишуть у PostingGroup/реєстри): обліковий state лишається інертним, робочий стан веде status + лінкований ApprovalProcess. Статус документа синхронізується з результатом процесу автоматично (через domain-event, без зв'язування движка з документом).

Підписи — дворівневі

  • Tier-1 (audit) — для більшості кроків: запис «хто / коли / рішення / коментар».
  • Tier-2 (crypto) — Ed25519-підпис над канонічним тілом документа (TEST MODE; у проді — КЕП/КЕС). Два режими:
  • person self-sign (службова) — особистим ключем користувача;
  • on-behalf-of-org (договір) — ключем юрособи (edo.SigningKey), з перевіркою повноважень (OrgMembership — який користувач уповноважений підписувати за яку юрособу).

Рівень підпису — атрибут кроку маршруту.

Що вміє маршрут

  • Послідовні + паралельні стадії.
  • Виконавець кроку: конкретна людина / роль (будь-який носій) / динамічно «керівник автора» (через core.User.manager).
  • Умовна маршрутизація — крок застосовується лише якщо документ задовольняє умову (напр. сума ≥ X → додати фіндиректора); інакше крок пропускається.
  • Дедлайни на крок + детекція overdue; авто-ескалація прострочених кроків угору по ланцюгу керівників (cron-команда escalate_overdue_approvals).
  • Делегування — заступник діє за делегатора на період.
  • Необов'язкові кроки (is_optional) — не блокують стадію.

Робочі місця (UI) — секція «Внутрішні документи»

Пункт Що
Внутрішні документи Список / створення / надсилання службових + трек узгодження (погодити / повернути / відхилити).
Договори Те саме для ContractDocument (підпис від імені юрособи).
Мої узгодження Черга кроків, що очікують на мою дію (з бейджем «прострочено»).
Налаштування узгодження (admin) Конструктор маршрутів (кроки + виконавці + умови + дедлайни + optional), види документів, делегування.

Паперові версії / скани: у кожному документі — блок «Файли» (загальний DocumentAttachment): завантаження сканів підписаного паперу, PDF, додатків. Цифровий трек = внутрішній запис; скан = паперовий артефакт.

API (огляд)

/api/v1/internal-documents/documents/        CRUD + {id}/send-for-approval/ + {id}/approval/
/api/v1/internal-documents/contracts/        CRUD + {id}/send-for-approval/ + {id}/approval/
/api/v1/internal-documents/kinds/            CRUD видів
/api/v1/approvals/processes/                 (read) процеси з кроками
/api/v1/approvals/tasks/{id}/{approve,reject}/   дія над кроком (reject: send_back=true → доопрацювання)
/api/v1/approvals/tasks/mine/                черга поточного користувача
/api/v1/approvals/{routes,route-steps}/      конструктор маршруту (admin)
/api/v1/approvals/delegations/               делегування
/api/v1/essentials/org-memberships/          хто за яку юрособу підписує (D8)
/api/v1/attachments/?content_type=internal_documents.internaldocument&object_id=…  скани

Авторитетність кроку — у сервісі (approve_task/reject_task перевіряють assignee / роль / делегата / повноваження юрособи), не в entity-RBAC; маршрути/види/делегування — admin-gated.

Налаштування (швидкий старт)

  1. Налаштування узгодження → Маршрути — створити маршрут, додати кроки (виконавці / умови / дедлайни).
  2. → Види документів — створити вид (службова/рапорт/…) з маршрутом за замовчуванням.
  3. (для договорів і Tier-2) Org-membership — призначити, хто підписує за яку юрособу.
  4. Демо-дані: python manage.py seed_internal_documents (маршрут + 3 види на тенант).

Новий backend-app у dev потребує: migrate + seed_shop (AppStore-реєстрація) + доменний seed + рестарт Daphne (без autoreload).

Обмеження / поза scope

  • ContractDocument ↔ essentials.Contract міст (підписаний → обліковий контракт) — не реалізовано.
  • Глибша HR-ієрархія (синхрон User.managerEmployee/Position) — окремий HRM-трек.
  • Auto-escalation потребує налаштованого cron на escalate_overdue_approvals.
  • Tier-2 крипто — TEST MODE (приватний ключ у БД); прод — КЕП/КЕС.