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