Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC
This commit is contained in:
148
rsbu_journal/models/rsbu_move.py
Normal file
148
rsbu_journal/models/rsbu_move.py
Normal file
@@ -0,0 +1,148 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import float_is_zero
|
||||
|
||||
|
||||
class RsbuMove(models.Model):
|
||||
"""Хозяйственная операция РСБУ (аналог account.move)."""
|
||||
_name = 'rsbu.move'
|
||||
_description = 'РСБУ — Хозяйственная операция'
|
||||
_order = 'date desc, name desc, id desc'
|
||||
_check_company_auto = True
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char(
|
||||
string='Номер', default='/', copy=False, readonly=True, index=True)
|
||||
date = fields.Date(
|
||||
string='Дата операции', required=True, index=True,
|
||||
default=fields.Date.context_today, tracking=True)
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Компания', required=True, index=True,
|
||||
default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', string='Валюта',
|
||||
compute='_compute_currency_id', store=True)
|
||||
state = fields.Selection(
|
||||
selection=[('draft', 'Черновик'), ('posted', 'Проведён')],
|
||||
string='Состояние', default='draft', required=True,
|
||||
copy=False, tracking=True, index=True)
|
||||
line_ids = fields.One2many(
|
||||
'rsbu.move.line', 'move_id', string='Строки проводки',
|
||||
copy=True)
|
||||
ref = fields.Char(string='Ссылка / описание')
|
||||
note = fields.Text(string='Примечание')
|
||||
|
||||
# Ссылка на документ-источник (используется Этапом 3).
|
||||
source_model = fields.Char(string='Модель источника', copy=False)
|
||||
source_res_id = fields.Integer(string='ID источника', copy=False)
|
||||
|
||||
# Контрольные обороты по проводке (для проверки баланса, не «сумма
|
||||
# операции»): при наличии внутренних проводок (напр. НДС на 90.03)
|
||||
# оборот по дебету не равен сумме документа, поэтому «сумму операции»
|
||||
# намеренно не храним и не показываем.
|
||||
total_debit = fields.Monetary(
|
||||
string='Оборот Дт', compute='_compute_totals', store=True,
|
||||
currency_field='currency_id')
|
||||
total_credit = fields.Monetary(
|
||||
string='Оборот Кт', compute='_compute_totals', store=True,
|
||||
currency_field='currency_id')
|
||||
line_count = fields.Integer(
|
||||
string='Кол-во строк', compute='_compute_line_count')
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Вычисляемые поля
|
||||
# ------------------------------------------------------------------
|
||||
@api.depends('company_id')
|
||||
def _compute_currency_id(self):
|
||||
for move in self:
|
||||
move.currency_id = move.company_id.currency_id
|
||||
|
||||
@api.depends('line_ids.debit', 'line_ids.credit')
|
||||
def _compute_totals(self):
|
||||
for move in self:
|
||||
move.total_debit = sum(move.line_ids.mapped('debit'))
|
||||
move.total_credit = sum(move.line_ids.mapped('credit'))
|
||||
|
||||
@api.depends('line_ids')
|
||||
def _compute_line_count(self):
|
||||
for move in self:
|
||||
move.line_count = len(move.line_ids)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Проверки при проведении
|
||||
# ------------------------------------------------------------------
|
||||
def _check_has_lines(self):
|
||||
for move in self:
|
||||
if not move.line_ids:
|
||||
raise UserError(_(
|
||||
'Нельзя провести операцию «%s» без строк.')
|
||||
% move.display_name)
|
||||
|
||||
def _check_balanced(self):
|
||||
for move in self:
|
||||
rounding = move.currency_id.rounding or 0.01
|
||||
total_debit = sum(move.line_ids.mapped('debit'))
|
||||
total_credit = sum(move.line_ids.mapped('credit'))
|
||||
if not float_is_zero(total_debit - total_credit,
|
||||
precision_rounding=rounding):
|
||||
raise UserError(_(
|
||||
'Операция «%(name)s» не сбалансирована: '
|
||||
'дебет %(debit)s ≠ кредит %(credit)s.',
|
||||
name=move.display_name,
|
||||
debit=total_debit, credit=total_credit))
|
||||
|
||||
def _check_required_subconto(self):
|
||||
for move in self:
|
||||
for line in move.line_ids:
|
||||
required = line.account_id.get_required_subconto()
|
||||
missing = [dim for dim in required
|
||||
if not line._has_subconto(dim)]
|
||||
if missing:
|
||||
labels = {'partner': 'Контрагент', 'product': 'Номенклатура',
|
||||
'user': 'Пользователь', 'employee': 'Сотрудник'}
|
||||
names = ', '.join(labels.get(m, m) for m in missing)
|
||||
raise UserError(_(
|
||||
'Строка со счётом «%(acc)s» требует обязательные '
|
||||
'субконто: %(subs)s.',
|
||||
acc=line.account_id.display_name, subs=names))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Жизненный цикл
|
||||
# ------------------------------------------------------------------
|
||||
def action_post(self):
|
||||
for move in self:
|
||||
if move.state == 'posted':
|
||||
continue
|
||||
move._check_has_lines()
|
||||
move._check_balanced()
|
||||
move._check_required_subconto()
|
||||
if move.name == '/' or not move.name:
|
||||
move.name = self.env['ir.sequence'].with_company(
|
||||
move.company_id).next_by_code('rsbu.move') or '/'
|
||||
move.state = 'posted'
|
||||
return True
|
||||
|
||||
def action_draft(self):
|
||||
for move in self:
|
||||
move.state = 'draft'
|
||||
return True
|
||||
|
||||
def write(self, vals):
|
||||
# Ограничение редактирования ключевых полей у проведённой операции.
|
||||
protected = {'line_ids', 'date', 'company_id'}
|
||||
if any(f in vals for f in protected):
|
||||
for move in self:
|
||||
if move.state == 'posted':
|
||||
raise UserError(_(
|
||||
'Операция «%s» проведена. Верните её в черновик '
|
||||
'перед изменением строк или даты.')
|
||||
% move.display_name)
|
||||
return super().write(vals)
|
||||
|
||||
@api.ondelete(at_uninstall=False)
|
||||
def _unlink_only_draft(self):
|
||||
for move in self:
|
||||
if move.state == 'posted':
|
||||
raise UserError(_(
|
||||
'Нельзя удалить проведённую операцию «%s». '
|
||||
'Сначала верните её в черновик.') % move.display_name)
|
||||
Reference in New Issue
Block a user