# Directive Common **name**: directive_common **version**: 19.0.1.0.0 **author**: Mk.Lab **depends:** `directive_layer`, `base_user_role`, `sale`, `mail`, `account`, `account_payment`, `mrp`, `project`, `stock` ## Назначение Модуль-компаньон подключает `directive_layer` к штатным моделям Odoo (`project.task`, `sale.order`, `account.move`, `account.payment`, `stock.picking`, `mrp.workorder` + `mrp.production`) и несёт крон-симулятор программных/машинных агентов. Служит референсом для разработчиков при подключении директив к новым моделям. --- ## Паттерн интеграции Каждая интегрируемая модель подключается к `directive.mixin` через `_inherit`. Автогенерация директив настраивается **декларативно** через `directive.policy` (UI «Директивы → Политики»), а не через код модели-источника: метод-заглушка `_directive_generation_spec` удалён, доменная фильтрация перенесена в поле `directive.policy.domain_filter`. ```python class SomeModel(models.Model): _name = 'some.model' _inherit = ['some.model', 'directive.mixin'] # handler завершения директивы (путь B, вызывается из directive.directive.action_done) def action_executor_handler(self, payload: dict) -> bool: self.message_post(body=f"Результат: {payload.get('directive_name')}") return True ``` ### Два пути `on_callback` **Путь A — политика `on_callback` на штатном методе источника.** Модель-источник переопределяет штатный метод Odoo через `super()` и **явно** вызывает публичный хелпер `self._directive_run_callback_policies("<имя_метода>")`. Это штатный override вместо прежнего monkey-patch — никакого `setattr`, `_register_hook` или оборачивания методов. Единственный штатный метод с этим путём — `stock.picking.button_validate` (см. ниже). Для демо-задачи этот же хелпер вызывается из собственного метода `project.task.action_demo_directive_callback`. > **Намеренный отказ от `directive_skip_generation`.** Старый monkey-patch > wrapper уважал контекст `directive_skip_generation` и гард `if result is not > False`. Контекст использовался только внутри самого удалённого wrapper'а и ни > в одном бизнес-потоке — поэтому он не переносится. Гард «не False / не возврат > визарда» сохранён только там, где он нужен — в `button_validate`. **Путь B — `handler_key` при `action_done`.** `directive.directive.action_done` читает `handler_key` из шаблона и вызывает одноимённый метод на записи-источнике с payload. Это штатный механизм `directive_layer`, не monkey-patch. Через него работают демо-handler'ы источников: | handler_key | Модель-источник | |---|---| | `directive_on_done_post_message` | `project.task` | | `action_wms_response` | `sale.order` | | `action_audit_response` | `account.move` | | `action_bank_gateway_response` | `account.payment` | | `action_agv_callback` | `stock.picking` | | `action_iot_signal` | `mrp.workorder` | --- ## Интегрированные модели ### `project.task` + `directive.mixin` **Файл:** `models/project_task.py` | Параметр | Значение | |---|---| | Событие (политика) | `on_write` по `stage_id` | | Тип исполнителя | `human` | **Handler завершения (путь B) `directive_on_done_post_message(payload)`** — публикует в чат задачи сообщение: «Директива выполнена: {directive_name}». **Демо-метод `action_demo_directive_callback()` (путь A)** — публикует «Demo callback executed» и затем **явно** вызывает `self._directive_run_callback_policies("action_demo_directive_callback")`. Используется тестом `on_callback` в `directive_layer`. Это не no-op переименование: именно эта строка-вызов заменяет прежний monkey-patch. --- ### `account.move` + `directive.mixin` **Файл:** `models/account_move.py` | Параметр | Значение | |---|---| | Событие (политика) | `on_write` по `state` | | Переход | `draft` → `posted` | | Domain | `directive.policy.domain_filter`, напр. `[("move_type", "=", "in_invoice")]` (только входящие счета) | | Тип исполнителя | `software` (аудит-бот) | **Handler завершения (путь B) `action_audit_response(payload)`** — публикует в чат счёта результат проверки с оценкой риска. --- ### `sale.order` + `directive.mixin` **Файл:** `models/sale_order.py` | Параметр | Значение | |---|---| | Событие (политика) | `on_write` по `state` | | Переход | `draft` → `sale` | | Тип исполнителя | `software` (WMS-агент) | **Handler завершения (путь B) `action_wms_response(payload)`** — публикует в чат заказа информацию о резервировании товара с указанием ячейки. --- ### `account.payment` + `directive.mixin` **Файл:** `models/account_payment.py` | Параметр | Значение | |---|---| | Событие (политика) | `on_write` по `state` | | Переход | `draft` → `in_process` | | Domain | `directive.policy.domain_filter`, напр. `[("payment_type", "=", "outbound")]` (только исходящие) | | Тип исполнителя | `software` (банковский шлюз) | **Handler завершения (путь B) `action_bank_gateway_response(payload)`** — публикует в чат платежа банковский референс в формате `REF-{id}-X99`. --- ### `stock.picking` + `directive.mixin` **Файл:** `models/stock_picking.py` | Параметр | Значение | |---|---| | Событие (политика) | `on_callback` на методе `button_validate` | | Тип исполнителя | `software` (AGV-робот) | **Путь A.** `button_validate` переопределён: `res = super().button_validate()`, затем — только если `res is not False` и результат не является возвратом визарда (`ir.actions.act_window` для backorder/immediate transfer) — вызывается `self._directive_run_callback_policies("button_validate")`. Гард сохраняет семантику прежнего wrapper'а: политика не срабатывает на промежуточном возврате визарда, когда перемещение ещё не проведено. **Handler завершения (путь B) `action_agv_callback(payload)`** — публикует в чат перемещения информацию о доставке в зону производства. --- ### `mrp.workorder` + `directive.mixin` **Файл:** `models/mrp_workorder.py` | Параметр | Значение | |---|---| | Событие (политика) | `on_write` по `state` | | Переход | `ready` → `progress` (через `button_start()`; в Odoo 19 состояния `pending` нет, дефолт — `ready`) | | Domain | `directive.policy.domain_filter`, напр. `[("name", "ilike", "резка")]` | | Тип исполнителя | `machine` (IoT/CNC) | **Handler завершения (путь B) `action_iot_signal(payload)`** — публикует в чат рабочей операции телеметрию оборудования (температура шпинделя, вибрация) и обновляет заголовок производственного заказа. --- ### `mrp.production` (расширение без mixin) **Файл:** `models/mrp_workorder.py` Добавляет метод `action_choose_directive_group()` в модель производственного заказа. Метод берёт первый рабочий центр из `workorder_ids` и открывает визард `directive.choose.group.wizard` с `active_model='mrp.workorder'`. При отсутствии рабочих центров выводит предупреждение. --- ## Имитация программных агентов (cron) **Метод:** `directive.directive._cron_simulate_system_agents()` (`models/directive_directive.py`) **Запись cron:** `data/cron.xml` → `ir_cron_simulate_system_agents` Запись `ir.cron` перенесена сюда из `directive_layer` (прежний битый `ir_cron_generate_simulation_data`, указывавший на несуществующий метод, удалён). Крон указывает на `directive_layer.model_directive_directive` (модель живёт в `directive_layer`), по умолчанию `active=False` (симулятор, не включать в проде), интервал — 1 минута. Регистрируется в манифесте через ключ `data`. Метод автоматически продвигает директивы программных и машинных исполнителей по стадиям: | Текущая стадия | Действие | |---|---| | `planned` | `_set_stage('in_progress')` | | `in_progress` | `action_done()` | Фильтр: `executor_type in ['software', 'machine']`, `stage_code in ['draft', 'planned', 'in_progress']`. --- ## Представления **Файл:** `views/project_task_views.xml` Добавляет единообразный набор элементов в формы шести объектов: `project.task`, `sale.order`, `account.move`, `account.payment`, `stock.picking`, `mrp.production`. | Элемент | Расположение | Описание | |---|---|---| | Кнопка «Выбрать тип» | `header` | Открывает визард выбора группы шаблонов | | Кнопка «Директивы» | `oe_button_box` | Открывает список директив объекта | | Поле `directive_execution_status` | `sheet` | Текущий статус исполнения (readonly) | Для задачи (`project.task`) дополнительно добавляется кнопка «Demo: Callback» для ручного тестирования механизма callback. --- ## Демо-пользователи **Файл:** `data/users.xml` (ключ манифеста **`demo`** — грузятся только в demo-БД) | XML ID | Логин | Имя | Бизнес-роль | |---|---|---|---| | `user_directive_executor` | `directive_executor` | Directive Executor | `directive_layer.role_directive_executor` | | `user_directive_requester` | `directive_requester` | Directive Requester | `directive_layer.role_directive_requester` | | `user_directive_dispatcher` | `directive_dispatcher` | Directive Dispatcher | `directive_layer.role_directive_dispatcher` | Каждому пользователю назначается `base.group_user` плюс бизнес-роль через `role_line_ids`. Назначение роли триггерит `set_groups_from_roles` (`base_user_role`), который через `implied_ids` помещает пользователя в соответствующую техническую группу `_res_groups` (ACL и record rules слоя директив). Это демо-данные, не базовые справочники.