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:
@@ -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)]
|
||||
|
||||
@@ -5,10 +5,9 @@ from datetime import date
|
||||
import pytest
|
||||
|
||||
from db import init_db
|
||||
from db.models import Base
|
||||
from db.repository import get_duties
|
||||
from db.session import get_session
|
||||
from importers.duty_schedule import DutyScheduleResult, parse_duty_schedule
|
||||
from importers.duty_schedule import DutyScheduleEntry, DutyScheduleResult, parse_duty_schedule
|
||||
|
||||
from handlers.import_duty_schedule import _run_import
|
||||
|
||||
@@ -36,17 +35,28 @@ def test_import_creates_users_and_duties(db_url):
|
||||
start_date=date(2026, 2, 16),
|
||||
end_date=date(2026, 2, 18),
|
||||
entries=[
|
||||
("Ivanov I.I.", [date(2026, 2, 16), date(2026, 2, 18)]),
|
||||
("Petrov P.P.", [date(2026, 2, 17)]),
|
||||
DutyScheduleEntry(
|
||||
full_name="Ivanov I.I.",
|
||||
duty_dates=[date(2026, 2, 16), date(2026, 2, 18)],
|
||||
unavailable_dates=[],
|
||||
vacation_dates=[],
|
||||
),
|
||||
DutyScheduleEntry(
|
||||
full_name="Petrov P.P.",
|
||||
duty_dates=[date(2026, 2, 17)],
|
||||
unavailable_dates=[],
|
||||
vacation_dates=[],
|
||||
),
|
||||
],
|
||||
)
|
||||
num_users, num_duties = _run_import(db_url, result, 6, 0)
|
||||
num_users, num_duty, num_unav, num_vac = _run_import(db_url, result, 6, 0)
|
||||
assert num_users == 2
|
||||
assert num_duties == 3
|
||||
assert num_duty == 3
|
||||
assert num_unav == 0
|
||||
assert num_vac == 0
|
||||
|
||||
session = get_session(db_url)
|
||||
try:
|
||||
# to_date inclusive: duty on 18th has start_at 2026-02-18T06:00:00Z, so use 2026-02-19
|
||||
duties = get_duties(session, "2026-02-16", "2026-02-19")
|
||||
finally:
|
||||
session.close()
|
||||
@@ -56,6 +66,8 @@ def test_import_creates_users_and_duties(db_url):
|
||||
assert "2026-02-16T06:00:00Z" in starts
|
||||
assert "2026-02-17T06:00:00Z" in starts
|
||||
assert "2026-02-18T06:00:00Z" in starts
|
||||
for d, _ in duties:
|
||||
assert d.event_type == "duty"
|
||||
|
||||
|
||||
def test_import_replaces_duties_in_range(db_url):
|
||||
@@ -63,7 +75,14 @@ def test_import_replaces_duties_in_range(db_url):
|
||||
result1 = DutyScheduleResult(
|
||||
start_date=date(2026, 2, 16),
|
||||
end_date=date(2026, 2, 17),
|
||||
entries=[("Sidorov", [date(2026, 2, 16), date(2026, 2, 17)])],
|
||||
entries=[
|
||||
DutyScheduleEntry(
|
||||
full_name="Sidorov",
|
||||
duty_dates=[date(2026, 2, 16), date(2026, 2, 17)],
|
||||
unavailable_dates=[],
|
||||
vacation_dates=[],
|
||||
)
|
||||
],
|
||||
)
|
||||
_run_import(db_url, result1, 9, 0)
|
||||
|
||||
@@ -77,7 +96,14 @@ def test_import_replaces_duties_in_range(db_url):
|
||||
result2 = DutyScheduleResult(
|
||||
start_date=date(2026, 2, 16),
|
||||
end_date=date(2026, 2, 17),
|
||||
entries=[("Sidorov", [date(2026, 2, 17)])],
|
||||
entries=[
|
||||
DutyScheduleEntry(
|
||||
full_name="Sidorov",
|
||||
duty_dates=[date(2026, 2, 17)],
|
||||
unavailable_dates=[],
|
||||
vacation_dates=[],
|
||||
)
|
||||
],
|
||||
)
|
||||
_run_import(db_url, result2, 9, 0)
|
||||
|
||||
@@ -97,9 +123,11 @@ def test_import_full_flow_parse_then_import(db_url):
|
||||
'"schedule": [{"name": "Alexey A.", "duty": "\u0431; ; \u0432"}]}'
|
||||
).encode("utf-8")
|
||||
parsed = parse_duty_schedule(raw)
|
||||
num_users, num_duties = _run_import(db_url, parsed, 6, 0)
|
||||
num_users, num_duty, num_unav, num_vac = _run_import(db_url, parsed, 6, 0)
|
||||
assert num_users == 1
|
||||
assert num_duties == 2
|
||||
assert num_duty == 2
|
||||
assert num_unav == 0
|
||||
assert num_vac == 0
|
||||
|
||||
session = get_session(db_url)
|
||||
try:
|
||||
@@ -108,3 +136,32 @@ def test_import_full_flow_parse_then_import(db_url):
|
||||
session.close()
|
||||
assert len(duties) == 2
|
||||
assert duties[0][1] == "Alexey A."
|
||||
assert duties[0][0].event_type == "duty"
|
||||
|
||||
|
||||
def test_import_event_types_unavailable_vacation(db_url):
|
||||
"""Import creates records with event_type duty, unavailable, vacation."""
|
||||
result = DutyScheduleResult(
|
||||
start_date=date(2026, 2, 16),
|
||||
end_date=date(2026, 2, 18),
|
||||
entries=[
|
||||
DutyScheduleEntry(
|
||||
full_name="Mixed User",
|
||||
duty_dates=[date(2026, 2, 16)],
|
||||
unavailable_dates=[date(2026, 2, 17)],
|
||||
vacation_dates=[date(2026, 2, 18)],
|
||||
),
|
||||
],
|
||||
)
|
||||
num_users, num_duty, num_unav, num_vac = _run_import(db_url, result, 6, 0)
|
||||
assert num_users == 1
|
||||
assert num_duty == 1 and num_unav == 1 and num_vac == 1
|
||||
|
||||
session = get_session(db_url)
|
||||
try:
|
||||
duties = get_duties(session, "2026-02-16", "2026-02-19")
|
||||
finally:
|
||||
session.close()
|
||||
assert len(duties) == 3
|
||||
types = {d[0].event_type for d in duties}
|
||||
assert types == {"duty", "unavailable", "vacation"}
|
||||
|
||||
Reference in New Issue
Block a user