projects: модуль проектов с шагами по отделам и Task.
Шаблоны/ручное создание, access_projects, сид «Открытие аптеки». Плюс SILO/S3 для Django media. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
"""Тесты модуля проектов: шаблон → 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)
|
||||
Reference in New Issue
Block a user