locations: право редактирования аптеки, формы и дислокация

Отдельное право на редактирование карточки аптеки, формы, пресеты колонок
дислокации и правки видимости карточки.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-09 18:03:32 +03:00
parent ac0418083b
commit 897ffc99c9
15 changed files with 1039 additions and 142 deletions
+184 -1
View File
@@ -260,6 +260,48 @@ class DislocationColumnsTests(TestCase):
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):
"""Матрица видимости вкладок карточки аптеки (синглтон в админке)."""
@@ -316,7 +358,8 @@ class PharmacyCardVisibilityTests(TestCase):
from apps.locations.models import PharmacyCardVisibility
policy = PharmacyCardVisibility.load()
setattr(policy, f'show_{section}', level)
attr = f'show_{section}' if section != 'edit' else 'edit_pharmacy'
setattr(policy, attr, level)
policy.save()
def test_own_or_staff_default(self):
@@ -329,6 +372,88 @@ class PharmacyCardVisibilityTests(TestCase):
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,
@@ -401,3 +526,61 @@ class PharmacyCardVisibilityTests(TestCase):
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)