From d36996b4e365dd1bf14c0a44d627b9126b5c8b1c Mon Sep 17 00:00:00 2001 From: lizerk Date: Wed, 26 Aug 2026 10:20:17 +0300 Subject: [PATCH] fix: treat remote work as employment activation --- backend/employees/services/secret_skills.py | 45 +++++++++++-------- backend/tests/employees/test_secret_skills.py | 35 +++++++++++++++ 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/backend/employees/services/secret_skills.py b/backend/employees/services/secret_skills.py index ca8d9879..e1efd192 100644 --- a/backend/employees/services/secret_skills.py +++ b/backend/employees/services/secret_skills.py @@ -19,6 +19,7 @@ from employees.constants import ( ACTION_ON_SICK_LEAVE, ACTION_REHIRED, ACTION_REMOTE, + ACTION_WORKING, ) from employees.models import Employee, EmployeeAction, EmployeeSkillProgress, Skill from employees.services.skill_progress import SKILL_RANKS, rarity_payload @@ -466,27 +467,33 @@ def _employment_period( actions: list[EmployeeAction], reference_date: date, ) -> tuple[date, date] | None: - activation_dates = [ - action_date - for action in actions - if action.action in {ACTION_HIRED, ACTION_REHIRED} - and (action_date := _action_date(action)) is not None - and action_date <= reference_date - ] created_date = timezone.localtime(employee.created_at).date() - start_date = max(activation_dates) if activation_dates else created_date - if start_date > reference_date: - return None + segment_start = created_date + segment_end: date | None = None + is_employed = True + for action in actions: + action_date = _action_date(action) + if action_date is None or action_date > reference_date: + continue + if action.action in {ACTION_HIRED, ACTION_REHIRED}: + segment_start = action_date + segment_end = None + is_employed = True + elif action.action in {ACTION_REMOTE, ACTION_WORKING}: + if not is_employed: + segment_start = action_date + segment_end = None + is_employed = True + elif action.action == ACTION_DISMISSED and is_employed: + segment_end = action_date + is_employed = False - dismissal_dates = [ - action_date - for action in actions - if action.action == ACTION_DISMISSED - and (action_date := _action_date(action)) is not None - and start_date <= action_date <= reference_date - ] - end_date = min(dismissal_dates) if dismissal_dates else reference_date - return start_date, end_date + if segment_start > reference_date: + return None + return ( + segment_start, + reference_date if is_employed else segment_end or reference_date, + ) def _action_period( diff --git a/backend/tests/employees/test_secret_skills.py b/backend/tests/employees/test_secret_skills.py index 3c2e4b40..fe773228 100644 --- a/backend/tests/employees/test_secret_skills.py +++ b/backend/tests/employees/test_secret_skills.py @@ -6,6 +6,7 @@ from django.utils import timezone from attendance.models import AttendanceAnalysisRun, AttendanceRecord from employees.constants import ( + ACTION_DISMISSED, ACTION_HIRED, ACTION_ON_DAY_OFF, ACTION_ON_SICK_LEAVE, @@ -327,6 +328,40 @@ def test_reliability_uses_only_personnel_events_and_recovers_rank(user_factory): assert progress.progress_data["penalty_levels"] == 0 +def test_remote_reactivates_employment_without_resetting_active_period(user_factory): + employee = user_factory() + start = date(2025, 1, 1) + _action(employee, ACTION_HIRED, start) + _action(employee, ACTION_REMOTE, start + timedelta(days=30)) + + with patch( + "employees.services.secret_skills.timezone.localdate", + return_value=start + timedelta(days=59), + ): + recalculate_attendance_secret_skills(employee.id) + + skill = Skill.objects.get(system_code="attendance_reliability") + progress = EmployeeSkillProgress.objects.get(employee=employee, skill=skill) + assert progress.special_rank == "bronze" + assert progress.progress_data["worked_days"] == 60 + + _action(employee, ACTION_DISMISSED, start + timedelta(days=60)) + _action(employee, ACTION_REMOTE, start + timedelta(days=70)) + with patch( + "employees.services.secret_skills.timezone.localdate", + return_value=start + timedelta(days=129), + ): + recalculate_attendance_secret_skills(employee.id) + + progress.refresh_from_db() + assert progress.special_rank == "bronze" + assert progress.progress_data["worked_days"] == 60 + assert ( + progress.progress_data["last_evaluated_date"] + == (start + timedelta(days=129)).isoformat() + ) + + def test_resilience_counts_sick_episodes_and_recovers_rank(user_factory): employee = user_factory() start = date(2024, 1, 1)