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,245 @@
# -*- coding: utf-8 -*-
"""Расчёт по страховым взносам (РСВ, КНД 1151111, формат NO_RASCHSV 5.08).
Строится поквартально нарастающим итогом с начала года из помесячных
итогов взносов (rsbu.rsv.summary). Профиль: УСН + субъект МСП, единый
пониженный тариф (п.2.5 ст.427 НК).
Разделы (первый релиз):
* Раздел 1 (ОбязПлатСВ) — сводные суммы взносов к уплате по единому
тарифу за расчётный период и помесячно (3 месяца квартала);
* подраздел 1 (РасчСВ_ОПСОМС) — расчёт по единому тарифу: число
застрахованных, база (в пределах/сверх предельной базы ОПС),
исчисленные взносы (нарастающим итогом + 3 месяца);
* Приложение 3.1 (ПравТариф13.2.427) — расчёт соответствия условиям
применения единого пониженного тарифа МСП (доходы — реквизиты);
* Раздел 3 (ПерсСвСтрахЛиц) — персонифицированные сведения по каждому
сотруднику (выплаты/база/взносы по месяцам квартала).
Расчёт и хранение — здесь; XML-выгрузка (5.08, windows-1251) — в
rsbu_rsv_xml. Реквизиты — из настроек компании (rsbu_reporting_base,
rsbu_payroll, l10n_ru).
"""
from odoo import api, fields, models
# Код отчётного периода РСВ по номеру квартала (нарастающим итогом).
RSV_PERIOD_CODE = {1: '21', 2: '31', 3: '33', 4: '34'}
class RsbuRsvDeclaration(models.Model):
_name = 'rsbu.rsv.declaration'
_description = 'РСБУ — Расчёт по страховым взносам (РСВ)'
_order = 'year desc, quarter 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)
quarter = fields.Selection(
selection=[('1', 'I квартал'), ('2', 'Полугодие'),
('3', '9 месяцев'), ('4', 'Год')],
string='Отчётный период', required=True, default='1',
help='Расчётный (отчётный) период нарастающим итогом.')
currency_id = fields.Many2one(
'res.currency', related='company_id.currency_id')
state = fields.Selection(
selection=[('draft', 'Черновик'), ('computed', 'Рассчитан')],
default='draft', string='Состояние', copy=False)
oktmo = fields.Char(string='ОКТМО')
avg_headcount = fields.Integer(
string='Среднесписочная численность',
help='Среднесписочная численность работников (атрибут СрЧисл).')
# --- Раздел 1 / подраздел 1: расчёт по единому тарифу (нараст. итогом) ---
insured_count = fields.Integer(string='Кол-во застрахованных лиц')
charged_count = fields.Integer(
string='Кол-во лиц с начисленными взносами')
payments_total = fields.Monetary(string='Выплаты (всего)',
currency_field='currency_id')
non_taxable_total = fields.Monetary(string='Необлагаемые выплаты',
currency_field='currency_id')
base_total = fields.Monetary(string='База для исчисления',
currency_field='currency_id')
base_within_total = fields.Monetary(
string='База не превышающая предел (ОПС)',
currency_field='currency_id')
base_over_total = fields.Monetary(
string='База сверх предела (ОПС)', currency_field='currency_id')
charged_total = fields.Monetary(string='Исчислено взносов (всего)',
currency_field='currency_id')
charged_within_total = fields.Monetary(
string='Исчислено с базы в пределах', currency_field='currency_id')
charged_over_total = fields.Monetary(
string='Исчислено с базы сверх', currency_field='currency_id')
oms_total = fields.Monetary(string='Взносы ОМС',
currency_field='currency_id')
vnim_total = fields.Monetary(string='Взносы ВНиМ',
currency_field='currency_id')
# Помесячная разбивка последних 3 месяцев квартала (к уплате, единый тариф).
contrib_month1 = fields.Monetary(string='Взносы 1-й мес.',
currency_field='currency_id')
contrib_month2 = fields.Monetary(string='Взносы 2-й мес.',
currency_field='currency_id')
contrib_month3 = fields.Monetary(string='Взносы 3-й мес.',
currency_field='currency_id')
contrib_period = fields.Monetary(string='Взносы за период (единый тариф)',
currency_field='currency_id')
# --- Приложение 3.1 (МСП, п.13.2 ст.427): доходы для проверки условий ---
msp_income_total = fields.Monetary(
string='Доходы всего (ст.26.2)', currency_field='currency_id',
help='Доходы для проверки соответствия условиям МСП (заполняются '
'по данным УСН/бухучёта). Приложение 3.1 РСВ.')
msp_income_main = fields.Monetary(
string='Доходы по основному виду деятельности',
currency_field='currency_id')
pers_line_ids = fields.One2many(
'rsbu.rsv.pers.line', 'declaration_id',
string='Персонифицированные сведения (раздел 3)')
@api.depends('year', 'quarter')
def _compute_name(self):
labels = dict(self._fields['quarter'].selection)
for rec in self:
rec.name = 'РСВ за %s, %s' % (
labels.get(rec.quarter, ''), rec.year or '?')
def _period_bounds(self):
"""Границы периода: (year_start, quarter_last_day, q1..q_last)."""
self.ensure_one()
q = int(self.quarter)
last_month = q * 3
import calendar
last_day = calendar.monthrange(self.year, last_month)[1]
return (
fields.Date.to_date('%04d-01-01' % self.year),
fields.Date.to_date('%04d-%02d-%02d' % (
self.year, last_month, last_day)),
q,
)
# ------------------------------------------------------------------
def action_compute(self):
"""Пересобрать итоги взносов за год и рассчитать РСВ."""
Summary = self.env['rsbu.rsv.summary'].sudo()
for rec in self:
date_from, date_to, _q = rec._period_bounds()
Summary.recompute_period(date_from, date_to, rec.company_id)
return self.compute_from_summary()
def compute_from_summary(self):
"""Рассчитать показатели РСВ из готовых помесячных итогов."""
Summary = self.env['rsbu.rsv.summary'].sudo()
for rec in self:
company = rec.company_id
q = int(rec.quarter)
m1, m2, m3 = Summary._quarter_months(q)
# Нарастающим итогом с начала года по конец квартала.
ytd = Summary.ytd_records(company, rec.year, q)
# Последние 3 месяца квартала (для помесячной разбивки).
last3 = Summary.records_for(company, rec.year, m1, m3)
rec.payments_total = round(sum(ytd.mapped('payments')), 2)
rec.non_taxable_total = round(sum(ytd.mapped('non_taxable')), 2)
rec.base_total = round(sum(ytd.mapped('base')), 2)
rec.base_within_total = round(
sum(ytd.mapped('base_within_limit')), 2)
rec.base_over_total = round(
sum(ytd.mapped('base_over_limit')), 2)
rec.charged_total = round(sum(ytd.mapped('ops')), 2)
rec.charged_within_total = round(sum(ytd.mapped('ops_within')), 2)
rec.charged_over_total = round(sum(ytd.mapped('ops_over')), 2)
rec.oms_total = round(sum(ytd.mapped('oms')), 2)
rec.vnim_total = round(sum(ytd.mapped('vnim')), 2)
# Число застрахованных / с начислениями (нарастающим итогом).
rec.insured_count = len(ytd.mapped('employee_id'))
rec.charged_count = len(ytd.filtered(
lambda r: r.base > 0).mapped('employee_id'))
# Помесячно за последние 3 месяца — взносы единого тарифа
# (ОПС+ОМС+ВНиМ) к уплате.
def month_contrib(month):
recs = last3.filtered(lambda r: r.month == month)
return round(sum(recs.mapped('ops'))
+ sum(recs.mapped('oms'))
+ sum(recs.mapped('vnim')), 2)
rec.contrib_month1 = month_contrib(m1)
rec.contrib_month2 = month_contrib(m2)
rec.contrib_month3 = month_contrib(m3)
rec.contrib_period = round(
rec.charged_total + rec.oms_total + rec.vnim_total, 2)
if not rec.oktmo:
rec.oktmo = company.rsbu_oktmo or ''
# --- Раздел 3: строки по сотрудникам за 3 месяца квартала ---
rec.pers_line_ids.unlink()
PersLine = self.env['rsbu.rsv.pers.line']
by_emp = {}
for r in last3:
by_emp.setdefault(r.employee_id, []).append(r)
pers_vals = []
for emp, recs in by_emp.items():
def m_sum(month, field):
mr = [x for x in recs if x.month == month]
return round(sum(x[field] for x in mr), 2)
pers_vals.append({
'declaration_id': rec.id,
'employee_id': emp.id,
'category': recs[0].insured_category or 'НР',
'pay_m1': m_sum(m1, 'payments'),
'pay_m2': m_sum(m2, 'payments'),
'pay_m3': m_sum(m3, 'payments'),
'base_m1': m_sum(m1, 'base'),
'base_m2': m_sum(m2, 'base'),
'base_m3': m_sum(m3, 'base'),
'ops_m1': m_sum(m1, 'ops'),
'ops_m2': m_sum(m2, 'ops'),
'ops_m3': m_sum(m3, 'ops'),
})
if pers_vals:
PersLine.create(pers_vals)
rec.state = 'computed'
return True
class RsbuRsvPersLine(models.Model):
_name = 'rsbu.rsv.pers.line'
_description = 'РСБУ — Перс. сведения РСВ (раздел 3)'
_order = 'declaration_id, employee_id'
declaration_id = fields.Many2one(
'rsbu.rsv.declaration', string='Расчёт', required=True,
ondelete='cascade', index=True)
employee_id = fields.Many2one('hr.employee', string='Сотрудник',
required=True)
currency_id = fields.Many2one(
'res.currency', related='declaration_id.currency_id')
category = fields.Char(string='Код категории')
pay_m1 = fields.Monetary(string='Выплаты 1-й мес.',
currency_field='currency_id')
pay_m2 = fields.Monetary(string='Выплаты 2-й мес.',
currency_field='currency_id')
pay_m3 = fields.Monetary(string='Выплаты 3-й мес.',
currency_field='currency_id')
base_m1 = fields.Monetary(string='База 1-й мес.',
currency_field='currency_id')
base_m2 = fields.Monetary(string='База 2-й мес.',
currency_field='currency_id')
base_m3 = fields.Monetary(string='База 3-й мес.',
currency_field='currency_id')
ops_m1 = fields.Monetary(string='ОПС 1-й мес.',
currency_field='currency_id')
ops_m2 = fields.Monetary(string='ОПС 2-й мес.',
currency_field='currency_id')
ops_m3 = fields.Monetary(string='ОПС 3-й мес.',
currency_field='currency_id')