154 lines
7.6 KiB
Python
154 lines
7.6 KiB
Python
# -*- 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)
|