073e48c396
* 391 Notification refactorings (#441) * Refactor notifications to allow easier plugins * Notification refactoring * Formatting * Move news to NewsService; cleanup of events * More refactoring; added send email out for news item and the template * Formatting * Formatting * Fix missing newsRepo (#445) * Refactor and add importer to Installer module #443 (#444) * Refactor and add importer to Installer module #443 * Refactor for finances to use in import * Import groups into roles * Formatting * Formatting * Add interface in installer for import * Notes about importing * Check for installer folder * Formatting * Fix pirep->user mapping * Unused import * Formatting * Replace importer with AJAX powered; better error handling #443 (#447) * Replace importer with AJAX powered; better error handling #443 * Formatting * Fix command line importer * Remove bootstrap cache (#448) * Cleanup the bootstrap/cache directory when packaging * Fix removal of bootstrap cache * Formatting * Stricter checks on ACARS API data (#451) * Stricter checks on ACARS API data * More checks * Fix for flight_number check forcing to exist * Allow nullable on flight_id * Avoid proc_open use #455 (#456) * Use PhpExecutableFinder() closes #457 #458 (#460) * Use DateTimeZone instead of int for creating datetime closes #461 * Fix CSV imports giving Storage class not found #454 (#462) * Fix CSV imports giving Storage class not found #454 * Update yarn files for security alert * Add PHP 7.4 support (#464) * Add PHP 7.4 to build matrix * DB fix * YAML parser fix in test data * Show versions * Package updates * Track used ICAOs * 7.4 METAR parsing fix * METAR parser fix * Formatting * Add meters to response units * Call instance for unit conversion * Return value * Catch exception for unknown quantity * Comment fix * Formatting * METAR parsing fixes on PHP 7.4 * Package updates * More random airport ID * More random airport ID * Properly disable toolbar * Semver written out to version file * Use dev as default identifier * Fix BindingResolutionError when debug toolbar isn't present (#465) * Fix BindingResolutionError when debug toolbar isn't present * Formatting * Split the importer module out from the installer module (#468) * Split the importer module out from the installer module * Cleanup of unused imports * Move updater into separate module #453 * Remove unused imports/formatting * Disable the install and importer modules at the end of the setup * Unused imports; update IJ style * test explicit stage for php+mysql * add more to matrix * Add different MariaDB versions * undo * Cleanup Model doc * Pilots cannot use the dashboard or flights without admin rights (#481) * Use auth middleware instead of specific groups for logged in state * Auth check for admin access * Check user admin access for updates * Formatting * Allow nullable field and calculate distance if nulled for flight import #478 (#482) * Check for no roles being attached #480 (#483) * Return the flight fares if there are no subfleet fares #488 (#489) * Return the flight fares if there are no subfleet fares #488 * Formatting * Formatting * Account for units when entering fuel amounts #493 * Search for ICAO not working properly (#496) * /flights and /flights/search direct to the same endpoint * Properly set the distance/planned_distance on save (#497) * 491 Installation Error (#495) * Disable CSRF token * Add error handling around looking up the theme and set a default * Note about logs in issue template * Formatting * Fix GeoService errors when viewing PIREP #498 (#499) * Add new command to export a specific PIREP for debugging (#501) * Set a default model value for airports on PIREP (#500) * Set a default model value for airports on PIREP * Fix airport icao reference * Default airport models * Catch broader exception writing out config files #491 * style * Add reference to docs on doc site (#502) * Properly create/update rows importing #486 (#503) * Add base Dockerfile for Dockerhub builds (#504) * New subfleet not being attached to an airline on import #479 (#505) * Fix subfleet not being attached to an airline on creation in import #479 * Call airline name with optional() around subfleet * Minor cleanup * Search flights by subfleet #484 (#506) * API level search of flights #484 * Add Subfleet to flights page for search * Make the fuel used optional (#512) * Add make to Docker container * Add getRootDomain() to Utils (#514) * Show admin dropdown for admin-access ability (#515) * Show admin dropdown for admin-access ability closes #509 * Formatting * Check user permissions on the routes #508 (#516) * Check user permissions on the routes #508 * Formatting * Return default value on exception for setting() * Correct text for no subfleets #507 (#518) * Add a public_url() helper #513 (#519) * Reduce number of queries for update check (#520) * Try to clear caches before updating (#522) * Try to clear caches before updating * Add clear-compiled to maintenance cache list * Formatting * Set PIREPs page to public (#526) Set PIREPs page to public * Fix live and route map errors #527 (#528) * Add menu bar for mobile (#529) * Format all blade templates to 2 spaces #530 (#531) * Fix PIREP edit endpoint closes #533 (#534) * Fix import during flight cron #532 (#535) * PIREPS resource except for show (#536) * Use optional() around the airport fields (#537) * Use optional() around the airport fields * Add null-coalesce around full_name * Add link to download ACARS config from profile (#539) * Add link to download ACARS config from profile * Formatting * Update xml config file template (#540)
426 lines
12 KiB
PHP
426 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Contracts\Service;
|
|
use App\Events\PirepAccepted;
|
|
use App\Events\PirepFiled;
|
|
use App\Events\PirepRejected;
|
|
use App\Events\UserStatsChanged;
|
|
use App\Exceptions\PirepCancelNotAllowed;
|
|
use App\Models\Acars;
|
|
use App\Models\Enums\AcarsType;
|
|
use App\Models\Enums\PirepSource;
|
|
use App\Models\Enums\PirepState;
|
|
use App\Models\Enums\PirepStatus;
|
|
use App\Models\Navdata;
|
|
use App\Models\Pirep;
|
|
use App\Models\PirepFieldValue;
|
|
use App\Models\User;
|
|
use App\Repositories\PirepRepository;
|
|
use Carbon\Carbon;
|
|
use function count;
|
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class PirepService extends Service
|
|
{
|
|
private $geoSvc;
|
|
private $pilotSvc;
|
|
private $pirepRepo;
|
|
|
|
/**
|
|
* @param GeoService $geoSvc
|
|
* @param PirepRepository $pirepRepo
|
|
* @param UserService $pilotSvc
|
|
*/
|
|
public function __construct(
|
|
GeoService $geoSvc,
|
|
PirepRepository $pirepRepo,
|
|
UserService $pilotSvc
|
|
) {
|
|
$this->geoSvc = $geoSvc;
|
|
$this->pilotSvc = $pilotSvc;
|
|
$this->pirepRepo = $pirepRepo;
|
|
}
|
|
|
|
/**
|
|
* Create a new PIREP with some given fields
|
|
*
|
|
* @param Pirep $pirep
|
|
* @param array PirepFieldValue[] $field_values
|
|
*
|
|
* @return Pirep
|
|
*/
|
|
public function create(Pirep $pirep, array $field_values = []): Pirep
|
|
{
|
|
if (empty($field_values)) {
|
|
$field_values = [];
|
|
}
|
|
|
|
// Check the block times. If a block on (arrival) time isn't
|
|
// specified, then use the time that it was submitted. It won't
|
|
// be the most accurate, but that might be OK
|
|
if (!$pirep->block_on_time) {
|
|
if ($pirep->submitted_at) {
|
|
$pirep->block_on_time = $pirep->submitted_at;
|
|
} else {
|
|
$pirep->block_on_time = Carbon::now('UTC');
|
|
}
|
|
}
|
|
|
|
// If the depart time isn't set, then try to calculate it by
|
|
// subtracting the flight time from the block_on (arrival) time
|
|
if (!$pirep->block_off_time && $pirep->flight_time > 0) {
|
|
$pirep->block_off_time = $pirep->block_on_time->subMinutes($pirep->flight_time);
|
|
}
|
|
|
|
// Check that there's a submit time
|
|
if (!$pirep->submitted_at) {
|
|
$pirep->submitted_at = Carbon::now('UTC');
|
|
}
|
|
|
|
$pirep->status = PirepStatus::ARRIVED;
|
|
|
|
// Copy some fields over from Flight if we have it
|
|
if ($pirep->flight) {
|
|
$pirep->planned_distance = $pirep->flight->distance;
|
|
$pirep->planned_flight_time = $pirep->flight->flight_time;
|
|
}
|
|
|
|
$pirep->save();
|
|
$pirep->refresh();
|
|
|
|
if (count($field_values) > 0) {
|
|
$this->updateCustomFields($pirep->id, $field_values);
|
|
}
|
|
|
|
return $pirep;
|
|
}
|
|
|
|
/**
|
|
* Find if there are duplicates to a given PIREP. Ideally, the passed
|
|
* in PIREP hasn't been saved or gone through the create() method
|
|
*
|
|
* @param Pirep $pirep
|
|
*
|
|
* @return bool|Pirep
|
|
*/
|
|
public function findDuplicate(Pirep $pirep)
|
|
{
|
|
$minutes = setting('pireps.duplicate_check_time', 10);
|
|
$time_limit = Carbon::now()->subMinutes($minutes)->toDateTimeString();
|
|
|
|
$where = [
|
|
'user_id' => $pirep->user_id,
|
|
'airline_id' => $pirep->airline_id,
|
|
'flight_number' => $pirep->flight_number,
|
|
];
|
|
|
|
if (filled($pirep->route_code)) {
|
|
$where['route_code'] = $pirep->route_code;
|
|
}
|
|
|
|
if (filled($pirep->route_leg)) {
|
|
$where['route_leg'] = $pirep->route_leg;
|
|
}
|
|
|
|
try {
|
|
$found_pireps = Pirep::where($where)
|
|
->where('state', '!=', PirepState::CANCELLED)
|
|
->where('created_at', '>=', $time_limit)
|
|
->get();
|
|
|
|
if ($found_pireps->count() === 0) {
|
|
return false;
|
|
}
|
|
|
|
return $found_pireps[0];
|
|
} catch (ModelNotFoundException $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save the route into the ACARS table with AcarsType::ROUTE
|
|
* This attempts to create the route from the navdata and the route
|
|
* entered into the PIREP's route field
|
|
*
|
|
* @param Pirep $pirep
|
|
*
|
|
* @throws \Exception
|
|
*
|
|
* @return Pirep
|
|
*/
|
|
public function saveRoute(Pirep $pirep): Pirep
|
|
{
|
|
// Delete all the existing nav points
|
|
Acars::where([
|
|
'pirep_id' => $pirep->id,
|
|
'type' => AcarsType::ROUTE,
|
|
])->delete();
|
|
|
|
// See if a route exists
|
|
if (!filled($pirep->route)) {
|
|
return $pirep;
|
|
}
|
|
|
|
if (!filled($pirep->dpt_airport)) {
|
|
Log::error('saveRoute: dpt_airport not found: '.$pirep->dpt_airport_id);
|
|
return $pirep;
|
|
}
|
|
|
|
$route = $this->geoSvc->getCoordsFromRoute(
|
|
$pirep->dpt_airport_id,
|
|
$pirep->arr_airport_id,
|
|
[$pirep->dpt_airport->lat, $pirep->dpt_airport->lon],
|
|
$pirep->route
|
|
);
|
|
|
|
/**
|
|
* @var $point Navdata
|
|
*/
|
|
$point_count = 1;
|
|
foreach ($route as $point) {
|
|
$acars = new Acars();
|
|
$acars->pirep_id = $pirep->id;
|
|
$acars->type = AcarsType::ROUTE;
|
|
$acars->nav_type = $point->type;
|
|
$acars->order = $point_count;
|
|
$acars->name = $point->id;
|
|
$acars->lat = $point->lat;
|
|
$acars->lon = $point->lon;
|
|
|
|
$acars->save();
|
|
$point_count++;
|
|
}
|
|
|
|
return $pirep;
|
|
}
|
|
|
|
/**
|
|
* Submit the PIREP. Figure out its default state
|
|
*
|
|
* @param Pirep $pirep
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public function submit(Pirep $pirep)
|
|
{
|
|
// Figure out what default state should be. Look at the default
|
|
// behavior from the rank that the pilot is assigned to
|
|
$default_state = PirepState::PENDING;
|
|
if ($pirep->source === PirepSource::ACARS) {
|
|
if ($pirep->user->rank->auto_approve_acars) {
|
|
$default_state = PirepState::ACCEPTED;
|
|
}
|
|
} else {
|
|
if ($pirep->user->rank->auto_approve_manual) {
|
|
$default_state = PirepState::ACCEPTED;
|
|
}
|
|
}
|
|
|
|
Log::info('New PIREP filed', [$pirep]);
|
|
event(new PirepFiled($pirep));
|
|
|
|
// only update the pilot last state if they are accepted
|
|
if ($default_state === PirepState::ACCEPTED) {
|
|
$pirep = $this->accept($pirep);
|
|
} else {
|
|
$pirep->state = $default_state;
|
|
}
|
|
|
|
$pirep->save();
|
|
}
|
|
|
|
/**
|
|
* Cancel a PIREP
|
|
*
|
|
* @param Pirep $pirep
|
|
*
|
|
* @throws \Prettus\Validator\Exceptions\ValidatorException
|
|
*
|
|
* @return Pirep
|
|
*/
|
|
public function cancel(Pirep $pirep): Pirep
|
|
{
|
|
if (in_array($pirep->state, Pirep::$cancel_states, true)) {
|
|
Log::info('PIREP '.$pirep->id.' can\'t be cancelled, state='.$pirep->state);
|
|
|
|
throw new PirepCancelNotAllowed($pirep);
|
|
}
|
|
|
|
$pirep = $this->pirepRepo->update([
|
|
'state' => PirepState::CANCELLED,
|
|
'status' => PirepStatus::CANCELLED,
|
|
], $pirep->id);
|
|
|
|
return $pirep;
|
|
}
|
|
|
|
/**
|
|
* Update any custom PIREP fields
|
|
*
|
|
* @param $pirep_id
|
|
* @param array $field_values
|
|
*/
|
|
public function updateCustomFields($pirep_id, array $field_values)
|
|
{
|
|
foreach ($field_values as $fv) {
|
|
PirepFieldValue::updateOrCreate(
|
|
['pirep_id' => $pirep_id,
|
|
'name' => $fv['name'],
|
|
],
|
|
['value' => $fv['value'],
|
|
'source' => $fv['source'],
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param Pirep $pirep
|
|
* @param int $new_state
|
|
*
|
|
* @throws \Exception
|
|
*
|
|
* @return Pirep
|
|
*/
|
|
public function changeState(Pirep $pirep, int $new_state)
|
|
{
|
|
Log::info('PIREP '.$pirep->id.' state change from '.$pirep->state.' to '.$new_state);
|
|
|
|
if ($pirep->state === $new_state) {
|
|
return $pirep;
|
|
}
|
|
|
|
/*
|
|
* Move from a PENDING status into either ACCEPTED or REJECTED
|
|
*/
|
|
if ($pirep->state === PirepState::PENDING) {
|
|
if ($new_state === PirepState::ACCEPTED) {
|
|
return $this->accept($pirep);
|
|
}
|
|
|
|
if ($new_state === PirepState::REJECTED) {
|
|
return $this->reject($pirep);
|
|
}
|
|
|
|
return $pirep;
|
|
}
|
|
|
|
/*
|
|
* Move from a ACCEPTED to REJECTED status
|
|
*/
|
|
if ($pirep->state === PirepState::ACCEPTED) {
|
|
$pirep = $this->reject($pirep);
|
|
return $pirep;
|
|
}
|
|
|
|
/*
|
|
* Move from REJECTED to ACCEPTED
|
|
*/
|
|
if ($pirep->state === PirepState::REJECTED) {
|
|
$pirep = $this->accept($pirep);
|
|
return $pirep;
|
|
}
|
|
|
|
return $pirep->refresh();
|
|
}
|
|
|
|
/**
|
|
* @param Pirep $pirep
|
|
*
|
|
* @throws \Exception
|
|
*
|
|
* @return Pirep
|
|
*/
|
|
public function accept(Pirep $pirep): Pirep
|
|
{
|
|
// moving from a REJECTED state to ACCEPTED, reconcile statuses
|
|
if ($pirep->state === PirepState::ACCEPTED) {
|
|
return $pirep;
|
|
}
|
|
|
|
$ft = $pirep->flight_time;
|
|
$pilot = $pirep->user;
|
|
|
|
$this->pilotSvc->adjustFlightTime($pilot, $ft);
|
|
$this->pilotSvc->adjustFlightCount($pilot, +1);
|
|
$this->pilotSvc->calculatePilotRank($pilot);
|
|
$pirep->user->refresh();
|
|
|
|
// Change the status
|
|
$pirep->state = PirepState::ACCEPTED;
|
|
$pirep->save();
|
|
$pirep->refresh();
|
|
|
|
Log::info('PIREP '.$pirep->id.' state change to ACCEPTED');
|
|
|
|
// Update the aircraft
|
|
$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->save();
|
|
|
|
$pirep->refresh();
|
|
|
|
$this->setPilotState($pilot, $pirep);
|
|
event(new PirepAccepted($pirep));
|
|
|
|
return $pirep;
|
|
}
|
|
|
|
/**
|
|
* @param Pirep $pirep
|
|
*
|
|
* @return Pirep
|
|
*/
|
|
public function reject(Pirep $pirep): Pirep
|
|
{
|
|
// If this was previously ACCEPTED, then reconcile the flight hours
|
|
// that have already been counted, etc
|
|
if ($pirep->state === PirepState::ACCEPTED) {
|
|
$user = $pirep->user;
|
|
$ft = $pirep->flight_time * -1;
|
|
|
|
$this->pilotSvc->adjustFlightTime($user, $ft);
|
|
$this->pilotSvc->adjustFlightCount($user, -1);
|
|
$this->pilotSvc->calculatePilotRank($user);
|
|
$pirep->user->refresh();
|
|
}
|
|
|
|
// Change the status
|
|
$pirep->state = PirepState::REJECTED;
|
|
$pirep->save();
|
|
$pirep->refresh();
|
|
|
|
$pirep->aircraft->flight_time -= $pirep->flight_time;
|
|
$pirep->aircraft->save();
|
|
|
|
Log::info('PIREP '.$pirep->id.' state change to REJECTED');
|
|
|
|
event(new PirepRejected($pirep));
|
|
|
|
return $pirep;
|
|
}
|
|
|
|
/**
|
|
* @param User $pilot
|
|
* @param Pirep $pirep
|
|
*/
|
|
public function setPilotState(User $pilot, Pirep $pirep)
|
|
{
|
|
$pilot->refresh();
|
|
|
|
$previous_airport = $pilot->curr_airport_id;
|
|
$pilot->curr_airport_id = $pirep->arr_airport_id;
|
|
$pilot->last_pirep_id = $pirep->id;
|
|
$pilot->save();
|
|
|
|
$pirep->refresh();
|
|
|
|
event(new UserStatsChanged($pilot, 'airport', $previous_airport));
|
|
}
|
|
}
|