diff --git a/backend/commerce/integrations/moysklad.py b/backend/commerce/integrations/moysklad.py index abcb1e05..cef1a681 100644 --- a/backend/commerce/integrations/moysklad.py +++ b/backend/commerce/integrations/moysklad.py @@ -136,7 +136,11 @@ class MoySkladClient: "MoySklad GET transient failure %s; retrying in %.1f seconds", type(exc).__name__, retry_delay, - extra={**log_context, "retry_in_seconds": retry_delay}, + extra={ + **log_context, + "event": "commerce.moysklad.get_retry", + "retry_in_seconds": retry_delay, + }, ) time.sleep(retry_delay) except httpx.HTTPStatusError as exc: @@ -149,6 +153,7 @@ class MoySkladClient: "MoySklad HTTP %s; retrying GET", exc.response.status_code, extra={ + "event": "commerce.moysklad.get_retry", "endpoint": endpoint, "attempt": attempt, "request_offset": (params or {}).get("offset"), diff --git a/backend/tests/api/v1/communications/test_change_role_improved.py b/backend/tests/api/v1/communications/test_change_role_improved.py index 094b3e9d..a5f3b292 100644 --- a/backend/tests/api/v1/communications/test_change_role_improved.py +++ b/backend/tests/api/v1/communications/test_change_role_improved.py @@ -8,7 +8,7 @@ """ import pytest -from communications.models import Chat, ChatMembership, Message +from communications.models import Chat, ChatMembership from django.contrib.auth import get_user_model from rest_framework import status from rest_framework.test import APIClient @@ -19,7 +19,7 @@ pytestmark = pytest.mark.django_db class TestChangeRoleImproved: """Улучшенные тесты изменения ролей с проверкой через GET запрос""" - + @pytest.fixture def owner(self, db): return User.objects.create_user( @@ -28,9 +28,10 @@ class TestChangeRoleImproved: first_name="Owner", last_name="Test", phone_number="+79001234567", + is_active=True, send_activation_email=False, ) - + @pytest.fixture def member_user(self, db): return User.objects.create_user( @@ -39,94 +40,84 @@ class TestChangeRoleImproved: first_name="Member", last_name="Test", phone_number="+79001234568", + is_active=True, send_activation_email=False, ) - + @pytest.fixture def test_chat(self, owner, member_user): - chat = Chat.objects.create( - type="group", - name="Test Chat", - created_by=owner - ) + chat = Chat.objects.create(type="group", name="Test Chat", created_by=owner) chat.participants.add(owner, member_user) - + ChatMembership.objects.create( - chat=chat, - user=member_user, - role='member', - invited_by=owner + chat=chat, user=member_user, role="member", invited_by=owner ) - + return chat - + @pytest.fixture def owner_client(self, owner): client = APIClient() client.force_authenticate(user=owner) return client - + def test_change_role_and_verify_via_api(self, owner_client, test_chat, member_user): """ Тест: изменить роль, дождаться ответа, проверить через GET """ # Шаг 1: Изменяем роль через POST change_url = f"/api/v1/communications/chats/{test_chat.id}/change-role/" - response = owner_client.post(change_url, { - 'user_id': member_user.id, - 'role': 'admin' - }) - + response = owner_client.post( + change_url, {"user_id": member_user.id, "role": "admin"} + ) + # Шаг 2: Проверяем успешный ответ assert response.status_code == status.HTTP_200_OK data = response.json() - assert data['ok'] is True - assert data['membership']['role'] == 'admin' - + assert data["ok"] is True + assert data["membership"]["role"] == "admin" + # Шаг 3: Получаем чат через GET и проверяем роль chat_url = f"/api/v1/communications/chats/{test_chat.id}/" get_response = owner_client.get(chat_url) - + assert get_response.status_code == status.HTTP_200_OK chat_data = get_response.json() - + # Шаг 4: Находим участника и проверяем роль member_membership = next( - (m for m in chat_data['memberships'] if m['user'] == member_user.id), - None + (m for m in chat_data["memberships"] if m["user"] == member_user.id), None ) - + assert member_membership is not None, "Участник не найден" - assert member_membership['role'] == 'admin' - assert member_membership['can_send_messages'] is True - assert member_membership['can_add_members'] is True - + assert member_membership["role"] == "admin" + assert member_membership["can_send_messages"] is True + assert member_membership["can_add_members"] is True + def test_member_to_guest_role_change(self, owner_client, test_chat, member_user): """ Тест: изменение member → guest с проверкой через API """ # Шаг 1: Изменяем роль change_url = f"/api/v1/communications/chats/{test_chat.id}/change-role/" - response = owner_client.post(change_url, { - 'user_id': member_user.id, - 'role': 'guest' - }) - + response = owner_client.post( + change_url, {"user_id": member_user.id, "role": "guest"} + ) + # Шаг 2: Проверяем ответ assert response.status_code == status.HTTP_200_OK - + # Шаг 3: Проверяем через GET chat_url = f"/api/v1/communications/chats/{test_chat.id}/" get_response = owner_client.get(chat_url) - + chat_data = get_response.json() member_membership = next( - (m for m in chat_data['memberships'] if m['user'] == member_user.id), - None + (m for m in chat_data["memberships"] if m["user"] == member_user.id), None ) - + # Шаг 4: Проверяем что стал гостем (только чтение) - assert member_membership['role'] == 'guest' - assert member_membership['can_send_messages'] is False - assert member_membership['can_add_members'] is False - assert member_membership['can_remove_members'] is False + assert member_membership["role"] == "guest" + assert member_membership["can_send_messages"] is False + assert member_membership["can_add_members"] is False + assert member_membership["can_remove_members"] is False diff --git a/backend/tests/api/v1/employees/test_department_calendar.py b/backend/tests/api/v1/employees/test_department_calendar.py index 40c0b5c6..079844cf 100644 --- a/backend/tests/api/v1/employees/test_department_calendar.py +++ b/backend/tests/api/v1/employees/test_department_calendar.py @@ -206,9 +206,7 @@ def test_department_calendar_event_permissions_for_non_member_and_member( ) assert forbidden_response.status_code == status.HTTP_403_FORBIDDEN - forbidden_delete = api_client.delete( - f"/api/v1/schedule/events/{member_event.id}/" - ) + forbidden_delete = api_client.delete(f"/api/v1/schedule/events/{member_event.id}/") assert forbidden_delete.status_code == status.HTTP_403_FORBIDDEN @@ -293,9 +291,7 @@ def test_department_calendar_participants_include_avatar_and_email( "calendar_id" ] - response = api_client.get( - f"/api/v1/schedule/calendars/{calendar_id}/participants/" - ) + response = api_client.get(f"/api/v1/schedule/calendars/{calendar_id}/participants/") assert response.status_code == status.HTTP_200_OK assert len(response.data) == 1 @@ -307,7 +303,7 @@ def test_department_calendar_participants_include_avatar_and_email( assert participant["user"]["last_name"] == "User" -def test_department_calendar_occurrences_allow_outsider_but_keep_permissions( +def test_department_calendar_occurrences_hide_events_from_outsider( api_client: APIClient, ): head = make_user("head@example.com") @@ -344,9 +340,19 @@ def test_department_calendar_occurrences_allow_outsider_but_keep_permissions( }, ) + assert response.status_code == status.HTTP_200_OK + assert response.data == [] + + api_client.force_authenticate(user=member) + response = api_client.get( + "/api/v1/schedule/events/occurrences/", + { + "calendar": calendar_id, + "start": (start - timedelta(minutes=5)).isoformat(), + "end": (start + timedelta(hours=2)).isoformat(), + }, + ) + assert response.status_code == status.HTTP_200_OK assert len(response.data) == 1 - occurrence = response.data[0] - assert occurrence["event_id"] == member_event.id - assert occurrence["can_edit"] is False - assert occurrence["can_delete"] is False + assert response.data[0]["event_id"] == member_event.id diff --git a/backend/tests/api/v1/procurement/test_equipment.py b/backend/tests/api/v1/procurement/test_equipment.py index 5b5c9e60..2f7f4acd 100644 --- a/backend/tests/api/v1/procurement/test_equipment.py +++ b/backend/tests/api/v1/procurement/test_equipment.py @@ -28,10 +28,7 @@ def api_client(): @pytest.fixture def department(db): """Тестовый отдел.""" - return Department.objects.create( - name="IT отдел", - description="Отдел IT" - ) + return Department.objects.create(name="IT отдел", description="Отдел IT") @pytest.fixture @@ -46,9 +43,7 @@ def user(db, department): send_activation_email=False, ) EmployeeDepartment.objects.create( - employee=user, - department=department, - is_active=True + employee=user, department=department, is_active=True ) return user @@ -71,9 +66,7 @@ def staff_user(db): def category(db): """Категория оборудования.""" return EquipmentCategory.objects.create( - name="Ноутбуки", - description="Портативные компьютеры", - icon="bi-laptop" + name="Ноутбуки", description="Портативные компьютеры", icon="bi-laptop" ) @@ -106,33 +99,29 @@ class TestEquipmentList: def test_list_unauthorized(self, api_client): """Неавторизованный доступ запрещен.""" - url = reverse('api:v1:procurement:equipment-list') + url = reverse("api:v1:procurement:equipment-list") response = api_client.get(url) assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_list_equipment( - self, api_client, user, equipment - ): + def test_list_equipment(self, api_client, user, equipment): """Получение списка оборудования.""" api_client.force_authenticate(user=user) - url = reverse('api:v1:procurement:equipment-list') + url = reverse("api:v1:procurement:equipment-list") response = api_client.get(url) - + assert response.status_code == status.HTTP_200_OK - results = response.data['results'] + results = response.data["results"] assert len(results) >= 1 - + # Проверяем структуру item = results[0] - assert 'inventory_number' in item - assert 'name' in item - assert 'status' in item - assert 'category_name' in item - assert 'department_name' in item + assert "inventory_number" in item + assert "name" in item + assert "status" in item + assert "category_name" in item + assert "department_name" in item - def test_filter_by_status( - self, api_client, user, category, department, equipment - ): + def test_filter_by_status(self, api_client, user, category, department, equipment): """Фильтр по статусу.""" # Создаем оборудование с разными статусами Equipment.objects.create( @@ -144,23 +133,22 @@ class TestEquipmentList: purchase_date=date.today(), purchase_cost=Decimal("20000.00"), ) - + api_client.force_authenticate(user=user) - url = reverse('api:v1:procurement:equipment-list') - + url = reverse("api:v1:procurement:equipment-list") + # Фильтр AVAILABLE - response = api_client.get(url, {'status': EquipmentStatus.AVAILABLE}) + response = api_client.get(url, {"status": EquipmentStatus.AVAILABLE}) assert response.status_code == status.HTTP_200_OK - results = response.data['results'] - assert all(r['status'] == EquipmentStatus.AVAILABLE for r in results) + results = response.data["results"] + assert all(r["status"] == EquipmentStatus.AVAILABLE for r in results) def test_filter_by_department( self, api_client, user, db, category, department, equipment ): """Фильтр по отделу.""" other_dept = Department.objects.create( - name="HR отдел", - description="Другой отдел" + name="HR отдел", description="Другой отдел" ) Equipment.objects.create( name="Принтер", @@ -171,26 +159,24 @@ class TestEquipmentList: purchase_date=date.today(), purchase_cost=Decimal("15000.00"), ) - - api_client.force_authenticate(user=user) - url = reverse('api:v1:procurement:equipment-list') - - response = api_client.get(url, {'department': department.id}) - assert response.status_code == status.HTTP_200_OK - results = response.data['results'] - # Все должны быть из нужного отдела - assert all(r['department'] == department.id for r in results) - def test_search_equipment( - self, api_client, user, equipment - ): + api_client.force_authenticate(user=user) + url = reverse("api:v1:procurement:equipment-list") + + response = api_client.get(url, {"department": department.id}) + assert response.status_code == status.HTTP_200_OK + results = response.data["results"] + # Все должны быть из нужного отдела + assert all(r["department"] == department.id for r in results) + + def test_search_equipment(self, api_client, user, equipment): """Поиск оборудования.""" api_client.force_authenticate(user=user) - url = reverse('api:v1:procurement:equipment-list') - - response = api_client.get(url, {'search': 'latitude'}) + url = reverse("api:v1:procurement:equipment-list") + + response = api_client.get(url, {"search": "latitude"}) assert response.status_code == status.HTTP_200_OK - assert len(response.data['results']) >= 1 + assert len(response.data["results"]) >= 1 # ============================================================================== @@ -206,44 +192,44 @@ class TestEquipmentCreate: ): """Создание оборудования staff пользователем.""" api_client.force_authenticate(user=staff_user) - url = reverse('api:v1:procurement:equipment-list') - + url = reverse("api:v1:procurement:equipment-list") + data = { - 'name': 'Монитор LG 27"', - 'inventory_number': 'INV-2026-100', - 'serial_number': 'LG987654321', - 'category': category.id, - 'status': EquipmentStatus.AVAILABLE, - 'department': department.id, - 'location': 'Склад', - 'purchase_date': '2026-02-01', - 'warranty_until': '2029-02-01', - 'purchase_cost': '25000.00', - 'notes': 'Для офиса', + "name": 'Монитор LG 27"', + "inventory_number": "INV-2026-100", + "serial_number": "LG987654321", + "category": category.id, + "status": EquipmentStatus.AVAILABLE, + "department": department.id, + "location": "Склад", + "purchase_date": "2026-02-01", + "warranty_until": "2029-02-01", + "purchase_cost": "25000.00", + "notes": "Для офиса", } - - response = api_client.post(url, data, format='json') + + response = api_client.post(url, data, format="json") assert response.status_code == status.HTTP_201_CREATED - assert response.data['name'] == 'Монитор LG 27"' - assert response.data['inventory_number'].startswith('INV-') + assert response.data["name"] == 'Монитор LG 27"' + assert response.data["inventory_number"].startswith("INV-") def test_create_equipment_regular_user_forbidden( self, api_client, user, category, department ): """Обычный пользователь не может создавать оборудование.""" api_client.force_authenticate(user=user) - url = reverse('api:v1:procurement:equipment-list') - + url = reverse("api:v1:procurement:equipment-list") + data = { - 'name': 'Тестовое оборудование', - 'inventory_number': 'INV-2026-999', - 'category': category.id, - 'department': department.id, - 'purchase_date': '2026-02-01', - 'purchase_cost': '10000.00', + "name": "Тестовое оборудование", + "inventory_number": "INV-2026-999", + "category": category.id, + "department": department.id, + "purchase_date": "2026-02-01", + "purchase_cost": "10000.00", } - - response = api_client.post(url, data, format='json') + + response = api_client.post(url, data, format="json") assert response.status_code == status.HTTP_403_FORBIDDEN def test_create_equipment_duplicate_inventory_number( @@ -251,20 +237,20 @@ class TestEquipmentCreate: ): """Нельзя создать с дублирующим инвентарным номером.""" api_client.force_authenticate(user=staff_user) - url = reverse('api:v1:procurement:equipment-list') - + url = reverse("api:v1:procurement:equipment-list") + data = { - 'name': 'Другое оборудование', - 'inventory_number': equipment.inventory_number, # Дубликат! - 'category': category.id, - 'department': department.id, - 'purchase_date': '2026-02-01', - 'purchase_cost': '10000.00', + "name": "Другое оборудование", + "inventory_number": equipment.inventory_number, # Дубликат! + "category": category.id, + "department": department.id, + "purchase_date": "2026-02-01", + "purchase_cost": "10000.00", } - - response = api_client.post(url, data, format='json') + + response = api_client.post(url, data, format="json") assert response.status_code == status.HTTP_201_CREATED - assert response.data['inventory_number'] != equipment.inventory_number + assert response.data["inventory_number"] != equipment.inventory_number # ============================================================================== @@ -275,27 +261,22 @@ class TestEquipmentCreate: class TestEquipmentDetail: """Тесты получения детальной информации об оборудовании.""" - def test_retrieve_equipment( - self, api_client, user, equipment - ): + def test_retrieve_equipment(self, api_client, user, equipment): """Получение детальной информации.""" api_client.force_authenticate(user=user) url = reverse( - 'api:v1:procurement:equipment-detail', - kwargs={'pk': equipment.id} + "api:v1:procurement:equipment-detail", kwargs={"pk": equipment.id} ) response = api_client.get(url) - - assert response.status_code == status.HTTP_200_OK - assert response.data['id'] == equipment.id - assert response.data['name'] == equipment.name - assert response.data['inventory_number'] == equipment.inventory_number - assert 'is_under_warranty' in response.data - assert response.data['is_under_warranty'] is True - def test_retrieve_with_maintenance_history( - self, api_client, user, equipment - ): + assert response.status_code == status.HTTP_200_OK + assert response.data["id"] == equipment.id + assert response.data["name"] == equipment.name + assert response.data["inventory_number"] == equipment.inventory_number + assert "is_under_warranty" in response.data + assert response.data["is_under_warranty"] is True + + def test_retrieve_with_maintenance_history(self, api_client, user, equipment): """Детальная информация включает историю ТО.""" # Создаем записи об обслуживании MaintenanceRecord.objects.create( @@ -306,16 +287,15 @@ class TestEquipmentDetail: cost=Decimal("5000.00"), performed_by=user, ) - + api_client.force_authenticate(user=user) url = reverse( - 'api:v1:procurement:equipment-detail', - kwargs={'pk': equipment.id} + "api:v1:procurement:equipment-detail", kwargs={"pk": equipment.id} ) response = api_client.get(url) - + assert response.status_code == status.HTTP_200_OK - assert response.data['maintenance_count'] >= 1 + assert response.data["maintenance_count"] >= 1 # ============================================================================== @@ -326,40 +306,34 @@ class TestEquipmentDetail: class TestEquipmentUpdate: """Тесты обновления оборудования.""" - def test_update_equipment_as_staff( - self, api_client, staff_user, equipment - ): + def test_update_equipment_as_staff(self, api_client, staff_user, equipment): """Обновление оборудования staff пользователем.""" api_client.force_authenticate(user=staff_user) url = reverse( - 'api:v1:procurement:equipment-detail', - kwargs={'pk': equipment.id} + "api:v1:procurement:equipment-detail", kwargs={"pk": equipment.id} ) - - data = { - 'location': 'Офис 4.01', - 'notes': 'Перемещено в новый офис', - } - response = api_client.patch(url, data, format='json') - - assert response.status_code == status.HTTP_200_OK - assert response.data['location'] == 'Офис 4.01' - assert 'новый офис' in response.data['notes'].lower() - def test_update_equipment_regular_user_forbidden( - self, api_client, user, equipment - ): + data = { + "location": "Офис 4.01", + "notes": "Перемещено в новый офис", + } + response = api_client.patch(url, data, format="json") + + assert response.status_code == status.HTTP_200_OK + assert response.data["location"] == "Офис 4.01" + assert "новый офис" in response.data["notes"].lower() + + def test_update_equipment_regular_user_forbidden(self, api_client, user, equipment): """Ответственный пользователь может обновлять своё оборудование.""" api_client.force_authenticate(user=user) url = reverse( - 'api:v1:procurement:equipment-detail', - kwargs={'pk': equipment.id} + "api:v1:procurement:equipment-detail", kwargs={"pk": equipment.id} ) - - data = {'location': 'Новое место'} - response = api_client.patch(url, data, format='json') + + data = {"location": "Новое место"} + response = api_client.patch(url, data, format="json") assert response.status_code == status.HTTP_200_OK - assert response.data['location'] == 'Новое место' + assert response.data["location"] == "Новое место" # ============================================================================== @@ -370,9 +344,7 @@ class TestEquipmentUpdate: class TestEquipmentActions: """Тесты специальных действий с оборудованием.""" - def test_transfer_equipment( - self, api_client, staff_user, equipment, db - ): + def test_transfer_equipment(self, api_client, staff_user, equipment, db): """Передача оборудования.""" new_user = Employee.objects.create_user( email="newuser@example.com", @@ -380,28 +352,29 @@ class TestEquipmentActions: phone_number="+79995555555", first_name="Новый", last_name="Пользователь", + is_active=True, send_activation_email=False, ) - + api_client.force_authenticate(user=staff_user) url = reverse( - 'api:v1:procurement:equipment-transfer', - kwargs={'pk': equipment.id} + "api:v1:procurement:equipment-transfer", kwargs={"pk": equipment.id} ) - + data = { - 'to_person': new_user.id, - 'to_location': 'Офис 2.10', - 'reason': 'Перевод в другой отдел', + "to_person": new_user.id, + "to_location": "Офис 2.10", + "reason": "Перевод в другой отдел", } - response = api_client.post(url, data, format='json') - + response = api_client.post(url, data, format="json") + assert response.status_code == status.HTTP_200_OK - + # Проверяем что ответственный изменился equipment.refresh_from_db() assert equipment.responsible_person == new_user - assert equipment.location == 'Офис 2.10' + assert equipment.location == "Офис 2.10" + # ============================================================================== # ТЕСТЫ КАТЕГОРИЙ @@ -411,47 +384,41 @@ class TestEquipmentActions: class TestEquipmentCategories: """Тесты API категорий оборудования.""" - def test_list_categories( - self, api_client, user, category - ): + def test_list_categories(self, api_client, user, category): """Список категорий.""" api_client.force_authenticate(user=user) - url = reverse('api:v1:procurement:equipmentcategory-list') + url = reverse("api:v1:procurement:equipmentcategory-list") response = api_client.get(url) - + assert response.status_code == status.HTTP_200_OK assert len(response.data) >= 1 - def test_create_category_as_staff( - self, api_client, staff_user - ): + def test_create_category_as_staff(self, api_client, staff_user): """Создание категории staff пользователем.""" api_client.force_authenticate(user=staff_user) - url = reverse('api:v1:procurement:equipmentcategory-list') - - data = { - 'name': 'Мониторы', - 'description': 'Все виды мониторов', - 'icon': 'bi-display', - } - response = api_client.post(url, data, format='json') - - assert response.status_code == status.HTTP_201_CREATED - assert response.data['name'] == 'Мониторы' + url = reverse("api:v1:procurement:equipmentcategory-list") - def test_hierarchical_categories( - self, api_client, staff_user, category - ): + data = { + "name": "Мониторы", + "description": "Все виды мониторов", + "icon": "bi-display", + } + response = api_client.post(url, data, format="json") + + assert response.status_code == status.HTTP_201_CREATED + assert response.data["name"] == "Мониторы" + + def test_hierarchical_categories(self, api_client, staff_user, category): """Создание подкатегории.""" api_client.force_authenticate(user=staff_user) - url = reverse('api:v1:procurement:equipmentcategory-list') - + url = reverse("api:v1:procurement:equipmentcategory-list") + data = { - 'name': 'Игровые ноутбуки', - 'parent': category.id, - 'description': 'Высокопроизводительные ноутбуки', + "name": "Игровые ноутбуки", + "parent": category.id, + "description": "Высокопроизводительные ноутбуки", } - response = api_client.post(url, data, format='json') - + response = api_client.post(url, data, format="json") + assert response.status_code == status.HTTP_201_CREATED - assert response.data['parent'] == category.id + assert response.data["parent"] == category.id diff --git a/backend/tests/finance/test_payroll_admin_api.py b/backend/tests/finance/test_payroll_admin_api.py index d8b11660..7fbc179b 100644 --- a/backend/tests/finance/test_payroll_admin_api.py +++ b/backend/tests/finance/test_payroll_admin_api.py @@ -31,6 +31,7 @@ from finance.models import ( PayrollTableReviewMark, PayrollWorkRecord, ) +from finance.payroll.config import DEFAULT_CONFIG from requests_app.enums import RequestStatus, RequestType from requests_app.models import Request @@ -394,9 +395,7 @@ def test_copy_input_lines_previews_scope_and_handles_existing_values( assert component_preview[correction_credit.pk]["line_count"] == 0 assert component_preview[correction_credit.pk]["employee_count"] == 0 assert component_preview[correction_credit.pk]["amount_total"] == "0.00" - employee_preview = { - item["id"]: item for item in body["employees"] - } + employee_preview = {item["id"]: item for item in body["employees"]} assert employee_preview[active_employee.pk]["is_active"] is True assert employee_preview[inactive_employee.pk]["is_active"] is False assert employee_preview[active_employee.pk]["existing_component_counts"] == { @@ -425,10 +424,13 @@ def test_copy_input_lines_previews_scope_and_handles_existing_values( "replaced_cells": 0, "voided": 0, } - assert PayrollInputLine.objects.filter( - period=target, - employee=inactive_employee, - ).count() == 0 + assert ( + PayrollInputLine.objects.filter( + period=target, + employee=inactive_employee, + ).count() + == 0 + ) replacement_key = uuid.uuid4() replaced = client.post( @@ -861,9 +863,7 @@ def test_workspace_contract_is_minimal_and_redacts_money_without_view_all( assert inactive_employee.pk not in {item["id"] for item in body["employees"]} assert inactive_employee.pk in {item["id"] for item in body["rate_employees"]} assert hidden_employee.pk not in {item["id"] for item in body["employees"]} - assert hidden_employee.pk not in { - item["id"] for item in body["rate_employees"] - } + assert hidden_employee.pk not in {item["id"] for item in body["rate_employees"]} def test_workspace_readiness_identifies_affected_employees_and_draft_counts( @@ -1018,9 +1018,7 @@ def test_period_table_lists_all_employees_and_requires_view_all( assert response.status_code == 200, response.content assert_no_store(response) body = response.json() - assert hidden_employee.pk not in { - item["employee"]["id"] for item in body["rows"] - } + assert hidden_employee.pk not in {item["employee"]["id"] for item in body["rows"]} assert body["period_id"] == period.pk assert body["calculation_rules"]["point_policy"] == "proportional_with_excess" assert body["calculation_rules"]["money_quantum"] == "0.01" @@ -1051,9 +1049,7 @@ def test_period_table_lists_all_employees_and_requires_view_all( assert row["opening_balance"] == "0" assert row["closing_balance"] == "0.00" assert row["totals_preliminary"] is True - assert all( - item["employee_id"] != employee.pk for item in body["carryovers"] - ) + assert all(item["employee_id"] != employee.pk for item in body["carryovers"]) assert body["summary"]["preliminary_count"] == body["summary"]["employee_count"] missing_row = next( item for item in body["rows"] if item["employee"]["id"] == missing_employee.pk @@ -1179,17 +1175,23 @@ def test_period_table_review_marks_are_personal_and_idempotent( assert response.status_code == 200, response.content assert response.json() == {"employee_ids": [employee.pk]} - assert PayrollTableReviewMark.objects.filter( - period=period, - reviewer=first_reviewer, - employee=employee, - ).count() == 1 + assert ( + PayrollTableReviewMark.objects.filter( + period=period, + reviewer=first_reviewer, + employee=employee, + ).count() + == 1 + ) assert first_client.get(api_url("admin-period-table", pk=period.pk)).json()[ "review_mark_employee_ids" ] == [employee.pk] - assert auth_client_factory(second_reviewer).get( - api_url("admin-period-table", pk=period.pk) - ).json()["review_mark_employee_ids"] == [] + assert ( + auth_client_factory(second_reviewer) + .get(api_url("admin-period-table", pk=period.pk)) + .json()["review_mark_employee_ids"] + == [] + ) cleared = first_client.delete(url) assert cleared.status_code == 200, cleared.content @@ -2148,7 +2150,8 @@ def test_workspace_blocks_period_outside_ruleset_effective_dates( "code": "RULESET_NOT_EFFECTIVE", "message": ( "Для выбранного периода нет действующих правил расчёта. " - "Набор eusrr-standard, версия 2026.08.1, применяется с 01.07.2026. " + f"Набор eusrr-standard, версия {DEFAULT_CONFIG['RULESET_VERSION']}, " + "применяется с 01.07.2026. " "Измените период или подключите историческую версию правил." ), "details": { @@ -2158,7 +2161,7 @@ def test_workspace_blocks_period_outside_ruleset_effective_dates( }, "ruleset": { "id": "eusrr-standard", - "version": "2026.08.1", + "version": DEFAULT_CONFIG["RULESET_VERSION"], "effective_from": "2026-07-01", }, }, @@ -3285,7 +3288,9 @@ def test_staff_admin_completes_entire_payroll_workflow_with_one_command( for row in table_after_confirmation.json()["rows"] if row["employee"]["id"] == employee.pk ) - assert confirmed_row["payment_confirmation"]["confirmed_by"]["id"] == administrator.pk + assert ( + confirmed_row["payment_confirmation"]["confirmed_by"]["id"] == administrator.pk + ) revoked = administrator_client.post( api_url( diff --git a/backend/tests/finance/test_payroll_services.py b/backend/tests/finance/test_payroll_services.py index a61eef09..1838e56f 100644 --- a/backend/tests/finance/test_payroll_services.py +++ b/backend/tests/finance/test_payroll_services.py @@ -32,6 +32,7 @@ from finance.models import ( PayrollWorkRecord, ) from finance.payroll.exceptions import PayrollOperationError, PayrollPermissionDenied +from finance.payroll.config import DEFAULT_CONFIG from finance.payroll.services import ( acknowledge_statement, approve_input_line, @@ -230,7 +231,9 @@ def test_only_manual_corrections_move_between_periods_and_can_be_settled( (june_payment, approve_input_line), (june_correction, approve_input_line), ): - approve(instance.pk, actor=approver, expected_lock_version=instance.lock_version) + approve( + instance.pk, actor=approver, expected_lock_version=instance.lock_version + ) june_run = calculate_period(june_period.pk, actor=operator) june_statement = june_run.statements.get(employee=employee) @@ -277,7 +280,9 @@ def test_only_manual_corrections_move_between_periods_and_can_be_settled( (july_payment, approve_input_line), (july_correction, approve_input_line), ): - approve(instance.pk, actor=approver, expected_lock_version=instance.lock_version) + approve( + instance.pk, actor=approver, expected_lock_version=instance.lock_version + ) july_run = calculate_period(july_period.pk, actor=operator) july_statement = july_run.statements.get(employee=employee) @@ -326,7 +331,9 @@ def test_only_manual_corrections_move_between_periods_and_can_be_settled( (august_payment, approve_input_line), (august_correction, approve_input_line), ): - approve(instance.pk, actor=approver, expected_lock_version=instance.lock_version) + approve( + instance.pk, actor=approver, expected_lock_version=instance.lock_version + ) august_run = calculate_period(august_period.pk, actor=operator) august_statement = august_run.statements.get(employee=employee) @@ -740,7 +747,8 @@ def test_ruleset_not_effective_keeps_domain_code_and_localized_message( assert error.value.code == "RULESET_NOT_EFFECTIVE" assert error.value.message == ( "Для выбранного периода нет действующих правил расчёта. " - "Набор eusrr-standard, версия 2026.08.1, применяется с 01.07.2026. " + f"Набор eusrr-standard, версия {DEFAULT_CONFIG['RULESET_VERSION']}, " + "применяется с 01.07.2026. " "Измените период или подключите историческую версию правил." ) assert error.value.details["employee_id"] == payroll_users["employee"].pk @@ -750,7 +758,7 @@ def test_ruleset_not_effective_keeps_domain_code_and_localized_message( } assert error.value.details["ruleset"] == { "id": "eusrr-standard", - "version": "2026.08.1", + "version": DEFAULT_CONFIG["RULESET_VERSION"], "effective_from": "2026-07-01", } assert {issue["code"] for issue in error.value.details["issues"]} == { diff --git a/backend/tests/procurement/test_expected_delivery_dates_migration.py b/backend/tests/procurement/test_expected_delivery_dates_migration.py index c8967fe2..41e51b32 100644 --- a/backend/tests/procurement/test_expected_delivery_dates_migration.py +++ b/backend/tests/procurement/test_expected_delivery_dates_migration.py @@ -11,6 +11,9 @@ pytestmark = pytest.mark.django_db(transaction=True) def _migrate_to(target): executor = MigrationExecutor(connection) executor.loader.build_graph() + target_apps = {app_label for app_label, _ in target} + if "employees" not in target_apps: + target = [*target, *executor.loader.graph.leaf_nodes("employees")] executor.migrate(target) return executor.loader.project_state(target).apps diff --git a/backend/tests/scheduling/test_calendar_binding_migration.py b/backend/tests/scheduling/test_calendar_binding_migration.py index 4838fd2d..ff777437 100644 --- a/backend/tests/scheduling/test_calendar_binding_migration.py +++ b/backend/tests/scheduling/test_calendar_binding_migration.py @@ -39,12 +39,17 @@ def test_migration_0002_backfills_department_binding_by_slug(): slug=f"department-{department.id}", ) - _migrate_to([("scheduling", "0002_calendarbinding")]) - - from scheduling.models import CalendarBinding + new_apps = _migrate_to( + [ + ("employees", "0047_alter_departmentpermission_code"), + ("schedule", "0015_rename_indexes"), + ("scheduling", "0002_calendarbinding"), + ] + ) + CalendarBinding = new_apps.get_model("scheduling", "CalendarBinding") binding = CalendarBinding.objects.get(calendar_id=calendar.id) - assert binding.type == CalendarBinding.BindingType.DEPARTMENT + assert binding.type == "department" assert binding.context_object_id == department.id assert binding.context_content_type.app_label == "employees" assert binding.context_content_type.model == "department"