Add importers in console and admin for flights/aircraft/subfleets and airport #194
This commit is contained in:
@@ -114,15 +114,15 @@ class FlightService extends Service
|
||||
|
||||
/**
|
||||
* Update any custom PIREP fields
|
||||
* @param Flight $flight_id
|
||||
* @param array $field_values
|
||||
* @param Flight $flight
|
||||
* @param array $field_values
|
||||
*/
|
||||
public function updateCustomFields(Flight $flight_id, array $field_values): void
|
||||
public function updateCustomFields(Flight $flight, array $field_values): void
|
||||
{
|
||||
foreach ($field_values as $fv) {
|
||||
FlightFieldValue::updateOrCreate(
|
||||
[
|
||||
'flight_id' => $flight_id,
|
||||
'flight_id' => $flight->id,
|
||||
'name' => $fv['name'],
|
||||
],
|
||||
[
|
||||
|
||||
90
app/Services/Import/AircraftImporter.php
Normal file
90
app/Services/Import/AircraftImporter.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Import;
|
||||
|
||||
use App\Interfaces\ImportExport;
|
||||
use App\Models\Aircraft;
|
||||
use App\Models\Enums\AircraftState;
|
||||
use App\Models\Enums\AircraftStatus;
|
||||
use App\Models\Subfleet;
|
||||
use App\Support\ICAO;
|
||||
|
||||
/**
|
||||
* Import aircraft
|
||||
* @package App\Services\Import
|
||||
*/
|
||||
class AircraftImporter extends ImportExport
|
||||
{
|
||||
/**
|
||||
* All of the columns that are in the CSV import
|
||||
* Should match the database fields, for the most part
|
||||
*/
|
||||
public static $columns = [
|
||||
'subfleet',
|
||||
'name',
|
||||
'registration',
|
||||
'hex_code',
|
||||
'status',
|
||||
];
|
||||
|
||||
/**
|
||||
* Find the subfleet specified, or just create it on the fly
|
||||
* @param $type
|
||||
* @return Subfleet|\Illuminate\Database\Eloquent\Model|null|object|static
|
||||
*/
|
||||
protected function getSubfleet($type)
|
||||
{
|
||||
$subfleet = Subfleet::where(['type' => $type])->first();
|
||||
if (!$subfleet) {
|
||||
$subfleet = new Subfleet([
|
||||
'type' => $type,
|
||||
'name' => $type,
|
||||
]);
|
||||
|
||||
$subfleet->save();
|
||||
}
|
||||
|
||||
return $subfleet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a flight, parse out the different rows
|
||||
* @param array $row
|
||||
* @param int $index
|
||||
* @return bool
|
||||
*/
|
||||
public function import(array $row, $index)
|
||||
{
|
||||
$subfleet = $this->getSubfleet($row['subfleet']);
|
||||
|
||||
$row['subfleet_id'] = $subfleet->id;
|
||||
|
||||
# Generate a hex code
|
||||
if(!$row['hex_code']) {
|
||||
$row['hex_code'] = ICAO::createHexCode();
|
||||
}
|
||||
|
||||
# Set a default status
|
||||
if($row['status'] === null) {
|
||||
$row['status'] = AircraftStatus::ACTIVE;
|
||||
}
|
||||
|
||||
# Just set its state right now as parked
|
||||
$row['state'] = AircraftState::PARKED;
|
||||
|
||||
# Try to add or update
|
||||
$aircraft = Aircraft::firstOrNew([
|
||||
'registration' => $row['registration'],
|
||||
], $row);
|
||||
|
||||
try {
|
||||
$aircraft->save();
|
||||
} catch(\Exception $e) {
|
||||
$this->status = 'Error in row '.$index.': '.$e->getMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->status = 'Imported '.$row['registration'].' '.$row['name'];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
55
app/Services/Import/AirportImporter.php
Normal file
55
app/Services/Import/AirportImporter.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Import;
|
||||
|
||||
use App\Interfaces\ImportExport;
|
||||
use App\Models\Airport;
|
||||
|
||||
/**
|
||||
* Import airports
|
||||
* @package App\Services\Import
|
||||
*/
|
||||
class AirportImporter extends ImportExport
|
||||
{
|
||||
/**
|
||||
* All of the columns that are in the CSV import
|
||||
* Should match the database fields, for the most part
|
||||
*/
|
||||
public static $columns = [
|
||||
'iata',
|
||||
'icao',
|
||||
'name',
|
||||
'location',
|
||||
'country',
|
||||
'timezone',
|
||||
'hub',
|
||||
'lat',
|
||||
'lon',
|
||||
];
|
||||
|
||||
/**
|
||||
* Import a flight, parse out the different rows
|
||||
* @param array $row
|
||||
* @param int $index
|
||||
* @return bool
|
||||
*/
|
||||
public function import(array $row, $index)
|
||||
{
|
||||
$row['id'] = $row['icao'];
|
||||
$row['hub'] = get_truth_state($row['hub']);
|
||||
|
||||
$airport = Airport::firstOrNew([
|
||||
'id' => $row['icao']
|
||||
], $row);
|
||||
|
||||
try {
|
||||
$airport->save();
|
||||
} catch(\Exception $e) {
|
||||
$this->status = 'Error in row '.$index.': '.$e->getMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->status = 'Imported ' . $row['icao'];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
168
app/Services/Import/FlightImporter.php
Normal file
168
app/Services/Import/FlightImporter.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Import;
|
||||
|
||||
use App\Interfaces\ImportExport;
|
||||
use App\Models\Enums\FlightType;
|
||||
use App\Models\Fare;
|
||||
use App\Models\Flight;
|
||||
use App\Models\Subfleet;
|
||||
use App\Repositories\AirlineRepository;
|
||||
use App\Services\FareService;
|
||||
use App\Services\FlightService;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* The flight importer can be imported or export. Operates on rows
|
||||
*
|
||||
* @package App\Services\Import
|
||||
*/
|
||||
class FlightImporter extends ImportExport
|
||||
{
|
||||
/**
|
||||
* All of the columns that are in the CSV import
|
||||
* Should match the database fields, for the most part
|
||||
*/
|
||||
public static $columns = [
|
||||
'airline',
|
||||
'flight_number',
|
||||
'route_code',
|
||||
'route_leg',
|
||||
'dpt_airport_id',
|
||||
'arr_airport_id',
|
||||
'alt_airport_id',
|
||||
'days',
|
||||
'dpt_time',
|
||||
'arr_time',
|
||||
'level',
|
||||
'distance',
|
||||
'flight_time',
|
||||
'flight_type',
|
||||
'route',
|
||||
'notes',
|
||||
'active',
|
||||
'subfleets',
|
||||
'fares',
|
||||
'fields',
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private $airlineRepo,
|
||||
$fareSvc,
|
||||
$flightSvc;
|
||||
|
||||
/**
|
||||
* FlightImportExporter constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->airlineRepo = app(AirlineRepository::class);
|
||||
$this->fareSvc = app(FareService::class);
|
||||
$this->flightSvc = app(FlightService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a flight, parse out the different rows
|
||||
* @param array $row
|
||||
* @param int $index
|
||||
* @return bool
|
||||
*/
|
||||
public function import(array $row, $index)
|
||||
{
|
||||
// Get the airline ID from the ICAO code
|
||||
$airline = $this->getAirline($row['airline']);
|
||||
|
||||
// Try to find this flight
|
||||
$flight = Flight::firstOrNew([
|
||||
'airline_id' => $airline->id,
|
||||
'flight_number' => $row['flight_number'],
|
||||
'route_code' => $row['route_code'],
|
||||
'route_leg' => $row['route_leg'],
|
||||
], $row);
|
||||
|
||||
// Any specific transformations
|
||||
// Flight type can be set to P - Passenger, C - Cargo, or H - Charter
|
||||
$flight->setAttribute('flight_type', FlightType::getFromCode($row['flight_type']));
|
||||
$flight->setAttribute('active', get_truth_state($row['active']));
|
||||
|
||||
try {
|
||||
$flight->save();
|
||||
} catch (\Exception $e) {
|
||||
$this->status = 'Error in row '.$index.': '.$e->getMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->processSubfleets($flight, $row['subfleets']);
|
||||
$this->processFares($flight, $row['fares']);
|
||||
$this->processFields($flight, $row['fields']);
|
||||
|
||||
$this->status = 'Imported row '.$index;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse out all of the subfleets and associate them to the flight
|
||||
* The subfleet is created if it doesn't exist
|
||||
* @param Flight $flight
|
||||
* @param $col
|
||||
*/
|
||||
protected function processSubfleets(Flight &$flight, $col): void
|
||||
{
|
||||
$count = 0;
|
||||
$subfleets = $this->parseMultiColumnValues($col);
|
||||
foreach($subfleets as $subfleet_type) {
|
||||
$subfleet = Subfleet::firstOrNew(
|
||||
['type' => $subfleet_type],
|
||||
['name' => $subfleet_type]
|
||||
);
|
||||
|
||||
$subfleet->save();
|
||||
|
||||
# sync
|
||||
$flight->subfleets()->syncWithoutDetaching([$subfleet->id]);
|
||||
$count ++;
|
||||
}
|
||||
|
||||
Log::info('Subfleets added/processed: '.$count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse all of the fares in the multi-format
|
||||
* @param Flight $flight
|
||||
* @param $col
|
||||
*/
|
||||
protected function processFares(Flight &$flight, $col): void
|
||||
{
|
||||
$fares = $this->parseMultiColumnValues($col);
|
||||
foreach ($fares as $fare_code => $fare_attributes) {
|
||||
if (\is_int($fare_code)) {
|
||||
$fare_code = $fare_attributes;
|
||||
$fare_attributes = [];
|
||||
}
|
||||
|
||||
$fare = Fare::firstOrNew(['code' => $fare_code], ['name' => $fare_code]);
|
||||
$this->fareSvc->setForFlight($flight, $fare, $fare_attributes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse all of the subfields
|
||||
* @param Flight $flight
|
||||
* @param $col
|
||||
*/
|
||||
protected function processFields(Flight &$flight, $col): void
|
||||
{
|
||||
$pass_fields = [];
|
||||
$fields = $this->parseMultiColumnValues($col);
|
||||
foreach($fields as $field_name => $field_value) {
|
||||
$pass_fields[] = [
|
||||
'name' => $field_name,
|
||||
'value' => $field_value,
|
||||
];
|
||||
}
|
||||
|
||||
$this->flightSvc->updateCustomFields($flight, $pass_fields);
|
||||
}
|
||||
}
|
||||
54
app/Services/Import/SubfleetImporter.php
Normal file
54
app/Services/Import/SubfleetImporter.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Import;
|
||||
|
||||
use App\Interfaces\ImportExport;
|
||||
use App\Models\Subfleet;
|
||||
|
||||
/**
|
||||
* Import subfleets
|
||||
* @package App\Services\Import
|
||||
*/
|
||||
class SubfleetImporter extends ImportExport
|
||||
{
|
||||
/**
|
||||
* All of the columns that are in the CSV import
|
||||
* Should match the database fields, for the most part
|
||||
*/
|
||||
public static $columns = [
|
||||
'airline',
|
||||
'type',
|
||||
'name',
|
||||
];
|
||||
|
||||
/**
|
||||
* Import a flight, parse out the different rows
|
||||
* @param array $row
|
||||
* @param int $index
|
||||
* @return bool
|
||||
*/
|
||||
public function import(array $row, $index)
|
||||
{
|
||||
$airline = $this->getAirline($row['airline']);
|
||||
if(!$airline) {
|
||||
$this->status = 'Airline '.$row['airline'].' not found, row: '.$index;
|
||||
return false;
|
||||
}
|
||||
|
||||
$row['airline_id'] = $airline->id;
|
||||
|
||||
$subfleet = Subfleet::firstOrNew([
|
||||
'type' => $row['type']
|
||||
], $row);
|
||||
|
||||
try {
|
||||
$subfleet->save();
|
||||
} catch(\Exception $e) {
|
||||
$this->status = 'Error in row '.$index.': '.$e->getMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->status = 'Imported ' . $row['type'];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,15 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Interfaces\ImportExport;
|
||||
use App\Interfaces\Service;
|
||||
use App\Models\Airport;
|
||||
use App\Repositories\FlightRepository;
|
||||
use App\Services\Import\AircraftImporter;
|
||||
use App\Services\Import\AirportImporter;
|
||||
use App\Services\Import\FlightImporter;
|
||||
use App\Services\Import\SubfleetImporter;
|
||||
use League\Csv\Reader;
|
||||
|
||||
/**
|
||||
* Class ImporterService
|
||||
@@ -24,75 +31,140 @@ class ImporterService extends Service
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a key-value pair to an array
|
||||
* @param $kvp_str
|
||||
* @param array $arr
|
||||
* @param $csv_file
|
||||
* @return Reader
|
||||
* @throws \League\Csv\Exception
|
||||
*/
|
||||
protected function setKvp($kvp_str, array &$arr)
|
||||
public function openCsv($csv_file)
|
||||
{
|
||||
$item = explode('=', $kvp_str);
|
||||
if (\count($item) === 1) { # just a list?
|
||||
$arr[] = trim($item[0]);
|
||||
} else { # actually a key-value pair
|
||||
$k = trim($item[0]);
|
||||
$v = trim($item[1]);
|
||||
$arr[$k] = $v;
|
||||
}
|
||||
$reader = Reader::createFromPath($csv_file);
|
||||
$reader->setDelimiter(',');
|
||||
$reader->setEnclosure('"');
|
||||
|
||||
return $reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a multi column values field. E.g:
|
||||
* Y?price=200&cost=100; F?price=1200
|
||||
* or
|
||||
* gate=B32;cost index=100
|
||||
*
|
||||
* Converted into a multi-dimensional array
|
||||
*
|
||||
* @param $field
|
||||
* @return array|string
|
||||
* Run the actual importer
|
||||
* @param Reader $reader
|
||||
* @param ImportExport $importer
|
||||
* @return array
|
||||
*/
|
||||
public function parseMultiColumnValues($field)
|
||||
protected function runImport(Reader $reader, ImportExport $importer): array
|
||||
{
|
||||
$ret = [];
|
||||
$split_values = explode(';', $field);
|
||||
$import_report = [
|
||||
'success' => [],
|
||||
'failed' => [],
|
||||
];
|
||||
|
||||
# No multiple values in here, just a straight value
|
||||
if (\count($split_values) === 1) {
|
||||
return $split_values[0];
|
||||
}
|
||||
$cols = $importer->getColumns();
|
||||
$first_header = $cols[0];
|
||||
|
||||
foreach ($split_values as $value) {
|
||||
# This isn't in the query string format, so it's
|
||||
# just a straight key-value pair set
|
||||
if (strpos($value, '?') === false) {
|
||||
$this->setKvp($value, $ret);
|
||||
$records = $reader->getRecords($cols);
|
||||
foreach ($records as $offset => $row) {
|
||||
// check if the first row being read is the header
|
||||
if ($row[$first_header] === $first_header) {
|
||||
continue;
|
||||
}
|
||||
|
||||
# This contains the query string, which turns it
|
||||
# into the multi-level array
|
||||
|
||||
$query_str = explode('?', $value);
|
||||
$parent = trim($query_str[0]);
|
||||
|
||||
$children = [];
|
||||
$kvp = explode('&', trim($query_str[1]));
|
||||
foreach ($kvp as $items) {
|
||||
$this->setKvp($items, $children);
|
||||
$success = $importer->import($row, $offset);
|
||||
if ($success) {
|
||||
$import_report['success'][] = $importer->status;
|
||||
} else {
|
||||
$import_report['failed'][] = $importer->status;
|
||||
}
|
||||
|
||||
$ret[$parent] = $children;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
return $import_report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import aircraft
|
||||
* @param string $csv_file
|
||||
* @param bool $delete_previous
|
||||
* @return mixed
|
||||
* @throws \League\Csv\Exception
|
||||
*/
|
||||
public function importAircraft($csv_file, bool $delete_previous = true)
|
||||
{
|
||||
if ($delete_previous) {
|
||||
# TODO: delete airports
|
||||
}
|
||||
|
||||
$reader = $this->openCsv($csv_file);
|
||||
if (!$reader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$importer = new AircraftImporter();
|
||||
return $this->runImport($reader, $importer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import airports
|
||||
* @param string $csv_file
|
||||
* @param bool $delete_previous
|
||||
* @return mixed
|
||||
* @throws \League\Csv\Exception
|
||||
*/
|
||||
public function importAirports($csv_file, bool $delete_previous = true)
|
||||
{
|
||||
if ($delete_previous) {
|
||||
Airport::truncate();
|
||||
}
|
||||
|
||||
$reader = $this->openCsv($csv_file);
|
||||
if (!$reader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$importer = new AirportImporter();
|
||||
return $this->runImport($reader, $importer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import flights
|
||||
* @param $csv_str
|
||||
* @param bool $delete_previous
|
||||
* @param string $csv_file
|
||||
* @param bool $delete_previous
|
||||
* @return mixed
|
||||
* @throws \League\Csv\Exception
|
||||
*/
|
||||
public function importFlights($csv_str, bool $delete_previous = true)
|
||||
public function importFlights($csv_file, bool $delete_previous = true)
|
||||
{
|
||||
if ($delete_previous) {
|
||||
# TODO: Delete all from: flights, flight_field_values
|
||||
}
|
||||
|
||||
$reader = $this->openCsv($csv_file);
|
||||
if (!$reader) {
|
||||
# TODO: Throw an error
|
||||
return false;
|
||||
}
|
||||
|
||||
$importer = new FlightImporter();
|
||||
return $this->runImport($reader, $importer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import subfleets
|
||||
* @param string $csv_file
|
||||
* @param bool $delete_previous
|
||||
* @return mixed
|
||||
* @throws \League\Csv\Exception
|
||||
*/
|
||||
public function importSubfleets($csv_file, bool $delete_previous = true)
|
||||
{
|
||||
if ($delete_previous) {
|
||||
# TODO: Cleanup subfleet data
|
||||
}
|
||||
|
||||
$reader = $this->openCsv($csv_file);
|
||||
if (!$reader) {
|
||||
# TODO: Throw an error
|
||||
return false;
|
||||
}
|
||||
|
||||
$importer = new SubfleetImporter();
|
||||
return $this->runImport($reader, $importer);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user