Harden portal access and hiring, add RRO portal CRUD.
Remove the is_staff permission bridge, tighten intern credential handling with one-time PDFs and random seed passwords, and let staff manage cash registers from the portal. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -132,3 +132,180 @@ class CashRegisterPortalTests(TestCase):
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertContains(resp, 'FN-PORTAL-1')
|
||||
self.assertContains(resp, 'ph-panel-rro')
|
||||
|
||||
def test_pharmacy_detail_hides_rro_for_other_pharmacy_user(self):
|
||||
other = Pharmacy.objects.create(
|
||||
organization=self.pharmacy.organization,
|
||||
location_region=self.pharmacy.location_region,
|
||||
pharmacy_number=99, location_type='pharmacy',
|
||||
street='Чужа', house_number='9',
|
||||
)
|
||||
stranger = Employee.objects.create(
|
||||
username='rro.stranger',
|
||||
email='rro.stranger@company.local',
|
||||
first_name='Інша',
|
||||
last_name='Аптека',
|
||||
hire_date=date(2026, 1, 1),
|
||||
user_location_type='pharmacy',
|
||||
user_pharmacy=other,
|
||||
is_staff=False,
|
||||
mattermost_sync_enabled=False,
|
||||
ad_sync_enabled=False,
|
||||
)
|
||||
self.client.force_login(stranger)
|
||||
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.assertNotContains(resp, 'FN-PORTAL-1')
|
||||
self.assertNotContains(resp, 'ph-panel-rro')
|
||||
self.assertFalse(resp.context['can_view_ops'])
|
||||
self.assertEqual(resp.context['staff'], [])
|
||||
self.assertEqual(resp.context['equipment'], [])
|
||||
self.assertEqual(resp.context['internet_contracts'], [])
|
||||
self.assertEqual(resp.context['utility_readings'], [])
|
||||
self.assertNotContains(resp, 'ph-panel-equipment')
|
||||
self.assertNotContains(resp, 'ph-panel-internet')
|
||||
self.assertNotContains(resp, 'ph-panel-utilities')
|
||||
|
||||
|
||||
class CashRegisterCrudTests(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},
|
||||
)
|
||||
cls.status_repair, _ = Directory.objects.get_or_create(
|
||||
directory_type=status_type, code='repair',
|
||||
defaults={'name': 'В ремонте', 'is_active': True},
|
||||
)
|
||||
region = Directory.objects.create(directory_type=region_type, code='od2', name='Одесса2')
|
||||
org = Directory.objects.create(directory_type=org_type, code='org2', name='Орг2')
|
||||
cls.pharmacy = Pharmacy.objects.create(
|
||||
organization=org, location_region=region,
|
||||
pharmacy_number=11, location_type='pharmacy',
|
||||
street='CRUD', house_number='1',
|
||||
)
|
||||
cls.staff = Employee.objects.create(
|
||||
username='rro.crud.staff',
|
||||
email='rro.crud.staff@company.local',
|
||||
first_name='CRUD',
|
||||
last_name='Staff',
|
||||
hire_date=date(2026, 1, 1),
|
||||
user_location_type='office',
|
||||
is_staff=True,
|
||||
mattermost_sync_enabled=False,
|
||||
ad_sync_enabled=False,
|
||||
totp_enabled=True,
|
||||
)
|
||||
cls.regular = Employee.objects.create(
|
||||
username='rro.crud.user',
|
||||
email='rro.crud.user@company.local',
|
||||
first_name='CRUD',
|
||||
last_name='User',
|
||||
hire_date=date(2026, 1, 1),
|
||||
user_location_type='pharmacy',
|
||||
user_pharmacy=cls.pharmacy,
|
||||
is_staff=False,
|
||||
mattermost_sync_enabled=False,
|
||||
ad_sync_enabled=False,
|
||||
)
|
||||
|
||||
def _login(self, user):
|
||||
self.client.force_login(user)
|
||||
session = self.client.session
|
||||
session['2fa_verified'] = True
|
||||
session.save()
|
||||
|
||||
def _payload(self, **extra):
|
||||
data = {
|
||||
'pharmacy': self.pharmacy.pk,
|
||||
'kind': CashRegister.Kind.PRRO,
|
||||
'fiscal_number': 'FN-CRUD-NEW',
|
||||
'status': self.status_active.pk,
|
||||
'model_name': 'Checkbox',
|
||||
'provider': 'checkbox',
|
||||
}
|
||||
data.update(extra)
|
||||
return data
|
||||
|
||||
def test_staff_create(self):
|
||||
self._login(self.staff)
|
||||
resp = self.client.post('/rro/create/', self._payload(), HTTP_HX_REQUEST='true')
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTrue(CashRegister.objects.filter(fiscal_number='FN-CRUD-NEW').exists())
|
||||
self.assertContains(resp, 'Кассовый аппарат добавлен')
|
||||
|
||||
def test_non_staff_create_forbidden(self):
|
||||
self._login(self.regular)
|
||||
resp = self.client.post('/rro/create/', self._payload(fiscal_number='FN-FORBIDDEN'))
|
||||
self.assertEqual(resp.status_code, 403)
|
||||
self.assertFalse(CashRegister.objects.filter(fiscal_number='FN-FORBIDDEN').exists())
|
||||
|
||||
def test_staff_update(self):
|
||||
reg = CashRegister.objects.create(
|
||||
pharmacy=self.pharmacy, kind=CashRegister.Kind.RRO,
|
||||
fiscal_number='FN-UPD', status=self.status_active,
|
||||
)
|
||||
self._login(self.staff)
|
||||
resp = self.client.post(
|
||||
f'/rro/{reg.pk}/update/',
|
||||
self._payload(
|
||||
fiscal_number='FN-UPD-2',
|
||||
kind=CashRegister.Kind.RRO,
|
||||
status=self.status_repair.pk,
|
||||
provider='',
|
||||
),
|
||||
HTTP_HX_REQUEST='true',
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
reg.refresh_from_db()
|
||||
self.assertEqual(reg.fiscal_number, 'FN-UPD-2')
|
||||
self.assertEqual(reg.status_id, self.status_repair.pk)
|
||||
|
||||
def test_duplicate_fiscal_shows_form_error(self):
|
||||
CashRegister.objects.create(
|
||||
pharmacy=self.pharmacy, kind=CashRegister.Kind.PRRO,
|
||||
fiscal_number='FN-DUP', status=self.status_active,
|
||||
)
|
||||
self._login(self.staff)
|
||||
resp = self.client.post(
|
||||
'/rro/create/',
|
||||
self._payload(fiscal_number='FN-DUP'),
|
||||
HTTP_HX_REQUEST='true',
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(CashRegister.objects.filter(fiscal_number='FN-DUP').count(), 1)
|
||||
self.assertTrue(resp.context['rro_form_errors'])
|
||||
|
||||
def test_primary_conflict_shows_form_error(self):
|
||||
CashRegister.objects.create(
|
||||
pharmacy=self.pharmacy, kind=CashRegister.Kind.PRRO,
|
||||
fiscal_number='FN-PRIM-1', status=self.status_active, is_primary=True,
|
||||
)
|
||||
self._login(self.staff)
|
||||
resp = self.client.post(
|
||||
'/rro/create/',
|
||||
self._payload(fiscal_number='FN-PRIM-2', is_primary='1'),
|
||||
HTTP_HX_REQUEST='true',
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertFalse(CashRegister.objects.filter(fiscal_number='FN-PRIM-2').exists())
|
||||
self.assertIn('is_primary', resp.context['rro_form_errors'])
|
||||
|
||||
def test_new_query_opens_create_panel(self):
|
||||
self._login(self.staff)
|
||||
resp = self.client.get(
|
||||
f'/rro/?pharmacy={self.pharmacy.pk}&new=1',
|
||||
HTTP_HX_REQUEST='true',
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTrue(resp.context['show_create_panel'])
|
||||
self.assertEqual(str(resp.context['prefill_pharmacy']), str(self.pharmacy.pk))
|
||||
|
||||
Reference in New Issue
Block a user