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

1"""Deleting a flight that is in more than one pilot's logbook. 

2 

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. 

7 

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""" 

16 

17from flask import flash # pyright: ignore[reportMissingImports] 

18from flask_babel import ngettext # pyright: ignore[reportMissingImports] 

19from models import Flight, db # pyright: ignore[reportMissingImports] 

20 

21 

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 

38 

39 

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 

48 

49 

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 

63 

64 

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 

75 

76 

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 )