Add event type handling for duties in the system

- Introduced a new `event_type` column in the `duties` table to categorize duties as 'duty', 'unavailable', or 'vacation'.
- Updated the duty schedule import functionality to parse and store event types from the JSON input.
- Enhanced the API response to include event types for each duty, improving the calendar display logic.
- Modified the web application to visually differentiate between duty types in the calendar and duty list.
- Updated tests to cover new event type functionality and ensure correct parsing and storage of duties.
- Revised README documentation to reflect changes in duty event types and their representation in the system.
This commit is contained in:
2026-02-17 23:01:07 +03:00
parent 78a1696a69
commit 7a963eccd1
12 changed files with 279 additions and 60 deletions

View File

@@ -6,7 +6,10 @@ import pytest
from importers.duty_schedule import (
DUTY_MARKERS,
UNAVAILABLE_MARKER,
VACATION_MARKER,
DutyScheduleParseError,
DutyScheduleEntry,
parse_duty_schedule,
)
@@ -24,16 +27,19 @@ def test_parse_valid_schedule():
# Petrov has 7 cells -> end = start + 6
assert result.end_date == date(2026, 2, 22)
assert len(result.entries) == 2
names = [e[0] for e in result.entries]
assert "Ivanov I.I." in names
assert "Petrov P.P." in names
by_name = {e[0]: e[1] for e in result.entries}
# Ivanov: indices 2, 3, 4 are duty (б, Б, в) -> 2026-02-18, 19, 20
ivan_dates = sorted(by_name["Ivanov I.I."])
assert ivan_dates == [date(2026, 2, 18), date(2026, 2, 19), date(2026, 2, 20)]
# Petrov: indices 1, 2 (Н, О) -> 2026-02-17, 18
petr_dates = sorted(by_name["Petrov P.P."])
assert petr_dates == [date(2026, 2, 17), date(2026, 2, 18)]
by_name = {e.full_name: e for e in result.entries}
assert "Ivanov I.I." in by_name
assert "Petrov P.P." in by_name
# Ivanov: only duty (б, Б, в) -> 2026-02-18, 19, 20
ivan = by_name["Ivanov I.I."]
assert sorted(ivan.duty_dates) == [date(2026, 2, 18), date(2026, 2, 19), date(2026, 2, 20)]
assert ivan.unavailable_dates == []
assert ivan.vacation_dates == []
# Petrov: one Н (unavailable), one О (vacation) -> 2026-02-17, 18
petr = by_name["Petrov P.P."]
assert petr.duty_dates == []
assert petr.unavailable_dates == [date(2026, 2, 17)]
assert petr.vacation_dates == [date(2026, 2, 18)]
def test_parse_empty_duty_string():
@@ -41,7 +47,11 @@ def test_parse_empty_duty_string():
result = parse_duty_schedule(raw)
assert result.start_date == date(2026, 2, 1)
assert result.end_date == date(2026, 2, 1)
assert result.entries == [("A", [])]
assert len(result.entries) == 1
assert result.entries[0].full_name == "A"
assert result.entries[0].duty_dates == []
assert result.entries[0].unavailable_dates == []
assert result.entries[0].vacation_dates == []
def test_parse_invalid_json():
@@ -89,4 +99,19 @@ def test_parse_schedule_item_empty_name():
def test_duty_markers():
assert "б" in DUTY_MARKERS and "Б" in DUTY_MARKERS and "в" in DUTY_MARKERS
assert DUTY_MARKERS == frozenset({"б", "Б", "в", "В"})
assert "Н" not in DUTY_MARKERS and "О" not in DUTY_MARKERS
def test_unavailable_and_vacation_markers():
assert UNAVAILABLE_MARKER == "Н"
assert VACATION_MARKER == "О"
raw = (
'{"meta": {"start_date": "2026-02-01"}, "schedule": ['
'{"name": "X", "duty": "\u041d; \u041e; \u0432"}]}'
).encode("utf-8")
result = parse_duty_schedule(raw)
entry = result.entries[0]
assert entry.unavailable_dates == [date(2026, 2, 1)]
assert entry.vacation_dates == [date(2026, 2, 2)]
assert entry.duty_dates == [date(2026, 2, 3)]