Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC

This commit is contained in:
CI Publish Bot
2026-11-01 22:54:30 +00:00
commit 26250aa580
2492 changed files with 664277 additions and 0 deletions

View File

@@ -0,0 +1,181 @@
"""Декларация по УСН (объект «Доходы», КНД 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