Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC
This commit is contained in:
128
rsbu_fns_reporting/tests/test_usn_declaration.py
Normal file
128
rsbu_fns_reporting/tests/test_usn_declaration.py
Normal file
@@ -0,0 +1,128 @@
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestUsnDeclaration(TransactionCase):
|
||||
"""Декларация УСН «Доходы»: расчёт разделов 1.1/2.1.1 и XML."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.company = cls.env.company
|
||||
cls.company.write({
|
||||
'rsbu_usn_rate': 6.0,
|
||||
'rsbu_tax_office_code': '7704',
|
||||
'rsbu_oktmo': '45301000',
|
||||
})
|
||||
cls.company.partner_id.write({'inn': '7704567896', 'kpp': '770401001'})
|
||||
cls.Decl = cls.env['rsbu.usn.declaration']
|
||||
cls.Summary = cls.env['rsbu.kudir.summary']
|
||||
|
||||
def _summary(self, quarter, income, insurance=0.0):
|
||||
"""Создать запись итогов КУДиР за квартал года."""
|
||||
return self.Summary.create({
|
||||
'company_id': self.company.id,
|
||||
'period_key': '2026-Q%s' % quarter,
|
||||
'income': income,
|
||||
'expense': 0.0,
|
||||
'insurance': insurance,
|
||||
})
|
||||
|
||||
def test_01_progression_of_advances(self):
|
||||
"""Авансы по срокам считаются нарастающим итогом (без взносов).
|
||||
|
||||
Квартальные доходы (summary хранит за квартал, ytd суммирует):
|
||||
Q1 1 000 000, Q2 500 000, Q3 500 000, Q4 1 000 000.
|
||||
Нарастающим: Q1 1М, H1 1.5М, 9м 2М, год 3М. Ставка 6%.
|
||||
Исчислено: 60000/90000/120000/180000. Взносов нет.
|
||||
Авансы: 020=60000, 040=30000, 070=30000, 100=60000.
|
||||
"""
|
||||
self._summary(1, 1_000_000)
|
||||
self._summary(2, 500_000)
|
||||
self._summary(3, 500_000)
|
||||
self._summary(4, 1_000_000)
|
||||
dec = self.Decl.create({
|
||||
'year': 2026, 'company_id': self.company.id,
|
||||
'taxpayer_sign': '1', 'rate': 6.0})
|
||||
dec.compute_from_summary()
|
||||
self.assertAlmostEqual(dec.tax_q1, 60000.0, 2)
|
||||
self.assertAlmostEqual(dec.tax_year, 180000.0, 2)
|
||||
self.assertAlmostEqual(dec.adv_q1, 60000.0, 2) # стр.020
|
||||
self.assertAlmostEqual(dec.adv_h1, 30000.0, 2) # стр.040
|
||||
self.assertAlmostEqual(dec.adv_9m, 30000.0, 2) # стр.070
|
||||
self.assertAlmostEqual(dec.tax_due_year, 60000.0, 2) # стр.100
|
||||
|
||||
def test_02_reduction_limited_50pct(self):
|
||||
"""Вычет взносов для признака «1» ограничен 50% исчисленного."""
|
||||
self._summary(1, 1_000_000, insurance=40000.0)
|
||||
dec = self.Decl.create({
|
||||
'year': 2026, 'company_id': self.company.id,
|
||||
'taxpayer_sign': '1', 'rate': 6.0})
|
||||
dec.compute_from_summary()
|
||||
# Исчислено 60000; взносы 40000, но лимит 50% = 30000.
|
||||
self.assertAlmostEqual(dec.tax_q1, 60000.0, 2)
|
||||
self.assertAlmostEqual(dec.ded_q1, 30000.0, 2)
|
||||
self.assertAlmostEqual(dec.adv_q1, 30000.0, 2) # 60000 − 30000
|
||||
|
||||
def test_03_advance_reduce_when_income_drops(self):
|
||||
"""Аванс к уменьшению (050), если исчисленное за полугодие < аванса Q1.
|
||||
|
||||
Q1 доход 1М (аванс 60000). Полугодие доход тот же 1М (нет прироста)
|
||||
→ (131−141)−020 = 60000 − 60000 = 0; при возврате дохода строка 050.
|
||||
Здесь проверяем неотрицательность и отсутствие двойного счёта.
|
||||
"""
|
||||
self._summary(1, 1_000_000)
|
||||
self._summary(2, 0.0) # прироста во II квартале нет → H1 = Q1
|
||||
dec = self.Decl.create({
|
||||
'year': 2026, 'company_id': self.company.id,
|
||||
'taxpayer_sign': '1', 'rate': 6.0})
|
||||
dec.compute_from_summary()
|
||||
self.assertAlmostEqual(dec.adv_q1, 60000.0, 2)
|
||||
self.assertAlmostEqual(dec.adv_h1, 0.0, 2)
|
||||
self.assertAlmostEqual(dec.adv_h1_reduce, 0.0, 2)
|
||||
|
||||
def test_04_xml_valid_against_xsd(self):
|
||||
"""Сгенерированный XML проходит проверку по XSD-схеме ФНС."""
|
||||
self._summary(1, 1_000_000)
|
||||
self._summary(2, 500_000)
|
||||
self._summary(3, 500_000)
|
||||
self._summary(4, 1_000_000)
|
||||
dec = self.Decl.create({
|
||||
'year': 2026, 'company_id': self.company.id,
|
||||
'taxpayer_sign': '1', 'rate': 6.0})
|
||||
dec.compute_from_summary()
|
||||
fname = dec._usn_xml_filename()
|
||||
xml_bytes = dec._usn_build_xml(filename=fname)
|
||||
ok, errors = dec._usn_validate_xml(xml_bytes)
|
||||
self.assertTrue(ok, 'XML не прошёл XSD: %s' % '; '.join(errors[:5]))
|
||||
# Кодировка windows-1251 в декларации XML.
|
||||
self.assertIn(b'windows-1251', xml_bytes[:60])
|
||||
# Префикс имени файла обмена — NO_USN (Порядок п.3).
|
||||
self.assertTrue(fname.startswith('NO_USN_'))
|
||||
# ИдФайл в XML совпадает с именем файла обмена.
|
||||
from lxml import etree
|
||||
root = etree.fromstring(xml_bytes)
|
||||
self.assertEqual(root.get('ИдФайл'), fname)
|
||||
|
||||
def test_05_export_always_recomputes(self):
|
||||
"""Выгрузка XML всегда пересчитывает данные (свежесть гарантирована).
|
||||
|
||||
Даже если декларация уже в состоянии «Рассчитана», action_export_xml
|
||||
обязан вызвать action_compute перед формированием файла — иначе
|
||||
выгрузятся устаревшие суммы после изменения КУДиР.
|
||||
"""
|
||||
dec = self.Decl.create({
|
||||
'year': 2026, 'company_id': self.company.id,
|
||||
'taxpayer_sign': '1', 'rate': 6.0})
|
||||
dec.action_compute()
|
||||
self.assertEqual(dec.state, 'computed')
|
||||
calls = []
|
||||
orig = type(dec).action_compute
|
||||
|
||||
def _spy(self2):
|
||||
calls.append(1)
|
||||
return orig(self2)
|
||||
|
||||
self.patch(type(dec), 'action_compute', _spy)
|
||||
dec.action_export_xml()
|
||||
self.assertTrue(calls, 'action_export_xml должен вызвать пересчёт')
|
||||
Reference in New Issue
Block a user