Files
public/rsbu_fns_reporting/tests/test_efs1_szvtd.py

209 lines
9.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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()