# -*- coding: utf-8 -*- """Отчёты по воинскому учёту: формы 6, 18, 19. Собирают численности из карточек сотрудников (hr.employee, поля воинского учёта) и выгружаются в DOCX через docx_report. * Форма 6 — отчёт о численности работающих и забронированных граждан, пребывающих в запасе (строки по категориям должности). * Форма 18 — карточка учёта организации (реквизиты + сводные числа). * Форма 19 — сведения об обеспеченности трудовыми ресурсами (строки по категориям должности с потребностью/некомплектом). Это ОБРАЗЕЦ: методика подсчёта упрощена и уточняется у специалиста ВУ. """ from odoo import api, fields, models from odoo.exceptions import UserError from .hr_employee import MIL_JOB_CATEGORY REPORT_KIND = [ ('form6', 'Форма 6 — численность работающих и забронированных в запасе'), ('form18', 'Форма 18 — карточка учёта организации'), ('form19', 'Форма 19 — обеспеченность трудовыми ресурсами'), ] # Шаблоны docx_report (xmlid docx.template → по виду формы). _REPORT_XMLID = { 'form6': 'ruodoo_rsbu_mil.docx_template_mil_form6', 'form18': 'ruodoo_rsbu_mil.docx_template_mil_form18', 'form19': 'ruodoo_rsbu_mil.docx_template_mil_form19', } class RuodooRsbuMilReport(models.Model): _name = 'ruodoo.rsbu.mil.report' _description = 'Отчёт по воинскому учёту' _order = 'report_date desc, id desc' name = fields.Char(string='Наименование', compute='_compute_name', store=True) kind = fields.Selection( selection=REPORT_KIND, string='Форма', required=True, default='form6') company_id = fields.Many2one( 'res.company', string='Организация', required=True, index=True, default=lambda self: self.env.company) report_date = fields.Date( string='По состоянию на', required=True, default=fields.Date.context_today, help='Дата, по состоянию на которую формируется отчёт.') year = fields.Integer( string='Отчётный год', default=lambda self: fields.Date.today().year) state = fields.Selection( selection=[('draft', 'Черновик'), ('computed', 'Рассчитано')], default='draft', string='Состояние', copy=False) line_ids = fields.One2many( 'ruodoo.rsbu.mil.report.line', 'report_id', string='Строки') # --- Сводные числа (используются формой 18 и итогами). --- total_employees = fields.Integer( string='Всего работающих', compute='_compute_totals', store=True) total_reserve = fields.Integer( string='В запасе, всего', compute='_compute_totals', store=True) total_reserve_officer = fields.Integer( string='В запасе, офицеры', compute='_compute_totals', store=True) total_reserve_soldier = fields.Integer( string='В запасе, прочие', compute='_compute_totals', store=True) total_booked = fields.Integer( string='Забронировано, всего', compute='_compute_totals', store=True) total_booked_officer = fields.Integer( string='Забронировано, офицеры', compute='_compute_totals', store=True) total_booked_soldier = fields.Integer( string='Забронировано, прочие', compute='_compute_totals', store=True) total_conscript = fields.Integer( string='Подлежат призыву', compute='_compute_totals', store=True) total_limited_fit = fields.Integer( string='Ограниченно годных', compute='_compute_totals', store=True) total_no_order = fields.Integer( string='В запасе без мобпредписаний', compute='_compute_totals', store=True) total_with_order = fields.Integer( string='В запасе с мобпредписаниями', compute='_compute_totals', store=True) @api.depends('kind', 'report_date', 'year') def _compute_name(self): labels = dict(REPORT_KIND) for rec in self: short = (labels.get(rec.kind, '?') or '').split('—')[0].strip() dt = rec.report_date and rec.report_date.strftime('%d.%m.%Y') or '?' rec.name = '%s на %s' % (short, dt) # ------------------------------------------------------------------ def _mil_employees(self): """Военнообязанные сотрудники организации.""" self.ensure_one() return self.env['hr.employee'].search([ ('company_id', '=', self.company_id.id), ('rsbu_mil_liable', '=', True), ]) @api.depends('company_id') def _compute_totals(self): for rec in self: emps = rec._mil_employees() reserve = emps.filtered('rsbu_mil_in_reserve') booked = reserve.filtered('rsbu_mil_booked') rec.total_employees = len(emps) rec.total_reserve = len(reserve) rec.total_reserve_officer = len(reserve.filtered( lambda e: e.rsbu_mil_rank_group == 'officer')) rec.total_reserve_soldier = len(reserve.filtered( lambda e: e.rsbu_mil_rank_group == 'soldier')) rec.total_booked = len(booked) rec.total_booked_officer = len(booked.filtered( lambda e: e.rsbu_mil_rank_group == 'officer')) rec.total_booked_soldier = len(booked.filtered( lambda e: e.rsbu_mil_rank_group == 'soldier')) rec.total_conscript = len(emps.filtered('rsbu_mil_conscript')) rec.total_limited_fit = len(reserve.filtered('rsbu_mil_limited_fit')) not_booked = reserve.filtered(lambda e: not e.rsbu_mil_booked) rec.total_no_order = len(not_booked.filtered( lambda e: not e.rsbu_mil_mobilization_order)) rec.total_with_order = len(not_booked.filtered( 'rsbu_mil_mobilization_order')) # ------------------------------------------------------------------ def action_compute(self): """Рассчитать строки отчёта по категориям должности.""" Line = self.env['ruodoo.rsbu.mil.report.line'] for rec in self: rec.line_ids.unlink() emps = rec._mil_employees() vals = [] for code, label in MIL_JOB_CATEGORY: cat = emps.filtered(lambda e: e.rsbu_mil_job_category == code) reserve = cat.filtered('rsbu_mil_in_reserve') booked = reserve.filtered('rsbu_mil_booked') conscript = cat.filtered('rsbu_mil_conscript') vals.append({ 'report_id': rec.id, 'category': code, 'name': label, 'total': len(cat), 'reserve_total': len(reserve), 'reserve_officer': len(reserve.filtered( lambda e: e.rsbu_mil_rank_group == 'officer')), 'reserve_soldier': len(reserve.filtered( lambda e: e.rsbu_mil_rank_group == 'soldier')), 'booked_total': len(booked), 'booked_officer': len(booked.filtered( lambda e: e.rsbu_mil_rank_group == 'officer')), 'booked_soldier': len(booked.filtered( lambda e: e.rsbu_mil_rank_group == 'soldier')), 'conscript_total': len(conscript), }) if vals: Line.create(vals) rec.state = 'computed' return True def action_print_docx(self): """Сформировать DOCX отчёта по шаблону, соответствующему форме.""" self.ensure_one() if self.state == 'draft': self.action_compute() xmlid = _REPORT_XMLID.get(self.kind) template = self.env.ref(xmlid, raise_if_not_found=False) if xmlid else None if not template: raise UserError( 'Не найден шаблон DOCX для выбранной формы воинского учёта.') return template.report_id.report_action(self) class RuodooRsbuMilReportLine(models.Model): _name = 'ruodoo.rsbu.mil.report.line' _description = 'Строка отчёта по воинскому учёту' _order = 'report_id, sequence, id' report_id = fields.Many2one( 'ruodoo.rsbu.mil.report', string='Отчёт', required=True, ondelete='cascade', index=True) sequence = fields.Integer(string='Порядок', default=10) category = fields.Selection( selection=MIL_JOB_CATEGORY, string='Категория должности') name = fields.Char(string='Наименование категории') total = fields.Integer(string='Всего работающих') reserve_total = fields.Integer(string='В запасе, всего') reserve_officer = fields.Integer(string='В запасе, офицеры') reserve_soldier = fields.Integer(string='В запасе, прочие') booked_total = fields.Integer(string='Забронировано, всего') booked_officer = fields.Integer(string='Забронировано, офицеры') booked_soldier = fields.Integer(string='Забронировано, прочие') conscript_total = fields.Integer(string='Подлежат призыву') note = fields.Char(string='Примечание')