Files
public/rsbu_vat/models/rsbu_vat_book_line.py

157 lines
7.8 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
# Код счёта начисленного НДС по умолчанию (кодировка 94н). Фактический счёт
# определяется по роли `vat_output` шаблонов маппинга (может быть 68.02 в
# кодировке 1С), а эта константа — фолбэк, если роль нигде не задана.
VAT_OUTPUT_CODE = '68.НДС'
# Код счёта единого налогового счёта (ЕНС). Перенос сальдо НДС на ЕНС
# (Дт 68.02 − Кт 68.90) — регламентная операция уплаты налога, а не
# начисление/вычет НДС, поэтому такие проводки в книги НДС не попадают.
ENS_ACCOUNT_CODE = '68.90'
class RsbuVatBookLine(models.Model):
"""Запись книги продаж / покупок (собирается из проводок НДС журнала)."""
_name = 'rsbu.vat.book.line'
_description = 'РСБУ — Запись книги НДС'
_order = 'date, id'
_check_company_auto = True
book = fields.Selection(
selection=[('sales', 'Книга продаж'),
('purchases', 'Книга покупок')],
string='Книга', required=True, index=True)
date = fields.Date(string='Дата', required=True, index=True)
company_id = fields.Many2one(
'res.company', string='Компания', required=True, index=True,
default=lambda self: self.env.company)
move_id = fields.Many2one('rsbu.move', string='Операция',
ondelete='cascade')
move_line_id = fields.Many2one('rsbu.move.line', string='Строка журнала',
ondelete='cascade')
invoice_id = fields.Many2one('rsbu.invoice', string='Счёт-фактура')
partner_id = fields.Many2one('res.partner', string='Контрагент')
base_amount = fields.Monetary(string='База', currency_field='currency_id')
vat_amount = fields.Monetary(string='Сумма НДС',
currency_field='currency_id')
currency_id = fields.Many2one('res.currency', string='Валюта',
compute='_compute_currency_id', store=True)
period_key = fields.Char(string='Квартал', compute='_compute_period_key',
store=True, index=True)
@api.depends('company_id')
def _compute_currency_id(self):
for line in self:
line.currency_id = line.company_id.currency_id
@api.depends('date')
def _compute_period_key(self):
for line in self:
if line.date:
quarter = (line.date.month - 1) // 3 + 1
line.period_key = '%s-Q%s' % (line.date.year, quarter)
else:
line.period_key = False
@api.model
def _vat_output_accounts(self, company):
"""Счета начисленного НДС для компании (по роли vat_output).
Определяем счёт(а) НДС по роли `vat_output` в шаблонах маппинга —
это работает независимо от кодировки (68.НДС в академическом плане
либо 68.02 в кодировке 1С). Фолбэк — счёт с кодом VAT_OUTPUT_CODE.
"""
Role = self.env['rsbu.operation.template.role'].sudo()
roles = Role.search([
('role', '=', 'vat_output'),
('company_id', 'in', (False, company.id)),
])
accounts = roles.mapped('account_id')
if not accounts:
accounts = self.env['rsbu.account'].sudo().search([
('code', '=', VAT_OUTPUT_CODE),
('company_id', '=', company.id)], limit=1)
return accounts
@api.model
def _ens_accounts(self, company):
"""Счёт(а) единого налогового счёта (ЕНС) для компании (код 68.90)."""
return self.env['rsbu.account'].sudo().search([
('code', '=', ENS_ACCOUNT_CODE),
('company_id', '=', company.id)])
@api.model
def _is_ens_transfer(self, move, vat_accounts, ens_accounts):
"""Проводка — перенос НДС на ЕНС (не операция начисления/вычета).
Признак: в проводке одновременно есть строка счёта НДС (vat_output)
и строка счёта ЕНС (68.90). Такой перенос сальдо расчётов по НДС на
единый налоговый счёт не является ни начислением (книга продаж), ни
вычетом (книга покупок) — в книги НДС не попадает.
"""
if not ens_accounts:
return False
accounts = move.line_ids.mapped('account_id')
return bool(accounts & vat_accounts) and bool(accounts & ens_accounts)
@api.model
def _is_vat_account(self, account, vat_accounts=None):
if vat_accounts is not None:
return account in vat_accounts
# Обратная совместимость (одиночный вызов): сверка по коду.
return account.code == VAT_OUTPUT_CODE
@api.model
def recompute_period(self, date_from, date_to, company):
"""Пересобрать книги продаж/покупок за период из проводок журнала.
Книга продаж — начисление НДС (Кт счёта vat_output);
книга покупок — вычет НДС (Дт счёта vat_output).
Счёт НДС определяется по роли (68.НДС или 68.02). Идемпотентно.
TODO(perf): при больших объёмах вынести в cron. Сейчас не делаем.
"""
self.search([
('company_id', '=', company.id),
('date', '>=', date_from),
('date', '<=', date_to),
]).unlink()
vat_accounts = self._vat_output_accounts(company)
ens_accounts = self._ens_accounts(company)
lines = self.env['rsbu.move.line'].search([
('company_id', '=', company.id),
('state', '=', 'posted'),
('date', '>=', date_from),
('date', '<=', date_to),
])
vals_list = []
for line in lines:
if not self._is_vat_account(line.account_id, vat_accounts):
continue
# Перенос сальдо НДС на ЕНС (Дт 68.02 − Кт 68.90) — регламентная
# уплата налога, не начисление/вычет НДС. В книги не включаем.
if self._is_ens_transfer(line.move_id, vat_accounts, ens_accounts):
continue
if line.credit: # начисление -> книга продаж
vals_list.append(self._line_vals(line, 'sales',
line.credit))
elif line.debit: # вычет -> книга покупок
vals_list.append(self._line_vals(line, 'purchases',
line.debit))
if vals_list:
return self.create(vals_list)
return self.browse()
@api.model
def _line_vals(self, line, book, vat_amount):
return {
'book': book,
'date': line.date,
'company_id': line.company_id.id,
'move_id': line.move_id.id,
'move_line_id': line.id,
'partner_id': line.partner_id.id or False,
'vat_amount': vat_amount,
}