Add LogStorm attendance payload builder

This commit is contained in:
lizerk 2026-04-21 13:56:20 +03:00
parent d5e4f6d577
commit 5bc0a33b50
2 changed files with 221 additions and 0 deletions

View File

@ -0,0 +1,109 @@
"""Helpers for building EUSRR -> LogStorm attendance requests."""
from datetime import date
from typing import Any, Optional
from employees.models import Employee
from common.logstorm_client import LogStormClient
DEFAULT_WORKDAYS = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
]
def build_logstorm_attendance_payload(
*,
employee: Employee,
period_start: date,
period_end: date,
schedule: Optional[dict[str, Any]] = None,
date_overrides: Optional[list[dict[str, Any]]] = None,
) -> dict[str, Any]:
"""
Build a LogStorm attendance analysis payload.
If schedule is omitted, the payload intentionally omits it so LogStorm can
apply its configured default schedule or reject the request by policy.
"""
payload = {
"employee_id": str(employee.id),
"display_name": _employee_display_name(employee),
"period_start": period_start.isoformat(),
"period_end": period_end.isoformat(),
}
if schedule is not None:
payload["schedule"] = normalize_logstorm_schedule(
schedule,
date_overrides=date_overrides,
)
return payload
def normalize_logstorm_schedule(
schedule: dict[str, Any],
*,
date_overrides: Optional[list[dict[str, Any]]] = None,
) -> dict[str, Any]:
return {
"start_time": schedule["start_time"],
"end_time": schedule["end_time"],
"expected_hours": schedule["expected_hours"],
"workdays": schedule.get("workdays", DEFAULT_WORKDAYS),
"date_overrides": [
_normalize_date_override(override)
for override in (
date_overrides
if date_overrides is not None
else schedule.get("date_overrides", [])
)
],
}
def analyze_employee_attendance(
*,
employee: Employee,
period_start: date,
period_end: date,
schedule: Optional[dict[str, Any]] = None,
date_overrides: Optional[list[dict[str, Any]]] = None,
client: Optional[LogStormClient] = None,
) -> dict[str, Any]:
payload = build_logstorm_attendance_payload(
employee=employee,
period_start=period_start,
period_end=period_end,
schedule=schedule,
date_overrides=date_overrides,
)
return (client or LogStormClient()).analyze_attendance(payload)
def _normalize_date_override(override: dict[str, Any]) -> dict[str, Any]:
result = {
"date": _format_date(override["date"]),
"is_workday": bool(override["is_workday"]),
}
for key in ("reason", "start_time", "end_time", "expected_hours"):
if key in override and override[key] is not None:
result[key] = override[key]
return result
def _format_date(value: Any) -> str:
if isinstance(value, date):
return value.isoformat()
return str(value)
def _employee_display_name(employee: Employee) -> str:
full_name = employee.get_full_name().strip()
return full_name or str(employee)

View File

@ -0,0 +1,112 @@
from datetime import date
import pytest
from common.logstorm_attendance import (
analyze_employee_attendance,
build_logstorm_attendance_payload,
normalize_logstorm_schedule,
)
pytestmark = pytest.mark.django_db
class FakeLogStormClient:
def __init__(self):
self.payloads = []
def analyze_attendance(self, payload):
self.payloads.append(payload)
return {"records": []}
def test_payload_omits_schedule_when_eusrr_has_no_schedule(user_factory):
employee = user_factory(first_name="Ivan", last_name="Petrov")
payload = build_logstorm_attendance_payload(
employee=employee,
period_start=date(2026, 4, 20),
period_end=date(2026, 4, 21),
)
assert payload["employee_id"] == str(employee.id)
assert payload["display_name"] == "Ivan Petrov"
assert payload["period_start"] == "2026-04-20"
assert payload["period_end"] == "2026-04-21"
assert "schedule" not in payload
def test_payload_includes_schedule_and_date_overrides(user_factory):
employee = user_factory(first_name="Ivan", last_name="Petrov")
payload = build_logstorm_attendance_payload(
employee=employee,
period_start=date(2026, 4, 20),
period_end=date(2026, 4, 26),
schedule={
"start_time": "09:00",
"end_time": "18:00",
"expected_hours": 9,
"workdays": ["Monday"],
},
date_overrides=[
{
"date": date(2026, 4, 25),
"is_workday": True,
"reason": "transferred_workday",
"start_time": "10:00",
"end_time": "16:00",
"expected_hours": 6,
}
],
)
assert payload["schedule"] == {
"start_time": "09:00",
"end_time": "18:00",
"expected_hours": 9,
"workdays": ["Monday"],
"date_overrides": [
{
"date": "2026-04-25",
"is_workday": True,
"reason": "transferred_workday",
"start_time": "10:00",
"end_time": "16:00",
"expected_hours": 6,
}
],
}
def test_schedule_defaults_to_regular_workweek():
schedule = normalize_logstorm_schedule({
"start_time": "09:00",
"end_time": "18:00",
"expected_hours": 9,
})
assert schedule["workdays"] == [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
]
assert schedule["date_overrides"] == []
def test_analyze_employee_attendance_calls_logstorm_client(user_factory):
employee = user_factory(first_name="Ivan", last_name="Petrov")
client = FakeLogStormClient()
result = analyze_employee_attendance(
employee=employee,
period_start=date(2026, 4, 20),
period_end=date(2026, 4, 20),
client=client,
)
assert result == {"records": []}
assert client.payloads[0]["employee_id"] == str(employee.id)
assert "schedule" not in client.payloads[0]