
This commit includes: - Project structure and FastAPI setup - SQLAlchemy models for users, vehicles, schedules, and tickets - Alembic migrations - User authentication and management - Vehicle and schedule management - Ticket purchase and cancellation with time restrictions - Comprehensive API documentation
238 lines
6.2 KiB
Python
238 lines
6.2 KiB
Python
from datetime import datetime
|
|
from typing import Any, List, Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.auth import get_current_active_user
|
|
from app.db.session import get_db
|
|
from app.models.ticket import Ticket, TicketStatus
|
|
from app.models.user import User
|
|
from app.models.vehicle import Schedule, Vehicle, VehicleType
|
|
from app.schemas.ticket import (
|
|
Ticket as TicketSchema,
|
|
)
|
|
from app.schemas.ticket import (
|
|
TicketCreate,
|
|
)
|
|
from app.services.ticket_service import (
|
|
assign_seat_number,
|
|
generate_ticket_number,
|
|
validate_cancellation_time,
|
|
validate_purchase_time,
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.post("/", response_model=TicketSchema, status_code=status.HTTP_201_CREATED)
|
|
def create_ticket(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
ticket_in: TicketCreate,
|
|
current_user: User = Depends(get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Purchase a ticket.
|
|
"""
|
|
# Check if schedule exists and is active
|
|
schedule = db.query(Schedule).join(Vehicle).filter(
|
|
Schedule.id == ticket_in.schedule_id,
|
|
Schedule.is_active == True,
|
|
Vehicle.is_active == True
|
|
).first()
|
|
|
|
if not schedule:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Schedule not found",
|
|
)
|
|
|
|
# Check if there are available seats
|
|
if schedule.available_seats <= 0:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="No available seats for this schedule",
|
|
)
|
|
|
|
# Validate purchase time (at least 10 minutes before departure)
|
|
validate_purchase_time(schedule)
|
|
|
|
# For trains, assign a seat number
|
|
vehicle_type = schedule.vehicle.vehicle_type
|
|
if vehicle_type == VehicleType.TRAIN:
|
|
seat_number = assign_seat_number(db, schedule, vehicle_type)
|
|
else:
|
|
seat_number = ticket_in.seat_number
|
|
|
|
# Create ticket
|
|
ticket = Ticket(
|
|
user_id=current_user.id,
|
|
schedule_id=ticket_in.schedule_id,
|
|
seat_number=seat_number,
|
|
purchase_time=datetime.utcnow(),
|
|
status=TicketStatus.ACTIVE,
|
|
is_active=True,
|
|
ticket_number=generate_ticket_number(),
|
|
)
|
|
|
|
# Update available seats in schedule
|
|
schedule.available_seats -= 1
|
|
|
|
db.add(ticket)
|
|
db.add(schedule)
|
|
db.commit()
|
|
db.refresh(ticket)
|
|
|
|
return ticket
|
|
|
|
|
|
@router.get("/", response_model=List[TicketSchema])
|
|
def list_tickets(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
status: Optional[TicketStatus] = None,
|
|
current_user: User = Depends(get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Retrieve all tickets for the current user.
|
|
"""
|
|
query = db.query(Ticket).filter(
|
|
Ticket.user_id == current_user.id,
|
|
Ticket.is_active == True
|
|
)
|
|
|
|
if status:
|
|
query = query.filter(Ticket.status == status)
|
|
|
|
tickets = query.offset(skip).limit(limit).all()
|
|
return tickets
|
|
|
|
|
|
@router.get("/active", response_model=List[TicketSchema])
|
|
def list_active_tickets(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Retrieve all active tickets for the current user.
|
|
"""
|
|
tickets = db.query(Ticket).filter(
|
|
Ticket.user_id == current_user.id,
|
|
Ticket.status == TicketStatus.ACTIVE,
|
|
Ticket.is_active == True
|
|
).all()
|
|
|
|
return tickets
|
|
|
|
|
|
@router.get("/history", response_model=List[TicketSchema])
|
|
def list_ticket_history(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Retrieve ticket history (used, cancelled, expired) for the current user.
|
|
"""
|
|
tickets = db.query(Ticket).filter(
|
|
Ticket.user_id == current_user.id,
|
|
Ticket.status != TicketStatus.ACTIVE,
|
|
Ticket.is_active == True
|
|
).all()
|
|
|
|
return tickets
|
|
|
|
|
|
@router.get("/{ticket_id}", response_model=TicketSchema)
|
|
def get_ticket(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
ticket_id: int,
|
|
current_user: User = Depends(get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Get ticket by ID.
|
|
"""
|
|
ticket = db.query(Ticket).filter(
|
|
Ticket.id == ticket_id,
|
|
Ticket.user_id == current_user.id,
|
|
Ticket.is_active == True
|
|
).first()
|
|
|
|
if not ticket:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Ticket not found",
|
|
)
|
|
|
|
return ticket
|
|
|
|
|
|
@router.get("/by-ticket-number/{ticket_number}", response_model=TicketSchema)
|
|
def get_ticket_by_number(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
ticket_number: str,
|
|
current_user: User = Depends(get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Get ticket by ticket number.
|
|
"""
|
|
ticket = db.query(Ticket).filter(
|
|
Ticket.ticket_number == ticket_number,
|
|
Ticket.user_id == current_user.id,
|
|
Ticket.is_active == True
|
|
).first()
|
|
|
|
if not ticket:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Ticket not found",
|
|
)
|
|
|
|
return ticket
|
|
|
|
|
|
@router.put("/{ticket_id}/cancel", response_model=TicketSchema)
|
|
def cancel_ticket(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
ticket_id: int,
|
|
current_user: User = Depends(get_current_active_user),
|
|
) -> Any:
|
|
"""
|
|
Cancel a ticket.
|
|
"""
|
|
ticket = db.query(Ticket).filter(
|
|
Ticket.id == ticket_id,
|
|
Ticket.user_id == current_user.id,
|
|
Ticket.is_active == True,
|
|
Ticket.status == TicketStatus.ACTIVE
|
|
).first()
|
|
|
|
if not ticket:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Active ticket not found",
|
|
)
|
|
|
|
# Validate cancellation time (at least 3 minutes before departure)
|
|
validate_cancellation_time(ticket)
|
|
|
|
# Update ticket status
|
|
ticket.status = TicketStatus.CANCELLED
|
|
|
|
# Increment available seats in schedule
|
|
schedule = db.query(Schedule).filter(Schedule.id == ticket.schedule_id).first()
|
|
schedule.available_seats += 1
|
|
|
|
db.add(ticket)
|
|
db.add(schedule)
|
|
db.commit()
|
|
db.refresh(ticket)
|
|
|
|
return ticket |