Files
public/rsbu_payroll/models/rsbu_rsv_summary.py

154 lines
7.6 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 -*-
"""Регистр помесячных итогов страховых взносов по сотрудникам (для РСВ).
Аналог 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)