125 lines
6.0 KiB
Python
125 lines
6.0 KiB
Python
# -*- coding: utf-8 -*-
|
||
from odoo.tests import TransactionCase, tagged
|
||
|
||
|
||
@tagged('post_install', '-at_install')
|
||
class TestRsvDeclaration(TransactionCase):
|
||
"""РСВ (NO_RASCHSV 5.08): расчёт разделов из итогов и выгрузка XML."""
|
||
|
||
@classmethod
|
||
def setUpClass(cls):
|
||
super().setUpClass()
|
||
cls.company = cls.env.company
|
||
cls.company.write({
|
||
'rsbu_tax_office_code': '7704',
|
||
'rsbu_oktmo': '45301000',
|
||
'rsbu_msp_enabled': True,
|
||
'rsbu_payer_tariff_code': '01',
|
||
})
|
||
cls.company.partner_id.write({'inn': '7704567896', 'kpp': '770401001'})
|
||
# Подписант.
|
||
user = cls.env['res.users'].search([], limit=1)
|
||
cls.company.rsbu_signatory_id = user.id
|
||
user.partner_id.write({'name': 'Иванов Иван Иванович'})
|
||
# Сотрудник с полными персональными данными (раздел 3).
|
||
cls.employee = cls.env['hr.employee'].create({
|
||
'name': 'Петров Пётр Петрович',
|
||
'company_id': cls.company.id,
|
||
'birthday': '1985-03-15',
|
||
'rsbu_snils': '112-233-445 95',
|
||
'rsbu_inn': '770412345678',
|
||
'rsbu_insured_category': 'НР',
|
||
'rsbu_sex': '1',
|
||
'rsbu_citizenship_code': '643',
|
||
'rsbu_id_doc_code': '21',
|
||
'rsbu_id_doc_number': '45 04 123456',
|
||
})
|
||
cls.Summary = cls.env['rsbu.rsv.summary']
|
||
cls.Decl = cls.env['rsbu.rsv.declaration']
|
||
|
||
def _summary(self, month, base, ops, oms, vnim, payments=None,
|
||
within=None, over=0.0):
|
||
"""Создать помесячный итог взносов сотрудника."""
|
||
return self.Summary.create({
|
||
'company_id': self.company.id,
|
||
'employee_id': self.employee.id,
|
||
'year': 2026, 'month': month,
|
||
'insured_category': 'НР',
|
||
'payments': payments if payments is not None else base,
|
||
'non_taxable': 0.0,
|
||
'base': base,
|
||
'base_within_limit': within if within is not None else base,
|
||
'base_over_limit': over,
|
||
'ops': ops, 'ops_within': ops, 'ops_over': 0.0,
|
||
'oms': oms, 'vnim': vnim,
|
||
})
|
||
|
||
def test_01_compute_section1_ytd(self):
|
||
"""Раздел 1 считается нарастающим итогом с начала года."""
|
||
# I квартал: по 100 000 базы в месяц, ОПС 22000, ОМС 5100, ВНиМ 2900.
|
||
for m in (1, 2, 3):
|
||
self._summary(m, 100000.0, 22000.0, 5100.0, 2900.0)
|
||
dec = self.Decl.create({
|
||
'year': 2026, 'company_id': self.company.id, 'quarter': '1'})
|
||
dec.compute_from_summary()
|
||
self.assertAlmostEqual(dec.base_total, 300000.0, 2)
|
||
self.assertAlmostEqual(dec.charged_total, 66000.0, 2) # ОПС 3×22000
|
||
self.assertAlmostEqual(dec.oms_total, 15300.0, 2)
|
||
self.assertAlmostEqual(dec.vnim_total, 8700.0, 2)
|
||
self.assertEqual(dec.insured_count, 1)
|
||
# Помесячно единый тариф = ОПС+ОМС+ВНиМ = 30000.
|
||
self.assertAlmostEqual(dec.contrib_month1, 30000.0, 2)
|
||
self.assertAlmostEqual(dec.contrib_period, 90000.0, 2)
|
||
|
||
def test_02_pers_lines_built(self):
|
||
"""Раздел 3: строки по сотрудникам за 3 месяца квартала."""
|
||
for m in (1, 2, 3):
|
||
self._summary(m, 100000.0, 22000.0, 5100.0, 2900.0)
|
||
dec = self.Decl.create({
|
||
'year': 2026, 'company_id': self.company.id, 'quarter': '1'})
|
||
dec.compute_from_summary()
|
||
self.assertEqual(len(dec.pers_line_ids), 1)
|
||
line = dec.pers_line_ids
|
||
self.assertEqual(line.employee_id, self.employee)
|
||
self.assertAlmostEqual(line.base_m1, 100000.0, 2)
|
||
self.assertAlmostEqual(line.ops_m3, 22000.0, 2)
|
||
|
||
def test_03_regression_split_in_section1(self):
|
||
"""База сверх предела ОПС отражается в БазПревышОПС раздела 1."""
|
||
# Месяц 3: часть базы сверх лимита.
|
||
self._summary(1, 100000.0, 22000.0, 5100.0, 2900.0)
|
||
self._summary(2, 100000.0, 22000.0, 5100.0, 2900.0)
|
||
self._summary(3, 100000.0, 15000.0, 5100.0, 2900.0,
|
||
within=50000.0, over=50000.0)
|
||
dec = self.Decl.create({
|
||
'year': 2026, 'company_id': self.company.id, 'quarter': '1'})
|
||
dec.compute_from_summary()
|
||
self.assertAlmostEqual(dec.base_within_total, 250000.0, 2)
|
||
self.assertAlmostEqual(dec.base_over_total, 50000.0, 2)
|
||
|
||
def test_04_xml_valid_against_xsd(self):
|
||
"""Сгенерированный РСВ проходит проверку по XSD 5.08."""
|
||
for m in (1, 2, 3):
|
||
self._summary(m, 100000.0, 22000.0, 5100.0, 2900.0)
|
||
dec = self.Decl.create({
|
||
'year': 2026, 'company_id': self.company.id, 'quarter': '1',
|
||
'msp_income_total': 5000000.0, 'msp_income_main': 5000000.0})
|
||
dec.compute_from_summary()
|
||
fname = dec._rsv_xml_filename()
|
||
xml_bytes = dec._rsv_build_xml(filename=fname)
|
||
ok, errors = dec._rsv_validate_xml(xml_bytes)
|
||
self.assertTrue(ok, 'РСВ не прошёл XSD: %s' % '; '.join(errors[:8]))
|
||
self.assertIn(b'windows-1251', xml_bytes[:60])
|
||
self.assertTrue(fname.startswith('NO_RASCHSV_'))
|
||
from lxml import etree
|
||
root = etree.fromstring(xml_bytes)
|
||
self.assertEqual(root.get('ИдФайл'), fname)
|
||
self.assertEqual(root.find('Документ').get('КНД'), '1151111')
|
||
|
||
def test_05_snils_normalized_with_dashes(self):
|
||
"""СНИЛС нормализуется к формату NNN-NNN-NNN-NN (дефисы)."""
|
||
dec = self.Decl.create({
|
||
'year': 2026, 'company_id': self.company.id, 'quarter': '1'})
|
||
self.assertEqual(dec._snils('112-233-445 95'), '112-233-445-95')
|
||
self.assertEqual(dec._snils('11223344595'), '112-233-445-95')
|