Coverage for app/flights/crew_removal.py: 100%
42 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 15:29 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 15:29 +0000
1"""Deleting a flight that is in more than one pilot's logbook.
3A Flight row is shared by both crew slots, so a plain delete would remove the
4flight from *every* linked pilot's logbook — letting one pilot (or an aircraft
5owner) wipe someone else's hours. Every delete path goes through these
6helpers instead, following one rule: you can only ever remove yourself.
8- Pilot side (logbook delete, pilot logbook import rollback):
9 ``remove_flight_for_user`` unlinks only the acting pilot's slot when another
10 account is still linked, and deletes the row only when nobody else is.
11- Aircraft side (aircraft log delete, airframe/GPS import rollback, aircraft
12 deletion): ``remove_from_aircraft_log`` / ``detach_from_aircraft`` take the
13 flight off the aircraft's log but keep it in the linked pilots' logbooks as
14 an "other aircraft" flight, with the registration and type preserved.
15"""
17from flask import flash # pyright: ignore[reportMissingImports]
18from flask_babel import ngettext # pyright: ignore[reportMissingImports]
19from models import Flight, db # pyright: ignore[reportMissingImports]
22def unlink_user(fe: Flight, user_id: int | None) -> None:
23 """Clear *user_id*'s crew slot identity, that slot's own function hours
24 and personal remark. The free-text name stays; the flight's shared
25 figures are left alone since the other pilot still depends on them."""
26 if user_id is None:
27 return
28 if fe.pic_user_id == user_id:
29 fe.pic_user_id = None
30 fe.function_pic = None
31 fe.pic_remarks = None
32 if fe.second_crew_user_id == user_id:
33 fe.second_crew_user_id = None
34 fe.function_dual = None
35 fe.function_copilot = None
36 fe.function_instructor = None
37 fe.second_crew_remarks = None
40def remove_flight_for_user(fe: Flight, user_id: int) -> bool:
41 """Remove *fe* from *user_id*'s logbook. Returns True when the row was
42 deleted, False when it was kept for another linked pilot."""
43 if fe.other_linked_user_ids(user_id):
44 unlink_user(fe, user_id)
45 return False
46 db.session.delete(fe)
47 return True
50def detach_from_aircraft(fe: Flight) -> None:
51 """Turn a managed-aircraft flight into a standalone ("other aircraft")
52 flight, so it drops out of that aircraft's log, hours and maintenance
53 tracking while staying in the crew's pilot logbooks."""
54 if fe.aircraft_id is None:
55 return
56 fe.other_aircraft_registration = fe.display_registration
57 fe.other_aircraft_type = fe.display_aircraft_type
58 fe.other_aircraft_type_icao = fe.display_aircraft_type_icao
59 fe.aircraft_id = None
60 fe.reservation_id = None
61 fe.gps_import_batch_id = None
62 fe.airframe_import_batch_id = None
65def remove_from_aircraft_log(fe: Flight, actor_id: int | None) -> bool:
66 """Aircraft-side delete by *actor_id*. Returns True when the row was
67 deleted, False when it was detached and kept for linked pilots (the
68 actor's own link, if any, is removed)."""
69 if fe.other_linked_user_ids(actor_id):
70 unlink_user(fe, actor_id)
71 detach_from_aircraft(fe)
72 return False
73 db.session.delete(fe)
74 return True
77def flash_kept_in_pilot_logbooks(kept: int) -> None:
78 """Tell an aircraft-side bulk delete how many flights were detached and
79 kept for pilots instead of deleted."""
80 if kept:
81 flash(
82 ngettext(
83 "One flight stays in a pilot's logbook as a flight on another "
84 "aircraft; it was only removed from this aircraft's log.",
85 "%(num)d flights stay in pilots' logbooks as flights on another "
86 "aircraft; they were only removed from this aircraft's log.",
87 kept,
88 ),
89 "info",
90 )