6ff01c4119
- employees: last_seen, internal_number, default_page_size; управление-команды - locations: категория/куратор/ответственный аптеки, seed должностей; Location.__str__ показывает номер аптеки (не только адрес) - hiring: HiringRequest (заявки на найм) + тесты (7) - utilities: UtilityReading (показания счётчиков по аптекам) - visit_manager: правки Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
115 lines
4.7 KiB
Python
115 lines
4.7 KiB
Python
from datetime import timedelta
|
|
|
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
|
from django.db import models
|
|
from django.utils import timezone
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from core.models import BaseModel
|
|
from core.utils.validators import validate_ukrainian_phone
|
|
|
|
|
|
class HiringRequest(BaseModel):
|
|
"""Заявка HR на найм кандидата: створення тимчасового (стажерського)
|
|
облікового запису та подальше затвердження/відхилення."""
|
|
|
|
STATUS_CHOICES = [
|
|
('trial', _('На випробуванні')),
|
|
('approved', _('Затверджено')),
|
|
('rejected', _('Відхилено')),
|
|
]
|
|
TRIAL_DAYS_CHOICES = [(i, _('%(n)d дн.') % {'n': i}) for i in range(1, 6)]
|
|
|
|
last_name = models.CharField(_('Прізвище'), max_length=150)
|
|
first_name = models.CharField(_('Ім\'я'), max_length=150)
|
|
middle_name = models.CharField(_('По батькові'), max_length=150, blank=True)
|
|
|
|
candidate_email = models.EmailField(_('Особистий email кандидата'), blank=True)
|
|
candidate_phone = models.CharField(
|
|
_('Телефон кандидата'), max_length=20, blank=True,
|
|
validators=[validate_ukrainian_phone],
|
|
)
|
|
|
|
LOCATION_TYPES = [
|
|
('pharmacy', _('Аптека')),
|
|
('warehouse', _('Склад')),
|
|
('office', _('Офіс')),
|
|
]
|
|
user_location_type = models.CharField(
|
|
_('Тип локації'), max_length=20, choices=LOCATION_TYPES,
|
|
)
|
|
department = models.ForeignKey(
|
|
'core.Directory', on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
limit_choices_to={'directory_type__code': 'department', 'is_active': True},
|
|
related_name='hiring_requests_department', verbose_name=_('Відділ'),
|
|
)
|
|
user_pharmacy = models.ForeignKey(
|
|
'locations.Pharmacy', on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='hiring_requests', verbose_name=_('Аптека'),
|
|
)
|
|
post = models.ForeignKey(
|
|
'core.Directory', on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
limit_choices_to={'directory_type__code': 'post', 'is_active': True},
|
|
related_name='hiring_requests_post', verbose_name=_('Посада'),
|
|
)
|
|
|
|
hire_date = models.DateField(_('Дата виходу'), default=timezone.localdate)
|
|
trial_days = models.PositiveSmallIntegerField(
|
|
_('Термін випробування, днів'), choices=TRIAL_DAYS_CHOICES, default=3,
|
|
validators=[MinValueValidator(1), MaxValueValidator(5)],
|
|
)
|
|
|
|
requested_access = models.ManyToManyField(
|
|
'core.Directory', blank=True,
|
|
limit_choices_to={'directory_type__code': 'access_system', 'is_active': True},
|
|
related_name='hiring_requests_access', verbose_name=_('Потрібні доступи'),
|
|
)
|
|
access_note = models.TextField(_('Примітка щодо доступів'), blank=True)
|
|
|
|
status = models.CharField(_('Статус'), max_length=10, choices=STATUS_CHOICES, default='trial')
|
|
|
|
requested_by = models.ForeignKey(
|
|
'employees.Employee', on_delete=models.PROTECT,
|
|
related_name='hiring_requests_submitted', verbose_name=_('Подав HR'),
|
|
)
|
|
employee = models.OneToOneField(
|
|
'employees.Employee', on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='hiring_request', verbose_name=_('Обліковий запис'),
|
|
)
|
|
decision_by = models.ForeignKey(
|
|
'employees.Employee', on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='hiring_requests_decided', verbose_name=_('Рішення прийняв'),
|
|
)
|
|
decision_at = models.DateTimeField(_('Дата рішення'), null=True, blank=True)
|
|
decision_note = models.TextField(_('Коментар до рішення'), blank=True)
|
|
|
|
granted_ticket = models.ForeignKey(
|
|
'it_manager.Ticket', on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='hiring_requests', verbose_name=_('Заявка в IT'),
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = _('Заявка на найм')
|
|
verbose_name_plural = _('Заявки на найм')
|
|
ordering = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['status']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f'{self.last_name} {self.first_name} ({self.get_status_display()})'
|
|
|
|
@property
|
|
def trial_end_date(self):
|
|
return self.hire_date + timedelta(days=self.trial_days)
|
|
|
|
@property
|
|
def is_overdue(self):
|
|
return self.status == 'trial' and self.trial_end_date < timezone.localdate()
|