Files
public/ruodoo_rsbu_act/models/ruodoo_rsbu_act.py

246 lines
12 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.
# -*- coding: utf-8 -*-
"""Акт сверки взаиморасчётов по РСБУ-слою.
Собирает сальдо и обороты по контрагенту за период из журнала РСБУ
(rsbu.move.line) по счетам расчётов (активно-пассивные счета с субконто
«Контрагент» — 60/62/76). Строки акта — хозяйственные операции по этому
контрагенту с суммами по дебету/кредиту (с позиции нашей организации).
Выгрузка: печатная форма DOCX (docx_report) и электронный XML обмена
(приказ ФНС 405, КНД 1110332) — в отдельных файлах-примесях.
"""
from odoo import _, api, fields, models
from odoo.exceptions import UserError
# Счета бухучёта, участвующие в акте сверки с контрагентом (по коду):
# 60 — расчёты с поставщиками и подрядчиками;
# 62 — расчёты с покупателями и заказчиками;
# 76 — расчёты с разными дебиторами и кредиторами;
# 66/67 — кредиты и займы (реже); 58 — предоставленные займы.
# Берутся сам счёт и его расчётные субсчета, КРОМЕ технических субсчетов
# авансового НДС/спецсчетов с суффиксом «.АВ» (напр. 76.АВ «НДС по авансам»,
# 62.АВ) — они к задолженности контрагента не относятся.
RECONCILE_ACCOUNT_PREFIXES = ('60', '62', '76', '66', '67', '58')
RECONCILE_EXCLUDE_SUFFIX = '.АВ'
class RuodooRsbuAct(models.Model):
_name = 'ruodoo.rsbu.act'
_description = 'РСБУ — Акт сверки взаиморасчётов'
_order = 'date_to desc, id desc'
_check_company_auto = True
name = fields.Char(string='Номер акта', default='/', copy=False,
index=True)
company_id = fields.Many2one(
'res.company', string='Организация', required=True, index=True,
default=lambda self: self.env.company)
partner_id = fields.Many2one(
'res.partner', string='Контрагент', required=True, index=True)
date_from = fields.Date(
string='Период с', required=True,
default=lambda self: fields.Date.today().replace(month=1, day=1))
date_to = fields.Date(
string='Период по', required=True,
default=fields.Date.context_today)
account_ids = fields.Many2many(
'rsbu.account', string='Счета расчётов',
domain="[('subconto_partner', '=', True), ('is_group', '=', False)]",
help='Счета расчётов для сверки (активно-пассивные с субконто '
'«Контрагент»: 60/62/76). Пусто — берутся все такие счета.')
currency_id = fields.Many2one(
'res.currency', related='company_id.currency_id')
state = fields.Selection(
selection=[('draft', 'Черновик'), ('computed', 'Рассчитан')],
default='draft', string='Состояние', copy=False)
line_ids = fields.One2many(
'ruodoo.rsbu.act.line', 'act_id', string='Операции')
# Сальдо и обороты (с позиции нашей организации по счетам расчётов).
opening_debit = fields.Monetary(
string='Сальдо нач. (дебет)', currency_field='currency_id',
compute='_compute_totals', store=True)
opening_credit = fields.Monetary(
string='Сальдо нач. (кредит)', currency_field='currency_id',
compute='_compute_totals', store=True)
turnover_debit = fields.Monetary(
string='Оборот по дебету', currency_field='currency_id',
compute='_compute_totals', store=True)
turnover_credit = fields.Monetary(
string='Оборот по кредиту', currency_field='currency_id',
compute='_compute_totals', store=True)
closing_debit = fields.Monetary(
string='Сальдо кон. (дебет)', currency_field='currency_id',
compute='_compute_totals', store=True)
closing_credit = fields.Monetary(
string='Сальдо кон. (кредит)', currency_field='currency_id',
compute='_compute_totals', store=True)
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('name', '/') == '/':
seq = self.env['ir.sequence'].with_company(
vals.get('company_id') or self.env.company.id)
vals['name'] = (seq.next_by_code('ruodoo.rsbu.act')
or 'АКТ-%s' % fields.Date.today())
return super().create(vals_list)
@api.depends('line_ids', 'line_ids.debit', 'line_ids.credit',
'opening_debit', 'opening_credit')
def _compute_totals(self):
for act in self:
t_deb = sum(act.line_ids.mapped('debit'))
t_cre = sum(act.line_ids.mapped('credit'))
act.turnover_debit = t_deb
act.turnover_credit = t_cre
# Конечное сальдо = начальное + обороты (развёрнуто по знаку).
raw = ((act.opening_debit - act.opening_credit)
+ (t_deb - t_cre))
act.closing_debit = raw if raw >= 0 else 0.0
act.closing_credit = -raw if raw < 0 else 0.0
# ------------------------------------------------------------------
def _settlement_accounts(self):
"""Счета расчётов для сверки.
Если счета заданы вручную (account_ids) — используются они. Иначе
отбираются счета расчётов с контрагентом по коду (60/62/76/66/67/58
и их расчётные субсчета), исключая технические субсчета авансового
НДС/спецсчета с суффиксом «.АВ» — они не отражают задолженность
и засоряют акт.
"""
self.ensure_one()
if self.account_ids:
return self.account_ids
accounts = self.env['rsbu.account'].search([
('subconto_partner', '=', True),
('is_group', '=', False),
])
return accounts.filtered(lambda a: self._is_reconcile_account(a.code))
@staticmethod
def _is_reconcile_account(code):
"""Код счёта относится к сверяемым расчётам (не технический аванс)."""
code = (code or '').strip()
if RECONCILE_EXCLUDE_SUFFIX in code:
return False
root = code.split('.')[0]
return root in RECONCILE_ACCOUNT_PREFIXES
def _move_line_domain(self, date_from, date_to):
self.ensure_one()
domain = [
('company_id', '=', self.company_id.id),
('partner_id', '=', self.partner_id.id),
('account_id', 'in', self._settlement_accounts().ids),
('state', '=', 'posted'),
]
if date_from:
domain.append(('date', '>=', date_from))
if date_to:
domain.append(('date', '<=', date_to))
return domain
def _opening_balance(self):
"""Сальдо на начало периода (развёрнуто дебет/кредит)."""
self.ensure_one()
Line = self.env['rsbu.move.line']
lines = Line.search(self._move_line_domain(None, False) + [
('date', '<', self.date_from)])
raw = sum(lines.mapped('debit')) - sum(lines.mapped('credit'))
raw = round(raw, 2)
return (raw, 0.0) if raw >= 0 else (0.0, -raw)
# ------------------------------------------------------------------
def action_compute(self):
"""Собрать операции и сальдо по контрагенту за период."""
Line = self.env['ruodoo.rsbu.act.line']
MoveLine = self.env['rsbu.move.line']
for act in self:
act.line_ids.unlink()
od, oc = act._opening_balance()
act.opening_debit = od
act.opening_credit = oc
ml = MoveLine.search(
act._move_line_domain(act.date_from, act.date_to),
order='date, move_id, id')
vals = []
for line in ml:
move = line.move_id
vals.append({
'act_id': act.id,
'date': line.date,
'name': line.name or move.ref or move.name,
'move_id': move.id,
'doc_name': move.ref or 'Операция',
'doc_number': move.name,
'doc_date': move.date,
'contract_id': line.contract_id.id,
'debit': line.debit,
'credit': line.credit,
})
if vals:
Line.create(vals)
act.state = 'computed'
# Если по одному акту ничего не собралось — явно предупредить
# (чаще всего выбран не тот контрагент/период или нет проводок на
# счетах расчётов), чтобы пустой акт не выглядел молчаливой ошибкой.
if len(self) == 1 and not self.line_ids and not (
self.opening_debit or self.opening_credit):
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Акт сверки пуст'),
'message': _(
'За период %(df)s — %(dt)s по контрагенту «%(p)s» '
'не найдено проводок на счетах расчётов (60/62/76). '
'Проверьте выбранного контрагента, период и наличие '
'проведённых операций.',
df=self.date_from, dt=self.date_to,
p=self.partner_id.name),
'type': 'warning',
'sticky': False,
},
}
return True
def _check_requisites(self):
self.ensure_one()
errors = []
company_partner = self.company_id.partner_id
if not (company_partner.inn or '').strip():
errors.append('ИНН организации')
if not (self.partner_id.inn or '').strip():
errors.append('ИНН контрагента')
if not self.line_ids and not (self.opening_debit or
self.opening_credit):
errors.append('нет операций и сальдо за период')
if errors:
raise UserError(
'Не удалось сформировать акт сверки: %s.'
% ', '.join(errors))
class RuodooRsbuActLine(models.Model):
_name = 'ruodoo.rsbu.act.line'
_description = 'РСБУ — Операция акта сверки'
_order = 'act_id, date, id'
act_id = fields.Many2one(
'ruodoo.rsbu.act', string='Акт', required=True,
ondelete='cascade', index=True)
currency_id = fields.Many2one(
'res.currency', related='act_id.currency_id')
date = fields.Date(string='Дата операции')
name = fields.Char(string='Содержание операции')
move_id = fields.Many2one('rsbu.move', string='Проводка РСБУ')
doc_name = fields.Char(string='Документ')
doc_number = fields.Char(string='Номер документа')
doc_date = fields.Date(string='Дата документа')
contract_id = fields.Many2one(
'partner.contract.customer', string='Договор')
debit = fields.Monetary(string='Дебет', currency_field='currency_id')
credit = fields.Monetary(string='Кредит', currency_field='currency_id')