# -*- 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')