Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC

This commit is contained in:
CI Publish Bot
2026-11-01 22:54:30 +00:00
commit 26250aa580
2492 changed files with 664277 additions and 0 deletions

View File

@@ -0,0 +1,255 @@
from odoo import api, fields, models
from odoo.exceptions import UserError
from .directive_constants import (
STAGE_CONFIRMED,
STAGE_DRAFT,
STAGE_IN_PROGRESS,
STAGE_PAUSED,
STAGE_PLANNED,
STAGE_DONE,
STAGE_CANCELLED,
)
import logging
_logger = logging.getLogger(__name__)
class DirectiveMixin(models.AbstractModel):
_name = "directive.mixin"
_description = "Directive Mixin"
directive_origin_id = fields.Many2one(
"directive.origin",
string="Directive Origin",
readonly=True,
index=True,
ondelete="set null",
)
directive_ids = fields.One2many(
"directive.directive",
string="Директивы",
related="directive_origin_id.directive_ids",
readonly=True,
)
directive_execution_status = fields.Char(
string="Текущий статус исполнения",
compute="_compute_directive_execution_status",
store=True,
)
directive_template_group_id = fields.Many2one(
"directive.template.group",
string="Группа шаблонов директив",
)
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
records._ensure_directive_origin()
return records
def write(self, vals):
_logger.debug(
"[directive] write() called. Model: %s, IDs: %s, Keys in vals: %s",
self._name, self.ids, list(vals.keys())
)
policies = self.env['directive.policy'].sudo().search([
('origin_model', '=', self._name),
('event', '=', 'on_write'),
('active', '=', True)
])
relevant_policies = policies.filtered(
lambda p: p.trigger_field and p.trigger_field in vals
)
before_map = {}
if relevant_policies:
watched_fields = set(relevant_policies.mapped('trigger_field'))
for rec in self:
before_map[rec.id] = {f: rec[f] for f in watched_fields}
res = super().write(vals)
self._ensure_directive_origin()
if relevant_policies:
for policy in relevant_policies:
field = policy.trigger_field
records_to_process = self.env[self._name]
for rec in self:
before_val = before_map[rec.id].get(field)
after_val = rec[field]
def _norm(v):
return v.id if hasattr(v, 'id') else v
b_val = _norm(before_val)
a_val = _norm(after_val)
if b_val != a_val and policy._policy_should_fire(rec):
records_to_process |= rec
if records_to_process:
_logger.info(
"[directive] applying policy '%s' for model=%s, ids=%s",
policy.name, self._name, records_to_process.ids
)
policy.sudo().apply_to_records(records_to_process)
return res
def _ensure_directive_origin(self):
for rec in self:
if not rec.id:
continue
if rec.directive_origin_id:
continue
origin = self.env["directive.origin"].sudo().search(
[("res_model", "=", rec._name), ("res_id", "=", rec.id)],
limit=1,
)
if not origin:
origin = self.env["directive.origin"].sudo().create(
{"res_model": rec._name, "res_id": rec.id}
)
rec.sudo().write({"directive_origin_id": origin.id})
@api.depends(
"directive_ids.stage_id",
"directive_ids.stage_id.code",
"directive_ids.deadline_at",
"directive_ids.planned_end_at",
)
def _compute_directive_execution_status(self):
for rec in self:
directives = rec.directive_ids.filtered(
lambda d: d.stage_id.code not in (STAGE_DONE, STAGE_CANCELLED)
)
if not directives:
rec.directive_execution_status = ""
continue
stage_priority = {
STAGE_PAUSED: 1,
STAGE_IN_PROGRESS: 2,
STAGE_PLANNED: 3,
STAGE_CONFIRMED: 3,
STAGE_DRAFT: 4,
}
def stage_rank(d):
return stage_priority.get(d.stage_id.code, 99)
current_directive = sorted(directives, key=stage_rank)[0]
stage_name = current_directive.stage_id.name
deadlines = directives.mapped("deadline_at")
nearest_deadline = min(deadlines) if deadlines else None
planned_ends = directives.mapped("planned_end_at")
planned_ends = [d for d in planned_ends if d]
expected_end = max(planned_ends) if planned_ends else max(deadlines)
parts = [stage_name]
if nearest_deadline:
parts.append(
f"до {fields.Datetime.to_string(nearest_deadline)[:10]}"
)
if expected_end:
parts.append(
f"ожидается завершение к {fields.Datetime.to_string(expected_end)[:10]}"
)
rec.directive_execution_status = ", ".join(parts)
def _directive_run_callback_policies(self, callback_name):
"""Прогоняет активные политики on_callback для данного метода-источника.
Вызывается ЯВНО из override штатного метода в модели-источнике
(directive_common), после super(). Никакого monkey-patch.
"""
policies = self.env['directive.policy'].sudo().search([
('origin_model', '=', self._name),
('event', '=', 'on_callback'),
('active', '=', True),
('trigger_field', '=', callback_name),
])
for policy in policies:
policy.sudo().apply_to_records(self)
def _directive_create_from_group(self, template_group, generator_key=None, policy=None):
self.ensure_one()
self._ensure_directive_origin()
if not template_group:
raise UserError("Template group is required.")
draft_stage = self.env["directive.stage"].search([("code", "=", "draft")], limit=1)
if not draft_stage:
raise UserError("Stage with code 'draft' not found.")
lines = template_group.template_line_ids.sorted(key=lambda l: (l.sequence, l.id))
if not lines:
raise UserError("Template group has no lines.")
directives_vals = []
for line in lines:
tmpl = line.template_id
directives_vals.append({
"generator_key": generator_key or False,
"name": tmpl.name,
"deadline_at": fields.Datetime.now(),
"planned_hours": tmpl.planned_hours,
"load_percent": tmpl.load_percent,
"priority": tmpl.priority,
"work_type_id": tmpl.work_type_id.id,
"executor_role_id": tmpl.executor_role_id.id or False,
"requester_role_id": tmpl.requester_role_id.id or False,
"stage_id": draft_stage.id,
"executor_stage_id": False,
"requester_stage_id": False,
"origin_id": self.directive_origin_id.id,
"template_id": tmpl.id,
"policy_id": policy.id if policy else False,
})
return self.env["directive.directive"].sudo().create(directives_vals)
def action_choose_directive_group(self):
self.ensure_one()
self._ensure_directive_origin()
return {
"type": "ir.actions.act_window",
"name": "Выбор группы директив",
"res_model": "directive.choose.group.wizard",
"view_mode": "form",
"target": "new",
"context": {
"active_model": self._name,
"active_id": self.id,
},
}
def action_open_directives(self):
self.ensure_one()
self._ensure_directive_origin()
return {
"type": "ir.actions.act_window",
"name": "Директивы",
"res_model": "directive.directive",
"view_mode": "kanban,list,form",
"domain": [("origin_id", "=", self.directive_origin_id.id)],
"context": {
"default_origin_id": self.directive_origin_id.id,
},
}