Files
public/directive_layer/TECHNICAL.md

21 KiB
Raw Blame History

Directive Layer (Слой директив)

name: directive_layer version: 19.0.1.0.0 author: Mk.Lab depends: base, web, base_user_role, project

Архитектура

directive_layer
├── directive.directive           — основная сущность: директива
├── directive.stage               — справочник стадий (7 стадий)
├── directive.work.type           — справочник видов работ
├── directive.template            — шаблон директивы
├── directive.template.group      — группа шаблонов (пакет директив)
├── directive.template.group.line — строки группы
├── directive.origin              — связка «модель + ID»
├── directive.policy              — правило автогенерации директив
├── directive.mixin               — AbstractModel для подключения к любой модели
├── res.users (расширение)        — авто-установка домашней страницы исполнителя
└── ExecutorBoard (OWL)           — рабочий стол исполнителя (fullscreen)

Модели

directive.directive — Директива

Файл: models/directive.py

Поле Тип Описание
name Char (required) Название директивы
active Boolean Признак активности
is_simulation Boolean Флаг тестовой/демо-директивы
deadline_at Datetime (required) Дедлайн исполнения
planned_hours Float (required) Трудоёмкость в часах
load_percent Integer (default: 10) Процент загрузки ресурса
executor_type Selection human / machine / software
success_probability Float (default: 98.0) Вероятность успешного выполнения, %
resource_cost Float Стоимость ресурса
retry_policy Selection none / retry_3 / backoff
priority Selection 0 — низкий / 1 — нормальный / 2 — высокий
work_type_id Many2one → directive.work.type (required) Тип работ
executor_role_id Many2one → res.users.role Роль исполнителя
requester_role_id Many2one → res.users.role Роль заявителя
stage_id Many2one → directive.stage Стадия (вид диспетчера)
stage_code Char (related, stored) Технический код текущей стадии
executor_stage_id Many2one → directive.stage Стадия (вид исполнителя)
requester_stage_id Many2one → directive.stage Стадия (вид заявителя)
executor_traffic_light Selection Светофор исполнителя: white/green/yellow/red/black
resource_traffic_light Selection Светофор ресурса: white/green/yellow/red/black
executor_user_id Many2one → res.users Назначенный исполнитель
origin_id Many2one → directive.origin (required, cascade) Объект-источник
template_id Many2one → directive.template Шаблон директивы
policy_id Many2one → directive.policy Политика создания
generator_key Char (index) Ключ дедупликации для режима create_once
predecessor_ids Many2many → directive.directive Директивы-предшественники
sequence Integer Порядок в планировщике

Вычисляемые поля:

Поле Описание
deadline_display Дедлайн в формате дд.мм.гг Ч:ММ с учётом часового пояса пользователя
time_left_display Оставшееся время в формате Xд Yч Zм (отрицательное, если просрочено)
is_overdue Boolean: дедлайн прошёл
priority_stars Строка ★ / ★★ / ★★★

Порядок сортировки по умолчанию: deadline_at asc, priority desc, id desc

Статусы

Допустимые переходы между стадиями:

Текущая стадия Допустимые следующие
draft confirmed, planned, cancelled
confirmed planned, in_progress, cancelled
planned in_progress, cancelled
in_progress paused, done
paused in_progress, cancelled
done — (финальная)
cancelled — (финальная)

Переходы контролируются методом _check_stage_transition(). Недопустимый переход вызывает UserError.

Методы

Метод Описание
action_assign_to_me() Назначить на текущего пользователя (из стадий confirmed / planned)
action_start() Перевести в in_progress
action_pause() Перевести в paused (только из in_progress)
action_done() Завершить директиву и вызвать callback в записи-источнике
action_cancel() Отменить (обновляет все три stage-поля одновременно)
action_open_delete_wizard() Открыть визард удаления
_set_stage(code) Установить стадию по коду (обновляет stage_id, executor_stage_id, requester_stage_id)
_get_origin_record() Получить запись-источник через origin_id.res_model + origin_id.res_id
_build_payload(event) Сформировать словарь данных для передачи в callback
action_quick_change_stage(id, code) @api.model — смена стадии по вызову из JS

Механизм callback при завершении

При вызове action_done() выполняется следующая последовательность:

  1. Стадия устанавливается в done через _set_stage("done")
  2. Из шаблона директивы читается handler_key — имя метода в модели-источнике
  3. Через _get_origin_record() получается запись-источник
  4. Метод handler_key вызывается на записи-источнике с payload

Состав payload:

event, directive_id, directive_name, template_id, template_code, work_type_id, executor_user_id, planned_start_at, planned_end_at, stage_code, priority, load_percent


directive.stage — Стадии

Файл: models/directive_stage.py

Поле Тип Описание
name Char (required) Название стадии
code Char (required, unique) Технический код
is_hidden Boolean Скрыть колонку в канбане

Предустановленные стадии (data/directive_stage_data.xml, noupdate="1"):

Код Название Скрыта в канбане
draft Черновик Нет
confirmed Подтверждена Нет
planned Запланирована Нет
in_progress В работе Нет
paused На паузе Нет
done Выполнена Да
cancelled Отменена Да

directive.template — Шаблон директивы

Файл: models/directive_template.py

Поле Тип Описание
name Char (required) Название шаблона
code Char (required, unique) Технический код
work_type_id Many2one → directive.work.type Тип работ
planned_hours Float (required) Трудоёмкость в часах
load_percent Integer Процент загрузки ресурса
priority Selection 0/1/2 Приоритет директив по этому шаблону
executor_role_id Many2one → res.users.role Роль исполнителя по умолчанию
requester_role_id Many2one → res.users.role Роль заявителя по умолчанию
default_stage_id Many2one → directive.stage Стартовая стадия (по умолчанию: draft)
handler_key Char Имя метода в модели-источнике, вызываемого при завершении директивы
description Text Описание и инструкции для исполнителя

directive.template.group — Группа шаблонов

Файл: models/directive_template_group.py

Поле Тип Описание
name Char (required) Название группы
code Char Технический код
applicable_model Char Техническое имя модели, к которой применяется группа
template_line_ids One2many → directive.template.group.line Строки группы

directive.template.group.line:

Поле Тип Описание
group_id Many2one → directive.template.group (cascade) Родительская группа
sequence Integer Порядок создания директив из этой строки
template_id Many2one → directive.template Шаблон директивы

directive.origin — Источник директив

Файл: models/directive_origin.py

Хранит хранит пару (res_model, res_id) и агрегирует все директивы для данного объекта. На каждую запись создаётся ровно один directive.origin.

Поле Тип Описание
res_model Char (required, index) Техническое имя модели Odoo
res_id Integer (required, index) ID записи
res_ref Reference (computed, stored) Прямая ссылка на запись
directive_ids One2many → directive.directive Все директивы источника
display_name Char (computed, stored) Отображаемое имя в формате res_model,res_id

directive.policy — Политика автогенерации

Файл: models/directive_policy.py

Поле Тип Описание
name Char (required) Название политики
active Boolean Политика активна
sequence Integer Порядок проверки политик
origin_model Char (required) Модель, к которой применяется политика
event Selection Тип события: manual / on_create / on_write / on_stage / on_cron / on_callback
mode Selection create_once — однократно; create_each_time — при каждом срабатывании
use_record_template_group Boolean Брать группу шаблонов из поля записи, а не из фиксированной
template_group_id Many2one → directive.template.group Фиксированная группа шаблонов
trigger_field Char Имя поля (для on_write) или метода (для on_callback)
trigger_value_int Integer Ожидаемое значение поля (ID записи для Many2one-полей)

Логика _policy_should_fire()

Для события on_callback — всегда возвращает True. Для остальных событий: читает значение trigger_field у записи и сравнивает с trigger_value_int. Для Many2one-полей сравнивается .id.

Логика apply_to_records()

Для каждой записи: в режиме create_once проверяет, нет ли уже директив от этой политики для данного origin_id. Если нет — определяет группу шаблонов и вызывает _directive_create_from_group().


directive.mixin — Миксин для моделей

Файл: models/directive_mixin.py
Тип: models.AbstractModel

Подключается к модели через _inherit:

class ProjectTask(models.Model):
    _name = 'project.task'
    _inherit = ['project.task', 'directive.mixin']

Поля, добавляемые в модель

Поле Тип Описание
directive_origin_id Many2one → directive.origin (readonly) Origin-запись данного объекта
directive_ids One2many (related) Все директивы объекта
directive_execution_status Char (computed, stored) Текстовый статус исполнения
directive_template_group_id Many2one → directive.template.group Группа шаблонов объекта

Методы

_ensure_directive_origin() — находит или создаёт directive.origin для записи. Вызывается при create и write.

write() — перехватывает изменения полей. Фиксирует значения до изменения, выполняет super().write(), затем проверяет политики с event='on_write' и вызывает apply_to_records() при совпадении.

_directive_register_callback_wrappers() — вызывается при старте сервера (_register_hook). Оборачивает методы модели через functools.wraps для обработки политик с event='on_callback'. Флаг _directive_wrapped предотвращает повторное оборачивание.

_compute_directive_execution_status() — формирует строку статуса на основе наиболее приоритетной активной директивы. Приоритет стадий: paused > in_progress > planned/confirmed > draft. Результат: "{stage_name}, до {nearest_deadline}, ожидается завершение к {expected_end}".

action_choose_directive_group() — открывает визард directive.choose.group.wizard.

action_open_directives() — открывает список директив, отфильтрованных по origin_id текущей записи.

_directive_create_from_group(template_group, generator_key, policy) — создаёт директивы по строкам группы в порядке sequence. Каждая директива получает параметры из шаблона, ссылку на origin и policy.

Методы для переопределения в модели-источнике

Метод Вызывается
_directive_on_assign(self, payload) При назначении исполнителя
_directive_on_start(self, payload) При переходе в in_progress
_directive_on_pause(self, payload) При переходе в paused
_directive_on_done(self, payload) При завершении директивы
_directive_generation_spec(self) Возвращает список спецификаций для автогенерации (альтернатива Policy UI)

res.users — Расширение

Файл: models/res_users.py

При создании или изменении пользователя проверяет вхождение в группу role_directive_executor_res_groups. При совпадении устанавливает action_id = action_executor_dashboard_client — рабочий стол исполнителя становится домашней страницей пользователя.


Визарды

directive.choose.group.wizard

Файл: wizard/directive_choose_group_wizard.py

Поле Тип Описание
template_group_id Many2one → directive.template.group (required) Выбранная группа шаблонов

Метод action_confirm() берёт active_model и active_id из контекста и записывает выбранную группу в поле directive_template_group_id исходной записи. Сами директивы не создаются — только сохраняется группа.

directive.delete.wizard

Файл: wizard/directive_delete_wizard.py

Режим Действие
archive Мягкое удаление: active=False
delete Жёсткое удаление: unlink()

Представления и меню

Файл: views/directive_views.xml, views/directive_policy_views.xml

Структура меню

Директивы                              (menu_directive_root)
├── Все директивы                      (menu_directive_all)
│       → action_directive_all: directive.directive (kanban/list/form)
├── Политики                           (menu_directive_policy_action)
│       → action_directive_policy: directive.policy (list/form)
└── Настройки                          (menu_directive_settings_root)
    ├── Источники директив             → directive.origin (list/form)
    ├── Статусы директив               → directive.stage (list/form)
    ├── Виды работ                     → directive.work.type (list/form)
    ├── Шаблоны директив               → directive.template (list/form)
    └── Группы шаблонов директив       → directive.template.group (list/form)

Рабочий стол исполнителя

Зарегистрирован как ir.actions.client (action_executor_dashboard_client) с тегом directive_layer.executor_dashboard_action. Открывается в режиме fullscreen. Не является пунктом меню — устанавливается как action_id (домашняя страница) пользователям с ролью исполнителя через res.users.


ExecutorBoard (OWL-компонент)

Файлы: static/src/components/executor_board/executor_board.{js,xml,scss}

Зарегистрирован как ir.actions.client с тегом directive_layer.executor_dashboard_action, открывается в режиме fullscreen.

Логика загрузки данных:

  1. Определяет userId из session.user_id или storeData
  2. Загружает все стадии (directive.stage)
  3. Загружает директивы с фильтром executor_user_id = userId
  4. Строит колонки канбана: стадия и директивы с совпадающим executor_stage_id

Быстрые действия на карточке директивы:

Действие Переход
Начать action_quick_change_stage(id, 'in_progress')
Пауза action_quick_change_stage(id, 'paused')
Завершить action_quick_change_stage(id, 'done')

После каждого действия данные перезагружаются.


Безопасность

Роли (security/roles.xml)

Каждая роль создаётся одновременно в двух системах:

  • res.users.role (модуль base_user_role) — для назначения через интерфейс ролей
  • res.groups — для использования в record rules и ir.model.access
Роль Назначение
role_directive_executor Исполнитель: видит свои директивы, берёт в работу, завершает
role_directive_requester Заявитель: создаёт и отслеживает директивы
role_directive_dispatcher Диспетчер: назначает исполнителей, управляет очередью

Record Rules (security/directives_rules.xml)

Ограничивают видимость директив по ролям пользователя.

Права доступа (security/ir.model.access.csv)

CRUD-права для всех моделей модуля, распределённые по ролям.


Тесты

Файл: tests/test_directive_layer.py

Что покрывает
Создание директив через шаблоны и группы
Статусная машина: допустимые и недопустимые переходы
Политики: срабатывание on_write
Миксин на модели project.task