From 154871d693becaa862f25c8eecbb80e5c5920d99 Mon Sep 17 00:00:00 2001 From: lizerk Date: Wed, 26 Aug 2026 15:31:27 +0300 Subject: [PATCH] fix: serialize empty secret skill periods --- backend/employees/services/secret_skills.py | 41 +++++++++---------- backend/tests/employees/test_secret_skills.py | 30 ++++++++++++++ 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/backend/employees/services/secret_skills.py b/backend/employees/services/secret_skills.py index e494646e..03c4bcd7 100644 --- a/backend/employees/services/secret_skills.py +++ b/backend/employees/services/secret_skills.py @@ -562,6 +562,23 @@ def _action_period( return period_start, period_end +def _empty_personnel_metrics(skill_states) -> dict[str, object]: + for state in skill_states.values(): + state.pop("_rolling_dates", None) + state["recent_violation_dates"] = [] + return { + "worked_days": 0, + "current_reliability_streak": 0, + "best_reliability_streak": 0, + "current_resilience_streak": 0, + "best_resilience_streak": 0, + "leave_or_day_off_episodes": 0, + "sick_leave_episodes": 0, + "personnel_last_evaluated_date": "", + "skill_states": skill_states, + } + + def calculate_personnel_secret_metrics( # noqa: C901 employee: Employee, actions: Iterable[EmployeeAction], @@ -592,33 +609,13 @@ def calculate_personnel_secret_metrics( # noqa: C901 } employment_period = _employment_period(employee, sorted_actions, reference_date) if employment_period is None: - return { - "worked_days": 0, - "current_reliability_streak": 0, - "best_reliability_streak": 0, - "current_resilience_streak": 0, - "best_resilience_streak": 0, - "leave_or_day_off_episodes": 0, - "sick_leave_episodes": 0, - "personnel_last_evaluated_date": "", - "skill_states": skill_states, - } + return _empty_personnel_metrics(skill_states) start_date, end_date = employment_period if since is not None: start_date = max(start_date, since) if start_date > end_date: - return { - "worked_days": 0, - "current_reliability_streak": 0, - "best_reliability_streak": 0, - "current_resilience_streak": 0, - "best_resilience_streak": 0, - "leave_or_day_off_episodes": 0, - "sick_leave_episodes": 0, - "personnel_last_evaluated_date": "", - "skill_states": skill_states, - } + return _empty_personnel_metrics(skill_states) reliability_blocked_dates: set[date] = set() reliability_paused_dates: set[date] = set() resilience_blocked_dates: set[date] = set() diff --git a/backend/tests/employees/test_secret_skills.py b/backend/tests/employees/test_secret_skills.py index 7da92691..b9ab35f7 100644 --- a/backend/tests/employees/test_secret_skills.py +++ b/backend/tests/employees/test_secret_skills.py @@ -376,6 +376,36 @@ def test_personnel_recalculation_counts_days_only_since_boundary(user_factory): assert progress.is_active is False +def test_personnel_cutoff_after_dismissal_saves_json_state(user_factory): + employee = user_factory() + start = date(2025, 1, 1) + _action(employee, ACTION_HIRED, start) + _action(employee, ACTION_DISMISSED, start + timedelta(days=10)) + skill = Skill.objects.get(system_code="attendance_reliability") + progress = EmployeeSkillProgress.objects.create( + employee=employee, + skill=skill, + special_rank="bronze", + is_active=True, + ) + employee.skills.add(skill) + + with patch( + "employees.services.secret_skills.timezone.localdate", + return_value=start + timedelta(days=100), + ): + recalculate_attendance_secret_skills( + employee.id, + since=start + timedelta(days=50), + ) + + progress.refresh_from_db() + assert progress.special_rank == "none" + assert progress.is_active is False + assert progress.progress_data["recent_violation_dates"] == [] + assert "_rolling_dates" not in progress.progress_data + + def test_remote_reactivates_employment_without_resetting_active_period(user_factory): employee = user_factory() start = date(2025, 1, 1)