# -*- coding: utf-8 -*- from odoo.tests import TransactionCase, tagged @tagged('post_install', '-at_install') class TestNdflReport(TransactionCase): """6-НДФЛ (NO_NDFL6.2 5.05): раздел 1 (6 сроков), раздел 2, XML.""" @classmethod def setUpClass(cls): super().setUpClass() cls.company = cls.env.company cls.company.write({ 'rsbu_tax_office_code': '5050', 'rsbu_oktmo': '46788000', 'rsbu_kbk_ndfl': '18210102010011000110', }) cls.company.partner_id.write({'inn': '5050164162', 'kpp': '505001001'}) user = cls.env['res.users'].search([], limit=1) cls.company.rsbu_signatory_id = user.id user.partner_id.write({'name': 'Коробков Сергей Васильевич'}) cls.emp1 = cls.env['hr.employee'].create({ 'name': 'Петров Пётр', 'company_id': cls.company.id}) cls.emp2 = cls.env['hr.employee'].create({ 'name': 'Сидоров Иван', 'company_id': cls.company.id}) cls.Summary = cls.env['rsbu.ndfl.summary'] cls.Report = cls.env['rsbu.ndfl.report'] def _summary(self, emp, month, accrued, withheld, wh_date, rate=13): """Создать помесячный итог НДФЛ с датой удержания.""" from odoo import fields d = fields.Date.to_date(wh_date) term = self.Summary._deadline_term(d) return self.Summary.create({ 'company_id': self.company.id, 'employee_id': emp.id, 'year': 2026, 'month': month, 'rate': rate, 'accrued': accrued, 'deduction': 0.0, 'base': accrued, 'computed_tax': withheld, 'withheld_tax': withheld, 'withholding_date': d, 'withholding_month': d.month, 'deadline_term': term, }) def test_01_deadline_term_mapping(self): """Срок квартала по дате удержания: 1-22 → нечёт, 23- → чёт.""" from odoo import fields # Апрель (2-й квартал, 1-й месяц квартала). self.assertEqual(self.Summary._deadline_term( fields.Date.to_date('2026-04-10')), 1) # 1-22 апр → срок 1 self.assertEqual(self.Summary._deadline_term( fields.Date.to_date('2026-04-25')), 2) # 23-30 апр → срок 2 self.assertEqual(self.Summary._deadline_term( fields.Date.to_date('2026-05-15')), 3) # 1-22 май → срок 3 self.assertEqual(self.Summary._deadline_term( fields.Date.to_date('2026-06-28')), 6) # 23-30 июн → срок 6 def test_02_section1_six_terms(self): """Раздел 1: удержанный НДФЛ разложен по 6 срокам квартала.""" # Полугодие (квартал 2): удержания в апреле/мае/июне по полупериодам. self._summary(self.emp1, 4, 100000.0, 13000.0, '2026-04-10') # срок1 self._summary(self.emp1, 4, 50000.0, 6500.0, '2026-04-25') # срок2 self._summary(self.emp1, 5, 100000.0, 13000.0, '2026-05-12') # срок3 self._summary(self.emp1, 6, 100000.0, 13000.0, '2026-06-20') # срок5 rep = self.Report.create({ 'year': 2026, 'company_id': self.company.id, 'quarter': '2'}) rep.compute_from_summary() self.assertAlmostEqual(rep.term1, 13000.0, 2) self.assertAlmostEqual(rep.term2, 6500.0, 2) self.assertAlmostEqual(rep.term3, 13000.0, 2) self.assertAlmostEqual(rep.term4, 0.0, 2) self.assertAlmostEqual(rep.term5, 13000.0, 2) self.assertAlmostEqual(rep.term6, 0.0, 2) self.assertAlmostEqual(rep.withheld_total, 45500.0, 2) def test_03_section2_by_rate(self): """Раздел 2: строка по ставке — число лиц, доход, удержано.""" self._summary(self.emp1, 4, 100000.0, 13000.0, '2026-04-10') self._summary(self.emp2, 4, 80000.0, 10400.0, '2026-04-10') rep = self.Report.create({ 'year': 2026, 'company_id': self.company.id, 'quarter': '2'}) rep.compute_from_summary() line = rep.line_ids.filtered(lambda l: l.rate == 13) self.assertTrue(line) self.assertEqual(line.persons_count, 2) self.assertAlmostEqual(line.accrued, 180000.0, 2) self.assertAlmostEqual(line.withheld_tax, 23400.0, 2) def test_04_xml_valid_against_xsd(self): """Сгенерированный 6-НДФЛ проходит XSD 5.05.""" self._summary(self.emp1, 4, 100000.0, 13000.0, '2026-04-10') self._summary(self.emp1, 5, 100000.0, 13000.0, '2026-05-12') self._summary(self.emp1, 6, 100000.0, 13000.0, '2026-06-20') rep = self.Report.create({ 'year': 2026, 'company_id': self.company.id, 'quarter': '2'}) rep.compute_from_summary() fname = rep._ndfl_xml_filename() xml_bytes = rep._ndfl_build_xml(filename=fname) ok, errors = rep._ndfl_validate_xml(xml_bytes) self.assertTrue(ok, '6-НДФЛ не прошёл XSD: %s' % '; '.join(errors[:8])) self.assertIn(b'windows-1251', xml_bytes[:60]) self.assertTrue(fname.startswith('NO_NDFL6.2_')) from lxml import etree root = etree.fromstring(xml_bytes) self.assertEqual(root.get('ИдФайл'), fname) doc = root.find('Документ') self.assertEqual(doc.get('КНД'), '1151100') self.assertEqual(doc.get('Период'), '31') obyaz = doc.find('НДФЛ6.2/ОбязНА') self.assertEqual(obyaz.get('КБК'), '18210102010011000110')