"""Тесты модуля проектов: шаблон → Task, прогресс, видимость, permission.""" from __future__ import annotations import datetime from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.test import Client, TestCase from apps.employees.models import Employee from apps.projects.models import ( Project, ProjectStep, ProjectStepItem, ProjectTemplate, ProjectTemplateStep, ProjectTemplateStepItem, ) from apps.projects.services import ( activate_project, create_manual_project, create_project_from_template, ) from apps.tasks.models import Task from core.models import Directory, DirectoryType, PortalAccess from core.test_utils import login_2fa def _grant_projects(user): ct = ContentType.objects.get_for_model(PortalAccess, for_concrete_model=False) perm, _ = Permission.objects.get_or_create( content_type=ct, codename='access_projects', defaults={'name': 'Доступ к разделу: Проекты'}, ) user.user_permissions.add(perm) class ProjectServicesTests(TestCase): @classmethod def setUpTestData(cls): dtype = DirectoryType.objects.create(code='department', name='Відділ') cls.dept_it = Directory.objects.create( directory_type=dtype, code='it', name='IT', ) cls.dept_hr = Directory.objects.create( directory_type=dtype, code='hr', name='HR', ) common = dict( mattermost_sync_enabled=False, ad_sync_enabled=False, hire_date=datetime.date(2020, 1, 1), user_location_type='office', ) cls.owner = Employee.objects.create_user( username='proj_owner', department=cls.dept_it, **common, ) cls.head = Employee.objects.create_user( username='proj_head', department=cls.dept_it, **common, ) cls.peer = Employee.objects.create_user( username='proj_peer', department=cls.dept_it, manager=cls.head, **common, ) # head is manager of peer → department_head heuristic cls.outsider = Employee.objects.create_user( username='proj_out', department=cls.dept_hr, **common, ) cls.tpl = ProjectTemplate.objects.create( name='Открытие аптеки', default_duration_days=90, ) s1 = ProjectTemplateStep.objects.create( template=cls.tpl, title='IT', order=0, department=cls.dept_it, offset_days=0, duration_days=14, ) ProjectTemplateStepItem.objects.create( step=s1, title='Настроить сеть', order=0, ) ProjectTemplateStepItem.objects.create( step=s1, title='Установить кассу', order=1, ) s2 = ProjectTemplateStep.objects.create( template=cls.tpl, title='HR', order=1, department=cls.dept_hr, offset_days=7, duration_days=21, ) ProjectTemplateStepItem.objects.create( step=s2, title='Нанять персонал', order=0, ) def test_instantiate_template_creates_steps_and_tasks_on_activate(self): project = create_project_from_template( template=self.tpl, title='Аптека тест', owner=self.owner, start_date=datetime.date(2026, 1, 1), ) self.assertEqual(project.status, Project.STATUS_DRAFT) self.assertEqual(project.steps.count(), 2) self.assertEqual( ProjectStepItem.objects.filter(step__project=project).count(), 3, ) self.assertEqual( ProjectStepItem.objects.filter(step__project=project, task__isnull=False).count(), 0, ) activate_project(project) project.refresh_from_db() self.assertEqual(project.status, Project.STATUS_ACTIVE) items = list( ProjectStepItem.objects.filter(step__project=project).select_related('task') ) self.assertEqual(len(items), 3) self.assertTrue(all(i.task_id for i in items)) self.assertEqual(Task.objects.filter(pk__in=[i.task_id for i in items]).count(), 3) def test_progress_and_auto_done(self): project = create_project_from_template( template=self.tpl, title='P2', owner=self.owner, ) activate_project(project) items = list( ProjectStepItem.objects.filter(step__project=project).select_related('task') ) self.assertEqual(project.progress_percent, 0) for item in items: item.task.set_status('done') project.refresh_from_db() self.assertEqual(project.progress_percent, 100) self.assertEqual(project.status, Project.STATUS_DONE) for step in project.steps.all(): self.assertEqual(step.status, ProjectStep.STATUS_DONE) def test_for_user_visibility(self): project = create_project_from_template( template=self.tpl, title='Vis', owner=self.owner, ) activate_project(project) self.assertTrue( Project.objects.for_user(self.owner).filter(pk=project.pk).exists() ) # peer in IT department of a step self.assertTrue( Project.objects.for_user(self.peer).filter(pk=project.pk).exists() ) # outsider HR department also has a step self.assertTrue( Project.objects.for_user(self.outsider).filter(pk=project.pk).exists() ) stranger = Employee.objects.create_user( username='stranger', mattermost_sync_enabled=False, ad_sync_enabled=False, hire_date=datetime.date(2020, 1, 1), user_location_type='office', ) self.assertFalse( Project.objects.for_user(stranger).filter(pk=project.pk).exists() ) def test_manual_create(self): project = create_manual_project( title='Ручной', owner=self.owner, steps=[{ 'title': 'Шаг 1', 'department_id': self.dept_it.pk, 'items': [{'title': 'A'}, {'title': 'B'}], }], ) self.assertEqual(project.steps.count(), 1) self.assertEqual(project.steps.first().items.count(), 2) class ProjectPortalAccessTests(TestCase): @classmethod def setUpTestData(cls): common = dict( mattermost_sync_enabled=False, ad_sync_enabled=False, hire_date=datetime.date(2020, 1, 1), ) cls.user = Employee.objects.create_user(username='proj_u', **common) def setUp(self): self.client = Client() def test_list_requires_permission(self): login_2fa(self.client, self.user) r = self.client.get('/projects/') self.assertIn(r.status_code, (302, 403)) _grant_projects(self.user) # clear perm cache self.user = Employee.objects.get(pk=self.user.pk) login_2fa(self.client, self.user) r = self.client.get('/projects/') self.assertEqual(r.status_code, 200)