Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC
This commit is contained in:
130
rsbu_connector/models/rsbu_posting_mixin.py
Normal file
130
rsbu_connector/models/rsbu_posting_mixin.py
Normal 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,
|
||||
}
|
||||
Reference in New Issue
Block a user