fix: treat remote work as employment activation

This commit is contained in:
lizerk 2026-08-26 10:20:17 +03:00
parent 5c896ad589
commit d36996b4e3
2 changed files with 61 additions and 19 deletions

View File

@ -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(

View File

@ -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)