146 lines
7.7 KiB
Python
146 lines
7.7 KiB
Python
"""Декларация по НДС (КНД 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
|