Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC

This commit is contained in:
CI Publish Bot
2026-11-01 22:54:30 +00:00
commit 26250aa580
2492 changed files with 664277 additions and 0 deletions

View 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()