# -*- coding: utf-8 -*- """Кадровое мероприятие для ЕФС-1 подраздел 1.1 (СЗВ-ТД). Одно мероприятие кадрового учёта (приём, перевод, увольнение и т.п.) по сотруднику — источник для элемента «Мероприятие» (тип ЕФС:ТипТД) формы ЕФС-1 подраздел 1.1 «Сведения о трудовой (иной) деятельности». ЕФС-1 (СФР) — событийная форма: подаётся не за период, а по факту кадрового события. При приёме и увольнении — не позднее следующего рабочего дня, при прочих мероприятиях — до 25-го числа следующего месяца. Поэтому мероприятие регистрируется отдельной записью и затем выгружается в документ ЕФС-1 (rsbu.efs1.szvtd). Поля и справочники — строго по XSD ЕФС:ТипТД (ТипыЕФС_2024-01-01): UUID, Дата, Вид (1..10), Должность, ЯвляетсяСовместителем, ВидПР, СтруктурноеПодразделение, КодВФ, КодВФпоОКЗ, ОснованиеУвольнения (КодПричиныУвольнения, НормативныйДокумент, Статья, Часть, Пункт, Подпункт, Абзац), Причина, Основание (Наименование, Дата, Серия, Номер). """ import uuid from odoo import api, fields, models # Виды мероприятий (ЕФС:ТипТД/Вид). Значения 1..10 по XSD. EVENT_KIND_SELECTION = [ ('1', 'ПРИЕМ'), ('2', 'ПЕРЕВОД'), ('3', 'ПЕРЕИМЕНОВАНИЕ'), ('4', 'УСТАНОВЛЕНИЕ (ПРИСВОЕНИЕ)'), ('5', 'УВОЛЬНЕНИЕ'), ('6', 'ЗАПРЕТ ЗАНИМАТЬ ДОЛЖНОСТЬ'), ('7', 'ПРИОСТАНОВЛЕНИЕ'), ('8', 'ВОЗОБНОВЛЕНИЕ'), ('9', 'НАЧАЛО ДОГОВОРА ГПХ'), ('10', 'ОКОНЧАНИЕ ДОГОВОРА ГПХ'), ] # Виды мероприятий, для которых КодВФпоОКЗ обязателен (по описанию XSD). KINDS_REQUIRE_OKZ = {'1', '2', '4', '5', '7', '8', '9', '10'} # Признак совместительства / срочности (ЕФС:ТипТД/ЯвляетсяСовместителем). EMPLOYMENT_TYPE_SELECTION = [ ('0', 'Бессрочный трудовой договор'), ('0.1', 'Срочный ТД до 6 месяцев'), ('0.2', 'Срочный ТД более 6 месяцев'), ('1', 'Совместительство'), ('1.1', 'Совместительство, срочный до 6 месяцев'), ('1.2', 'Совместительство, срочный более 6 месяцев'), ] class RsbuHrEvent(models.Model): _name = 'rsbu.hr.event' _description = 'РСБУ — Кадровое мероприятие (ЕФС-1, СЗВ-ТД)' _order = 'event_date desc, id desc' _check_company_auto = True name = fields.Char(string='Наименование', compute='_compute_name', store=True) company_id = fields.Many2one( 'res.company', string='Компания', required=True, index=True, default=lambda self: self.env.company) employee_id = fields.Many2one( 'hr.employee', string='Сотрудник', required=True, index=True, check_company=True) event_uuid = fields.Char( string='UUID мероприятия', required=True, copy=False, index=True, default=lambda self: str(uuid.uuid4()), help='Уникальный идентификатор мероприятия (элемент UUID). ' 'Используется СФР для сопоставления и отмены мероприятий.') event_date = fields.Date( string='Дата мероприятия', required=True, default=fields.Date.context_today, help='Дата проведения кадрового мероприятия (приёма, увольнения ' 'и т.п.).') kind = fields.Selection( selection=EVENT_KIND_SELECTION, string='Вид мероприятия', required=True, default='1') # Сведения о занятости. job_title = fields.Char( string='Должность', help='Должность, профессия, специальность, квалификация (элемент ' 'Должность).') employment_type = fields.Selection( selection=EMPLOYMENT_TYPE_SELECTION, string='Характер работы', default='0', help='Признак совместительства/срочности трудового договора ' '(элемент ЯвляетсяСовместителем).') work_kind = fields.Char( string='Вид поручаемой работы', help='Конкретный вид поручаемой работы (элемент ВидПР).') department_name = fields.Char( string='Структурное подразделение', help='Структурное подразделение (элемент СтруктурноеПодразделение).') func_code = fields.Char( string='Код выполняемой функции', help='Код выполняемой функции (элемент КодВФ): особый код трудовой ' 'функции (ДИСТ, ДГПХ и т.п.) либо код по профстандарту.') okz_code = fields.Char( string='Код по ОКЗ', help='Кодовое обозначение занятия по ОКЗ, формат NNNN.N (элемент ' 'КодВФпоОКЗ). По умолчанию берётся из карточки сотрудника.') # Документ-основание (приказ). basis_name = fields.Char( string='Документ-основание', default='Приказ', help='Наименование документа-основания (элемент Основание/' 'Наименование), напр. «Приказ».') basis_date = fields.Date( string='Дата основания', help='Дата документа-основания (элемент Основание/Дата).') basis_series = fields.Char( string='Серия основания', help='Серия документа-основания (элемент Основание/Серия).') basis_number = fields.Char( string='Номер основания', help='Номер документа-основания (элемент Основание/Номер).') # Основание увольнения (только для вида «УВОЛЬНЕНИЕ»). dismissal_reason_code = fields.Char( string='Код причины увольнения', help='Код причины увольнения (элемент ОснованиеУвольнения/' 'КодПричиныУвольнения), напр. «п3ч1с77тк».') dismissal_document = fields.Char( string='Нормативный документ', default='Трудовой кодекс Российской Федерации', help='Наименование нормативного документа (элемент ' 'ОснованиеУвольнения/НормативныйДокумент).') dismissal_article = fields.Char( string='Статья', help='Номер статьи нормативного акта (элемент ОснованиеУвольнения/' 'Статья).') dismissal_part = fields.Char( string='Часть', help='Часть статьи (элемент ОснованиеУвольнения/Часть).') dismissal_point = fields.Char( string='Пункт', help='Пункт статьи (элемент ОснованиеУвольнения/Пункт).') dismissal_subpoint = fields.Char( string='Подпункт', help='Подпункт статьи (элемент ОснованиеУвольнения/Подпункт).') dismissal_paragraph = fields.Char( string='Абзац', help='Абзац статьи (элемент ОснованиеУвольнения/Абзац).') dismissal_reason = fields.Char( string='Причина увольнения', help='Текстовая формулировка причины увольнения (элемент Причина).') state = fields.Selection( selection=[('draft', 'Черновик'), ('reported', 'Подано')], default='draft', string='Состояние', copy=False) @api.depends('employee_id', 'kind', 'event_date') def _compute_name(self): labels = dict(EVENT_KIND_SELECTION) for rec in self: label = labels.get(rec.kind, '?') emp = rec.employee_id.name or '?' dt = rec.event_date and rec.event_date.strftime('%d.%m.%Y') or '?' rec.name = '%s — %s (%s)' % (label, emp, dt) @api.onchange('employee_id') def _onchange_employee_id(self): """Подставить должность/подразделение/ОКЗ из карточки сотрудника.""" for rec in self: emp = rec.employee_id if not emp: continue if not rec.job_title: rec.job_title = emp.job_title or ( emp.job_id.name if emp.job_id else False) if not rec.department_name: rec.department_name = ( emp.department_id.name if emp.department_id else False) if not rec.okz_code: rec.okz_code = emp.rsbu_okz_code or False @api.onchange('kind') def _onchange_kind(self): """Подставить дату основания из даты мероприятия, если пусто.""" for rec in self: if not rec.basis_date and rec.event_date: rec.basis_date = rec.event_date def _okz_required(self): self.ensure_one() return self.kind in KINDS_REQUIRE_OKZ def _effective_okz(self): """Код ОКЗ мероприятия (из записи или карточки сотрудника).""" self.ensure_one() return self.okz_code or self.employee_id.rsbu_okz_code or ''