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

@@ -4,17 +4,29 @@ import json
from dataclasses import dataclass
from datetime import date, timedelta
# Символы, обозначающие день дежурства в ячейке duty (CSV с разделителем ;)
DUTY_MARKERS = frozenset({"б", "Б", "в", "Н", "О"})
# Символы дежурства в ячейке duty (CSV с разделителем ;)
DUTY_MARKERS = frozenset({"б", "Б", "в", "В"})
UNAVAILABLE_MARKER = "Н"
VACATION_MARKER = "О"
@dataclass
class DutyScheduleEntry:
"""One person's schedule: full_name and three lists of dates by event type."""
full_name: str
duty_dates: list[date]
unavailable_dates: list[date]
vacation_dates: list[date]
@dataclass
class DutyScheduleResult:
"""Parsed duty schedule: start_date, end_date, and per-person duty dates."""
"""Parsed duty schedule: start_date, end_date, and per-person entries."""
start_date: date
end_date: date
entries: list[tuple[str, list[date]]] # (full_name, list of duty dates)
entries: list[DutyScheduleEntry]
class DutyScheduleParseError(Exception):
@@ -24,12 +36,12 @@ class DutyScheduleParseError(Exception):
def parse_duty_schedule(raw_bytes: bytes) -> DutyScheduleResult:
"""Parse duty-schedule JSON. Returns start_date, end_date, and list of (full_name, duty_dates).
"""Parse duty-schedule JSON. Returns start_date, end_date, and list of DutyScheduleEntry.
- meta.start_date (YYYY-MM-DD) and schedule (array) required.
- meta.weeks optional; number of days from max duty string length (split by ';').
- For each schedule item: name (required), duty = CSV with ';'; index i = start_date + i days.
- Cell value after strip in DUTY_MARKERS => duty day.
- Cell value after strip: в/В/б/Б => duty, Н => unavailable, О => vacation; rest ignored.
"""
try:
data = json.loads(raw_bytes.decode("utf-8"))
@@ -53,7 +65,7 @@ def parse_duty_schedule(raw_bytes: bytes) -> DutyScheduleResult:
raise DutyScheduleParseError("Missing or invalid 'schedule' (must be array)")
max_days = 0
entries: list[tuple[str, list[date]]] = []
entries: list[DutyScheduleEntry] = []
for row in schedule:
if not isinstance(row, dict):
@@ -75,11 +87,24 @@ def parse_duty_schedule(raw_bytes: bytes) -> DutyScheduleResult:
max_days = max(max_days, len(cells))
duty_dates: list[date] = []
unavailable_dates: list[date] = []
vacation_dates: list[date] = []
for i, cell in enumerate(cells):
d = start_date + timedelta(days=i)
if cell in DUTY_MARKERS:
d = start_date + timedelta(days=i)
duty_dates.append(d)
entries.append((full_name, duty_dates))
elif cell == UNAVAILABLE_MARKER:
unavailable_dates.append(d)
elif cell == VACATION_MARKER:
vacation_dates.append(d)
entries.append(
DutyScheduleEntry(
full_name=full_name,
duty_dates=duty_dates,
unavailable_dates=unavailable_dates,
vacation_dates=vacation_dates,
)
)
if max_days == 0:
end_date = start_date