From 5862b4061f2b7fc014ec0d85119651d371ef31da Mon Sep 17 00:00:00 2001 From: "B.Fatih KOZ" Date: Mon, 18 Nov 2024 20:53:09 +0300 Subject: [PATCH] SimBrief Airframes & Aircraft Weight Casting (#1892) * SimBrief Airframes * Update AirframeController.php * Settings and Manual Update * Convert Aircraft Weights to Pounds If the 'kg' is selected as weight unit. * Fix Fuel Casting * Update simbrief_form.blade.php * Display weights with local units * StyleFix CI Fixes :) * StyleCI Fix 2 :) * Update aircraft.csv for tests Provide some weights * Update ApiTest with AC Weights * Update ApiTest.php * Don't check the weights during tests (to test) * Update ImporterTest.php Add weight to importer * Fix for wrong airframe_id generation. Ref PilotID should be used. * Use ENUMS instead of strings For separating Internal / External airframes * Forgot one :) * Update Aircraft Resource * Update Aircraft.php --- app/Cron/Weekly/UpdateSimbriefData.php | 28 ++ ..._105923_create_simbrief_support_tables.php | 49 ++++ ..._16_122416_fetch_initial_simbrief_data.php | 16 ++ ..._11_17_114057_convert_aircraft_weights.php | 34 +++ app/Database/seeds/settings.yml | 14 + .../Controllers/Admin/AircraftController.php | 13 + .../Controllers/Admin/AirframeController.php | 174 ++++++++++++ .../Frontend/SimBriefController.php | 37 ++- app/Http/Requests/CreateAirframeRequest.php | 19 ++ app/Http/Requests/UpdateAirframeRequest.php | 19 ++ app/Http/Resources/Aircraft.php | 7 + app/Models/Aircraft.php | 29 +- app/Models/Casts/MassCast.php | 56 ++++ app/Models/Enums/AirframeSource.php | 16 ++ app/Models/SimBriefAircraft.php | 35 +++ app/Models/SimBriefAirframe.php | 41 +++ app/Models/SimBriefLayout.php | 28 ++ app/Providers/RouteServiceProvider.php | 4 + app/Repositories/AirframeRepository.php | 51 ++++ .../ImportExport/AircraftImporter.php | 19 +- app/Services/SimBriefService.php | 82 ++++++ config/phpvms.php | 10 + .../views/admin/aircraft/fields.blade.php | 39 ++- .../views/admin/airframes/create.blade.php | 11 + .../views/admin/airframes/edit.blade.php | 11 + .../views/admin/airframes/fields.blade.php | 25 ++ .../views/admin/airframes/index.blade.php | 25 ++ .../views/admin/airframes/show.blade.php | 17 ++ .../admin/airframes/show_fields.blade.php | 36 +++ .../views/admin/airframes/table.blade.php | 30 ++ resources/views/admin/menu.blade.php | 4 + .../default/flights/simbrief_form.blade.php | 258 +++++++++++++++--- tests/ApiTest.php | 14 +- tests/ImporterTest.php | 2 +- tests/data/aircraft-update.csv | 4 +- tests/data/aircraft.csv | 4 +- 36 files changed, 1182 insertions(+), 79 deletions(-) create mode 100644 app/Cron/Weekly/UpdateSimbriefData.php create mode 100644 app/Database/migrations/2024_11_16_105923_create_simbrief_support_tables.php create mode 100644 app/Database/migrations_data/2024_11_16_122416_fetch_initial_simbrief_data.php create mode 100644 app/Database/migrations_data/2024_11_17_114057_convert_aircraft_weights.php create mode 100644 app/Http/Controllers/Admin/AirframeController.php create mode 100644 app/Http/Requests/CreateAirframeRequest.php create mode 100644 app/Http/Requests/UpdateAirframeRequest.php create mode 100644 app/Models/Casts/MassCast.php create mode 100644 app/Models/Enums/AirframeSource.php create mode 100644 app/Models/SimBriefAircraft.php create mode 100644 app/Models/SimBriefAirframe.php create mode 100644 app/Models/SimBriefLayout.php create mode 100644 app/Repositories/AirframeRepository.php create mode 100644 resources/views/admin/airframes/create.blade.php create mode 100644 resources/views/admin/airframes/edit.blade.php create mode 100644 resources/views/admin/airframes/fields.blade.php create mode 100644 resources/views/admin/airframes/index.blade.php create mode 100644 resources/views/admin/airframes/show.blade.php create mode 100644 resources/views/admin/airframes/show_fields.blade.php create mode 100644 resources/views/admin/airframes/table.blade.php diff --git a/app/Cron/Weekly/UpdateSimbriefData.php b/app/Cron/Weekly/UpdateSimbriefData.php new file mode 100644 index 000000000..35997a1ec --- /dev/null +++ b/app/Cron/Weekly/UpdateSimbriefData.php @@ -0,0 +1,28 @@ +getAircraftAndAirframes(); + $SimBriefSVC->GetBriefingLayouts(); + } +} diff --git a/app/Database/migrations/2024_11_16_105923_create_simbrief_support_tables.php b/app/Database/migrations/2024_11_16_105923_create_simbrief_support_tables.php new file mode 100644 index 000000000..141805cc7 --- /dev/null +++ b/app/Database/migrations/2024_11_16_105923_create_simbrief_support_tables.php @@ -0,0 +1,49 @@ +increments('id'); + $table->string('icao'); + $table->string('name'); + $table->mediumText('details')->nullable(); + $table->timestamps(); + }); + } + + if (!Schema::hasTable('simbrief_airframes')) { + Schema::create('simbrief_airframes', function (Blueprint $table) { + $table->increments('id'); + $table->string('icao'); + $table->string('name'); + $table->string('airframe_id')->nullable(); + $table->unsignedTinyInteger('source')->nullable(); + $table->mediumText('details')->nullable(); + $table->mediumText('options')->nullable(); + $table->timestamps(); + }); + } + + if (!Schema::hasTable('simbrief_layouts')) { + Schema::create('simbrief_layouts', function (Blueprint $table) { + $table->string('id'); + $table->string('name'); + $table->string('name_long'); + $table->timestamps(); + }); + } + } + + public function down(): void + { + Schema::dropIfExists('simbrief_aircraft'); + Schema::dropIfExists('simbrief_airframes'); + Schema::dropIfExists('simbrief_layouts'); + } +}; diff --git a/app/Database/migrations_data/2024_11_16_122416_fetch_initial_simbrief_data.php b/app/Database/migrations_data/2024_11_16_122416_fetch_initial_simbrief_data.php new file mode 100644 index 000000000..a7b9b21da --- /dev/null +++ b/app/Database/migrations_data/2024_11_16_122416_fetch_initial_simbrief_data.php @@ -0,0 +1,16 @@ +getAircraftAndAirframes(); + $SimBriefSVC->GetBriefingLayouts(); + } + } +}; diff --git a/app/Database/migrations_data/2024_11_17_114057_convert_aircraft_weights.php b/app/Database/migrations_data/2024_11_17_114057_convert_aircraft_weights.php new file mode 100644 index 000000000..331082584 --- /dev/null +++ b/app/Database/migrations_data/2024_11_17_114057_convert_aircraft_weights.php @@ -0,0 +1,34 @@ +whereNotNull('dow')->orWhereNotNull('zfw')->orWhereNotNull('mtow')->orWhereNotNull('mlw')->orderBy('id')->get(); + Log::debug('Begin weight conversion for '.$aircraft->count().' aircraft records'); + foreach ($aircraft as $ac) { + Log::debug('Converting and Updating Weights for '.$ac->registration); + DB::table('aircraft')->where('id', $ac->id)->update([ + 'dow' => $this->PoundsConversion($ac->dow), + 'zfw' => $this->PoundsConversion($ac->zfw), + 'mtow' => $this->PoundsConversion($ac->mtow), + 'mlw' => $this->PoundsConversion($ac->mlw), + ]); + } + } + } + + public function PoundsConversion($value) + { + if ($value > 0) { + return round($value / 0.45359237, 2); + } + + return null; + } +}; diff --git a/app/Database/seeds/settings.yml b/app/Database/seeds/settings.yml index fbac5d46f..5a24e2dd7 100644 --- a/app/Database/seeds/settings.yml +++ b/app/Database/seeds/settings.yml @@ -340,6 +340,20 @@ options: '' type: boolean description: 'When enabled, an aircraft can only be used for one active SimBrief OFP and Flight/Pirep' +- key: simbrief.use_standard_weights + name: 'Use Only phpVMS Weights' + group: simbrief + value: false + options: '' + type: boolean + description: 'When enabled, only phpVMS Passenger and Baggage weights will be used (instead of Airframe definitions)' +- key: simbrief.use_custom_airframes + name: 'Use Only Custom Airframes' + group: simbrief + value: false + options: '' + type: boolean + description: 'When enabled, only phpVMS Airframes will be listed for flight planning (instead of combined list)' - key: pireps.duplicate_check_time name: 'PIREP duplicate time check' group: pireps diff --git a/app/Http/Controllers/Admin/AircraftController.php b/app/Http/Controllers/Admin/AircraftController.php index 5cb8208ad..431d258bd 100644 --- a/app/Http/Controllers/Admin/AircraftController.php +++ b/app/Http/Controllers/Admin/AircraftController.php @@ -17,6 +17,7 @@ use App\Services\FileService; use App\Services\FinanceService; use App\Services\ImportService; +use App\Support\Units\Mass; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\View\View; @@ -119,6 +120,12 @@ public function create(Request $request): View public function store(CreateAircraftRequest $request): RedirectResponse { $attrs = $request->all(); + // Set the correct mass units + $attrs['dow'] = (filled($attrs['dow']) && $attrs['dow'] > 0) ? Mass::make((float) $request->input('dow'), setting('units.weight')) : null; + $attrs['zfw'] = (filled($attrs['zfw']) && $attrs['zfw'] > 0) ? Mass::make((float) $request->input('zfw'), setting('units.weight')) : null; + $attrs['mtow'] = (filled($attrs['mtow']) && $attrs['mtow'] > 0) ? Mass::make((float) $request->input('mtow'), setting('units.weight')) : null; + $attrs['mlw'] = (filled($attrs['mlw']) && $attrs['mlw'] > 0) ? Mass::make((float) $request->input('mlw'), setting('units.weight')) : null; + $aircraft = $this->aircraftRepo->create($attrs); Flash::success('Aircraft saved successfully.'); @@ -204,6 +211,12 @@ public function update(int $id, UpdateAircraftRequest $request): RedirectRespons } $attrs = $request->all(); + // Set the correct mass units + $attrs['dow'] = (filled($attrs['dow']) && $attrs['dow'] > 0) ? Mass::make((float) $request->input('dow'), setting('units.weight')) : null; + $attrs['zfw'] = (filled($attrs['zfw']) && $attrs['zfw'] > 0) ? Mass::make((float) $request->input('zfw'), setting('units.weight')) : null; + $attrs['mtow'] = (filled($attrs['mtow']) && $attrs['mtow'] > 0) ? Mass::make((float) $request->input('mtow'), setting('units.weight')) : null; + $attrs['mlw'] = (filled($attrs['mlw']) && $attrs['mlw'] > 0) ? Mass::make((float) $request->input('mlw'), setting('units.weight')) : null; + $this->aircraftRepo->update($attrs, $id); Flash::success('Aircraft updated successfully.'); diff --git a/app/Http/Controllers/Admin/AirframeController.php b/app/Http/Controllers/Admin/AirframeController.php new file mode 100644 index 000000000..450c7c815 --- /dev/null +++ b/app/Http/Controllers/Admin/AirframeController.php @@ -0,0 +1,174 @@ +airframeRepo->pushCriteria(new RequestCriteria($request)); + $airframes = $this->airframeRepo->where('source', AirframeSource::INTERNAL)->orderby('icao', 'asc')->orderby('name', 'asc')->get(); + + return view('admin.airframes.index', [ + 'airframes' => $airframes, + ]); + } + + /** + * @return View + */ + public function create(): View + { + return view('admin.airframes.create', [ + 'icao_codes' => Aircraft::whereNotNull('icao')->groupBy('icao')->pluck('icao')->toArray(), + ]); + } + + /** + * @param CreateAirframeRequest $request + * + * @throws ValidatorException + * + * @return RedirectResponse + */ + public function store(CreateAirframeRequest $request): RedirectResponse + { + $input = $request->all(); + + $model = $this->airframeRepo->create($input); + Flash::success('Airframe saved successfully.'); + + return redirect(route('admin.airframes.index')); + } + + /** + * @param int $id + * + * @return RedirectResponse|View + */ + public function show(int $id): RedirectResponse|View + { + $airframe = $this->airframeRepo->findWithoutFail($id); + + if (empty($airframe)) { + Flash::error('SimBrief Airframe not found'); + + return redirect(route('admin.airframes.index')); + } + + return view('admin.airframes.show', [ + 'airframe' => $airframe, + ]); + } + + /** + * @param int $id + * + * @return RedirectResponse|View + */ + public function edit(int $id): RedirectResponse|View + { + $airframe = $this->airframeRepo->findWithoutFail($id); + + if (empty($airframe)) { + Flash::error('SimBrief Airframe not found'); + + return redirect(route('admin.airframes.index')); + } + + return view('admin.airframes.edit', [ + 'airframe' => $airframe, + 'icao_codes' => Aircraft::whereNotNull('icao')->groupBy('icao')->pluck('icao')->toArray(), + ]); + } + + /** + * @param int $id + * @param UpdateAirframeRequest $request + * + * @throws ValidatorException + * + * @return RedirectResponse + */ + public function update(int $id, UpdateAirframeRequest $request): RedirectResponse + { + $airframe = $this->airframeRepo->findWithoutFail($id); + + if (empty($airframe)) { + Flash::error('SimBrief Airframe not found'); + + return redirect(route('admin.airframes.index')); + } + + $airframe = $this->airframeRepo->update($request->all(), $id); + Flash::success('SimBrief Airport updated successfully.'); + + return redirect(route('admin.airframes.index')); + } + + /** + * @param int $id + * + * @return RedirectResponse + */ + public function destroy(int $id): RedirectResponse + { + $airframe = $this->airframeRepo->findWithoutFail($id); + + if (empty($airframe)) { + Flash::error('SimBrief Airframe not found'); + + return redirect(route('admin.airframes.index')); + } + + $this->airframeRepo->delete($id); + + Flash::success('SimBrief Airframe deleted successfully.'); + + return redirect(route('admin.airframes.index')); + } + + // Manually trigger update of SimBrief Airframe and Layouts + public function updateSimbriefData() + { + Log::debug('Manually Updating SimBrief Support Data'); + $SimBriefSVC = app(SimBriefService::class); + $SimBriefSVC->getAircraftAndAirframes(); + $SimBriefSVC->GetBriefingLayouts(); + + Flash::success('SimBrief Airframe and Layouts updated successfully.'); + + return redirect(route('admin.airframes.index')); + } +} diff --git a/app/Http/Controllers/Frontend/SimBriefController.php b/app/Http/Controllers/Frontend/SimBriefController.php index 4acbe488f..b4e64196b 100644 --- a/app/Http/Controllers/Frontend/SimBriefController.php +++ b/app/Http/Controllers/Frontend/SimBriefController.php @@ -7,11 +7,13 @@ use App\Models\Bid; use App\Models\Enums\AircraftState; use App\Models\Enums\AircraftStatus; +use App\Models\Enums\AirframeSource; use App\Models\Enums\FareType; use App\Models\Enums\FlightType; use App\Models\Fare; use App\Models\Flight; use App\Models\SimBrief; +use App\Models\SimBriefLayout; use App\Models\User; use App\Repositories\FlightRepository; use App\Services\FareService; @@ -102,7 +104,7 @@ public function generate(Request $request): RedirectResponse|View // Build proper aircraft collection considering all possible settings // Flight subfleets, user subfleet restrictions, pirep restrictions, simbrief blocking etc - $aircraft = Aircraft::withCount($withCount)->where($where) + $aircraft = Aircraft::withCount($withCount)->with(['sbaircraft', 'sbairframes'])->where($where) ->when(setting('simbrief.block_aircraft'), function ($query) { return $query->having('simbriefs_count', 0); })->whereIn('subfleet_id', $subfleet_ids) @@ -128,7 +130,7 @@ public function generate(Request $request): RedirectResponse|View // SimBrief profile does not exists and everything else is ok // Select aircraft which will be used for calculations and details /** @var Aircraft $aircraft */ - $aircraft = Aircraft::with(['airline'])->where('id', $aircraft_id)->first(); + $aircraft = Aircraft::with(['airline', 'sbaircraft', 'sbairframes'])->where('id', $aircraft_id)->first(); // Figure out the proper fares to use for this flight/aircraft $all_fares = $this->fareSvc->getFareWithOverrides($aircraft->subfleet->fares, $flight->fares); @@ -182,6 +184,7 @@ public function generate(Request $request): RedirectResponse|View $pax_load_sheet = []; $tpaxfig = 0; + $acd_maxpax = 0; /** @var Fare $fare */ foreach ($all_fares as $fare) { @@ -189,6 +192,7 @@ public function generate(Request $request): RedirectResponse|View continue; } + $acd_maxpax = $acd_maxpax + $fare->capacity; $count = floor(($fare->capacity * rand($loadmin, $loadmax)) / 100); $tpaxfig += $count; $pax_load_sheet[] = [ @@ -203,7 +207,7 @@ public function generate(Request $request): RedirectResponse|View $loaddist[] = $fare->code.' '.$count; } - // Calculate total weights + // Calculate and convert weights according to SimBrief requirements if (setting('units.weight') === 'kg') { $tpaxload = round(($pax_weight * $tpaxfig) / 2.205); $tbagload = round(($bag_weight * $tpaxfig) / 2.205); @@ -213,7 +217,6 @@ public function generate(Request $request): RedirectResponse|View } // Load up fares for cargo - $tcargoload = 0; $cargo_load_sheet = []; foreach ($all_fares as $fare) { @@ -239,6 +242,25 @@ public function generate(Request $request): RedirectResponse|View $request->session()->put('simbrief_fares', array_merge($pax_load_sheet, $cargo_load_sheet)); + // Prepare SimBrief layouts, acdata json and actype code + $layouts = SimBriefLayout::get(); + + $acdata = [ + // Passenger and Baggage Weights needs to pounds, integer like 185 + 'paxwgt' => $pax_weight, + 'bagwgt' => $bag_weight, + // Airframe Weights needs to be thousands of pounds, with 3 digit precision like 85.715 + 'mzfw' => (filled($aircraft->zfw) && $aircraft->zfw->internal(0) > 0) ? round($aircraft->zfw->internal(0) / 1000, 3) : null, + 'mtow' => (filled($aircraft->mtow) && $aircraft->mtow->internal(0) > 0) ? round($aircraft->mtow->internal(0) / 1000, 3) : null, + 'mlw' => (filled($aircraft->mlw) && $aircraft->mlw->internal(0) > 0) ? round($aircraft->mlw->internal(0) / 1000, 3) : null, + 'hexcode' => filled($aircraft->hex_code) ? $aircraft->hex_code : null, + 'maxpax' => $acd_maxpax, + ]; + + $actype = (filled($aircraft->simbrief_type)) ? $aircraft->simbrief_type : ((filled(optional($aircraft->subfleet)->simbrief_type)) ? $aircraft->subfleet->simbrief_type : $aircraft->icao); + $sbaircraft = filled($aircraft->sbaircraft) ? collect(json_decode($aircraft->sbaircraft->details)) : null; + $sbairframes = (setting('simbrief.use_custom_airframes', false)) ? $aircraft->sbairframes->where('source', AirframeSource::INTERNAL) : $aircraft->sbairframes; + // Show the main simbrief form return view('flights.simbrief_form', [ 'user' => $user, @@ -255,8 +277,13 @@ public function generate(Request $request): RedirectResponse|View 'tbagload' => $tbagload, 'tpayload' => $tpayload, 'tcargoload' => $tcargoload, - 'loaddist' => implode(' ', $loaddist), + 'loaddist' => implode(' ', $loaddist).' BAG '.$tbagload, 'static_id' => $static_id, + 'sbaircraft' => $sbaircraft, + 'sbairframes' => filled($sbairframes) ? $sbairframes : null, + 'acdata' => json_encode($acdata), + 'actype' => $actype, + 'layouts' => $layouts, ]); } diff --git a/app/Http/Requests/CreateAirframeRequest.php b/app/Http/Requests/CreateAirframeRequest.php new file mode 100644 index 000000000..4b3858cbd --- /dev/null +++ b/app/Http/Requests/CreateAirframeRequest.php @@ -0,0 +1,19 @@ +ident; + // Set these to the response units + $res['dow'] = $this->dow->getResponseUnits(); + $res['zfw'] = $this->zfw->getResponseUnits(); + $res['mtow'] = $this->mtow->getResponseUnits(); + $res['mlw'] = $this->mlw->getResponseUnits(); + $res['fuel_onboard'] = $this->fuel_onboard->getResponseUnits(); + return $res; } } diff --git a/app/Models/Aircraft.php b/app/Models/Aircraft.php index 7a1321861..bbdee5c2a 100644 --- a/app/Models/Aircraft.php +++ b/app/Models/Aircraft.php @@ -4,6 +4,7 @@ use App\Contracts\Model; use App\Models\Casts\FuelCast; +use App\Models\Casts\MassCast; use App\Models\Enums\AircraftStatus; use App\Models\Traits\ExpensableTrait; use App\Models\Traits\FilesTrait; @@ -31,10 +32,10 @@ * @property string registration * @property string fin * @property int flight_time - * @property float dow - * @property float mlw - * @property float mtow - * @property float zfw + * @property Mass dow + * @property Mass mlw + * @property Mass mtow + * @property Mass zfw * @property string hex_code * @property string selcal * @property Airport airport @@ -46,7 +47,7 @@ * @property int state * @property string simbrief_type * @property Carbon landing_time - * @property float fuel_onboard + * @property Fuel fuel_onboard * @property Bid bid */ class Aircraft extends Model @@ -90,12 +91,12 @@ class Aircraft extends Model protected $casts = [ 'flight_time' => 'float', 'fuel_onboard' => FuelCast::class, - 'dow' => 'float', - 'mlw' => 'float', - 'mtow' => 'float', + 'dow' => MassCast::class, + 'mlw' => MassCast::class, + 'mtow' => MassCast::class, 'state' => 'integer', 'subfleet_id' => 'integer', - 'zfw' => 'float', + 'zfw' => MassCast::class, ]; /** @@ -243,4 +244,14 @@ public function subfleet(): BelongsTo { return $this->belongsTo(Subfleet::class, 'subfleet_id'); } + + public function sbaircraft(): HasOne + { + return $this->hasOne(SimBriefAircraft::class, 'icao', 'icao'); + } + + public function sbairframes(): HasMany + { + return $this->hasMany(SimBriefAirframe::class, 'icao', 'icao'); + } } diff --git a/app/Models/Casts/MassCast.php b/app/Models/Casts/MassCast.php new file mode 100644 index 000000000..f91244495 --- /dev/null +++ b/app/Models/Casts/MassCast.php @@ -0,0 +1,56 @@ +toUnit(config('phpvms.internal_units.mass')); + } + + return $value; + } +} diff --git a/app/Models/Enums/AirframeSource.php b/app/Models/Enums/AirframeSource.php new file mode 100644 index 000000000..c478131b0 --- /dev/null +++ b/app/Models/Enums/AirframeSource.php @@ -0,0 +1,16 @@ + 'Custom', + self::SIMBRIEF => 'SimBrief', + ]; +} diff --git a/app/Models/SimBriefAircraft.php b/app/Models/SimBriefAircraft.php new file mode 100644 index 000000000..574af8cef --- /dev/null +++ b/app/Models/SimBriefAircraft.php @@ -0,0 +1,35 @@ + 'string', + 'name' => 'string', + ]; + + public static array $rules = [ + 'icao' => 'required|string', + 'name' => 'required|string', + 'details' => 'nullable', + ]; + + // Relationships + public function sbairframes(): HasMany + { + return $this->hasMany(SimBriefAirframe::class, 'icao', 'icao'); + } +} diff --git a/app/Models/SimBriefAirframe.php b/app/Models/SimBriefAirframe.php new file mode 100644 index 000000000..b14fe53e9 --- /dev/null +++ b/app/Models/SimBriefAirframe.php @@ -0,0 +1,41 @@ + 'string', + 'name' => 'string', + ]; + + public static array $rules = [ + 'icao' => 'required|string', + 'name' => 'required|string', + 'airframe_id' => 'nullable', + 'source' => 'nullable', + 'details' => 'nullable', + 'options' => 'nullable', + ]; + + // Relationships + public function sbaircraft(): BelongsTo + { + return $this->belongsTo(SimBriefAircraft::class, 'icao', 'icao'); + } +} diff --git a/app/Models/SimBriefLayout.php b/app/Models/SimBriefLayout.php new file mode 100644 index 000000000..8dab632f1 --- /dev/null +++ b/app/Models/SimBriefLayout.php @@ -0,0 +1,28 @@ + 'string', + 'name' => 'string', + 'name_long' => 'string', + ]; + + public static array $rules = [ + 'id' => 'required|string', + 'name' => 'required|string', + 'name_long' => 'required|string', + ]; +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 61afe39ef..fe214cc6f 100755 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -407,6 +407,10 @@ private function mapAdminRoutes() 'delete', ], 'typeratings/{id}/users', 'TypeRatingController@users')->middleware('ability:admin,typeratings'); + // SimBrief Airframes + Route::resource('airframes', 'AirframeController')->middleware('ability:admin,aircraft,fleet'); + Route::get('sbupdate', 'AirframeController@updateSimbriefData')->name('airframes.sbupdate')->middleware('ability:admin,aircraft,fleet'); + // maintenance Route::match(['get'], 'maintenance', 'MaintenanceController@index') ->name('maintenance.index')->middleware('ability:admin,maintenance'); diff --git a/app/Repositories/AirframeRepository.php b/app/Repositories/AirframeRepository.php new file mode 100644 index 000000000..5a9dd62f1 --- /dev/null +++ b/app/Repositories/AirframeRepository.php @@ -0,0 +1,51 @@ + 'like', + 'name' => 'like', + 'airframe_id' => 'like', + 'source' => 'like', + ]; + + public function model() + { + return SimBriefAirframe::class; + } + + public function selectBoxList($add_blank = false, $only_custom = false, $icao = null): array + { + $retval = []; + $where = []; + if ($only_custom) { + $where['source'] = AirframeSource::INTERNAL; + } + + if (filled($icao)) { + $where['icao'] = $icao; + } + + $items = $this->findWhere($where); + + if ($add_blank) { + $retval[''] = ''; + } + + foreach ($items as $i) { + $retval[$i->id] = $i->name; + } + + return $retval; + } +} diff --git a/app/Services/ImportExport/AircraftImporter.php b/app/Services/ImportExport/AircraftImporter.php index da5ae0cd1..842e5f598 100644 --- a/app/Services/ImportExport/AircraftImporter.php +++ b/app/Services/ImportExport/AircraftImporter.php @@ -9,6 +9,7 @@ use App\Models\Enums\AircraftStatus; use App\Models\Subfleet; use App\Support\ICAO; +use App\Support\Units\Mass; /** * Import aircraft @@ -90,12 +91,13 @@ public function import(array $row, $index): bool // Check fields and set to null if they are blank // Somehow they got empty strings instead of null without this! $row['fin'] = blank($row['fin']) ? null : $row['fin']; - $row['dow'] = blank($row['dow']) ? null : $row['dow']; - $row['zfw'] = blank($row['zfw']) ? null : $row['zfw']; - $row['mtow'] = blank($row['mtow']) ? null : $row['mtow']; - $row['mlw'] = blank($row['mlw']) ? null : $row['mlw']; $row['selcal'] = blank($row['selcal']) ? null : $row['selcal']; $row['simbrief_type'] = blank($row['simbrief_type']) ? null : $row['simbrief_type']; + // Set the correct mass units + $row['dow'] = $this->CorrectMassUnit($row['dow']); + $row['zfw'] = $this->CorrectMassUnit($row['zfw']); + $row['mtow'] = $this->CorrectMassUnit($row['mtow']); + $row['mlw'] = $this->CorrectMassUnit($row['mlw']); // Try to add or update try { @@ -110,4 +112,13 @@ public function import(array $row, $index): bool $this->log('Imported '.$row['registration'].' '.$row['name']); return true; } + + public function CorrectMassUnit($value) + { + if ($value > 0) { + return Mass::make((float) $value, setting('units.weight')); + } + + return null; + } } diff --git a/app/Services/SimBriefService.php b/app/Services/SimBriefService.php index 097dc52fe..04121a318 100644 --- a/app/Services/SimBriefService.php +++ b/app/Services/SimBriefService.php @@ -5,12 +5,17 @@ use App\Contracts\Service; use App\Models\Acars; use App\Models\Enums\AcarsType; +use App\Models\Enums\AirframeSource; use App\Models\Pirep; use App\Models\SimBrief; +use App\Models\SimBriefAircraft; +use App\Models\SimBriefAirframe; +use App\Models\SimBriefLayout; use App\Models\SimBriefXML; use Carbon\Carbon; use GuzzleHttp\Client as GuzzleClient; use GuzzleHttp\Exception\GuzzleException; +use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; class SimBriefService extends Service @@ -218,4 +223,81 @@ public function removeExpiredEntries(): void // TODO: Delete any assets (Which assets ?) } } + + /** + * Get Aircraft and Airframe Data from SimBrief + * Insert or Update relevant models, for proper and detailed flight planning + */ + public function getAircraftAndAirframes() + { + $url = config('phpvms.simbrief_airframes_url'); + $sbdata = Http::get($url); + + if ($sbdata->ok()) { + Log::debug('SimBrief | Aircraft and Airframe data obtained'); + $json = $sbdata->json(); + + // Update Or Create Aircraft Entries + foreach ($json as $ac) { + Log::debug('SimBrief | Importing Aircraft : '.$ac['aircraft_icao'].' '.$ac['aircraft_name']); + + foreach ($ac['airframes'] as $af) { + Log::debug('SimBrief | Importing Airframe : '.$af['airframe_name'].' '.$af['airframe_comments']); + SimBriefAirframe::updateOrCreate([ + 'icao' => $af['airframe_icao'], + 'name' => $af['airframe_comments'], + ], [ + 'icao' => $af['airframe_icao'], + 'name' => $af['airframe_comments'], + 'airframe_id' => ($af['airframe_id'] != false) ? $af['pilot_id'].'_'.$af['airframe_id'] : null, + 'source' => AirframeSource::SIMBRIEF, + 'details' => json_encode($af), + 'options' => json_encode($af['airframe_options']), + ]); + } + + unset($ac['airframes']); + + SimBriefAircraft::updateOrCreate([ + 'icao' => $ac['aircraft_icao'], + ], [ + 'icao' => $ac['aircraft_icao'], + 'name' => $ac['aircraft_name'], + 'details' => json_encode($ac), + ]); + } + } else { + Log::error('SimBrief | An Error Occured while trying to get aircraft and airframe data!'); + } + } + + /** + * Get OFP Layouts from SimBrief + * Insert or Update relevant model for proper flight planning + */ + public function GetBriefingLayouts() + { + $url = config('phpvms.simbrief_layouts_url'); + $sbdata = Http::get($url); + + if ($sbdata->ok()) { + Log::debug('SimBrief | OFP Layouts data obtained'); + $json = $sbdata->json(); + + // Update Or Create Layout Entries + foreach ($json['layouts'] as $sb) { + Log::debug('SimBrief | Importing Layout : '.$sb['name_long']); + + SimBriefLayout::updateOrCreate([ + 'id' => $sb['id'], + ], [ + 'id' => $sb['id'], + 'name' => $sb['name_short'], + 'name_long' => $sb['name_long'], + ]); + } + } else { + Log::error('SimBrief | An Error Occured while trying to get layout data!'); + } + } } diff --git a/config/phpvms.php b/config/phpvms.php index 92cda6ea7..3250b57c2 100644 --- a/config/phpvms.php +++ b/config/phpvms.php @@ -57,6 +57,16 @@ */ 'simbrief_update_url' => 'https://www.simbrief.com/api/xml.fetcher.php?userid={sb_user_id}&static_id={sb_static_id}', + /* + * URL for fetching Simbrief aircraft and airframe data + */ + 'simbrief_airframes_url' => 'http://www.simbrief.com/api/inputs.airframes.json', + + /* + * URL for fetching Simbrief layouts data + */ + 'simbrief_layouts_url' => 'http://www.simbrief.com/api/inputs.list.json', + /* * Your vaCentral API key */ diff --git a/resources/views/admin/aircraft/fields.blade.php b/resources/views/admin/aircraft/fields.blade.php index 98f663844..af102c004 100644 --- a/resources/views/admin/aircraft/fields.blade.php +++ b/resources/views/admin/aircraft/fields.blade.php @@ -84,6 +84,11 @@ {{ Form::text('simbrief_type', null, ['class' => 'form-control']) }}

{{ $errors->first('simbrief_type') }}

+
+ {{ Form::label('hex_code', 'Hex Code:') }} + {{ Form::text('hex_code', null, ['class' => 'form-control']) }} +

{{ $errors->first('hex_code') }}

+
@@ -93,28 +98,44 @@
-
 Certified Weights
+
 Certified Weights ({{ setting('units.weight') }})
{{ Form::label('dow', 'Dry Operating Weight (DOW/OEW):') }} - {{ Form::number('dow', null, ['class' => 'form-control']) }} -

{{ $errors->first('dow') }}

+
+
+ +

{{ $errors->first('dow') }}

+
+
{{ Form::label('zfw', 'Max Zero Fuel Weight (MZFW):') }} - {{ Form::number('zfw', null, ['class' => 'form-control']) }} -

{{ $errors->first('zfw') }}

+
+
+ +

{{ $errors->first('zfw') }}

+
+
{{ Form::label('mtow', 'Max Takeoff Weight (MTOW):') }} - {{ Form::number('mtow', null, ['class' => 'form-control']) }} -

{{ $errors->first('mtow') }}

+
+
+ +

{{ $errors->first('mtow') }}

+
+
{{ Form::label('mlw', 'Max Landing Weight (MLW):') }} - {{ Form::number('mlw', null, ['class' => 'form-control']) }} -

{{ $errors->first('mlw') }}

+
+
+ +

{{ $errors->first('mlw') }}

+
+
diff --git a/resources/views/admin/airframes/create.blade.php b/resources/views/admin/airframes/create.blade.php new file mode 100644 index 000000000..a6657fb0e --- /dev/null +++ b/resources/views/admin/airframes/create.blade.php @@ -0,0 +1,11 @@ +@extends('admin.app') +@section('title', 'Add SimBrief Airframe') +@section('content') +
+
+ {{ Form::open(['route' => 'admin.airframes.store', 'class' => 'add_airframe', 'method'=>'POST', 'autocomplete' => false]) }} + @include('admin.airframes.fields') + {{ Form::close() }} +
+
+@endsection diff --git a/resources/views/admin/airframes/edit.blade.php b/resources/views/admin/airframes/edit.blade.php new file mode 100644 index 000000000..55447c83c --- /dev/null +++ b/resources/views/admin/airframes/edit.blade.php @@ -0,0 +1,11 @@ +@extends('admin.app') +@section('title', 'Edit '.$airframe->name) +@section('content') +
+
+ {{ Form::model($airframe, ['route' => ['admin.airframes.update', $airframe->id], 'method' => 'patch', 'autocomplete' => false]) }} + @include('admin.airframes.fields') + {{ Form::close() }} +
+
+@endsection diff --git a/resources/views/admin/airframes/fields.blade.php b/resources/views/admin/airframes/fields.blade.php new file mode 100644 index 000000000..2b7c689d1 --- /dev/null +++ b/resources/views/admin/airframes/fields.blade.php @@ -0,0 +1,25 @@ +
+
+ {{ Form::label('icao', 'ICAO:') }} + {{ Form::text('icao', null, ['class' => 'form-control']) }} +

{{ $errors->first('icao') }}

+
+
+ {{ Form::label('name', 'Name:') }} + {{ Form::text('name', null, ['class' => 'form-control']) }} +

{{ $errors->first('name') }}

+
+
+ {{ Form::label('airframe_id', 'SB Airframe ID:') }} + {{ Form::text('airframe_id', null, ['class' => 'form-control']) }} +

{{ $errors->first('airframe_id') }}

+
+
+
+
+
+ {{ Form::hidden('source', \App\Models\Enums\AirframeSource::INTERNAL) }} + {{ Form::button('Save', ['type' => 'submit', 'class' => 'btn btn-success']) }} +
+
+
diff --git a/resources/views/admin/airframes/index.blade.php b/resources/views/admin/airframes/index.blade.php new file mode 100644 index 000000000..b6fcd6577 --- /dev/null +++ b/resources/views/admin/airframes/index.blade.php @@ -0,0 +1,25 @@ +@extends('admin.app') +@section('title', 'SimBrief Airframes') +@section('actions') +
  • + + + Add New Airframe + +
  • +   +
  • + + + Update SimBrief Airframes & Layouts + +
  • +@endsection + +@section('content') +
    +
    + @include('admin.airframes.table') +
    +
    +@endsection diff --git a/resources/views/admin/airframes/show.blade.php b/resources/views/admin/airframes/show.blade.php new file mode 100644 index 000000000..a55420ef5 --- /dev/null +++ b/resources/views/admin/airframes/show.blade.php @@ -0,0 +1,17 @@ +@extends('admin.app') + +@section('content') +
    +

    {{ $airframe->name }}

    +
    +
    +
    +
    +
    + @include('admin.airframes.show_fields') +
    +
    +
    +
    +@endsection + diff --git a/resources/views/admin/airframes/show_fields.blade.php b/resources/views/admin/airframes/show_fields.blade.php new file mode 100644 index 000000000..8c4543795 --- /dev/null +++ b/resources/views/admin/airframes/show_fields.blade.php @@ -0,0 +1,36 @@ + +
    + {{ Form::label('id', 'Id:') }} +

    {{ $airframe->id }}

    +
    + + +
    + {{ Form::label('type', 'ICAO Code:') }} +

    {{ $airframe->icao }}

    +
    + + +
    + {{ Form::label('name', 'Name:') }} +

    {{ $airframe->name }}

    +
    + + +
    + {{ Form::label('airframe_id', 'SB Airframe ID:') }} +

    {{ $airframe->airframe_id }}

    +
    + + +
    + {{ Form::label('created_at', 'Created At:') }} +

    {{ show_datetime($airframe->created_at) }}

    +
    + + +
    + {{ Form::label('updated_at', 'Updated At:') }} +

    {{ show_datetime($airframe->updated_at) }}

    +
    + diff --git a/resources/views/admin/airframes/table.blade.php b/resources/views/admin/airframes/table.blade.php new file mode 100644 index 000000000..daf57b7bb --- /dev/null +++ b/resources/views/admin/airframes/table.blade.php @@ -0,0 +1,30 @@ +
    + + + + + + + + + + + @foreach($airframes as $af) + + + + + + + + + @endforeach + +
    ICAONameSB Airframe IDCreated AtUpdated At
    {{ $af->icao }}{{ $af->name }}{{ $af->airframe_id }}{{ $af->created_at->format('d.M.y H:i') }}{{ $af->updated_at->format('d.M.y H:i') }} + {{ Form::open(['route' => ['admin.airframes.destroy', $af->id], 'method' => 'delete']) }} + + + {{ Form::button('', ['type' => 'submit', 'class' => 'btn btn-sm btn-danger btn-icon', 'onclick' => "return confirm('Are you sure?')"]) }} + {{ Form::close() }} +
    +
    diff --git a/resources/views/admin/menu.blade.php b/resources/views/admin/menu.blade.php index a1faa0ced..ecc75340a 100644 --- a/resources/views/admin/menu.blade.php +++ b/resources/views/admin/menu.blade.php @@ -50,6 +50,10 @@
  • airlines
  • @endability + @ability('admin', 'aircraft', 'fleet') +
  • sb airframes
  • + @endability + @ability('admin', 'airports')
  • airports
  • @endability diff --git a/resources/views/layouts/default/flights/simbrief_form.blade.php b/resources/views/layouts/default/flights/simbrief_form.blade.php index 25f50def7..a78ab895b 100644 --- a/resources/views/layouts/default/flights/simbrief_form.blade.php +++ b/resources/views/layouts/default/flights/simbrief_form.blade.php @@ -2,39 +2,67 @@ @section('title', 'SimBrief Flight Planning') @section('content') -
    -

    Create Simbrief Briefing

    +

    Create Simbrief Flight Plan & Briefing Package

    -
     Aircraft Details
    -
    +
    - @if(filled($aircraft->simbrief_type)) - - @elseif(filled($aircraft->subfleet->simbrief_type)) - - @else - - @endif +
    -
    +
    + @if($sbairframes) +
    + + +
    + @endif

    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    -
     @lang('pireps.flightinformations') For {{ $flight->airline->code }}{{ $flight->flight_number }} ({{ \App\Models\Enums\FlightType::label($flight->flight_type) }})
    @@ -84,9 +112,8 @@

    -
    -
     Configuration And Load Information For +
     Configuration And Estimated Load Information For {{ $aircraft->registration }} ({{ $aircraft->subfleet->name }})
    @foreach($pax_load_sheet as $fare) @@ -98,7 +125,7 @@ {{-- Generate Load Figures For Cargo Fares --}} @foreach($cargo_load_sheet as $fare)
    - +
    @endforeach @@ -108,22 +135,27 @@
    @if($tpaxload) -
    +
    -
    +
    @endif @if($tpaxload && $tcargoload) -
    +
    +
    + + +
    @endif -
    +
    +
    @@ -131,17 +163,16 @@ @endif
    - {{-- Prepare Form Fields For SimBrief --}} - - @if($tpaxfig) - - @elseif(!$tpaxfig && $tcargoload) - - @endif - @if($tcargoload) - - @endif + + @if($tpaxfig) + + @elseif(!$tpaxfig && $tcargoload) + + @endif + @if($tcargoload) + + @endif @if(isset($tpayload) && $tpayload > 0) @endif @@ -155,12 +186,6 @@ - - - - - - {{-- For more info about form fields and their details check SimBrief Forum / API Support --}}
    @@ -189,6 +214,58 @@ @endif + @if($sbaircraft) + + Climb Profile: + + + + + + Cruise Profile: + + + + + + Cost Index: + + + + Descent Profile: + + + + + @else + + Cruise Profile: + + + + + + Cost Index: + + + @endif Cont Fuel: @@ -237,6 +314,24 @@ + + Use SIDs: + + + + + + Use STARs: + + + + Plan Stepclimbs: @@ -249,12 +344,35 @@ ETOPS Planning: - + + ETOPS Threshold Time: + + + + + + ETOPS Rule Time: + + + +

    @@ -275,10 +393,22 @@ + @if($layouts) + + OFP Layout: + + + + + @endif Detailed Navlog: - @@ -344,7 +474,55 @@ class="btn btn-primary" value="Generate"> @section('scripts') + + +