Save user flights
This commit is contained in:
@@ -5,38 +5,17 @@ namespace App\Http\Controllers;
|
||||
use App\Models\Aircraft;
|
||||
use App\Models\Airline;
|
||||
use App\Models\Airport;
|
||||
use App\Models\FlightClass;
|
||||
use App\Models\FlightReason;
|
||||
use App\Models\ImportedFlight;
|
||||
use App\Models\SeatType;
|
||||
use App\Models\UserFlight;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class FlightImportController extends Controller
|
||||
{
|
||||
|
||||
const array FLIGHT_CLASSES = [
|
||||
0 => 'Please Select',
|
||||
1 => 'Economy',
|
||||
2 => 'Business',
|
||||
3 => 'First',
|
||||
4 => 'Premium Economy',
|
||||
5 => 'Private',
|
||||
];
|
||||
|
||||
const array SEAT_TYPES = [
|
||||
0 => 'Please Select',
|
||||
1 => 'Window',
|
||||
2 => 'Middle',
|
||||
3 => 'Aisle',
|
||||
];
|
||||
|
||||
const array FLIGHT_REASONS = [
|
||||
0 => 'Please Select',
|
||||
1 => 'Pleasure',
|
||||
2 => 'Business',
|
||||
3 => 'Crew',
|
||||
4 => 'Other',
|
||||
];
|
||||
|
||||
private function formatTime(?string $time): string
|
||||
{
|
||||
if (!$time) return '00:00';
|
||||
@@ -45,6 +24,15 @@ class FlightImportController extends Controller
|
||||
return str_pad($hours, 2, '0', STR_PAD_LEFT) . ':' . $minutes;
|
||||
}
|
||||
|
||||
private function selectOptions($model): array
|
||||
{
|
||||
return $model::orderBy('id')
|
||||
->get(['id', 'name'])
|
||||
->map(fn($item) => ['value' => $item->id, 'title' => $item->name])
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function getPossibleAircraft(string $aircraftQuery) {
|
||||
preg_match('/\((\w+)\)/', $aircraftQuery, $matches);
|
||||
$designator = $matches[1] ?? null;
|
||||
@@ -153,19 +141,10 @@ class FlightImportController extends Controller
|
||||
|
||||
|
||||
return [
|
||||
'flight_classes' => collect(self::FLIGHT_CLASSES)->map(fn($title, $value) => [
|
||||
'value' => $value,
|
||||
'title' => $title,
|
||||
])->values(),
|
||||
'flight_reasons' => collect(self::FLIGHT_REASONS)->map(fn($title, $value) => [
|
||||
'value' => $value,
|
||||
'title' => $title,
|
||||
])->values(),
|
||||
'seat_types' => collect(self::SEAT_TYPES)->map(fn($title, $value) => [
|
||||
'value' => $value,
|
||||
'title' => $title,
|
||||
])->values(),
|
||||
|
||||
'imported_flight_id' => $flightToReconcile->id,
|
||||
'flight_classes' => $this->selectOptions(FlightClass::class),
|
||||
'flight_reasons' => $this->selectOptions(FlightReason::class),
|
||||
'seat_types' => $this->selectOptions(SeatType::class),
|
||||
'flight_number' => $flightToReconcile->flight_number ?? '',
|
||||
'date' => $date ?? '',
|
||||
'dep_time' => $this->formatTime($flightToReconcile->dep_time),
|
||||
@@ -184,9 +163,11 @@ class FlightImportController extends Controller
|
||||
];
|
||||
}
|
||||
|
||||
public function save(Request $request){
|
||||
public function save(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'date' => 'required|date',
|
||||
'imported_flight_id' => 'required|exists:imported_flights,id',
|
||||
'flight_number' => 'required|string',
|
||||
'from_id' => 'required|integer|exists:airports,id',
|
||||
'to_id' => 'required|integer|exists:airports,id',
|
||||
@@ -197,11 +178,12 @@ class FlightImportController extends Controller
|
||||
'aircraft_id' => 'nullable|integer|exists:aircraft,id',
|
||||
'registration' => 'nullable|string',
|
||||
'seat_number' => 'nullable|string',
|
||||
'seat_type_id' => 'nullable|integer',
|
||||
'flight_class_id' => 'nullable|integer',
|
||||
'flight_reason_id' => 'nullable|integer',
|
||||
'seat_type_id' => 'nullable|integer|exists:seat_types,id',
|
||||
'flight_class_id' => 'nullable|integer|exists:flight_classes,id',
|
||||
'flight_reason_id' => 'nullable|integer|exists:flight_reasons,id',
|
||||
'note' => 'nullable|string',
|
||||
],[
|
||||
], [
|
||||
'imported_flight_id.required' => 'The flight you are trying to reconcile needs to be reimported or refreshed.',
|
||||
'from_id.required' => 'Please select a departure airport.',
|
||||
'to_id.required' => 'Please select an arrival airport.',
|
||||
'dep_time.date_format' => 'Departure time must be in HH:MM format, i.e: 01:25',
|
||||
@@ -210,6 +192,61 @@ class FlightImportController extends Controller
|
||||
'duration.required' => 'A duration is required to be able to accurately calculate the arrival date',
|
||||
]);
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
$departureAirport = Airport::find($validated['from_id']);
|
||||
$arrivalAirport = Airport::find($validated['to_id']);
|
||||
|
||||
// Parse departure in local airport timezone, then convert to UTC
|
||||
$depTime = $validated['dep_time'] ?? '00:00';
|
||||
$departure = Carbon::createFromFormat(
|
||||
'Y-m-d H:i',
|
||||
$validated['date'] . ' ' . $depTime,
|
||||
$departureAirport->timezone
|
||||
)->utc();
|
||||
|
||||
// Calculate duration-based arrival in UTC
|
||||
[$durationHours, $durationMinutes] = explode(':', $validated['duration']);
|
||||
$durationArrival = $departure->copy()
|
||||
->addHours((int) $durationHours)
|
||||
->addMinutes((int) $durationMinutes);
|
||||
|
||||
// If arrival time provided, parse it in arrival airport timezone and convert to UTC
|
||||
if (!empty($validated['arr_time'])) {
|
||||
$arrival = Carbon::createFromFormat(
|
||||
'Y-m-d H:i',
|
||||
$validated['date'] . ' ' . $validated['arr_time'],
|
||||
$arrivalAirport->timezone
|
||||
)->utc();
|
||||
|
||||
// If arrival is not after departure, fall back to duration-based arrival
|
||||
if ($arrival->lte($departure)) {
|
||||
$arrival = $durationArrival;
|
||||
}
|
||||
} else {
|
||||
$arrival = $durationArrival;
|
||||
}
|
||||
|
||||
UserFlight::create([
|
||||
'user_id' => $user->id,
|
||||
'departure_date' => $departure,
|
||||
'arrival_date' => $arrival,
|
||||
'flight_number' => $validated['flight_number'],
|
||||
'departure_airport_id' => $validated['from_id'],
|
||||
'arrival_airport_id' => $validated['to_id'],
|
||||
'airline_id' => $validated['airline_id'] ?? null,
|
||||
'aircraft_id' => $validated['aircraft_id'] ?? null,
|
||||
'aircraft_registration' => $validated['registration'] ?? null,
|
||||
'seat_number' => $validated['seat_number'] ?? null,
|
||||
'seat_type_id' => $validated['seat_type_id'] ?? null,
|
||||
'flight_class_id' => $validated['flight_class_id'] ?? null,
|
||||
'flight_reason_id' => $validated['flight_reason_id'] ?? null,
|
||||
'note' => $validated['note'] ?? null,
|
||||
]);
|
||||
|
||||
ImportedFlight::destroy($validated['imported_flight_id']);
|
||||
|
||||
return redirect()->route('reconcile');
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class FlightClass extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class FlightReason extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SeatType extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class UserFlight extends Model
|
||||
{
|
||||
protected $table = 'user_flights';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'departure_date',
|
||||
'arrival_date',
|
||||
'flight_number',
|
||||
'departure_airport_id',
|
||||
'arrival_airport_id',
|
||||
'airline_id',
|
||||
'aircraft_id',
|
||||
'aircraft_registration',
|
||||
'seat_number',
|
||||
'seat_type_id',
|
||||
'flight_class_id',
|
||||
'flight_reason_id',
|
||||
'note',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'departure_date' => 'immutable_datetime',
|
||||
'arrival_date' => 'immutable_datetime',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function departureAirport(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Airport::class, 'departure_airport_id');
|
||||
}
|
||||
|
||||
public function arrivalAirport(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Airport::class, 'arrival_airport_id');
|
||||
}
|
||||
|
||||
public function airline(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Airline::class);
|
||||
}
|
||||
|
||||
public function aircraft(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Aircraft::class);
|
||||
}
|
||||
|
||||
public function seatType(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(SeatType::class);
|
||||
}
|
||||
|
||||
public function flightClass(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(FlightClass::class);
|
||||
}
|
||||
|
||||
public function flightReason(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(FlightReason::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('flight_classes', function (Blueprint $table) {
|
||||
$table->unsignedTinyInteger('id')->primary();
|
||||
$table->string('name');
|
||||
});
|
||||
|
||||
Schema::create('seat_types', function (Blueprint $table) {
|
||||
$table->unsignedTinyInteger('id')->primary();
|
||||
$table->string('name');
|
||||
});
|
||||
|
||||
Schema::create('flight_reasons', function (Blueprint $table) {
|
||||
$table->unsignedTinyInteger('id')->primary();
|
||||
$table->string('name');
|
||||
});
|
||||
|
||||
DB::table('flight_classes')->insert([
|
||||
['id' => 0, 'name' => 'Unspecified'],
|
||||
['id' => 1, 'name' => 'Economy'],
|
||||
['id' => 2, 'name' => 'Business'],
|
||||
['id' => 3, 'name' => 'First'],
|
||||
['id' => 4, 'name' => 'Premium Economy'],
|
||||
['id' => 5, 'name' => 'Private'],
|
||||
]);
|
||||
|
||||
DB::table('seat_types')->insert([
|
||||
['id' => 0, 'name' => 'Unspecified'],
|
||||
['id' => 1, 'name' => 'Window'],
|
||||
['id' => 2, 'name' => 'Middle'],
|
||||
['id' => 3, 'name' => 'Aisle'],
|
||||
]);
|
||||
|
||||
DB::table('flight_reasons')->insert([
|
||||
['id' => 0, 'name' => 'No Particular Reason'],
|
||||
['id' => 1, 'name' => 'Pleasure'],
|
||||
['id' => 2, 'name' => 'Business'],
|
||||
['id' => 3, 'name' => 'Crew'],
|
||||
['id' => 4, 'name' => 'Other'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('flight_reasons');
|
||||
Schema::dropIfExists('seat_types');
|
||||
Schema::dropIfExists('flight_classes');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('user_flights', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->timestampTz('departure_date');
|
||||
$table->timestampTz('arrival_date');
|
||||
$table->string('flight_number')->nullable();
|
||||
$table->foreignId('departure_airport_id')->constrained('airports');
|
||||
$table->foreignId('arrival_airport_id')->constrained('airports');
|
||||
$table->foreignId('airline_id')->nullable()->constrained('airlines');
|
||||
$table->foreignId('aircraft_id')->nullable()->constrained('aircraft');
|
||||
$table->string('aircraft_registration')->nullable();
|
||||
$table->string('seat_number')->nullable();
|
||||
$table->unsignedTinyInteger('seat_type_id')->nullable();
|
||||
$table->unsignedTinyInteger('flight_reason_id')->nullable();
|
||||
$table->unsignedTinyInteger('flight_class_id')->nullable();
|
||||
$table->text('note')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('seat_type_id')->references('id')->on('seat_types');
|
||||
$table->foreign('flight_reason_id')->references('id')->on('flight_reasons');
|
||||
$table->foreign('flight_class_id')->references('id')->on('flight_classes');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('user_flights');
|
||||
}
|
||||
};
|
||||
@@ -11,6 +11,7 @@ defineOptions({ layout: MainLayout });
|
||||
|
||||
const props = defineProps<{
|
||||
flight: {
|
||||
imported_flight_id: number,
|
||||
flight_reasons: { value: number, title: string }[]
|
||||
flight_classes:{ value: number, title: string }[]
|
||||
seat_types: { value: number, title: string }[]
|
||||
@@ -34,6 +35,7 @@ const props = defineProps<{
|
||||
const flight = props.flight;
|
||||
|
||||
const form = useForm({
|
||||
imported_flight_id: flight.imported_flight_id,
|
||||
date: flight.date,
|
||||
flight_number: flight.flight_number,
|
||||
from: flight.from_options[0] ?? null,
|
||||
@@ -53,6 +55,7 @@ const form = useForm({
|
||||
});
|
||||
|
||||
const submitForm = useForm({
|
||||
imported_flight_id: flight.imported_flight_id,
|
||||
date: '' as string | null,
|
||||
flight_number: '' as string | null,
|
||||
from_id: null as number | null,
|
||||
@@ -71,6 +74,7 @@ const submitForm = useForm({
|
||||
});
|
||||
|
||||
function submit() {
|
||||
submitForm.imported_flight_id = form.imported_flight_id;
|
||||
submitForm.date = form.date;
|
||||
submitForm.flight_number = form.flight_number;
|
||||
submitForm.from_id = form.from?.value ?? null;
|
||||
@@ -139,10 +143,10 @@ function submit() {
|
||||
<!-- Dep time / Arr time / Duration -->
|
||||
<v-row>
|
||||
<v-col cols="12" md="4">
|
||||
<v-text-field v-model="form.dep_time" type="time" label="Departure Time" placeholder="09:30" :error-messages="submitForm.errors.dep_time" />
|
||||
<v-text-field clearable v-model="form.dep_time" type="time" label="Departure Time" placeholder="09:30" :error-messages="submitForm.errors.dep_time" />
|
||||
</v-col>
|
||||
<v-col cols="12" md="4">
|
||||
<v-text-field v-model="form.arr_time" type="time" label="Arrival Time" placeholder="11:45" :error-messages="submitForm.errors.arr_time" />
|
||||
<v-text-field clearable v-model="form.arr_time" type="time" label="Arrival Time" placeholder="11:45" :error-messages="submitForm.errors.arr_time" />
|
||||
</v-col>
|
||||
<v-col cols="12" md="4">
|
||||
<v-text-field v-model="form.duration" label="Duration" placeholder="1:23" :error-messages="submitForm.errors.duration" />
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ Route::domain(config('app.domain'))->group(
|
||||
$flight = new FlightImportController()->reconcile(request());
|
||||
|
||||
if (!$flight) {
|
||||
return redirect('/');
|
||||
return redirect('/import/fr24');
|
||||
}
|
||||
|
||||
return Inertia::render('ReconcileFlight', [
|
||||
|
||||
Reference in New Issue
Block a user