182 lines
10 KiB
Python
182 lines
10 KiB
Python
"""Декларация по УСН (объект «Доходы», КНД 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
|