fix(planning): scope Pronote removals to imported week

This commit is contained in:
root 2026-08-22 23:26:02 +00:00
parent 8248ca0df3
commit 90de40e227
3 changed files with 33 additions and 3 deletions

View file

@ -79,7 +79,7 @@ def available_slots(room_id, day, window_start=time(8), window_end=time(17), dur
return slots return slots
def sync_pronote_schedules(room_id, entries, *, synced_at=None): def sync_pronote_schedules(room_id, entries, *, synced_at=None, week_start=None):
"""Applique un lot Pronote sans toucher aux créneaux manuels protégés. """Applique un lot Pronote sans toucher aux créneaux manuels protégés.
``entries`` est une liste de dictionnaires normalisés par l'intégration ``entries`` est une liste de dictionnaires normalisés par l'intégration
@ -87,6 +87,9 @@ def sync_pronote_schedules(room_id, entries, *, synced_at=None):
libellés optionnels). Cette fonction ne contacte jamais Pronote elle-même. libellés optionnels). Cette fonction ne contacte jamais Pronote elle-même.
""" """
synced_at = synced_at or datetime.utcnow() synced_at = synced_at or datetime.utcnow()
synced_weeks = {entry.get("week_start") for entry in entries if entry.get("week_start")}
if week_start:
synced_weeks.add(week_start)
seen = set() seen = set()
for entry in entries: for entry in entries:
external_id = str(entry.get("external_id") or "").strip() external_id = str(entry.get("external_id") or "").strip()
@ -140,7 +143,10 @@ def sync_pronote_schedules(room_id, entries, *, synced_at=None):
note = "Conflit avec un créneau manuel protégé" note = "Conflit avec un créneau manuel protégé"
manual.conflict_note = note manual.conflict_note = note
row.conflict_note = note row.conflict_note = note
existing = RoomSchedule.query.filter_by(room_id=room_id, source="pronote").all() existing_query = RoomSchedule.query.filter_by(room_id=room_id, source="pronote")
if synced_weeks:
existing_query = existing_query.filter(RoomSchedule.week_start.in_(synced_weeks))
existing = existing_query.all()
for row in existing: for row in existing:
if row.external_id not in seen: if row.external_id not in seen:
row.resolution_status = "disabled" row.resolution_status = "disabled"

View file

@ -207,7 +207,7 @@ def import_room_planning(room_id):
try: try:
lessons = get_lessons_for_room(client, room.name, monday, monday + timedelta(days=6), strict=True) lessons = get_lessons_for_room(client, room.name, monday, monday + timedelta(days=6), strict=True)
entries = normalize_pronote_lessons(lessons, monday) entries = normalize_pronote_lessons(lessons, monday)
sync_pronote_schedules(room.id, entries) sync_pronote_schedules(room.id, entries, week_start=monday)
flash(f'{len(entries)} créneaux Pronote synchronisés. Les protections locales ont été conservées.', 'success') flash(f'{len(entries)} créneaux Pronote synchronisés. Les protections locales ont été conservées.', 'success')
except Exception: except Exception:
db.session.rollback() db.session.rollback()

View file

@ -172,6 +172,30 @@ def test_pronote_sync_change_removal_ambiguity_and_freshness(app):
assert db.session.get(RoomSchedule, row.id) is not None assert db.session.get(RoomSchedule, row.id) is not None
def test_pronote_sync_removal_is_scoped_to_imported_week(app):
with app.app_context():
building = Building(name="Phase15 scope building")
db.session.add(building)
db.session.flush()
room = Room(name="Phase15 scope room", building_id=building.id)
db.session.add(room)
db.session.flush()
current_week, next_week = date(2026, 9, 7), date(2026, 9, 14)
current = RoomSchedule(room_id=room.id, week_start=current_week, day_of_week=0,
start_time=time(8), end_time=time(9), source="pronote",
external_id="scope-current")
future = RoomSchedule(room_id=room.id, week_start=next_week, day_of_week=0,
start_time=time(8), end_time=time(9), source="pronote",
external_id="scope-future")
db.session.add_all([current, future])
db.session.commit()
sync_pronote_schedules(room.id, [], week_start=current_week)
db.session.refresh(current)
db.session.refresh(future)
assert current.resolution_status == "disabled"
assert future.resolution_status == "active"
def test_normalize_pronote_lessons_is_stable_and_skips_invalid_entries(): def test_normalize_pronote_lessons_is_stable_and_skips_invalid_entries():
week = date(2026, 9, 7) week = date(2026, 9, 7)
lessons = [{ lessons = [{