эксплуатация: показания счётчиков, энергоактивы и РРО
Конфиг счётчиков и день/ночь-тарифы, учёт энергосистем (генераторы, инверторы), доступ к разделу РРО/ПРРО. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+99
-1
@@ -1,17 +1,41 @@
|
||||
from datetime import date
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.test import TestCase
|
||||
|
||||
from core.models import Directory, DirectoryType
|
||||
from core.models import Directory, DirectoryType, PortalAccess
|
||||
from apps.employees.models import EmployeePermissionOverride
|
||||
from apps.locations.models import Pharmacy
|
||||
from apps.rro.models import CashRegister
|
||||
|
||||
Employee = get_user_model()
|
||||
|
||||
|
||||
def grant_office_access(user):
|
||||
user.groups.add(Group.objects.get(name='Офисный сотрудник'))
|
||||
for attr in ('_perm_cache', '_user_perm_cache', '_group_perm_cache'):
|
||||
if hasattr(user, attr):
|
||||
delattr(user, attr)
|
||||
|
||||
|
||||
def grant_rro_access(user):
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
ct = ContentType.objects.get_for_model(PortalAccess, for_concrete_model=False)
|
||||
perm = Permission.objects.get(content_type=ct, codename='access_rro')
|
||||
EmployeePermissionOverride.objects.get_or_create(
|
||||
employee=user,
|
||||
permission=perm,
|
||||
defaults={'override_type': 'grant'},
|
||||
)
|
||||
for attr in ('_perm_cache', '_user_perm_cache', '_group_perm_cache'):
|
||||
if hasattr(user, attr):
|
||||
delattr(user, attr)
|
||||
|
||||
|
||||
class CashRegisterModelTests(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
@@ -113,6 +137,7 @@ class CashRegisterPortalTests(TestCase):
|
||||
ad_sync_enabled=False,
|
||||
totp_enabled=True,
|
||||
)
|
||||
grant_rro_access(cls.user)
|
||||
|
||||
def _login(self):
|
||||
self.client.force_login(self.user)
|
||||
@@ -170,6 +195,76 @@ class CashRegisterPortalTests(TestCase):
|
||||
self.assertNotContains(resp, 'ph-panel-utilities')
|
||||
|
||||
|
||||
class ManagerRroScopeTests(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
org_type = DirectoryType.objects.create(code='organization', name='Организация')
|
||||
region_type = DirectoryType.objects.create(code='region', name='Регион')
|
||||
status_type, _ = DirectoryType.objects.get_or_create(
|
||||
code='rro_status', defaults={'name': 'Статус РРО/ПРРО', 'hierarchy_type': 'strict'},
|
||||
)
|
||||
cls.status_active, _ = Directory.objects.get_or_create(
|
||||
directory_type=status_type, code='active',
|
||||
defaults={'name': 'Активный', 'is_active': True},
|
||||
)
|
||||
region = Directory.objects.create(directory_type=region_type, code='kv2', name='Киев')
|
||||
org = Directory.objects.create(directory_type=org_type, code='orgm', name='ТестОрг')
|
||||
cls.pharmacy_a = Pharmacy.objects.create(
|
||||
organization=org, location_region=region,
|
||||
pharmacy_number=1, location_type='pharmacy',
|
||||
street='A', house_number='1',
|
||||
)
|
||||
cls.pharmacy_b = Pharmacy.objects.create(
|
||||
organization=org, location_region=region,
|
||||
pharmacy_number=2, location_type='pharmacy',
|
||||
street='B', house_number='2',
|
||||
)
|
||||
CashRegister.objects.create(
|
||||
pharmacy=cls.pharmacy_a, kind=CashRegister.Kind.RRO,
|
||||
fiscal_number='FN-MINE', status=cls.status_active,
|
||||
)
|
||||
CashRegister.objects.create(
|
||||
pharmacy=cls.pharmacy_b, kind=CashRegister.Kind.RRO,
|
||||
fiscal_number='FN-OTHER', status=cls.status_active,
|
||||
)
|
||||
cls.manager = Employee.objects.create(
|
||||
username='rro.manager',
|
||||
email='rro.manager@company.local',
|
||||
first_name='Зав',
|
||||
last_name='Аптекой',
|
||||
hire_date=date(2026, 1, 1),
|
||||
user_location_type='pharmacy',
|
||||
user_pharmacy=cls.pharmacy_a,
|
||||
is_staff=False,
|
||||
mattermost_sync_enabled=False,
|
||||
ad_sync_enabled=False,
|
||||
)
|
||||
cls.manager.groups.add(Group.objects.get(name='Заведующий аптекой'))
|
||||
|
||||
def _login(self):
|
||||
self.client.force_login(self.manager)
|
||||
session = self.client.session
|
||||
session['2fa_verified'] = True
|
||||
session.save()
|
||||
|
||||
def test_manager_sees_only_own_pharmacy(self):
|
||||
self._login()
|
||||
resp = self.client.get('/rro/', HTTP_HX_REQUEST='true')
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertContains(resp, 'FN-MINE')
|
||||
self.assertNotContains(resp, 'FN-OTHER')
|
||||
self.assertTrue(resp.context['rro_pharmacy_scoped'])
|
||||
|
||||
def test_manager_cannot_bypass_with_query_param(self):
|
||||
self._login()
|
||||
resp = self.client.get(
|
||||
f'/rro/?pharmacy={self.pharmacy_b.pk}',
|
||||
HTTP_HX_REQUEST='true',
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertNotContains(resp, 'FN-OTHER')
|
||||
|
||||
|
||||
class CashRegisterCrudTests(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
@@ -217,6 +312,7 @@ class CashRegisterCrudTests(TestCase):
|
||||
mattermost_sync_enabled=False,
|
||||
ad_sync_enabled=False,
|
||||
)
|
||||
grant_rro_access(cls.staff)
|
||||
|
||||
def _login(self, user):
|
||||
self.client.force_login(user)
|
||||
@@ -244,6 +340,8 @@ class CashRegisterCrudTests(TestCase):
|
||||
self.assertContains(resp, 'Кассовый аппарат добавлен')
|
||||
|
||||
def test_non_staff_create_forbidden(self):
|
||||
# Есть просмотр РРО, но без staff — create запрещён (не редирект раздела).
|
||||
grant_rro_access(self.regular)
|
||||
self._login(self.regular)
|
||||
resp = self.client.post('/rro/create/', self._payload(fiscal_number='FN-FORBIDDEN'))
|
||||
self.assertEqual(resp.status_code, 403)
|
||||
|
||||
Reference in New Issue
Block a user