Journaling for all flights

This commit is contained in:
2025-12-18 07:34:19 -05:00
parent f3eb83665f
commit a2682314c9
16 changed files with 594 additions and 87 deletions

View File

@@ -4,6 +4,8 @@ from sqlalchemy import and_, or_, func, desc
from datetime import date, datetime
from app.models.arrival import Arrival, ArrivalStatus
from app.schemas.arrival import ArrivalCreate, ArrivalUpdate, ArrivalStatusUpdate
from app.models.journal import EntityType
from app.crud.crud_journal import journal
class CRUDArrival:
@@ -56,16 +58,43 @@ class CRUDArrival:
db.refresh(db_obj)
return db_obj
def update(self, db: Session, db_obj: Arrival, obj_in: ArrivalUpdate) -> Arrival:
def update(self, db: Session, db_obj: Arrival, obj_in: ArrivalUpdate, user: str = "system", user_ip: Optional[str] = None) -> Arrival:
from datetime import datetime as dt
update_data = obj_in.dict(exclude_unset=True)
changes = []
for field, value in update_data.items():
if value is not None:
old_value = getattr(db_obj, field)
# Normalize datetime values for comparison (ignore timezone differences)
if isinstance(old_value, dt) and isinstance(value, dt):
# Compare only the date and time, ignoring timezone
old_normalized = old_value.replace(tzinfo=None) if old_value.tzinfo else old_value
new_normalized = value.replace(tzinfo=None) if value.tzinfo else value
if old_normalized == new_normalized:
continue # Skip if datetimes are the same
if old_value != value:
changes.append(f"{field} changed from '{old_value}' to '{value}'")
setattr(db_obj, field, value)
db.add(db_obj)
db.commit()
db.refresh(db_obj)
if changes:
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log changes in journal
for change in changes:
journal.log_change(
db,
EntityType.ARRIVAL,
db_obj.id,
change,
user,
user_ip
)
return db_obj
def update_status(
@@ -73,12 +102,15 @@ class CRUDArrival:
db: Session,
arrival_id: int,
status: ArrivalStatus,
timestamp: Optional[datetime] = None
timestamp: Optional[datetime] = None,
user: str = "system",
user_ip: Optional[str] = None
) -> Optional[Arrival]:
db_obj = self.get(db, arrival_id)
if not db_obj:
return None
old_status = db_obj.status
db_obj.status = status
if status == ArrivalStatus.LANDED and timestamp:
@@ -87,6 +119,17 @@ class CRUDArrival:
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log status change in journal
journal.log_change(
db,
EntityType.ARRIVAL,
arrival_id,
f"Status changed from {old_status.value} to {status.value}",
user,
user_ip
)
return db_obj
def cancel(self, db: Session, arrival_id: int) -> Optional[Arrival]:

View File

@@ -4,6 +4,8 @@ from sqlalchemy import and_, or_, func, desc
from datetime import date, datetime
from app.models.departure import Departure, DepartureStatus
from app.schemas.departure import DepartureCreate, DepartureUpdate, DepartureStatusUpdate
from app.models.journal import EntityType
from app.crud.crud_journal import journal
class CRUDDeparture:
@@ -56,16 +58,43 @@ class CRUDDeparture:
db.refresh(db_obj)
return db_obj
def update(self, db: Session, db_obj: Departure, obj_in: DepartureUpdate) -> Departure:
def update(self, db: Session, db_obj: Departure, obj_in: DepartureUpdate, user: str = "system", user_ip: Optional[str] = None) -> Departure:
from datetime import datetime as dt
update_data = obj_in.dict(exclude_unset=True)
changes = []
for field, value in update_data.items():
if value is not None:
old_value = getattr(db_obj, field)
# Normalize datetime values for comparison (ignore timezone differences)
if isinstance(old_value, dt) and isinstance(value, dt):
# Compare only the date and time, ignoring timezone
old_normalized = old_value.replace(tzinfo=None) if old_value.tzinfo else old_value
new_normalized = value.replace(tzinfo=None) if value.tzinfo else value
if old_normalized == new_normalized:
continue # Skip if datetimes are the same
if old_value != value:
changes.append(f"{field} changed from '{old_value}' to '{value}'")
setattr(db_obj, field, value)
db.add(db_obj)
db.commit()
db.refresh(db_obj)
if changes:
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log changes in journal
for change in changes:
journal.log_change(
db,
EntityType.DEPARTURE,
db_obj.id,
change,
user,
user_ip
)
return db_obj
def update_status(
@@ -73,12 +102,15 @@ class CRUDDeparture:
db: Session,
departure_id: int,
status: DepartureStatus,
timestamp: Optional[datetime] = None
timestamp: Optional[datetime] = None,
user: str = "system",
user_ip: Optional[str] = None
) -> Optional[Departure]:
db_obj = self.get(db, departure_id)
if not db_obj:
return None
old_status = db_obj.status
db_obj.status = status
if status == DepartureStatus.DEPARTED and timestamp:
@@ -87,6 +119,17 @@ class CRUDDeparture:
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log status change in journal
journal.log_change(
db,
EntityType.DEPARTURE,
departure_id,
f"Status changed from {old_status.value} to {status.value}",
user,
user_ip
)
return db_obj
def cancel(self, db: Session, departure_id: int) -> Optional[Departure]:

View File

@@ -1,35 +1,95 @@
from typing import List
from typing import List, Optional
from sqlalchemy.orm import Session
from app.models.ppr import Journal
from app.schemas.ppr import JournalCreate
from app.models.journal import JournalEntry, EntityType
from datetime import datetime
class CRUDJournal:
def create(self, db: Session, obj_in: JournalCreate) -> Journal:
db_obj = Journal(**obj_in.dict())
db.add(db_obj)
db.commit()
db.refresh(db_obj)
return db_obj
def get_by_ppr_id(self, db: Session, ppr_id: int) -> List[Journal]:
return db.query(Journal).filter(Journal.ppr_id == ppr_id).order_by(Journal.entry_dt.desc()).all()
"""CRUD operations for the generic journal table.
This journal is immutable - entries can only be created (by backend) and queried.
There are no API endpoints for creating journal entries; the backend logs changes directly.
"""
def log_change(
self,
db: Session,
ppr_id: int,
entity_type: EntityType,
entity_id: int,
entry: str,
user: str,
ip: str
) -> Journal:
journal_in = JournalCreate(
ppr_id=ppr_id,
user: str,
ip: Optional[str] = None
) -> JournalEntry:
"""Log a change to an entity. Internal backend use only."""
journal_entry = JournalEntry(
entity_type=entity_type.value,
entity_id=entity_id,
entry=entry,
user=user,
ip=ip,
entry_dt=datetime.utcnow()
)
db.add(journal_entry)
db.commit()
db.refresh(journal_entry)
return journal_entry
def get_entity_journal(
self,
db: Session,
entity_type: EntityType,
entity_id: int,
limit: int = 100
) -> List[JournalEntry]:
"""Get all journal entries for a specific entity. Read-only API endpoint."""
return db.query(JournalEntry).filter(
JournalEntry.entity_type == entity_type.value,
JournalEntry.entity_id == entity_id
).order_by(JournalEntry.entry_dt.desc()).limit(limit).all()
def get_user_journal(
self,
db: Session,
user: str,
limit: int = 100
) -> List[JournalEntry]:
"""Get all journal entries created by a specific user."""
return db.query(JournalEntry).filter(
JournalEntry.user == user
).order_by(JournalEntry.entry_dt.desc()).limit(limit).all()
# Convenience methods for backward compatibility with PPR journal
def log_ppr_change(
self,
db: Session,
ppr_id: int,
entry: str,
user: str,
ip: Optional[str] = None
) -> JournalEntry:
"""Log a change to a PPR (convenience method)."""
return self.log_change(
db=db,
entity_type=EntityType.PPR,
entity_id=ppr_id,
entry=entry,
user=user,
ip=ip
)
return self.create(db, journal_in)
def get_ppr_journal(
self,
db: Session,
ppr_id: int,
limit: int = 100
) -> List[JournalEntry]:
"""Get all journal entries for a PPR (convenience method)."""
return self.get_entity_journal(
db=db,
entity_type=EntityType.PPR,
entity_id=ppr_id,
limit=limit
)
journal = CRUDJournal()

View File

@@ -4,6 +4,8 @@ from sqlalchemy import and_, or_, func, desc
from datetime import date, datetime
from app.models.local_flight import LocalFlight, LocalFlightStatus, LocalFlightType
from app.schemas.local_flight import LocalFlightCreate, LocalFlightUpdate, LocalFlightStatusUpdate
from app.models.journal import EntityType
from app.crud.crud_journal import journal
class CRUDLocalFlight:
@@ -82,16 +84,43 @@ class CRUDLocalFlight:
db.refresh(db_obj)
return db_obj
def update(self, db: Session, db_obj: LocalFlight, obj_in: LocalFlightUpdate) -> LocalFlight:
def update(self, db: Session, db_obj: LocalFlight, obj_in: LocalFlightUpdate, user: str = "system", user_ip: Optional[str] = None) -> LocalFlight:
from datetime import datetime as dt
update_data = obj_in.dict(exclude_unset=True)
changes = []
for field, value in update_data.items():
if value is not None:
old_value = getattr(db_obj, field)
# Normalize datetime values for comparison (ignore timezone differences)
if isinstance(old_value, dt) and isinstance(value, dt):
# Compare only the date and time, ignoring timezone
old_normalized = old_value.replace(tzinfo=None) if old_value.tzinfo else old_value
new_normalized = value.replace(tzinfo=None) if value.tzinfo else value
if old_normalized == new_normalized:
continue # Skip if datetimes are the same
if old_value != value:
changes.append(f"{field} changed from '{old_value}' to '{value}'")
setattr(db_obj, field, value)
db.add(db_obj)
db.commit()
db.refresh(db_obj)
if changes:
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log changes in journal
for change in changes:
journal.log_change(
db,
EntityType.LOCAL_FLIGHT,
db_obj.id,
change,
user,
user_ip
)
return db_obj
def update_status(
@@ -99,7 +128,9 @@ class CRUDLocalFlight:
db: Session,
flight_id: int,
status: LocalFlightStatus,
timestamp: Optional[datetime] = None
timestamp: Optional[datetime] = None,
user: str = "system",
user_ip: Optional[str] = None
) -> Optional[LocalFlight]:
db_obj = self.get(db, flight_id)
if not db_obj:
@@ -109,6 +140,7 @@ class CRUDLocalFlight:
if isinstance(status, str):
status = LocalFlightStatus(status)
old_status = db_obj.status
db_obj.status = status
# Set timestamps based on status
@@ -121,6 +153,17 @@ class CRUDLocalFlight:
db.add(db_obj)
db.commit()
db.refresh(db_obj)
# Log status change in journal
journal.log_change(
db,
EntityType.LOCAL_FLIGHT,
flight_id,
f"Status changed from {old_status.value} to {status.value}",
user,
user_ip
)
return db_obj
def cancel(self, db: Session, flight_id: int) -> Optional[LocalFlight]:

View File

@@ -98,11 +98,22 @@ class CRUDPPR:
return db_obj
def update(self, db: Session, db_obj: PPRRecord, obj_in: PPRUpdate, user: str = "system", user_ip: str = "127.0.0.1") -> PPRRecord:
from datetime import datetime as dt
update_data = obj_in.dict(exclude_unset=True)
changes = []
for field, value in update_data.items():
old_value = getattr(db_obj, field)
# Normalize datetime values for comparison (ignore timezone differences)
if isinstance(old_value, dt) and isinstance(value, dt):
# Compare only the date and time, ignoring timezone
old_normalized = old_value.replace(tzinfo=None) if old_value.tzinfo else old_value
new_normalized = value.replace(tzinfo=None) if value.tzinfo else value
if old_normalized == new_normalized:
continue # Skip if datetimes are the same
if old_value != value:
changes.append(f"{field} changed from '{old_value}' to '{value}'")
setattr(db_obj, field, value)
@@ -114,7 +125,7 @@ class CRUDPPR:
# Log changes in journal
for change in changes:
crud_journal.log_change(db, db_obj.id, change, user, user_ip)
crud_journal.log_ppr_change(db, db_obj.id, change, user, user_ip)
return db_obj
@@ -146,7 +157,7 @@ class CRUDPPR:
db.refresh(db_obj)
# Log status change in journal
crud_journal.log_change(
crud_journal.log_ppr_change(
db,
db_obj.id,
f"Status changed from {old_status.value} to {status.value}",