Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC
This commit is contained in:
208
rsbu_fns_reporting/tests/test_efs1_szvtd.py
Normal file
208
rsbu_fns_reporting/tests/test_efs1_szvtd.py
Normal file
@@ -0,0 +1,208 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""ЕФС-1 подраздел 1.1 (СЗВ-ТД): автозаполнение, построение XML, XSD.
|
||||
|
||||
Сверка с живыми образцами 1С (ref_1c/new), принятыми СФР:
|
||||
* ПФР_..._20260401 — ПРИЕМ (вид 1);
|
||||
* ПФР_..._20260803 — УВОЛЬНЕНИЕ (вид 5, с основанием увольнения).
|
||||
"""
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
NS_EFS1 = 'http://пф.рф/ЕФС-1/2024-01-01'
|
||||
NS_EFS = 'http://пф.рф/ВС/ЕФС/2024-01-01'
|
||||
NS_UT2 = 'http://пф.рф/УТ/2017-08-21'
|
||||
NS_AF5 = 'http://пф.рф/АФ/2018-12-07'
|
||||
|
||||
|
||||
def _q(ns, tag):
|
||||
return '{%s}%s' % (ns, tag)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestEfs1Szvtd(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.company = cls.env.company
|
||||
cls.company.write({
|
||||
'rsbu_sfr_reg_number': '060-054-063199',
|
||||
'rsbu_okved': '62.01',
|
||||
'rsbu_ogrn': '1245000059622',
|
||||
'rsbu_signatory_title': 'ГЕНЕРАЛЬНЫЙ ДИРЕКТОР',
|
||||
})
|
||||
cls.company.partner_id.write({
|
||||
'inn': '5050164162', 'kpp': '505001001'})
|
||||
user = cls.env['res.users'].search([], limit=1)
|
||||
user.partner_id.write({'name': 'Коробков Сергей Васильевич'})
|
||||
cls.company.rsbu_signatory_id = user.id
|
||||
|
||||
cls.emp_hire = cls.env['hr.employee'].create({
|
||||
'name': 'Горский Алексей Алексеевич', 'company_id': cls.company.id,
|
||||
'rsbu_snils': '195-652-650 11', 'rsbu_inn': '780604730641',
|
||||
'rsbu_citizenship_code': '643', 'birthday': '2003-09-03'})
|
||||
cls.emp_fire = cls.env['hr.employee'].create({
|
||||
'name': 'Зимина Мария Михайловна', 'company_id': cls.company.id,
|
||||
'rsbu_snils': '171-732-575 70', 'rsbu_inn': '671405753608',
|
||||
'rsbu_citizenship_code': '643', 'birthday': '1997-08-11'})
|
||||
|
||||
cls.Event = cls.env['rsbu.hr.event']
|
||||
cls.Efs1 = cls.env['rsbu.efs1.szvtd']
|
||||
|
||||
def _hire_event(self):
|
||||
return self.Event.create({
|
||||
'company_id': self.company.id,
|
||||
'employee_id': self.emp_hire.id,
|
||||
'event_date': '2026-04-01',
|
||||
'kind': '1',
|
||||
'job_title': 'Младший менеджер проектов',
|
||||
'employment_type': '0',
|
||||
'department_name': 'Отдел разработки программного обеспечения',
|
||||
'func_code': 'ДИСТ',
|
||||
'okz_code': '2421.9',
|
||||
'basis_name': 'Приказ',
|
||||
'basis_date': '2026-04-01',
|
||||
'basis_number': '1',
|
||||
})
|
||||
|
||||
def _fire_event(self):
|
||||
return self.Event.create({
|
||||
'company_id': self.company.id,
|
||||
'employee_id': self.emp_fire.id,
|
||||
'event_date': '2026-08-03',
|
||||
'kind': '5',
|
||||
'job_title': 'Разработчик',
|
||||
'employment_type': '0',
|
||||
'work_kind': 'Разработка программного обеспечения',
|
||||
'department_name': 'Отдел разработки программного обеспечения',
|
||||
'okz_code': '2512.1',
|
||||
'dismissal_reason_code': 'п3ч1с77тк',
|
||||
'dismissal_document': 'Трудовой кодекс Российской Федерации',
|
||||
'dismissal_article': '77',
|
||||
'dismissal_part': '1',
|
||||
'dismissal_point': '3',
|
||||
'dismissal_reason':
|
||||
'Расторжение трудового договора по инициативе работника',
|
||||
'basis_name': 'Приказ',
|
||||
'basis_date': '2026-08-03',
|
||||
'basis_number': '1',
|
||||
})
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
def test_01_autofill_period(self):
|
||||
"""action_compute собирает draft-мероприятия за период."""
|
||||
self._hire_event()
|
||||
self._fire_event()
|
||||
doc = self.Efs1.create({
|
||||
'company_id': self.company.id,
|
||||
'date_from': '2026-04-01', 'date_to': '2026-08-31',
|
||||
'date_fill': '2026-08-31'})
|
||||
doc.action_compute()
|
||||
self.assertEqual(doc.state, 'computed')
|
||||
self.assertEqual(doc.event_count, 2)
|
||||
self.assertEqual(doc.employee_count, 2)
|
||||
|
||||
def test_02_hire_xml_valid_and_structure(self):
|
||||
"""XML приёма проходит XSD и совпадает по структуре с образцом."""
|
||||
self._hire_event()
|
||||
doc = self.Efs1.create({
|
||||
'company_id': self.company.id,
|
||||
'date_from': '2026-04-01', 'date_to': '2026-04-30',
|
||||
'date_fill': '2026-04-01'})
|
||||
doc.action_compute()
|
||||
xml_bytes = doc._efs1_build_xml(guid='311086e3-d18b-4794-a0ea-033f23c1edb2')
|
||||
ok, errors = doc._efs1_validate_xml(xml_bytes)
|
||||
self.assertTrue(ok, 'ЕФС-1 приём не прошёл XSD: %s'
|
||||
% '; '.join(errors[:5]))
|
||||
|
||||
root = ET.fromstring(xml_bytes)
|
||||
self.assertEqual(root.tag, _q(NS_EFS1, 'ЭДПФР'))
|
||||
efs1 = root.find(_q(NS_EFS1, 'ЕФС-1'))
|
||||
self.assertIsNotNone(efs1)
|
||||
strah = efs1.find(_q(NS_EFS1, 'Страхователь'))
|
||||
self.assertEqual(strah.find(_q(NS_EFS, 'РегНомер')).text,
|
||||
'060-054-063199')
|
||||
self.assertEqual(strah.find(_q(NS_UT2, 'ИНН')).text, '5050164162')
|
||||
self.assertEqual(strah.find(_q(NS_EFS, 'ОГРН')).text,
|
||||
'1245000059622')
|
||||
# ЗЛ → СЗВ-ТД → ТД → Мероприятие
|
||||
zl = efs1.find(_q(NS_EFS1, 'СЗВ')).find(_q(NS_EFS1, 'ЗЛ'))
|
||||
self.assertEqual(zl.find(_q(NS_UT2, 'СНИЛС')).text, '195-652-650 11')
|
||||
self.assertEqual(zl.find(_q(NS_EFS1, 'СтатусЗЛ')).text, 'ГРФ')
|
||||
self.assertEqual(zl.find(_q(NS_EFS1, 'Гражданство')).text, '643')
|
||||
td = zl.find(_q(NS_EFS1, 'СЗВ-ТД')).find(_q(NS_EFS1, 'ТД'))
|
||||
m = td.find(_q(NS_EFS1, 'Мероприятие'))
|
||||
self.assertEqual(m.find(_q(NS_EFS, 'Вид')).text, '1')
|
||||
self.assertEqual(m.find(_q(NS_EFS, 'КодВФпоОКЗ')).text, '2421.9')
|
||||
self.assertEqual(m.find(_q(NS_EFS, 'КодВФ')).text, 'ДИСТ')
|
||||
basis = m.find(_q(NS_EFS, 'Основание'))
|
||||
self.assertEqual(basis.find(_q(NS_EFS, 'Номер')).text, '1')
|
||||
# Руководитель + СлужебнаяИнформация
|
||||
ruk = efs1.find(_q(NS_EFS1, 'Руководитель'))
|
||||
self.assertEqual(ruk.find(_q(NS_UT2, 'Должность')).text,
|
||||
'ГЕНЕРАЛЬНЫЙ ДИРЕКТОР')
|
||||
svc = root.find(_q(NS_EFS1, 'СлужебнаяИнформация'))
|
||||
self.assertEqual(svc.find(_q(NS_AF5, 'GUID')).text,
|
||||
'311086e3-d18b-4794-a0ea-033f23c1edb2')
|
||||
|
||||
def test_03_fire_xml_valid_and_dismissal(self):
|
||||
"""XML увольнения проходит XSD, блок ОснованиеУвольнения заполнен."""
|
||||
self._fire_event()
|
||||
doc = self.Efs1.create({
|
||||
'company_id': self.company.id,
|
||||
'date_from': '2026-08-01', 'date_to': '2026-08-31',
|
||||
'date_fill': '2026-08-03'})
|
||||
doc.action_compute()
|
||||
xml_bytes = doc._efs1_build_xml()
|
||||
ok, errors = doc._efs1_validate_xml(xml_bytes)
|
||||
self.assertTrue(ok, 'ЕФС-1 увольнение не прошло XSD: %s'
|
||||
% '; '.join(errors[:5]))
|
||||
|
||||
root = ET.fromstring(xml_bytes)
|
||||
m = (root.find(_q(NS_EFS1, 'ЕФС-1'))
|
||||
.find(_q(NS_EFS1, 'СЗВ')).find(_q(NS_EFS1, 'ЗЛ'))
|
||||
.find(_q(NS_EFS1, 'СЗВ-ТД')).find(_q(NS_EFS1, 'ТД'))
|
||||
.find(_q(NS_EFS1, 'Мероприятие')))
|
||||
self.assertEqual(m.find(_q(NS_EFS, 'Вид')).text, '5')
|
||||
ou = m.find(_q(NS_EFS, 'ОснованиеУвольнения'))
|
||||
self.assertIsNotNone(ou)
|
||||
self.assertEqual(ou.find(_q(NS_EFS, 'КодПричиныУвольнения')).text,
|
||||
'п3ч1с77тк')
|
||||
self.assertEqual(ou.find(_q(NS_EFS, 'Статья')).text, '77')
|
||||
self.assertEqual(ou.find(_q(NS_EFS, 'Часть')).text, '1')
|
||||
self.assertEqual(ou.find(_q(NS_EFS, 'Пункт')).text, '3')
|
||||
self.assertEqual(m.find(_q(NS_EFS, 'Причина')).text,
|
||||
'Расторжение трудового договора по инициативе работника')
|
||||
|
||||
def test_04_multi_zl(self):
|
||||
"""Два сотрудника → два блока ЗЛ в одном документе."""
|
||||
self._hire_event()
|
||||
self._fire_event()
|
||||
doc = self.Efs1.create({
|
||||
'company_id': self.company.id,
|
||||
'date_from': '2026-04-01', 'date_to': '2026-08-31',
|
||||
'date_fill': '2026-08-31'})
|
||||
doc.action_compute()
|
||||
xml_bytes = doc._efs1_build_xml()
|
||||
ok, errors = doc._efs1_validate_xml(xml_bytes)
|
||||
self.assertTrue(ok, 'ЕФС-1 (2 ЗЛ) не прошёл XSD: %s'
|
||||
% '; '.join(errors[:5]))
|
||||
root = ET.fromstring(xml_bytes)
|
||||
zls = root.find(_q(NS_EFS1, 'ЕФС-1')).find(
|
||||
_q(NS_EFS1, 'СЗВ')).findall(_q(NS_EFS1, 'ЗЛ'))
|
||||
self.assertEqual(len(zls), 2)
|
||||
|
||||
def test_05_okz_required_validation(self):
|
||||
"""Отсутствие ОКЗ для приёма блокирует выгрузку (UserError)."""
|
||||
from odoo.exceptions import UserError
|
||||
ev = self._hire_event()
|
||||
ev.okz_code = False
|
||||
self.emp_hire.rsbu_okz_code = False
|
||||
doc = self.Efs1.create({
|
||||
'company_id': self.company.id,
|
||||
'date_from': '2026-04-01', 'date_to': '2026-04-30',
|
||||
'date_fill': '2026-04-01'})
|
||||
doc.action_compute()
|
||||
with self.assertRaises(UserError):
|
||||
doc._efs1_build_xml()
|
||||
Reference in New Issue
Block a user