1385 lines
57 KiB
Python
1385 lines
57 KiB
Python
import os
|
||
import django
|
||
|
||
# Set up Django environment
|
||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'airport_management.settings')
|
||
django.setup()
|
||
|
||
import random
|
||
import uuid
|
||
from decimal import Decimal
|
||
from datetime import date, datetime, timedelta
|
||
from django.utils import timezone
|
||
from django.db import models
|
||
from django.contrib.auth.models import User
|
||
from ebt.models import *
|
||
|
||
|
||
def create_users():
|
||
"""Create user accounts for the system"""
|
||
users = []
|
||
|
||
# Create admin user if it doesn't exist
|
||
admin_username = 'admin'
|
||
if not User.objects.filter(username=admin_username).exists():
|
||
admin_user = User.objects.create_superuser(
|
||
username=admin_username,
|
||
email='admin@example.com',
|
||
password='adminpassword',
|
||
first_name='System',
|
||
last_name='Administrator'
|
||
)
|
||
users.append(admin_user)
|
||
|
||
# Create staff users for different roles
|
||
staff_data = [
|
||
{'username': 'manager1', 'email': 'manager1@example.com', 'password': 'managerpassword',
|
||
'first_name': 'Abdullah', 'last_name': 'Al-Otaibi', 'is_staff': True},
|
||
{'username': 'supervisor1', 'email': 'supervisor1@example.com', 'password': 'supervisorpassword',
|
||
'first_name': 'Mohammed', 'last_name': 'Al-Ghamdi', 'is_staff': True},
|
||
{'username': 'agent1', 'email': 'agent1@example.com', 'password': 'agentpassword',
|
||
'first_name': 'Fatima', 'last_name': 'Al-Qahtani', 'is_staff': False},
|
||
{'username': 'agent2', 'email': 'agent2@example.com', 'password': 'agentpassword',
|
||
'first_name': 'Ali', 'last_name': 'Al-Harbi', 'is_staff': False},
|
||
{'username': 'agent3', 'email': 'agent3@example.com', 'password': 'agentpassword',
|
||
'first_name': 'Nora', 'last_name': 'Al-Shammari', 'is_staff': False},
|
||
{'username': 'auditor1', 'email': 'auditor1@example.com', 'password': 'auditorpassword',
|
||
'first_name': 'Khalid', 'last_name': 'Al-Dossari', 'is_staff': True},
|
||
{'username': 'finance1', 'email': 'finance1@example.com', 'password': 'financepassword',
|
||
'first_name': 'Sara', 'last_name': 'Al-Zahrani', 'is_staff': True},
|
||
]
|
||
|
||
for data in staff_data:
|
||
username = data['username']
|
||
if not User.objects.filter(username=username).exists():
|
||
user = User.objects.create_user(
|
||
username=username,
|
||
email=data['email'],
|
||
password=data['password'],
|
||
first_name=data['first_name'],
|
||
last_name=data['last_name'],
|
||
is_staff=data['is_staff']
|
||
)
|
||
users.append(user)
|
||
|
||
return users
|
||
|
||
|
||
def create_cashiers(users, airports):
|
||
"""Create cashier records for users"""
|
||
cashiers = []
|
||
|
||
# Get list of valid users that aren't already cashiers
|
||
existing_cashier_users = Cashier.objects.values_list('user_id', flat=True)
|
||
available_users = [u for u in users if u.id not in existing_cashier_users]
|
||
|
||
if not available_users:
|
||
print("No available users to create cashiers")
|
||
return cashiers
|
||
|
||
# Create a cashier for each airport with multiple users where possible
|
||
for airport in airports:
|
||
# Create between 2 and 5 cashiers per airport, depending on available users
|
||
num_cashiers = min(random.randint(2, 5), len(available_users))
|
||
|
||
for _ in range(num_cashiers):
|
||
if not available_users:
|
||
break
|
||
|
||
# Pick a random user and remove from available list
|
||
user = random.choice(available_users)
|
||
available_users.remove(user)
|
||
|
||
# Generate employee ID based on user and airport
|
||
employee_id = f"{airport.code}-{random.randint(1000, 9999)}"
|
||
|
||
# Create cashier
|
||
cashier,_ = Cashier.objects.get_or_create(
|
||
user=user,
|
||
employee_id=employee_id,
|
||
airport=airport,
|
||
is_active=True,
|
||
created_at=timezone.now()
|
||
)
|
||
|
||
cashiers.append(cashier)
|
||
|
||
return cashiers
|
||
|
||
|
||
def create_cash_shifts(cashiers, num_shifts=100):
|
||
"""Create cash shift records for cashiers"""
|
||
shifts = []
|
||
|
||
# Start date (14 days ago)
|
||
start_date = timezone.now() - timedelta(days=14)
|
||
|
||
# For each cashier, create a number of shifts
|
||
for cashier in cashiers:
|
||
# Random number of shifts per cashier over the period
|
||
cashier_shifts = random.randint(3, 10)
|
||
|
||
for shift_idx in range(cashier_shifts):
|
||
# Create shift date/time - distribute over the period
|
||
shift_day = random.randint(0, 13) # 0 to 13 days ago
|
||
shift_date = start_date + timedelta(days=shift_day)
|
||
|
||
# Set shift time (morning, afternoon, or night)
|
||
shift_type = random.choice(['morning', 'afternoon', 'night'])
|
||
|
||
if shift_type == 'morning':
|
||
shift_hour = random.randint(6, 9)
|
||
elif shift_type == 'afternoon':
|
||
shift_hour = random.randint(12, 15)
|
||
else: # night
|
||
shift_hour = random.randint(18, 21)
|
||
|
||
shift_minute = random.choice([0, 15, 30, 45])
|
||
|
||
# Set shift start time
|
||
shift_start = shift_date.replace(hour=shift_hour, minute=shift_minute, second=0, microsecond=0)
|
||
|
||
# Set shift duration (between 6 and 8 hours)
|
||
shift_duration_hours = random.uniform(6, 8)
|
||
|
||
# Set shift end time for closed shifts
|
||
if shift_start < timezone.now() - timedelta(hours=9):
|
||
# Shift is in the past and should be closed
|
||
shift_end = shift_start + timedelta(hours=shift_duration_hours)
|
||
status = 'closed'
|
||
else:
|
||
# Recent or current shift might still be open
|
||
if shift_start > timezone.now():
|
||
# Future shift
|
||
shift_end = None
|
||
status = 'pending'
|
||
elif shift_start + timedelta(hours=shift_duration_hours) < timezone.now():
|
||
# Past shift but might not be closed
|
||
if random.random() < 0.9: # 90% are closed properly
|
||
shift_end = shift_start + timedelta(hours=shift_duration_hours)
|
||
status = 'closed'
|
||
else:
|
||
shift_end = None
|
||
status = 'open' # Open shift that should be closed
|
||
else:
|
||
# Currently open shift
|
||
shift_end = None
|
||
status = 'open'
|
||
|
||
# Set opening cash (typically between 1000-2000 SAR)
|
||
opening_cash = Decimal(str(random.randint(1000, 2000)))
|
||
|
||
# Set closing cash for closed shifts
|
||
if status == 'closed':
|
||
# Closing should roughly match opening plus cash sales, but with some variance
|
||
# We'll set initial values and update them later based on actual transactions
|
||
closing_cash = opening_cash + Decimal(str(random.randint(0, 2000)))
|
||
else:
|
||
closing_cash = None
|
||
|
||
# Create shift
|
||
shift, _ = CashShift.objects.get_or_create(
|
||
cashier=cashier,
|
||
shift_start=shift_start,
|
||
shift_end=shift_end,
|
||
opening_cash=opening_cash,
|
||
closing_cash=closing_cash,
|
||
total_sales=Decimal('0.00'), # Will be updated later
|
||
cash_sales=Decimal('0.00'), # Will be updated later
|
||
pos_sales=Decimal('0.00'), # Will be updated later
|
||
status=status,
|
||
notes=f"Auto-generated {shift_type} shift" if random.random() < 0.3 else "",
|
||
created_at=shift_start
|
||
)
|
||
|
||
shifts.append(shift)
|
||
|
||
return shifts
|
||
|
||
|
||
def create_ticket_inventory(airlines, airports):
|
||
"""Create ticket inventory records"""
|
||
inventory_records = []
|
||
|
||
# Create ticket inventories for the last 12 months
|
||
today = date.today()
|
||
|
||
# Create ticket inventory for each airline at each airport
|
||
for airline in airlines:
|
||
for airport in airports:
|
||
# Create between 1 and 3 inventory records per airline per airport
|
||
num_inventories = random.randint(1, 3)
|
||
|
||
for _ in range(num_inventories):
|
||
# Determine inventory date (within last 12 months)
|
||
months_ago = random.randint(0, 11)
|
||
receive_date = today - timedelta(days=30 * months_ago)
|
||
|
||
# Set expiry date (6-12 months after received)
|
||
expiry_months = random.randint(6, 12)
|
||
expiry_date = receive_date + timedelta(days=30 * expiry_months)
|
||
|
||
# Generate EMD serial ranges
|
||
serial_prefix = f"{airline.code}{airport.code}"
|
||
serial_start_num = random.randint(100000, 999000)
|
||
|
||
# Create inventory quantity (typically 1000-5000 tickets)
|
||
quantity = random.randint(1000, 5000)
|
||
serial_end_num = serial_start_num + quantity - 1
|
||
|
||
emd_serial_start = f"{serial_prefix}{serial_start_num}"
|
||
emd_serial_end = f"{serial_prefix}{serial_end_num}"
|
||
|
||
# Determine usage (varies based on how old the inventory is)
|
||
usage_factor = min(0.9, months_ago / 12) # Older inventory has higher usage
|
||
usage_factor = max(0.1, usage_factor) # Even new inventory has some usage
|
||
|
||
# Add randomness to usage factor
|
||
usage_factor = usage_factor * random.uniform(0.8, 1.2)
|
||
usage_factor = min(0.95, usage_factor) # Cap at 95% usage
|
||
|
||
quantity_used = int(quantity * usage_factor)
|
||
quantity_remaining = quantity - quantity_used
|
||
|
||
# Determine status based on remaining quantity
|
||
if quantity_remaining <= 0:
|
||
status = 'depleted'
|
||
elif quantity_remaining < quantity * 0.1:
|
||
status = 'low'
|
||
elif expiry_date < today:
|
||
status = 'expired'
|
||
else:
|
||
status = 'active'
|
||
|
||
# Create inventory record
|
||
inventory, _ = TicketInventory.objects.get_or_create(
|
||
airline=airline,
|
||
airport=airport,
|
||
emd_serial_start=emd_serial_start,
|
||
emd_serial_end=emd_serial_end,
|
||
quantity_received=quantity,
|
||
quantity_used=quantity_used,
|
||
quantity_remaining=quantity_remaining,
|
||
status=status,
|
||
received_date=receive_date,
|
||
expiry_date=expiry_date,
|
||
notes=f"Batch {serial_start_num}" if random.random() < 0.3 else ""
|
||
)
|
||
|
||
inventory_records.append(inventory)
|
||
|
||
return inventory_records
|
||
|
||
|
||
def create_pos_devices(airports):
|
||
"""Create POS devices for airports"""
|
||
devices = []
|
||
|
||
# Create multiple POS devices for each airport
|
||
for airport in airports:
|
||
# Number of devices per airport (larger airports have more)
|
||
airport_size = len(airport.name) # Simple heuristic based on name length
|
||
num_devices = random.randint(2, max(3, airport_size // 3))
|
||
|
||
for i in range(num_devices):
|
||
# Generate device ID
|
||
device_id = f"POS-{airport.code}-{i + 1:02d}"
|
||
|
||
# Generate serial number
|
||
serial_number = f"M{random.randint(1000000, 9999999)}"
|
||
|
||
# Assign to terminal
|
||
terminal = random.choice(['T1', 'T2', 'T3', 'Main', 'Domestic', 'International'])
|
||
|
||
# Set location within terminal
|
||
locations = [
|
||
f"Check-in counter {random.randint(1, 50)}",
|
||
f"Gate {random.choice('ABCDEFG')}-{random.randint(1, 30)}",
|
||
f"EBT counter {random.randint(1, 10)}",
|
||
f"Customer service desk {random.randint(1, 5)}",
|
||
f"Airline office {random.randint(1, 15)}"
|
||
]
|
||
location = random.choice(locations)
|
||
|
||
# Set status (mostly active)
|
||
status_weights = {'active': 0.85, 'maintenance': 0.1, 'inactive': 0.05}
|
||
status = random.choices(
|
||
list(status_weights.keys()),
|
||
weights=list(status_weights.values()),
|
||
k=1
|
||
)[0]
|
||
|
||
# Set last reconciliation date for active devices
|
||
if status == 'active':
|
||
days_ago = random.randint(0, 7) # Last 7 days
|
||
last_reconciliation = timezone.now() - timedelta(days=days_ago)
|
||
else:
|
||
last_reconciliation = None
|
||
|
||
# Create device
|
||
device, _ = POSDevice.objects.get_or_create(
|
||
device_id=device_id,
|
||
serial_number=serial_number,
|
||
airport=airport,
|
||
terminal=terminal,
|
||
location=location,
|
||
status=status,
|
||
last_reconciliation=last_reconciliation,
|
||
created_at=timezone.now() - timedelta(days=random.randint(30, 365)), # Created 1-12 months ago
|
||
updated_at=timezone.now() - timedelta(days=random.randint(0, 30)) # Updated within last month
|
||
)
|
||
|
||
devices.append(device)
|
||
|
||
return devices
|
||
|
||
|
||
def create_airlines():
|
||
"""Create airline records"""
|
||
airlines = []
|
||
|
||
# Create airlines
|
||
airline_data = [
|
||
{'code': 'SV', 'name': 'Saudia', 'contact_email': 'contact@saudia.com'},
|
||
{'code': 'XY', 'name': 'flynas', 'contact_email': 'contact@flynas.com'},
|
||
{'code': 'F3', 'name': 'flyadeal', 'contact_email': 'contact@flyadeal.com'},
|
||
{'code': 'EK', 'name': 'Emirates', 'contact_email': 'contact@emirates.com'},
|
||
{'code': 'EY', 'name': 'Etihad Airways', 'contact_email': 'contact@etihad.ae'},
|
||
{'code': 'QR', 'name': 'Qatar Airways', 'contact_email': 'contact@qatarairways.com'},
|
||
{'code': 'GF', 'name': 'Gulf Air', 'contact_email': 'contact@gulfair.com'},
|
||
{'code': 'KU', 'name': 'Kuwait Airways', 'contact_email': 'contact@kuwaitairways.com'},
|
||
{'code': 'ME', 'name': 'Middle East Airlines', 'contact_email': 'contact@mea.com.lb'},
|
||
{'code': 'RJ', 'name': 'Royal Jordanian', 'contact_email': 'contact@rj.com'},
|
||
{'code': 'TK', 'name': 'Turkish Airlines', 'contact_email': 'contact@thy.com'},
|
||
{'code': 'BA', 'name': 'British Airways', 'contact_email': 'contact@ba.com'},
|
||
{'code': 'LH', 'name': 'Lufthansa', 'contact_email': 'contact@lufthansa.com'},
|
||
{'code': 'AF', 'name': 'Air France', 'contact_email': 'contact@airfrance.fr'},
|
||
{'code': 'MS', 'name': 'Egyptair', 'contact_email': 'contact@egyptair.com'},
|
||
]
|
||
|
||
for data in airline_data:
|
||
# Skip if airline already exists
|
||
if Airline.objects.filter(code=data['code']).exists():
|
||
airline = Airline.objects.get(code=data['code'])
|
||
else:
|
||
airline, _ = Airline.objects.get_or_create(
|
||
code=data['code'],
|
||
name=data['name'],
|
||
contact_email=data['contact_email'],
|
||
is_active=True,
|
||
created_at=timezone.now(),
|
||
updated_at=timezone.now()
|
||
)
|
||
airlines.append(airline)
|
||
|
||
return airlines
|
||
|
||
|
||
def create_airports():
|
||
"""Create airport records"""
|
||
airports = []
|
||
|
||
# Create airports
|
||
airport_data = [
|
||
{'code': 'RUH', 'name': 'King Khalid International Airport', 'city': 'Riyadh', 'country': 'Saudi Arabia'},
|
||
{'code': 'JED', 'name': 'King Abdulaziz International Airport', 'city': 'Jeddah', 'country': 'Saudi Arabia'},
|
||
{'code': 'DMM', 'name': 'King Fahd International Airport', 'city': 'Dammam', 'country': 'Saudi Arabia'},
|
||
{'code': 'MED', 'name': 'Prince Mohammed Bin Abdulaziz Airport', 'city': 'Medina', 'country': 'Saudi Arabia'},
|
||
{'code': 'TIF', 'name': 'Taif Regional Airport', 'city': 'Taif', 'country': 'Saudi Arabia'},
|
||
{'code': 'AHB', 'name': 'Abha International Airport', 'city': 'Abha', 'country': 'Saudi Arabia'},
|
||
{'code': 'GIZ', 'name': 'Jizan Airport', 'city': 'Jizan', 'country': 'Saudi Arabia'},
|
||
{'code': 'TUU', 'name': 'Tabuk Airport', 'city': 'Tabuk', 'country': 'Saudi Arabia'},
|
||
{'code': 'YNB', 'name': 'Yanbu Airport', 'city': 'Yanbu', 'country': 'Saudi Arabia'},
|
||
{'code': 'ELQ', 'name': 'Gassim Airport', 'city': 'Gassim', 'country': 'Saudi Arabia'},
|
||
{'code': 'DXB', 'name': 'Dubai International Airport', 'city': 'Dubai', 'country': 'United Arab Emirates'},
|
||
{'code': 'AUH', 'name': 'Abu Dhabi International Airport', 'city': 'Abu Dhabi',
|
||
'country': 'United Arab Emirates'},
|
||
{'code': 'DOH', 'name': 'Hamad International Airport', 'city': 'Doha', 'country': 'Qatar'},
|
||
{'code': 'BAH', 'name': 'Bahrain International Airport', 'city': 'Manama', 'country': 'Bahrain'},
|
||
{'code': 'KWI', 'name': 'Kuwait International Airport', 'city': 'Kuwait City', 'country': 'Kuwait'},
|
||
{'code': 'CAI', 'name': 'Cairo International Airport', 'city': 'Cairo', 'country': 'Egypt'},
|
||
{'code': 'AMM', 'name': 'Queen Alia International Airport', 'city': 'Amman', 'country': 'Jordan'},
|
||
{'code': 'BEY', 'name': 'Beirut–Rafic Hariri International Airport', 'city': 'Beirut', 'country': 'Lebanon'},
|
||
{'code': 'IST', 'name': 'Istanbul Airport', 'city': 'Istanbul', 'country': 'Turkey'},
|
||
{'code': 'LHR', 'name': 'Heathrow Airport', 'city': 'London', 'country': 'United Kingdom'},
|
||
{'code': 'CDG', 'name': 'Charles de Gaulle Airport', 'city': 'Paris', 'country': 'France'},
|
||
{'code': 'FRA', 'name': 'Frankfurt Airport', 'city': 'Frankfurt', 'country': 'Germany'},
|
||
{'code': 'FCO', 'name': 'Leonardo da Vinci–Fiumicino Airport', 'city': 'Rome', 'country': 'Italy'},
|
||
]
|
||
|
||
for data in airport_data:
|
||
# Skip if airport already exists
|
||
if Airport.objects.filter(code=data['code']).exists():
|
||
airport = Airport.objects.get(code=data['code'])
|
||
else:
|
||
airport, _ = Airport.objects.get_or_create(
|
||
code=data['code'],
|
||
name=data['name'],
|
||
city=data['city'],
|
||
state='',
|
||
country=data['country'],
|
||
latitude=Decimal('0.0'),
|
||
longitude=Decimal('0.0'),
|
||
elevation_m=Decimal('0.0'),
|
||
is_active=True,
|
||
created_at=timezone.now()
|
||
)
|
||
airports.append(airport)
|
||
|
||
return airports
|
||
|
||
|
||
def create_flights(airlines, airports, num_flights=100):
|
||
"""Create flight data"""
|
||
flights = []
|
||
|
||
# Get common aircraft types
|
||
aircraft_types = {
|
||
'SV': ['Boeing 777-300ER', 'Boeing 787-9', 'Airbus A320-200', 'Airbus A330-300'],
|
||
'XY': ['Airbus A320neo', 'Airbus A320-200'],
|
||
'F3': ['Airbus A320-200'],
|
||
'EK': ['Boeing 777-300ER', 'Airbus A380-800', 'Boeing 777-200LR'],
|
||
'EY': ['Boeing 787-9', 'Boeing 777-300ER', 'Airbus A320-200'],
|
||
'QR': ['Boeing 777-300ER', 'Airbus A350-900', 'Boeing 787-8'],
|
||
'default': ['Boeing 737-800', 'Airbus A320-200', 'Boeing 777-200', 'Airbus A330-200']
|
||
}
|
||
|
||
# Flight durations between popular routes (in minutes)
|
||
route_durations = {
|
||
('RUH', 'JED'): 105,
|
||
('JED', 'RUH'): 110,
|
||
('RUH', 'DMM'): 70,
|
||
('DMM', 'RUH'): 75,
|
||
('JED', 'DMM'): 120,
|
||
('DMM', 'JED'): 125,
|
||
('RUH', 'DXB'): 105,
|
||
('DXB', 'RUH'): 110,
|
||
('JED', 'CAI'): 120,
|
||
('CAI', 'JED'): 125,
|
||
('RUH', 'CAI'): 180,
|
||
('CAI', 'RUH'): 185,
|
||
('RUH', 'DOH'): 75,
|
||
('DOH', 'RUH'): 80,
|
||
('JED', 'IST'): 240,
|
||
('IST', 'JED'): 245,
|
||
('default', 'default'): 120
|
||
}
|
||
|
||
# Common domestic routes in Saudi Arabia
|
||
domestic_routes = [
|
||
('RUH', 'JED'), ('JED', 'RUH'),
|
||
('RUH', 'DMM'), ('DMM', 'RUH'),
|
||
('JED', 'DMM'), ('DMM', 'JED'),
|
||
('RUH', 'MED'), ('MED', 'RUH'),
|
||
('JED', 'MED'), ('MED', 'JED'),
|
||
('RUH', 'AHB'), ('AHB', 'RUH'),
|
||
('JED', 'GIZ'), ('GIZ', 'JED'),
|
||
('RUH', 'TUU'), ('TUU', 'RUH'),
|
||
('RUH', 'TIF'), ('TIF', 'RUH'),
|
||
('JED', 'TIF'), ('TIF', 'JED'),
|
||
('RUH', 'ELQ'), ('ELQ', 'RUH'),
|
||
('RUH', 'YNB'), ('YNB', 'RUH'),
|
||
]
|
||
|
||
# Common international routes from Saudi Arabia
|
||
international_routes = [
|
||
('RUH', 'DXB'), ('DXB', 'RUH'),
|
||
('JED', 'DXB'), ('DXB', 'JED'),
|
||
('RUH', 'DOH'), ('DOH', 'RUH'),
|
||
('JED', 'DOH'), ('DOH', 'JED'),
|
||
('RUH', 'AUH'), ('AUH', 'RUH'),
|
||
('JED', 'CAI'), ('CAI', 'JED'),
|
||
('RUH', 'CAI'), ('CAI', 'RUH'),
|
||
('JED', 'AMM'), ('AMM', 'JED'),
|
||
('RUH', 'BAH'), ('BAH', 'RUH'),
|
||
('JED', 'IST'), ('IST', 'JED'),
|
||
('RUH', 'IST'), ('IST', 'RUH'),
|
||
('JED', 'KWI'), ('KWI', 'JED'),
|
||
('RUH', 'BEY'), ('BEY', 'RUH'),
|
||
('JED', 'LHR'), ('LHR', 'JED'),
|
||
('RUH', 'CDG'), ('CDG', 'RUH'),
|
||
]
|
||
|
||
# Get all Saudi airports
|
||
saudi_airports = [airport for airport in airports if airport.country == 'Saudi Arabia']
|
||
|
||
# Flight numbers for airlines
|
||
flight_number_ranges = {
|
||
'SV': (1000, 1999),
|
||
'XY': (100, 999),
|
||
'F3': (100, 999),
|
||
'EK': (800, 899),
|
||
'EY': (600, 699),
|
||
'QR': (1200, 1299),
|
||
'GF': (500, 599),
|
||
'KU': (100, 199),
|
||
'ME': (400, 499),
|
||
'MS': (600, 699),
|
||
'RJ': (300, 399),
|
||
'TK': (100, 199),
|
||
'default': (1000, 9999)
|
||
}
|
||
|
||
# Start date for flight schedules (7 days ago)
|
||
start_date = timezone.now() - timedelta(days=7)
|
||
|
||
# Create flights for the next 30 days
|
||
for day_offset in range(0, 30):
|
||
current_date = start_date + timedelta(days=day_offset)
|
||
|
||
# Create more flights for busier days (weekends)
|
||
is_weekend = current_date.weekday() >= 4 # Friday and Saturday in Middle East
|
||
daily_flights = int(num_flights / 20) * (2 if is_weekend else 1)
|
||
|
||
for _ in range(daily_flights):
|
||
# Select airline (with more weight to Saudi airlines)
|
||
airline_weights = [3 if a.code in ['SV', 'XY', 'F3'] else 1 for a in airlines]
|
||
airline = random.choices(airlines, weights=airline_weights, k=1)[0]
|
||
|
||
# Determine if domestic or international flight
|
||
is_domestic = random.random() < 0.6 if airline.code in ['SV', 'XY', 'F3'] else 0.1
|
||
|
||
# Select route
|
||
if is_domestic:
|
||
# Domestic route
|
||
if random.random() < 0.8 and domestic_routes:
|
||
origin_code, dest_code = random.choice(domestic_routes)
|
||
else:
|
||
# Random domestic route
|
||
if len(saudi_airports) >= 2:
|
||
origin, dest = random.sample(saudi_airports, 2)
|
||
origin_code, dest_code = origin.code, dest.code
|
||
else:
|
||
# Fallback if not enough Saudi airports
|
||
route = random.choice(domestic_routes or international_routes)
|
||
origin_code, dest_code = route
|
||
else:
|
||
# International route
|
||
if random.random() < 0.8 and international_routes:
|
||
origin_code, dest_code = random.choice(international_routes)
|
||
else:
|
||
# Random international route
|
||
saudi_airport = random.choice(saudi_airports)
|
||
international_airport = random.choice([a for a in airports if a.country != 'Saudi Arabia'])
|
||
if random.random() < 0.5:
|
||
origin_code, dest_code = saudi_airport.code, international_airport.code
|
||
else:
|
||
origin_code, dest_code = international_airport.code, saudi_airport.code
|
||
|
||
# Find the airport objects
|
||
origin_airport = next((a for a in airports if a.code == origin_code), None)
|
||
dest_airport = next((a for a in airports if a.code == dest_code), None)
|
||
|
||
if not origin_airport or not dest_airport:
|
||
continue
|
||
|
||
# Generate flight number
|
||
range_min, range_max = flight_number_ranges.get(airline.code, flight_number_ranges['default'])
|
||
flight_number = str(random.randint(range_min, range_max))
|
||
|
||
# Generate departure time
|
||
hour = random.randint(0, 23)
|
||
minute = random.choice([0, 15, 30, 45])
|
||
scheduled_departure = current_date.replace(hour=hour, minute=minute, second=0, microsecond=0)
|
||
|
||
# Calculate flight duration and arrival time
|
||
route_key = (origin_code, dest_code)
|
||
default_key = ('default', 'default')
|
||
duration_minutes = route_durations.get(route_key, route_durations.get(default_key, 120))
|
||
|
||
# Add some randomness to duration
|
||
duration_minutes += random.randint(-10, 20)
|
||
scheduled_arrival = scheduled_departure + timedelta(minutes=duration_minutes)
|
||
|
||
# Determine flight type
|
||
flight_type = 'domestic' if is_domestic else 'international'
|
||
|
||
# Determine flight status based on departure time
|
||
now = timezone.now()
|
||
actual_departure = None
|
||
actual_arrival = None
|
||
|
||
if scheduled_departure > now + timedelta(hours=1):
|
||
status = 'scheduled'
|
||
elif scheduled_departure > now:
|
||
status = 'boarding'
|
||
elif scheduled_arrival > now:
|
||
status = 'departed'
|
||
# Set actual departure time
|
||
delay_minutes = random.choices(
|
||
[0, random.randint(1, 15), random.randint(16, 60), random.randint(61, 180)],
|
||
weights=[0.7, 0.15, 0.1, 0.05],
|
||
k=1
|
||
)[0]
|
||
actual_departure = scheduled_departure + timedelta(minutes=delay_minutes)
|
||
else:
|
||
status = 'arrived'
|
||
# Set actual departure and arrival times
|
||
delay_minutes = random.choices(
|
||
[0, random.randint(1, 15), random.randint(16, 60), random.randint(61, 180)],
|
||
weights=[0.7, 0.15, 0.1, 0.05],
|
||
k=1
|
||
)[0]
|
||
actual_departure = scheduled_departure + timedelta(minutes=delay_minutes)
|
||
|
||
# Actual arrival might be faster or slower than scheduled
|
||
arrival_variance = random.randint(-15, 30)
|
||
actual_flight_duration = duration_minutes + arrival_variance
|
||
actual_arrival = actual_departure + timedelta(minutes=actual_flight_duration)
|
||
|
||
# Randomly cancel some flights
|
||
if random.random() < 0.03: # 3% chance of cancellation
|
||
status = 'cancelled'
|
||
actual_departure = None
|
||
actual_arrival = None
|
||
|
||
# Set delays for some flights
|
||
if random.random() < 0.15 and status == 'scheduled': # 15% chance of delay
|
||
status = 'delayed'
|
||
|
||
# Determine aircraft type
|
||
aircraft_types_for_airline = aircraft_types.get(airline.code, aircraft_types['default'])
|
||
aircraft_type = random.choice(aircraft_types_for_airline)
|
||
|
||
# Generate aircraft registration
|
||
if airline.code == 'SV':
|
||
registration = f"HZ-A{random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ')}{random.randint(10, 99)}"
|
||
else:
|
||
country_codes = {'EK': 'A6-', 'EY': 'A6-', 'QR': 'A7-', 'GF': 'A9C-', 'KU': '9K-'}
|
||
prefix = country_codes.get(airline.code, random.choice(['N', 'G-', 'F-', 'D-', 'C-', 'B-', 'VH-']))
|
||
registration = f"{prefix}{random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ')}{random.randint(100, 999)}"
|
||
|
||
# Set passenger capacity based on aircraft type
|
||
if 'A380' in aircraft_type:
|
||
passenger_capacity = random.randint(480, 615)
|
||
elif '777-300' in aircraft_type:
|
||
passenger_capacity = random.randint(340, 420)
|
||
elif '777-200' in aircraft_type:
|
||
passenger_capacity = random.randint(280, 350)
|
||
elif '787' in aircraft_type:
|
||
passenger_capacity = random.randint(240, 330)
|
||
elif 'A350' in aircraft_type:
|
||
passenger_capacity = random.randint(300, 370)
|
||
elif 'A330' in aircraft_type:
|
||
passenger_capacity = random.randint(250, 330)
|
||
elif '737' in aircraft_type or 'A320' in aircraft_type:
|
||
passenger_capacity = random.randint(150, 200)
|
||
else:
|
||
passenger_capacity = random.randint(100, 350)
|
||
|
||
# Set cargo capacity
|
||
if 'A380' in aircraft_type:
|
||
cargo_capacity_kg = Decimal(str(random.randint(40000, 60000)))
|
||
elif '777' in aircraft_type:
|
||
cargo_capacity_kg = Decimal(str(random.randint(20000, 35000)))
|
||
elif '787' in aircraft_type:
|
||
cargo_capacity_kg = Decimal(str(random.randint(15000, 25000)))
|
||
elif 'A350' in aircraft_type:
|
||
cargo_capacity_kg = Decimal(str(random.randint(18000, 28000)))
|
||
elif 'A330' in aircraft_type:
|
||
cargo_capacity_kg = Decimal(str(random.randint(15000, 25000)))
|
||
elif '737' in aircraft_type or 'A320' in aircraft_type:
|
||
cargo_capacity_kg = Decimal(str(random.randint(5000, 10000)))
|
||
else:
|
||
cargo_capacity_kg = Decimal(str(random.randint(3000, 20000)))
|
||
|
||
# Set terminal and gate information
|
||
departure_terminal = random.choice(['1', '2', '3', '4', 'A', 'B', 'C', 'D', 'International', 'Domestic'])
|
||
departure_gate = f"{random.choice('ABCDEFG')}{random.randint(1, 50)}"
|
||
arrival_terminal = random.choice(['1', '2', '3', '4', 'A', 'B', 'C', 'D', 'International', 'Domestic'])
|
||
arrival_gate = f"{random.choice('ABCDEFG')}{random.randint(1, 50)}"
|
||
|
||
# Create the flight
|
||
flight, _ = Flight.objects.get_or_create(
|
||
flight_number=flight_number,
|
||
airline=airline,
|
||
origin_airport=origin_airport,
|
||
destination_airport=dest_airport,
|
||
scheduled_departure=scheduled_departure,
|
||
scheduled_arrival=scheduled_arrival,
|
||
actual_departure=actual_departure,
|
||
actual_arrival=actual_arrival,
|
||
aircraft_type=aircraft_type,
|
||
aircraft_registration=registration,
|
||
flight_type=flight_type,
|
||
status=status,
|
||
passenger_capacity=passenger_capacity,
|
||
cargo_capacity_kg=cargo_capacity_kg,
|
||
departure_terminal=departure_terminal,
|
||
departure_gate=departure_gate,
|
||
arrival_terminal=arrival_terminal,
|
||
arrival_gate=arrival_gate,
|
||
notes=''
|
||
)
|
||
flights.append(flight)
|
||
|
||
return flights
|
||
|
||
|
||
def create_passengers(flights, users):
|
||
"""Create passenger data for flights"""
|
||
passengers = []
|
||
|
||
# Names data
|
||
first_names_male = [
|
||
'Mohammed', 'Abdullah', 'Ahmed', 'Khalid', 'Fahad', 'Ali', 'Omar', 'Ibrahim',
|
||
'Saleh', 'Saad', 'Nasser', 'Abdulaziz', 'Faisal', 'Yousef', 'Saud', 'Bandar',
|
||
'Turki', 'Majid', 'Waleed', 'Nawaf', 'Mishaal', 'Sultan', 'Talal', 'Meshari',
|
||
'Bader', 'Rayan', 'Hamad', 'Naif', 'Mansour', 'Mutlaq', 'Adel', 'Ziyad'
|
||
]
|
||
|
||
first_names_female = [
|
||
'Fatima', 'Aisha', 'Nora', 'Maha', 'Sara', 'Hessa', 'Latifa', 'Layla',
|
||
'Reema', 'Nouf', 'Mona', 'Abeer', 'Reem', 'Mariam', 'Asma', 'Amira',
|
||
'Mashael', 'Haifa', 'Najla', 'Munira', 'Samira', 'Dalal', 'Joud', 'Lamia',
|
||
'Ghadah', 'Eman', 'Nada', 'Noof', 'Shaikha', 'Hind', 'Alaa', 'Ghada'
|
||
]
|
||
|
||
last_names = [
|
||
'Al-Saud', 'Al-Qahtani', 'Al-Ghamdi', 'Al-Otaibi', 'Al-Harbi', 'Al-Shammari',
|
||
'Al-Dossari', 'Al-Zahrani', 'Al-Mutairi', 'Al-Anazi', 'Al-Shamrani', 'Al-Qurashi',
|
||
'Al-Juhani', 'Al-Maliki', 'Al-Ruwaili', 'Al-Subaie', 'Al-Khaldi', 'Al-Najjar',
|
||
'Al-Balawi', 'Al-Rashidi', 'Al-Yami', 'Al-Asiri', 'Al-Dawsari', 'Al-Mohsen',
|
||
'Al-Enezi', 'Al-Faifi', 'Al-Subhi', 'Al-Ahmari', 'Al-Hamad', 'Al-Marri',
|
||
'Al-Hajri', 'Al-Ghamidi', 'Al-Zaidi', 'Al-Buqami', 'Al-Hawawi', 'Al-Harthy'
|
||
]
|
||
|
||
# Nationalities for international diversity
|
||
nationalities = [
|
||
'Saudi', 'Saudi', 'Saudi', 'Saudi', 'Saudi', # More weight to Saudi
|
||
'Emirati', 'Egyptian', 'Jordanian', 'Lebanese', 'Kuwaiti',
|
||
'Bahraini', 'Qatari', 'Omani', 'Pakistani', 'Indian',
|
||
'Filipino', 'Bangladeshi', 'American', 'British', 'French'
|
||
]
|
||
|
||
# Email domains
|
||
email_domains = ['gmail.com', 'hotmail.com', 'yahoo.com', 'outlook.com', 'icloud.com']
|
||
|
||
# Meal codes
|
||
special_meals = ['', '', '', '', '', # More weight to no special meal
|
||
'VGML', 'KSML', 'MOML', 'DBML', 'GFML', 'PFML', 'AVML', 'SFML', 'LCML']
|
||
|
||
# Special assistance types
|
||
special_assistance_types = ['', '', '', '', '', # More weight to no special assistance
|
||
'Wheelchair', 'Unaccompanied minor', 'Blind passenger',
|
||
'Deaf passenger', 'Medical oxygen', 'Service animal']
|
||
|
||
# Frequent flyer tiers
|
||
ff_tiers = ['', '', '', '', '', # More weight to no tier
|
||
'Blue', 'Silver', 'Gold', 'Platinum', 'Diamond']
|
||
|
||
# Assign a random admin user as the creator
|
||
admin_user = random.choice(users) if users else None
|
||
|
||
# Process each flight
|
||
for flight in flights:
|
||
# Skip cancelled flights
|
||
if flight.status == 'cancelled':
|
||
continue
|
||
|
||
# Only create passengers for flights that are within 2 days before/after now
|
||
flight_time = flight.scheduled_departure
|
||
now = timezone.now()
|
||
if abs((flight_time - now).total_seconds()) > 172800: # 48 hours in seconds
|
||
# For flights outside our window, only create a few passengers (10% chance)
|
||
if random.random() > 0.1:
|
||
continue
|
||
|
||
# Determine passenger load factor (how full is the flight)
|
||
if flight.status in ['departed', 'arrived']:
|
||
# Departed/arrived flights tend to have higher load factors
|
||
load_factor = random.uniform(0.7, 0.95)
|
||
else:
|
||
# Future flights have more variable load factors
|
||
load_factor = random.uniform(0.4, 0.9)
|
||
|
||
# Calculate number of passengers to create
|
||
if flight.passenger_capacity:
|
||
num_passengers = int(flight.passenger_capacity * load_factor)
|
||
else:
|
||
# Default if capacity not set
|
||
num_passengers = random.randint(50, 150)
|
||
|
||
# Create passengers
|
||
for _ in range(num_passengers):
|
||
# Generate basic info
|
||
gender = random.choice(['M', 'F'])
|
||
first_name = random.choice(first_names_male if gender == 'M' else first_names_female)
|
||
last_name = random.choice(last_names)
|
||
|
||
# Sometimes add a middle name
|
||
if random.random() < 0.3:
|
||
middle_name = random.choice(first_names_male if gender == 'M' else first_names_female)
|
||
else:
|
||
middle_name = ''
|
||
|
||
# Generate date of birth (age distribution)
|
||
age_group = random.choices(['adult', 'child', 'infant', 'senior'],
|
||
weights=[0.75, 0.15, 0.05, 0.05], k=1)[0]
|
||
|
||
today = date.today()
|
||
if age_group == 'adult':
|
||
age = random.randint(18, 65)
|
||
elif age_group == 'child':
|
||
age = random.randint(2, 17)
|
||
elif age_group == 'infant':
|
||
age = random.randint(0, 1)
|
||
else: # senior
|
||
age = random.randint(66, 85)
|
||
|
||
birth_year = today.year - age
|
||
birth_month = random.randint(1, 12)
|
||
# Make sure February dates are valid
|
||
max_day = 28 if birth_month == 2 else 30 if birth_month in [4, 6, 9, 11] else 31
|
||
birth_day = random.randint(1, max_day)
|
||
|
||
try:
|
||
date_of_birth = date(birth_year, birth_month, birth_day)
|
||
except ValueError:
|
||
# If invalid date, use safe defaults
|
||
date_of_birth = date(birth_year, 1, 1)
|
||
|
||
# Nationality
|
||
nationality = random.choice(nationalities)
|
||
|
||
# Contact information
|
||
if random.random() < 0.7: # 70% have email
|
||
email = f"{first_name.lower()}.{last_name.lower()}{random.randint(1, 999)}@{random.choice(email_domains)}"
|
||
else:
|
||
email = ''
|
||
|
||
if random.random() < 0.8: # 80% have phone
|
||
if nationality == 'Saudi':
|
||
phone = f"+966 5{random.randint(10000000, 99999999)}"
|
||
else:
|
||
phone = f"+{random.randint(1, 999)} {random.randint(1000000, 9999999)}"
|
||
else:
|
||
phone = ''
|
||
|
||
# Travel documents
|
||
passport_number = f"{random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ')}{random.randint(10000000, 99999999)}"
|
||
passport_expiry = today + timedelta(days=random.randint(30, 3650)) # 1 month to 10 years
|
||
|
||
# Visa number (for some passengers)
|
||
if random.random() < 0.3:
|
||
visa_number = f"V{random.randint(1000000, 9999999)}"
|
||
else:
|
||
visa_number = ''
|
||
|
||
# Generate ticket number
|
||
ticket_number = f"{flight.airline.code}{random.randint(1000000000, 9999999999)}"
|
||
|
||
# Generate booking reference (PNR)
|
||
booking_reference = ''.join(random.choices('ABCDEFGHJKLMNPQRSTUVWXYZ23456789', k=6))
|
||
|
||
# Seat assignment
|
||
if flight.status in ['boarding', 'departed', 'arrived']:
|
||
# Assign seats for boarding/departed flights
|
||
row = random.randint(1, 50)
|
||
seat_letter = random.choice('ABCDEFGHJK')
|
||
seat_number = f"{row}{seat_letter}"
|
||
else:
|
||
# Some passengers don't have seat assignments yet
|
||
seat_number = '' if random.random() < 0.3 else f"{random.randint(1, 50)}{random.choice('ABCDEFGHJK')}"
|
||
|
||
# Passenger type
|
||
if age <= 1:
|
||
passenger_type = 'infant'
|
||
elif age <= 17:
|
||
passenger_type = 'child'
|
||
elif age >= 65:
|
||
passenger_type = 'senior'
|
||
else:
|
||
passenger_type = 'adult'
|
||
|
||
# Travel class with weighting
|
||
travel_class_weights = {'economy': 0.85, 'business': 0.12, 'first': 0.03}
|
||
travel_class = random.choices(
|
||
list(travel_class_weights.keys()),
|
||
weights=list(travel_class_weights.values()),
|
||
k=1
|
||
)[0]
|
||
|
||
# Passenger status
|
||
if flight.status == 'scheduled':
|
||
# Future flight - most are just checked in
|
||
status_weights = {'checked_in': 0.97, 'cancelled': 0.03}
|
||
elif flight.status == 'boarding':
|
||
# Boarding flight - mix of checked in and boarded
|
||
status_weights = {'checked_in': 0.4, 'boarded': 0.6}
|
||
elif flight.status in ['departed', 'arrived']:
|
||
# Flight has left - most are boarded, some no-shows
|
||
status_weights = {'boarded': 0.97, 'no_show': 0.03}
|
||
else:
|
||
# Default/delayed flight
|
||
status_weights = {'checked_in': 0.95, 'cancelled': 0.05}
|
||
|
||
status = random.choices(
|
||
list(status_weights.keys()),
|
||
weights=list(status_weights.values()),
|
||
k=1
|
||
)[0]
|
||
|
||
# Baggage information
|
||
if travel_class == 'economy':
|
||
checked_bags_count = random.choices([0, 1, 2], weights=[0.2, 0.7, 0.1], k=1)[0]
|
||
elif travel_class == 'business':
|
||
checked_bags_count = random.choices([0, 1, 2, 3], weights=[0.1, 0.3, 0.5, 0.1], k=1)[0]
|
||
else: # first class
|
||
checked_bags_count = random.choices([0, 1, 2, 3], weights=[0.05, 0.25, 0.5, 0.2], k=1)[0]
|
||
|
||
# Calculate baggage weight
|
||
if checked_bags_count == 0:
|
||
checked_bags_weight_kg = Decimal('0.00')
|
||
else:
|
||
# Weight per bag
|
||
weight_per_bag = Decimal(str(random.uniform(15.0, 25.0)))
|
||
checked_bags_weight_kg = round(weight_per_bag * Decimal(str(checked_bags_count)), 2)
|
||
|
||
# Carry-on bags
|
||
carry_on_bags_count = random.choices([0, 1, 2], weights=[0.05, 0.85, 0.1], k=1)[0]
|
||
|
||
# Special requirements
|
||
special_meal = random.choice(special_meals)
|
||
special_assistance = random.choice(special_assistance_types)
|
||
|
||
# Medical conditions (rarely populated)
|
||
if random.random() < 0.05:
|
||
medical_conditions_list = [
|
||
"Diabetes", "Hypertension", "Asthma", "Heart condition",
|
||
"Mobility issues", "Pregnancy", "Allergies"
|
||
]
|
||
medical_conditions = random.choice(medical_conditions_list)
|
||
else:
|
||
medical_conditions = ""
|
||
|
||
# Check-in information
|
||
if status in ['checked_in', 'boarded', 'no_show']:
|
||
# Check-in happens 24-4 hours before flight
|
||
checkin_offset = timedelta(hours=random.randint(4, 24))
|
||
check_in_time = flight.scheduled_departure - checkin_offset
|
||
check_in_counter = f"{random.randint(1, 50)}"
|
||
else:
|
||
check_in_time = None
|
||
check_in_counter = ""
|
||
|
||
# Boarding information
|
||
if status == 'boarded':
|
||
# Boarding happens 90-30 minutes before flight
|
||
boarding_offset = timedelta(minutes=random.randint(30, 90))
|
||
boarding_time = flight.scheduled_departure - boarding_offset
|
||
boarding_gate = flight.departure_gate
|
||
else:
|
||
boarding_time = None
|
||
boarding_gate = ""
|
||
|
||
# Frequent flyer information
|
||
if random.random() < 0.4: # 40% have frequent flyer numbers
|
||
frequent_flyer_number = f"{flight.airline.code}{random.randint(10000000, 99999999)}"
|
||
frequent_flyer_tier = random.choice(ff_tiers)
|
||
else:
|
||
frequent_flyer_number = ""
|
||
frequent_flyer_tier = ""
|
||
|
||
# Create the passenger
|
||
passenger, _ = Passenger.objects.get_or_create(
|
||
first_name=first_name,
|
||
last_name=last_name,
|
||
middle_name=middle_name,
|
||
date_of_birth=date_of_birth,
|
||
nationality=nationality,
|
||
gender=gender,
|
||
passport_number=passport_number,
|
||
passport_expiry=passport_expiry,
|
||
visa_number=visa_number,
|
||
email=email,
|
||
phone=phone,
|
||
flight=flight,
|
||
ticket_number=ticket_number,
|
||
booking_reference=booking_reference,
|
||
seat_number=seat_number,
|
||
passenger_type=passenger_type,
|
||
travel_class=travel_class,
|
||
status=status,
|
||
checked_bags_count=checked_bags_count,
|
||
checked_bags_weight_kg=checked_bags_weight_kg,
|
||
carry_on_bags_count=carry_on_bags_count,
|
||
special_meal=special_meal,
|
||
special_assistance=special_assistance,
|
||
medical_conditions=medical_conditions,
|
||
check_in_time=check_in_time,
|
||
boarding_time=boarding_time,
|
||
check_in_counter=check_in_counter,
|
||
boarding_gate=boarding_gate,
|
||
frequent_flyer_number=frequent_flyer_number,
|
||
frequent_flyer_tier=frequent_flyer_tier,
|
||
created_by=admin_user
|
||
)
|
||
passengers.append(passenger)
|
||
|
||
return passengers
|
||
|
||
|
||
def create_ebt_transactions(airlines, shifts, devices):
|
||
"""Create EBT transactions"""
|
||
transactions = []
|
||
|
||
# Saudi and Arab passenger names
|
||
saudi_first_names = [
|
||
'Mohammed', 'Abdullah', 'Ahmed', 'Khalid', 'Fahad', 'Ali', 'Omar', 'Ibrahim',
|
||
'Saleh', 'Saad', 'Nasser', 'Abdulaziz', 'Faisal', 'Yousef', 'Saud', 'Bandar',
|
||
'Fatima', 'Aisha', 'Nora', 'Maha', 'Sara', 'Hessa', 'Latifa', 'Layla',
|
||
'Reema', 'Nouf', 'Mona', 'Abeer', 'Reem', 'Mariam', 'Asma', 'Amira'
|
||
]
|
||
|
||
saudi_last_names = [
|
||
'Al-Saud', 'Al-Qahtani', 'Al-Ghamdi', 'Al-Otaibi', 'Al-Harbi', 'Al-Shammari',
|
||
'Al-Dossari', 'Al-Zahrani', 'Al-Mutairi', 'Al-Anazi', 'Al-Shamrani', 'Al-Qurashi',
|
||
'Al-Juhani', 'Al-Maliki', 'Al-Ruwaili', 'Al-Subaie', 'Al-Khaldi', 'Al-Najjar',
|
||
'Al-Balawi', 'Al-Rashidi', 'Al-Yami', 'Al-Asiri', 'Al-Dawsari', 'Al-Mohsen'
|
||
]
|
||
|
||
# Flight numbers for airlines
|
||
flight_patterns = {
|
||
'SV': 'SV{0}', # Saudia
|
||
'XY': 'XY{0}', # flynas
|
||
'F3': 'F3{0}', # flyadeal
|
||
'EK': 'EK{0}', # Emirates
|
||
'EY': 'EY{0}', # Etihad
|
||
'QR': 'QR{0}', # Qatar
|
||
'GF': 'GF{0}', # Gulf Air
|
||
'KU': 'KU{0}', # Kuwait
|
||
'ME': 'ME{0}', # MEA
|
||
'RJ': 'RJ{0}', # Royal Jordanian
|
||
'TK': 'TK{0}', # Turkish
|
||
'BA': 'BA{0}', # British Airways
|
||
'LH': 'LH{0}', # Lufthansa
|
||
'AF': 'AF{0}', # Air France
|
||
'MS': 'MS{0}', # Egyptair
|
||
}
|
||
|
||
# Common destinations from Saudi airports
|
||
destinations = {
|
||
'SV': ['CAI', 'DXB', 'KHI', 'ISL', 'AMM', 'BEY', 'KWI', 'BAH', 'DOH', 'IST', 'LHR', 'CDG', 'FCO', 'JFK', 'KUL',
|
||
'CGK'],
|
||
'XY': ['CAI', 'DXB', 'AMM', 'BAH', 'KWI', 'DOH', 'IST', 'HRG', 'SSH'],
|
||
'F3': ['CAI', 'DXB', 'AMM', 'KWI', 'BAH', 'DOH'],
|
||
'EK': ['DXB'],
|
||
'EY': ['AUH'],
|
||
'QR': ['DOH'],
|
||
'GF': ['BAH'],
|
||
'KU': ['KWI'],
|
||
'ME': ['BEY'],
|
||
'RJ': ['AMM'],
|
||
'TK': ['IST'],
|
||
'BA': ['LHR'],
|
||
'LH': ['FRA', 'MUC'],
|
||
'AF': ['CDG'],
|
||
'MS': ['CAI'],
|
||
}
|
||
|
||
# Popular Saudi domestic routes
|
||
domestic_routes = {
|
||
'SV': ['RUH', 'JED', 'DMM', 'MED', 'AHB', 'TIF', 'GIZ', 'TUU', 'YNB', 'ELQ'],
|
||
'XY': ['RUH', 'JED', 'DMM', 'MED', 'AHB', 'TIF', 'GIZ'],
|
||
'F3': ['RUH', 'JED', 'DMM', 'MED'],
|
||
}
|
||
|
||
for shift in shifts:
|
||
# Skip some shifts to make data more realistic
|
||
if random.random() > 0.8:
|
||
continue
|
||
|
||
# Determine number of transactions in this shift
|
||
num_transactions = random.randint(5, 25)
|
||
|
||
for _ in range(num_transactions):
|
||
# Select a random airline with more weight to Saudi airlines
|
||
airline_weights = [3 if a.code in ['SV', 'XY', 'F3'] else 1 for a in airlines]
|
||
airline = random.choices(airlines, weights=airline_weights, k=1)[0]
|
||
|
||
# Generate flight number
|
||
flight_num = random.randint(100, 1999)
|
||
if airline.code in domestic_routes and random.random() < 0.6:
|
||
# Domestic flight
|
||
origin = shift.cashier.airport.code
|
||
destination = random.choice([d for d in domestic_routes[airline.code] if d != origin])
|
||
flight_number = flight_patterns[airline.code].format(flight_num)
|
||
else:
|
||
# International flight
|
||
if airline.code in destinations:
|
||
destination = random.choice(destinations[airline.code])
|
||
else:
|
||
destination = random.choice(['LHR', 'CDG', 'FRA', 'IST', 'DXB', 'DOH', 'CAI'])
|
||
flight_number = flight_patterns[airline.code].format(flight_num)
|
||
|
||
# Generate passenger name
|
||
first_name = random.choice(saudi_first_names)
|
||
last_name = random.choice(saudi_last_names)
|
||
passenger_name = f"{first_name} {last_name}"
|
||
|
||
# Generate ticket number
|
||
ticket_number = f"{airline.code}{random.randint(1000000000, 9999999999)}"
|
||
|
||
# Generate EMD serial (sometimes blank for cash transactions)
|
||
if random.random() < 0.8:
|
||
emd_serial = f"{airline.code}{shift.cashier.airport.code}{random.randint(100000, 999999)}"
|
||
else:
|
||
emd_serial = ""
|
||
|
||
# Generate baggage weight and rate
|
||
weight_kg = Decimal(str(random.randint(5, 40)))
|
||
|
||
# Rates vary by airline and destination
|
||
if airline.code in ['SV', 'XY', 'F3']: # Saudi airlines
|
||
if destination in domestic_routes.get(airline.code, []): # Domestic
|
||
rate_per_kg = Decimal(str(random.randint(40, 60)))
|
||
else: # International
|
||
rate_per_kg = Decimal(str(random.randint(70, 120)))
|
||
else: # Foreign airlines
|
||
rate_per_kg = Decimal(str(random.randint(80, 150)))
|
||
|
||
# Calculate total
|
||
total_amount = weight_kg * rate_per_kg
|
||
|
||
# Payment method (MADA is most common in KSA)
|
||
payment_method_weights = {'cash': 0.3, 'mada': 0.5, 'visa': 0.1, 'mastercard': 0.07, 'amex': 0.03}
|
||
payment_method = random.choices(
|
||
list(payment_method_weights.keys()),
|
||
weights=list(payment_method_weights.values()),
|
||
k=1
|
||
)[0]
|
||
|
||
# POS device for card payments
|
||
pos_device = None
|
||
pos_reference = ""
|
||
if payment_method != 'cash':
|
||
# Get POS device at the same airport
|
||
airport_devices = [d for d in devices if d.airport == shift.cashier.airport and d.status == 'active']
|
||
if airport_devices:
|
||
pos_device = random.choice(airport_devices)
|
||
pos_reference = f"MADA{random.randint(1000000, 9999999)}"
|
||
|
||
# Transaction status (mostly completed)
|
||
status_weights = {'completed': 0.94, 'refunded': 0.03, 'cancelled': 0.02, 'pending': 0.01}
|
||
status = random.choices(
|
||
list(status_weights.keys()),
|
||
weights=list(status_weights.values()),
|
||
k=1
|
||
)[0]
|
||
|
||
# Transaction date within shift timeframe
|
||
if shift.shift_end:
|
||
transaction_date = shift.shift_start + timedelta(
|
||
minutes=random.randint(0, int((shift.shift_end - shift.shift_start).total_seconds() / 60)))
|
||
else:
|
||
transaction_date = shift.shift_start + timedelta(minutes=random.randint(0, 480)) # within 8 hours
|
||
|
||
# Refund data if applicable
|
||
refund_date = None
|
||
refund_type = "full"
|
||
refund_method = "original"
|
||
refunded_by = None
|
||
if status == 'refunded':
|
||
refund_date = transaction_date + timedelta(hours=random.randint(1, 24))
|
||
refund_types = ['full', 'partial', 'fees']
|
||
refund_type = random.choices(refund_types, weights=[0.7, 0.2, 0.1], k=1)[0]
|
||
refund_methods = ['original', 'cash', 'bank']
|
||
refund_method = random.choices(refund_methods, weights=[0.6, 0.3, 0.1], k=1)[0]
|
||
refunded_by = shift.cashier.user
|
||
|
||
# Notes
|
||
notes_options = [
|
||
"",
|
||
"Hajj/Umrah passenger",
|
||
"VIP passenger",
|
||
"Sports equipment",
|
||
"Medical equipment",
|
||
"Special handling required",
|
||
"Fragile items"
|
||
]
|
||
notes = random.choice(notes_options)
|
||
|
||
transaction, _ = EBTTransaction.objects.get_or_create(
|
||
transaction_id=uuid.uuid4(),
|
||
airline=airline,
|
||
flight_number=flight_number,
|
||
passenger_name=passenger_name,
|
||
ticket_number=ticket_number,
|
||
emd_serial=emd_serial,
|
||
weight_kg=weight_kg,
|
||
rate_per_kg=rate_per_kg,
|
||
total_amount=total_amount,
|
||
payment_method=payment_method,
|
||
pos_device=pos_device,
|
||
pos_reference=pos_reference,
|
||
cashier=shift.cashier,
|
||
cash_shift=shift,
|
||
status=status,
|
||
transaction_date=transaction_date,
|
||
refund_date=refund_date,
|
||
refund_type=refund_type,
|
||
refund_method=refund_method,
|
||
refunded_by=refunded_by,
|
||
notes=notes
|
||
)
|
||
transactions.append(transaction)
|
||
|
||
return transactions
|
||
|
||
|
||
def create_daily_reconciliations(airports, transactions):
|
||
"""Create daily reconciliation records"""
|
||
reconciliations = []
|
||
|
||
# Get unique dates from transactions
|
||
transaction_dates = set()
|
||
for transaction in transactions:
|
||
# Extract just the date part
|
||
transaction_date = transaction.transaction_date.date()
|
||
transaction_dates.add(transaction_date)
|
||
|
||
# Get admin users who might reconcile
|
||
from django.contrib.auth.models import User
|
||
admin_users = User.objects.filter(is_staff=True)
|
||
admin_user = admin_users.first() if admin_users.exists() else None
|
||
|
||
# For each airport and each date, create a reconciliation
|
||
for airport in airports:
|
||
for date in transaction_dates:
|
||
# Only create reconciliations for some dates
|
||
if random.random() > 0.7:
|
||
continue
|
||
|
||
# Get transactions for this airport and date
|
||
day_transactions = [
|
||
t for t in transactions
|
||
if t.cashier.airport == airport and t.transaction_date.date() == date
|
||
]
|
||
|
||
if not day_transactions:
|
||
continue
|
||
|
||
# Calculate reconciliation data
|
||
total_transactions = len([t for t in day_transactions if t.status == 'completed'])
|
||
|
||
# Calculate totals
|
||
total_sales = sum(t.total_amount for t in day_transactions if t.status == 'completed')
|
||
cash_sales = sum(
|
||
t.total_amount for t in day_transactions
|
||
if t.status == 'completed' and t.payment_method == 'cash'
|
||
)
|
||
pos_sales = sum(
|
||
t.total_amount for t in day_transactions
|
||
if t.status == 'completed' and t.payment_method != 'cash'
|
||
)
|
||
|
||
# Bank deposits (might have slight variances)
|
||
cash_variance_factor = random.uniform(0.98, 1.02) # +/- 2%
|
||
pos_variance_factor = random.uniform(0.99, 1.01) # +/- 1%
|
||
|
||
cash_deposited = cash_sales * Decimal(str(cash_variance_factor))
|
||
pos_settled = pos_sales * Decimal(str(pos_variance_factor))
|
||
|
||
# Calculate variances
|
||
cash_variance = cash_deposited - cash_sales
|
||
pos_variance = pos_settled - pos_sales
|
||
|
||
# Determine status based on variance
|
||
if abs(cash_variance) > 100 or abs(pos_variance) > 50:
|
||
status = 'discrepancy'
|
||
else:
|
||
status = 'completed'
|
||
|
||
# Create reconciliation record
|
||
reconciliation, _ = DailyReconciliation.objects.get_or_create(
|
||
date=date,
|
||
airport=airport,
|
||
total_transactions=total_transactions,
|
||
total_sales=total_sales,
|
||
cash_sales=cash_sales,
|
||
pos_sales=pos_sales,
|
||
cash_deposited=cash_deposited,
|
||
pos_settled=pos_settled,
|
||
cash_variance=cash_variance,
|
||
pos_variance=pos_variance,
|
||
status=status,
|
||
reconciled_by=admin_user,
|
||
reconciled_at=timezone.now() if status != 'pending' else None,
|
||
notes=f"Automatic reconciliation for {date.strftime('%Y-%m-%d')}" if status != 'pending' else ""
|
||
)
|
||
|
||
reconciliations.append(reconciliation)
|
||
|
||
return reconciliations
|
||
|
||
|
||
def main():
|
||
"""Main function to generate all data"""
|
||
# Create users
|
||
print("Creating users...")
|
||
users = create_users()
|
||
|
||
# Create airlines
|
||
print("Creating airlines...")
|
||
airlines = create_airlines()
|
||
|
||
# Create airports
|
||
print("Creating airports...")
|
||
airports = create_airports()
|
||
|
||
# Create POS devices
|
||
print("Creating POS devices...")
|
||
devices = create_pos_devices(airports)
|
||
|
||
# Create ticket inventory
|
||
print("Creating ticket inventory...")
|
||
inventory = create_ticket_inventory(airlines, airports)
|
||
|
||
# Create cashiers
|
||
print("Creating cashiers...")
|
||
cashiers = create_cashiers(users, airports)
|
||
|
||
# Create cash shifts
|
||
print("Creating cash shifts...")
|
||
shifts = create_cash_shifts(cashiers)
|
||
|
||
# Create flights
|
||
print("Creating flights...")
|
||
flights = create_flights(airlines, airports)
|
||
|
||
# Create passengers
|
||
print("Creating passengers...")
|
||
passengers = create_passengers(flights, users)
|
||
|
||
# Create EBT transactions
|
||
print("Creating EBT transactions...")
|
||
transactions = create_ebt_transactions(airlines, shifts, devices)
|
||
|
||
# Create daily reconciliations
|
||
print("Creating daily reconciliations...")
|
||
reconciliations = create_daily_reconciliations(airports, transactions)
|
||
|
||
# Update cash shift totals based on transactions
|
||
print("Updating cash shift totals...")
|
||
for shift in CashShift.objects.all():
|
||
shift.update_sales_totals()
|
||
|
||
print("Data generation complete!")
|
||
print(f"Created {len(users)} users")
|
||
print(f"Created {len(airlines)} airlines")
|
||
print(f"Created {len(airports)} airports")
|
||
print(f"Created {len(devices)} POS devices")
|
||
print(f"Created {len(inventory)} ticket inventory records")
|
||
print(f"Created {len(cashiers)} cashiers")
|
||
print(f"Created {len(shifts)} cash shifts")
|
||
print(f"Created {len(flights)} flights")
|
||
print(f"Created {len(passengers)} passengers")
|
||
print(f"Created {len(transactions)} transactions")
|
||
print(f"Created {len(reconciliations)} reconciliations")
|
||
|
||
|
||
# If running as a script
|
||
if __name__ == "__main__":
|
||
|
||
main() |