from odoo.tests import TransactionCase, tagged @tagged('post_install', '-at_install') class TestNdsDeclaration(TransactionCase): """Декларация НДС (комплект 3 файла): расчёт из книг, XML по XSD 5.12.""" @classmethod def setUpClass(cls): super().setUpClass() cls.company = cls.env.company cls.company.write({ 'rsbu_vat_mode': 'vat_5', 'rsbu_tax_office_code': '7704', 'rsbu_oktmo': '45301000', }) cls.company.partner_id.write({'inn': '7704567896', 'kpp': '770401001'}) cls.user = cls.env['res.users'].search([], limit=1) cls.company.rsbu_signatory_id = cls.user.id cls.Decl = cls.env['rsbu.nds.declaration'] cls.BookLine = cls.env['rsbu.vat.book.line'] cls.buyer = cls.env['res.partner'].create({ 'name': 'ООО Покупатель', 'inn': '7730179829', 'kpp': '773001001'}) def _sales_line(self, base, vat): return self.BookLine.create({ 'company_id': self.company.id, 'book': 'sales', 'date': '2026-04-15', 'base_amount': base, 'vat_amount': vat, 'partner_id': self.buyer.id}) def _purchase_line(self, vat): return self.BookLine.create({ 'company_id': self.company.id, 'book': 'purchases', 'date': '2026-05-10', 'vat_amount': vat, 'partner_id': self.company.partner_id.id}) def _declaration(self): return self.Decl.create({ 'company_id': self.company.id, 'date_from': '2026-04-01', 'date_to': '2026-06-30'}) def _compute_no_recompute(self, dec): """Расчёт без пересбора книг (иначе тестовые строки затрутся). recompute_period пересобирает книги из проводок; в тесте их нет, поэтому вызываем внутренний расчёт по существующим строкам напрямую. """ # Разложение и суммы считаем по уже созданным строкам книги. real_vat, adv_vat = dec._split_sales_vat() fraction = 5.0 / 105.0 inv = 1.0 / fraction - 1.0 dec.vat_tax_realization = real_vat dec.vat_base_realization = round(real_vat * inv, 2) dec.vat_tax_advance = adv_vat dec.vat_base_advance = round(adv_vat / fraction, 2) if adv_vat else 0.0 dec.vat_charged_total = round(real_vat + adv_vat, 2) ded = sum(self.BookLine.search([ ('company_id', '=', self.company.id), ('book', '=', 'purchases'), ('date', '>=', dec.date_from), ('date', '<=', dec.date_to), ]).mapped('vat_amount')) dec.vat_deduction = round(ded, 2) dec.vat_payable = round(dec.vat_charged_total - ded, 2) dec.oktmo = self.company.rsbu_oktmo dec.kbk = self.company.rsbu_kbk_nds dec.state = 'computed' def test_01_totals(self): """Начислено = книга продаж, вычет = книга покупок, к уплате = разница.""" self._sales_line(100000.0, 5000.0) # реализация 5% self._purchase_line(2000.0) # вычет dec = self._declaration() self._compute_no_recompute(dec) self.assertAlmostEqual(dec.vat_tax_realization, 5000.0, 2) self.assertAlmostEqual(dec.vat_base_realization, 100000.0, 2) self.assertAlmostEqual(dec.vat_charged_total, 5000.0, 2) self.assertAlmostEqual(dec.vat_deduction, 2000.0, 2) self.assertAlmostEqual(dec.vat_payable, 3000.0, 2) def test_02_three_files_valid_against_xsd(self): """Комплект из 3 файлов валиден против XSD 5.12.""" self._sales_line(100000.0, 5000.0) self._purchase_line(2000.0) dec = self._declaration() self._compute_no_recompute(dec) files = dec._nds_build_all() self.assertEqual(len(files), 3) for (fname, xml), kind in zip(files, ['root', 'purchases', 'sales']): ok, errors = dec._nds_validate_xml(xml, kind) self.assertTrue(ok, '%s не прошёл XSD: %s' % (kind, '; '.join(errors[:4]))) self.assertIn(b'windows-1251', xml[:60]) def test_03_root_references_sections(self): """Корневая декларация ссылается на файлы разделов 8 и 9.""" self._sales_line(100000.0, 5000.0) self._purchase_line(2000.0) dec = self._declaration() self._compute_no_recompute(dec) files = dec._nds_build_all() name_root, name_pokup, name_prod = (f[0] for f in files) root_xml = files[0][1].decode('cp1251') self.assertIn(name_pokup, root_xml) # КнигаПокуп/НаимКнПок self.assertIn(name_prod, root_xml) # КнигаПрод/НаимКнПрод self.assertTrue(name_pokup.startswith('NO_NDS.8_')) self.assertTrue(name_prod.startswith('NO_NDS.9_')) def test_04_advance_split(self): """Начисленный НДС делится на реализацию и авансы (по 76.АВ).""" # Реализация (без 76.АВ) и аванс (эмулируем через отдельную строку). self._sales_line(100000.0, 5000.0) dec = self._declaration() real, adv = dec._split_sales_vat() # Без авансовых проводок весь НДС — реализация. self.assertAlmostEqual(real, 5000.0, 2) self.assertAlmostEqual(adv, 0.0, 2)