Files
public/directive_common/TECHNICAL.md

218 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 слоя
директив). Это демо-данные, не базовые справочники.