Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC
This commit is contained in:
217
directive_common/TECHNICAL.md
Normal file
217
directive_common/TECHNICAL.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# 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 слоя
|
||||
директив). Это демо-данные, не базовые справочники.
|
||||
Reference in New Issue
Block a user