Getting there
This commit is contained in:
@@ -20,13 +20,13 @@ depends_on = None
|
||||
|
||||
def upgrade() -> None:
|
||||
"""
|
||||
Create local_flights table for tracking aircraft that book out locally.
|
||||
Create local_flights, departures, and arrivals tables.
|
||||
"""
|
||||
|
||||
op.create_table('local_flights',
|
||||
sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
|
||||
sa.Column('registration', sa.String(length=16), nullable=False),
|
||||
sa.Column('type', sa.String(length=32), nullable=False),
|
||||
sa.Column('type', sa.String(length=32), nullable=True),
|
||||
sa.Column('callsign', sa.String(length=16), nullable=True),
|
||||
sa.Column('pob', sa.Integer(), nullable=False),
|
||||
sa.Column('flight_type', sa.Enum('LOCAL', 'CIRCUITS', 'DEPARTURE', name='localflighttype'), nullable=False),
|
||||
@@ -43,16 +43,70 @@ def upgrade() -> None:
|
||||
mysql_collate='utf8mb4_unicode_ci'
|
||||
)
|
||||
|
||||
# Create indexes for frequently queried columns
|
||||
# Create indexes for local_flights
|
||||
op.create_index('idx_registration', 'local_flights', ['registration'])
|
||||
op.create_index('idx_flight_type', 'local_flights', ['flight_type'])
|
||||
op.create_index('idx_status', 'local_flights', ['status'])
|
||||
op.create_index('idx_booked_out_dt', 'local_flights', ['booked_out_dt'])
|
||||
op.create_index('idx_created_by', 'local_flights', ['created_by'])
|
||||
|
||||
# Create departures table for non-PPR departures to other airports
|
||||
op.create_table('departures',
|
||||
sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
|
||||
sa.Column('registration', sa.String(length=16), nullable=False),
|
||||
sa.Column('type', sa.String(length=32), nullable=True),
|
||||
sa.Column('callsign', sa.String(length=16), nullable=True),
|
||||
sa.Column('pob', sa.Integer(), nullable=False),
|
||||
sa.Column('out_to', sa.String(length=64), nullable=False),
|
||||
sa.Column('status', sa.Enum('BOOKED_OUT', 'DEPARTED', 'CANCELLED', name='departuresstatus'), nullable=False, server_default='BOOKED_OUT'),
|
||||
sa.Column('notes', sa.Text(), nullable=True),
|
||||
sa.Column('booked_out_dt', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
|
||||
sa.Column('departure_dt', sa.DateTime(), nullable=True),
|
||||
sa.Column('created_by', sa.String(length=16), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8mb4',
|
||||
mysql_collate='utf8mb4_unicode_ci'
|
||||
)
|
||||
|
||||
op.create_index('idx_dep_registration', 'departures', ['registration'])
|
||||
op.create_index('idx_dep_out_to', 'departures', ['out_to'])
|
||||
op.create_index('idx_dep_status', 'departures', ['status'])
|
||||
op.create_index('idx_dep_booked_out_dt', 'departures', ['booked_out_dt'])
|
||||
op.create_index('idx_dep_created_by', 'departures', ['created_by'])
|
||||
|
||||
# Create arrivals table for non-PPR arrivals from elsewhere
|
||||
op.create_table('arrivals',
|
||||
sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
|
||||
sa.Column('registration', sa.String(length=16), nullable=False),
|
||||
sa.Column('type', sa.String(length=32), nullable=True),
|
||||
sa.Column('callsign', sa.String(length=16), nullable=True),
|
||||
sa.Column('pob', sa.Integer(), nullable=False),
|
||||
sa.Column('in_from', sa.String(length=64), nullable=False),
|
||||
sa.Column('status', sa.Enum('BOOKED_IN', 'LANDED', 'CANCELLED', name='arrivalsstatus'), nullable=False, server_default='BOOKED_IN'),
|
||||
sa.Column('notes', sa.Text(), nullable=True),
|
||||
sa.Column('booked_in_dt', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
|
||||
sa.Column('landed_dt', sa.DateTime(), nullable=True),
|
||||
sa.Column('created_by', sa.String(length=16), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8mb4',
|
||||
mysql_collate='utf8mb4_unicode_ci'
|
||||
)
|
||||
|
||||
op.create_index('idx_arr_registration', 'arrivals', ['registration'])
|
||||
op.create_index('idx_arr_in_from', 'arrivals', ['in_from'])
|
||||
op.create_index('idx_arr_status', 'arrivals', ['status'])
|
||||
op.create_index('idx_arr_booked_in_dt', 'arrivals', ['booked_in_dt'])
|
||||
op.create_index('idx_arr_created_by', 'arrivals', ['created_by'])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""
|
||||
Drop the local_flights table.
|
||||
Drop the local_flights, departures, and arrivals tables.
|
||||
"""
|
||||
op.drop_table('arrivals')
|
||||
op.drop_table('departures')
|
||||
op.drop_table('local_flights')
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from fastapi import APIRouter
|
||||
from app.api.endpoints import auth, pprs, public, aircraft, airport, local_flights
|
||||
from app.api.endpoints import auth, pprs, public, aircraft, airport, local_flights, departures, arrivals
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
api_router.include_router(auth.router, prefix="/auth", tags=["authentication"])
|
||||
api_router.include_router(pprs.router, prefix="/pprs", tags=["pprs"])
|
||||
api_router.include_router(local_flights.router, prefix="/local-flights", tags=["local_flights"])
|
||||
api_router.include_router(departures.router, prefix="/departures", tags=["departures"])
|
||||
api_router.include_router(arrivals.router, prefix="/arrivals", tags=["arrivals"])
|
||||
api_router.include_router(public.router, prefix="/public", tags=["public"])
|
||||
api_router.include_router(aircraft.router, prefix="/aircraft", tags=["aircraft"])
|
||||
api_router.include_router(airport.router, prefix="/airport", tags=["airport"])
|
||||
167
backend/app/api/endpoints/arrivals.py
Normal file
167
backend/app/api/endpoints/arrivals.py
Normal file
@@ -0,0 +1,167 @@
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
||||
from sqlalchemy.orm import Session
|
||||
from datetime import date
|
||||
from app.api.deps import get_db, get_current_read_user, get_current_operator_user
|
||||
from app.crud.crud_arrival import arrival as crud_arrival
|
||||
from app.schemas.arrival import Arrival, ArrivalCreate, ArrivalUpdate, ArrivalStatus, ArrivalStatusUpdate
|
||||
from app.models.ppr import User
|
||||
from app.core.utils import get_client_ip
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/", response_model=List[Arrival])
|
||||
async def get_arrivals(
|
||||
request: Request,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
status: Optional[ArrivalStatus] = None,
|
||||
date_from: Optional[date] = None,
|
||||
date_to: Optional[date] = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_read_user)
|
||||
):
|
||||
"""Get arrival records with optional filtering"""
|
||||
arrivals = crud_arrival.get_multi(
|
||||
db, skip=skip, limit=limit, status=status,
|
||||
date_from=date_from, date_to=date_to
|
||||
)
|
||||
return arrivals
|
||||
|
||||
|
||||
@router.post("/", response_model=Arrival)
|
||||
async def create_arrival(
|
||||
request: Request,
|
||||
arrival_in: ArrivalCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_operator_user)
|
||||
):
|
||||
"""Create a new arrival record"""
|
||||
arrival = crud_arrival.create(db, obj_in=arrival_in, created_by=current_user.username)
|
||||
|
||||
# Send real-time update via WebSocket
|
||||
if hasattr(request.app.state, 'connection_manager'):
|
||||
await request.app.state.connection_manager.broadcast({
|
||||
"type": "arrival_booked_in",
|
||||
"data": {
|
||||
"id": arrival.id,
|
||||
"registration": arrival.registration,
|
||||
"in_from": arrival.in_from,
|
||||
"status": arrival.status.value
|
||||
}
|
||||
})
|
||||
|
||||
return arrival
|
||||
|
||||
|
||||
@router.get("/{arrival_id}", response_model=Arrival)
|
||||
async def get_arrival(
|
||||
arrival_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_read_user)
|
||||
):
|
||||
"""Get a specific arrival record"""
|
||||
arrival = crud_arrival.get(db, arrival_id=arrival_id)
|
||||
if not arrival:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Arrival record not found"
|
||||
)
|
||||
return arrival
|
||||
|
||||
|
||||
@router.put("/{arrival_id}", response_model=Arrival)
|
||||
async def update_arrival(
|
||||
request: Request,
|
||||
arrival_id: int,
|
||||
arrival_in: ArrivalUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_operator_user)
|
||||
):
|
||||
"""Update an arrival record"""
|
||||
db_arrival = crud_arrival.get(db, arrival_id=arrival_id)
|
||||
if not db_arrival:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Arrival record not found"
|
||||
)
|
||||
|
||||
arrival = crud_arrival.update(db, db_obj=db_arrival, obj_in=arrival_in)
|
||||
|
||||
# Send real-time update
|
||||
if hasattr(request.app.state, 'connection_manager'):
|
||||
await request.app.state.connection_manager.broadcast({
|
||||
"type": "arrival_updated",
|
||||
"data": {
|
||||
"id": arrival.id,
|
||||
"registration": arrival.registration,
|
||||
"status": arrival.status.value
|
||||
}
|
||||
})
|
||||
|
||||
return arrival
|
||||
|
||||
|
||||
@router.patch("/{arrival_id}/status", response_model=Arrival)
|
||||
async def update_arrival_status(
|
||||
request: Request,
|
||||
arrival_id: int,
|
||||
status_update: ArrivalStatusUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_operator_user)
|
||||
):
|
||||
"""Update arrival status"""
|
||||
arrival = crud_arrival.update_status(
|
||||
db,
|
||||
arrival_id=arrival_id,
|
||||
status=status_update.status,
|
||||
timestamp=status_update.timestamp
|
||||
)
|
||||
if not arrival:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Arrival record not found"
|
||||
)
|
||||
|
||||
# Send real-time update
|
||||
if hasattr(request.app.state, 'connection_manager'):
|
||||
await request.app.state.connection_manager.broadcast({
|
||||
"type": "arrival_status_update",
|
||||
"data": {
|
||||
"id": arrival.id,
|
||||
"registration": arrival.registration,
|
||||
"status": arrival.status.value,
|
||||
"landed_dt": arrival.landed_dt.isoformat() if arrival.landed_dt else None
|
||||
}
|
||||
})
|
||||
|
||||
return arrival
|
||||
|
||||
|
||||
@router.delete("/{arrival_id}", response_model=Arrival)
|
||||
async def cancel_arrival(
|
||||
request: Request,
|
||||
arrival_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_operator_user)
|
||||
):
|
||||
"""Cancel an arrival record"""
|
||||
arrival = crud_arrival.cancel(db, arrival_id=arrival_id)
|
||||
if not arrival:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Arrival record not found"
|
||||
)
|
||||
|
||||
# Send real-time update
|
||||
if hasattr(request.app.state, 'connection_manager'):
|
||||
await request.app.state.connection_manager.broadcast({
|
||||
"type": "arrival_cancelled",
|
||||
"data": {
|
||||
"id": arrival.id,
|
||||
"registration": arrival.registration
|
||||
}
|
||||
})
|
||||
|
||||
return arrival
|
||||
167
backend/app/api/endpoints/departures.py
Normal file
167
backend/app/api/endpoints/departures.py
Normal file
@@ -0,0 +1,167 @@
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
||||
from sqlalchemy.orm import Session
|
||||
from datetime import date
|
||||
from app.api.deps import get_db, get_current_read_user, get_current_operator_user
|
||||
from app.crud.crud_departure import departure as crud_departure
|
||||
from app.schemas.departure import Departure, DepartureCreate, DepartureUpdate, DepartureStatus, DepartureStatusUpdate
|
||||
from app.models.ppr import User
|
||||
from app.core.utils import get_client_ip
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/", response_model=List[Departure])
|
||||
async def get_departures(
|
||||
request: Request,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
status: Optional[DepartureStatus] = None,
|
||||
date_from: Optional[date] = None,
|
||||
date_to: Optional[date] = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_read_user)
|
||||
):
|
||||
"""Get departure records with optional filtering"""
|
||||
departures = crud_departure.get_multi(
|
||||
db, skip=skip, limit=limit, status=status,
|
||||
date_from=date_from, date_to=date_to
|
||||
)
|
||||
return departures
|
||||
|
||||
|
||||
@router.post("/", response_model=Departure)
|
||||
async def create_departure(
|
||||
request: Request,
|
||||
departure_in: DepartureCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_operator_user)
|
||||
):
|
||||
"""Create a new departure record"""
|
||||
departure = crud_departure.create(db, obj_in=departure_in, created_by=current_user.username)
|
||||
|
||||
# Send real-time update via WebSocket
|
||||
if hasattr(request.app.state, 'connection_manager'):
|
||||
await request.app.state.connection_manager.broadcast({
|
||||
"type": "departure_booked_out",
|
||||
"data": {
|
||||
"id": departure.id,
|
||||
"registration": departure.registration,
|
||||
"out_to": departure.out_to,
|
||||
"status": departure.status.value
|
||||
}
|
||||
})
|
||||
|
||||
return departure
|
||||
|
||||
|
||||
@router.get("/{departure_id}", response_model=Departure)
|
||||
async def get_departure(
|
||||
departure_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_read_user)
|
||||
):
|
||||
"""Get a specific departure record"""
|
||||
departure = crud_departure.get(db, departure_id=departure_id)
|
||||
if not departure:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Departure record not found"
|
||||
)
|
||||
return departure
|
||||
|
||||
|
||||
@router.put("/{departure_id}", response_model=Departure)
|
||||
async def update_departure(
|
||||
request: Request,
|
||||
departure_id: int,
|
||||
departure_in: DepartureUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_operator_user)
|
||||
):
|
||||
"""Update a departure record"""
|
||||
db_departure = crud_departure.get(db, departure_id=departure_id)
|
||||
if not db_departure:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Departure record not found"
|
||||
)
|
||||
|
||||
departure = crud_departure.update(db, db_obj=db_departure, obj_in=departure_in)
|
||||
|
||||
# Send real-time update
|
||||
if hasattr(request.app.state, 'connection_manager'):
|
||||
await request.app.state.connection_manager.broadcast({
|
||||
"type": "departure_updated",
|
||||
"data": {
|
||||
"id": departure.id,
|
||||
"registration": departure.registration,
|
||||
"status": departure.status.value
|
||||
}
|
||||
})
|
||||
|
||||
return departure
|
||||
|
||||
|
||||
@router.patch("/{departure_id}/status", response_model=Departure)
|
||||
async def update_departure_status(
|
||||
request: Request,
|
||||
departure_id: int,
|
||||
status_update: DepartureStatusUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_operator_user)
|
||||
):
|
||||
"""Update departure status"""
|
||||
departure = crud_departure.update_status(
|
||||
db,
|
||||
departure_id=departure_id,
|
||||
status=status_update.status,
|
||||
timestamp=status_update.timestamp
|
||||
)
|
||||
if not departure:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Departure record not found"
|
||||
)
|
||||
|
||||
# Send real-time update
|
||||
if hasattr(request.app.state, 'connection_manager'):
|
||||
await request.app.state.connection_manager.broadcast({
|
||||
"type": "departure_status_update",
|
||||
"data": {
|
||||
"id": departure.id,
|
||||
"registration": departure.registration,
|
||||
"status": departure.status.value,
|
||||
"departure_dt": departure.departure_dt.isoformat() if departure.departure_dt else None
|
||||
}
|
||||
})
|
||||
|
||||
return departure
|
||||
|
||||
|
||||
@router.delete("/{departure_id}", response_model=Departure)
|
||||
async def cancel_departure(
|
||||
request: Request,
|
||||
departure_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_operator_user)
|
||||
):
|
||||
"""Cancel a departure record"""
|
||||
departure = crud_departure.cancel(db, departure_id=departure_id)
|
||||
if not departure:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Departure record not found"
|
||||
)
|
||||
|
||||
# Send real-time update
|
||||
if hasattr(request.app.state, 'connection_manager'):
|
||||
await request.app.state.connection_manager.broadcast({
|
||||
"type": "departure_cancelled",
|
||||
"data": {
|
||||
"id": departure.id,
|
||||
"registration": departure.registration
|
||||
}
|
||||
})
|
||||
|
||||
return departure
|
||||
@@ -4,8 +4,12 @@ from sqlalchemy.orm import Session
|
||||
from app.api.deps import get_db
|
||||
from app.crud.crud_ppr import ppr as crud_ppr
|
||||
from app.crud.crud_local_flight import local_flight as crud_local_flight
|
||||
from app.crud.crud_departure import departure as crud_departure
|
||||
from app.crud.crud_arrival import arrival as crud_arrival
|
||||
from app.schemas.ppr import PPRPublic
|
||||
from app.models.local_flight import LocalFlightStatus
|
||||
from app.models.departure import DepartureStatus
|
||||
from app.models.arrival import ArrivalStatus
|
||||
from datetime import date
|
||||
|
||||
router = APIRouter()
|
||||
@@ -56,7 +60,7 @@ async def get_public_arrivals(db: Session = Depends(get_db)):
|
||||
|
||||
@router.get("/departures")
|
||||
async def get_public_departures(db: Session = Depends(get_db)):
|
||||
"""Get today's departures for public display (PPR and local flights)"""
|
||||
"""Get today's departures for public display (PPR, local flights, and departures to other airports)"""
|
||||
departures = crud_ppr.get_departures_today(db)
|
||||
|
||||
# Convert PPR departures to dictionaries
|
||||
@@ -70,7 +74,8 @@ async def get_public_departures(db: Session = Depends(get_db)):
|
||||
'etd': departure.etd,
|
||||
'departed_dt': departure.departed_dt,
|
||||
'status': departure.status.value,
|
||||
'isLocalFlight': False
|
||||
'isLocalFlight': False,
|
||||
'isDeparture': False
|
||||
})
|
||||
|
||||
# Add local flights with BOOKED_OUT status
|
||||
@@ -91,7 +96,29 @@ async def get_public_departures(db: Session = Depends(get_db)):
|
||||
'departed_dt': None,
|
||||
'status': 'BOOKED_OUT',
|
||||
'isLocalFlight': True,
|
||||
'flight_type': flight.flight_type.value
|
||||
'flight_type': flight.flight_type.value,
|
||||
'isDeparture': False
|
||||
})
|
||||
|
||||
# Add departures to other airports with BOOKED_OUT status
|
||||
departures_to_airports = crud_departure.get_multi(
|
||||
db,
|
||||
status=DepartureStatus.BOOKED_OUT,
|
||||
limit=1000
|
||||
)
|
||||
|
||||
# Convert departures to match the format for display
|
||||
for dep in departures_to_airports:
|
||||
departures_list.append({
|
||||
'ac_call': dep.callsign or dep.registration,
|
||||
'ac_reg': dep.registration,
|
||||
'ac_type': dep.type,
|
||||
'out_to': dep.out_to,
|
||||
'etd': dep.booked_out_dt,
|
||||
'departed_dt': None,
|
||||
'status': 'BOOKED_OUT',
|
||||
'isLocalFlight': False,
|
||||
'isDeparture': True
|
||||
})
|
||||
|
||||
return departures_list
|
||||
104
backend/app/crud/crud_arrival.py
Normal file
104
backend/app/crud/crud_arrival.py
Normal file
@@ -0,0 +1,104 @@
|
||||
from typing import List, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
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
|
||||
|
||||
|
||||
class CRUDArrival:
|
||||
def get(self, db: Session, arrival_id: int) -> Optional[Arrival]:
|
||||
return db.query(Arrival).filter(Arrival.id == arrival_id).first()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
status: Optional[ArrivalStatus] = None,
|
||||
date_from: Optional[date] = None,
|
||||
date_to: Optional[date] = None
|
||||
) -> List[Arrival]:
|
||||
query = db.query(Arrival)
|
||||
|
||||
if status:
|
||||
query = query.filter(Arrival.status == status)
|
||||
|
||||
if date_from:
|
||||
query = query.filter(func.date(Arrival.booked_in_dt) >= date_from)
|
||||
|
||||
if date_to:
|
||||
query = query.filter(func.date(Arrival.booked_in_dt) <= date_to)
|
||||
|
||||
return query.order_by(desc(Arrival.booked_in_dt)).offset(skip).limit(limit).all()
|
||||
|
||||
def get_arrivals_today(self, db: Session) -> List[Arrival]:
|
||||
"""Get today's arrivals (booked in or landed)"""
|
||||
today = date.today()
|
||||
return db.query(Arrival).filter(
|
||||
and_(
|
||||
func.date(Arrival.booked_in_dt) == today,
|
||||
or_(
|
||||
Arrival.status == ArrivalStatus.BOOKED_IN,
|
||||
Arrival.status == ArrivalStatus.LANDED
|
||||
)
|
||||
)
|
||||
).order_by(Arrival.booked_in_dt).all()
|
||||
|
||||
def create(self, db: Session, obj_in: ArrivalCreate, created_by: str) -> Arrival:
|
||||
db_obj = Arrival(
|
||||
**obj_in.dict(),
|
||||
created_by=created_by,
|
||||
status=ArrivalStatus.BOOKED_IN
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update(self, db: Session, db_obj: Arrival, obj_in: ArrivalUpdate) -> Arrival:
|
||||
update_data = obj_in.dict(exclude_unset=True)
|
||||
|
||||
for field, value in update_data.items():
|
||||
if value is not None:
|
||||
setattr(db_obj, field, value)
|
||||
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update_status(
|
||||
self,
|
||||
db: Session,
|
||||
arrival_id: int,
|
||||
status: ArrivalStatus,
|
||||
timestamp: Optional[datetime] = None
|
||||
) -> Optional[Arrival]:
|
||||
db_obj = self.get(db, arrival_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
db_obj.status = status
|
||||
|
||||
if status == ArrivalStatus.LANDED and timestamp:
|
||||
db_obj.landed_dt = timestamp
|
||||
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def cancel(self, db: Session, arrival_id: int) -> Optional[Arrival]:
|
||||
db_obj = self.get(db, arrival_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
db_obj.status = ArrivalStatus.CANCELLED
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
|
||||
arrival = CRUDArrival()
|
||||
104
backend/app/crud/crud_departure.py
Normal file
104
backend/app/crud/crud_departure.py
Normal file
@@ -0,0 +1,104 @@
|
||||
from typing import List, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
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
|
||||
|
||||
|
||||
class CRUDDeparture:
|
||||
def get(self, db: Session, departure_id: int) -> Optional[Departure]:
|
||||
return db.query(Departure).filter(Departure.id == departure_id).first()
|
||||
|
||||
def get_multi(
|
||||
self,
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
status: Optional[DepartureStatus] = None,
|
||||
date_from: Optional[date] = None,
|
||||
date_to: Optional[date] = None
|
||||
) -> List[Departure]:
|
||||
query = db.query(Departure)
|
||||
|
||||
if status:
|
||||
query = query.filter(Departure.status == status)
|
||||
|
||||
if date_from:
|
||||
query = query.filter(func.date(Departure.booked_out_dt) >= date_from)
|
||||
|
||||
if date_to:
|
||||
query = query.filter(func.date(Departure.booked_out_dt) <= date_to)
|
||||
|
||||
return query.order_by(desc(Departure.booked_out_dt)).offset(skip).limit(limit).all()
|
||||
|
||||
def get_departures_today(self, db: Session) -> List[Departure]:
|
||||
"""Get today's departures (booked out or departed)"""
|
||||
today = date.today()
|
||||
return db.query(Departure).filter(
|
||||
and_(
|
||||
func.date(Departure.booked_out_dt) == today,
|
||||
or_(
|
||||
Departure.status == DepartureStatus.BOOKED_OUT,
|
||||
Departure.status == DepartureStatus.DEPARTED
|
||||
)
|
||||
)
|
||||
).order_by(Departure.booked_out_dt).all()
|
||||
|
||||
def create(self, db: Session, obj_in: DepartureCreate, created_by: str) -> Departure:
|
||||
db_obj = Departure(
|
||||
**obj_in.dict(),
|
||||
created_by=created_by,
|
||||
status=DepartureStatus.BOOKED_OUT
|
||||
)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update(self, db: Session, db_obj: Departure, obj_in: DepartureUpdate) -> Departure:
|
||||
update_data = obj_in.dict(exclude_unset=True)
|
||||
|
||||
for field, value in update_data.items():
|
||||
if value is not None:
|
||||
setattr(db_obj, field, value)
|
||||
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update_status(
|
||||
self,
|
||||
db: Session,
|
||||
departure_id: int,
|
||||
status: DepartureStatus,
|
||||
timestamp: Optional[datetime] = None
|
||||
) -> Optional[Departure]:
|
||||
db_obj = self.get(db, departure_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
db_obj.status = status
|
||||
|
||||
if status == DepartureStatus.DEPARTED and timestamp:
|
||||
db_obj.departure_dt = timestamp
|
||||
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def cancel(self, db: Session, departure_id: int) -> Optional[Departure]:
|
||||
db_obj = self.get(db, departure_id)
|
||||
if not db_obj:
|
||||
return None
|
||||
|
||||
db_obj.status = DepartureStatus.CANCELLED
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
|
||||
departure = CRUDDeparture()
|
||||
@@ -11,6 +11,8 @@ from app.api.api import api_router
|
||||
# Import models to ensure they're registered with SQLAlchemy
|
||||
from app.models.ppr import PPRRecord, User, Journal, Airport, Aircraft
|
||||
from app.models.local_flight import LocalFlight
|
||||
from app.models.departure import Departure
|
||||
from app.models.arrival import Arrival
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
29
backend/app/models/arrival.py
Normal file
29
backend/app/models/arrival.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from sqlalchemy import Column, BigInteger, String, Integer, Text, DateTime, Enum as SQLEnum, func
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from enum import Enum
|
||||
from datetime import datetime
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class ArrivalStatus(str, Enum):
|
||||
BOOKED_IN = "BOOKED_IN"
|
||||
LANDED = "LANDED"
|
||||
CANCELLED = "CANCELLED"
|
||||
|
||||
|
||||
class Arrival(Base):
|
||||
__tablename__ = "arrivals"
|
||||
|
||||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||
registration = Column(String(16), nullable=False, index=True)
|
||||
type = Column(String(32), nullable=True)
|
||||
callsign = Column(String(16), nullable=True)
|
||||
pob = Column(Integer, nullable=False)
|
||||
in_from = Column(String(4), nullable=False, index=True)
|
||||
status = Column(SQLEnum(ArrivalStatus), default=ArrivalStatus.BOOKED_IN, nullable=False, index=True)
|
||||
notes = Column(Text, nullable=True)
|
||||
booked_in_dt = Column(DateTime, server_default=func.now(), nullable=False, index=True)
|
||||
landed_dt = Column(DateTime, nullable=True)
|
||||
created_by = Column(String(16), nullable=True, index=True)
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), nullable=False)
|
||||
29
backend/app/models/departure.py
Normal file
29
backend/app/models/departure.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from sqlalchemy import Column, BigInteger, String, Integer, Text, DateTime, Enum as SQLEnum, func
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from enum import Enum
|
||||
from datetime import datetime
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class DepartureStatus(str, Enum):
|
||||
BOOKED_OUT = "BOOKED_OUT"
|
||||
DEPARTED = "DEPARTED"
|
||||
CANCELLED = "CANCELLED"
|
||||
|
||||
|
||||
class Departure(Base):
|
||||
__tablename__ = "departures"
|
||||
|
||||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||
registration = Column(String(16), nullable=False, index=True)
|
||||
type = Column(String(32), nullable=True)
|
||||
callsign = Column(String(16), nullable=True)
|
||||
pob = Column(Integer, nullable=False)
|
||||
out_to = Column(String(4), nullable=False, index=True)
|
||||
status = Column(SQLEnum(DepartureStatus), default=DepartureStatus.BOOKED_OUT, nullable=False, index=True)
|
||||
notes = Column(Text, nullable=True)
|
||||
booked_out_dt = Column(DateTime, server_default=func.now(), nullable=False, index=True)
|
||||
departure_dt = Column(DateTime, nullable=True)
|
||||
created_by = Column(String(16), nullable=True, index=True)
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), nullable=False)
|
||||
66
backend/app/schemas/arrival.py
Normal file
66
backend/app/schemas/arrival.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from pydantic import BaseModel, validator
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ArrivalStatus(str, Enum):
|
||||
BOOKED_IN = "BOOKED_IN"
|
||||
LANDED = "LANDED"
|
||||
CANCELLED = "CANCELLED"
|
||||
|
||||
|
||||
class ArrivalBase(BaseModel):
|
||||
registration: str
|
||||
type: Optional[str] = None
|
||||
callsign: Optional[str] = None
|
||||
pob: int
|
||||
in_from: str
|
||||
notes: Optional[str] = None
|
||||
|
||||
@validator('registration')
|
||||
def validate_registration(cls, v):
|
||||
if not v or len(v.strip()) == 0:
|
||||
raise ValueError('Aircraft registration is required')
|
||||
return v.strip().upper()
|
||||
|
||||
@validator('in_from')
|
||||
def validate_in_from(cls, v):
|
||||
if not v or len(v.strip()) == 0:
|
||||
raise ValueError('Origin airport is required')
|
||||
return v.strip()
|
||||
|
||||
@validator('pob')
|
||||
def validate_pob(cls, v):
|
||||
if v is not None and v < 1:
|
||||
raise ValueError('Persons on board must be at least 1')
|
||||
return v
|
||||
|
||||
|
||||
class ArrivalCreate(ArrivalBase):
|
||||
pass
|
||||
|
||||
|
||||
class ArrivalUpdate(BaseModel):
|
||||
type: Optional[str] = None
|
||||
callsign: Optional[str] = None
|
||||
pob: Optional[int] = None
|
||||
in_from: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class ArrivalStatusUpdate(BaseModel):
|
||||
status: ArrivalStatus
|
||||
timestamp: Optional[datetime] = None
|
||||
|
||||
|
||||
class Arrival(ArrivalBase):
|
||||
id: int
|
||||
status: ArrivalStatus
|
||||
booked_in_dt: datetime
|
||||
landed_dt: Optional[datetime] = None
|
||||
created_by: Optional[str] = None
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
66
backend/app/schemas/departure.py
Normal file
66
backend/app/schemas/departure.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from pydantic import BaseModel, validator
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class DepartureStatus(str, Enum):
|
||||
BOOKED_OUT = "BOOKED_OUT"
|
||||
DEPARTED = "DEPARTED"
|
||||
CANCELLED = "CANCELLED"
|
||||
|
||||
|
||||
class DepartureBase(BaseModel):
|
||||
registration: str
|
||||
type: Optional[str] = None
|
||||
callsign: Optional[str] = None
|
||||
pob: int
|
||||
out_to: str
|
||||
notes: Optional[str] = None
|
||||
|
||||
@validator('registration')
|
||||
def validate_registration(cls, v):
|
||||
if not v or len(v.strip()) == 0:
|
||||
raise ValueError('Aircraft registration is required')
|
||||
return v.strip().upper()
|
||||
|
||||
@validator('out_to')
|
||||
def validate_out_to(cls, v):
|
||||
if not v or len(v.strip()) == 0:
|
||||
raise ValueError('Destination airport is required')
|
||||
return v.strip()
|
||||
|
||||
@validator('pob')
|
||||
def validate_pob(cls, v):
|
||||
if v is not None and v < 1:
|
||||
raise ValueError('Persons on board must be at least 1')
|
||||
return v
|
||||
|
||||
|
||||
class DepartureCreate(DepartureBase):
|
||||
pass
|
||||
|
||||
|
||||
class DepartureUpdate(BaseModel):
|
||||
type: Optional[str] = None
|
||||
callsign: Optional[str] = None
|
||||
pob: Optional[int] = None
|
||||
out_to: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class DepartureStatusUpdate(BaseModel):
|
||||
status: DepartureStatus
|
||||
timestamp: Optional[datetime] = None
|
||||
|
||||
|
||||
class Departure(DepartureBase):
|
||||
id: int
|
||||
status: DepartureStatus
|
||||
booked_out_dt: datetime
|
||||
departure_dt: Optional[datetime] = None
|
||||
created_by: Optional[str] = None
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -19,7 +19,7 @@ class LocalFlightStatus(str, Enum):
|
||||
|
||||
class LocalFlightBase(BaseModel):
|
||||
registration: str
|
||||
type: str # Aircraft type
|
||||
type: Optional[str] = None # Aircraft type - optional, can be looked up later
|
||||
callsign: Optional[str] = None
|
||||
pob: int
|
||||
flight_type: LocalFlightType
|
||||
@@ -31,11 +31,13 @@ class LocalFlightBase(BaseModel):
|
||||
raise ValueError('Aircraft registration is required')
|
||||
return v.strip().upper()
|
||||
|
||||
@validator('type')
|
||||
@validator('type', pre=True, always=False)
|
||||
def validate_type(cls, v):
|
||||
if not v or len(v.strip()) == 0:
|
||||
raise ValueError('Aircraft type is required')
|
||||
return v.strip()
|
||||
if v is None or (isinstance(v, str) and len(v.strip()) == 0):
|
||||
return None
|
||||
if isinstance(v, str):
|
||||
return v.strip()
|
||||
return v
|
||||
|
||||
@validator('pob')
|
||||
def validate_pob(cls, v):
|
||||
|
||||
Reference in New Issue
Block a user