from core.test_utils import grant_all_locations from unittest import mock from django.core.exceptions import ValidationError from django.db import IntegrityError, connection, transaction from django.test import TestCase from core.models import Directory, DirectoryType from apps.locations.models import Pharmacy class CarbonioEmailTests(TestCase): """Генерация адреса Carbonio (<префикс организации><номер>@домен), ручной override и проверка уникальности.""" @classmethod def setUpTestData(cls): org_type = DirectoryType.objects.create(code='organization', name='Организация') region_type = DirectoryType.objects.create(code='region', name='Регион') cls.region = Directory.objects.create(directory_type=region_type, code='od', name='Одесса') cls.aks = Directory.objects.create( directory_type=org_type, code='akc', name='АКС', extra_data={'mail_prefix': 'a'}) cls.tira = Directory.objects.create( directory_type=org_type, code='tira', name='Тира', extra_data={'mail_prefix': 't'}) cls.noprefix = Directory.objects.create( directory_type=org_type, code='xx', name='Без префикса', extra_data={}) def _pharmacy(self, org, number, **kw): return Pharmacy.objects.create( organization=org, location_region=self.region, pharmacy_number=number, location_type='pharmacy', **kw) def test_build_email_format(self): self.assertEqual(self._pharmacy(self.aks, 5).build_carbonio_email(), 'a5@aks-apteka.com.ua') def test_build_email_uses_org_prefix(self): self.assertEqual(self._pharmacy(self.tira, 5).build_carbonio_email(), 't5@aks-apteka.com.ua') def test_build_email_empty_without_prefix(self): self.assertEqual(self._pharmacy(self.noprefix, 5).build_carbonio_email(), '') def test_autogenerate_on_save_when_enabled(self): p = self._pharmacy(self.aks, 7, carbonio_sync_enabled=True) self.assertEqual(p.carbonio_email, 'a7@aks-apteka.com.ua') def test_no_autogenerate_when_disabled(self): self.assertEqual(self._pharmacy(self.aks, 8).carbonio_email, '') def test_manual_override_respected(self): p = self._pharmacy(self.aks, 9, carbonio_sync_enabled=True, carbonio_email='custom@aks-apteka.com.ua') self.assertEqual(p.carbonio_email, 'custom@aks-apteka.com.ua') def test_uniqueness_rejected_in_clean(self): self._pharmacy(self.aks, 10, carbonio_sync_enabled=True) # a10@ p2 = self._pharmacy(self.tira, 11) p2.carbonio_email = 'a10@aks-apteka.com.ua' with self.assertRaises(ValidationError): p2.clean() def test_uniqueness_enforced_by_db(self): self._pharmacy(self.aks, 12, carbonio_sync_enabled=True) # a12@ with self.assertRaises(IntegrityError): with transaction.atomic(): self._pharmacy(self.tira, 13, carbonio_email='a12@aks-apteka.com.ua') def test_autogenerate_skips_on_collision(self): # ручной a15@ занят другой аптекой -> автоген для АКС №15 оставляет пусто self._pharmacy(self.tira, 99, carbonio_sync_enabled=True, carbonio_email='a15@aks-apteka.com.ua') p = self._pharmacy(self.aks, 15, carbonio_sync_enabled=True) self.assertEqual(p.carbonio_email, '') def test_blank_emails_do_not_clash(self): # несколько аптек без адреса — частичный уникальный индекс их не блокирует self._pharmacy(self.aks, 16) self._pharmacy(self.tira, 17) self.assertEqual(Pharmacy.objects.filter(carbonio_email='').count(), 2) class CarbonioPasswordSyncTests(TestCase): """sync_pharmacy_to_carbonio с замоканным CarbonioService: пароль ящика сохраняется у нас зашифрованным и совпадает с отданным в Carbonio.""" @classmethod def setUpTestData(cls): org_type = DirectoryType.objects.create(code='organization', name='Организация') region_type = DirectoryType.objects.create(code='region', name='Регион') cls.region = Directory.objects.create(directory_type=region_type, code='od', name='Одесса') cls.aks = Directory.objects.create( directory_type=org_type, code='akc', name='АКС', extra_data={'mail_prefix': 'a'}) def _pharmacy(self, number, **kw): return Pharmacy.objects.create( organization=self.aks, location_region=self.region, pharmacy_number=number, location_type='pharmacy', **kw) def test_create_stores_encrypted_password(self): from apps.mail_connector.services.sync_service import sync_pharmacy_to_carbonio p = self._pharmacy(20) # синхронизация выключена, адрес пуст fake = mock.Mock() fake.get_account.return_value = None # аккаунта ещё нет → create fake.create_account.return_value = 'acc-123' with mock.patch( 'apps.mail_connector.services.sync_service.CarbonioService', return_value=fake): self.assertTrue(sync_pharmacy_to_carbonio(p)) p.refresh_from_db() self.assertEqual(p.carbonio_email, 'a20@aks-apteka.com.ua') self.assertEqual(p.carbonio_account_id, 'acc-123') self.assertEqual(p.carbonio_sync_status, 'synced') self.assertTrue(p.carbonio_password) # тот же пароль ушёл в Carbonio, что и сохранён у нас self.assertEqual(fake.create_account.call_args.args[1], p.carbonio_password) # в БД пароль лежит зашифрованным table, pk = Pharmacy._meta.db_table, Pharmacy._meta.pk.column with connection.cursor() as cur: cur.execute(f'SELECT carbonio_password FROM {table} WHERE {pk}=%s', [p.pk]) raw = cur.fetchone()[0] self.assertTrue(raw.startswith('gAAAAA')) def test_update_path_does_not_touch_password(self): from apps.mail_connector.services.sync_service import sync_pharmacy_to_carbonio p = self._pharmacy(21, carbonio_email='a21@aks-apteka.com.ua') fake = mock.Mock() fake.get_account.return_value = {'id': 'acc-existing'} # аккаунт уже есть → update with mock.patch( 'apps.mail_connector.services.sync_service.CarbonioService', return_value=fake): self.assertTrue(sync_pharmacy_to_carbonio(p)) p.refresh_from_db() self.assertEqual(p.carbonio_account_id, 'acc-existing') self.assertEqual(p.carbonio_password, '') # на update пароль не трогаем fake.create_account.assert_not_called() fake.set_account_status.assert_called_once_with('acc-existing', 'active') class DislocationColumnsTests(TestCase): """Whitelist, резолв пресета default и сохранение preference пользователя.""" @classmethod def setUpTestData(cls): from datetime import date from django.contrib.auth import get_user_model from apps.locations.dislocation_columns import HR_COLUMNS, OPS_COLUMNS, STANDARD_COLUMNS from apps.locations.models import DislocationColumnPreset Employee = get_user_model() org_type = DirectoryType.objects.create(code='organization', name='Организация') region_type = DirectoryType.objects.create(code='region', name='Регион') cls.region = Directory.objects.create(directory_type=region_type, code='kv', name='Киев') cls.org = Directory.objects.create(directory_type=org_type, code='org', name='ТестОрг') cls.user = Employee.objects.create( username='dislo.cols', email='dislo.cols@company.local', first_name='Коля', last_name='Колонки', hire_date=date(2026, 1, 1), user_location_type='office', mattermost_sync_enabled=False, ad_sync_enabled=False, ) # Любая группа = доступ к открытым разделам (в т.ч. /dislocation/). from django.contrib.auth.models import Group cls.user.groups.add(Group.objects.get_or_create(name='Сотрудники (тест)')[0]) DislocationColumnPreset.objects.update_or_create( code='standard', defaults={ 'name': 'Стандарт', 'columns': list(STANDARD_COLUMNS), 'is_default': True, }, ) DislocationColumnPreset.objects.update_or_create( code='hr', defaults={'name': 'HR', 'columns': list(HR_COLUMNS), 'is_default': False}, ) DislocationColumnPreset.objects.update_or_create( code='ops', defaults={'name': 'Ops', 'columns': list(OPS_COLUMNS), 'is_default': False}, ) Pharmacy.objects.create( organization=cls.org, location_region=cls.region, pharmacy_number=101, location_type='pharmacy', street='Тестовая', house_number='1', ) def test_normalize_whitelist_and_locked_pharmacy(self): from apps.locations.dislocation_columns import normalize_columns self.assertEqual( normalize_columns(['email', 'pharmacy', 'unknown', 'email', 'region']), ['email', 'pharmacy', 'region'], ) self.assertEqual( normalize_columns(['organization', 'pharmacy', 'phone']), ['organization', 'pharmacy', 'phone'], ) self.assertEqual(normalize_columns([]), ['pharmacy']) self.assertEqual(normalize_columns(['secret', 'password']), ['pharmacy']) # pharmacy обязательна, но если её нет в списке — добавляется первой self.assertEqual( normalize_columns(['region', 'phone']), ['pharmacy', 'region', 'phone'], ) def test_resolve_uses_default_preset(self): from apps.locations.dislocation_columns import STANDARD_COLUMNS, resolve_columns_for_user self.user.dislocation_columns = None self.user.save(update_fields=['dislocation_columns']) self.assertEqual(resolve_columns_for_user(self.user), list(STANDARD_COLUMNS)) def test_resolve_uses_custom_preference(self): from apps.locations.dislocation_columns import resolve_columns_for_user self.user.dislocation_columns = ['region', 'phone', 'bogus'] self.user.save(update_fields=['dislocation_columns']) self.assertEqual( resolve_columns_for_user(self.user), ['pharmacy', 'region', 'phone'], ) def test_post_saves_columns_preference(self): self.client.force_login(self.user) session = self.client.session session['2fa_verified'] = True session.save() resp = self.client.post( '/dislocation/columns/', {'columns': ['organization', 'pharmacy', 'staff', 'region', 'email', 'evil']}, HTTP_HX_REQUEST='true', ) self.assertEqual(resp.status_code, 200) self.user.refresh_from_db() self.assertEqual( self.user.dislocation_columns, ['organization', 'pharmacy', 'staff', 'region', 'email'], ) self.assertContains(resp, "disloSortBy('organization'") self.assertContains(resp, "disloSortBy('region'") self.assertContains(resp, "disloSortBy('email'") self.assertNotContains(resp, "disloSortBy('phone'") def test_post_preset_code_applies_hr(self): from apps.locations.dislocation_columns import HR_COLUMNS self.client.force_login(self.user) session = self.client.session session['2fa_verified'] = True session.save() resp = self.client.post( '/dislocation/columns/', {'preset_code': 'hr'}, HTTP_HX_REQUEST='true', ) self.assertEqual(resp.status_code, 200) self.user.refresh_from_db() self.assertEqual(self.user.dislocation_columns, list(HR_COLUMNS)) self.assertContains(resp, "disloSortBy('curator'") self.assertContains(resp, "disloSortBy('manager'") self.assertNotContains(resp, "disloSortBy('phone'") def test_staff_can_create_preset(self): from apps.locations.models import DislocationColumnPreset from core.test_utils import login_2fa self.user.is_staff = True self.user.save(update_fields=['is_staff']) login_2fa(self.client, self.user) resp = self.client.post( '/dislocation/column-presets/', { 'action': 'create', 'name': 'IT набор', 'code': 'it', 'columns': ['pharmacy', 'mikrotik', 'provider', 'gpon'], 'is_default': '', }, HTTP_HX_REQUEST='true', ) self.assertEqual(resp.status_code, 200) preset = DislocationColumnPreset.objects.get(code='it') self.assertEqual(preset.name, 'IT набор') self.assertEqual(preset.columns, ['pharmacy', 'mikrotik', 'provider', 'gpon']) def test_non_staff_cannot_create_preset(self): self.client.force_login(self.user) session = self.client.session session['2fa_verified'] = True session.save() resp = self.client.post( '/dislocation/column-presets/', { 'action': 'create', 'name': 'Hack', 'code': 'hack', 'columns': ['pharmacy', 'phone'], }, HTTP_HX_REQUEST='true', ) self.assertEqual(resp.status_code, 403) class PharmacyCardVisibilityTests(TestCase): """Матрица видимости вкладок карточки аптеки (синглтон в админке).""" @classmethod def setUpTestData(cls): from datetime import date from django.contrib.auth.models import Group from apps.employees.models import Employee from apps.locations.models import PharmacyCardVisibility org_type = DirectoryType.objects.create(code='organization', name='Организация') region_type = DirectoryType.objects.create(code='region', name='Регион') region = Directory.objects.create(directory_type=region_type, code='vis', name='Вид') org = Directory.objects.create(directory_type=org_type, code='visorg', name='ВизОрг') cls.pharmacy = Pharmacy.objects.create( organization=org, location_region=region, pharmacy_number=42, location_type='pharmacy', street='Вид', house_number='1', ) other = Pharmacy.objects.create( organization=org, location_region=region, pharmacy_number=43, location_type='pharmacy', street='Інша', house_number='2', ) common = dict( hire_date=date(2024, 1, 1), mattermost_sync_enabled=False, ad_sync_enabled=False, ) cls.own = Employee.objects.create_user( username='vis.own', user_location_type='pharmacy', user_pharmacy=cls.pharmacy, **common, ) cls.stranger = Employee.objects.create_user( username='vis.stranger', user_location_type='pharmacy', user_pharmacy=other, **common, ) cls.staff = Employee.objects.create_user( username='vis.staff', is_staff=True, totp_enabled=True, **common, ) cls.superuser = Employee.objects.create_user( username='vis.super', is_staff=True, is_superuser=True, totp_enabled=True, **common, ) cls.it_user = Employee.objects.create_user(username='vis.it', **common) it_group, _ = Group.objects.get_or_create(name='IT') cls.it_user.groups.add(it_group) grant_all_locations(cls.it_user) grant_all_locations(cls.staff) PharmacyCardVisibility.load() def _set_level(self, section, level): from apps.locations.models import PharmacyCardVisibility policy = PharmacyCardVisibility.load() attr = f'show_{section}' if section != 'edit' else 'edit_pharmacy' setattr(policy, attr, level) policy.save() def test_own_or_staff_default(self): from apps.locations.models import ( PharmacyCardVisibility, user_can_view_pharmacy_section, ) level = PharmacyCardVisibility.Level.OWN_OR_STAFF self.assertTrue(user_can_view_pharmacy_section(self.own, self.pharmacy, level)) self.assertFalse(user_can_view_pharmacy_section(self.stranger, self.pharmacy, level)) self.assertTrue(user_can_view_pharmacy_section(self.staff, self.pharmacy, level)) def test_edit_pharmacy_default_staff_only(self): from apps.locations.models import pharmacy_card_flags, user_can_edit_pharmacy self.assertFalse(user_can_edit_pharmacy(self.own, self.pharmacy)) self.assertTrue(user_can_edit_pharmacy(self.staff, self.pharmacy)) flags = pharmacy_card_flags(self.own, self.pharmacy) self.assertFalse(flags['can_edit']) flags_staff = pharmacy_card_flags(self.staff, self.pharmacy) self.assertTrue(flags_staff['can_edit']) def test_edit_pharmacy_own_or_staff_allows_own(self): from apps.locations.models import PharmacyCardVisibility, user_can_edit_pharmacy self._set_level('edit', PharmacyCardVisibility.Level.OWN_OR_STAFF) self.assertTrue(user_can_edit_pharmacy(self.own, self.pharmacy)) self.assertFalse(user_can_edit_pharmacy(self.stranger, self.pharmacy)) self.assertTrue(user_can_edit_pharmacy(self.staff, self.pharmacy)) def test_portal_edit_forbidden_without_permission(self): self.client.force_login(self.own) session = self.client.session session['2fa_verified'] = True session.save() resp = self.client.post( f'/pharmacies/{self.pharmacy.pk}/edit/', {'working_hours': '09:00-21:00'}, HTTP_HX_REQUEST='true', ) self.assertEqual(resp.status_code, 403) def test_portal_edit_saves_when_allowed(self): from apps.locations.models import PharmacyCardVisibility self._set_level('edit', PharmacyCardVisibility.Level.OWN_OR_STAFF) self.client.force_login(self.own) session = self.client.session session['2fa_verified'] = True session.save() resp = self.client.post( f'/pharmacies/{self.pharmacy.pk}/edit/', { 'organization': self.pharmacy.organization_id, 'pharmacy_number': self.pharmacy.pharmacy_number, 'status': 'open', 'category': 'A', 'vat_category': '', 'working_hours': '09:00-21:00', 'location_region': self.pharmacy.location_region_id, 'street': 'Нова', 'house_number': '10', 'building': '', 'apartment': '', 'location': '46.4825,30.7233', 'phone': '', 'license_number': '', 'license_expiry_date': '', 'head_pharmacist': '', 'responsible_manager': '', 'curator': '', 'pharmacists_count': 2, 'comments': 'Оновлено з порталу', }, HTTP_HX_REQUEST='true', ) self.assertEqual(resp.status_code, 200) self.pharmacy.refresh_from_db() self.assertEqual(self.pharmacy.working_hours, '09:00-21:00') self.assertEqual(self.pharmacy.street, 'Нова') self.assertEqual(self.pharmacy.comments, 'Оновлено з порталу') self.assertContains(resp, 'Изменения сохранены') def test_detail_shows_edit_button_for_staff(self): self.client.force_login(self.staff) session = self.client.session session['2fa_verified'] = True session.save() resp = self.client.get(f'/pharmacies/{self.pharmacy.pk}/', HTTP_HX_REQUEST='true') self.assertEqual(resp.status_code, 200) self.assertTrue(resp.context['can_edit']) self.assertContains(resp, 'edit=1') self.assertNotContains(resp, 'Редактировать в админке') def test_everyone_and_hidden(self): from apps.locations.models import ( PharmacyCardVisibility, user_can_view_pharmacy_section, ) everyone = PharmacyCardVisibility.Level.EVERYONE hidden = PharmacyCardVisibility.Level.HIDDEN self.assertFalse(user_can_view_pharmacy_section(self.stranger, self.pharmacy, everyone)) self.assertFalse(user_can_view_pharmacy_section(self.staff, self.pharmacy, hidden)) self.assertTrue(user_can_view_pharmacy_section(self.superuser, self.pharmacy, hidden)) def test_staff_and_it_levels(self): from apps.locations.models import ( PharmacyCardVisibility, user_can_view_pharmacy_section, ) staff_lvl = PharmacyCardVisibility.Level.STAFF it_lvl = PharmacyCardVisibility.Level.IT self.assertFalse(user_can_view_pharmacy_section(self.own, self.pharmacy, staff_lvl)) self.assertTrue(user_can_view_pharmacy_section(self.staff, self.pharmacy, staff_lvl)) self.assertTrue(user_can_view_pharmacy_section(self.it_user, self.pharmacy, it_lvl)) self.assertFalse(user_can_view_pharmacy_section(self.own, self.pharmacy, it_lvl)) self.assertFalse(user_can_view_pharmacy_section(self.staff, self.pharmacy, it_lvl)) def test_pharmacy_card_flags_respect_per_section(self): from apps.locations.models import ( PharmacyCardVisibility, pharmacy_card_flags, ) self._set_level('rro', PharmacyCardVisibility.Level.EVERYONE) self._set_level('equipment', PharmacyCardVisibility.Level.HIDDEN) flags = pharmacy_card_flags(self.stranger, self.pharmacy) self.assertFalse(flags['can_view_rro']) self.assertFalse(flags['can_view_equipment']) def test_detail_view_hides_tab_when_section_staff_only(self): from apps.locations.models import PharmacyCardVisibility from apps.rro.models import CashRegister from core.models import Directory, DirectoryType status_type, _ = DirectoryType.objects.get_or_create( code='rro_status', defaults={'name': 'Статус РРО', 'hierarchy_type': 'strict'}, ) status, _ = Directory.objects.get_or_create( directory_type=status_type, code='active', defaults={'name': 'Активный', 'is_active': True}, ) CashRegister.objects.create( pharmacy=self.pharmacy, kind='prro', fiscal_number='FN-VIS-1', status=status, is_primary=True, ) self._set_level('rro', PharmacyCardVisibility.Level.STAFF) self.client.force_login(self.own) session = self.client.session session['2fa_verified'] = True session.save() resp = self.client.get(f'/pharmacies/{self.pharmacy.pk}/', HTTP_HX_REQUEST='true') self.assertEqual(resp.status_code, 200) self.assertFalse(resp.context['can_view_rro']) self.assertNotContains(resp, 'FN-VIS-1') self.assertNotContains(resp, 'ph-panel-rro') self.client.force_login(self.staff) session = self.client.session session['2fa_verified'] = True session.save() resp = self.client.get(f'/pharmacies/{self.pharmacy.pk}/', HTTP_HX_REQUEST='true') self.assertEqual(resp.status_code, 200) self.assertTrue(resp.context.get('can_view_rro')) self.assertContains(resp, 'FN-VIS-1') self.assertContains(resp, 'ph-panel-rro') class PharmacyHistoryTests(TestCase): """PharmacyHistory заполняется при изменении аптеки, а не при создании.""" @classmethod def setUpTestData(cls): org_type = DirectoryType.objects.create(code='organization', name='Организация') region_type = DirectoryType.objects.create(code='region', name='Регион') cls.region = Directory.objects.create(directory_type=region_type, code='od', name='Одесса') cls.aks = Directory.objects.create( directory_type=org_type, code='akc', name='АКС', extra_data={'mail_prefix': 'a'}) def _pharmacy(self, number=1, **kw): return Pharmacy.objects.create( organization=self.aks, location_region=self.region, pharmacy_number=number, location_type='pharmacy', **kw) def test_create_does_not_write_history(self): from apps.locations.models import PharmacyHistory p = self._pharmacy(30) self.assertEqual(PharmacyHistory.objects.filter(pharmacy=p).count(), 0) def test_field_change_writes_history(self): from apps.locations.models import PharmacyHistory p = self._pharmacy(31, phone='+380501111111') p.phone = '+380502222222' p.save() rows = list(PharmacyHistory.objects.filter(pharmacy=p)) self.assertEqual(len(rows), 1) self.assertEqual(rows[0].changed_field, 'phone') self.assertEqual(rows[0].old_value, '+380501111111') self.assertEqual(rows[0].new_value, '+380502222222') self.assertIsNone(rows[0].changed_by) def test_unchanged_save_writes_nothing(self): from apps.locations.models import PharmacyHistory p = self._pharmacy(32) p.save() self.assertEqual(PharmacyHistory.objects.filter(pharmacy=p).count(), 0) def test_history_user_is_recorded(self): from apps.locations.models import PharmacyHistory from apps.employees.tests import make_employee user = make_employee('hist-user') p = self._pharmacy(33, working_hours='08:00-20:00') p.working_hours = '09:00-21:00' p._history_user = user p.save() row = PharmacyHistory.objects.get(pharmacy=p, changed_field='working_hours') self.assertEqual(row.changed_by, user) def test_untracked_update_fields_skip_history(self): from apps.locations.models import PharmacyHistory p = self._pharmacy(34, carbonio_sync_status='pending') p.carbonio_sync_status = 'synced' p.save(update_fields=['carbonio_sync_status']) self.assertEqual(PharmacyHistory.objects.filter(pharmacy=p).count(), 0) class LocationSortKeyTests(TestCase): """Селекты IT: офис сверху, аптеки по коду А1…А70, не по адресу.""" def test_pharmacies_sorted_by_object_code(self): from apps.locations.models.base import Location, location_sort_key org_type, _ = DirectoryType.objects.get_or_create( code='organization', defaults={'name': 'Организация'}, ) region_type, _ = DirectoryType.objects.get_or_create( code='region', defaults={'name': 'Регион'}, ) region = Directory.objects.create(directory_type=region_type, code='od-sort', name='Одесса-sort') org_a = Directory.objects.create( directory_type=org_type, code='aks-sort', name='АКС', extra_data={'mail_prefix': 'a'}, ) org_p = Directory.objects.create( directory_type=org_type, code='pent-sort', name='Пентальфа', extra_data={'mail_prefix': 'p'}, ) office = Location.objects.create( location_type='office', location_region=region, street='Канатная', house_number='78', is_active=True, ) p68 = Pharmacy.objects.create( organization=org_a, location_region=region, pharmacy_number=68, location_type='pharmacy', street='Танцюри', house_number='16', is_active=True, ) p3 = Pharmacy.objects.create( organization=org_a, location_region=region, pharmacy_number=3, location_type='pharmacy', street='Ясная', house_number='1', is_active=True, ) p1 = Pharmacy.objects.create( organization=org_p, location_region=region, pharmacy_number=1, location_type='pharmacy', street='Академическая', house_number='2', is_active=True, ) ordered = sorted([p68, office, p1, p3], key=location_sort_key) self.assertEqual(ordered[0].pk, office.pk) self.assertEqual( [location_sort_key(x)[1:3] for x in ordered[1:]], [(0, 3), (0, 68), (1, 1)], # А3, А68, П1 ) def test_closed_pharmacy_label_and_it_locations(self): from apps.it_manager.views import _it_locations_sorted from apps.locations.models.base import Location from core.test_utils import grant_all_locations from apps.employees.tests import make_employee org_type, _ = DirectoryType.objects.get_or_create( code='organization', defaults={'name': 'Организация'}, ) region_type, _ = DirectoryType.objects.get_or_create( code='region', defaults={'name': 'Регион'}, ) region = Directory.objects.create( directory_type=region_type, code='od-closed', name='Одесса-closed', ) org = Directory.objects.create( directory_type=org_type, code='aks-closed', name='АКС', extra_data={'mail_prefix': 'a'}, ) closed = Pharmacy.objects.create( organization=org, location_region=region, pharmacy_number=99, location_type='pharmacy', street='Закрытая', house_number='1', status='closed', ) self.assertFalse(closed.is_active) # В селектах IT — Location.__str__ (не Pharmacy.__str__) label = str(Location.objects.get(pk=closed.pk)) self.assertIn('А99', label) self.assertIn('Закрыта', label) user = make_employee('it-loc-closed') grant_all_locations(user) ids = {loc.pk for loc in _it_locations_sorted(user)} self.assertIn(closed.pk, ids) # Неактивный офис не попадает dead_office = Location.objects.create( location_type='office', location_region=region, street='Старый', house_number='2', is_active=False, ) self.assertNotIn(dead_office.pk, ids) def test_vendor_location_in_select_and_report_code(self): from apps.it_manager.views import _it_locations_sorted from apps.locations.models.base import Location from apps.reports.it_monthly.builder import location_object_code from core.test_utils import grant_all_locations from apps.employees.tests import make_employee region_type, _ = DirectoryType.objects.get_or_create( code='region', defaults={'name': 'Регион'}, ) region = Directory.objects.create( directory_type=region_type, code='od-vendor', name='Одесса-vendor', ) vendor = Location.objects.create( location_type='vendor', name='Вайтер Юг', location_region=region, is_active=True, ) self.assertEqual(str(vendor), 'Вайтер Юг') self.assertEqual(location_object_code(vendor), 'Вайтер Юг') user = make_employee('it-loc-vendor') grant_all_locations(user) ids = {loc.pk for loc in _it_locations_sorted(user)} self.assertIn(vendor.pk, ids) class DislocationVendorsTabTests(TestCase): """Вкладка «Сервисы / поставщики» на /dislocation/.""" @classmethod def setUpTestData(cls): from django.contrib.auth.models import Group from apps.employees.tests import make_employee from apps.locations.models.base import Location region_type, _ = DirectoryType.objects.get_or_create( code='region', defaults={'name': 'Регион'}, ) cls.region = Directory.objects.create( directory_type=region_type, code='od-dislo-v', name='Одесса-dislo-v', ) cls.vendor = Location.objects.create( location_type='vendor', name='ТестСервис', location_region=cls.region, is_active=True, ) cls.viewer = make_employee('dislo-vend-viewer', user_location_type='office') dept_type, _ = DirectoryType.objects.get_or_create( code='department', defaults={'name': 'Отдел'}, ) dept = Directory.objects.create(directory_type=dept_type, code='it-dv', name='IT-dv') cls.viewer.department = dept cls.viewer.save(update_fields=['department']) cls.viewer.groups.add(Group.objects.get_or_create(name='Сотрудники (тест)')[0]) cls.it_user = make_employee('dislo-vend-it', user_location_type='office') cls.it_user.department = dept cls.it_user.save(update_fields=['department']) cls.it_user.groups.add(Group.objects.get_or_create(name='IT')[0]) def test_tab_lists_vendors(self): from django.test import Client from core.test_utils import login_2fa client = Client() login_2fa(client, self.viewer) r = client.get('/dislocation/', {'tab': 'vendors'}, HTTP_HX_REQUEST='true') self.assertEqual(r.status_code, 200) self.assertContains(r, 'Сервисы / поставщики') self.assertContains(r, 'ТестСервис') self.assertNotContains(r, 'Добавить') def test_it_can_create_vendor(self): from django.test import Client from apps.locations.models.base import Location from core.test_utils import login_2fa client = Client() login_2fa(client, self.it_user) r = client.post('/dislocation/vendors/create/', { 'name': 'Новый Поставщик', 'location_region': str(self.region.pk), 'is_active': 'on', }, HTTP_HX_REQUEST='true') self.assertEqual(r.status_code, 200) self.assertTrue( Location.objects.filter(location_type='vendor', name='Новый Поставщик').exists() ) self.assertContains(r, 'Новый Поставщик') def test_viewer_cannot_create_vendor(self): from django.test import Client from core.test_utils import login_2fa client = Client() login_2fa(client, self.viewer) r = client.post('/dislocation/vendors/create/', { 'name': 'Запрещённый', 'location_region': str(self.region.pk), }, HTTP_HX_REQUEST='true') self.assertEqual(r.status_code, 403) def test_it_can_update_vendor(self): from django.test import Client from core.test_utils import login_2fa client = Client() login_2fa(client, self.it_user) r = client.post(f'/dislocation/vendors/{self.vendor.pk}/update/', { 'name': 'ТестСервис Обновлён', 'location_region': str(self.region.pk), 'comments': 'ремонт техники', 'is_active': 'on', }, HTTP_HX_REQUEST='true') self.assertEqual(r.status_code, 200) self.vendor.refresh_from_db() self.assertEqual(self.vendor.name, 'ТестСервис Обновлён') self.assertEqual(self.vendor.comments, 'ремонт техники') def test_vendor_detail_card_and_map(self): from django.test import Client from core.test_utils import login_2fa client = Client() login_2fa(client, self.viewer) r = client.get(f'/dislocation/vendors/{self.vendor.pk}/', HTTP_HX_REQUEST='true') self.assertEqual(r.status_code, 200) self.assertContains(r, 'ТестСервис') self.assertContains(r, 'Координаты не указаны') login_2fa(client, self.it_user) r2 = client.post(f'/dislocation/vendors/{self.vendor.pk}/update/', { 'name': 'ТестСервис', 'location_region': str(self.region.pk), 'location': '46.482500,30.723300', 'is_active': 'on', 'from_card': '1', }, HTTP_HX_REQUEST='true') self.assertEqual(r2.status_code, 200) self.vendor.refresh_from_db() # Дефолт поля location — как раз Одесса; has_coordinates False для дефолта. # Ставим недефолтные координаты. self.vendor.location = '46.500000,30.750000' self.vendor.save(update_fields=['location', 'address']) r3 = client.get(f'/dislocation/vendors/{self.vendor.pk}/', HTTP_HX_REQUEST='true') self.assertEqual(r3.status_code, 200) self.assertContains(r3, 'vendor-map') self.assertContains(r3, '46') self.assertContains(r3, '30,75000')