246 lines
13 KiB
Python
246 lines
13 KiB
Python
# -*- 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')
|