Files
public/rsbu_fns_reporting/models/rsbu_usn_declaration.py

182 lines
10 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.
"""Декларация по УСН (объект «Доходы», КНД 1152017).
Расчёт разделов 2.1.1 и 1.1 нарастающим итогом по отчётным периодам
(I квартал, полугодие, 9 месяцев, год) из итогов КУДиР (rsbu.kudir.summary).
Формулы — по Порядку заполнения (разделы III и V): исчисленный налог,
уменьшение на страховые взносы (для работодателя не более 50%), авансовые
платежи и налог к доплате/уменьшению за год.
XML-выгрузка — в rsbu_usn_xml (Этап 10-совместимый генератор). Здесь —
расчёт и хранение показателей для сверки и последующей выгрузки.
"""
from odoo import api, fields, models
class RsbuUsnDeclaration(models.Model):
_name = 'rsbu.usn.declaration'
_description = 'РСБУ — Декларация УСН (Доходы)'
_order = 'year 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)
year = fields.Integer(string='Отчётный год', required=True,
default=lambda self: fields.Date.today().year)
taxpayer_sign = fields.Selection(
selection=[('1', 'Производит выплаты физлицам'),
('2', 'ИП без выплат физлицам')],
string='Признак налогоплательщика', default='1', required=True,
help='Стр. 102. Признак «1» — уменьшение налога на взносы не более '
'50%; «2» (ИП без работников) — до 100%.')
currency_id = fields.Many2one(
'res.currency', related='company_id.currency_id')
state = fields.Selection(
selection=[('draft', 'Черновик'), ('computed', 'Рассчитана')],
default='draft', string='Состояние', copy=False)
# --- Раздел 2.1.1: доход, ставка, исчислено, вычет (нарастающим итогом) ---
# Доход (стр. 110-113).
income_q1 = fields.Monetary(string='Доход за I кв. (110)',
currency_field='currency_id')
income_h1 = fields.Monetary(string='Доход за полугодие (111)',
currency_field='currency_id')
income_9m = fields.Monetary(string='Доход за 9 мес. (112)',
currency_field='currency_id')
income_year = fields.Monetary(string='Доход за год (113)',
currency_field='currency_id')
# Ставка (стр. 120-123), %.
rate = fields.Float(string='Ставка УСН, % (120-123)', default=6.0)
# Исчислено (стр. 130-133).
tax_q1 = fields.Monetary(string='Исчислено за I кв. (130)',
currency_field='currency_id')
tax_h1 = fields.Monetary(string='Исчислено за полугодие (131)',
currency_field='currency_id')
tax_9m = fields.Monetary(string='Исчислено за 9 мес. (132)',
currency_field='currency_id')
tax_year = fields.Monetary(string='Исчислено за год (133)',
currency_field='currency_id')
# Вычет: взносы/пособия (стр. 140-143), ≤ 50% исчисленного (признак 1).
ded_q1 = fields.Monetary(string='Вычет за I кв. (140)',
currency_field='currency_id')
ded_h1 = fields.Monetary(string='Вычет за полугодие (141)',
currency_field='currency_id')
ded_9m = fields.Monetary(string='Вычет за 9 мес. (142)',
currency_field='currency_id')
ded_year = fields.Monetary(string='Вычет за год (143)',
currency_field='currency_id')
# --- Раздел 1.1: авансы к уплате/уменьшению по срокам ---
adv_q1 = fields.Monetary(string='Аванс к уплате I кв. (020)',
currency_field='currency_id')
adv_h1 = fields.Monetary(string='Аванс к уплате полугодие (040)',
currency_field='currency_id')
adv_h1_reduce = fields.Monetary(string='Аванс к уменьшению полугодие (050)',
currency_field='currency_id')
adv_9m = fields.Monetary(string='Аванс к уплате 9 мес. (070)',
currency_field='currency_id')
adv_9m_reduce = fields.Monetary(string='Аванс к уменьшению 9 мес. (080)',
currency_field='currency_id')
tax_due_year = fields.Monetary(string='Налог к доплате за год (100)',
currency_field='currency_id')
tax_reduce_year = fields.Monetary(string='Налог к уменьшению за год (110)',
currency_field='currency_id')
oktmo = fields.Char(string='ОКТМО (010)')
@api.depends('year', 'company_id')
def _compute_name(self):
for rec in self:
rec.name = 'Декларация УСН за %s' % (rec.year or '?')
# ------------------------------------------------------------------
def _reduction_limited(self, insurance, tax):
"""Вычет взносов, ограниченный признаком налогоплательщика.
Признак «1» (работодатель): не более 50% исчисленного налога.
Признак «2» (ИП без работников): до 100% (в пределах налога).
В обоих случаях не больше самого налога и не больше фактических
взносов.
"""
if tax <= 0:
return 0.0
cap = tax * 0.5 if self.taxpayer_sign == '1' else tax
return round(min(insurance, cap), 2)
def action_compute(self):
"""Пересобрать итоги КУДиР за год и рассчитать декларацию."""
Summary = self.env['rsbu.kudir.summary'].sudo()
for rec in self:
date_from = fields.Date.to_date('%s-01-01' % rec.year)
date_to = fields.Date.to_date('%s-12-31' % rec.year)
Summary.recompute_period(date_from, date_to, rec.company_id)
return self.compute_from_summary()
def compute_from_summary(self):
"""Рассчитать показатели декларации из готовых итогов КУДиР.
Отделено от action_compute, чтобы считать по существующим записям
rsbu.kudir.summary без принудительного пересбора (для тестов и
повторного расчёта).
"""
Summary = self.env['rsbu.kudir.summary'].sudo()
for rec in self:
company = rec.company_id
year = rec.year
rate = (rec.rate or 0.0) / 100.0
# Доход нарастающим итогом по отчётным периодам.
inc = {
q: Summary.ytd_totals(company, year, q)['income']
for q in (1, 2, 3, 4)
}
# Взносы/пособия (раздел IV) нарастающим итогом.
ins = {
q: Summary.ytd_totals(company, year, q)['insurance']
for q in (1, 2, 3, 4)
}
# Исчислено (стр. 130-133) = доход × ставка.
t = {q: round(inc[q] * rate, 2) for q in (1, 2, 3, 4)}
# Вычет (стр. 140-143), ограничение по признаку НП.
d = {q: rec._reduction_limited(ins[q], t[q]) for q in (1, 2, 3, 4)}
rec.income_q1, rec.income_h1, rec.income_9m, rec.income_year = (
inc[1], inc[2], inc[3], inc[4])
rec.tax_q1, rec.tax_h1, rec.tax_9m, rec.tax_year = (
t[1], t[2], t[3], t[4])
rec.ded_q1, rec.ded_h1, rec.ded_9m, rec.ded_year = (
d[1], d[2], d[3], d[4])
# --- Раздел 1.1 (авансы по срокам, формулы Порядка п.16-25) ---
# Аванс за I кв. (020) = (130 − 140), если ≥ 0.
a020 = max(0.0, round(t[1] - d[1], 2))
# Полугодие: (131 − 141) − 020.
base_h1 = round(t[2] - d[2], 2)
diff_h1 = round(base_h1 - a020, 2)
a040 = max(0.0, diff_h1) # к уплате (040)
a050 = max(0.0, -diff_h1) # к уменьшению (050)
# 9 мес.: (132 − 142) − (020 + 040 − 050).
base_9m = round(t[3] - d[3], 2)
prev_9m = round(a020 + a040 - a050, 2)
diff_9m = round(base_9m - prev_9m, 2)
a070 = max(0.0, diff_9m) # к уплате (070)
a080 = max(0.0, -diff_9m) # к уменьшению (080)
# Год: (133 − 143) − (020 + 040 − 050 + 070 − 080).
base_year = round(t[4] - d[4], 2)
prev_year = round(a020 + a040 - a050 + a070 - a080, 2)
diff_year = round(base_year - prev_year, 2)
a100 = max(0.0, diff_year) # к доплате (100)
a110 = max(0.0, -diff_year) # к уменьшению (110)
rec.adv_q1 = a020
rec.adv_h1 = a040
rec.adv_h1_reduce = a050
rec.adv_9m = a070
rec.adv_9m_reduce = a080
rec.tax_due_year = a100
rec.tax_reduce_year = a110
if not rec.oktmo:
rec.oktmo = company.rsbu_oktmo or ''
rec.state = 'computed'
return True