hiring: автоназначение ролей по типу локации
При утверждении найма сотрудник получает роли (группы) по умолчанию для своего типа локации (Аптека/Офис/Склад). Наборы настраиваются в EmployeeDefaults (админка «Настройки сотрудников по умолчанию»). - EmployeeDefaults: 3 M2M default_groups_* + apply_default_groups(); - approve_hiring_request вызывает apply_default_groups после создания Employee; - админка: блок «Роли по умолчанию при найме» (filter_horizontal); - миграция 0023; тесты найма. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -414,6 +414,9 @@ class EmployeeAdmin(ModelAdmin):
|
||||
class EmployeeDefaultsAdmin(ModelAdmin):
|
||||
"""Единственная запись настроек по умолчанию для сотрудников."""
|
||||
|
||||
filter_horizontal = (
|
||||
'default_groups_office', 'default_groups_pharmacy', 'default_groups_warehouse',
|
||||
)
|
||||
fieldsets = (
|
||||
(_('Офисные сотрудники'), {
|
||||
'fields': ('office_email_domain',),
|
||||
@@ -422,6 +425,15 @@ class EmployeeDefaultsAdmin(ModelAdmin):
|
||||
'назначается рабочий email: логин@домен.'
|
||||
),
|
||||
}),
|
||||
(_('Роли по умолчанию при найме'), {
|
||||
'fields': (
|
||||
'default_groups_office', 'default_groups_pharmacy', 'default_groups_warehouse',
|
||||
),
|
||||
'description': _(
|
||||
'Эти роли (группы) автоматически выдаются новому сотруднику '
|
||||
'при утверждении найма — по типу его локации.'
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
def has_add_permission(self, request):
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 6.0.3 on 2026-09-21 13:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
('employees', '0022_employee_it_responsibility_zone'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='employeedefaults',
|
||||
name='default_groups_office',
|
||||
field=models.ManyToManyField(blank=True, related_name='+', to='auth.group', verbose_name='Роли по умолчанию — Офис'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='employeedefaults',
|
||||
name='default_groups_pharmacy',
|
||||
field=models.ManyToManyField(blank=True, related_name='+', to='auth.group', verbose_name='Роли по умолчанию — Аптека'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='employeedefaults',
|
||||
name='default_groups_warehouse',
|
||||
field=models.ManyToManyField(blank=True, related_name='+', to='auth.group', verbose_name='Роли по умолчанию — Склад'),
|
||||
),
|
||||
]
|
||||
@@ -19,6 +19,20 @@ class EmployeeDefaults(models.Model):
|
||||
),
|
||||
)
|
||||
|
||||
# Роли (группы), выдаваемые новому сотруднику по типу его локации.
|
||||
default_groups_office = models.ManyToManyField(
|
||||
'auth.Group', blank=True, related_name='+',
|
||||
verbose_name=_('Роли по умолчанию — Офис'),
|
||||
)
|
||||
default_groups_pharmacy = models.ManyToManyField(
|
||||
'auth.Group', blank=True, related_name='+',
|
||||
verbose_name=_('Роли по умолчанию — Аптека'),
|
||||
)
|
||||
default_groups_warehouse = models.ManyToManyField(
|
||||
'auth.Group', blank=True, related_name='+',
|
||||
verbose_name=_('Роли по умолчанию — Склад'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Настройки сотрудников по умолчанию')
|
||||
verbose_name_plural = _('Настройки сотрудников по умолчанию')
|
||||
@@ -41,3 +55,25 @@ class EmployeeDefaults(models.Model):
|
||||
@property
|
||||
def normalized_office_domain(self) -> str:
|
||||
return (self.office_email_domain or 'aks-apteka.com.ua').strip().lstrip('@').lower()
|
||||
|
||||
def groups_for(self, location_type: str):
|
||||
"""M2M-менеджер ролей по умолчанию для типа локации (или None)."""
|
||||
return {
|
||||
'office': self.default_groups_office,
|
||||
'pharmacy': self.default_groups_pharmacy,
|
||||
'warehouse': self.default_groups_warehouse,
|
||||
}.get(location_type)
|
||||
|
||||
def apply_default_groups(self, employee) -> list:
|
||||
"""Добавить сотруднику роли по умолчанию для его типа локации.
|
||||
|
||||
add() (не set) — не затирает уже назначенные роли. Возвращает
|
||||
список добавленных групп (для лога/сообщений).
|
||||
"""
|
||||
mgr = self.groups_for(employee.user_location_type)
|
||||
if mgr is None:
|
||||
return []
|
||||
groups = list(mgr.all())
|
||||
if groups:
|
||||
employee.groups.add(*groups)
|
||||
return groups
|
||||
|
||||
@@ -249,6 +249,10 @@ def approve_hiring_request(hiring_request, decided_by, note=''):
|
||||
employee.full_clean()
|
||||
employee.save()
|
||||
|
||||
# Роли по умолчанию по типу локации (Аптека/Офис/Склад).
|
||||
from apps.employees.models import EmployeeDefaults
|
||||
EmployeeDefaults.load().apply_default_groups(employee)
|
||||
|
||||
from apps.employees.services.password_invite import create_password_invite
|
||||
invite = create_password_invite(employee, created_by=decided_by)
|
||||
|
||||
|
||||
@@ -142,6 +142,26 @@ class HiringServiceTests(TestCase):
|
||||
create_hiring_request(**self._office_kwargs(user_location_id=None))
|
||||
self.assertIn('user_location', cm.exception.message_dict)
|
||||
|
||||
def test_approve_assigns_default_groups_by_location(self):
|
||||
from django.contrib.auth.models import Group
|
||||
from apps.employees.models import EmployeeDefaults
|
||||
role = Group.objects.create(name='Аптека-базовая')
|
||||
EmployeeDefaults.load().default_groups_pharmacy.add(role)
|
||||
|
||||
hr = create_hiring_request(**self._pharmacy_kwargs())
|
||||
approved = approve_hiring_request(hr, decided_by=self.hr_user)
|
||||
self.assertIn(role, approved.employee.groups.all())
|
||||
|
||||
def test_approve_does_not_assign_other_location_groups(self):
|
||||
from django.contrib.auth.models import Group
|
||||
from apps.employees.models import EmployeeDefaults
|
||||
pharm_role = Group.objects.create(name='Аптека-роль')
|
||||
EmployeeDefaults.load().default_groups_pharmacy.add(pharm_role)
|
||||
|
||||
hr = create_hiring_request(**self._office_kwargs())
|
||||
approved = approve_hiring_request(hr, decided_by=self.hr_user)
|
||||
self.assertNotIn(pharm_role, approved.employee.groups.all())
|
||||
|
||||
def test_is_overdue_when_trial_period_passed(self):
|
||||
hr = create_hiring_request(**self._pharmacy_kwargs(
|
||||
last_name='Прострочений', first_name='Тест',
|
||||
|
||||
Reference in New Issue
Block a user