196 lines
10 KiB
Python
196 lines
10 KiB
Python
# -*- 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='Примечание')
|