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,130 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class RsbuPostingMixin(models.AbstractModel):
"""Абстрактный миксин связи документа-источника с журналом РСБУ.
Инкапсулирует механику: идемпотентную генерацию РСБУ-операции,
зеркалирование отмены, транзакционную блокировку и навигацию.
Расчёт строк Дт/Кт делегируется методу _rsbu_build_lines (Этап 4);
на Этапе 3 это заглушка, возвращающая пустой список.
"""
_name = 'rsbu.posting.mixin'
_description = 'РСБУ — Миксин проведения'
rsbu_move_id = fields.Many2one(
'rsbu.move', string='РСБУ-проводка', copy=False, readonly=True)
rsbu_move_count = fields.Integer(
string='РСБУ-проводки', compute='_compute_rsbu_move_count')
@api.depends('rsbu_move_id')
def _compute_rsbu_move_count(self):
for rec in self:
rec.rsbu_move_count = 1 if rec.rsbu_move_id else 0
# ------------------------------------------------------------------
# Точки расширения (реализуются Этапом 4)
# ------------------------------------------------------------------
def _rsbu_is_applicable(self):
"""Входит ли документ в РСБУ-периметр (нужна ли РСБУ-проводка).
На Этапе 3 маппинга ещё нет, поэтому по умолчанию False —
штатное проведение документов Odoo не затрагивается. На Этапе 4
применимость определяется матрицей связности (наличием шаблона).
"""
self.ensure_one()
return False
def _rsbu_get_move_values(self):
"""Значения шапки rsbu.move для документа-источника."""
self.ensure_one()
return {
'date': self._rsbu_move_date(),
'company_id': self.company_id.id,
'ref': self.display_name,
'source_model': self._name,
'source_res_id': self.id,
}
def _rsbu_move_date(self):
"""Дата РСБУ-операции. По умолчанию — поле date документа/сегодня."""
self.ensure_one()
return getattr(self, 'date', False) or fields.Date.context_today(self)
def _rsbu_build_lines(self):
"""Список значений строк rsbu.move.line (Дт/Кт + субконто).
ЗАГЛУШКА Этапа 3: возвращает пустой список -> трактуется как
«нет маппинга». Реализуется маппингом на Этапе 4.
"""
self.ensure_one()
return []
# ------------------------------------------------------------------
# Механика
# ------------------------------------------------------------------
def _rsbu_generate(self):
"""Идемпотентно создать и провести РСБУ-операцию по документу.
Управляемый обход: при context ``rsbu_skip=True`` генерация
пропускается (по аналогии с with_user/with_context Odoo) —
для служебных/внешних вызовов, где РСБУ-слой не нужен.
"""
self.ensure_one()
if self.env.context.get('rsbu_skip'):
return
if not self._rsbu_is_applicable() or self.rsbu_move_id:
return
lines = self._rsbu_build_lines()
if not lines:
raise UserError(_(
'Не удалось сформировать РСБУ-проводку для «%s»: '
'не определён маппинг типовой операции.')
% self.display_name)
vals = self._rsbu_get_move_values()
# Служебный маркер резолвера: строки с origin_self=True ссылаются на
# саму создаваемую проводку как на документ-основание (аванс).
# Убираем маркер из vals строк (ORM его не знает) и запоминаем позиции.
origin_self_idx = []
clean_lines = []
for i, line in enumerate(lines):
line = dict(line)
if line.pop('origin_self', False):
origin_self_idx.append(i)
clean_lines.append(line)
vals['line_ids'] = [(0, 0, line) for line in clean_lines]
# sudo: РСБУ-проводка — внутренняя механика, создаётся при проведении
# документа независимо от прав пользователя на РСБУ-модели.
move = self.env['rsbu.move'].sudo().create(vals)
# Проставить origin_move_id = сама проводка для помеченных строк.
if origin_self_idx:
move_lines = move.line_ids
for i in origin_self_idx:
if i < len(move_lines):
move_lines[i].origin_move_id = move.id
move.action_post() # проверки баланса/субконто Этапа 2
self.sudo().rsbu_move_id = move.id
def _rsbu_remove(self):
"""Зеркалирование отмены: удалить связанную РСБУ-операцию."""
for rec in self:
move = rec.rsbu_move_id.sudo()
if move:
rec.sudo().rsbu_move_id = False
if move.state == 'posted':
move.action_draft()
move.unlink()
# ------------------------------------------------------------------
# Навигация
# ------------------------------------------------------------------
def action_view_rsbu_move(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': _('РСБУ-проводка'),
'res_model': 'rsbu.move',
'view_mode': 'form',
'res_id': self.rsbu_move_id.id,
}