Files
public/rsbu_connector/models/rsbu_posting_mixin.py

131 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
}