Compare commits

...

18 Commits

Author SHA1 Message Date
Shift
1f78434b71 Add .shift to open Pull Request 2022-01-28 00:53:11 +00:00
Nabeel Shahzad
723f66a382 More data updates 2022-01-27 13:30:49 -05:00
Nabeel Shahzad
27be992395 Update seeds for a shorter test flight 2022-01-27 12:14:28 -05:00
Sam
6160d57790 Fix docker image name (#1382)
closes #1381
2022-01-11 09:46:53 -05:00
B.Fatih KOZ
7fabd57e13 Fix flight and subfleet import with edited fares (#1379)
* Fix fare import

* StyleFix
2022-01-11 08:17:32 -05:00
B.Fatih KOZ
09453becf8 Award Checks Update (#1376)
Add active/passive check for awards and update the handler to pass only active ones to the process when needed.
2022-01-10 15:49:50 -05:00
B.Fatih KOZ
023313c681 Check settings and filter aircraft list if needed (#1377)
Check settings and filter aircraft list if needed.
2022-01-10 13:29:30 -05:00
B.Fatih KOZ
d3b7d25abd Add ability to export flights of an airline only(#1375)
* Airline Flight Export

Add ability to export flights of an airline only

* StyleFix
2022-01-10 09:27:40 -05:00
B.Fatih KOZ
7799867302 Fix Weather Widget blade (#1372)
Old blade was failing due to missing items in a metar like below

`SVMC 031703Z AUTO NIL`
2022-01-04 14:51:39 -05:00
B.Fatih KOZ
fd7c1b8314 Update RegisterController.php (#1373)
Get only active and displayed UserFields.
2022-01-04 14:29:41 -05:00
B.Fatih KOZ
c12cf0964a Fix setting name (#1371) 2021-12-14 18:10:39 -05:00
Nabeel S
2202d5bf99 Setting for how often the live map updates #1369 (#1370) 2021-12-14 12:48:21 -05:00
B.Fatih KOZ
064682b71f Type Rating update (#1366)
* Update Typerating.php

Add `'active'` to fillable

* Update fields.blade.php

Fixed grid, positioning of checkbox label and removed extra row
2021-12-08 12:40:10 -05:00
Nabeel Shahzad
3c9d419ebb Clarify logs for pirep cancel/updates 2021-12-07 13:24:18 -05:00
Nabeel S
a3f110c0c0 Add PHP 8.1 to shims and build (#1365)
* Add PHP 8.1 to shims and build

* Lock psr/container version

* Lock psr version

* Restrict symfony component versions

* Downgrade event-dispatcher-contracts

* Update more package versions

* php-cs fixes

* style fix

* cs-fixer file fix

* Exclude resources dir from cs fixer

* Ignore cs fixer env check

* Update league/csv
2021-12-03 09:23:59 -05:00
B.Fatih KOZ
c45d52dffa Aircraft level Hub definitions (#1363)
* Aircraft level Hub definitions

* Add ability to define a hub for an aircraft apart from its subfleet.

* Update csv import/export capability for hub_id field

* Fix importertest source csv

* Fix source csv for updating too

* Update aircraft_empty_cols.csv

Co-authored-by: Nabeel S <nabeelio@users.noreply.github.com>
2021-11-30 14:54:29 -05:00
B.Fatih KOZ
4d21ca0982 Update RegisterController.php (#1358)
Only active fields should be passed to view during registration.

Co-authored-by: Nabeel S <nabeelio@users.noreply.github.com>
2021-11-30 14:40:18 -05:00
B.Fatih KOZ
52e716d6ee Type Ratings | New Feature (#1360)
* Type Ratings

Adding "Type Rating" definition and assignment possibility to v7 core.

* Update ProfileController.php

* StyleFix 1

* Update settings.yml

Change description text as requested
2021-11-30 14:36:17 -05:00
75 changed files with 2263 additions and 1034 deletions

View File

@@ -23,6 +23,7 @@ declare -a remove_files=(
.eslintrc
.php_cs
.php_cs.cache
.php-cs-fixer.php
.phpstorm.meta.php
.styleci.yml
.phpunit.result.cache

View File

@@ -2,12 +2,12 @@ name: 'Build'
on: ['push', 'pull_request', 'workflow_dispatch', 'release']
jobs:
build:
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
if: github.repository == 'nabeelio/phpvms'
strategy:
fail-fast: true
matrix:
php-versions: ['7.3', '7.4', '8.0']
php-versions: ['7.3', '7.4', '8.0', '8.1']
name: PHP ${{ matrix.php-versions }}
env:
extensions: intl, pcov, mbstring
@@ -78,7 +78,8 @@ jobs:
- name: Run Tests
run: |
vendor/bin/php-cs-fixer fix --config=.php_cs -v --dry-run --diff --using-cache=no
export PHP_CS_FIXER_IGNORE_ENV=1
vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php -v --dry-run --diff --using-cache=no
vendor/bin/phpunit --debug --verbose
# This runs after all of the tests, run have run. Creates a cleaned up version of the

1
.gitignore vendored
View File

@@ -76,3 +76,4 @@ error_log
/config.php
/config.bak.php
/VERSION
sync.sh

69
.php-cs-fixer.php Normal file
View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
$header = <<<'EOF'
This file is part of PHP CS Fixer.
(c) Fabien Potencier <fabien@symfony.com>
Dariusz Rumiński <dariusz.ruminski@gmail.com>
This source file is subject to the MIT license that is bundled
with this source code in the file LICENSE.
EOF;
$finder = PhpCsFixer\Finder::create()
->ignoreVCSIgnored(true)
->exclude('tests/data')
->exclude('storage')
->exclude('resources')
->in(__DIR__)
->append([
__DIR__.'/dev-tools/doc.php',
// __DIR__.'/php-cs-fixer', disabled, as we want to be able to run bootstrap file even on lower PHP version, to show nice message
__FILE__,
])
;
$config = new PhpCsFixer\Config();
$config
->setRiskyAllowed(true)
->setRules([
'@PSR2' => true,
'strict_param' => true,
'no_php4_constructor' => true,
'no_extra_blank_lines' => true,
'no_superfluous_elseif' => true,
'single_line_comment_style' => false,
'simple_to_complex_string_variable' => true,
'array_syntax' => [
'syntax' => 'short',
],
'binary_operator_spaces' => [
'operators' => [
'=>' => 'align_single_space_minimal'
]
],
/*
'blank_line_before_statement' => [
'statements' => [
'declare',
'for',
'return',
'throw',
'try',
],
],
*/
])
->setFinder($finder);
return $config;

37
.php_cs
View File

@@ -1,37 +0,0 @@
<?php
$finder = PhpCsFixer\Finder::create()
->in('app')
->in('config');
return PhpCsFixer\Config::create()
->setHideProgress(true)
->setUsingCache(false)
->setRiskyAllowed(true)
->setRules([
'@PSR2' => true,
'strict_param' => true,
'no_php4_constructor' => true,
'no_extra_blank_lines' => true,
'no_superfluous_elseif' => true,
'single_line_comment_style' => false,
'simple_to_complex_string_variable' => true,
'array_syntax' => [
'syntax' => 'short',
],
'binary_operator_spaces' => [
'align_double_arrow' => true,
],
/*
'blank_line_before_statement' => [
'statements' => [
'declare',
'for',
'return',
'throw',
'try',
],
],
*/
])
->setFinder($finder);

4
.shift Normal file
View File

@@ -0,0 +1,4 @@
This file was added by Shift #54503 in order to open a
Pull Request since no other commits were made.
You should remove this file.

View File

@@ -166,6 +166,27 @@ class ImportExport
return [];
}
if (strpos($split_values[0], '?') !== false) {
// This contains the query string, which turns it into a multi-level array
$query_str = explode('?', $split_values[0]);
$parent = trim($query_str[0]);
$children = [];
$kvp = explode('&', trim($query_str[1]));
foreach ($kvp as $items) {
if (!$items) {
continue;
}
$this->kvpToArray($items, $children);
}
$ret[$parent] = $children;
return $ret;
}
// This is not a query string, return it back untouched
return [$split_values[0]];
}

View File

@@ -0,0 +1,53 @@
<?php
use App\Contracts\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddTypeRatingTables extends Migration
{
public function up()
{
if (!Schema::hasTable('typeratings')) {
Schema::create('typeratings', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('type');
$table->string('description')->nullable();
$table->string('image_url')->nullable();
$table->boolean('active')->default(true);
$table->timestamps();
$table->unique('id');
$table->unique('name');
});
}
if (!Schema::hasTable('typerating_user')) {
Schema::create('typerating_user', function (Blueprint $table) {
$table->unsignedInteger('typerating_id');
$table->unsignedInteger('user_id');
$table->primary(['typerating_id', 'user_id']);
$table->index(['typerating_id', 'user_id']);
});
}
if (!Schema::hasTable('typerating_subfleet')) {
Schema::create('typerating_subfleet', function (Blueprint $table) {
$table->unsignedInteger('typerating_id');
$table->unsignedInteger('subfleet_id');
$table->primary(['typerating_id', 'subfleet_id']);
$table->index(['typerating_id', 'subfleet_id']);
});
}
}
public function down()
{
Schema::dropIfExists('typeratings');
Schema::dropIfExists('typerating_user');
Schema::dropIfExists('typerating_subfleet');
}
}

View File

@@ -0,0 +1,15 @@
<?php
use App\Contracts\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddHubToAircraft extends Migration
{
public function up()
{
Schema::table('aircraft', function (Blueprint $table) {
$table->string('hub_id', 5)->nullable()->after('airport_id');
});
}
}

View File

@@ -0,0 +1,15 @@
<?php
use App\Contracts\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class UpdateAwardsAddActive extends Migration
{
public function up()
{
Schema::table('awards', function (Blueprint $table) {
$table->boolean('active')->default(true)->nullable()->after('ref_model_params');
});
}
}

View File

@@ -36,7 +36,8 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- pirep_id: b68R5gwVzpVe
- id: 9aAN4mxlK9zb1
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -54,7 +55,8 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- pirep_id: b68R5gwVzpVe
- id: 9aAN4mxlK9zb2
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -72,7 +74,8 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- pirep_id: b68R5gwVzpVe
- id: 9aAN4mxlK9zb3
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -90,7 +93,8 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- pirep_id: b68R5gwVzpVe
- id: 9aAN4mxlK9zb34
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -108,7 +112,8 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- pirep_id: b68R5gwVzpVe
- id: 9aAN4mxlK9zb35
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -126,7 +131,8 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- pirep_id: b68R5gwVzpVe
- id: 9aAN4mxlK9zb36
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -144,7 +150,8 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- pirep_id: b68R5gwVzpVe
- id: 9aAN4mxlK9zb37
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -162,7 +169,8 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- pirep_id: b68R5gwVzpVe
- id: 9aAN4mxlK9zb38
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -180,7 +188,8 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- pirep_id: b68R5gwVzpVe
- id: 9aAN4mxlK9zb39
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'

View File

@@ -34,6 +34,17 @@ airports:
lon: -97.6698889
hub: 1
ground_handling_cost: 100
- id: KPHN
iata: HPN
icao: KPHN
name: Westchester County Airport
location: White Plains
country: United States
timezone: America/New_York
lat: 41.067
lon: -73.7076
hub: 1
ground_handling_cost: 100
- id: KJFK
iata: JFK
icao: KJFK
@@ -448,6 +459,21 @@ flights:
- id: flightid_1
airline_id: 1
flight_number: 100
dpt_airport_id: KHPN
arr_airport_id: KJFK
route: DCT CMK V374 DENNA V44 BELTT/N0346F140 V44 DPK DCT
distance: 113
level: 110
dpt_time: 6PM CST
arr_time: 11PM EST
flight_time: 240
flight_type: J
load_factor: 100
created_at: NOW
updated_at: NOW
- id: flightid_2
airline_id: 1
flight_number: 101
dpt_airport_id: KAUS
arr_airport_id: KJFK
route: KAUS SID TNV J87 IAH J2 LCH J22 MEI J239 ATL J52 AJFEB J14 BYJAC Q60 JAXSN J14 COLIN J61 HUBBS J55 SIE STAR KJFK

File diff suppressed because one or more lines are too long

View File

@@ -42,6 +42,9 @@
- name: ranks
display_name: Ranks
description: Create/edit ranks
- name: typeratings
display_name: Type Ratings
description: Create/edit type ratings
- name: users
display_name: Users
description: Create/edit users

View File

@@ -137,6 +137,13 @@
options: ''
type: int
description: 'Initial zoom level on the map'
- key: acars.update_interval
name: 'Refresh Interval'
group: acars
value: 60
options: ''
type: int
description: 'How often the live map updates its data'
- key: airports.default_ground_handling_cost
name: 'Default Ground Handling Cost'
group: airports
@@ -283,7 +290,14 @@
value: true
options: ''
type: boolean
description: 'Aircraft that can be flown are restricted to a user''s rank'
description: 'Aircraft restricted to user''s rank'
- key: pireps.restrict_aircraft_to_typerating
name: 'Restrict Aircraft by Type Ratings'
group: pireps
value: false
options: ''
type: boolean
description: 'Aircraft restricted to user type ratings'
- key: pireps.only_aircraft_at_dpt_airport
name: 'Restrict Aircraft At Departure'
group: pireps

View File

@@ -79,6 +79,7 @@ class AircraftController extends Controller
{
return view('admin.aircraft.create', [
'airports' => $this->airportRepo->selectBoxList(),
'hubs' => $this->airportRepo->selectBoxList(true, true),
'subfleets' => Subfleet::all()->pluck('name', 'id'),
'statuses' => AircraftStatus::select(false),
'subfleet_id' => $request->query('subfleet'),
@@ -143,6 +144,7 @@ class AircraftController extends Controller
return view('admin.aircraft.edit', [
'aircraft' => $aircraft,
'airports' => $this->airportRepo->selectBoxList(),
'hubs' => $this->airportRepo->selectBoxList(true, true),
'subfleets' => Subfleet::all()->pluck('name', 'id'),
'statuses' => AircraftStatus::select(false),
]);

View File

@@ -300,14 +300,18 @@ class FlightController extends Controller
public function export(Request $request)
{
$exporter = app(ExportService::class);
$flights = $this->flightRepo->all();
$where = [];
$file_name = 'flights.csv';
if ($request->input('airline_id')) {
$airline_id = $request->input('airline_id');
$where['airline_id'] = $airline_id;
$file_name = 'flights-'.$airline_id.'.csv';
}
$flights = $this->flightRepo->where($where)->get();
$path = $exporter->exportFlights($flights);
return response()
->download($path, 'flights.csv', [
'content-type' => 'text/csv',
])
->deleteFileAfterSend(true);
return response()->download($path, $file_name, ['content-type' => 'text/csv'])->deleteFileAfterSend(true);
}
/**

View File

@@ -17,6 +17,7 @@ use App\Repositories\AircraftRepository;
use App\Repositories\FareRepository;
use App\Repositories\RankRepository;
use App\Repositories\SubfleetRepository;
use App\Repositories\TypeRatingRepository;
use App\Services\ExportService;
use App\Services\FareService;
use App\Services\FleetService;
@@ -36,26 +37,29 @@ class SubfleetController extends Controller
private $importSvc;
private $rankRepo;
private $subfleetRepo;
private $typeratingRepo;
/**
* SubfleetController constructor.
*
* @param AircraftRepository $aircraftRepo
* @param FleetService $fleetSvc
* @param FareRepository $fareRepo
* @param FareService $fareSvc
* @param ImportService $importSvc
* @param RankRepository $rankRepo
* @param SubfleetRepository $subfleetRepo
* @param AircraftRepository $aircraftRepo
* @param FareRepository $fareRepo
* @param FareService $fareSvc
* @param FleetService $fleetSvc
* @param ImportService $importSvc
* @param RankRepository $rankRepo
* @param SubfleetRepository $subfleetRepo
* @param TypeRatingRepository $typeratingRepo
*/
public function __construct(
AircraftRepository $aircraftRepo,
FleetService $fleetSvc,
FareRepository $fareRepo,
FareService $fareSvc,
FleetService $fleetSvc,
ImportService $importSvc,
RankRepository $rankRepo,
SubfleetRepository $subfleetRepo
SubfleetRepository $subfleetRepo,
TypeRatingRepository $typeratingRepo
) {
$this->aircraftRepo = $aircraftRepo;
$this->fareRepo = $fareRepo;
@@ -64,48 +68,7 @@ class SubfleetController extends Controller
$this->importSvc = $importSvc;
$this->rankRepo = $rankRepo;
$this->subfleetRepo = $subfleetRepo;
}
/**
* Get the ranks that are available to the subfleet
*
* @param $subfleet
*
* @return array
*/
protected function getAvailRanks($subfleet)
{
$retval = [];
$all_ranks = $this->rankRepo->all();
$avail_ranks = $all_ranks->except($subfleet->ranks->modelKeys());
foreach ($avail_ranks as $rank) {
$retval[$rank->id] = $rank->name;
}
return $retval;
}
/**
* Get all the fares that haven't been assigned to a given subfleet
*
* @param mixed $subfleet
*
* @return array
*/
protected function getAvailFares($subfleet)
{
$retval = [];
$all_fares = $this->fareRepo->all();
$avail_fares = $all_fares->except($subfleet->fares->modelKeys());
foreach ($avail_fares as $fare) {
$retval[$fare->id] = $fare->name.
' (price: '.$fare->price.
', type: '.FareType::label($fare->type).
', cost: '.$fare->cost.
', capacity: '.$fare->capacity.')';
}
return $retval;
$this->typeratingRepo = $typeratingRepo;
}
/**
@@ -152,8 +115,8 @@ class SubfleetController extends Controller
{
$input = $request->all();
$subfleet = $this->subfleetRepo->create($input);
Flash::success('Subfleet saved successfully.');
return redirect(route('admin.subfleets.edit', [$subfleet->id]));
}
@@ -172,10 +135,12 @@ class SubfleetController extends Controller
if (empty($subfleet)) {
Flash::error('Subfleet not found');
return redirect(route('admin.subfleets.index'));
}
$avail_fares = $this->getAvailFares($subfleet);
return view('admin.subfleets.show', [
'subfleet' => $subfleet,
'avail_fares' => $avail_fares,
@@ -192,24 +157,27 @@ class SubfleetController extends Controller
public function edit($id)
{
$subfleet = $this->subfleetRepo
->with(['fares', 'ranks'])
->with(['fares', 'ranks', 'typeratings'])
->findWithoutFail($id);
if (empty($subfleet)) {
Flash::error('Subfleet not found');
return redirect(route('admin.subfleets.index'));
}
$avail_fares = $this->getAvailFares($subfleet);
$avail_ranks = $this->getAvailRanks($subfleet);
$avail_ratings = $this->getAvailTypeRatings($subfleet);
return view('admin.subfleets.edit', [
'airlines' => Airline::all()->pluck('name', 'id'),
'hubs' => Airport::where('hub', 1)->pluck('name', 'id'),
'fuel_types' => FuelType::labels(),
'avail_fares' => $avail_fares,
'avail_ranks' => $avail_ranks,
'subfleet' => $subfleet,
'airlines' => Airline::all()->pluck('name', 'id'),
'hubs' => Airport::where('hub', 1)->pluck('name', 'id'),
'fuel_types' => FuelType::labels(),
'avail_fares' => $avail_fares,
'avail_ranks' => $avail_ranks,
'avail_ratings' => $avail_ratings,
'subfleet' => $subfleet,
]);
}
@@ -229,12 +197,13 @@ class SubfleetController extends Controller
if (empty($subfleet)) {
Flash::error('Subfleet not found');
return redirect(route('admin.subfleets.index'));
}
$this->subfleetRepo->update($request->all(), $id);
Flash::success('Subfleet updated successfully.');
return redirect(route('admin.subfleets.index'));
}
@@ -251,6 +220,7 @@ class SubfleetController extends Controller
if (empty($subfleet)) {
Flash::error('Subfleet not found');
return redirect(route('admin.subfleets.index'));
}
@@ -259,12 +229,13 @@ class SubfleetController extends Controller
$aircraft = $this->aircraftRepo->findWhere(['subfleet_id' => $id], ['id']);
if ($aircraft->count() > 0) {
Flash::error('There are aircraft still assigned to this subfleet, you can\'t delete it!')->important();
return redirect(route('admin.subfleets.index'));
}
$this->subfleetRepo->delete($id);
Flash::success('Subfleet deleted successfully.');
return redirect(route('admin.subfleets.index'));
}
@@ -281,11 +252,8 @@ class SubfleetController extends Controller
$subfleets = $this->subfleetRepo->all();
$path = $exporter->exportSubfleets($subfleets);
return response()
->download($path, 'subfleets.csv', [
'content-type' => 'text/csv',
])
->deleteFileAfterSend(true);
return response()->download($path, 'subfleets.csv', ['content-type' => 'text/csv'])->deleteFileAfterSend(true);
}
/**
@@ -312,77 +280,64 @@ class SubfleetController extends Controller
}
/**
* @param Subfleet $subfleet
* Get all the fares that haven't been assigned to a given subfleet
*
* @return mixed
* @param mixed $subfleet
*
* @return array
*/
protected function return_ranks_view(?Subfleet $subfleet)
protected function getAvailFares($subfleet)
{
$subfleet->refresh();
$retval = [];
$all_fares = $this->fareRepo->all();
$avail_fares = $all_fares->except($subfleet->fares->modelKeys());
foreach ($avail_fares as $fare) {
$retval[$fare->id] = $fare->name.
' (price: '.$fare->price.
', type: '.FareType::label($fare->type).
', cost: '.$fare->cost.
', capacity: '.$fare->capacity.')';
}
$avail_ranks = $this->getAvailRanks($subfleet);
return view('admin.subfleets.ranks', [
'subfleet' => $subfleet,
'avail_ranks' => $avail_ranks,
]);
return $retval;
}
/**
* @param Subfleet $subfleet
* Get the ranks that are available to the subfleet
*
* @return mixed
* @param $subfleet
*
* @return array
*/
protected function return_fares_view(?Subfleet $subfleet)
protected function getAvailRanks($subfleet)
{
$subfleet->refresh();
$retval = [];
$all_ranks = $this->rankRepo->all();
$avail_ranks = $all_ranks->except($subfleet->ranks->modelKeys());
foreach ($avail_ranks as $rank) {
$retval[$rank->id] = $rank->name;
}
$avail_fares = $this->getAvailFares($subfleet);
return view('admin.subfleets.fares', [
'subfleet' => $subfleet,
'avail_fares' => $avail_fares,
]);
return $retval;
}
/**
* Operations for associating ranks to the subfleet
* Get the type ratings that are available to the subfleet
*
* @param $id
* @param Request $request
* @param $subfleet
*
* @return mixed
* @return array
*/
public function ranks($id, Request $request)
protected function getAvailTypeRatings($subfleet)
{
$subfleet = $this->subfleetRepo->findWithoutFail($id);
if (empty($subfleet)) {
return $this->return_ranks_view($subfleet);
$retval = [];
$all_ratings = $this->typeratingRepo->all();
$avail_ratings = $all_ratings->except($subfleet->typeratings->modelKeys());
foreach ($avail_ratings as $tr) {
$retval[$tr->id] = $tr->name.' ('.$tr->type.')';
}
if ($request->isMethod('get')) {
return $this->return_ranks_view($subfleet);
}
/*
* update specific rank data
*/
if ($request->isMethod('post')) {
$rank = $this->rankRepo->find($request->input('rank_id'));
$this->fleetSvc->addSubfleetToRank($subfleet, $rank);
} elseif ($request->isMethod('put')) {
$override = [];
$rank = $this->rankRepo->find($request->input('rank_id'));
$override[$request->name] = $request->value;
$this->fleetSvc->addSubfleetToRank($subfleet, $rank, $override);
} // dissassociate fare from teh aircraft
elseif ($request->isMethod('delete')) {
$rank = $this->rankRepo->find($request->input('rank_id'));
$this->fleetSvc->removeSubfleetFromRank($subfleet, $rank);
}
$subfleet->save();
return $this->return_ranks_view($subfleet);
return $retval;
}
/**
@@ -398,6 +353,54 @@ class SubfleetController extends Controller
]);
}
/**
* @param Subfleet $subfleet
*
* @return mixed
*/
protected function return_fares_view(?Subfleet $subfleet)
{
$subfleet->refresh();
$avail_fares = $this->getAvailFares($subfleet);
return view('admin.subfleets.fares', [
'subfleet' => $subfleet,
'avail_fares' => $avail_fares,
]);
}
/**
* @param Subfleet $subfleet
*
* @return mixed
*/
protected function return_ranks_view(?Subfleet $subfleet)
{
$subfleet->refresh();
$avail_ranks = $this->getAvailRanks($subfleet);
return view('admin.subfleets.ranks', [
'subfleet' => $subfleet,
'avail_ranks' => $avail_ranks,
]);
}
/**
* @param Subfleet $subfleet
*
* @return mixed
*/
protected function return_typeratings_view(?Subfleet $subfleet)
{
$subfleet->refresh();
$avail_ratings = $this->getAvailTypeRatings($subfleet);
return view('admin.subfleets.type_ratings', [
'subfleet' => $subfleet,
'avail_ratings' => $avail_ratings,
]);
}
/**
* Operations for associating ranks to the subfleet
*
@@ -479,4 +482,79 @@ class SubfleetController extends Controller
return $this->return_fares_view($subfleet);
}
/**
* Operations for associating ranks to the subfleet
*
* @param $id
* @param Request $request
*
* @return mixed
*/
public function ranks($id, Request $request)
{
$subfleet = $this->subfleetRepo->findWithoutFail($id);
if (empty($subfleet)) {
return $this->return_ranks_view($subfleet);
}
if ($request->isMethod('get')) {
return $this->return_ranks_view($subfleet);
}
// associate rank with the subfleet
if ($request->isMethod('post')) {
$rank = $this->rankRepo->find($request->input('rank_id'));
$this->fleetSvc->addSubfleetToRank($subfleet, $rank);
} // override definitions
elseif ($request->isMethod('put')) {
$override = [];
$rank = $this->rankRepo->find($request->input('rank_id'));
$override[$request->name] = $request->value;
$this->fleetSvc->addSubfleetToRank($subfleet, $rank, $override);
} // dissassociate rank from the subfleet
elseif ($request->isMethod('delete')) {
$rank = $this->rankRepo->find($request->input('rank_id'));
$this->fleetSvc->removeSubfleetFromRank($subfleet, $rank);
}
$subfleet->save();
return $this->return_ranks_view($subfleet);
}
/**
* Operations for associating type ratings to the subfleet
*
* @param $id
* @param Request $request
*
* @return mixed
*/
public function typeratings($id, Request $request)
{
$subfleet = $this->subfleetRepo->findWithoutFail($id);
if (empty($subfleet)) {
return $this->return_typeratings_view($subfleet);
}
if ($request->isMethod('get')) {
return $this->return_typeratings_view($subfleet);
}
// associate subfleet with type rating
if ($request->isMethod('post')) {
$typerating = $this->typeratingRepo->find($request->input('typerating_id'));
$this->fleetSvc->addSubfleetToTypeRating($subfleet, $typerating);
} // dissassociate subfleet from the type rating
elseif ($request->isMethod('delete')) {
$typerating = $this->typeratingRepo->find($request->input('typerating_id'));
$this->fleetSvc->removeSubfleetFromTypeRating($subfleet, $typerating);
}
$subfleet->save();
return $this->return_typeratings_view($subfleet);
}
}

View File

@@ -0,0 +1,168 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Contracts\Controller;
use App\Http\Requests\CreateTypeRatingRequest;
use App\Http\Requests\UpdateTypeRatingRequest;
use App\Repositories\SubfleetRepository;
use App\Repositories\TypeRatingRepository;
use App\Services\FleetService;
use Cache;
use Illuminate\Http\Request;
use Laracasts\Flash\Flash;
use Prettus\Repository\Criteria\RequestCriteria;
class TypeRatingController extends Controller
{
private $fleetSvc;
private $subfleetRepo;
private $typeratingRepo;
public function __construct(
FleetService $fleetSvc,
SubfleetRepository $subfleetRepo,
TypeRatingRepository $typeratingRepo
) {
$this->fleetSvc = $fleetSvc;
$this->subfleetRepo = $subfleetRepo;
$this->typeratingRepo = $typeratingRepo;
}
public function index(Request $request)
{
$this->typeratingRepo->pushCriteria(new RequestCriteria($request));
$typeratings = $this->typeratingRepo->all();
return view('admin.typeratings.index', [
'typeratings' => $typeratings,
]);
}
public function create()
{
return view('admin.typeratings.create');
}
public function store(CreateTypeRatingRequest $request)
{
$input = $request->all();
$model = $this->typeratingRepo->create($input);
Flash::success('Type Rating saved successfully.');
// Cache::forget(config('cache.keys.RANKS_PILOT_LIST.key'));
return redirect(route('admin.typeratings.edit', [$model->id]));
}
public function show($id)
{
$typerating = $this->typeratingRepo->findWithoutFail($id);
if (empty($typerating)) {
Flash::error('Type Rating not found');
return redirect(route('admin.typeratings.index'));
}
return view('admin.typeratings.show', [
'typerating' => $typerating,
]);
}
public function edit($id)
{
$typerating = $this->typeratingRepo->findWithoutFail($id);
if (empty($typerating)) {
Flash::error('Type Rating not found');
return redirect(route('admin.typeratings.index'));
}
$avail_subfleets = $this->getAvailSubfleets($typerating);
return view('admin.typeratings.edit', [
'typerating' => $typerating,
'avail_subfleets' => $avail_subfleets,
]);
}
public function update($id, UpdateTypeRatingRequest $request)
{
$typerating = $this->typeratingRepo->findWithoutFail($id);
if (empty($typerating)) {
Flash::error('Type Rating not found');
return redirect(route('admin.typeratings.index'));
}
$typerating = $this->typeratingRepo->update($request->all(), $id);
// Cache::forget(config('cache.keys.RANKS_PILOT_LIST.key'));
Flash::success('Type Rating updated successfully.');
return redirect(route('admin.typeratings.index'));
}
public function destroy($id)
{
$typerating = $this->typeratingRepo->findWithoutFail($id);
if (empty($typerating)) {
Flash::error('Type Rating not found');
return redirect(route('admin.typeratings.index'));
}
$this->typeratingRepo->delete($id);
Flash::success('Type Rating deleted successfully.');
return redirect(route('admin.typeratings.index'));
}
protected function getAvailSubfleets($typerating)
{
$retval = [];
$all_subfleets = $this->subfleetRepo->all();
$avail_subfleets = $all_subfleets->except($typerating->subfleets->modelKeys());
foreach ($avail_subfleets as $subfleet) {
$retval[$subfleet->id] = $subfleet->name.' (Airline: '.$subfleet->airline->code.')';
}
return $retval;
}
protected function return_subfleet_view($typerating)
{
$avail_subfleets = $this->getAvailSubfleets($typerating);
return view('admin.typeratings.subfleets', [
'typerating' => $typerating,
'avail_subfleets' => $avail_subfleets,
]);
}
public function subfleets($id, Request $request)
{
$typerating = $this->typeratingRepo->findWithoutFail($id);
if (empty($typerating)) {
Flash::error('Type Rating not found!');
return redirect(route('admin.typeratings.index'));
}
// add subfleet to type rating
if ($request->isMethod('post')) {
$subfleet = $this->subfleetRepo->find($request->input('subfleet_id'));
$this->fleetSvc->addSubfleetToTypeRating($subfleet, $typerating);
}
// remove subfleet from type rating
elseif ($request->isMethod('delete')) {
$subfleet = $this->subfleetRepo->find($request->input('subfleet_id'));
$this->fleetSvc->removeSubfleetFromTypeRating($subfleet, $typerating);
}
return $this->return_subfleet_view($typerating);
}
}

View File

@@ -12,6 +12,7 @@ use App\Repositories\AirlineRepository;
use App\Repositories\AirportRepository;
use App\Repositories\PirepRepository;
use App\Repositories\RoleRepository;
use App\Repositories\TypeRatingRepository;
use App\Repositories\UserRepository;
use App\Services\UserService;
use App\Support\Timezonelist;
@@ -30,24 +31,27 @@ class UserController extends Controller
private $airportRepo;
private $pirepRepo;
private $roleRepo;
private $typeratingRepo;
private $userRepo;
private $userSvc;
/**
* UserController constructor.
*
* @param AirlineRepository $airlineRepo
* @param AirportRepository $airportRepo
* @param PirepRepository $pirepRepo
* @param RoleRepository $roleRepo
* @param UserRepository $userRepo
* @param UserService $userSvc
* @param AirlineRepository $airlineRepo
* @param AirportRepository $airportRepo
* @param PirepRepository $pirepRepo
* @param RoleRepository $roleRepo
* @param TypeRatingRepository $typeratingRepo
* @param UserRepository $userRepo
* @param UserService $userSvc
*/
public function __construct(
AirlineRepository $airlineRepo,
AirportRepository $airportRepo,
PirepRepository $pirepRepo,
RoleRepository $roleRepo,
TypeRatingRepository $typeratingRepo,
UserRepository $userRepo,
UserService $userSvc
) {
@@ -55,6 +59,7 @@ class UserController extends Controller
$this->airportRepo = $airportRepo;
$this->pirepRepo = $pirepRepo;
$this->roleRepo = $roleRepo;
$this->typeratingRepo = $typeratingRepo;
$this->userSvc = $userSvc;
$this->userRepo = $userRepo;
}
@@ -149,7 +154,7 @@ class UserController extends Controller
public function edit($id)
{
$user = $this->userRepo
->with(['awards', 'fields', 'rank'])
->with(['awards', 'fields', 'rank', 'typeratings'])
->findWithoutFail($id);
if (empty($user)) {
@@ -169,17 +174,19 @@ class UserController extends Controller
$airlines = $this->airlineRepo->selectBoxList();
$airports = $this->airportRepo->selectBoxList(false);
$roles = $this->roleRepo->selectBoxList(false, true);
$avail_ratings = $this->getAvailTypeRatings($user);
return view('admin.users.edit', [
'user' => $user,
'pireps' => $pireps,
'country' => new ISO3166(),
'countries' => $countries,
'timezones' => Timezonelist::toArray(),
'airports' => $airports,
'airlines' => $airlines,
'ranks' => Rank::all()->pluck('name', 'id'),
'roles' => $roles,
'user' => $user,
'pireps' => $pireps,
'country' => new ISO3166(),
'countries' => $countries,
'timezones' => Timezonelist::toArray(),
'airports' => $airports,
'airlines' => $airlines,
'ranks' => Rank::all()->pluck('name', 'id'),
'roles' => $roles,
'avail_ratings' => $avail_ratings,
]);
}
@@ -260,6 +267,7 @@ class UserController extends Controller
$user = $this->userRepo->findWithoutFail($id);
if (empty($user)) {
Flash::error('User not found');
return redirect(route('admin.users.index'));
}
@@ -283,6 +291,7 @@ class UserController extends Controller
$userAward = UserAward::where(['user_id' => $id, 'award_id' => $award_id]);
if (empty($userAward)) {
Flash::error('The user award could not be found');
return redirect()->back();
}
@@ -311,4 +320,73 @@ class UserController extends Controller
return redirect(route('admin.users.edit', [$id]));
}
/**
* Get the type ratings that are available to the user
*
* @param $user
*
* @return array
*/
protected function getAvailTypeRatings($user)
{
$retval = [];
$all_ratings = $this->typeratingRepo->all();
$avail_ratings = $all_ratings->except($user->typeratings->modelKeys());
foreach ($avail_ratings as $tr) {
$retval[$tr->id] = $tr->name.' ('.$tr->type.')';
}
return $retval;
}
/**
* @param User $user
*
* @return mixed
*/
protected function return_typeratings_view(?User $user)
{
$user->refresh();
$avail_ratings = $this->getAvailTypeRatings($user);
return view('admin.users.type_ratings', [
'user' => $user,
'avail_ratings' => $avail_ratings,
]);
}
/**
* Operations for associating type ratings to the user
*
* @param $id
* @param Request $request
*
* @return mixed
*/
public function typeratings($id, Request $request)
{
$user = $this->userRepo->findWithoutFail($id);
if (empty($user)) {
return $this->return_typeratings_view($user);
}
if ($request->isMethod('get')) {
return $this->return_typeratings_view($user);
}
// associate user with type rating
if ($request->isMethod('post')) {
$typerating = $this->typeratingRepo->find($request->input('typerating_id'));
$this->userSvc->addUserToTypeRating($user, $typerating);
} // dissassociate user from the type rating
elseif ($request->isMethod('delete')) {
$typerating = $this->typeratingRepo->find($request->input('typerating_id'));
$this->userSvc->removeUserFromTypeRating($user, $typerating);
}
$user->save();
return $this->return_typeratings_view($user);
}
}

View File

@@ -148,10 +148,10 @@ class AcarsController extends Controller
$pirep = Pirep::find($id);
$this->checkCancelled($pirep);
Log::debug(
/*Log::debug(
'Posting ACARS update (user: '.Auth::user()->ident.', pirep id :'.$id.'): ',
$request->post()
);
);*/
$count = 0;
$positions = $request->post('positions');
@@ -223,7 +223,7 @@ class AcarsController extends Controller
$pirep = Pirep::find($id);
$this->checkCancelled($pirep);
Log::debug('Posting ACARS log, PIREP: '.$id, $request->post());
// Log::debug('Posting ACARS log, PIREP: '.$id, $request->post());
$count = 0;
$logs = $request->post('logs');

View File

@@ -25,6 +25,7 @@ use App\Models\Pirep;
use App\Models\PirepComment;
use App\Models\PirepFare;
use App\Models\PirepFieldValue;
use App\Models\User;
use App\Repositories\JournalRepository;
use App\Repositories\PirepRepository;
use App\Services\Finance\PirepFinanceService;
@@ -235,8 +236,12 @@ class PirepController extends Controller
Log::info('PIREP Update, user '.Auth::id());
Log::info($request->getContent());
/** @var User $user */
$user = Auth::user();
/** @var Pirep $pirep */
$pirep = Pirep::find($pirep_id);
$this->checkCancelled($pirep);
$attrs = $this->parsePirep($request);
@@ -278,6 +283,7 @@ class PirepController extends Controller
{
Log::info('PIREP file, user '.Auth::id(), $request->post());
/** @var User $user */
$user = Auth::user();
// Check if the status is cancelled...
@@ -332,7 +338,7 @@ class PirepController extends Controller
*/
public function cancel($pirep_id, Request $request)
{
Log::info('PIREP Cancel, user '.Auth::id(), $request->post());
Log::info('PIREP '.$pirep_id.' Cancel, user '.Auth::id(), $request->post());
$pirep = Pirep::find($pirep_id);
$this->pirepSvc->cancel($pirep);

View File

@@ -62,7 +62,7 @@ class RegisterController extends Controller
{
$airports = $this->airportRepo->selectBoxList(false, setting('pilots.home_hubs_only'));
$airlines = $this->airlineRepo->selectBoxList();
$userFields = UserField::where(['show_on_registration' => true])->get();
$userFields = UserField::where(['show_on_registration' => true, 'active' => true])->get();
return view('auth.register', [
'airports' => $airports,
@@ -138,7 +138,7 @@ class RegisterController extends Controller
Log::info('User registered: ', $user->toArray());
$userFields = UserField::all();
$userFields = UserField::where(['show_on_registration' => true, 'active' => true])->get();
foreach ($userFields as $field) {
$field_name = 'field_'.$field->slug;
UserFieldValue::updateOrCreate([

View File

@@ -91,8 +91,12 @@ class PirepController extends Controller
*/
public function aircraftList($add_blank = false)
{
$user = Auth::user();
$user_loc = filled($user->curr_airport_id) ? $user->curr_airport_id : $user->home_airport_id;
$location_check = setting('pireps.only_aircraft_at_dpt_airport', false);
$aircraft = [];
$subfleets = $this->userSvc->getAllowableSubfleets(Auth::user());
$subfleets = $this->userSvc->getAllowableSubfleets($user);
if ($add_blank) {
$aircraft[''] = '';
@@ -100,7 +104,9 @@ class PirepController extends Controller
foreach ($subfleets as $subfleet) {
$tmp = [];
foreach ($subfleet->aircraft as $ac) {
foreach ($subfleet->aircraft->when($location_check, function ($query) use ($user_loc) {
return $query->where('airport_id', $user_loc);
}) as $ac) {
$tmp[$ac->id] = $ac['name'].' - '.$ac['registration'];
}

View File

@@ -80,7 +80,7 @@ class ProfileController extends Controller
public function show($id)
{
/** @var \App\Models\User $user */
$with = ['airline', 'awards', 'current_airport', 'fields.field', 'home_airport', 'last_pirep', 'rank'];
$with = ['airline', 'awards', 'current_airport', 'fields.field', 'home_airport', 'last_pirep', 'rank', 'typeratings'];
$user = User::with($with)->where('id', $id)->first();
if (empty($user)) {

View File

@@ -80,46 +80,39 @@ class SimBriefController
// No aircraft selected, show selection form
if (!$aircraft_id) {
// If no subfleets defined for flight get them from user
if ($flight->subfleets->count() > 0) {
$subfleets = $flight->subfleets;
} else {
$subfleets = $this->userSvc->getAllowableSubfleets($user);
}
// Build an array of subfleet id's from the subfleets collection
$sf_ids = $subfleets->map(function ($subfleets) {
return collect($subfleets->toArray())
->only(['id'])
->all();
});
// Get user's allowed subfleets and intersect it with flight subfleets
// so we will have a proper list which the user is allowed to fly
$user_subfleets = $this->userSvc->getAllowableSubfleets($user)->pluck('id')->toArray();
$flight_subfleets = $flight->subfleets->pluck('id')->toArray();
// Now we can build a proper aircrafts collection
// Contents will be either members of flight->subfleets
// or members of user's allowable subfleets
$aircrafts = Aircraft::whereIn('subfleet_id', $sf_ids)
->where('state', AircraftState::PARKED)
->where('status', AircraftStatus::ACTIVE)
->orderby('icao')
->orderby('registration')
->get();
$subfleet_ids = filled($flight_subfleets) ? array_intersect($user_subfleets, $flight_subfleets) : $user_subfleets;
// Prepare variables for single aircraft query
$where = [];
$where['state'] = AircraftState::PARKED;
$where['status'] = AircraftStatus::ACTIVE;
if (setting('pireps.only_aircraft_at_dpt_airport')) {
$aircrafts = $aircrafts->where('airport_id', $flight->dpt_airport_id);
$where['airport_id'] = $flight->dpt_airport_id;
}
if (setting('simbrief.block_aircraft')) {
// Build a list of aircraft_id's being used for active sb packs
$sb_aircraft = SimBrief::whereNotNull('flight_id')->pluck('aircraft_id');
$withCount = ['simbriefs' => function ($query) {
$query->whereNull('pirep_id');
}];
// Filter aircraft list to non used/blocked ones
$aircrafts = $aircrafts->whereNotIn('id', $sb_aircraft);
}
// Build proper aircraft collection considering all possible settings
// Flight subfleets, user subfleet restrictions, pirep restrictions, simbrief blocking etc
$aircraft = Aircraft::withCount($withCount)->where($where)
->when(setting('simbrief.block_aircraft'), function ($query) {
return $query->having('simbriefs_count', 0);
})->whereIn('subfleet_id', $subfleet_ids)
->orderby('icao')->orderby('registration')
->get();
return view('flights.simbrief_aircraft', [
'flight' => $flight,
'aircrafts' => $aircrafts,
'subfleets' => $subfleets,
'aircrafts' => $aircraft,
]);
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Requests;
use App\Contracts\FormRequest;
use App\Models\Typerating;
class CreateTypeRatingRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return Typerating::$rules;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Requests;
use App\Contracts\FormRequest;
use App\Models\Typerating;
class UpdateTypeRatingRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return Typerating::$rules;
}
}

View File

@@ -60,7 +60,7 @@ class AwardHandler extends Listener
*/
public function checkForAwards($user)
{
$awards = Award::all();
$awards = Award::where('active', 1)->get();
foreach ($awards as $award) {
$klass = $award->getReference($award, $user);
if ($klass) {

View File

@@ -13,6 +13,7 @@ use Znck\Eloquent\Traits\BelongsToThrough;
* @property int id
* @property mixed subfleet_id
* @property string airport_id The apt where the aircraft is
* @property string hub_id The apt where the aircraft is based
* @property string ident
* @property string name
* @property string icao
@@ -22,6 +23,7 @@ use Znck\Eloquent\Traits\BelongsToThrough;
* @property float zfw
* @property string hex_code
* @property Airport airport
* @property Airport hub
* @property Subfleet subfleet
* @property int status
* @property int state
@@ -39,6 +41,7 @@ class Aircraft extends Model
protected $fillable = [
'subfleet_id',
'airport_id',
'hub_id',
'iata',
'icao',
'name',
@@ -127,6 +130,11 @@ class Aircraft extends Model
return $this->belongsTo(Airport::class, 'airport_id');
}
public function hub()
{
return $this->hasOne(Airport::class, 'id', 'hub_id');
}
public function pireps()
{
return $this->hasMany(Pirep::class, 'aircraft_id');

View File

@@ -21,6 +21,7 @@ class Award extends Model
'image_url',
'ref_model',
'ref_model_params',
'active',
];
public static $rules = [
@@ -29,6 +30,7 @@ class Award extends Model
'image_url' => 'nullable',
'ref_model' => 'required',
'ref_model_params' => 'nullable',
'active' => 'nullable',
];
/**

View File

@@ -107,4 +107,9 @@ class Subfleet extends Model
return $this->belongsToMany(Rank::class, 'subfleet_rank')
->withPivot('acars_pay', 'manual_pay');
}
public function typeratings()
{
return $this->belongsToMany(Typerating::class, 'typerating_subfleet', 'subfleet_id', 'typerating_id');
}
}

37
app/Models/Typerating.php Normal file
View File

@@ -0,0 +1,37 @@
<?php
namespace App\Models;
use App\Contracts\Model;
class Typerating extends Model
{
public $table = 'typeratings';
protected $fillable = [
'name',
'type',
'description',
'image_url',
'active',
];
// Validation
public static $rules = [
'name' => 'required',
'type' => 'required',
'description' => 'nullable',
'image_url' => 'nullable',
];
// Relationships
public function subfleets()
{
return $this->belongsToMany(Subfleet::class, 'typerating_subfleet', 'typerating_id', 'subfleet_id');
}
public function users()
{
return $this->belongsToMany(User::class, 'typerating_user', 'typerating_id', 'user_id');
}
}

View File

@@ -7,6 +7,7 @@ use App\Models\Traits\JournalTrait;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laratrust\Traits\LaratrustUserTrait;
use Staudenmeir\EloquentHasManyDeep\HasRelationships;
/**
* @property int id
@@ -42,6 +43,7 @@ use Laratrust\Traits\LaratrustUserTrait;
* @property UserFieldValue[] fields
* @property Role[] roles
* @property Subfleet[] subfleets
* @property TypeRating[] typeratings
*
* @mixin \Illuminate\Database\Eloquent\Builder
* @mixin \Illuminate\Notifications\Notifiable
@@ -52,6 +54,7 @@ class User extends Authenticatable
use JournalTrait;
use LaratrustUserTrait;
use Notifiable;
use HasRelationships;
public $table = 'users';
@@ -191,8 +194,7 @@ class User extends Authenticatable
{
$default = config('gravatar.default');
$uri = config('gravatar.url')
.md5(strtolower(trim($this->email))).'?d='.urlencode($default);
$uri = config('gravatar.url').md5(strtolower(trim($this->email))).'?d='.urlencode($default);
if ($size !== null) {
$uri .= '&s='.$size;
@@ -265,4 +267,14 @@ class User extends Authenticatable
{
return $this->belongsTo(Rank::class, 'rank_id');
}
public function typeratings()
{
return $this->belongsToMany(Typerating::class, 'typerating_user', 'user_id', 'typerating_id');
}
public function rated_subfleets()
{
return $this->hasManyDeep(Subfleet::class, ['typerating_user', Typerating::class, 'typerating_subfleet']);
}
}

View File

@@ -376,6 +376,21 @@ class RouteServiceProvider extends ServiceProvider
], 'settings', 'SettingsController@update')
->name('settings.update')->middleware('ability:admin,settings');
// Type Ratings
Route::resource('typeratings', 'TypeRatingController')->middleware('ability:admin,typeratings');
Route::match([
'get',
'post',
'put',
'delete',
], 'typeratings/{id}/subfleets', 'TypeRatingController@subfleets')->middleware('ability:admin,typeratings');
Route::match([
'get',
'post',
'put',
'delete',
], 'typeratings/{id}/users', 'TypeRatingController@users')->middleware('ability:admin,typeratings');
// maintenance
Route::match(['get'], 'maintenance', 'MaintenanceController@index')
->name('maintenance.index')->middleware('ability:admin,maintenance');
@@ -426,6 +441,13 @@ class RouteServiceProvider extends ServiceProvider
'delete',
], 'subfleets/{id}/ranks', 'SubfleetController@ranks')->middleware('ability:admin,fleet');
Route::match([
'get',
'post',
'put',
'delete',
], 'subfleets/{id}/typeratings', 'SubfleetController@typeratings')->middleware('ability:admin,fleet');
Route::resource('subfleets', 'SubfleetController')->middleware('ability:admin,fleet');
/**
@@ -439,6 +461,13 @@ class RouteServiceProvider extends ServiceProvider
Route::resource('users', 'UserController')->middleware('ability:admin,users');
Route::match([
'get',
'post',
'put',
'delete',
], 'users/{id}/typeratings', 'UserController@typeratings')->middleware('ability:admin,users');
// defaults
Route::get('', ['uses' => 'DashboardController@index'])
->middleware('update_pending', 'ability:admin,admin-access');

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Repositories;
use App\Contracts\Repository;
use App\Models\Typerating;
use Prettus\Repository\Contracts\CacheableInterface;
use Prettus\Repository\Traits\CacheableRepository;
class TypeRatingRepository extends Repository implements CacheableInterface
{
use CacheableRepository;
protected $fieldSearchable = [
'name' => 'like',
'type' => 'like',
];
public function model()
{
return Typerating::class;
}
public function selectBoxList($add_blank = false, $only_active = true): array
{
$retval = [];
$where = [
'active' => $only_active,
];
$items = $this->findWhere($where);
if ($add_blank) {
$retval[''] = '';
}
foreach ($items as $i) {
$retval[$i->id] = $i->name;
}
return $retval;
}
}

View File

@@ -6,6 +6,7 @@ use App\Contracts\Service;
use App\Models\Flight;
use App\Models\Rank;
use App\Models\Subfleet;
use App\Models\Typerating;
class FleetService extends Service
{
@@ -33,7 +34,36 @@ class FleetService extends Service
public function removeSubfleetFromRank(Subfleet $subfleet, Rank $rank)
{
$subfleet->ranks()->detach($rank->id);
$subfleet->save();
$subfleet->refresh();
return $subfleet;
}
/**
* Add the subfleet to a type rating
*
* @param Subfleet $subfleet
* @param Typerating $typerating
*/
public function addSubfleetToTypeRating(Subfleet $subfleet, Typerating $typerating)
{
$subfleet->typeratings()->syncWithoutDetaching([$typerating->id]);
$subfleet->save();
$subfleet->refresh();
return $subfleet;
}
/**
* Remove the subfleet from a type rating
*
* @param Subfleet $subfleet
* @param Typerating $typerating
*/
public function removeSubfleetFromTypeRating(Subfleet $subfleet, Typerating $typerating)
{
$subfleet->typeratings()->detach($typerating->id);
$subfleet->save();
$subfleet->refresh();

View File

@@ -25,6 +25,7 @@ class AircraftImporter extends ImportExport
'subfleet' => 'required',
'iata' => 'nullable',
'icao' => 'nullable',
'hub_id' => 'nullable',
'airport_id' => 'nullable',
'name' => 'required',
'registration' => 'required',
@@ -53,7 +54,7 @@ class AircraftImporter extends ImportExport
}
/**
* Import a flight, parse out the different rows
* Import an aircraft, parse out the different rows
*
* @param array $row
* @param int $index

View File

@@ -253,8 +253,9 @@ class FlightImporter extends ImportExport
$fare_attributes = [];
}
$fare = Fare::updateOrCreate(['code' => $fare_code], ['name' => $fare_code]);
$fare = Fare::firstOrCreate(['code' => $fare_code], ['name' => $fare_code]);
$this->fareSvc->setForFlight($flight, $fare, $fare_attributes);
$fare->save();
}
}

View File

@@ -84,8 +84,9 @@ class SubfleetImporter extends ImportExport
$fare_attributes = [];
}
$fare = Fare::updateOrCreate(['code' => $fare_code], ['name' => $fare_code]);
$fare = Fare::firstOrCreate(['code' => $fare_code], ['name' => $fare_code]);
$this->fareSvc->setForSubfleet($subfleet, $fare, $fare_attributes);
$fare->save();
}
}
}

View File

@@ -15,6 +15,7 @@ use App\Models\Enums\UserState;
use App\Models\Pirep;
use App\Models\Rank;
use App\Models\Role;
use App\Models\Typerating;
use App\Models\User;
use App\Models\UserFieldValue;
use App\Repositories\AircraftRepository;
@@ -343,7 +344,7 @@ class UserService extends Service
/**
* Return the subfleets this user is allowed access to,
* based on their current rank
* based on their current Rank and/or by Type Rating
*
* @param $user
*
@@ -351,14 +352,31 @@ class UserService extends Service
*/
public function getAllowableSubfleets($user)
{
if ($user === null || setting('pireps.restrict_aircraft_to_rank') === false) {
/** @var Collection $subfleets */
$subfleets = $this->subfleetRepo->with('aircraft')->all();
$restrict_rank = setting('pireps.restrict_aircraft_to_rank', true);
$restrict_type = setting('pireps.restrict_aircraft_to_typerating', false);
$restricted_to = [];
if ($user) {
$rank_sf_array = $restrict_rank ? $user->rank->subfleets()->pluck('id')->toArray() : [];
$type_sf_array = $restrict_type ? $user->rated_subfleets->pluck('id')->toArray() : [];
if ($restrict_rank && !$restrict_type) {
$restricted_to = $rank_sf_array;
} elseif (!$restrict_rank && $restrict_type) {
$restricted_to = $type_sf_array;
} elseif ($restrict_rank && $restrict_type) {
$restricted_to = array_intersect($rank_sf_array, $type_sf_array);
}
} else {
/** @var Collection $subfleets */
$subfleets = $user->rank->subfleets()->with('aircraft')->get();
$restrict_rank = false;
$restrict_type = false;
}
// @var Collection $subfleets
$subfleets = $this->subfleetRepo->when(($restrict_rank || $restrict_type), function ($query) use ($restricted_to) {
return $query->whereIn('id', $restricted_to);
})->with('aircraft')->get();
// Map the subfleets with the proper fare information
return $subfleets->transform(function ($sf, $key) {
$sf->fares = $this->fareSvc->getForSubfleet($sf);
@@ -398,9 +416,7 @@ class UserService extends Service
return $user;
}
Log::info('User '.$user->ident.' state changing from '
.UserState::label($old_state).' to '
.UserState::label($user->state));
Log::info('User '.$user->ident.' state changing from '.UserState::label($old_state).' to '.UserState::label($user->state));
event(new UserStateChanged($user, $old_state));
@@ -561,11 +577,39 @@ class UserService extends Service
// Recalc the rank
$this->calculatePilotRank($user);
Log::info('User '.$user->ident.' updated; pirep count='.$pirep_count
.', rank='.$user->rank->name
.', flight_time='.$user->flight_time.' minutes');
Log::info('User '.$user->ident.' updated; pirep count='.$pirep_count.', rank='.$user->rank->name.', flight_time='.$user->flight_time.' minutes');
$user->save();
return $user;
}
/**
* Attach a type rating to the user
*
* @param User $user
* @param Typerating $typerating
*/
public function addUserToTypeRating(User $user, Typerating $typerating)
{
$user->typeratings()->syncWithoutDetaching([$typerating->id]);
$user->save();
$user->refresh();
return $user;
}
/**
* Detach a type rating from the user
*
* @param User $user
* @param Typerating $typerating
*/
public function removeUserFromTypeRating(User $user, Typerating $typerating)
{
$user->typeratings()->detach($typerating->id);
$user->save();
$user->refresh();
return $user;
}
}

View File

@@ -134,6 +134,11 @@ class Database
->where($id_col, $row[$id_col])
->update($row);
} else {
// Remove ID column if it exists and its empty, let the DB set it
/*if (array_key_exists($id_col, $row) && empty($row[$id_col])) {
unset($row[$id_col]);
}*/
DB::table($table)->insert($row);
}
} catch (QueryException $e) {

View File

@@ -39,7 +39,7 @@ class application extends Illuminate\Foundation\Application
$bootstrappers = array_replace(
$bootstrappers,
array_fill_keys(
array_keys($bootstrappers, $find),
array_keys($bootstrappers, $find, true),
$replace
)
);

View File

@@ -20,7 +20,12 @@
"symfony/polyfill-mbstring": "*",
"symfony/polyfill-php74": "*",
"symfony/polyfill-php80": "*",
"composer/composer": "~1.0",
"symfony/polyfill-php81": "*",
"symfony/event-dispatcher-contracts": "^2",
"symfony/property-info": "^5.4",
"symfony/string": "^5.4",
"psr/container": "1.1.1",
"composer/composer": "~2.0",
"composer/installers": "~1.0",
"laravel/framework": "~8.0",
"anhskohbo/no-captcha": "^3.0",
@@ -42,7 +47,7 @@
"laravelcollective/html": "~6.2.0",
"jeremykendall/php-domain-parser": "~5.7.2",
"league/commonmark": "~1.6",
"league/csv": "9.2.*",
"league/csv": "9.7.*",
"league/geotools": "0.8.*",
"league/iso3166": "^3.0.0",
"markrogoyski/math-php": "^1.10",
@@ -70,14 +75,15 @@
"queueworker/sansdaemon": "^1.2",
"jpkleemans/attribute-events": "^1.1",
"akaunting/laravel-money": "^1.2",
"staudenmeir/belongs-to-through": "^2.5"
"staudenmeir/belongs-to-through": "^2.5",
"staudenmeir/eloquent-has-many-deep": "1.14.3"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.5",
"barryvdh/laravel-ide-helper": "^2.9",
"barryvdh/laravel-ide-helper": "^2.10",
"bpocallaghan/generators": "~7.0",
"filp/whoops": "~2.0",
"friendsofphp/php-cs-fixer": "^2.16",
"friendsofphp/php-cs-fixer": "^3.3",
"mockery/mockery": "^1.4.0",
"nunomaduro/collision": "^5.3.0",
"phpunit/phpunit": "~9.0",
@@ -105,7 +111,10 @@
"installer-paths": {
"modules/{$name}/": ["type:phpvms-module"]
},
"module-dir": "modules"
"module-dir": "modules",
"symfony": {
"require": "^5.4"
}
},
"scripts": {
"pre-package-uninstall": [

1274
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
version: '3'
services:
app:
image: phpvms:latest
image: nabeelio/phpvms
environment:
DB_HOST: mysql
REDIS_HOST: redis

View File

@@ -5,7 +5,7 @@
&nbsp;Subfleet and Status
</h6>
<div class="form-container-body row">
<div class="form-group col-sm-4">
<div class="form-group col-sm-3">
{{ Form::label('subfleet_id', 'Subfleet:') }}
{{ Form::select('subfleet_id', $subfleets, $subfleet_id ?? null, [
'class' => 'form-control select2',
@@ -15,17 +15,21 @@
<p class="text-danger">{{ $errors->first('subfleet_id') }}</p>
</div>
<div class="form-group col-sm-4">
<div class="form-group col-sm-3">
{{ Form::label('status', 'Status:') }}
{{ Form::select('status', $statuses, null, ['class' => 'form-control select2', 'placeholder' => 'Select Status']) }}
<p class="text-danger">{{ $errors->first('subfleet_id') }}</p>
</div>
<div class="form-group col-sm-4">
<div class="form-group col-sm-3">
{{ Form::label('hub_id', 'Hub:') }}
{{ Form::select('hub_id', $hubs, null, ['class' => 'form-control select2']) }}
<p class="text-danger">{{ $errors->first('hub_id') }}</p>
</div>
<div class="form-group col-sm-3">
{{ Form::label('airport_id', 'Location:') }}
{{ Form::select('airport_id', $airports, null, [
'class' => 'form-control select2'
]) }}
{{ Form::select('airport_id', $airports, null, ['class' => 'form-control select2']) }}
<p class="text-danger">{{ $errors->first('airport_id') }}</p>
</div>
</div>

View File

@@ -3,6 +3,7 @@
<th>Name</th>
<th style="text-align: center;">Registration</th>
<th>Subfleet</th>
<th style="text-align: center;">Hub</th>
<th style="text-align: center;">Location</th>
<th style="text-align: center;">Hours</th>
<th style="text-align: center;">Active</th>
@@ -22,6 +23,7 @@
-
@endif
</td>
<td style="text-align: center;">{{ $ac->hub_id }}</td>
<td style="text-align: center;">{{ $ac->airport_id }}</td>
<td style="text-align: center;">
@minutestotime($ac->flight_time)

View File

@@ -1,5 +1,5 @@
@extends('admin.app')
@section('title', "Edit \"$award->title\" Award")
@section('title', "Edit \"$award->name\" Award")
@section('content')
<div class="card border-blue-bottom">
<div class="content">

View File

@@ -18,17 +18,13 @@
<p class="text-danger">{{ $errors->first('name') }}</p>
</div>
<div class="form-group col-sm-6">
{!! Form::label('image', 'Image:') !!}
<div class="callout callout-info">
<i class="icon fa fa-info">&nbsp;&nbsp;</i>
This is the image of the award. Be creative!
</div>
{!! Form::text('image_url', null, [
'class' => 'form-control',
'placeholder' => 'Enter the url of the image location'
]) !!}
{!! Form::text('image_url', null, ['class' => 'form-control', 'placeholder' => 'Enter the url of the image location']) !!}
<p class="text-danger">{{ $errors->first('image_url') }}</p>
</div>
</div>
@@ -47,29 +43,31 @@
<div class="form-group col-sm-6">
<div>
{{ Form::label('ref_model', 'Award Class:') }}
{{ Form::select('ref_model', $award_classes, null , [
'class' => 'form-control select2',
'id' => 'award_class_select',
]) }}
{{ Form::select('ref_model', $award_classes, null, ['class' => 'form-control select2', 'id' => 'award_class_select']) }}
<p class="text-danger">{{ $errors->first('ref_model') }}</p>
</div>
<div>
{{ Form::label('ref_model_params', 'Award Class parameters') }}
{{ Form::text('ref_model_params', null, ['class' => 'form-control']) }}
<p class="text-danger">{{ $errors->first('ref_model_params') }}</p>
<p id="ref_model_param_description">
</p>
<p id="ref_model_param_description"></p>
</div>
</div>
</div>
<div class="row">
<!-- Submit Field -->
<div class="form-group col-sm-12">
{{-- Active/Deactive Checkbox --}}
<div class="form-group col-sm-6 text-left">
<div class="checkbox">
<label class="checkbox-inline">
{{ Form::label('active', 'Active: ') }}
{{ Form::hidden('active', false) }}
{{ Form::checkbox('active') }}
</label>
</div>
</div>
{{-- Form Actions --}}
<div class="form-group col-sm-6">
<div class="pull-right">
{!! Form::button('Save', ['type' => 'submit', 'class' => 'btn btn-success']) !!}
<a href="{!! route('admin.awards.index') !!}" class="btn btn-warn">Cancel</a>

View File

@@ -1,40 +1,47 @@
<table class="table table-hover table-responsive" id="awards-table">
<thead>
<th>Name</th>
<th>Description</th>
<th>Image</th>
<th class="text-right">Action</th>
<th>Name</th>
<th>Description</th>
<th>Image</th>
<th class="text-center">Active</th>
<th class="text-right">Action</th>
</thead>
<tbody>
@foreach($awards as $award)
<tr>
<td>
<a href="{{ route('admin.awards.edit', [$award->id]) }}">
{{ $award->name }}</a>
</td>
<td>{{ $award->description }}</td>
<td>
@if($award->image_url)
<img src="{{ $award->image_url }}" name="{{ $award->name }}" alt="No Image Available" style="height: 100px"/>
@else
-
@endif
</td>
<td class="text-right">
{{ Form::open(['route' => ['admin.awards.destroy', $award->id], 'method' => 'delete']) }}
<a href="{{ route('admin.awards.edit', [$award->id]) }}" class='btn btn-sm btn-success btn-icon'>
<i class="fas fa-pencil-alt"></i></a>
{{ Form::button('<i class="fa fa-times"></i>', [
'type' => 'submit',
'class' => 'btn btn-sm btn-danger btn-icon',
'onclick' => "return confirm('Are you sure you want to delete this award?')"
]) }}
{{ Form::close() }}
</td>
</tr>
@endforeach
@foreach($awards->sortby('name', SORT_NATURAL) as $award)
<tr>
<td>
<a href="{{ route('admin.awards.edit', [$award->id]) }}">{{ $award->name }}</a>
</td>
<td>
{{ $award->description }}
</td>
<td>
@if($award->image_url)
<img src="{{ $award->image_url }}" name="{{ $award->name }}" alt="No Image Available" style="height: 100px"/>
@else
-
@endif
</td>
<td class="text-center">
@if($award->active)
<i class="fas fa-check-circle fa-2x text-success"></i>
@else
<i class="fas fa-times-circle fa-2x text-danger"></i>
@endif
</td>
<td class="text-right">
{{ Form::open(['route' => ['admin.awards.destroy', $award->id], 'method' => 'delete']) }}
<a href="{{ route('admin.awards.edit', [$award->id]) }}" class='btn btn-sm btn-success btn-icon'>
<i class="fas fa-pencil-alt"></i>
</a>
{{ Form::button('<i class="fa fa-times"></i>', [
'type' => 'submit',
'class' => 'btn btn-sm btn-danger btn-icon',
'onclick' => "return confirm('Are you sure you want to delete this award?')"
]) }}
{{ Form::close() }}
</td>
</tr>
@endforeach
</tbody>
</table>
</table>

View File

@@ -2,8 +2,15 @@
@section('title', 'Flights')
@section('actions')
<li><a href="{{ route('admin.flights.export') }}"><i class="ti-plus"></i>Export to CSV</a></li>
<li><a href="{{ route('admin.flights.import') }}"><i class="ti-plus"></i>Import from CSV</a></li>
<li>
<a href="{{ route('admin.flights.export') }}@if(request()->get('airline_id')){{ '?airline_id='.request()->get('airline_id') }}@endif">
<i class="ti-plus"></i>
Export to CSV @if(request()->get('airline_id')) (Selected Airline) @endif
</a>
</li>
<li>
<a href="{{ route('admin.flights.import') }}"><i class="ti-plus"></i>Import from CSV</a>
</li>
<li>
<a href="{{ route('admin.flights.create') }}">
<i class="ti-plus"></i>

View File

@@ -62,6 +62,10 @@
<li><a href="{{ url('/admin/ranks') }}"><i class="pe-7s-graph1"></i>ranks</a></li>
@endability
@ability('admin', 'typeratings')
<li><a href="{{ url('/admin/typeratings') }}"><i class="pe-7s-plane"></i>type ratings</a></li>
@endability
@ability('admin', 'awards')
<li><a href="{!! url('/admin/awards') !!}"><i class="pe-7s-diamond"></i>awards</a></li>
@endability

View File

@@ -16,6 +16,12 @@
</div>
</div>
<div class="card border-blue-bottom">
<div class="content">
@include('admin.subfleets.type_ratings')
</div>
</div>
<div class="card border-blue-bottom">
<div class="content">
@include('admin.subfleets.fares')

View File

@@ -114,6 +114,12 @@
$.pjax.submit(event, '#subfleet_ranks_wrapper', {push: false});
});
$(document).on('submit', 'form.modify_typerating', function (event) {
event.preventDefault();
console.log(event);
$.pjax.submit(event, '#subfleet_typeratings_wrapper', {push: false});
});
$(document).on('submit', 'form.modify_expense', function (event) {
event.preventDefault();
console.log(event);

View File

@@ -4,6 +4,7 @@
<th>Name</th>
<th>Airline</th>
<th>Type</th>
<th>Hub</th>
<th>Aircraft</th>
<th></th>
</thead>
@@ -17,6 +18,7 @@
</td>
<td>{{ optional($subfleet->airline)->name }}</td>
<td>{{ $subfleet->type }}</td>
<td>{{ $subfleet->hub_id }}</td>
<td>{{ $subfleet->aircraft->count() }}</td>
<td class="text-right">
{{ Form::open(['route' => ['admin.subfleets.destroy', $subfleet->id], 'method' => 'delete']) }}

View File

@@ -0,0 +1,45 @@
<div id="subfleet_typeratings_wrapper" class="dataTables_wrapper form-inline dt-bootstrap">
<div class="header">
<h3>type ratings</h3>
@component('admin.components.info')
These type ratings are allowed to fly aircraft in this subfleet.
@endcomponent
</div>
<br/>
<table id="subfleet_ranks" class="table table-hover">
@if(count($subfleet->typeratings))
<thead>
<tr>
<th>Type</th>
<th>Name</th>
<th></th>
</tr>
</thead>
@endif
<tbody>
@foreach($subfleet->typeratings as $tr)
<tr>
<td class="sorting_1">{{ $tr->type }}</td>
<td>{{ $tr->name }}</td>
<td style="text-align: right; width:3%;">
{{ Form::open(['url' => '/admin/subfleets/'.$subfleet->id.'/typeratings', 'method' => 'delete', 'class' => 'modify_typerating']) }}
{{ Form::hidden('typerating_id', $tr->id) }}
{{ Form::button('<i class="fa fa-times"></i>', ['type' => 'submit', 'class' => 'btn btn-sm btn-danger btn-icon']) }}
{{ Form::close() }}
</td>
</tr>
@endforeach
</tbody>
</table>
<hr/>
<div class="row">
<div class="col-xs-12">
<div class="text-right">
{{ Form::open(['url' => '/admin/subfleets/'.$subfleet->id.'/typeratings', 'method' => 'post', 'class' => 'modify_typerating form-inline']) }}
{{ Form::select('typerating_id', $avail_ratings, null, ['placeholder' => 'Select Type Rating', 'class' => 'ac-fare-dropdown form-control input-lg select2']) }}
{{ Form::button('<i class="glyphicon glyphicon-plus"></i> add', ['type' => 'submit', 'class' => 'btn btn-success btn-s']) }}
{{ Form::close() }}
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,12 @@
@extends('admin.app')
@section('title', 'Add Type Rating')
@section('content')
<div class="card border-blue-bottom">
<div class="content">
{{ Form::open(['route' => 'admin.typeratings.store', 'class' => 'add_typerating', 'method'=>'POST', 'autocomplete' => false]) }}
@include('admin.typeratings.fields')
{{ Form::close() }}
</div>
</div>
@endsection
@include('admin.typeratings.scripts')

View File

@@ -0,0 +1,26 @@
@extends('admin.app')
@section('title', 'Edit '.$typerating->name)
@section('content')
<div class="card border-blue-bottom">
<div class="content">
{{ Form::model($typerating, ['route' => ['admin.typeratings.update', $typerating->id], 'method' => 'patch', 'autocomplete' => false]) }}
@include('admin.typeratings.fields')
{{ Form::close() }}
</div>
</div>
<div class="card border-blue-bottom">
<div class="content">
<div class="header">
<h3>Subfleets</h3>
@component('admin.components.info')
These are the subfleets this type rating is allowed to use.
@endcomponent
</div>
<div class="row">
@include('admin.typeratings.subfleets')
</div>
</div>
</div>
@endsection
@include('admin.typeratings.scripts')

View File

@@ -0,0 +1,42 @@
<div class="row">
<div class="form-group col-sm-7">
{{ Form::label('name', 'Name:') }}
{{ Form::text('name', null, ['class' => 'form-control']) }}
<p class="text-danger">{{ $errors->first('name') }}</p>
</div>
<div class="form-group col-sm-5">
{{ Form::label('type', 'Type Code:') }}
{{ Form::text('type', null, ['class' => 'form-control']) }}
<p class="text-danger">{{ $errors->first('type') }}</p>
</div>
</div>
<div class="row">
<div class="form-group col-sm-7">
{{ Form::label('description', 'Description:') }}
{{ Form::text('description', null, ['class' => 'form-control']) }}
<p class="text-danger">{{ $errors->first('description') }}</p>
</div>
<div class="form-group col-sm-5">
{{ Form::label('image_url', 'Image Link:') }}
{{ Form::text('image_url', null, ['class' => 'form-control']) }}
<p class="text-danger">{{ $errors->first('image_url') }}</p>
</div>
</div>
<div class="row">
<div class="form-group col-sm-3">
<div class="checkbox">
<label class="checkbox-inline">
{{ Form::label('active', 'Active:') }}
{{ Form::hidden('active', false) }}
{{ Form::checkbox('active') }}
</label>
</div>
</div>
<div class="col-sm-9">
<div class="text-right">
{{ Form::button('Save', ['type' => 'submit', 'class' => 'btn btn-success']) }}
</div>
</div>
</div>

View File

@@ -0,0 +1,18 @@
@extends('admin.app')
@section('title', 'Type Ratings')
@section('actions')
<li>
<a href="{{ route('admin.typeratings.create') }}">
<i class="ti-plus"></i>
Add New
</a>
</li>
@endsection
@section('content')
<div class="card border-blue-bottom">
<div class="content">
@include('admin.typeratings.table')
</div>
</div>
@endsection

View File

@@ -0,0 +1,48 @@
@section('scripts')
<script>
function setEditable() {
const token = $('meta[name="csrf-token"]').attr('content');
const api_key = $('meta[name="api-key"]').attr('content');
@if(isset($typerating))
$('#subfleets-table a').editable({
type: 'text',
mode: 'inline',
emptytext: 'inherited',
url: '{{ url('/admin/typerating/'.$typerating->id.'/subfleets') }}',
title: 'Enter override value',
ajaxOptions: {
type: 'put',
headers: {
'x-api-key': api_key,
'X-CSRF-TOKEN': token,
}
},
params: function (params) {
return {
subfleet_id: params.pk,
name: params.name,
value: params.value
}
}
});
@endif
}
$(document).ready(function () {
setEditable();
$(document).on('submit', 'form.pjax_form', function (event) {
event.preventDefault();
$.pjax.submit(event, '#typerating_subfleet_wrapper', {push: false});
});
$(document).on('pjax:complete', function () {
initPlugins();
setEditable();
});
});
</script>
@endsection

View File

@@ -0,0 +1,17 @@
@extends('admin.app')
@section('content')
<section class="content-header">
<h1>{{ $typerating->name }}</h1>
</section>
<div class="content">
<div class="box box-primary">
<div class="box-body">
<div class="row" style="padding-left: 20px">
@include('admin.typerating.show_fields')
</div>
</div>
</div>
</div>
@endsection
@include('admin.typerating.scripts')

View File

@@ -0,0 +1,42 @@
<!-- Id Field -->
<div class="form-group">
{{ Form::label('id', 'Id:') }}
<p>{{ $typerating->id }}</p>
</div>
<!-- Name Field -->
<div class="form-group">
{{ Form::label('name', 'Name:') }}
<p>{{ $typerating->name }}</p>
</div>
<!-- Type Code Field -->
<div class="form-group">
{{ Form::label('type', 'Type Code:') }}
<p>{{ $typerating->type }}</p>
</div>
<!-- Description Field -->
<div class="form-group">
{{ Form::label('description', 'Description:') }}
<p>{{ $typerating->description }}</p>
</div>
<!-- Image URL Field -->
<div class="form-group">
{{ Form::label('image_url', 'Image URL:') }}
<p>{{ $typerating->image_url }}</p>
</div>
<!-- Created At Field -->
<div class="form-group">
{{ Form::label('created_at', 'Created At:') }}
<p>{{ show_datetime($typerating->created_at) }}</p>
</div>
<!-- Updated At Field -->
<div class="form-group">
{{ Form::label('updated_at', 'Updated At:') }}
<p>{{ show_datetime($typerating->updated_at) }}</p>
</div>

View File

@@ -0,0 +1,54 @@
<div id="typerating_subfleet_wrapper" class="dataTables_wrapper form-inline dt-bootstrap col-lg-12">
@if(count($typerating->subfleets) === 0)
@include('admin.common.none_added', ['type' => 'subfleets'])
@endif
<table class="table table-responsive" id="subfleets-table">
@if(count($typerating->subfleets))
<thead>
<th>Airline</th>
<th>Name</th>
<th style="text-align: center;">Actions</th>
</thead>
@endif
<tbody>
@foreach($typerating->subfleets as $sf)
<tr>
<td>{{ $sf->airline->name }}</td>
<td>{{ $sf->name.' ('.$sf->type.')' }}</td>
<td style="width: 10%; text-align: center;" class="form-inline">
{{ Form::open(['url' => '/admin/typeratings/'.$typerating->id.'/subfleets', 'method' => 'delete', 'class' => 'pjax_form']) }}
{{ Form::hidden('subfleet_id', $sf->id) }}
<div class='btn-group'>
{{ Form::button('<i class="fa fa-times"></i>',
['type' => 'submit',
'class' => 'btn btn-sm btn-danger btn-icon'])
}}
</div>
{{ Form::close() }}
</td>
</tr>
@endforeach
</tbody>
</table>
<hr/>
<div class="row">
<div class="col-lg-12">
<div class="input-group input-group-lg pull-right">
{{ Form::open(['url' => url('/admin/typeratings/'.$typerating->id.'/subfleets'),
'method' => 'post',
'class' => 'pjax_form form-inline'
])
}}
{{ Form::select('subfleet_id', $avail_subfleets, null, [
'placeholder' => 'Select Subfleet',
'class' => 'select2 form-control input-lg'])
}}
{{ Form::button('<i class="fa fa-plus"></i> Add',
['type' => 'submit',
'class' => 'btn btn-success btn-small']) }}
{{ Form::close() }}
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,26 @@
<div id="typeratings_table_wrapper">
<table class="table table-hover table-responsive">
<thead>
<th>Type Code</th>
<th>Name</th>
<th>Description</th>
<th></th>
</thead>
<tbody>
@foreach($typeratings as $typerating)
<tr>
<td><a href="{{ route('admin.typeratings.edit', [$typerating->id]) }}">{{ $typerating->type }}</a></td>
<td>{{ $typerating->name }}</td>
<td>{{ $typerating->description }}</td>
<td class="text-right">
{{ Form::open(['route' => ['admin.typeratings.destroy', $typerating->id], 'method' => 'delete']) }}
<a href="{{ route('admin.typeratings.edit', [$typerating->id]) }}" class='btn btn-sm btn-success btn-icon'>
<i class="fas fa-pencil-alt"></i></a>
{{ Form::button('<i class="fa fa-times"></i>', ['type' => 'submit', 'class' => 'btn btn-sm btn-danger btn-icon', 'onclick' => "return confirm('Are you sure?')"]) }}
{{ Form::close() }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>

View File

@@ -9,6 +9,15 @@
</div>
</div>
<div class="card border-blue-bottom">
<div class="content">
<div class="header">
<h3>Type Ratings</h3>
</div>
@include('admin.users.type_ratings')
</div>
</div>
<div class="card border-blue-bottom">
<div class="content">
<div class="header">
@@ -34,3 +43,4 @@
</div>
</div>
@endsection
@include('admin.users.script')

View File

@@ -0,0 +1,25 @@
@section('scripts')
<script>
function setEditable() {
const token = $('meta[name="csrf-token"]').attr('content');
const api_key = $('meta[name="api-key"]').attr('content');
}
$(document).ready(function () {
setEditable();
$(document).on('submit', 'form.modify_typerating', function (event) {
event.preventDefault();
console.log(event);
$.pjax.submit(event, '#user_typeratings_wrapper', {push: false});
});
$(document).on('pjax:complete', function () {
initPlugins();
setEditable();
});
});
</script>
@endsection

View File

@@ -0,0 +1,38 @@
<div id="user_typeratings_wrapper" class="dataTables_wrapper form-inline dt-bootstrap">
<table id="user_typeratings" class="table table-hover">
@if(count($user->typeratings))
<thead>
<tr>
<th>Type</th>
<th>Name</th>
<th></th>
</tr>
</thead>
@endif
<tbody>
@foreach($user->typeratings as $tr)
<tr>
<td class="sorting_1">{{ $tr->type }}</td>
<td>{{ $tr->name }}</td>
<td style="text-align: right; width:3%;">
{{ Form::open(['url' => '/admin/users/'.$user->id.'/typeratings', 'method' => 'delete', 'class' => 'modify_typerating']) }}
{{ Form::hidden('typerating_id', $tr->id) }}
{{ Form::button('<i class="fa fa-times"></i>', ['type' => 'submit', 'class' => 'btn btn-sm btn-danger btn-icon']) }}
{{ Form::close() }}
</td>
</tr>
@endforeach
</tbody>
</table>
<hr/>
<div class="row">
<div class="col-xs-12">
<div class="text-right">
{{ Form::open(['url' => '/admin/users/'.$user->id.'/typeratings', 'method' => 'post', 'class' => 'modify_typerating form-inline']) }}
{{ Form::select('typerating_id', $avail_ratings, null, ['placeholder' => 'Select Type Rating', 'class' => 'ac-fare-dropdown form-control input-lg select2']) }}
{{ Form::button('<i class="glyphicon glyphicon-plus"></i> add', ['type' => 'submit', 'class' => 'btn btn-success btn-s']) }}
{{ Form::close() }}
</div>
</div>
</div>
</div>

View File

@@ -114,6 +114,7 @@ and being mindful of the rivets bindings
center: ['{{ $center[0] }}', '{{ $center[1] }}'],
zoom: '{{ $zoom }}',
aircraft_icon: '{!! public_asset('/assets/img/acars/aircraft.png') !!}',
refresh_interval: {{ setting('acars.update_interval', 60) }},
units: '{{ setting('units.distance') }}',
leafletOptions: {
scrollWheelZoom: false,

View File

@@ -14,7 +14,7 @@ https://api.checkwx.com/#metar-decoded
<td>@lang('widgets.weather.wind')</td>
<td>
@if($metar['wind_speed'] < '1') Calm @else {{ $metar['wind_speed'] }} kts @lang('common.from') {{ $metar['wind_direction_label'] }} ({{ $metar['wind_direction']}}&deg;) @endif
@if($metar['wind_gust_speed']) @lang('widgets.weather.guststo') {{ $metar['wind_gust_speed'] }} @endif
@if($metar['wind_gust_speed']) @lang('widgets.weather.guststo') {{ $metar['wind_gust_speed'] }} @endif
</td>
</tr>
@if($metar['visibility'])
@@ -24,58 +24,62 @@ https://api.checkwx.com/#metar-decoded
</tr>
@endif
@if($metar['runways_visual_range'])
<tr>
<td>Runway Visual Range</td>
<td>
@foreach($metar['runways_visual_range'] as $rvr)
<b>RWY{{ $rvr['runway'] }}</b>; {{ $rvr['report'] }}<br>
@endforeach
</td>
</tr>
@endif
@if($metar['present_weather_report'] <> 'Dry')
<tr>
<td>Phenomena</td>
<td>{{ $metar['present_weather_report'] }}</td>
</tr>
@endif
<tr>
<td>Runway Visual Range</td>
<td>
@foreach($metar['runways_visual_range'] as $rvr)
<b>RWY{{ $rvr['runway'] }}</b>; {{ $rvr['report'] }}<br>
@endforeach
</td>
</tr>
@endif
@if($metar['present_weather_report'] && $metar['present_weather_report'] <> 'Dry')
<tr>
<td>Phenomena</td>
<td>{{ $metar['present_weather_report'] }}</td>
</tr>
@endif
@if($metar['clouds'] || $metar['cavok'])
<tr>
<td>@lang('widgets.weather.clouds')</td>
<td>
@if($unit_alt === 'ft') {{ $metar['clouds_report_ft'] }} @else {{ $metar['clouds_report'] }} @endif
@if($metar['cavok'] == 1) Ceiling and Visibility OK @endif
</td>
</tr>
@endif
<tr>
<td>@lang('widgets.weather.temp')</td>
<td>
@if($metar['temperature'][$unit_temp]) {{ $metar['temperature'][$unit_temp] }} @else 0 @endif &deg;{{strtoupper($unit_temp)}}
@if($metar['dew_point']), @lang('widgets.weather.dewpoint') @if($metar['dew_point'][$unit_temp]) {{ $metar['dew_point'][$unit_temp] }} @else 0 @endif &deg;{{strtoupper($unit_temp)}} @endif
@if($metar['humidity']), @lang('widgets.weather.humidity') {{ $metar['humidity'] }}% @endif
</td>
</tr>
<tr>
<td>@lang('widgets.weather.barometer')</td>
<td>{{ number_format($metar['barometer']['hPa']) }} hPa / {{ number_format($metar['barometer']['inHg'], 2) }} inHg</td>
</tr>
<tr>
<td>@lang('widgets.weather.clouds')</td>
<td>
@if($unit_alt === 'ft') {{ $metar['clouds_report_ft'] }} @else {{ $metar['clouds_report'] }} @endif
@if($metar['cavok'] == 1) Ceiling and Visibility OK @endif
</td>
</tr>
@endif
@if($metar['temperature'])
<tr>
<td>@lang('widgets.weather.temp')</td>
<td>
@if($metar['temperature'][$unit_temp]) {{ $metar['temperature'][$unit_temp] }} @else 0 @endif &deg;{{strtoupper($unit_temp)}}
@if($metar['dew_point']), @lang('widgets.weather.dewpoint') @if($metar['dew_point'][$unit_temp]) {{ $metar['dew_point'][$unit_temp] }} @else 0 @endif &deg;{{strtoupper($unit_temp)}} @endif
@if($metar['humidity']), @lang('widgets.weather.humidity') {{ $metar['humidity'] }}% @endif
</td>
</tr>
@endif
@if($metar['barometer'])
<tr>
<td>@lang('widgets.weather.barometer')</td>
<td>{{ number_format($metar['barometer']['hPa']) }} hPa / {{ number_format($metar['barometer']['inHg'], 2) }} inHg</td>
</tr>
@endif
@if($metar['recent_weather_report'])
<tr>
<td>Recent Phenomena</td>
<td>{{ $metar['recent_weather_report'] }}</td>
</tr>
@endif
@if($metar['runways_report'])
<tr>
<td>Runway Condition</td>
<td>
@foreach($metar['runways_report'] as $runway)
<b>RWY{{ $runway['runway'] }}</b>; {{ $runway['report'] }}<br>
@endforeach
</td>
</tr>
@endif
<tr>
<td>Recent Phenomena</td>
<td>{{ $metar['recent_weather_report'] }}</td>
</tr>
@endif
@if($metar['runways_report'])
<tr>
<td>Runway Condition</td>
<td>
@foreach($metar['runways_report'] as $runway)
<b>RWY{{ $runway['runway'] }}</b>; {{ $runway['report'] }}<br>
@endforeach
</td>
</tr>
@endif
@if($metar['remarks'])
<tr>
<td>@lang('widgets.weather.remarks')</td>

View File

@@ -38,7 +38,7 @@ class VersionTest extends TestCase
$this->assertTrue(
$versionSvc->isGreaterThan($newVersion, $currentVersion),
"{$newVersion} not greater than ${currentVersion}"
"$newVersion not greater than $currentVersion"
);
}
}

View File

@@ -1,3 +1,3 @@
subfleet,iata, icao,airport_id, name,registration,hex_code,mtow,zfw,status
A32X,A320,320,,A320-211,N309US,,,,S
74X,747 400, ,,,
subfleet,iata,icao,hub_id,airport_id,name,registration,hex_code,mtow,zfw,status
A32X,A320,320,,,A320-211,N309US,,,,S
74X,747 400,, ,,,
1 subfleet,iata, icao,airport_id, name,registration,hex_code,mtow,zfw,status subfleet,iata,icao,hub_id,airport_id,name,registration,hex_code,mtow,zfw,status
2 A32X,A320,320,,A320-211,N309US,,,,S A32X,A320,320,,,A320-211,N309US,,,,S
3 74X,747 400, ,,, 74X,747 400,, ,,,

View File

@@ -1,3 +1,3 @@
subfleet,iata, icao,airport_id, name,registration,hex_code,mtow,zfw,status
A32X,A320,320,,A320-211,N309US,,,,A
74X,747 400,, ,,
subfleet,iata,icao,hub_id,airport_id,name,registration,hex_code,mtow,zfw,status
A32X,A320,320,,,A320-211,N309US,,,,A
74X,747 400,,, ,,
1 subfleet,iata, icao,airport_id, name,registration,hex_code,mtow,zfw,status subfleet,iata,icao,hub_id,airport_id,name,registration,hex_code,mtow,zfw,status
2 A32X,A320,320,,A320-211,N309US,,,,A A32X,A320,320,,,A320-211,N309US,,,,A
3 74X,747 400,, ,, 74X,747 400,,, ,,

View File

@@ -1,3 +1,3 @@
subfleet,iata, icao,airport_id, name,registration,hex_code,mtow,zfw,status
A32X,A320-211,,N309US,,,
, B744-GE,, N304,,,
subfleet,iata,icao,hub_id,airport_id,name,registration,hex_code,mtow,zfw,status
A32X,A320-211,,,N309US,,,
, B744-GE,,, N304,,,
1 subfleet,iata, icao,airport_id, name,registration,hex_code,mtow,zfw,status subfleet,iata,icao,hub_id,airport_id,name,registration,hex_code,mtow,zfw,status
2 A32X,A320-211,,N309US,,, A32X,A320-211,,,N309US,,,
3 , B744-GE,, N304,,, , B744-GE,,, N304,,,