Add LogStorm attendance payload builder
This commit is contained in:
parent
d5e4f6d577
commit
5bc0a33b50
109
backend/common/logstorm_attendance.py
Normal file
109
backend/common/logstorm_attendance.py
Normal 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)
|
||||
112
backend/tests/integration/test_logstorm_attendance.py
Normal file
112
backend/tests/integration/test_logstorm_attendance.py
Normal 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]
|
||||
Loading…
x
Reference in New Issue
Block a user