fix(absences): POST /absences/ reicht warnings jetzt an den Client durch
create_absence() gibt (absence, warnings) zurück, der Router verwarf die Warnungen bisher (im Unterschied zu approve_absence, das sie via AbsenceApproveOut.warnings durchreicht). Neues AbsenceCreateOut-Schema analog dazu. Regressionstest über Überschneidungs-Warnung. Gefunden während Coverage-Arbeit an absence_service.py. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
This commit is contained in:
@@ -262,7 +262,11 @@ async def list_absences(
|
|||||||
return AbsenceListResponse(total=total, items=[AbsenceOut.model_validate(a) for a in absences])
|
return AbsenceListResponse(total=total, items=[AbsenceOut.model_validate(a) for a in absences])
|
||||||
|
|
||||||
|
|
||||||
@router.post("/absences/", response_model=AbsenceOut, status_code=201)
|
class AbsenceCreateOut(AbsenceOut):
|
||||||
|
warnings: list[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/absences/", response_model=AbsenceCreateOut, status_code=201)
|
||||||
async def create_absence(
|
async def create_absence(
|
||||||
data: AbsenceCreate,
|
data: AbsenceCreate,
|
||||||
current_user: CurrentUser,
|
current_user: CurrentUser,
|
||||||
@@ -281,7 +285,9 @@ async def create_absence(
|
|||||||
acting_user = target
|
acting_user = target
|
||||||
absence, warnings = await absence_service.create_absence(data, acting_user, db)
|
absence, warnings = await absence_service.create_absence(data, acting_user, db)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
return AbsenceOut.model_validate(absence)
|
out = AbsenceCreateOut.model_validate(absence)
|
||||||
|
out.warnings = warnings
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/absences/{absence_id}", response_model=AbsenceOut)
|
@router.patch("/absences/{absence_id}", response_model=AbsenceOut)
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ Deckt:
|
|||||||
+ Schwellwert-Grenzfall working_days == two_stage_min_days
|
+ Schwellwert-Grenzfall working_days == two_stage_min_days
|
||||||
5. DATEV-Export: Feiertag-Kürzel "F" schlägt Urlaubs-Kürzel "U"; working_days konsistent
|
5. DATEV-Export: Feiertag-Kürzel "F" schlägt Urlaubs-Kürzel "U"; working_days konsistent
|
||||||
6. _categorize_hours: Nachtschicht über Mitternacht + Sonntag+Feiertag (höchster Zuschlag gewinnt)
|
6. _categorize_hours: Nachtschicht über Mitternacht + Sonntag+Feiertag (höchster Zuschlag gewinnt)
|
||||||
|
7. POST /absences/ reicht warnings jetzt durch (API-Konsistenz-Fix)
|
||||||
|
|
||||||
|
Nebenbei gefixte Bugs (während dieser Coverage-Arbeit gefunden):
|
||||||
|
- _get_holiday_dates() rechnete Feiertage im Folgejahr bei Anträgen über den
|
||||||
|
Jahreswechsel nicht aus working_days heraus (siehe
|
||||||
|
test_holiday_dates_cover_year_boundary + absence_service.py year_to-Parameter)
|
||||||
|
- POST /absences/ verwarf die warnings-Liste von create_absence() (siehe Test 7)
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
@@ -116,12 +123,9 @@ async def test_carryover_expiry_via_create_absence(
|
|||||||
nicht das Signal 'Konto reicht' bekommen, obwohl entitled+carried_over rechnerisch
|
nicht das Signal 'Konto reicht' bekommen, obwohl entitled+carried_over rechnerisch
|
||||||
reichen würde – der verfallene Übertrag darf effective_available nicht aufblähen.
|
reichen würde – der verfallene Übertrag darf effective_available nicht aufblähen.
|
||||||
|
|
||||||
Ruft absence_service.create_absence() direkt auf (statt über HTTP), weil der
|
Ruft absence_service.create_absence() direkt auf (statt über HTTP) um den
|
||||||
Router POST /absences/ die von create_absence() zurückgegebenen `warnings`
|
warnings-Rückgabewert isoliert vom HTTP-Layer zu prüfen (Test 7 unten deckt
|
||||||
**verwirft** (siehe app/routers/absences.py::create_absence – nur approve_absence
|
den HTTP-Pfad inkl. warnings-Durchreichung separat ab)."""
|
||||||
reicht `warnings` über AbsenceOut.warnings durch). Das ist ein eigenständiger,
|
|
||||||
hier entdeckter Bug (siehe Abschlussbericht), separat vom hier geprüften
|
|
||||||
Verfalls-Verhalten von effective_available()."""
|
|
||||||
from app.services.absence_service import absence_service
|
from app.services.absence_service import absence_service
|
||||||
from app.schemas.absence import AbsenceCreate
|
from app.schemas.absence import AbsenceCreate
|
||||||
|
|
||||||
@@ -580,3 +584,31 @@ def test_categorize_hours_sunday_and_holiday_highest_wins():
|
|||||||
assert result.holiday_150_hours == pytest.approx(8.0)
|
assert result.holiday_150_hours == pytest.approx(8.0)
|
||||||
assert result.sunday_hours == pytest.approx(0.0)
|
assert result.sunday_hours == pytest.approx(0.0)
|
||||||
assert result.holiday_125_hours == pytest.approx(0.0)
|
assert result.holiday_125_hours == pytest.approx(0.0)
|
||||||
|
|
||||||
|
|
||||||
|
# ── 7. POST /absences/ gibt warnings zurück (API-Konsistenz-Fix) ────────────────
|
||||||
|
|
||||||
|
async def test_create_absence_returns_warnings(
|
||||||
|
client: AsyncClient, cov_headers, cov_vacation_type_id,
|
||||||
|
):
|
||||||
|
"""Regressionstest: POST /absences/ verwarf bisher die von create_absence()
|
||||||
|
zurückgegebenen warnings (im Unterschied zu approve_absence, das sie über
|
||||||
|
AbsenceApproveOut.warnings durchreicht). Fix: neues AbsenceCreateOut-Schema.
|
||||||
|
Trigger hier: Überschneidungs-Warnung bei zwei sich überlappenden Anträgen."""
|
||||||
|
start = _future_monday(20)
|
||||||
|
end = start + timedelta(days=2)
|
||||||
|
|
||||||
|
resp1 = await client.post("/api/v1/absences/", json={
|
||||||
|
"type_id": str(cov_vacation_type_id), "start_date": str(start), "end_date": str(end),
|
||||||
|
}, headers=cov_headers)
|
||||||
|
assert resp1.status_code == 201, resp1.text
|
||||||
|
assert resp1.json().get("warnings") == []
|
||||||
|
|
||||||
|
# Überlappender zweiter Antrag im selben Zeitraum -> Warnung erwartet
|
||||||
|
resp2 = await client.post("/api/v1/absences/", json={
|
||||||
|
"type_id": str(cov_vacation_type_id), "start_date": str(start), "end_date": str(end + timedelta(days=1)),
|
||||||
|
}, headers=cov_headers)
|
||||||
|
assert resp2.status_code == 201, resp2.text
|
||||||
|
data2 = resp2.json()
|
||||||
|
assert "warnings" in data2
|
||||||
|
assert any("Überschneidung" in w for w in data2["warnings"]), data2["warnings"]
|
||||||
|
|||||||
Reference in New Issue
Block a user