IT-менеджер: каталог моделей оборудования + перевод choices на справочники
- EquipmentModel (каталог: тип+бренд+модель заводятся раз) отделён от Equipment (экземпляр: серийник+локация); миграция сворачивает данные. Массовый ввод серийников, массовая выдача на локации/сотрудников - Enum-choices (тип/статус/приоритет заявок, вид работ, каналы, расходники, договоры) переведены на core.Directory с quick-add из формы - Фронт-страница /it-manager/: вкладки оборудование/заявки/работы/ расходники/провайдеры/обращения; searchable-select локаций - Тесты (10) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
"""Тести IT-порталу: доступ через групу IT, інвентаризація обладнання
|
||||
(зі списанням історії видачі), черга заявок, журнал робіт."""
|
||||
from datetime import date
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from apps.it_manager.models import Equipment, EquipmentAssignment, Ticket, WorkLog
|
||||
from core.models.directories import Directory
|
||||
|
||||
Employee = get_user_model()
|
||||
|
||||
_employee_seq = 0
|
||||
|
||||
|
||||
def _dir_id(type_code, code):
|
||||
"""Знаходить Directory.pk за (тип, код) — довідники засіяні data-міграціями конверсії choices."""
|
||||
return Directory.objects.get(directory_type__code=type_code, code=code).id
|
||||
|
||||
|
||||
def make_employee(**overrides):
|
||||
global _employee_seq
|
||||
_employee_seq += 1
|
||||
defaults = {
|
||||
'username': f'itman.user{_employee_seq}',
|
||||
'email': f'itman.user{_employee_seq}@company.local',
|
||||
'first_name': 'Тест',
|
||||
'last_name': 'Співробітник',
|
||||
'hire_date': date(2026, 1, 1),
|
||||
'user_location_type': 'office',
|
||||
'mattermost_sync_enabled': False,
|
||||
'ad_sync_enabled': False,
|
||||
}
|
||||
defaults.update(overrides)
|
||||
return Employee.objects.create(**defaults)
|
||||
|
||||
|
||||
def make_equipment(**overrides):
|
||||
defaults = {
|
||||
'equipment_type_id': _dir_id('equipment_type', 'laptop'),
|
||||
'model': 'ThinkPad T14',
|
||||
'serial_number': f'SN-{Equipment.objects.count() + 1}',
|
||||
}
|
||||
defaults.update(overrides)
|
||||
return Equipment.objects.create(**defaults)
|
||||
|
||||
|
||||
class ItManagerAccessTests(TestCase):
|
||||
"""Сторінка й усі мутації доступні лише групі IT (або суперюзеру)."""
|
||||
|
||||
def setUp(self):
|
||||
self.outsider = make_employee()
|
||||
self.client = Client()
|
||||
self.client.force_login(self.outsider)
|
||||
|
||||
def test_page_forbidden_for_non_it(self):
|
||||
r = self.client.get('/it-manager/')
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
def test_equipment_create_forbidden_for_non_it(self):
|
||||
r = self.client.post('/it-manager/equipment/create/', {
|
||||
'equipment_type': 'laptop', 'model': 'X', 'serial_number': 'X1',
|
||||
})
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
def test_worklog_create_forbidden_for_non_it(self):
|
||||
r = self.client.post('/it-manager/worklog/create/', {
|
||||
'work_type': 'repair', 'description': 'test',
|
||||
})
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
|
||||
class ItManagerEquipmentTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.it_user = make_employee(is_staff=False)
|
||||
self.it_user.groups.add(Group.objects.get(name='IT'))
|
||||
self.client = Client()
|
||||
self.client.force_login(self.it_user)
|
||||
|
||||
def test_page_accessible_for_it_group(self):
|
||||
r = self.client.get('/it-manager/')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_create_equipment(self):
|
||||
r = self.client.post('/it-manager/equipment/create/', {
|
||||
'equipment_type': 'laptop', 'model': 'ThinkPad T14', 'serial_number': 'SN-100',
|
||||
}, HTTP_HX_REQUEST='true')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(Equipment.objects.filter(serial_number='SN-100').exists())
|
||||
|
||||
def test_assign_equipment_creates_and_closes_history(self):
|
||||
equipment = make_equipment()
|
||||
emp1 = make_employee()
|
||||
emp2 = make_employee()
|
||||
|
||||
self.client.post(f'/it-manager/equipment/{equipment.id}/update/', {
|
||||
'status': 'assigned', 'assigned_to_id': emp1.id,
|
||||
})
|
||||
equipment.refresh_from_db()
|
||||
self.assertEqual(equipment.assigned_to_id, emp1.id)
|
||||
self.assertEqual(equipment.status.code, 'assigned')
|
||||
self.assertEqual(EquipmentAssignment.objects.filter(equipment=equipment, returned_at__isnull=True).count(), 1)
|
||||
|
||||
self.client.post(f'/it-manager/equipment/{equipment.id}/update/', {
|
||||
'status': 'assigned', 'assigned_to_id': emp2.id,
|
||||
})
|
||||
equipment.refresh_from_db()
|
||||
self.assertEqual(equipment.assigned_to_id, emp2.id)
|
||||
first_assignment = EquipmentAssignment.objects.get(equipment=equipment, assigned_to=emp1)
|
||||
self.assertIsNotNone(first_assignment.returned_at)
|
||||
self.assertEqual(
|
||||
EquipmentAssignment.objects.filter(equipment=equipment, assigned_to=emp2, returned_at__isnull=True).count(), 1,
|
||||
)
|
||||
|
||||
def test_unassign_equipment_closes_history_without_new_entry(self):
|
||||
equipment = make_equipment()
|
||||
emp1 = make_employee()
|
||||
self.client.post(f'/it-manager/equipment/{equipment.id}/update/', {
|
||||
'status': 'assigned', 'assigned_to_id': emp1.id,
|
||||
})
|
||||
self.client.post(f'/it-manager/equipment/{equipment.id}/update/', {
|
||||
'status': 'stock', 'assigned_to_id': '',
|
||||
})
|
||||
equipment.refresh_from_db()
|
||||
self.assertIsNone(equipment.assigned_to_id)
|
||||
self.assertEqual(equipment.status.code, 'stock')
|
||||
self.assertEqual(EquipmentAssignment.objects.filter(equipment=equipment, returned_at__isnull=True).count(), 0)
|
||||
|
||||
|
||||
class ItManagerTicketTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.it_user = make_employee(is_staff=False)
|
||||
self.it_user.groups.add(Group.objects.get(name='IT'))
|
||||
self.requester = make_employee()
|
||||
self.client = Client()
|
||||
self.client.force_login(self.it_user)
|
||||
|
||||
def test_resolve_ticket_stamps_resolved_at(self):
|
||||
ticket = Ticket.objects.create(
|
||||
title='Не працює принтер', description='...',
|
||||
ticket_type_id=_dir_id('ticket_type', 'repair'),
|
||||
requester=self.requester,
|
||||
)
|
||||
self.client.post(f'/it-manager/tickets/{ticket.id}/update/', {
|
||||
'status': 'resolved', 'assignee_id': self.it_user.id,
|
||||
})
|
||||
ticket.refresh_from_db()
|
||||
self.assertEqual(ticket.status.code, 'resolved')
|
||||
self.assertEqual(ticket.assignee_id, self.it_user.id)
|
||||
self.assertIsNotNone(ticket.resolved_at)
|
||||
|
||||
def test_add_comment(self):
|
||||
ticket = Ticket.objects.create(
|
||||
title='Питання', description='...',
|
||||
ticket_type_id=_dir_id('ticket_type', 'consultation'),
|
||||
requester=self.requester,
|
||||
)
|
||||
r = self.client.post(f'/it-manager/tickets/{ticket.id}/comment/', {
|
||||
'text': 'Дивимось', 'is_internal': 'on',
|
||||
})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(ticket.comments.count(), 1)
|
||||
self.assertTrue(ticket.comments.first().is_internal)
|
||||
|
||||
|
||||
class ItManagerWorkLogTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.it_user = make_employee(is_staff=False)
|
||||
self.it_user.groups.add(Group.objects.get(name='IT'))
|
||||
self.client = Client()
|
||||
self.client.force_login(self.it_user)
|
||||
|
||||
def test_create_worklog(self):
|
||||
r = self.client.post('/it-manager/worklog/create/', {
|
||||
'work_type': 'maintenance', 'description': 'Профілактика серверної',
|
||||
'hours_spent': '2.5',
|
||||
})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
log = WorkLog.objects.get(description='Профілактика серверної')
|
||||
self.assertEqual(log.technician_id, self.it_user.id)
|
||||
self.assertEqual(float(log.hours_spent), 2.5)
|
||||
Reference in New Issue
Block a user