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,153 @@
# -*- coding: utf-8 -*-
"""Регистр помесячных итогов страховых взносов по сотрудникам (для РСВ).
Аналог rsbu.kudir.summary/rsbu.vat.summary: агрегирует проведённые
расчётные листки (hr.payslip) по (сотрудник × месяц), вычисляя базу и
взносы ОПС/ОМС/ВНиМ через hr.payslip.rsbu_insurance_breakdown (с учётом
регрессии по годовой предельной базе и льготы МСП сверх 1.5 МРОТ).
Служит источником данных для расчёта по страховым взносам (РСВ,
NO_RASCHSV 5.08): раздел 1 (сводные обязательства и подраздел единого
тарифа) и раздел 3 (персонифицированные сведения по месяцам).
"""
from odoo import api, fields, models
class RsbuRsvSummary(models.Model):
_name = 'rsbu.rsv.summary'
_description = 'РСБУ — Итоги страховых взносов по сотрудникам (РСВ)'
_order = 'year desc, month, employee_id'
_check_company_auto = True
company_id = fields.Many2one(
'res.company', string='Компания', required=True, index=True,
default=lambda self: self.env.company)
employee_id = fields.Many2one(
'hr.employee', string='Сотрудник', required=True, index=True)
year = fields.Integer(string='Год', required=True, index=True)
month = fields.Integer(string='Месяц', required=True, index=True)
currency_id = fields.Many2one(
'res.currency', string='Валюта',
compute='_compute_currency_id', store=True)
# Код категории застрахованного (НР/МС) — из сотрудника на момент расчёта.
insured_category = fields.Char(string='Код категории')
payments = fields.Monetary(string='Выплаты (всего)',
currency_field='currency_id')
non_taxable = fields.Monetary(string='Необлагаемые',
currency_field='currency_id')
base = fields.Monetary(string='База', currency_field='currency_id')
base_within_limit = fields.Monetary(
string='База в пределах лимита ОПС', currency_field='currency_id')
base_over_limit = fields.Monetary(
string='База сверх лимита ОПС', currency_field='currency_id')
ops = fields.Monetary(string='Взносы ОПС', currency_field='currency_id')
ops_within = fields.Monetary(string='ОПС в пределах',
currency_field='currency_id')
ops_over = fields.Monetary(string='ОПС сверх',
currency_field='currency_id')
oms = fields.Monetary(string='Взносы ОМС', currency_field='currency_id')
vnim = fields.Monetary(string='Взносы ВНиМ', currency_field='currency_id')
_sql_uniq = models.Constraint(
'unique(company_id, employee_id, year, month)',
'Итог РСВ уникален по сотруднику и месяцу.')
@api.depends('company_id')
def _compute_currency_id(self):
for rec in self:
rec.currency_id = rec.company_id.currency_id
# ------------------------------------------------------------------
@api.model
def recompute_period(self, date_from, date_to, company):
"""Пересобрать помесячные итоги взносов за период из листков.
Идемпотентно: удаляет итоги месяцев периода и заново агрегирует
проведённые расчётные листки (state='done') по сотруднику и месяцу.
"""
Slip = self.env['hr.payslip'].sudo()
# Удаляем прежние записи месяцев, попадающих в период.
self.search([
('company_id', '=', company.id),
('year', '>=', date_from.year),
('year', '<=', date_to.year),
]).filtered(lambda r: date_from <= fields.Date.to_date(
'%04d-%02d-01' % (r.year, r.month)) <= date_to).unlink()
slips = Slip.search([
('company_id', '=', company.id),
('state', '=', 'done'),
('date_from', '>=', date_from),
('date_from', '<=', date_to),
])
acc = {}
for slip in slips:
key = (slip.employee_id.id, slip.date_from.year,
slip.date_from.month)
bd = slip.rsbu_insurance_breakdown()
data = acc.setdefault(key, {
'payments': 0.0, 'non_taxable': 0.0, 'base': 0.0,
'base_within_limit': 0.0, 'base_over_limit': 0.0,
'ops': 0.0, 'ops_within': 0.0, 'ops_over': 0.0,
'oms': 0.0, 'vnim': 0.0,
'category': slip.employee_id.rsbu_insured_category or 'НР',
})
gross = slip._rsbu_insurance_gross()
data['payments'] += gross
data['non_taxable'] += max(0.0, gross - bd['base'])
data['base'] += bd['base']
data['base_within_limit'] += bd['base_within_limit']
data['base_over_limit'] += bd['base_over_limit']
data['ops'] += bd['ops']
data['ops_within'] += bd['ops_within']
data['ops_over'] += bd['ops_over']
data['oms'] += bd['oms']
data['vnim'] += bd['vnim']
vals_list = []
for (emp_id, year, month), d in acc.items():
vals_list.append({
'company_id': company.id,
'employee_id': emp_id,
'year': year,
'month': month,
'insured_category': d['category'],
'payments': round(d['payments'], 2),
'non_taxable': round(d['non_taxable'], 2),
'base': round(d['base'], 2),
'base_within_limit': round(d['base_within_limit'], 2),
'base_over_limit': round(d['base_over_limit'], 2),
'ops': round(d['ops'], 2),
'ops_within': round(d['ops_within'], 2),
'ops_over': round(d['ops_over'], 2),
'oms': round(d['oms'], 2),
'vnim': round(d['vnim'], 2),
})
return self.create(vals_list) if vals_list else self.browse()
# ------------------------------------------------------------------
# Выборки для РСВ
# ------------------------------------------------------------------
@staticmethod
def _quarter_months(quarter):
"""Три календарных месяца квартала (1..4) → (m1, m2, m3)."""
start = (quarter - 1) * 3 + 1
return (start, start + 1, start + 2)
@api.model
def records_for(self, company, year, month_from, month_to):
"""Записи итогов за интервал месяцев года (включительно)."""
return self.search([
('company_id', '=', company.id),
('year', '=', year),
('month', '>=', month_from),
('month', '<=', month_to),
])
@api.model
def ytd_records(self, company, year, up_to_quarter):
"""Записи нарастающим итогом с начала года по конец квартала."""
_, _, last_month = self._quarter_months(up_to_quarter)
return self.records_for(company, year, 1, last_month)