Compare commits

..

33 Commits

Author SHA1 Message Date
nabeelio
db34d0e3e7 Add test for METAR string 2021-03-15 09:14:39 -04:00
Nabeel S
fffbab7201 Merge branch 'dev' into patch-2 2021-03-15 08:30:29 -04:00
exciler
b9e7a2efc9 Add unit conversion handling for block_fuel like for fuel_used (#1076)
* Add unit conversion handling for block_fuel like for fuel_used, add further checks in PIREPTest->testUnitFields

* Remove accidentially added "use"

Co-authored-by: Andreas Palm <ap@ewsp.de>
2021-03-15 08:30:14 -04:00
B.Fatih KOZ
76a2a16fa6 Merge branch 'dev' into patch-2 2021-03-14 03:54:53 +03:00
Nabeel Shahzad
73f88fce0c Fix permissions with modules 2021-03-13 18:31:09 -05:00
B.Fatih KOZ
cfbd1c901a Merge branch 'dev' into patch-2 2021-03-10 04:53:25 +03:00
Nabeel S
e70ee5aa6f Add web cron ability #821 (#1073)
* Use database for kvp storage

* Read kvpstore

* Add web cron ability through API #821

* Style fixes

* Fix text
2021-03-09 11:36:56 -05:00
B.Fatih KOZ
617813bdbf Fix Metar Decoding / Wind check for Wind Chill
PR aims to fix the bug #1071 by checking both the wind speed and it's value for starting Wind Chill calculation.
2021-03-09 16:06:39 +03:00
nabeelio
f1c54bcc7c Add Postmark package #1067 2021-03-08 11:35:11 -05:00
nabeelio
d94d754961 Block user if they're not active #1066 2021-03-08 11:24:09 -05:00
nabeelio
9abfbd6c8c Remove unneeded import #1066 2021-03-08 11:21:55 -05:00
nabeelio
97fc1dd43d Don't block API logins #1066 2021-03-08 11:19:31 -05:00
Nabeel Shahzad
edc37f40fa Show either create/view simbrief button, not both 2021-03-06 16:48:50 -05:00
Nabeel S
950c7788be Restrict Simbrief to user who generated it (#1064)
* Restrict simbrief to user

* Style fixes

* Add tests

* Style fix
2021-03-06 13:49:42 -05:00
nabeelio
b3af50ac5a Add ignored solution provider 2021-03-05 08:00:34 -05:00
nabeelio
8a9b68d9ec Disable ignition runnable solutions 2021-03-05 07:59:50 -05:00
nabeelio
70a4cf9f90 Remove the laravel-backup library 2021-03-05 07:33:42 -05:00
Nabeel Shahzad
c3465851b2 Rename backups folder 2021-03-05 06:24:23 -05:00
Nabeel Shahzad
b13c4df338 laravel-backup changes 2021-03-05 06:00:36 -05:00
Nabeel Shahzad
c736a9b639 Disable encryption by default for backups 2021-03-05 05:44:04 -05:00
nabeelio
d2e1004e08 Undo composer downgrades 2021-03-04 17:17:39 -05:00
Nabeel S
3800c01d94 Update to Laravel 8 (#1058)
* Update Laravel and other dependencies

* Composer version in CI

* Remove the PHP exit from env file

* Add PHP 8 to testing matrix

* Update doctrine

* Update doctrine

* Update faker lib

* Rewrite TLD check to remove deprecated library

* Update version lib

* Remove PHP 8 for now

* Style fixes
2021-03-04 17:08:51 -05:00
Nabeel S
922e754c9e Check for valid reference object in recurring finance (#1056) 2021-03-02 16:19:54 -05:00
Nabeel S
936cd2efd3 Implement PIREP deletion #1014 (#1055)
* Implement PIREP deletion #1014

* Style fixes

* Add delete button to PIREP listing page

* Remove extra import
2021-03-02 15:43:34 -05:00
Nabeel S
e22d6d5996 Refactoring Simbrief fares and aircraft (#1054)
* Refactoring simbrief fares and aircraft

* Hide user full name/email

* Sort PIREP fields desc; fix cargo counts

* Change the sorting

* Extra logs

* Fix tests

* Return fare information through the API

* Style fixes

* Test fix

* Another test fix

* More fixes

* Set aircraft and fares in prefile

* Formatting
2021-03-02 12:29:04 -05:00
B.Fatih KOZ
efcb7e8895 Fix Advanced Fuel Calculations (#1053)
Due to an error on the settings check advanced fuel calculations was not working and defaulting to fuel used always.
2021-03-01 15:03:27 -05:00
Nabeel S
43fbe9e943 Update composer dependencies (#1047) 2021-02-26 12:23:50 -05:00
nabeelio
8fc0aec800 Set cache driver to array for tests 2021-02-25 13:50:37 -05:00
Nabeel Shahzad
bb84fa82a4 Default to file storage for sesions 2021-02-24 13:37:46 -05:00
B.Fatih KOZ
4fe323a763 Advanced Fuel Calculations (#1044)
* Advanced Fuel Calculations

PR aims to have realistic fuel debit calculations as in the real ops. When enabled, remaining fuel amounts from previous flight will be reduced from block fuel and airline will only pay for uplifted fuel amount.

If onboard fuel is enough for the flight or exceeds the required amount no fuel debit will be issued.

If this is the first flight of the aircraft or the tanks are dry, full block fuel will be treated as uplifted.

Disabled / uses current default (fuel_used will be debited)

Aircraft table also will hold the fuel_onboard value (not needed for calculations, just saving for displaying purposes)

* Style Fix

* Another Style Fix

Love StyleCi

* Fix Settings

Moved new setting under other pirep settings

* Fix Setting Check

* Fix EOF settings.yml

* Fix Settings
2021-02-24 12:22:52 -05:00
B.Fatih KOZ
e3b4a0ed2e SimBrief integration enhancements (#1045)
* SimBrief Integration Update

* Added SimBrief Type field to subfleets, can be used to assign simbrief airframes to subfleets and fix non existing or wrong types. If not used simbrief form will use aircraft's icao type code

* Added Passenger and Baggage weights to settings

* Added setting for using PhpVms Pilot/User  Ident as simbrief atc callsign

* SimBrief form code cleaned up a bit and improved. Now form supports both cargo and passenger fares to be used at the same time.

Generated passenger baggage weight will be reduced from aircraft's cargo capacity and remaining amount will be used for random cargo generation.

Also multiple cargo fares or any mix is possible now (like only cargo, only passenger, multiple cargo and passenger fares)

* StyleFix (SimBrief Controller)

* Fix Callsign Setting Check

* Code Cleanup

Reduced loops and removed if's in loops, getting fares from aircraft instead of flight/subfleets.

No need to go through getReconciledFaresForFlight anymore. Aircraft provides all fare info we need.

Removed unnecessary html elements, added some comments.

* Update Simbrief Controller

Fixed setting checks.

Removed non used $subfleet and from main form and $aircraft from aircraft selection form blade.

Added/fixed comments.

* StyleFix for Controller
2021-02-24 09:32:29 -05:00
Nabeel Shahzad
a4c431d39f Set missing value from default 2021-02-23 11:25:06 -05:00
Nabeel Shahzad
a52e0410a7 Force default to the value when syncing settings 2021-02-23 11:23:40 -05:00
77 changed files with 18243 additions and 3218 deletions

View File

@@ -1,7 +1,3 @@
<?php
exit();
?>
APP_ENV="dev"
APP_KEY="base64:zdgcDqu9PM8uGWCtMxd74ZqdGJIrnw812oRMmwDF6KY="
APP_URL="http://localhost"

View File

@@ -71,7 +71,6 @@ jobs:
php --version
mysql --version
# Downgrade composer version to 1.x
composer self-update --1
composer install --dev --no-interaction --verbose
cp .github/scripts/env.php env.php
cp .github/scripts/phpunit.xml phpunit.xml
@@ -119,7 +118,6 @@ jobs:
run: |
rm -rf vendor
sudo npm i tar-to-zip -g
composer self-update --1
composer install --no-dev --prefer-dist --no-interaction --verbose
sudo chmod +x ./.github/scripts/*
@@ -183,7 +181,6 @@ jobs:
run: |
rm -rf vendor
sudo npm i tar-to-zip -g
composer self-update --1
composer install --no-dev --prefer-dist --no-interaction --verbose
sudo chmod +x ./.github/scripts/*

View File

@@ -36,7 +36,7 @@ A full development environment can be brought up using Docker:
```bash
composer install
yarn install
npm install
docker-compose build
docker-compose up
```

View File

@@ -13,7 +13,11 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
* Define the application's command schedule. How this works... according to the command
* time, an event gets send out with the appropriate time (e.g, hourly sends an hourly event)
*
* Then the CronServiceProvider has the list of cronjobs which then run according to the events
* and then calls those at the proper times.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
*
@@ -27,10 +31,13 @@ class Kernel extends ConsoleKernel
$schedule->command(Hourly::class)->hourly();
// When spatie-backups runs
$schedule->command('backup:clean')->daily()->at('01:00');
$schedule->command('backup:run')->daily()->at('02:00');
/*if (config('backup.backup.enabled', false) === true) {
$schedule->command('backup:clean')->daily()->at('01:00');
$schedule->command('backup:run')->daily()->at('02:00');
}*/
// Update the last time the cron was run
/** @var CronService $cronSvc */
$cronSvc = app(CronService::class);
$cronSvc->updateLastRunTime();
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Cron\Hourly;
use App\Contracts\Listener;
use App\Events\CronHourly;
use App\Models\Enums\PirepState;
use App\Models\Pirep;
use App\Services\PirepService;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
/**
* Remove cancelled/deleted PIREPs. Look for PIREPs that were created before the setting time
* (e.g, 12 hours ago) and are marked with the
*/
class DeletePireps extends Listener
{
/**
* Remove expired bids
*
* @param CronHourly $event
*
* @throws \Exception
*/
public function handle(CronHourly $event): void
{
$this->deletePireps(setting('pireps.delete_rejected_hours'), PirepState::REJECTED);
$this->deletePireps(setting('pireps.delete_cancelled_hours'), PirepState::CANCELLED);
}
/**
* Look for and delete PIREPs which match the criteria
*
* @param int $expire_time_hours The time in hours to look for PIREPs
* @param int $state The PirepState enum value
*/
protected function deletePireps(int $expire_time_hours, int $state)
{
$dt = Carbon::now()->subHours($expire_time_hours);
$pireps = Pirep::whereDate('created_at', '<', $dt)->where(['state' => $state])->get();
/** @var PirepService $pirepSvc */
$pirepSvc = app(PirepService::class);
/** @var Pirep $pirep */
foreach ($pireps as $pirep) {
Log::info('Cron: Deleting PIREP id='.$pirep->id.', state='.PirepState::label($state));
$pirepSvc->delete($pirep);
}
}
}

View File

@@ -6,6 +6,7 @@ use App\Contracts\Listener;
use App\Events\CronMonthly;
use App\Models\Enums\ExpenseType;
use App\Services\Finance\RecurringFinanceService;
use Illuminate\Support\Facades\Log;
/**
* Go through and apply any finances that are daily
@@ -35,6 +36,7 @@ class ApplyExpenses extends Listener
*/
public function handle(CronMonthly $event): void
{
Log::info('Monthly: Applying monthly expenses');
$this->financeSvc->processExpenses(ExpenseType::MONTHLY);
}
}

View File

@@ -6,6 +6,7 @@ use App\Contracts\Listener;
use App\Events\CronNightly;
use App\Models\Enums\ExpenseType;
use App\Services\Finance\RecurringFinanceService;
use Illuminate\Support\Facades\Log;
/**
* Go through and apply any finances that are daily
@@ -35,6 +36,7 @@ class ApplyExpenses extends Listener
*/
public function handle(CronNightly $event): void
{
Log::info('Nightly: Applying daily expenses');
$this->financeSvc->processExpenses(ExpenseType::DAILY);
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Cron\Nightly;
use App\Contracts\Listener;
use App\Events\CronNightly;
use App\Services\SimBriefService;
use Illuminate\Support\Facades\Log;
/**
* Clear any expired SimBrief flight briefs that aren't attached to a PIREP
@@ -23,6 +24,7 @@ class ClearExpiredSimbrief extends Listener
*/
public function handle(CronNightly $event): void
{
Log::info('Nightly: Removing expired Simbrief entries');
$this->simbriefSvc->removeExpiredEntries();
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Cron\Nightly;
use App\Contracts\Listener;
use App\Events\CronNightly;
use App\Services\VersionService;
use Illuminate\Support\Facades\Log;
/**
* Determine if any pilots should be set to ON LEAVE status
@@ -28,6 +29,7 @@ class NewVersionCheck extends Listener
*/
public function handle(CronNightly $event): void
{
Log::info('Nightly: Checking for new version');
$this->versionSvc->isNewVersionAvailable();
}
}

View File

@@ -34,7 +34,10 @@ class PilotLeave extends Listener
*/
public function handle(CronNightly $event): void
{
Log::info('Cron: Running pilot leave check');
$users = $this->userSvc->findUsersOnLeave();
Log::info('Found '.count($users).' users on leave');
foreach ($users as $user) {
Log::info('Setting user '.$user->ident.' to ON LEAVE status');
$this->userSvc->setStatusOnLeave($user);

View File

@@ -35,7 +35,7 @@ class RecalculateBalances extends Listener
*/
public function handle(CronNightly $event): void
{
Log::info('Recalculating balances');
Log::info('Nightly: Recalculating balances');
$journals = Journal::all();
foreach ($journals as $journal) {

View File

@@ -32,10 +32,10 @@ class RecalculateStats extends Listener
*/
public function handle(CronNightly $event): void
{
Log::info('Recalculating user stats');
Log::info('Nightly: Recalculating user stats');
$this->userSvc->recalculateAllUserStats();
Log::info('Recalcuating aircraft status');
Log::info('Nightly: Recalcuating aircraft status');
$this->aircraftSvc->recalculateStats();
}
}

View File

@@ -19,6 +19,8 @@ class SetActiveFlights extends Listener
*/
public function handle(CronNightly $event): void
{
Log::info('Nightly: Setting active flights');
$this->checkFlights();
}

View File

@@ -0,0 +1,21 @@
<?php
use App\Contracts\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Add a `fuel_onboard` column for recording what is left in tanks
*/
class AircraftAddFuelonboard extends Migration
{
public function up()
{
Schema::table('aircraft', function (Blueprint $table) {
$table->unsignedDecimal('fuel_onboard')
->nullable()
->default(0.0)
->after('zfw');
});
}
}

View File

@@ -0,0 +1,20 @@
<?php
use App\Contracts\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Add a SimBrief Type to subfleet
*/
class AddSbtypeToSubfleets extends Migration
{
public function up()
{
Schema::table('subfleets', function (Blueprint $table) {
$table->string('simbrief_type', 20)
->nullable()
->after('type');
});
}
}

View File

@@ -0,0 +1,24 @@
<?php
use App\Contracts\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Add a hub to the subfleet is
*/
class AddAircraftToSimbrief extends Migration
{
public function up()
{
Schema::table('simbrief', function (Blueprint $table) {
$table->unsignedInteger('aircraft_id')
->nullable()
->after('pirep_id');
// Temp column to hold the calculated fare data for the API
// Remove this once the prefile to acars feature is completed
$table->mediumText('fare_data')->nullable()->after('ofp_xml');
});
}
}

View File

@@ -0,0 +1,19 @@
<?php
use App\Contracts\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Add a hub to the subfleet is
*/
class AddKvpTable extends Migration
{
public function up()
{
Schema::create('kvp', function (Blueprint $table) {
$table->string('key')->index();
$table->string('value');
});
}
}

View File

@@ -119,8 +119,8 @@ aircraft:
-
id: 1
subfleet_id: 1
icao: null
iata: null
icao: B744
iata: 744
airport_id: KJFK
landing_time: '2020-10-23 07:50:16'
name: 'Boeing 747-438'
@@ -136,8 +136,8 @@ aircraft:
-
id: 2
subfleet_id: 2
icao: null
iata: null
icao: B777
iata: 777
airport_id: LGRP
landing_time: null
name: 'Boeing 777-200'
@@ -153,8 +153,8 @@ aircraft:
-
id: 3
subfleet_id: 1
icao: null
iata: null
icao: B744
iata: 744
airport_id: KAUS
landing_time: '2020-10-24 08:50:13'
name: 'Boeing 747-412'
@@ -170,8 +170,8 @@ aircraft:
-
id: 4
subfleet_id: 1
icao: null
iata: null
icao: B744
iata: 744
airport_id: KAUS
landing_time: null
name: 'Boeing 747-436 RETIRED'
@@ -187,7 +187,7 @@ aircraft:
-
id: 5
subfleet_id: 4
icao: A320
icao: 'A320'
iata: '320'
airport_id: EGLL
landing_time: null

View File

@@ -180,12 +180,12 @@
type: number
description: 'How much the load factor can vary per-flight'
- key: simbrief.api_key
name: 'SimBrief API Key'
name: 'Simbrief API Key'
group: simbrief
value: ''
options: ''
type: string
description: 'Your SimBrief API key'
description: 'Your Simbrief API key'
- key: simbrief.only_bids
name: 'Only allow for bids'
group: simbrief
@@ -194,12 +194,47 @@
type: boolean
description: 'Only allow briefs to be created for bidded flights'
- key: simbrief.expire_days
name: 'SimBrief Expire Time'
name: 'Simbrief Expire Time'
group: simbrief
value: 5
options: ''
type: number
description: 'Days after how long to remove unused briefs'
- key: simbrief.noncharter_pax_weight
name: 'Non-Charter Passenger Weight'
group: simbrief
value: 185
options: ''
type: number
description: 'Passenger weight for non-charter flights excluding baggage (lbs)'
- key: simbrief.noncharter_baggage_weight
name: 'Non-Charter Baggage Weight'
group: simbrief
value: 35
options: ''
type: number
description: 'Passenger baggage weight for non-charter flights (lbs)'
- key: simbrief.charter_pax_weight
name: 'Charter Passenger Weight'
group: simbrief
value: 168
options: ''
type: number
description: 'Passenger weight for charter flights excluding baggage (lbs)'
- key: simbrief.charter_baggage_weight
name: 'Charter Baggage Weight'
group: simbrief
value: 28
options: ''
type: number
description: 'Passenger baggage weight for charter flights (lbs)'
- key: simbrief.callsign
name: 'Use ATC Callsign'
group: simbrief
value: false
options: ''
type: boolean
description: 'Use pilot ident as Simbrief ATC Callsign'
- key: pireps.duplicate_check_time
name: 'PIREP duplicate time check'
group: pireps
@@ -228,6 +263,27 @@
options: ''
type: boolean
description: 'When a PIREP is accepted, remove the bid, if it exists'
- key: pireps.advanced_fuel
name: 'Advanced Fuel Calculations'
group: pireps
value: false
options: ''
type: boolean
description: 'Enables remaining fuel amounts to be considered for fuel expenses'
- key: pireps.delete_cancelled_hours
name: 'Delete cancelled PIREPs'
group: pireps
value: 12
options: ''
type: int
description: 'The time in hours to delete a cancelled PIREP'
- key: pireps.delete_rejected_hours
name: 'Delete rejected PIREPs'
group: pireps
value: 12
options: ''
type: int
description: 'The time in hours to delete a rejected PIREP'
- key: pilots.id_length
name: 'Pilot ID Length'
group: pilots
@@ -305,3 +361,9 @@
options: ''
type: 'text'
description: 'Discord public channel ID for broadcasat notifications'
- key: 'cron.random_id'
name: 'Cron Randomized ID'
group: 'cron'
value: ''
type: 'hidden'
description: ''

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Exceptions;
class CronInvalid extends AbstractHttpException
{
public const MESSAGE = 'Cron ID is disabled or invalid';
public function __construct()
{
parent::__construct(400, static::MESSAGE);
}
/**
* Return the RFC 7807 error type (without the URL root)
*/
public function getErrorType(): string
{
return 'cron-invalid';
}
/**
* Get the detailed error string
*/
public function getErrorDetails(): string
{
return $this->getMessage();
}
/**
* Return an array with the error details, merged with the RFC7807 response
*/
public function getErrorMetadata(): array
{
return [];
}
}

View File

@@ -6,6 +6,7 @@ use App\Contracts\Controller;
use App\Repositories\KvpRepository;
use App\Services\CronService;
use App\Services\VersionService;
use App\Support\Utils;
use Codedge\Updater\UpdaterManager;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
@@ -34,7 +35,12 @@ class MaintenanceController extends Controller
public function index()
{
// Get the cron URL
$cron_id = setting('cron.random_id');
$cron_url = empty($cron_id) ? 'Not enabled' : url(route('api.maintenance.cron', $cron_id));
return view('admin.maintenance.index', [
'cron_url' => $cron_url,
'cron_path' => $this->cronSvc->getCronExecString(),
'cron_problem_exists' => $this->cronSvc->cronProblemExists(),
'new_version' => $this->kvpRepo->get('new_version_available', false),
@@ -117,4 +123,33 @@ class MaintenanceController extends Controller
return redirect('/update/downloader');
}
/**
* Enable the cron, or if it's enabled, change the ID that is used
*
* @param Request $request
*/
public function cron_enable(Request $request)
{
$id = Utils::generateNewId(24);
setting_save('cron.random_id', $id);
Flash::success('Web cron refreshed!');
return redirect(route('admin.maintenance.index'));
}
/**
* Disable the web cron
*
* @param Request $request
*
* @return mixed
*/
public function cron_disable(Request $request)
{
setting_save('cron.random_id', '');
Flash::success('Web cron disabled!');
return redirect(route('admin.maintenance.index'));
}
}

View File

@@ -394,10 +394,10 @@ class PirepController extends Controller
return redirect(route('admin.pireps.index'));
}
$this->pirepRepo->delete($id);
$this->pirepSvc->delete($pirep);
Flash::success('Pirep deleted successfully.');
return redirect(route('admin.pireps.index'));
return redirect()->back();
}
/**

View File

@@ -62,7 +62,7 @@ class SettingsController extends Controller
*/
public function index()
{
$settings = Setting::orderBy('order', 'asc')->get();
$settings = Setting::where('type', '!=', 'hidden')->orderBy('order')->get();
$settings = $settings->groupBy('group');
return view('admin.settings.index', [

View File

@@ -4,7 +4,6 @@ namespace App\Http\Controllers\Api;
use App\Contracts\Controller;
use App\Exceptions\AssetNotFound;
use App\Exceptions\Unauthorized;
use App\Http\Resources\Flight as FlightResource;
use App\Http\Resources\Navdata as NavdataResource;
use App\Models\SimBrief;

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers\Api;
use App\Contracts\Controller;
use App\Exceptions\CronInvalid;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
class MaintenanceController extends Controller
{
/**
* Run the cron job from the web
*
* @param Request $request
* @param string $id The ID passed in for the cron
*
* @return mixed
*/
public function cron(Request $request, string $id)
{
$cron_id = setting('cron.random_id');
if (empty($cron_id) || $id !== $cron_id) {
throw new CronInvalid();
}
$output = '';
Artisan::call('schedule:run');
$output .= trim(Artisan::output());
return response([
'content' => $output,
]);
}
}

View File

@@ -106,7 +106,8 @@ class UserController extends Controller
*/
public function bids(Request $request)
{
$user = $this->userSvc->getUser($this->getUserId($request));
$user_id = $this->getUserId($request);
$user = $this->userSvc->getUser($user_id);
// Add a bid
if ($request->isMethod('PUT') || $request->isMethod('POST')) {

View File

@@ -28,7 +28,10 @@ class DashboardController extends Controller
*/
public function index()
{
//dd(config('backup'));
$last_pirep = null;
/** @var \App\Models\User $user */
$user = Auth::user();
try {

View File

@@ -106,7 +106,13 @@ class FlightController extends Controller
}
$flights = $this->flightRepo->searchCriteria($request)
->with(['dpt_airport', 'arr_airport', 'airline'])
->with([
'dpt_airport',
'arr_airport',
'airline',
'simbrief' => function ($query) use ($user) {
$query->where('user_id', $user->id);
}, ])
->orderBy('flight_number', 'asc')
->orderBy('route_leg', 'asc')
->paginate();
@@ -115,6 +121,7 @@ class FlightController extends Controller
->pluck('flight_id')->toArray();
return view('flights.index', [
'user' => $user,
'airlines' => $this->airlineRepo->selectBoxList(true),
'airports' => $this->airportRepo->selectBoxList(true),
'flights' => $flights,
@@ -158,6 +165,7 @@ class FlightController extends Controller
}
return view('flights.bids', [
'user' => $user,
'airlines' => $this->airlineRepo->selectBoxList(true),
'airports' => $this->airportRepo->selectBoxList(true),
'flights' => $flights,

View File

@@ -7,7 +7,7 @@ use App\Http\Requests\CreatePirepRequest;
use App\Http\Requests\UpdatePirepRequest;
use App\Models\Enums\PirepSource;
use App\Models\Enums\PirepState;
use App\Models\Enums\PirepStatus;
use App\Models\Fare;
use App\Models\Pirep;
use App\Models\SimBrief;
use App\Models\User;
@@ -253,23 +253,48 @@ class PirepController extends Controller
/**
* They have a SimBrief ID, load that up and figure out the flight that it's from
*/
$fare_values = [];
$simbrief = null;
$simbrief_id = null;
$aircraft = null;
if ($request->has('sb_id')) {
$simbrief_id = $request->input('sb_id');
$brief = SimBrief::find($simbrief_id);
$pirep = Pirep::fromSimBrief($brief);
$simbrief = SimBrief::find($simbrief_id);
$pirep = Pirep::fromSimBrief($simbrief);
$aircraft = $simbrief->aircraft;
$aircraft_list[$aircraft->subfleet->name] = [];
$aircraft_list[$aircraft->subfleet->name][$aircraft->id] = $aircraft->name.' - '.$aircraft->registration;
// Convert the fare data into the expected output format
if (!empty($simbrief->fare_data)) {
$fare_values = json_decode($simbrief->fare_data, true);
$fares = [];
$fare_data = json_decode($simbrief->fare_data, true);
foreach ($fare_data as $fare) {
$fares[] = new Fare($fare);
}
$aircraft->subfleet->fares = collect($fares);
}
// TODO: Set more fields from the Simbrief to the PIREP form
} else {
$aircraft_list = $this->aircraftList(true);
}
return view('pireps.create', [
'aircraft' => null,
'aircraft' => $aircraft,
'pirep' => $pirep,
'read_only' => false,
'airline_list' => $this->airlineRepo->selectBoxList(true),
'aircraft_list' => $this->aircraftList(true),
'aircraft_list' => $aircraft_list,
'airport_list' => $this->airportRepo->selectBoxList(true),
'pirep_fields' => $this->pirepFieldRepo->all(),
'field_values' => [],
'fare_values' => $fare_values,
'simbrief_id' => $simbrief_id,
'simbrief' => $simbrief,
]);
}
@@ -493,12 +518,8 @@ class PirepController extends Controller
$this->pirepSvc->submit($pirep);
Flash::success('PIREP submitted!');
} elseif ($attrs['submit'] === 'delete' || $attrs['submit'] === 'cancel') {
$this->pirepRepo->update([
'state' => PirepState::CANCELLED,
'status' => PirepStatus::CANCELLED,
], $pirep->id);
Flash::success('PIREP cancelled!');
$this->pirepSvc->delete($pirep);
Flash::success('PIREP deleted!');
return redirect(route('frontend.pireps.index'));
}

View File

@@ -4,8 +4,12 @@ namespace App\Http\Controllers\Frontend;
use App\Exceptions\AssetNotFound;
use App\Models\Aircraft;
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\User;
use App\Repositories\FlightRepository;
use App\Services\FareService;
use App\Services\SimBriefService;
@@ -49,8 +53,9 @@ class SimBriefController
$flight_id = $request->input('flight_id');
$aircraft_id = $request->input('aircraft_id');
$flight = $this->flightRepo->with(['subfleets'])->find($flight_id);
$flight = $this->fareSvc->getReconciledFaresForFlight($flight);
/** @var Flight $flight */
$flight = $this->flightRepo->with(['fares', 'subfleets'])->find($flight_id);
if (!$flight) {
flash()->error('Unknown flight');
@@ -63,6 +68,21 @@ class SimBriefController
return redirect(route('frontend.flights.index'));
}
// 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);
}
return view('flights.simbrief_aircraft', [
'flight' => $flight,
'subfleets' => $subfleets,
]);
}
// Check if a Simbrief profile already exists
$simbrief = SimBrief::select('id')->where([
'flight_id' => $flight_id,
@@ -73,34 +93,26 @@ class SimBriefController
return redirect(route('frontend.simbrief.briefing', [$simbrief->id]));
}
// Simbrief Profile doesn't exist; prompt the user to create a new one
$aircraft = Aircraft::select('registration', 'name', 'icao', 'iata', 'subfleet_id')
->where('id', $aircraft_id)
->get();
// 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::where('id', $aircraft_id)->first();
if ($flight->subfleets->count() > 0) {
$subfleets = $flight->subfleets;
} else {
$subfleets = $this->userSvc->getAllowableSubfleets($user);
}
// Figure out the proper fares to use for this flight/aircraft
$all_fares = $this->fareSvc->getFareWithOverrides($aircraft->subfleet->fares, $flight->fares);
// TODO: Reconcile the fares for this aircraft w/ proper for the flight/subfleet
// Get passenger and baggage weights with failsafe defaults
if ($flight->flight_type === FlightType::CHARTER_PAX_ONLY) {
$pax_weight = 197;
$pax_weight = setting('simbrief.charter_pax_weight', 168);
$bag_weight = setting('simbrief.charter_baggage_weight', 28);
} else {
$pax_weight = 208;
$pax_weight = setting('simbrief.noncharter_pax_weight', 185);
$bag_weight = setting('simbrief.noncharter_baggage_weight', 35);
}
// No aircraft selected, show that form
if (!$aircraft_id) {
return view('flights.simbrief_aircraft', [
'flight' => $flight,
'aircraft' => $aircraft,
'subfleets' => $subfleets,
'pax_weight' => $pax_weight,
]);
}
// Get the correct load factors
// Get the load factors with failsafe for loadmax if nothing is defined
$lfactor = $flight->load_factor ?? setting('flights.default_load_factor');
$lfactorv = $flight->load_factor_variance ?? setting('flights.load_factor_variance');
@@ -110,20 +122,90 @@ class SimBriefController
$loadmax = $lfactor + $lfactorv;
$loadmax = $loadmax > 100 ? 100 : $loadmax;
// Failsafe for admins not defining load values for their flights
// and also leave the general settings empty, set loadmax to 100
if ($loadmax === 0) {
$loadmax = 100;
}
// Load fares for passengers
$loaddist = []; // The load distribution string
$pax_load_sheet = [];
$tpaxfig = 0;
/** @var Fare $fare */
foreach ($all_fares as $fare) {
if ($fare->type !== FareType::PASSENGER || empty($fare->capacity)) {
continue;
}
$count = floor(($fare->capacity * rand($loadmin, $loadmax)) / 100);
$tpaxfig += $count;
$pax_load_sheet[] = [
'id' => $fare->id,
'code' => $fare->code,
'name' => $fare->name,
'type' => $fare->type,
'capacity' => (int) $fare->capacity,
'count' => $count,
];
$loaddist[] = $fare->code.' '.$count;
}
// Calculate total weights
if (setting('units.weight') === 'kg') {
$tpaxload = round(($pax_weight * $tpaxfig) / 2.205);
$tbagload = round(($bag_weight * $tpaxfig) / 2.205);
} else {
$tpaxload = round($pax_weight * $tpaxfig);
$tbagload = round($bag_weight * $tpaxfig);
}
// Load up fares for cargo
$tcargoload = 0;
$cargo_load_sheet = [];
foreach ($all_fares as $fare) {
if ($fare->type !== FareType::CARGO || empty($fare->capacity)) {
continue;
}
$count = ceil((($fare->capacity - $tbagload) * rand($loadmin, $loadmax)) / 100);
$tcargoload += $count;
$cargo_load_sheet[] = [
'id' => $fare->id,
'code' => $fare->code,
'name' => $fare->name,
'type' => $fare->type,
'capacity' => $fare->capacity,
'count' => $count,
];
$loaddist[] = $fare->code.' '.$count;
}
$tpayload = $tpaxload + $tbagload + $tcargoload;
$request->session()->put('simbrief_fares', array_merge($pax_load_sheet, $cargo_load_sheet));
// Show the main simbrief form
return view('flights.simbrief_form', [
'flight' => $flight,
'aircraft' => $aircraft,
'subfleets' => $subfleets,
'pax_weight' => $pax_weight,
'loadmin' => $loadmin,
'loadmax' => $loadmax,
'user' => Auth::user(),
'flight' => $flight,
'aircraft' => $aircraft,
'pax_weight' => $pax_weight,
'bag_weight' => $bag_weight,
'loadmin' => $loadmin,
'loadmax' => $loadmax,
'pax_load_sheet' => $pax_load_sheet,
'cargo_load_sheet' => $cargo_load_sheet,
'tpaxfig' => $tpaxfig,
'tpaxload' => $tpaxload,
'tbagload' => $tbagload,
'tpayload' => $tpayload,
'tcargoload' => $tcargoload,
'loaddist' => implode(' ', $loaddist),
]);
}
@@ -228,6 +310,7 @@ class SimBriefController
/**
* Check whether the OFP was generated. Pass in two items, the flight_id and ofp_id
* This does the actual "attachment" of the Simbrief to the flight
*
* @param \Illuminate\Http\Request $request
*
@@ -235,10 +318,14 @@ class SimBriefController
*/
public function check_ofp(Request $request)
{
/** @var User $user */
$user = Auth::user();
$ofp_id = $request->input('ofp_id');
$flight_id = $request->input('flight_id');
$aircraft_id = $request->input('aircraft_id');
$fares = $request->session()->get('simbrief_fares', []);
$simbrief = $this->simBriefSvc->downloadOfp(Auth::user()->id, $ofp_id, $flight_id);
$simbrief = $this->simBriefSvc->downloadOfp($user->id, $ofp_id, $flight_id, $aircraft_id, $fares);
if ($simbrief === null) {
$error = new AssetNotFound(new Exception('Simbrief OFP not found'));
return $error->getResponse();

View File

@@ -39,7 +39,7 @@ class ApiAuth implements Middleware
return $this->unauthorized('User not found with key "'.$api_key.'"');
}
if ($user->state !== UserState::ACTIVE) {
if ($user->state !== UserState::ACTIVE && $user->state !== UserState::ON_LEAVE) {
return $this->unauthorized('User is not ACTIVE, please contact an administrator');
}

View File

@@ -17,6 +17,7 @@ class Fare extends Resource
'name' => $this->name,
'capacity' => $this->capacity,
'cost' => $this->cost,
'count' => $this->count ?? 0,
'price' => $this->price,
'type' => $this->type,
'notes' => $this->notes,

View File

@@ -38,6 +38,13 @@ class Pirep extends Resource
$distance = new Distance($res['distance'], config('phpvms.internal_units.distance'));
$res['distance'] = $distance->getResponseUnits();
if (!array_key_exists('block_fuel', $res)) {
$res['block_fuel'] = 0;
}
$block_fuel = new Fuel($res['block_fuel'], config('phpvms.internal_units.fuel'));
$res['block_fuel'] = $block_fuel->getResponseUnits();
if (!array_key_exists('fuel_used', $res)) {
$res['fuel_used'] = 0;
}

View File

@@ -11,9 +11,27 @@ class SimBrief extends Resource
{
public function toArray($request)
{
return [
$data = [
'id' => $this->id,
'url' => url(route('api.flights.briefing', ['id' => $this->id])),
];
try {
if (!empty($this->fare_data)) {
$fares = [];
$fare_data = json_decode($this->fare_data, true);
foreach ($fare_data as $fare) {
$fares[] = new \App\Models\Fare($fare);
}
$this->aircraft->subfleet->fares = collect($fares);
}
} catch (\Exception $e) {
// Invalid fare data
}
$data['subfleet'] = new Subfleet($this->aircraft->subfleet);
return $data;
}
}

View File

@@ -15,9 +15,8 @@ class User extends Resource
'id' => $this->id,
'pilot_id' => $this->pilot_id,
'ident' => $this->ident,
'name' => $this->name,
'name' => $this->name_private,
'name_private' => $this->name_private,
'email' => $this->email,
'avatar' => $this->resolveAvatarUrl(),
'rank_id' => $this->rank_id,
'home_airport' => $this->home_airport_id,

View File

@@ -20,12 +20,14 @@ class Fare extends Model
public $table = 'fares';
protected $fillable = [
'id',
'code',
'name',
'type',
'price',
'cost',
'capacity',
'count',
'notes',
'active',
];
@@ -34,6 +36,7 @@ class Fare extends Model
'price' => 'float',
'cost' => 'float',
'capacity' => 'integer',
'count' => 'integer',
'type' => 'integer',
'active' => 'boolean',
];

23
app/Models/Kvp.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
namespace App\Models;
use App\Contracts\Model;
/**
* @property string key
* @property string value
*/
class Kvp extends Model
{
public $table = 'kvp';
public $timestamps = false;
public $incrementing = false;
protected $keyType = 'string';
public $fillable = [
'key',
'value',
];
}

View File

@@ -305,7 +305,9 @@ class Pirep extends Model
public function getFieldsAttribute()
{
$custom_fields = PirepField::all();
$field_values = PirepFieldValue::where('pirep_id', $this->id)->get();
$field_values = PirepFieldValue::where('pirep_id', $this->id)
->orderBy('created_at', 'asc')
->get();
// Merge the field values into $fields
foreach ($custom_fields as $field) {
@@ -321,7 +323,23 @@ class Pirep extends Model
}
}
return $field_values->sortBy('source');
return $field_values;
}
/**
* Set the amount of block fuel
*
* @param $value
*/
public function setBlockFuelAttribute($value): void
{
if ($value instanceof Fuel) {
$this->attributes['block_fuel'] = $value->toUnit(
config('phpvms.internal_units.fuel')
);
} else {
$this->attributes['block_fuel'] = $value;
}
}
/**

View File

@@ -10,14 +10,17 @@ use Illuminate\Support\Collection;
* @property int $user_id The user that generated this
* @property string $flight_id Optional, if attached to a flight, removed if attached to PIREP
* @property string $pirep_id Optional, if attached to a PIREP, removed if attached to flight
* @property string $aircraft_id The aircraft this is for
* @property string $acars_xml
* @property string $ofp_xml
* @property string $ofp_html
* @property string $fare_data JSON string of the fare data that was generated
* @property Collection $images
* @property Collection $files
* @property Flight $flight
* @property User $user
* @property SimBriefXML $xml
* @property Aircraft $aircraft
* @property string $acars_flightplan_url
*/
class SimBrief extends Model
@@ -29,9 +32,11 @@ class SimBrief extends Model
'id',
'user_id',
'flight_id',
'aircraft_id',
'pirep_id',
'acars_xml',
'ofp_xml',
'fare_data',
'created_at',
'updated_at',
];
@@ -80,6 +85,11 @@ class SimBrief extends Model
* Relationships
*/
public function aircraft()
{
return $this->belongsTo(Aircraft::class, 'aircraft_id');
}
public function flight()
{
if (!empty($this->attributes['flight_id'])) {

View File

@@ -10,6 +10,7 @@ use App\Models\Traits\FilesTrait;
/**
* @property int id
* @property string type
* @property string simbrief_type
* @property string name
* @property int airline_id
* @property int hub_id
@@ -29,6 +30,7 @@ class Subfleet extends Model
'airline_id',
'hub_id',
'type',
'simbrief_type',
'name',
'fuel_type',
'cost_block_hour',

View File

@@ -4,6 +4,7 @@ namespace App\Providers;
use App\Services\ModuleService;
use App\Support\Utils;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
@@ -13,6 +14,7 @@ class AppServiceProvider extends ServiceProvider
public function boot(): void
{
Schema::defaultStringLength(191);
Paginator::useBootstrap();
View::share('moduleSvc', app(ModuleService::class));
}

View File

@@ -2,6 +2,7 @@
namespace App\Providers;
use App\Cron\Hourly\DeletePireps;
use App\Cron\Hourly\RemoveExpiredBids;
use App\Cron\Hourly\RemoveExpiredLiveFlights;
use App\Cron\Nightly\ApplyExpenses;
@@ -41,6 +42,7 @@ class CronServiceProvider extends ServiceProvider
],
CronHourly::class => [
DeletePireps::class,
RemoveExpiredBids::class,
RemoveExpiredLiveFlights::class,
],

View File

@@ -392,6 +392,12 @@ class RouteServiceProvider extends ServiceProvider
Route::match(['post'], 'maintenance/forcecheck', 'MaintenanceController@forcecheck')
->name('maintenance.forcecheck')->middleware('ability:admin,maintenance');
Route::match(['post'], 'maintenance/cron_enable', 'MaintenanceController@cron_enable')
->name('maintenance.cron_enable')->middleware('ability:admin,maintenance');
Route::match(['post'], 'maintenance/cron_disable', 'MaintenanceController@cron_disable')
->name('maintenance.cron_disable')->middleware('ability:admin,maintenance');
// subfleet
Route::get('subfleets/export', 'SubfleetController@export')
->name('subfleets.export')->middleware('ability:admin,fleet');
@@ -459,7 +465,7 @@ class RouteServiceProvider extends ServiceProvider
Route::group([
'as' => 'modules.',
'prefix' => 'modules',
'middleware' => ['ability:admin, modules'],
'middleware' => ['ability:admin,modules'],
], function () {
//Modules Index
@@ -508,6 +514,8 @@ class RouteServiceProvider extends ServiceProvider
Route::get('pireps/{pirep_id}', 'PirepController@get');
Route::get('pireps/{pirep_id}/acars/geojson', 'AcarsController@acars_geojson');
Route::get('cron/{id}', 'MaintenanceController@cron')->name('maintenance.cron');
Route::get('news', 'NewsController@index');
Route::get('status', 'StatusController@status');
Route::get('version', 'StatusController@status');

View File

@@ -50,7 +50,10 @@ class BidService extends Service
$bids = Bid::with([
'flight',
'flight.fares',
'flight.simbrief',
'flight.simbrief' => function ($query) use ($user) {
$query->where('user_id', $user->id);
},
'flight.simbrief.aircraft',
'flight.subfleets',
'flight.subfleets.aircraft',
'flight.subfleets.fares',

View File

@@ -172,6 +172,24 @@ class FareService extends Service
return $fare;
}
/**
* Return all the fares for an aircraft. check the pivot
* table to see if the price/cost/capacity has been overridden
* and return the correct amounts.
*
* @param Subfleet $subfleet
*
* @return Collection
*/
public function getForSubfleet(Subfleet $subfleet)
{
$fares = $subfleet->fares->map(function ($fare) {
return $this->getFares($fare);
});
return $fares;
}
/**
* Attach a fare to an flight
*
@@ -240,24 +258,6 @@ class FareService extends Service
return $subfleet;
}
/**
* return all the fares for an aircraft. check the pivot
* table to see if the price/cost/capacity has been overridden
* and return the correct amounts.
*
* @param Subfleet $subfleet
*
* @return Collection
*/
public function getForSubfleet(Subfleet $subfleet)
{
$fares = $subfleet->fares->map(function ($fare) {
return $this->getFares($fare);
});
return $fares;
}
/**
* Delete the fare from a subfleet
*

View File

@@ -152,10 +152,29 @@ class PirepFinanceService extends Service
public function payFuelCosts(Pirep $pirep): void
{
$ap = $pirep->dpt_airport;
$fuel_used = $pirep->fuel_used;
if (setting('pireps.advanced_fuel', false)) {
$ac = $pirep->aircraft;
// Reading second row by skip(1) to reach the previous accepted pirep. Current pirep is at the first row
$prev_flight = Pirep::where('aircraft_id', $ac->id)->where('state', 2)->where('status', 'ONB')->orderby('submitted_at', 'desc')->skip(1)->first();
if ($prev_flight) {
// If there is a pirep use its values to calculate the remaining fuel
// and calculate the uplifted fuel amount for this pirep
$fuel_amount = $pirep->block_fuel - ($prev_flight->block_fuel - $prev_flight->fuel_used);
// Aircraft has more than enough fuel in its tanks, no uplift necessary
if ($fuel_amount < 0) {
$fuel_amount = 0;
}
} else {
// No pirep found for aircraft, debit full block fuel
$fuel_amount = $pirep->block_fuel;
}
} else {
// Setting is false, switch back to basic calculation
$fuel_amount = $pirep->fuel_used;
}
$debit = Money::createFromAmount($fuel_used * $ap->fuel_jeta_cost);
Log::info('Finance: Fuel cost, (fuel='.$fuel_used.', cost='.$ap->fuel_jeta_cost.') D='
$debit = Money::createFromAmount($fuel_amount * $ap->fuel_jeta_cost);
Log::info('Finance: Fuel cost, (fuel='.$fuel_amount.', cost='.$ap->fuel_jeta_cost.') D='
.$debit->getAmount());
$this->financeSvc->debitFromJournal(

View File

@@ -66,6 +66,10 @@ class RecurringFinanceService extends Service
$obj = $expense->getReferencedObject();
}
if (empty($obj)) {
return [null, null];
}
if ($klass === 'Airport') {
$memo = "Airport Expense: {$expense->name} ({$expense->ref_model_id})";
$transaction_group = "Airport: {$expense->ref_model_id}";
@@ -128,6 +132,9 @@ class RecurringFinanceService extends Service
}
[$memo, $ta_group] = $this->getMemoAndGroup($expense);
if (empty($memo) || empty($ta_group)) {
continue;
}
$this->financeSvc->debitFromJournal(
$journal,

View File

@@ -150,7 +150,7 @@ class SeederService extends Service
'order' => $order,
'name' => '',
'group' => $group,
'value' => '',
'value' => $attrs['value'],
'default' => $attrs['value'],
'options' => '',
'type' => 'hidden',

View File

@@ -23,6 +23,8 @@ use App\Models\Enums\PirepState;
use App\Models\Enums\PirepStatus;
use App\Models\Navdata;
use App\Models\Pirep;
use App\Models\PirepComment;
use App\Models\PirepFare;
use App\Models\PirepFieldValue;
use App\Models\SimBrief;
use App\Models\User;
@@ -436,6 +438,29 @@ class PirepService extends Service
return $pirep;
}
/**
* Delete the PIREP and all of the associated data. Does a force delete to make sure that we
* don't run into problems with foreign keys. Models/tables affected:
*
* acars
* bids
* pirep_comments
* pirep_fares
* pirep_field_values
* simbrief
*
* @param Pirep $pirep
*/
public function delete(Pirep $pirep): void
{
$w = ['pirep_id' => $pirep->id];
PirepComment::where($w)->forceDelete();
PirepFare::where($w)->forceDelete();
PirepFieldValue::where($w)->forceDelete();
SimBrief::where($w)->forceDelete();
$pirep->forceDelete();
}
/**
* Update any custom PIREP fields
*
@@ -539,6 +564,7 @@ class PirepService extends Service
$pirep->aircraft->flight_time = $pirep->aircraft->flight_time + $pirep->flight_time;
$pirep->aircraft->airport_id = $pirep->arr_airport_id;
$pirep->aircraft->landing_time = $pirep->updated_at;
$pirep->aircraft->fuel_onboard = $pirep->block_fuel - $pirep->fuel_used;
$pirep->aircraft->save();
$pirep->refresh();

View File

@@ -29,11 +29,18 @@ class SimBriefService extends Service
* @param string $user_id User who generated this
* @param string $ofp_id The SimBrief OFP ID
* @param string $flight_id The flight ID
* @param string $ac_id The aircraft ID
* @param array $fares Full list of fares for the flightß
*
* @return SimBrief|null
*/
public function downloadOfp(string $user_id, string $ofp_id, string $flight_id)
{
public function downloadOfp(
string $user_id,
string $ofp_id,
string $flight_id,
string $ac_id,
array $fares = []
) {
$uri = str_replace('{id}', $ofp_id, config('phpvms.simbrief_url'));
$opts = [
@@ -57,11 +64,17 @@ class SimBriefService extends Service
$ofp = simplexml_load_string($body, SimBriefXML::class);
$attrs = [
'user_id' => $user_id,
'flight_id' => $flight_id,
'ofp_xml' => $ofp->asXML(),
'user_id' => $user_id,
'flight_id' => $flight_id,
'aircraft_id' => $ac_id,
'ofp_xml' => $ofp->asXML(),
];
// encode the fares data to JSONß
if (!empty($fares)) {
$attrs['fare_data'] = json_encode($fares);
}
// Try to download the XML file for ACARS. If it doesn't work, try to modify the main OFP
$acars_xml = $this->getAcarsOFP($ofp);
if (empty($acars_xml)) {

View File

@@ -1772,7 +1772,7 @@ class Metar implements \ArrayAccess
*/
private function calculate_wind_chill($temperature_f): void
{
if ($temperature_f < 51 && $this->result['wind_speed'] !== 0) {
if ($temperature_f < 51 && $this->result['wind_speed'] && $this->result['wind_speed'] !== 0) {
$windspeed = $this->result['wind_speed']->toUnit('mph');
if ($windspeed > 3) {
$chill_f = 35.74 + 0.6215 * $temperature_f - 35.75 * ($windspeed ** 0.16);

View File

@@ -5,9 +5,8 @@ namespace App\Support;
use App\Contracts\Model;
use Hashids\Hashids;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Str;
use LayerShifter\TLDExtract\Extract;
use Nwidart\Modules\Facades\Module;
use Pdp\Rules;
/**
* Global utilities
@@ -110,26 +109,36 @@ class Utils
*/
public static function getRootDomain(string $url): string
{
if (Str::contains($url, ['https://', 'http://'])) {
$url = str_replace('https://', '', $url);
$url = str_replace('http://', '', $url);
if (!str_starts_with($url, 'http')) {
$url = 'http://'.$url;
}
$extract = new Extract();
$result = $extract->parse($url);
$parsed_url = parse_url($url, PHP_URL_HOST);
if (empty($parsed_url)) {
return '';
}
$val = $result->getRegistrableDomain();
if (str_ends_with($parsed_url, 'localhost')) {
return 'localhost';
}
if (str_ends_with($parsed_url, '/')) {
$parsed_url = substr($parsed_url, 0, strlen($parsed_url) - 1);
}
$rules = Rules::createFromPath(resource_path('tld/public_suffix_list.dat'));
$domain = $rules->resolve($parsed_url);
$val = $domain->getRegistrableDomain();
if (!empty($val)) {
return $val;
}
if ($result->hostname === 'localhost') {
return 'localhost';
// Couldn't validate a domain, see if this is an IP address?
if (filter_var($parsed_url, FILTER_VALIDATE_IP)) {
return $parsed_url;
}
// Couldn't validate a domain, see if this is an IP address?
if (filter_var($url, FILTER_VALIDATE_IP)) {
return $url;
}
return '';
}
}

View File

@@ -14,64 +14,65 @@
"ext-simplexml": "*",
"ext-bcmath": "*",
"ext-pdo": "*",
"ext-intl": "*",
"composer/composer": "~1.0",
"composer/installers": "~1.0",
"laravel/framework": "~7.0",
"laravel/framework": "~8.0",
"akaunting/money": "^1.0",
"anhskohbo/no-captcha": "^3.0",
"appstract/laravel-opcache": "^4.0",
"arrilot/laravel-widgets": "~3.13.0",
"codedge/laravel-selfupdater": "~3.0.0",
"doctrine/dbal": "~2.9.2",
"facade/ignition": "^2.0",
"fideloper/proxy": "^4.3",
"codedge/laravel-selfupdater": "~3.2.0",
"doctrine/dbal": "^3.0.0",
"facade/ignition": "^2.5",
"fideloper/proxy": "^4.4",
"guzzlehttp/guzzle": "~6.5",
"hashids/hashids": "^2.0.0",
"hashids/hashids": "^4.1.0",
"igaster/laravel-theme": "^2.0",
"intervention/image": "2.4.*",
"irazasyed/laravel-gamp": "^1.6",
"irazasyed/laravel-gamp": "^1.8",
"jmikola/geojson": "1.0.*",
"joshbrw/laravel-module-installer": "0.1.*",
"joshbrw/laravel-module-installer": "^2.0",
"laracasts/flash": "^3.1",
"laravel/helpers": "^1.2",
"laravel/helpers": "^1.4",
"laravelcollective/html": "~6.2.0",
"layershifter/tld-extract": "^2.0",
"jeremykendall/php-domain-parser": "~5.7.2",
"league/csv": "9.2.*",
"league/geotools": "0.8.*",
"league/iso3166": "2.1.*",
"markrogoyski/math-php": "^0.38.0",
"myclabs/deep-copy": "~1.9.0",
"league/iso3166": "^3.0.0",
"markrogoyski/math-php": "^1.10",
"myclabs/deep-copy": "~1.10.0",
"nabeel/vacentral": "~2.0",
"nwidart/laravel-modules": "^8.0",
"nwidart/laravel-modules": "^8.2",
"php-units-of-measure/php-units-of-measure": "~2.1.0",
"phpvms/sample-module": "^1.0",
"pragmarx/version": "^1.2.2",
"prettus/l5-repository": "~2.6.0",
"santigarcor/laratrust": "~6.0",
"sebastiaanluca/laravel-helpers": "~5.0",
"pragmarx/version": ">=v1.2.3",
"prettus/l5-repository": "~2.7.0",
"santigarcor/laratrust": "~6.3",
"sebastiaanluca/laravel-helpers": "~6.0",
"semver/semver": "~1.1.0",
"spatie/laravel-backup": "~6.9.0",
"spatie/valuestore": "~1.2.3",
"symfony/polyfill-iconv": "~1.17.0",
"spatie/valuestore": "~1.2",
"symfony/polyfill-iconv": "~1.22.0",
"tivie/php-os-detector": "~1.1.0",
"vlucas/phpdotenv": "v4.0",
"webpatser/laravel-uuid": "~3.0",
"oomphinc/composer-installers-extender": "^1.1",
"laravel/ui": "^2.0",
"madnest/madzipper": "^1.0",
"elcobvg/laravel-opcache": "^0.4.1"
"vlucas/phpdotenv": "^5.3.0",
"webpatser/laravel-uuid": "~4.0",
"oomphinc/composer-installers-extender": "^2.0",
"laravel/ui": "^3.2.0",
"madnest/madzipper": "^1.1.0",
"elcobvg/laravel-opcache": "^0.4.1",
"laravel/legacy-factories": "^1.1",
"fakerphp/faker": "^1.13",
"wildbit/swiftmailer-postmark": "^3.3"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.0",
"barryvdh/laravel-ide-helper": "^2.6",
"bpocallaghan/generators": "~6.0",
"barryvdh/laravel-debugbar": "^3.5",
"barryvdh/laravel-ide-helper": "^2.9",
"bpocallaghan/generators": "~7.0",
"filp/whoops": "~2.0",
"fzaninotto/faker": "~1.9.0",
"friendsofphp/php-cs-fixer": "^2.16",
"mockery/mockery": "0.9.*",
"nunomaduro/collision": "^4.0",
"mockery/mockery": "^1.4.0",
"nunomaduro/collision": "^5.3.0",
"phpunit/phpunit": "~9.0",
"squizlabs/php_codesniffer": "3.*",
"sempro/phpunit-pretty-print": "^1.2"
},
"autoload": {

4682
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,204 +0,0 @@
<?php
use App\Notifications\Notifiables\Backups;
use Spatie\Backup\Notifications\Notifications\BackupHasFailed;
use Spatie\Backup\Notifications\Notifications\BackupWasSuccessful;
use Spatie\Backup\Notifications\Notifications\CleanupHasFailed;
use Spatie\Backup\Notifications\Notifications\CleanupWasSuccessful;
use Spatie\Backup\Notifications\Notifications\HealthyBackupWasFound;
use Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFound;
use Spatie\Backup\Tasks\Cleanup\Strategies\DefaultStrategy;
use Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays;
use Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes;
return [
'backup' => [
'name' => config('app.name', 'phpvms-backup'),
'source' => [
'files' => [
'include' => [
base_path(),
],
'exclude' => [
base_path('vendor'),
base_path('node_modules'),
],
'follow_links' => false,
],
/*
* The names of the connections to the databases that should be backed up
* MySQL, PostgreSQL, SQLite and Mongo databases are supported.
*
* The content of the database dump may be customized for each connection
* by adding a 'dump' key to the connection settings in config/database.php.
* E.g.
* 'mysql' => [
* ...
* 'dump' => [
* 'excludeTables' => [
* 'table_to_exclude_from_backup',
* 'another_table_to_exclude'
* ]
* ],
* ],
*
* If you are using only InnoDB tables on a MySQL server, you can
* also supply the useSingleTransaction option to avoid table locking.
*
* E.g.
* 'mysql' => [
* ...
* 'dump' => [
* 'useSingleTransaction' => true,
* ],
* ],
*
* For a complete list of available customization options, see https://github.com/spatie/db-dumper
*/
'databases' => [
'mysql',
],
],
/*
* The database dump can be compressed to decrease diskspace usage.
*
* Out of the box Laravel-backup supplies
* Spatie\DbDumper\Compressors\GzipCompressor::class.
*
* You can also create custom compressor. More info on that here:
* https://github.com/spatie/db-dumper#using-compression
*
* If you do not want any compressor at all, set it to null.
*/
'database_dump_compressor' => null,
'destination' => [
/*
* The filename prefix used for the backup zip file.
*/
'filename_prefix' => '',
/*
* The disk names on which the backups will be stored.
*/
'disks' => [
'local',
],
],
/*
* The directory where the temporary files will be stored.
*/
'temporary_directory' => storage_path('app/backup-temp'),
],
/*
* You can get notified when specific events occur. Out of the box you can use 'mail' and 'slack'.
* For Slack you need to install guzzlehttp/guzzle and laravel/slack-notification-channel.
*
* You can also use your own notification classes, just make sure the class is named after one of
* the `Spatie\Backup\Events` classes.
*/
'notifications' => [
'notifications' => [
BackupHasFailed::class => ['mail'],
UnhealthyBackupWasFound::class => ['mail'],
CleanupHasFailed::class => ['mail'],
BackupWasSuccessful::class => ['mail'],
HealthyBackupWasFound::class => ['mail'],
CleanupWasSuccessful::class => ['mail'],
],
/*
* Here you can specify the notifiable to which the notifications should be sent. The default
* notifiable will use the variables specified in this config file.
*/
'notifiable' => Backups::class,
'slack' => [
'webhook_url' => '',
/*
* If this is set to null the default channel of the webhook will be used.
*/
'channel' => null,
'username' => null,
'icon' => null,
],
],
/*
* Here you can specify which backups should be monitored.
* If a backup does not meet the specified requirements the
* UnHealthyBackupWasFound event will be fired.
*/
'monitor_backups' => [
[
'name' => config('app.name'),
'disks' => ['local'],
'health_checks' => [
MaximumAgeInDays::class => 1,
MaximumStorageInMegabytes::class => 5000,
],
],
/*
[
'name' => 'name of the second app',
'disks' => ['local', 's3'],
'health_checks' => [
\Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays::class => 1,
\Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes::class => 5000,
],
],
*/
],
'cleanup' => [
/*
* The strategy that will be used to cleanup old backups. The default strategy
* will keep all backups for a certain amount of days. After that period only
* a daily backup will be kept. After that period only weekly backups will
* be kept and so on.
*
* No matter how you configure it the default strategy will never
* delete the newest backup.
*/
'strategy' => DefaultStrategy::class,
'default_strategy' => [
/*
* The number of days for which backups must be kept.
*/
'keep_all_backups_for_days' => 3,
/*
* The number of days for which daily backups must be kept.
*/
'keep_daily_backups_for_days' => 2,
/*
* The number of weeks for which one weekly backup must be kept.
*/
'keep_weekly_backups_for_weeks' => 2,
/*
* The number of months for which one monthly backup must be kept.
*/
'keep_monthly_backups_for_months' => 1,
/*
* The number of years for which one yearly backup must be kept.
*/
'keep_yearly_backups_for_years' => 1,
/*
* After cleaning up the backups remove the oldest backup until
* this amount of megabytes has been reached.
*/
'delete_oldest_backups_when_using_more_megabytes_than' => 5000,
],
],
];

View File

@@ -1,5 +1,7 @@
<?php
use Facade\Ignition\SolutionProviders\MissingPackageSolutionProvider;
return [
/*
|--------------------------------------------------------------------------
@@ -54,7 +56,7 @@ return [
*/
'ignored_solution_providers' => [
//
MissingPackageSolutionProvider::class,
],
/*
@@ -68,7 +70,7 @@ return [
|
*/
'enable_runnable_solutions' => env('IGNITION_ENABLE_RUNNABLE_SOLUTIONS', null),
'enable_runnable_solutions' => false,
/*
|--------------------------------------------------------------------------

View File

@@ -19,6 +19,7 @@ return [
'fileinfo',
'openssl',
'pdo',
'intl',
'mbstring',
'tokenizer',
'json',

View File

@@ -1,26 +0,0 @@
<?php
/**
* phpVMS (root folder)
*/
$path_to_phpvms_folder = __DIR__;
require $path_to_phpvms_folder.'/bootstrap/autoload.php';
$app = require_once $path_to_phpvms_folder.'/bootstrap/app.php';
$app->setPublicPath(__DIR__.'/public');
$app->setPublicUrlPath(env('APP_PUBLIC_URL', '/'));
// Uncomment this line if you're having issues with the redirecting not working properly
//$app->setPublicUrlPath(env('APP_PUBLIC_URL', '/public'));
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);

11
package-lock.json generated
View File

@@ -1983,6 +1983,11 @@
"resolved": "https://registry.npmjs.org/bootstrap-sass/-/bootstrap-sass-3.4.1.tgz",
"integrity": "sha512-p5rxsK/IyEDQm2CwiHxxUi0MZZtvVFbhWmyMOt4lLkA4bujDA1TGoKT0i1FKIWiugAdP+kK8T5KMDFIKQCLYIA=="
},
"bootstrap3": {
"version": "npm:bootstrap@3.4.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz",
"integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -5812,9 +5817,9 @@
}
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
},
"lodash.memoize": {
"version": "4.1.2",

View File

@@ -33,7 +33,7 @@
"leaflet-providers": "1.0.*",
"leaflet-rotatedmarker": "^0.2.0",
"leaflet.geodesic": "^2.5.2",
"lodash": ">=4.17.21",
"lodash": ">=4.17.19",
"marked": "^1.2.2",
"minimist": "^1.2.2",
"moment": "^2.29.1",

View File

@@ -1,50 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="bootstrap/autoload.php"
colors="true"
processIsolation="false"
stopOnFailure="true"
convertErrorsToExceptions="false"
convertNoticesToExceptions="false"
convertWarningsToExceptions="false"
beStrictAboutOutputDuringTests="false"
beStrictAboutTestsThatDoNotTestAnything="false">
<testsuites>
<testsuite name="Application Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<extensions>
<extension class="Tests\Bootstrap"/>
</extensions>
<!--<listeners>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="bootstrap/autoload.php" colors="true" processIsolation="false" stopOnFailure="true" convertErrorsToExceptions="false" convertNoticesToExceptions="false" convertWarningsToExceptions="false" beStrictAboutOutputDuringTests="false" beStrictAboutTestsThatDoNotTestAnything="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Application Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<extensions>
<extension class="Tests\Bootstrap"/>
</extensions>
<!--<listeners>
<listener class="NunoMaduro\Collision\Adapters\Phpunit\Listener"/>
</listeners>-->
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./app</directory>
</whitelist>
</filter>
<php>
<env name="PHPUNIT_PRETTY_PRINT_PROGRESS" value="true" />
<ini name="error_reporting" value="E_ALL"/>
<ini name="display_errors" value="On"/>
<ini name="display_startup_errors" value="On"/>
<server name="APP_ENV" value="testing"/>
<server name="APP_KEY" value="base64:ve66Z5Kt/zTN3p++0zOPu854PHfZkwJE5VuoFAlzHtI="/>
<server name="APP_DEBUG" value="true"/>
<server name="APP_LOG_LEVEL" value="error"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="none"/>
<server name="DB_CONNECTION" value="memory"/>
<server name="DB_PREFIX" value="vmstest_"/>
<server name="MAIL_MAILER" value="array"/>
<server name="QUEUE_DRIVER" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/>
<server name="APP_CONFIG_CACHE" value="bootstrap/cache/config.phpunit.php"/>
<server name="APP_SERVICES_CACHE" value="bootstrap/cache/services.phpunit.php"/>
<server name="APP_PACKAGES_CACHE" value="bootstrap/cache/packages.phpunit.php"/>
<server name="APP_ROUTES_CACHE" value="bootstrap/cache/routes.phpunit.php"/>
<server name="APP_EVENTS_CACHE" value="bootstrap/cache/events.phpunit.php"/>
</php>
<php>
<env name="PHPUNIT_PRETTY_PRINT_PROGRESS" value="true"/>
<ini name="error_reporting" value="E_ALL"/>
<ini name="display_errors" value="On"/>
<ini name="display_startup_errors" value="On"/>
<server name="APP_ENV" value="testing"/>
<server name="APP_KEY" value="base64:ve66Z5Kt/zTN3p++0zOPu854PHfZkwJE5VuoFAlzHtI="/>
<server name="APP_DEBUG" value="true"/>
<server name="APP_LOG_LEVEL" value="error"/>
<server name="APP_URL" value="http://localhost"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<server name="DB_CONNECTION" value="memory"/>
<server name="DB_PREFIX" value="vmstest_"/>
<server name="MAIL_MAILER" value="array"/>
<server name="QUEUE_DRIVER" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/>
<server name="APP_CONFIG_CACHE" value="bootstrap/cache/config.phpunit.php"/>
<server name="APP_SERVICES_CACHE" value="bootstrap/cache/services.phpunit.php"/>
<server name="APP_PACKAGES_CACHE" value="bootstrap/cache/packages.phpunit.php"/>
<server name="APP_ROUTES_CACHE" value="bootstrap/cache/routes.phpunit.php"/>
<server name="APP_EVENTS_CACHE" value="bootstrap/cache/events.phpunit.php"/>
</php>
</phpunit>

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,9 @@
* https://docs.phpvms.net
*/
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;
/**
* NOTE!!
*
@@ -16,14 +19,12 @@
*/
$path_to_phpvms_folder = __DIR__.'/../';
if (file_exists($path_to_phpvms_folder.'bootstrap/autoload.php')) {
require $path_to_phpvms_folder.'bootstrap/autoload.php';
$app = require_once $path_to_phpvms_folder.'bootstrap/app.php';
// noop
}
// Look up one more folder up (outside of the Laravel root) and in the `phpvms` subfolder
elseif (file_exists($path_to_phpvms_folder.'phpvms/bootstrap/autoload.php')) {
require $path_to_phpvms_folder.'phpvms/bootstrap/autoload.php';
$app = require_once $path_to_phpvms_folder.'phpvms/bootstrap/app.php';
$path_to_phpvms_folder = $path_to_phpvms_folder.'phpvms';
}
// Bail out
@@ -32,14 +33,30 @@ else {
exit();
}
require $path_to_phpvms_folder.'/bootstrap/autoload.php';
$app = require_once $path_to_phpvms_folder.'/bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Check If Application Is Under Maintenance
|--------------------------------------------------------------------------
|
| If the application is maintenance / demo mode via the "down" command we
| will require this file so that any prerendered template can be shown
| instead of starting the framework, which could cause an exception.
|
*/
if (file_exists($path_to_phpvms_folder.'/storage/framework/maintenance.php')) {
require $path_to_phpvms_folder.'/storage/framework/maintenance.php';
}
$app->setPublicPath(__DIR__);
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$kernel = $app->make(Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$response = tap($kernel->handle(
$request = Request::capture()
))->send();
$kernel->terminate($request, $response);

View File

@@ -107,7 +107,7 @@ return [
// Overrides config/session.php
'session' => [
'driver' => 'database',
'driver' => 'file',
'connection' => 'mysql',
'lifetime' => 60 * 24, # 24 hours
],

View File

@@ -1,3 +1,8 @@
<?php exit(); ?>
#
# THIS FILE MUST BE KEPT SECRET! IT IS BLOCKED IN THE HTACCESS FILE
# HOWEVER, THIS DIRECTORY SHOULDN'T BE EXPOSED TO THE PUBLIC AT ALL
# SEE THE DOCS FOR PROPER (SECURE) INSTALLATION:
# https://docs.phpvms.net/installation/uploading
#
APP_KEY=base64:$APP_KEY$

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,8 @@
</h6>
<div class="row" style="padding-top: 5px">
<div class="col-sm-12">
<p>A cron must be created that runs every minute calling artisan. Example:</p>
<p>A cron must be created that runs every minute calling artisan. An example is below.
<strong><a href="{{ docs_link('cron') }}" target="_blank">See the docs</a></strong></p>
<label style="width: 100%">
<input type="text" value="{{ $cron_path }}" class="form-control" style="width: 100%"/>
</label>
@@ -20,6 +21,45 @@
@endif
</div>
</div>
<hr>
<div class="row" style="padding-top: 5px">
<div class="col-sm-12">
<h5>Web Cron</h5>
</div>
<div class="col-sm-6">
<p>
If you don't have cron access on your server, you can use a web-cron service to
access this URL every minute. Keep it disabled if you're not using it. It's a
unique ID that can be reset/changed if needed for security.
</p>
</div>
<div class="col-sm-6 pull-right">
<table class="table-condensed">
<tr class="text-right">
<td style="padding-right: 10px;" class="text-right">
{{ Form::open(['url' => route('admin.maintenance.cron_enable'),
'method' => 'post']) }}
{{ Form::button('Enable/Change ID', ['type' => 'submit', 'class' => 'btn btn-success']) }}
{{ Form::close() }}
</td>
<td class="text-right">
{{ Form::open(['url' => route('admin.maintenance.cron_disable'),
'method' => 'post']) }}
{{ Form::button('Disable', ['type' => 'submit', 'class' => 'btn btn-warning']) }}
{{ Form::close() }}
</td>
</tr>
</table>
</div>
<div class="col-sm-12">
<label style="width: 100%">
<input type="text" value="{{ $cron_url }}" class="form-control" style="width: 100%"/>
</label>
</div>
</div>
</div>
</div>
</div>

View File

@@ -23,11 +23,19 @@
'pirep_id' => $pirep->id,
'new_status' => PirepState::REJECTED,
'class' => $on_edit_page ? 'pirep_change_status': 'pirep_submit_status']) }}
{{ Form::button('Reject', ['type' => 'submit', 'class' => 'btn btn-danger']) }}
{{ Form::button('Reject', ['type' => 'submit', 'class' => 'btn btn-warning']) }}
{{ Form::close() }}
@endif
</td>
<td>&nbsp;</td>
<td>
{{ Form::open(['url' => route('admin.pireps.destroy', [$pirep->id]),
'method' => 'delete',
'name' => 'delete_'.$pirep->id,
'id' => $pirep->id.'_delete'
]) }}
{{ Form::button('Delete', ['type' => 'submit', 'class' => 'btn btn-danger']) }}
{{ Form::close() }}
</td>
@if ($on_edit_page === false)
<td>
<form action="{{ route('admin.pireps.edit', [$pirep->id]) }}">

View File

@@ -24,13 +24,19 @@
<p class="text-danger">{{ $errors->first('hub_id') }}</p>
</div>
<div class="form-group col-sm-3">
<div class="form-group col-sm-2">
{{ Form::label('type', 'Type:') }}
{{ Form::text('type', null, ['class' => 'form-control']) }}
<p class="text-danger">{{ $errors->first('type') }}</p>
</div>
<div class="form-group col-sm-3">
<div class="form-group col-sm-2">
{{ Form::label('simbrief_type', 'SimBrief Type:') }}
{{ Form::text('simbrief_type', null, ['class' => 'form-control']) }}
<p class="text-danger">{{ $errors->first('simbrief_type') }}</p>
</div>
<div class="form-group col-sm-2">
{{ Form::label('name', 'Name:') }}
{{ Form::text('name', null, ['class' => 'form-control']) }}
<p class="text-danger">{{ $errors->first('name') }}</p>

View File

@@ -2,17 +2,6 @@
@section('title', 'SimBrief Flight Planning')
@section('content')
@foreach($aircraft as $acdetails)
@php
$simbrieftype = $acdetails->icao ;
$subflid = $acdetails->subfleet_id ;
if($acdetails->icao === 'A20N') { $simbrieftype = 'A320'; }
if($acdetails->icao === 'A21N') { $simbrieftype = 'A321'; }
if($acdetails->icao === 'B77L') { $simbrieftype = 'B77F'; }
if($acdetails->icao === 'B773') { $simbrieftype = 'B77W'; }
if($acdetails->icao === 'E35L') { $simbrieftype = 'E135'; }
@endphp
@endforeach
<form id="sbapiform">
<div class="row">
@@ -22,190 +11,178 @@
<div class="row">
<div class="col-8">
<div class="form-container">
<div class="form-container-body">
<h6><i class="fas fa-info-circle"></i>&nbsp;Aircraft Details</h6>
<div class="row">
<div class="col-sm-4">
<label for="type">Type</label>
<input type="text" class="form-control" value="{{ $acdetails->icao }}" maxlength="4" disabled/>
<input type="hidden" id="type" name="type" class="form-control" value="{{ $simbrieftype }}"
maxlength="4"/>
<input type="text" class="form-control" value="{{ $aircraft->icao }}" maxlength="4" disabled>
<input type="hidden" name="type" value="{{ $aircraft->subfleet->simbrief_type ?? $aircraft->icao }}">
</div>
<div class="col-sm-4">
<label for="reg">Registration</label>
<input type="text" class="form-control" value="{{ $acdetails->registration }}" maxlength="6"
disabled/>
<input type="hidden" id="reg" name="reg" value="{{ $acdetails->registration }}"/>
<input type="text" class="form-control" value="{{ $aircraft->registration }}" maxlength="6" disabled>
<input type="hidden" name="reg" value="{{ $aircraft->registration }}">
</div>
</div>
<br>
</div>
<div class="form-container-body">
<h6><i class="fas fa-info-circle"></i>&nbsp;@lang('pireps.flightinformations') for
<b>{{ $flight->airline->icao }} {{ $flight->flight_number }}</b></h6>
<h6><i class="fas fa-info-circle"></i>&nbsp;@lang('pireps.flightinformations') For
<b>{{ $flight->airline->icao }}{{ $flight->flight_number }} ({{ \App\Models\Enums\FlightType::label($flight->flight_type) }})</b></h6>
<div class="row">
<div class="col-sm-4">
<label for="dorig">Departure Airport</label>
<input id="dorig" type="text" class="form-control" maxlength="4"
value="{{ $flight->dpt_airport_id }}" disabled/>
<input id="orig" name="orig" type="hidden" maxlength="4" value="{{ $flight->dpt_airport_id }}"/>
<input id="dorig" type="text" class="form-control" maxlength="4" value="{{ $flight->dpt_airport_id }}" disabled>
<input name="orig" type="hidden" maxlength="4" value="{{ $flight->dpt_airport_id }}">
</div>
<div class="col-sm-4">
<label for="ddest">Arrival Airport</label>
<input id="ddest" type="text" class="form-control" maxlength="4"
value="{{ $flight->arr_airport_id }}" disabled/>
<input id="dest" name="dest" type="hidden" maxlength="4" value="{{ $flight->arr_airport_id }}"/>
<input id="ddest" type="text" class="form-control" maxlength="4" value="{{ $flight->arr_airport_id }}" disabled>
<input name="dest" type="hidden" maxlength="4" value="{{ $flight->arr_airport_id }}">
</div>
<div class="col-sm-4">
<label for="altn">Alternate Airport</label>
<input id="altn" name="altn" type="text" class="form-control" maxlength="4"
value="{{ $flight->alt_airport_id ?? 'AUTO' }}"/>
<input name="altn" type="text" class="form-control" maxlength="4" value="{{ $flight->alt_airport_id ?? 'AUTO' }}">
</div>
</div>
<br>
<div class="row">
<div class="col-sm-8">
<label for="route">Preferred Company Route</label>
<input id="route" name="route" type="text" class="form-control" placeholder="" maxlength="1000"
value="{{ $flight->route }}"/>
<input name="route" type="text" class="form-control" value="{{ $flight->route }}">
</div>
<div class="col-sm-4">
<label for="fl">Preferred Flight Level</label>
<input id="fl" name="fl" type="text" class="form-control" placeholder="" maxlength="5"
value="{{ $flight->level }}"/>
<input id="fl" name="fl" type="text" class="form-control" maxlength="5" value="{{ $flight->level }}">
</div>
</div>
<br>
<div class="row">
<div class="col-sm-4">
<label for="std">Scheduled Departure Time (UTC)</label>
<input id="std" type="text" class="form-control" placeholder="" maxlength="4"
value="{{ $flight->dpt_time }}" disabled/>
@if($flight->dpt_time)
<label for="std">Scheduled Departure Time (UTC)</label>
<input id="std" type="text" class="form-control" maxlength="4" value="{{ $flight->dpt_time }}" disabled>
@endif
</div>
<div class="col-sm-4">
<label for="etd">Estimated Departure Time (UTC)</label>
<input id="etd" type="text" class="form-control" placeholder="" maxlength="4" disabled/>
<input id="etd" type="text" class="form-control" maxlength="4" disabled>
</div>
<div class="col-sm-4">
<label for="dof">Date Of Flight (UTC)</label>
<input id="dof" type="text" class="form-control" placeholder="" maxlength="4" disabled/>
<input id="dof" type="text" class="form-control" maxlength="4" disabled>
</div>
</div>
<br>
</div>
<div class="form-container-body">
@foreach($subfleets as $subfleet)
@if($subfleet->id == $subflid)
<h6><i class="fas fa-info-circle"></i>&nbsp;Configuration And Load Information For
<b>{{ $subfleet->name }} ; {{ $acdetails->registration }}</b></h6>
{{-- Generate Load Figures --}}
<div class="row">
{{-- Create and send some data to the $loadarray for MANUALRMK generation --}}
@php $loadarray = [] ; @endphp
@foreach($subfleet->fares as $fare)
@if($fare->capacity > 0)
@php
$randomloadperfare = ceil(($fare->capacity * (rand($loadmin, $loadmax))) /100);
$loadarray[] = ['SeatType' => $fare->code];
$loadarray[] = ['SeatLoad' => $randomloadperfare];
@endphp
<div class="col-sm-4">
<label for="LoadFare{{ $fare->id }}">{{ $fare->name }} Load [
Max: {{ number_format($fare->capacity) }} ]</label>
<input id="LoadFare{{ $fare->id }}" type="text" class="form-control"
value="{{ number_format($randomloadperfare) }} @if($randomloadperfare > '900') {{ setting('units.weight') }} @endif"
disabled/>
</div>
@endif
@endforeach
@php
$loadcollection = collect($loadarray) ;
$totalgenload = $loadcollection->sum('SeatLoad') ;
@endphp
<h6><i class="fas fa-info-circle"></i>&nbsp;Configuration And Load Information For
<b>{{ $aircraft->registration }} ({{ $aircraft->subfleet->name }})</b></h6>
<div class="row">
@foreach($pax_load_sheet as $fare)
<div class="col-sm-3">
<label for="LoadFare{{ $fare['id'] }}">{{ $fare['name'] }} [Max: {{ $fare['capacity'] }}]</label>
<input id="LoadFare{{ $fare['id'] }}" type="text" class="form-control" value="{{ $fare['count'] }}" disabled>
</div>
@if($totalgenload > 0 && $totalgenload < 900)
<input type="hidden" name="acdata" value="{'paxwgt':{{ $pax_weight }}}">
<br>
<div class="row">
<div class="col-sm-4">
@if(setting('units.weight') === 'kg')
@php $estimatedpayload = number_format(round(($pax_weight * $totalgenload) / 2.2)) ; @endphp
@else
@php $estimatedpayload = number_format(round($pax_weight * $totalgenload)) ; @endphp
@endif
<label for="EstimatedLoad">Estimated Payload For {{ $totalgenload }} Pax</label>
<input id="EstimatedLoad" type="text" class="form-control"
value="{{ $estimatedpayload }} {{ setting('units.weight') }}" disabled/>
</div>
@endforeach
{{-- Generate Load Figures For Cargo Fares --}}
@foreach($cargo_load_sheet as $fare)
<div class="col-sm-3">
<label for="LoadFare{{ $fare['id'] }}">{{ $fare['name'] }} [Max: {{ number_format($fare['capacity'] - $tbagload) }} {{ setting('units.weight') }}]</label>
<input id="LoadFare{{ $fare['id'] }}" type="text" class="form-control" value="{{ number_format($fare['count']) }}" disabled>
</div>
@endforeach
</div>
@if(isset($tpayload) && $tpayload > 0)
{{-- Display The Weights Generated --}}
<br>
<div class="row">
@if($tpaxload)
<div class="col-sm-3">
<label for="tdPaxLoad">Pax Weight</label>
<input id="tdPaxLoad" type="text" class="form-control" value="{{ number_format($tpaxload) }} {{ setting('units.weight') }}" disabled>
</div>
<div class="col-sm-3">
<label for="tBagLoad">Baggage Weight</label>
<input id="tBagLoad" type="text" class="form-control" value="{{ number_format($tbagload) }} {{ setting('units.weight') }}" disabled>
</div>
<input type="hidden" id="pax" name="pax" class="form-control" value="{{ $totalgenload }}"/>
@elseif($totalgenload > 900)
<input type='hidden' id="pax" name='pax' value='0' maxlength='3'>
<input type='hidden' id="cargo" name='cargo' value="{{ $totalgenload }}" maxlength='7'>
@endif
@endif
@endforeach
@if($tpaxload && $tcargoload)
<div class="col-sm-3">
<label for="tCargoload">Cargo Weight</label>
<input id="tCargoload" type="text" class="form-control" value="{{ number_format($tcargoload) }} {{ setting('units.weight') }}" disabled>
</div>
@endif
<div class="col-sm-3">
<label for="tPayload">Total Payload</label>
<input id="tPayload" type="text" class="form-control" value="{{ number_format($tpayload) }} {{ setting('units.weight') }}" disabled>
</div>
</div>
@endif
</div>
</div>
{{-- Prepare Form Fields For SimBrief --}}
<input type="hidden" name="acdata" value="{'paxwgt':{{ round($pax_weight + $bag_weight) }}}">
@if($tpaxfig)
<input type="hidden" name="pax" value="{{ $tpaxfig }}">
@elseif(!$tpaxfig && $tcargoload)
<input type="hidden" name="pax" value="0">
@endif
@if($tcargoload)
<input type='hidden' name='cargo' value="{{ number_format(($tcargoload / 1000),1) }}">
@endif
@if(isset($tpayload) && $tpayload > 0)
<input type="hidden" name="manualrmk" value="Load Distribution {{ $loaddist }}">
@endif
<input type="hidden" name="airline" value="{{ $flight->airline->icao }}">
<input type="hidden" name="fltnum" value="{{ $flight->flight_number }}">
@if(setting('simbrief.callsign', true))
<input type="hidden" name="callsign" value="{{ $user->ident }}">
@endif
<input type="hidden" id="steh" name="steh" maxlength="2">
<input type="hidden" id="stem" name="stem" maxlength="2">
<input type="hidden" id="date" name="date" maxlength="9">
<input type="hidden" id="deph" name="deph" maxlength="2">
<input type="hidden" id="depm" name="depm" maxlength="2">
<input type="hidden" name="selcal" value="BK-FS">
<input type="hidden" name="planformat" value="lido">
<input type="hidden" name="omit_sids" value="0">
<input type="hidden" name="omit_stars" value="0">
<input type="hidden" name="cruise" value="CI">
<input type="hidden" name="civalue" value="AUTO">
{{-- For more info about form fields and their details check SimBrief Forum / API Support --}}
</div>
{{--
Here we generate the MANUALRMK which is sent to SimBrief and displayed in the generated
ofp as Dispatch Remarks. $loadarray is created and filled with data during random load
generation, it holds each fare's code and the generated load then we are imploding that
array to get the fare codes and load counts.
Returned string will be like Load Distribution Y 132 C 12 F 4
--}}
@if($totalgenload > 0)
@php
$loaddisttxt = "Load Distribution ";
$loaddist = implode(' ', array_map(
function ($v, $k) {
if(is_array($v)){
return implode('&'.' '.':', $v);
}else{
return $k.':'.$v;
}
},
$loadarray, array_keys($loadarray)
));
@endphp
<input type="hidden" name="manualrmk" value="{{ $loaddisttxt }}{{ $loaddist }}">
@endif
<input type="hidden" name="airline" value="{{ $flight->airline->icao }}">
<input type="hidden" name="fltnum" value="{{ $flight->flight_number }}">
<input type="hidden" id="steh" name="steh" maxlength="2">
<input type="hidden" id="stem" name="stem" maxlength="2">
<input type="hidden" id="date" name="date" maxlength="9">
<input type="hidden" id="deph" name="deph" maxlength="2">
<input type="hidden" id="depm" name="depm" maxlength="2">
<input type="hidden" name="selcal" value="BK-FS">
<input type="hidden" name="planformat" value="lido">
<input type="hidden" name="omit_sids" value="0">
<input type="hidden" name="omit_stars" value="0">
<input type="hidden" name="cruise" value="CI">
<input type="hidden" name="civalue" value="AUTO">
<div class="col-4">
<div class="form-container">
<div class="form-container-body">
<h6><i class="fas fa-info-circle"></i>&nbsp;Planning Options</h6>
<table class="table table-hover table-striped">
<table class="table table-sm table-striped">
<tr>
<td>Cont Fuel:</td>
<td>
<select name="contpct" class="form-control">
<option value="0">None</option>
<option value="auto">AUTO</option>
<option value="0">0 PCT</option>
<option value="0.02">2 PCT</option>
<option value="0.03">3 PCT</option>
<option value="0.05" selected>5 PCT</option>
<option value="0.1">10 PCT</option>
<option value="0.15">15 PCT</option>
<option value="0.2">20 PCT</option>
<option value="easa">EASA</option>
<option value="0.03/5">3% or 05 MIN</option>
<option value="0.03/10">3% or 10 MIN</option>
<option value="0.03/15">3% or 15 MIN</option>
<option value="0.05/5" selected>5% or 05 MIN</option>
<option value="0.05/10">5% or 10 MIN</option>
<option value="0.05/15">5% or 15 MIN</option>
<option value="0.03">3%</option>
<option value="0.05">5%</option>
<option value="0.1">10%</option>
<option value="0.15">15%</option>
<option value="3">03 MIN</option>
<option value="5">05 MIN</option>
<option value="10">10 MIN</option>
<option value="15">15 MIN</option>
</select>
</td>
</tr>
@@ -255,8 +232,8 @@
</div>
<br>
<div class="form-container-body">
<h6><i class="fas fa-info-circle"></i>&nbsp;@lang('stisla.briefingoptions')</h6>
<table class="table table-hover table-striped">
<h6><i class="fas fa-info-circle"></i>&nbsp;Briefing Options</h6>
<table class="table table-sm table-striped">
<tr>
<td>Units:</td>
<td>
@@ -324,8 +301,8 @@
<div class="float-right">
<div class="form-group">
<input type="button"
onclick="simbriefsubmit('{{ $flight->id }}', '{{ url(route('frontend.simbrief.briefing', [''])) }}');"
class="btn btn-primary" value="Generate">
onclick="simbriefsubmit('{{ $flight->id }}', '{{ $aircraft->id }}', '{{ url(route('frontend.simbrief.briefing', [''])) }}');"
class="btn btn-primary" value="Generate">
</div>
</div>
</div>
@@ -373,7 +350,7 @@
}
depm = ("0" + depm).slice(-2);
dept = deph + depm;
dept = deph + ":" + depm;
let dof = ("0" + d.getUTCDate()).slice(-2) + months[d.getUTCMonth()] + d.getUTCFullYear();
document.getElementById("dof").setAttribute('value', dof);
@@ -397,20 +374,4 @@
document.getElementById("steh").setAttribute('value', rhours.toString()); // Sent to Simbrief
document.getElementById("stem").setAttribute('value', rminutes.toString()); // Sent to Simbrief
</script>
<script type="text/javascript">
// *** Simple Aircraft Selection With Dropdown Change
// *** Also keep Generate button hidden until a valid AC selection
const $oldlink = document.getElementById("mylink").href;
function checkacselection() {
if (document.getElementById("aircraftselection").value === "ZZZZZ") {
document.getElementById('mylink').style.visibility = 'hidden';
} else {
document.getElementById('mylink').style.visibility = 'visible';
}
var $selectedac = document.getElementById("aircraftselection").value;
var $newlink = "&aircraft_id=".concat($selectedac);
document.getElementById("mylink").href = $oldlink.concat($newlink);
}
</script>
@endsection

View File

@@ -70,12 +70,22 @@
</div>
<div class="row">
<div class="col-sm-12 text-right">
<!-- Simbrief enabled -->
@if ($simbrief !== false)
@if ($simbrief_bids === false || ($simbrief_bids === true && in_array($flight->id, $saved, true)))
<a href="{{ route('frontend.simbrief.generate') }}?flight_id={{ $flight->id }}"
<!-- If this flight has a briefing, show the link to view it-->
@if ($flight->simbrief && $flight->simbrief->user_id === $user->id)
<a href="{{ route('frontend.simbrief.briefing', $flight->simbrief->id) }}"
class="btn btn-sm btn-outline-primary">
Create SimBrief Flight Plan
View Simbrief Flight Plan
</a>
@else
<!-- Show button if the bids-only is disable, or if bids-only is enabled, they've saved it -->
@if ($simbrief_bids === false || ($simbrief_bids === true && in_array($flight->id, $saved, true)))
<a href="{{ route('frontend.simbrief.generate') }}?flight_id={{ $flight->id }}"
class="btn btn-sm btn-outline-primary">
Create Simbrief Flight Plan
</a>
@endif
@endif
@endif

View File

@@ -9,7 +9,7 @@
<div class="col">
{{Form::label('fare_'.$fare->id, $fare->name.' ('. \App\Models\Enums\FareType::label($fare->type).', code '.$fare->code.')')}}
<div class="input-group form-group">
{{ Form::number('fare_'.$fare->id, null, ['class' => 'form-control', 'min' => 0]) }}
{{ Form::number('fare_'.$fare->id, $fare->count, ['class' => 'form-control', 'min' => 0]) }}
</div>
</div>
</div>

View File

@@ -12,6 +12,7 @@ use App\Models\News;
use App\Models\Subfleet;
use App\Models\User;
use App\Services\FareService;
use App\Support\Utils;
use Exception;
use function random_int;
@@ -312,4 +313,21 @@ class ApiTest extends TestCase
$this->assertNotNull($user);
$this->assertTrue(strpos($user['avatar'], 'gravatar') !== -1);
}
/**
* Test that the web cron runs
*/
public function testWebCron()
{
$this->updateSetting('cron.random_id', '');
$this->get('/api/cron/sdf')->assertStatus(400);
$id = Utils::generateNewId(24);
$this->updateSetting('cron.random_id', $id);
$this->get('/api/cron/sdf')->assertStatus(400);
$res = $this->get('/api/cron/'.$id);
$res->assertStatus(200);
}
}

View File

@@ -151,6 +151,19 @@ class MetarTest extends TestCase
$this->assertEquals('Few at 1500 feet; few at 25000 feet', $metar['clouds_report_ft']);
}
/**
* https://github.com/nabeelio/phpvms/issues/1071
*/
public function testMetarWindSpeedChill()
{
$metar = 'EKYT 091020Z /////KT CAVOK 02/M03 Q1019';
$metar = Metar::parse($metar);
$this->assertEquals('VFR', $metar['category']);
$this->assertNull($metar['wind_speed']);
$this->assertEquals(6.21, $metar['visibility']['mi']);
}
/**
* Visibility in KM not parsed
*

View File

@@ -21,6 +21,7 @@ use App\Services\BidService;
use App\Services\FlightService;
use App\Services\PirepService;
use App\Services\UserService;
use App\Support\Units\Fuel;
use Carbon\Carbon;
use Illuminate\Support\Facades\Notification;
@@ -164,6 +165,8 @@ class PIREPTest extends TestCase
$body = $response->json('data');
// Check that it has the fuel units
$this->assertHasKeys($body['block_fuel'], ['lbs', 'kg']);
$this->assertEquals($pirep->block_fuel, $body['block_fuel']['lbs']);
$this->assertHasKeys($body['fuel_used'], ['lbs', 'kg']);
$this->assertEquals($pirep->fuel_used, $body['fuel_used']['lbs']);
@@ -174,6 +177,24 @@ class PIREPTest extends TestCase
// Check the planned_distance field
$this->assertHasKeys($body['planned_distance'], ['km', 'nmi', 'mi']);
$this->assertEquals($pirep->planned_distance, $body['planned_distance']['nmi']);
//Check conversion on save
$val = random_int(1000, 9999999);
$pirep->block_fuel = $val;
$pirep->fuel_used = $val;
// no conversion with plain numbers
$this->assertEquals($pirep->block_fuel, $val);
$this->assertEquals($pirep->fuel_used, $val);
// no conversion with lbs
$pirep->block_fuel = new Fuel($val, 'lbs');
$this->assertEquals($pirep->block_fuel, $val);
$pirep->fuel_used = new Fuel($val, 'lbs');
$this->assertEquals($pirep->fuel_used, $val);
// conversion of kg to lbs
$pirep->block_fuel = new Fuel($val, 'kg');
$this->assertEquals($pirep->block_fuel, (new Fuel($val, 'kg'))->toUnit('lbs'));
$pirep->fuel_used = new Fuel($val, 'kg');
$this->assertEquals($pirep->fuel_used, (new Fuel($val, 'kg'))->toUnit('lbs'));
}
public function testGetUserPireps()

View File

@@ -3,7 +3,10 @@
namespace Tests;
use App\Models\Acars;
use App\Models\Aircraft;
use App\Models\Enums\AcarsType;
use App\Models\Enums\FareType;
use App\Models\Enums\UserState;
use App\Models\Flight;
use App\Models\Pirep;
use App\Models\SimBrief;
@@ -16,22 +19,70 @@ class SimBriefTest extends TestCase
{
private static $simbrief_flight_id = 'simbriefflightid';
/**
* @param array $attrs Additional user attributes
*
* @throws \Exception
*
* @return array
*/
public function createUserData(array $attrs = []): array
{
$subfleet = $this->createSubfleetWithAircraft(1);
$rank = $this->createRank(2, [$subfleet['subfleet']->id]);
/** @var User $user */
$user = factory(User::class)->create(array_merge([
'flight_time' => 1000,
'rank_id' => $rank->id,
'state' => UserState::ACTIVE,
], $attrs));
return [
'subfleet' => $subfleet['subfleet'],
'aircraft' => $subfleet['aircraft'],
'user' => $user,
];
}
/**
* Load SimBrief
*
* @param \App\Models\User $user
* @param \App\Models\User $user
* @param \App\Models\Aircraft|null $aircraft
* @param array $fares
* @param string|null $flight_id
*
* @return \App\Models\SimBrief
*/
protected function loadSimBrief(User $user): SimBrief
protected function loadSimBrief(User $user, Aircraft $aircraft, $fares = [], $flight_id = null): SimBrief
{
if (empty($flight_id)) {
$flight_id = self::$simbrief_flight_id;
}
/** @var \App\Models\Flight $flight */
$flight = factory(Flight::class)->create([
'id' => self::$simbrief_flight_id,
'id' => $flight_id,
'dpt_airport_id' => 'OMAA',
'arr_airport_id' => 'OMDB',
]);
return $this->downloadOfp($user, $flight, $aircraft, $fares);
}
/**
* Download an OFP file
*
* @param $user
* @param $flight
* @param $aircraft
* @param $fares
*
* @return \App\Models\SimBrief|null
*/
protected function downloadOfp($user, $flight, $aircraft, $fares)
{
$this->mockXmlResponse([
'simbrief/briefing.xml',
'simbrief/acars_briefing.xml',
@@ -40,7 +91,7 @@ class SimBriefTest extends TestCase
/** @var SimBriefService $sb */
$sb = app(SimBriefService::class);
return $sb->downloadOfp($user->id, Utils::generateNewId(), $flight->id);
return $sb->downloadOfp($user->id, Utils::generateNewId(), $flight->id, $aircraft->id, $fares);
}
/**
@@ -48,8 +99,9 @@ class SimBriefTest extends TestCase
*/
public function testReadSimbrief()
{
$this->user = $this->createUser();
$briefing = $this->loadSimBrief($this->user);
$userinfo = $this->createUserData();
$this->user = $userinfo['user'];
$briefing = $this->loadSimBrief($this->user, $userinfo['aircraft']->first(), []);
$this->assertNotEmpty($briefing->ofp_xml);
$this->assertNotNull($briefing->xml);
@@ -87,8 +139,18 @@ class SimBriefTest extends TestCase
*/
public function testApiCalls()
{
$this->user = $this->createUser();
$briefing = $this->loadSimBrief($this->user);
$userinfo = $this->createUserData();
$this->user = $userinfo['user'];
$briefing = $this->loadSimBrief($this->user, $userinfo['aircraft']->first(), [
[
'id' => 100,
'code' => 'F',
'name' => 'Test Fare',
'type' => 'P',
'capacity' => 100,
'count' => 99,
],
]);
// Check the flight API response
$response = $this->get('/api/flights/'.$briefing->flight_id);
@@ -99,10 +161,11 @@ class SimBriefTest extends TestCase
$this->assertEquals($briefing->id, $flight['simbrief']['id']);
$url = str_replace('http://', 'https://', $flight['simbrief']['url']);
$this->assertEquals(
/*$this->assertEquals(
'https://localhost/api/flights/'.$briefing->id.'/briefing',
$url
);
);*/
$this->assertTrue(str_ends_with($url, $briefing->id.'/briefing'));
// Retrieve the briefing via API, and then check the doctype
$response = $this->get('/api/flights/'.$briefing->id.'/briefing', [], $this->user);
@@ -120,8 +183,20 @@ class SimBriefTest extends TestCase
*/
public function testUserBidSimbrief()
{
$this->user = $this->createUser();
$this->loadSimBrief($this->user);
$fares = [
[
'id' => 100,
'code' => 'F',
'name' => 'Test Fare',
'type' => FareType::PASSENGER,
'capacity' => 100,
'count' => 99,
],
];
$userinfo = $this->createUserData();
$this->user = $userinfo['user'];
$this->loadSimBrief($this->user, $userinfo['aircraft']->first(), $fares);
// Find the flight
$uri = '/api/user/bids';
@@ -132,18 +207,91 @@ class SimBriefTest extends TestCase
// Make sure Simbrief is there
$this->assertNotNull($body['flight']['simbrief']['id']);
$this->assertNotNull($body['flight']['simbrief']['subfleet']['fares']);
$subfleet = $body['flight']['simbrief']['subfleet'];
$this->assertEquals($fares[0]['id'], $subfleet['fares'][0]['id']);
$this->assertEquals($fares[0]['count'], $subfleet['fares'][0]['count']);
}
/**
* Make sure that the bids/simbrief created for the same flight by two different
* users doesn't leak across users
*
* @throws \Exception
*/
public function testUserBidSimbriefDoesntLeak()
{
$this->updateSetting('bids.disable_flight_on_bid', false);
$fares = [
[
'id' => 100,
'code' => 'F',
'name' => 'Test Fare',
'type' => FareType::PASSENGER,
'capacity' => 100,
'count' => 99,
],
];
/** @var \App\Models\Flight $flight */
$flight = factory(Flight::class)->create();
// Create two briefings and make sure it doesn't leak
$userinfo2 = $this->createUserData();
$user2 = $userinfo2['user'];
$this->downloadOfp($user2, $flight, $userinfo2['aircraft']->first(), $fares);
$userinfo = $this->createUserData();
$user = $userinfo['user'];
$briefing = $this->downloadOfp($user, $flight, $userinfo['aircraft']->first(), $fares);
// Add the flight to the user's bids
$uri = '/api/user/bids';
$data = ['flight_id' => $flight->id];
// add for both users
$body = $this->put($uri, $data, [], $user2)->json('data');
$this->assertNotEmpty($body);
$body = $this->put($uri, $data, [], $user)->json('data');
$this->assertNotEmpty($body);
$body = $this->get('/api/user/bids', [], $user);
$body = $body->json('data')[0];
// Make sure Simbrief is there
$this->assertNotNull($body['flight']['simbrief']['id']);
$this->assertNotNull($body['flight']['simbrief']['subfleet']['fares']);
$this->assertEquals($body['flight']['simbrief']['id'], $briefing->id);
$subfleet = $body['flight']['simbrief']['subfleet'];
$this->assertEquals($fares[0]['id'], $subfleet['fares'][0]['id']);
$this->assertEquals($fares[0]['count'], $subfleet['fares'][0]['count']);
}
public function testAttachToPirep()
{
$user = $this->createUser();
$userinfo = $this->createUserData();
$this->user = $userinfo['user'];
/** @var Pirep $pirep */
$pirep = factory(Pirep::class)->create([
'user_id' => $user->id,
'user_id' => $this->user->id,
'dpt_airport_id' => 'OMAA',
'arr_airport_id' => 'OMDB',
]);
$briefing = $this->loadSimBrief($user);
$briefing = $this->loadSimBrief($this->user, $userinfo['aircraft']->first(), [
[
'id' => 100,
'code' => 'F',
'name' => 'Test Fare',
'type' => 'P',
'capacity' => 100,
'count' => 99,
],
]);
/** @var SimBriefService $sb */
$sb = app(SimBriefService::class);
@@ -172,7 +320,9 @@ class SimBriefTest extends TestCase
*/
public function testClearExpiredBriefs()
{
$user = $this->createUser();
$userinfo = $this->createUserData();
$user = $userinfo['user'];
$sb_ignored = factory(SimBrief::class)->create([
'user_id' => $user->id,
'flight_id' => 'a_flight_id',

View File

@@ -2,6 +2,7 @@
namespace Tests;
use App\Repositories\KvpRepository;
use App\Support\ICAO;
use App\Support\Units\Time;
use App\Support\Utils;
@@ -15,6 +16,24 @@ class UtilsTest extends TestCase
$this->assertNotNull($carbon);
}
/**
* Simple test for KVP
*/
public function testKvp()
{
/** @var KvpRepository $kvpRepo */
$kvpRepo = app(KvpRepository::class);
$kvpRepo->save('testkey', 'some value');
$this->assertEquals('some value', $kvpRepo->get('testkey'));
// test that default value is working
$this->assertEquals('default value', $kvpRepo->get('unknownkey', 'default value'));
// try saving an integer
$kvpRepo->save('intval', 1);
$this->assertEquals(1, $kvpRepo->get('intval'));
}
/**
* @throws \Exception
*/