Créneaux à la main + grille des besoins en axe des temps (30 min)
Un poste ne génère plus de créneaux à sa création : la coordination crée elle-même ceux dont elle a besoin, avec ses propres horaires de début et de fin (ConfigGrid.tsx). La génération depuis les créneaux types reste disponible en option (bouton « Générer les créneaux »). La grille des besoins devient un vrai axe des temps : une colonne = 30 min, bornée par jour à la plage réellement utilisée, avec les cases fusionnées pour correspondre aux horaires de chaque créneau (buildTimeGrid / placeInTimeGrid). La « Vue bénévoles » garde son ancien modèle (une colonne par créneau distinct), plus adapté à son tableau bénévole × créneau. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f1d88302a4
commit
15109acfe5
@@ -1,4 +1,9 @@
|
||||
"""Génération des créneaux d'un poste à partir des créneaux types."""
|
||||
"""Génération des créneaux d'un poste à partir des créneaux types.
|
||||
|
||||
Depuis le passage à la création de créneaux à la main (voir
|
||||
`test_shifts_api.py`), un `Position` ne génère plus rien automatiquement à sa
|
||||
création : `generate-shifts` reste l'outil optionnel pour repartir des
|
||||
créneaux types de l'édition."""
|
||||
|
||||
import datetime as dt
|
||||
|
||||
@@ -13,12 +18,26 @@ def _create_position(client, edition, name="Bar"):
|
||||
).json()
|
||||
|
||||
|
||||
def test_creating_a_position_generates_one_shift_per_day_and_type(client, edition):
|
||||
def _generate(client, edition, position):
|
||||
return client.post(f"{_positions_url(edition)}/{position['id']}/generate-shifts")
|
||||
|
||||
|
||||
def test_creating_a_position_generates_no_shifts(client, edition):
|
||||
position = _create_position(client, edition)
|
||||
shifts = client.get(
|
||||
f"/editions/{edition.id}/shifts", params={"position_id": position["id"]}
|
||||
).json()
|
||||
assert shifts == []
|
||||
|
||||
|
||||
def test_generate_shifts_creates_one_per_day_and_type(client, edition):
|
||||
position = _create_position(client, edition)
|
||||
resp = _generate(client, edition, position)
|
||||
# 4 jours (2026-07-09..12) x 3 créneaux types.
|
||||
assert resp.json()["created"] == 12
|
||||
shifts = client.get(
|
||||
f"/editions/{edition.id}/shifts", params={"position_id": position["id"]}
|
||||
).json()
|
||||
assert len(shifts) == 12
|
||||
assert all(s["min_staff"] == 0 and s["ideal_staff"] == 0 for s in shifts)
|
||||
assert all(s["day_part_id"] is not None for s in shifts)
|
||||
@@ -26,20 +45,18 @@ def test_creating_a_position_generates_one_shift_per_day_and_type(client, editio
|
||||
|
||||
def test_generate_is_idempotent(client, edition):
|
||||
position = _create_position(client, edition)
|
||||
resp = client.post(
|
||||
f"{_positions_url(edition)}/{position['id']}/generate-shifts"
|
||||
)
|
||||
_generate(client, edition, position)
|
||||
resp = _generate(client, edition, position)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["created"] == 0
|
||||
|
||||
|
||||
def test_extending_the_edition_then_generating_adds_the_new_days(client, edition):
|
||||
position = _create_position(client, edition)
|
||||
_generate(client, edition, position)
|
||||
client.patch(f"/editions/{edition.id}", json={"end_date": "2026-07-14"})
|
||||
|
||||
resp = client.post(
|
||||
f"{_positions_url(edition)}/{position['id']}/generate-shifts"
|
||||
)
|
||||
resp = _generate(client, edition, position)
|
||||
# 2 jours de plus x 3 créneaux types.
|
||||
assert resp.json()["created"] == 6
|
||||
shifts = client.get(
|
||||
@@ -50,13 +67,12 @@ def test_extending_the_edition_then_generating_adds_the_new_days(client, edition
|
||||
|
||||
def test_adding_a_day_part_then_generating_adds_it_everywhere(client, edition):
|
||||
position = _create_position(client, edition)
|
||||
_generate(client, edition, position)
|
||||
client.post(
|
||||
f"/editions/{edition.id}/day-parts",
|
||||
json={"label": "Nuit", "start_time": "02:00", "end_time": "05:00"},
|
||||
)
|
||||
resp = client.post(
|
||||
f"{_positions_url(edition)}/{position['id']}/generate-shifts"
|
||||
)
|
||||
resp = _generate(client, edition, position)
|
||||
assert resp.json()["created"] == 4 # 1 par jour
|
||||
|
||||
|
||||
@@ -67,11 +83,12 @@ def test_day_part_before_the_day_boundary_rolls_to_the_next_calendar_day(client,
|
||||
suite de `Soirée` 19 h–00 h) est donc la fin de la nuit du jour
|
||||
*précédent* : il doit être généré sur le jour suivant, pas sur `day`."""
|
||||
position = _create_position(client, edition)
|
||||
_generate(client, edition, position)
|
||||
client.post(
|
||||
f"/editions/{edition.id}/day-parts",
|
||||
json={"label": "Nuit", "start_time": "02:00", "end_time": "05:00", "order": 3},
|
||||
)
|
||||
client.post(f"{_positions_url(edition)}/{position['id']}/generate-shifts")
|
||||
_generate(client, edition, position)
|
||||
|
||||
shifts = client.get(
|
||||
f"/editions/{edition.id}/shifts", params={"position_id": position["id"]}
|
||||
@@ -89,6 +106,7 @@ def test_day_part_before_the_day_boundary_rolls_to_the_next_calendar_day(client,
|
||||
|
||||
def test_generation_does_not_touch_staffing_already_set(client, edition):
|
||||
position = _create_position(client, edition)
|
||||
_generate(client, edition, position)
|
||||
shift = client.get(
|
||||
f"/editions/{edition.id}/shifts", params={"position_id": position["id"]}
|
||||
).json()[0]
|
||||
@@ -96,6 +114,6 @@ def test_generation_does_not_touch_staffing_already_set(client, edition):
|
||||
f"/editions/{edition.id}/shifts/{shift['id']}",
|
||||
json={"min_staff": 3, "ideal_staff": 5},
|
||||
)
|
||||
client.post(f"{_positions_url(edition)}/{position['id']}/generate-shifts")
|
||||
_generate(client, edition, position)
|
||||
again = client.get(f"/editions/{edition.id}/shifts/{shift['id']}").json()
|
||||
assert again["min_staff"] == 3 and again["ideal_staff"] == 5
|
||||
|
||||
Reference in New Issue
Block a user