Files
public/rsbu_fns_reporting/models/rsbu_rsv_declaration.py

246 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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')