Files
public/rsbu_fns_reporting/models/rsbu_nds_declaration.py

146 lines
7.7 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.
"""Декларация по НДС (КНД 1151001, УСН + спецставка НДS 5%/7%).
Комплект из трёх XML: корневая декларация (раздел «НДС» — СумУплНП/СумУпл164)
и разделы 8 (книга покупок) и 9 (книга продаж). Расчёт — из готовых
регистров НДС: книги (rsbu.vat.book.line) и итоги (rsbu.vat.summary).
Начисленный НДС раскладывается на реализацию (РеалТов5) и авансы
(ОплПредПост): авансовый НДС определяется по наличию в проводке-источнике
начисления строки счёта авансового НДС (76.АВ). Вычет — книга покупок
(вычет авансового НДС при отгрузке).
Постоянная модель (не транзиент): сохраняется, переоткрывается, хранит
рассчитанные показатели. XML — в rsbu_nds_xml. Свежесть: выгрузка XML
всегда выполняет пересчёт (см. rsbu_nds_xml.action_export_xml).
"""
from odoo import api, fields, models
# Расчётная доля НДС в авансе по ставочному режиму (для базы авансов).
_ADVANCE_VAT_FRACTION = {'vat_5': 5.0 / 105.0, 'vat_7': 7.0 / 107.0}
# Код счёта авансового НДС — признак авансового начисления в проводке.
VAT_ADVANCE_CODE = '76.АВ'
class RsbuNdsDeclaration(models.Model):
_name = 'rsbu.nds.declaration'
_description = 'РСБУ — Декларация НДС'
_order = 'date_to desc, id desc'
_check_company_auto = True
name = fields.Char(string='Наименование', compute='_compute_name',
store=True)
company_id = fields.Many2one(
'res.company', string='Компания', required=True, index=True,
default=lambda self: self.env.company)
date_from = fields.Date(string='Период с', required=True)
date_to = fields.Date(string='Период по', required=True)
currency_id = fields.Many2one(
'res.currency', related='company_id.currency_id')
state = fields.Selection(
selection=[('draft', 'Черновик'), ('computed', 'Рассчитана')],
default='draft', string='Состояние', copy=False)
# --- Начисление (раздел НДС / СумУпл164) ---
vat_base_realization = fields.Monetary(
string='База реализации (РеалТов5)', currency_field='currency_id')
vat_tax_realization = fields.Monetary(
string='НДС с реализации', currency_field='currency_id')
vat_base_advance = fields.Monetary(
string='База авансов (ОплПредПост)', currency_field='currency_id')
vat_tax_advance = fields.Monetary(
string='НДС с авансов', currency_field='currency_id')
vat_charged_total = fields.Monetary(
string='НДС начислен всего (НалВосстОбщ)',
currency_field='currency_id')
# --- Вычет и итог ---
vat_deduction = fields.Monetary(
string='Вычет (СумНалВыч)', currency_field='currency_id')
vat_payable = fields.Monetary(
string='НДС к уплате (СумПУ_173.1)', currency_field='currency_id')
oktmo = fields.Char(string='ОКТМО')
kbk = fields.Char(string='КБК')
@api.depends('date_from', 'date_to')
def _compute_name(self):
for rec in self:
rec.name = 'Декларация НДС %s — %s' % (
rec.date_from or '?', rec.date_to or '?')
# ------------------------------------------------------------------
def _vat_advance_account(self):
"""Счёт авансового НДС (76.АВ) — признак авансового начисления."""
self.ensure_one()
return self.env['rsbu.account'].sudo().search([
('code', '=', VAT_ADVANCE_CODE),
('company_id', '=', self.company_id.id)], limit=1)
def _split_sales_vat(self):
"""Разложить начисленный НДС книги продаж на реализацию и авансы.
Авансовое начисление — проводка-источник строки книги содержит
строку счёта 76.АВ (Дт). Возвращает (нДС_реализации, НДС_авансов).
"""
self.ensure_one()
BookLine = self.env['rsbu.vat.book.line'].sudo()
adv_acc = self._vat_advance_account()
sales = BookLine.search([
('company_id', '=', self.company_id.id), ('book', '=', 'sales'),
('date', '>=', self.date_from), ('date', '<=', self.date_to),
])
real_vat = 0.0
adv_vat = 0.0
for line in sales:
move = line.move_id
is_advance = adv_acc and any(
l.account_id == adv_acc and l.debit > 0
for l in move.line_ids)
if is_advance:
adv_vat += line.vat_amount
else:
real_vat += line.vat_amount
return round(real_vat, 2), round(adv_vat, 2)
def action_compute(self):
"""Пересобрать книги/итоги НДС и рассчитать показатели декларации.
Идемпотентно. Начислено — из книги продаж (разделено на реализацию
и авансы), вычет — из книги покупок. Реквизиты — из настроек.
"""
BookLine = self.env['rsbu.vat.book.line'].sudo()
Summary = self.env['rsbu.vat.summary'].sudo()
for rec in self:
company = rec.company_id
BookLine.recompute_period(rec.date_from, rec.date_to, company)
Summary.recompute_period(rec.date_from, rec.date_to, company)
real_vat, adv_vat = rec._split_sales_vat()
# База по расчётной ставке: реализация = НДС/долю − НДС... но для
# спецставки 5% база реализации = НДС × (1/доля − 1) = НДС × 20.
fraction = _ADVANCE_VAT_FRACTION.get(company.rsbu_vat_mode, 0.0)
inv = (1.0 / fraction - 1.0) if fraction else 0.0
real_base = round(real_vat * inv, 2)
# База авансов (ОплПредПост/НалБаза) — сумма аванса С НДС по
# расчётной ставке (в 1С: НалБаза = НДС / доля = НДС × 105/5),
# в отличие от реализации, где база — без НДС.
adv_base = round(adv_vat / fraction, 2) if fraction else 0.0
deduction = round(sum(BookLine.search([
('company_id', '=', company.id), ('book', '=', 'purchases'),
('date', '>=', rec.date_from), ('date', '<=', rec.date_to),
]).mapped('vat_amount')), 2)
charged = round(real_vat + adv_vat, 2)
rec.vat_base_realization = real_base
rec.vat_tax_realization = real_vat
rec.vat_base_advance = adv_base
rec.vat_tax_advance = adv_vat
rec.vat_charged_total = charged
rec.vat_deduction = deduction
rec.vat_payable = round(charged - deduction, 2)
if not rec.oktmo:
rec.oktmo = company.rsbu_oktmo or ''
if not rec.kbk:
rec.kbk = company.rsbu_kbk_nds or ''
rec.state = 'computed'
return True