|
#!/usr/bin/env python3 |
|
# -*- coding: utf-8 -*- |
|
""" |
|
Rent A Car Database - Complete Setup |
|
Tek dosya çalıştırma ile tüm yapıyı oluşturur |
|
""" |
|
|
|
import os |
|
import random |
|
from datetime import datetime, timedelta |
|
from faker import Faker |
|
|
|
# DETERMINISTIC SEED - Sabit kalacak |
|
RANDOM_SEED = 42 |
|
random.seed(RANDOM_SEED) |
|
Faker.seed(RANDOM_SEED) |
|
fake = Faker(['en_US', 'de_DE', 'tr_TR', 'fr_FR', 'es_ES', 'it_IT', 'nl_NL']) |
|
|
|
# Yapılandırma |
|
CONFIG = { |
|
'total_customers': 350, |
|
'total_vehicles': 3000, |
|
'total_branches': 300, |
|
'output_dir': 'rentacar-db' |
|
} |
|
|
|
# Veri setleri |
|
EUROPEAN_COUNTRIES = [ |
|
('DE', 'Germany', '+49', 'EUR', 0.30), ('FR', 'France', '+33', 'EUR', 0.20), |
|
('IT', 'Italy', '+39', 'EUR', 0.15), ('ES', 'Spain', '+34', 'EUR', 0.15), |
|
('NL', 'Netherlands', '+31', 'EUR', 0.10), ('TR', 'Turkey', '+90', 'TRY', 0.05), |
|
('BE', 'Belgium', '+32', 'EUR', 0.02), ('AT', 'Austria', '+43', 'EUR', 0.02), |
|
('CH', 'Switzerland', '+41', 'CHF', 0.01) |
|
] |
|
|
|
CITIES_BY_COUNTRY = { |
|
'DE': ['Berlin', 'Munich', 'Hamburg', 'Frankfurt', 'Cologne', 'Stuttgart', 'Düsseldorf'], |
|
'FR': ['Paris', 'Lyon', 'Marseille', 'Toulouse', 'Nice', 'Bordeaux', 'Strasbourg'], |
|
'IT': ['Rome', 'Milan', 'Naples', 'Turin', 'Palermo', 'Florence', 'Bologna'], |
|
'ES': ['Madrid', 'Barcelona', 'Valencia', 'Seville', 'Zaragoza', 'Málaga'], |
|
'NL': ['Amsterdam', 'Rotterdam', 'The Hague', 'Utrecht', 'Eindhoven'], |
|
'TR': ['Istanbul', 'Ankara', 'Izmir', 'Antalya', 'Bursa', 'Adana', 'Gaziantep'], |
|
'BE': ['Brussels', 'Antwerp', 'Ghent', 'Charleroi'], |
|
'AT': ['Vienna', 'Graz', 'Linz', 'Salzburg'], |
|
'CH': ['Zurich', 'Geneva', 'Basel', 'Bern'] |
|
} |
|
|
|
BRANCH_PREFIXES = ['Central', 'Airport', 'Downtown', 'West', 'East', 'North', 'South', 'Main'] |
|
VEHICLE_BRANDS = [('Toyota', 'JP'), ('Volkswagen', 'DE'), ('BMW', 'DE'), ('Mercedes-Benz', 'DE'), ('Audi', 'DE'), ('Ford', 'US'), ('Peugeot', 'FR'), ('Renault', 'FR'), ('Fiat', 'IT'), ('Volvo', 'SE'), ('Skoda', 'CZ'), ('Seat', 'ES'), ('Hyundai', 'KR'), ('Kia', 'KR'), ('Tesla', 'US'), ('Nissan', 'JP'), ('Honda', 'JP'), ('Citroen', 'FR'), ('Opel', 'DE'), ('Dacia', 'RO')] |
|
|
|
MODELS = { |
|
'SUV': [('Tiguan', 'VW', 45, 65), ('Q5', 'Audi', 80, 120), ('X5', 'BMW', 90, 140), ('GLC', 'Mercedes', 85, 130), ('RAV4', 'Toyota', 50, 75), ('Sportage', 'Kia', 40, 60), ('Tucson', 'Hyundai', 42, 62), ('Kuga', 'Ford', 40, 58), ('Captur', 'Renault', 35, 50), ('Duster', 'Dacia', 30, 45), ('XC60', 'Volvo', 70, 100), ('Model Y', 'Tesla', 75, 110)], |
|
'Sedan': [('Passat', 'VW', 40, 60), ('A4', 'Audi', 60, 90), ('3 Series', 'BMW', 55, 85), ('C-Class', 'Mercedes', 60, 95), ('Camry', 'Toyota', 45, 70), ('Model 3', 'Tesla', 65, 95), ('Octavia', 'Skoda', 35, 50), ('508', 'Peugeot', 40, 60), ('Taliant', 'Renault', 30, 45), ('Egea', 'Fiat', 28, 42)], |
|
'Hatchback': [('Golf', 'VW', 35, 50), ('A3', 'Audi', 50, 75), ('1 Series', 'BMW', 45, 68), ('A-Class', 'Mercedes', 48, 72), ('Corolla', 'Toyota', 32, 48), ('Clio', 'Renault', 25, 38), ('208', 'Peugeot', 26, 40), ('i20', 'Hyundai', 24, 36), ('Fabia', 'Skoda', 22, 34)] |
|
} |
|
|
|
COLORS = ['White', 'Black', 'Silver', 'Gray', 'Blue', 'Red', 'Green', 'Yellow', 'Brown', 'Orange'] |
|
TRANSMISSIONS = ['Automatic', 'Manual'] |
|
|
|
def create_directories(): |
|
base = CONFIG['output_dir'] |
|
dirs = [base, f"{base}/init", f"{base}/seed_data"] |
|
for d in dirs: |
|
os.makedirs(d, exist_ok=True) |
|
print(f"📁 Created: {d}") |
|
|
|
def write_file(path, content): |
|
with open(os.path.join(CONFIG['output_dir'], path), 'w', encoding='utf-8') as f: |
|
f.write(content) |
|
print(f"📝 Created: {path}") |
|
|
|
def generate_countries(): |
|
return [(i+1, code, name, prefix, currency) for i, (code, name, prefix, currency, _) in enumerate(EUROPEAN_COUNTRIES)] |
|
|
|
def generate_cities(): |
|
cities = [] |
|
city_id = 1 |
|
for country_id, (code, _, _, _, _) in enumerate(EUROPEAN_COUNTRIES, 1): |
|
for city_name in CITIES_BY_COUNTRY[code]: |
|
cities.append((city_id, country_id, city_name, 'Europe/' + code)) |
|
city_id += 1 |
|
return cities |
|
|
|
def escape_sql(val): |
|
return str(val).replace("'", "''") |
|
|
|
def main(): |
|
print("🚀 Rent A Car Database Setup Başlatılıyor...") |
|
create_directories() |
|
|
|
countries = generate_countries() |
|
cities = generate_cities() |
|
|
|
# 1. Schema |
|
schema = """-- Complete Database Schema |
|
CREATE TABLE IF NOT EXISTS countries (country_id SERIAL PRIMARY KEY, country_code CHAR(2) UNIQUE NOT NULL, country_name VARCHAR(100) NOT NULL, phone_prefix VARCHAR(5), currency_code CHAR(3)); |
|
CREATE TABLE IF NOT EXISTS cities (city_id SERIAL PRIMARY KEY, country_id INTEGER REFERENCES countries(country_id), city_name VARCHAR(100) NOT NULL, timezone VARCHAR(50)); |
|
CREATE TABLE IF NOT EXISTS branches (branch_id SERIAL PRIMARY KEY, branch_code VARCHAR(10) UNIQUE NOT NULL, branch_name VARCHAR(100) NOT NULL, city_id INTEGER REFERENCES cities(city_id), address TEXT, phone VARCHAR(20), email VARCHAR(100), opening_date DATE, is_active BOOLEAN DEFAULT true, capacity INTEGER); |
|
CREATE TABLE IF NOT EXISTS vehicle_categories (category_id SERIAL PRIMARY KEY, category_name VARCHAR(50) NOT NULL, description TEXT); |
|
CREATE TABLE IF NOT EXISTS fuel_types (fuel_type_id SERIAL PRIMARY KEY, fuel_type_name VARCHAR(50) NOT NULL, is_fossil BOOLEAN DEFAULT true); |
|
CREATE TABLE IF NOT EXISTS vehicle_brands (brand_id SERIAL PRIMARY KEY, brand_name VARCHAR(50) NOT NULL, country_id INTEGER REFERENCES countries(country_id)); |
|
CREATE TABLE IF NOT EXISTS vehicle_models (model_id SERIAL PRIMARY KEY, brand_id INTEGER REFERENCES vehicle_brands(brand_id), model_name VARCHAR(50) NOT NULL, category_id INTEGER REFERENCES vehicle_categories(category_id), fuel_type_id INTEGER REFERENCES fuel_types(fuel_type_id), seat_count INTEGER DEFAULT 5, transmission_type VARCHAR(20), daily_rate_min DECIMAL(10,2), daily_rate_max DECIMAL(10,2)); |
|
CREATE TABLE IF NOT EXISTS vehicles (vehicle_id SERIAL PRIMARY KEY, license_plate VARCHAR(20) UNIQUE NOT NULL, model_id INTEGER REFERENCES vehicle_models(model_id), branch_id INTEGER REFERENCES branches(branch_id), manufacture_year INTEGER, color VARCHAR(30), mileage INTEGER DEFAULT 0, status VARCHAR(20) DEFAULT 'available', purchase_date DATE, daily_rate DECIMAL(10,2) NOT NULL, last_maintenance_date DATE, next_maintenance_date DATE); |
|
CREATE TABLE IF NOT EXISTS customers (customer_id SERIAL PRIMARY KEY, first_name VARCHAR(50) NOT NULL, last_name VARCHAR(50) NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, phone VARCHAR(20), date_of_birth DATE, country_id INTEGER REFERENCES countries(country_id), city_id INTEGER REFERENCES cities(city_id), address TEXT, driver_license_number VARCHAR(50), license_issue_date DATE, license_class VARCHAR(10), registration_date DATE DEFAULT CURRENT_DATE, is_verified BOOLEAN DEFAULT false, credit_score INTEGER CHECK (credit_score BETWEEN 0 AND 1000)); |
|
CREATE TABLE IF NOT EXISTS rentals (rental_id SERIAL PRIMARY KEY, rental_code VARCHAR(20) UNIQUE NOT NULL, customer_id INTEGER REFERENCES customers(customer_id), vehicle_id INTEGER REFERENCES vehicles(vehicle_id), pickup_branch_id INTEGER REFERENCES branches(branch_id), return_branch_id INTEGER REFERENCES branches(branch_id), pickup_date TIMESTAMP NOT NULL, return_date TIMESTAMP NOT NULL, actual_return_date TIMESTAMP, status VARCHAR(20) DEFAULT 'confirmed', daily_rate DECIMAL(10,2) NOT NULL, total_days INTEGER GENERATED ALWAYS AS (EXTRACT(DAY FROM (return_date - pickup_date))) STORED, subtotal DECIMAL(10,2), discount_amount DECIMAL(10,2) DEFAULT 0, tax_amount DECIMAL(10,2), total_amount DECIMAL(10,2), mileage_start INTEGER, mileage_end INTEGER, fuel_level_start VARCHAR(20), fuel_level_end VARCHAR(20), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP); |
|
CREATE TABLE IF NOT EXISTS payments (payment_id SERIAL PRIMARY KEY, rental_id INTEGER REFERENCES rentals(rental_id), payment_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, amount DECIMAL(10,2) NOT NULL, payment_method VARCHAR(30), transaction_id VARCHAR(100), status VARCHAR(20) DEFAULT 'completed', currency_code CHAR(3) DEFAULT 'EUR'); |
|
CREATE TABLE IF NOT EXISTS insurance_policies (policy_id SERIAL PRIMARY KEY, policy_name VARCHAR(100), coverage_type VARCHAR(50), daily_cost DECIMAL(10,2), max_coverage_amount DECIMAL(12,2)); |
|
CREATE TABLE IF NOT EXISTS rental_insurances (rental_id INTEGER REFERENCES rentals(rental_id), policy_id INTEGER REFERENCES insurance_policies(policy_id), PRIMARY KEY (rental_id, policy_id)); |
|
CREATE INDEX idx_vehicles_branch ON vehicles(branch_id); CREATE INDEX idx_vehicles_status ON vehicles(status); CREATE INDEX idx_customers_country ON customers(country_id); CREATE INDEX idx_rentals_customer ON rentals(customer_id); CREATE INDEX idx_rentals_dates ON rentals(pickup_date, return_date);""" |
|
|
|
write_file("init/01-schema.sql", schema) |
|
|
|
# 2. Countries |
|
vals = [f"({c[0]}, '{c[1]}', '{c[2]}', '{c[3]}', '{c[4]}')" for c in countries] |
|
write_file("seed_data/01_countries.sql", "INSERT INTO countries (country_id, country_code, country_name, phone_prefix, currency_code) VALUES\n" + ",\n".join(vals) + ";\n") |
|
|
|
# 3. Cities |
|
vals = [f"({c[0]}, {c[1]}, '{c[2]}', '{c[3]}')" for c in cities] |
|
write_file("seed_data/02_cities.sql", "INSERT INTO cities (city_id, country_id, city_name, timezone) VALUES\n" + ",\n".join(vals) + ";\n") |
|
|
|
# 4. Branches (300) |
|
print("🏢 300 şube oluşturuluyor...") |
|
branches = [] |
|
used_names = set() |
|
for i in range(1, 301): |
|
city = random.choice(cities) |
|
prefix = random.choice(BRANCH_PREFIXES) |
|
name = f"{prefix} {city[2]}" |
|
counter = 1 |
|
while name in used_names: |
|
name = f"{prefix} {city[2]} {counter}" |
|
counter += 1 |
|
used_names.add(name) |
|
code = f"BR{str(i).zfill(4)}" |
|
address = escape_sql(fake.street_address()) |
|
phone = f"+{random.randint(1,99)} {random.randint(100000000, 999999999)}" |
|
email = f"{name.lower().replace(' ', '.')}@rentacar.com" |
|
opening = fake.date_between(start_date='-10y', end_date='-1y') |
|
capacity = random.randint(20, 100) |
|
branches.append(f"({i}, '{code}', '{escape_sql(name)}', {city[0]}, '{address}', '{phone}', '{email}', '{opening}', true, {capacity})") |
|
write_file("seed_data/03_branches.sql", "INSERT INTO branches (branch_id, branch_code, branch_name, city_id, address, phone, email, opening_date, is_active, capacity) VALUES\n" + ",\n".join(branches) + ";\n") |
|
|
|
# 5. Fuel Types & Categories |
|
write_file("seed_data/04_fuel_types.sql", "INSERT INTO fuel_types (fuel_type_id, fuel_type_name, is_fossil) VALUES (1, 'Gasoline', true), (2, 'Diesel', true), (3, 'Electric', false), (4, 'Hybrid', false);\n") |
|
write_file("seed_data/05_categories.sql", "INSERT INTO vehicle_categories (category_id, category_name) VALUES (1, 'SUV'), (2, 'Sedan'), (3, 'Hatchback');\n") |
|
|
|
# 6. Brands |
|
country_map = {code: i+1 for i, (code, _, _, _, _) in enumerate(EUROPEAN_COUNTRIES)} |
|
country_map.update({'JP': 11, 'US': 12, 'KR': 13, 'SE': 14, 'CZ': 15, 'RO': 16}) |
|
brands = [(i, name, country_map.get(cc, 1)) for i, (name, cc) in enumerate(VEHICLE_BRANDS, 1)] |
|
vals = [f"({b[0]}, '{b[1]}', {b[2]})" for b in brands] |
|
write_file("seed_data/06_brands.sql", "INSERT INTO vehicle_brands (brand_id, brand_name, country_id) VALUES\n" + ",\n".join(vals) + ";\n") |
|
|
|
# 7. Models |
|
print("🚗 Araç modelleri oluşturuluyor...") |
|
models = [] |
|
model_id = 1 |
|
brand_map = {name: i for i, (name, _) in enumerate(VEHICLE_BRANDS, 1)} |
|
cat_map = {'SUV': 1, 'Sedan': 2, 'Hatchback': 3} |
|
for cat, mlist in MODELS.items(): |
|
for mname, bname, minr, maxr in mlist: |
|
bid = brand_map.get(bname, 1) |
|
cid = cat_map[cat] |
|
fid = 3 if 'Tesla' in bname else (1 if random.random() < 0.6 else 2) |
|
seats = 5 if cat != 'SUV' else random.choice([5, 7]) |
|
trans = random.choice(TRANSMISSIONS) |
|
models.append(f"({model_id}, {bid}, '{mname}', {cid}, {fid}, {seats}, '{trans}', {minr}, {maxr})") |
|
model_id += 1 |
|
write_file("seed_data/07_models.sql", "INSERT INTO vehicle_models (model_id, brand_id, model_name, category_id, fuel_type_id, seat_count, transmission_type, daily_rate_min, daily_rate_max) VALUES\n" + ",\n".join(models) + ";\n") |
|
|
|
# 8. Vehicles (3000) - Batch olarak |
|
print(f"🚙 {CONFIG['total_vehicles']} araç oluşturuluyor...") |
|
model_list = list(range(1, model_id)) |
|
used_plates = set() |
|
batch_size = 500 |
|
|
|
for batch in range(0, CONFIG['total_vehicles'], batch_size): |
|
vehicles = [] |
|
for i in range(batch + 1, min(batch + batch_size, CONFIG['total_vehicles']) + 1): |
|
mid = random.choice(model_list) |
|
bid = random.randint(1, 300) |
|
plate = f"{random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ')}{random.randint(10000, 99999)}" |
|
while plate in used_plates: |
|
plate = f"{random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ')}{random.randint(10000, 99999)}" |
|
used_plates.add(plate) |
|
year = random.randint(2019, 2024) |
|
color = random.choice(COLORS) |
|
mile = random.randint(0, 150000) |
|
status = random.choices(['available', 'rented', 'maintenance', 'retired'], weights=[70, 20, 8, 2])[0] |
|
purch = fake.date_between(start_date=f'-{2024-year+1}y', end_date=f'-{2024-year}y') |
|
rate = round(random.uniform(20, 150), 2) |
|
last = fake.date_between(start_date='-1y', end_date='today') |
|
next_m = last + timedelta(days=180) |
|
vehicles.append(f"({i}, '{plate}', {mid}, {bid}, {year}, '{color}', {mile}, '{status}', '{purch}', {rate}, '{last}', '{next_m}')") |
|
write_file(f"seed_data/08_vehicles_{batch//batch_size + 1}.sql", f"INSERT INTO vehicles (vehicle_id, license_plate, model_id, branch_id, manufacture_year, color, mileage, status, purchase_date, daily_rate, last_maintenance_date, next_maintenance_date) VALUES\n" + ",\n".join(vehicles) + ";\n") |
|
|
|
# 9. Customers (350) |
|
print(f"👥 {CONFIG['total_customers']} müşteri oluşturuluyor...") |
|
tr_cities = [c for c in cities if c[1] == 6] |
|
other_cities = [c for c in cities if c[1] != 6] |
|
customers = [] |
|
|
|
for i in range(1, CONFIG['total_customers'] + 1): |
|
city = random.choice(tr_cities) if random.random() < 0.15 else random.choice(other_cities) |
|
fname = escape_sql(fake.first_name()) |
|
lname = escape_sql(fake.last_name()) |
|
email = f"{fname.lower()}.{lname.lower()}{random.randint(1,999)}@example.com" |
|
phone = f"+{random.randint(1,99)}-{random.randint(100000000, 999999999)}" |
|
dob = fake.date_of_birth(minimum_age=21, maximum_age=70) |
|
addr = escape_sql(fake.street_address()) |
|
lic = f"DL{random.randint(10000000, 99999999)}" |
|
lic_date = fake.date_between(start_date=dob + timedelta(days=365*18), end_date='-1y') |
|
lic_class = random.choice(['B', 'B1', 'BE']) |
|
reg = fake.date_between(start_date='-5y', end_date='today') |
|
verified = random.random() < 0.9 |
|
score = random.randint(300, 850) if verified else random.randint(100, 500) |
|
customers.append(f"({i}, '{fname}', '{lname}', '{email}', '{phone}', '{dob}', {city[1]}, {city[0]}, '{addr}', '{lic}', '{lic_date}', '{lic_class}', '{reg}', {verified}, {score})") |
|
write_file("seed_data/09_customers.sql", "INSERT INTO customers (customer_id, first_name, last_name, email, phone, date_of_birth, country_id, city_id, address, driver_license_number, license_issue_date, license_class, registration_date, is_verified, credit_score) VALUES\n" + ",\n".join(customers) + ";\n") |
|
|
|
# 10. Loader |
|
loader = """\\i /seed_data/01_countries.sql |
|
\\i /seed_data/02_cities.sql |
|
\\i /seed_data/03_branches.sql |
|
\\i /seed_data/04_fuel_types.sql |
|
\\i /seed_data/05_categories.sql |
|
\\i /seed_data/06_brands.sql |
|
\\i /seed_data/07_models.sql |
|
\\i /seed_data/08_vehicles_1.sql |
|
\\i /seed_data/08_vehicles_2.sql |
|
\\i /seed_data/08_vehicles_3.sql |
|
\\i /seed_data/08_vehicles_4.sql |
|
\\i /seed_data/08_vehicles_5.sql |
|
\\i /seed_data/08_vehicles_6.sql |
|
\\i /seed_data/09_customers.sql |
|
INSERT INTO insurance_policies (policy_id, policy_name, coverage_type, daily_cost, max_coverage_amount) VALUES (1, 'Basic Coverage', 'basic', 15.00, 50000.00), (2, 'Full Protection', 'full', 35.00, 150000.00), (3, 'Premium Plus', 'premium', 55.00, 300000.00);""" |
|
write_file("init/02-seed-loader.sql", loader) |
|
|
|
# 11. Docker Compose |
|
docker_compose = """version: '3.8' |
|
services: |
|
postgres: |
|
image: postgres:15-alpine |
|
container_name: rentacar_db |
|
environment: |
|
POSTGRES_DB: rentacar |
|
POSTGRES_USER: admin |
|
POSTGRES_PASSWORD: RentAcar2024! |
|
PGDATA: /var/lib/postgresql/data/pgdata |
|
ports: |
|
- "5432:5432" |
|
volumes: |
|
- postgres_data:/var/lib/postgresql/data |
|
- ./init:/docker-entrypoint-initdb.d |
|
- ./seed_data:/seed_data |
|
networks: |
|
- rentacar_network |
|
healthcheck: |
|
test: ["CMD-SHELL", "pg_isready -U admin -d rentacar"] |
|
interval: 10s |
|
timeout: 5s |
|
retries: 5 |
|
pgadmin: |
|
image: dpage/pgadmin4:latest |
|
container_name: rentacar_pgadmin |
|
environment: |
|
PGADMIN_DEFAULT_EMAIL: admin@rentacar.com |
|
PGADMIN_DEFAULT_PASSWORD: admin123 |
|
PGADMIN_CONFIG_SERVER_MODE: 'False' |
|
ports: |
|
- "8080:80" |
|
volumes: |
|
- pgadmin_data:/var/lib/pgadmin |
|
networks: |
|
- rentacar_network |
|
depends_on: |
|
- postgres |
|
networks: |
|
rentacar_network: |
|
driver: bridge |
|
volumes: |
|
postgres_data: |
|
pgadmin_data:""" |
|
write_file("docker-compose.yml", docker_compose) |
|
|
|
# 12. how_to_use.md |
|
how_to_use = """# Rent A Car Veritabanı - Kullanım Kılavuzu |
|
|
|
## 🚀 Hızlı Başlangıç |
|
|
|
```bash |
|
# 1. Projeye git |
|
cd rentacar-db |
|
|
|
# 2. Docker ile başlat |
|
docker-compose up -d |
|
|
|
# 3. Veritabanının hazır olmasını bekle (10-15 saniye) |
|
docker exec rentacar_db pg_isready -U admin -d rentacar |
|
|
|
# 4. Bağlantı testi |
|
docker exec -it rentacar_db psql -U admin -d rentacar -c "SELECT COUNT(*) FROM vehicles;" |