Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC
This commit is contained in:
152
directive_layer/models/directive_policy.py
Normal file
152
directive_layer/models/directive_policy.py
Normal file
@@ -0,0 +1,152 @@
|
||||
from odoo import fields, models
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class DirectivePolicy(models.Model):
|
||||
_name = "directive.policy"
|
||||
_description = "Directive Policy"
|
||||
_order = "sequence, id"
|
||||
|
||||
name = fields.Char(required=True)
|
||||
active = fields.Boolean(default=True)
|
||||
sequence = fields.Integer(default=10)
|
||||
|
||||
origin_model = fields.Char(
|
||||
string="Origin model",
|
||||
required=True,
|
||||
index=True,
|
||||
help="Техническое имя модели источника, например 'project.task'",
|
||||
)
|
||||
|
||||
event = fields.Selection(
|
||||
[
|
||||
("manual", "Manual"),
|
||||
("on_create", "On Create"),
|
||||
("on_write", "On Write"),
|
||||
("on_stage", "On Stage Change"),
|
||||
("on_cron", "On Cron"),
|
||||
("on_callback", "При вызове метода (on_callback)"),
|
||||
],
|
||||
required=True,
|
||||
index=True,
|
||||
default="manual",
|
||||
)
|
||||
|
||||
mode = fields.Selection(
|
||||
[
|
||||
("create_once", "Create once"),
|
||||
("create_each_time", "Create each time"),
|
||||
],
|
||||
required=True,
|
||||
default="create_once",
|
||||
)
|
||||
|
||||
use_record_template_group = fields.Boolean(
|
||||
default=True,
|
||||
help="Если включено, группа берётся из поля записи-источника directive_template_group_id.",
|
||||
)
|
||||
|
||||
template_group_id = fields.Many2one(
|
||||
"directive.template.group",
|
||||
string="Fixed template group",
|
||||
help="Если указано и use_record_template_group=False — будет использоваться эта группа.",
|
||||
)
|
||||
|
||||
trigger_field = fields.Char(
|
||||
help="Имя поля источника, например 'stage_id'."
|
||||
)
|
||||
trigger_value_int = fields.Integer(
|
||||
help="Для Many2one/Integer: ожидаемое значение (например ID стадии)."
|
||||
)
|
||||
trigger_value_char = fields.Char(
|
||||
help="Для Char/Selection-полей (например mrp.workorder.state='progress'): "
|
||||
"ожидаемое строковое значение. Для Many2one/Integer использовать trigger_value_int.",
|
||||
)
|
||||
domain_filter = fields.Char(
|
||||
string="Доменный фильтр источника",
|
||||
help="Odoo-домен (строка), применяется к записи-источнику перед генерацией. "
|
||||
"Пусто — без фильтра.",
|
||||
)
|
||||
|
||||
def _policy_should_fire(self, record, payload: dict | None = None) -> bool:
|
||||
self.ensure_one()
|
||||
|
||||
# (1) Доменный фильтр источника применяется ко ВСЕМ событиям (вкл. on_callback).
|
||||
if self.domain_filter:
|
||||
try:
|
||||
domain = safe_eval(self.domain_filter) or []
|
||||
except Exception as e:
|
||||
_logger.error("[directive.policy %s] bad domain_filter: %s", self.name, e)
|
||||
return False
|
||||
if domain and not record.filtered_domain(domain):
|
||||
return False
|
||||
|
||||
# (2) Для on_callback факт вызова метода - это уже триггер (домен уже отфильтровал).
|
||||
if self.event == 'on_callback':
|
||||
return True
|
||||
|
||||
if not self.trigger_field:
|
||||
return True
|
||||
|
||||
value = getattr(record, self.trigger_field, False)
|
||||
|
||||
# (3a) Many2one: сравнение по id с trigger_value_int.
|
||||
if hasattr(value, "id"):
|
||||
return bool(value and value.id == self.trigger_value_int)
|
||||
|
||||
# (3b) Char/Selection: сравнение с trigger_value_char. ПРАВИЛО fail-loud:
|
||||
# если trigger_value_char пуст, НЕ проваливаемся в целочисленную ветку
|
||||
# (сравнение 'progress' == 0 всегда False) — логируем и возвращаем False.
|
||||
if isinstance(value, str):
|
||||
if self.trigger_value_char:
|
||||
return value == self.trigger_value_char
|
||||
_logger.warning(
|
||||
"[directive.policy %s] trigger_field '%s' вернул строку '%s', но "
|
||||
"trigger_value_char не задан — политика не срабатывает.",
|
||||
self.name, self.trigger_field, value,
|
||||
)
|
||||
return False
|
||||
|
||||
# (3c) Integer/число: сравнение с trigger_value_int (как сейчас — test_01 'color').
|
||||
try:
|
||||
return value == self.trigger_value_int
|
||||
except Exception as e:
|
||||
_logger.error("[directive.policy] Error comparing values: %s", e)
|
||||
return False
|
||||
|
||||
def _resolve_template_group(self, record):
|
||||
self.ensure_one()
|
||||
if self.use_record_template_group:
|
||||
return record.directive_template_group_id
|
||||
return self.template_group_id
|
||||
|
||||
def apply_to_records(self, records, payload: dict | None = None):
|
||||
self.ensure_one()
|
||||
if not records:
|
||||
return 0
|
||||
|
||||
created_total = 0
|
||||
for rec in records:
|
||||
if rec._name != self.origin_model:
|
||||
continue
|
||||
|
||||
if not self._policy_should_fire(rec, payload=payload):
|
||||
continue
|
||||
|
||||
group = self._resolve_template_group(rec)
|
||||
if not group:
|
||||
continue
|
||||
|
||||
if self.mode == "create_once":
|
||||
existing = rec.env["directive.directive"].search_count([
|
||||
("origin_id", "=", rec.directive_origin_id.id),
|
||||
("policy_id", "=", self.id),
|
||||
])
|
||||
if existing:
|
||||
continue
|
||||
|
||||
rec._directive_create_from_group(group, policy=self)
|
||||
created_total += 1
|
||||
|
||||
return created_total
|
||||
Reference in New Issue
Block a user