diff --git a/acore/settings/base.py b/acore/settings/base.py index 429361d..9825560 100644 --- a/acore/settings/base.py +++ b/acore/settings/base.py @@ -80,6 +80,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django.contrib.postgres', # ArrayField (mattermost sync location_types) # Third-party 'treenode', diff --git a/apps/mattermost_connector/admin.py b/apps/mattermost_connector/admin.py index 554354a..51475da 100755 --- a/apps/mattermost_connector/admin.py +++ b/apps/mattermost_connector/admin.py @@ -1,8 +1,10 @@ +from django import forms from django.contrib import admin from unfold.admin import ModelAdmin, TabularInline from unfold.decorators import display from django.utils.html import format_html from .models import ( + LOCATION_TYPE_CHOICES, MattermostUser, MattermostTeam, MattermostChannel, @@ -14,6 +16,28 @@ from .models import ( ) +class TeamSyncSettingsForm(forms.ModelForm): + location_types = forms.MultipleChoiceField( + choices=LOCATION_TYPE_CHOICES, required=False, + widget=forms.CheckboxSelectMultiple, label='Типы локаций', + ) + + class Meta: + model = TeamSyncSettings + fields = '__all__' + + +class ChannelSyncSettingsForm(forms.ModelForm): + location_types = forms.MultipleChoiceField( + choices=LOCATION_TYPE_CHOICES, required=False, + widget=forms.CheckboxSelectMultiple, label='Типы локаций', + ) + + class Meta: + model = ChannelSyncSettings + fields = '__all__' + + @admin.register(MattermostBot) class MattermostBotAdmin(ModelAdmin): """Боты Mattermost по ролям. Токен хранится в БД зашифрованным.""" @@ -39,6 +63,7 @@ class MattermostTeamAdmin(ModelAdmin): @admin.register(TeamSyncSettings) class TeamSyncSettingsAdmin(ModelAdmin): """Админка для настроек синхронизации команд""" + form = TeamSyncSettingsForm list_display = ( 'team_display', 'is_active_display', @@ -63,6 +88,10 @@ class TeamSyncSettingsAdmin(ModelAdmin): 'fields': ('filter_by_department', 'departments'), 'description': 'Если включено, в команду будут добавлены только пользователи из выбранных отделов' }), + ('Фильтрация по типу локации', { + 'fields': ('filter_by_location_type', 'location_types'), + 'description': 'Если включено, в команду будут добавлены только сотрудники с выбранным типом локации (Аптека/Склад/Офис). При совместном включении с фильтром по отделам условия объединяются по И.' + }), ('Дополнительно', { 'fields': ('notes',), 'classes': ('collapse',) @@ -173,6 +202,7 @@ class MattermostChannelAdmin(ModelAdmin): @admin.register(ChannelSyncSettings) class ChannelSyncSettingsAdmin(ModelAdmin): """Админка для настроек синхронизации каналов""" + form = ChannelSyncSettingsForm list_display = ( 'channel_display', 'team_display', @@ -197,6 +227,10 @@ class ChannelSyncSettingsAdmin(ModelAdmin): 'fields': ('filter_by_department', 'departments'), 'description': 'Если включено, в канал будут добавлены только пользователи из выбранных отделов' }), + ('Фильтрация по типу локации', { + 'fields': ('filter_by_location_type', 'location_types'), + 'description': 'Если включено, в канал будут добавлены только сотрудники с выбранным типом локации (Аптека/Склад/Офис). При совместном включении с фильтром по отделам условия объединяются по И.' + }), ('Дополнительно', { 'fields': ('notes',), 'classes': ('collapse',) diff --git a/apps/mattermost_connector/migrations/0004_channelsyncsettings_filter_by_location_type_and_more.py b/apps/mattermost_connector/migrations/0004_channelsyncsettings_filter_by_location_type_and_more.py new file mode 100644 index 0000000..d45e0d1 --- /dev/null +++ b/apps/mattermost_connector/migrations/0004_channelsyncsettings_filter_by_location_type_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 6.0.3 on 2026-09-16 13:16 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mattermost_connector', '0003_mattermostbot'), + ] + + operations = [ + migrations.AddField( + model_name='channelsyncsettings', + name='filter_by_location_type', + field=models.BooleanField(default=False, help_text='Добавлять только пользователей с выбранным типом локации (Аптека/Склад/Офис)', verbose_name='Фильтровать по типу локации'), + ), + migrations.AddField( + model_name='channelsyncsettings', + name='location_types', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('pharmacy', 'Аптека'), ('warehouse', 'Склад'), ('office', 'Офис')], max_length=20), blank=True, default=list, help_text='Пользователи с этими типами локации будут добавлены в канал', verbose_name='Типы локаций'), + ), + migrations.AddField( + model_name='teamsyncsettings', + name='filter_by_location_type', + field=models.BooleanField(default=False, help_text='Добавлять только пользователей с выбранным типом локации (Аптека/Склад/Офис)', verbose_name='Фильтровать по типу локации'), + ), + migrations.AddField( + model_name='teamsyncsettings', + name='location_types', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('pharmacy', 'Аптека'), ('warehouse', 'Склад'), ('office', 'Офис')], max_length=20), blank=True, default=list, help_text='Пользователи с этими типами локации будут добавлены в команду', verbose_name='Типы локаций'), + ), + ] diff --git a/apps/mattermost_connector/models.py b/apps/mattermost_connector/models.py index c5e5fb5..b098b46 100755 --- a/apps/mattermost_connector/models.py +++ b/apps/mattermost_connector/models.py @@ -1,10 +1,18 @@ # models.py +from django.contrib.postgres.fields import ArrayField from django.db import models from django.contrib.auth import get_user_model from django.utils import timezone from core.models.fields import EncryptedCharField +# Типы локаций сотрудника (Employee.LOCATION_TYPES) — для фильтра синхронизации. +LOCATION_TYPE_CHOICES = [ + ('pharmacy', 'Аптека'), + ('warehouse', 'Склад'), + ('office', 'Офис'), +] + User = get_user_model() @@ -180,6 +188,19 @@ class TeamSyncSettings(models.Model): help_text='Отделы, пользователи которых будут добавлены в команду' ) + filter_by_location_type = models.BooleanField( + default=False, + verbose_name='Фильтровать по типу локации', + help_text='Добавлять только пользователей с выбранным типом локации (Аптека/Склад/Офис)' + ) + + location_types = ArrayField( + models.CharField(max_length=20, choices=LOCATION_TYPE_CHOICES), + blank=True, default=list, + verbose_name='Типы локаций', + help_text='Пользователи с этими типами локации будут добавлены в команду' + ) + is_active = models.BooleanField( default=True, verbose_name='Активна', @@ -248,6 +269,19 @@ class ChannelSyncSettings(models.Model): help_text='Отделы, пользователи которых будут добавлены в канал' ) + filter_by_location_type = models.BooleanField( + default=False, + verbose_name='Фильтровать по типу локации', + help_text='Добавлять только пользователей с выбранным типом локации (Аптека/Склад/Офис)' + ) + + location_types = ArrayField( + models.CharField(max_length=20, choices=LOCATION_TYPE_CHOICES), + blank=True, default=list, + verbose_name='Типы локаций', + help_text='Пользователи с этими типами локации будут добавлены в канал' + ) + is_active = models.BooleanField( default=True, verbose_name='Активна', diff --git a/apps/mattermost_connector/services/sync_service.py b/apps/mattermost_connector/services/sync_service.py index f8a3afe..ff5d369 100755 --- a/apps/mattermost_connector/services/sync_service.py +++ b/apps/mattermost_connector/services/sync_service.py @@ -352,13 +352,18 @@ def add_user_to_teams(employee: Employee) -> bool: should_add = False if setting.auto_add_users: + # Фильтры комбинируются по AND: если включены оба (отдел + тип + # локации), пользователь должен подходить под оба условия. + should_add = True if setting.filter_by_department: - # Проверяем, что пользователь из нужного отдела - if employee.department and setting.departments.filter(id=employee.department.id).exists(): - should_add = True - else: - # Добавляем всех пользователей - should_add = True + should_add = should_add and bool( + employee.department_id + and setting.departments.filter(id=employee.department_id).exists() + ) + if setting.filter_by_location_type: + should_add = should_add and ( + employee.user_location_type in (setting.location_types or []) + ) if should_add: # Проверяем, не добавлен ли уже пользователь в эту команду @@ -433,13 +438,17 @@ def add_user_to_channels(employee: Employee) -> bool: should_add = False if setting.auto_add_users: + # Фильтры комбинируются по AND (отдел + тип локации). + should_add = True if setting.filter_by_department: - # Проверяем, что пользователь из нужного отдела - if employee.department and setting.departments.filter(id=employee.department.id).exists(): - should_add = True - else: - # Добавляем всех пользователей - should_add = True + should_add = should_add and bool( + employee.department_id + and setting.departments.filter(id=employee.department_id).exists() + ) + if setting.filter_by_location_type: + should_add = should_add and ( + employee.user_location_type in (setting.location_types or []) + ) if should_add: # Проверяем, не добавлен ли уже пользователь в этот канал