{{ $errors->first('csv_file') }}
+Logs
+ @foreach($logs['success'] as $line) +{{ $line }}
+ @endforeach + @endif + + @if($logs['errors']) +Errors
+ @foreach($logs['errors'] as $line) +{{ $line }}
+ @endforeach + @endif +diff --git a/app/Http/Controllers/Admin/AircraftController.php b/app/Http/Controllers/Admin/AircraftController.php index 190604e8..45810666 100644 --- a/app/Http/Controllers/Admin/AircraftController.php +++ b/app/Http/Controllers/Admin/AircraftController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Admin; use App\Http\Requests\CreateAircraftRequest; +use App\Http\Requests\ImportRequest; use App\Http\Requests\UpdateAircraftRequest; use App\Interfaces\Controller; use App\Models\Aircraft; @@ -177,6 +178,7 @@ class AircraftController extends Controller * @param Request $request * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View * @throws \League\Csv\Exception + * @throws \Illuminate\Validation\ValidationException */ public function import(Request $request) { @@ -186,6 +188,8 @@ class AircraftController extends Controller ]; if ($request->isMethod('post')) { + ImportRequest::validate($request); + $path = Storage::putFileAs( 'import', $request->file('csv_file'), 'aircraft' ); diff --git a/app/Http/Controllers/Admin/AirlinesController.php b/app/Http/Controllers/Admin/AirlinesController.php index a9e8343a..264faa29 100644 --- a/app/Http/Controllers/Admin/AirlinesController.php +++ b/app/Http/Controllers/Admin/AirlinesController.php @@ -24,9 +24,7 @@ class AirlinesController extends Controller * AirlinesController constructor. * @param AirlineRepository $airlinesRepo */ - public function __construct( - AirlineRepository $airlinesRepo - ) { + public function __construct(AirlineRepository $airlinesRepo) { $this->airlineRepo = $airlinesRepo; } @@ -56,6 +54,7 @@ class AirlinesController extends Controller /** * Store a newly created Airlines in storage. + * @throws \Prettus\Validator\Exceptions\ValidatorException */ public function store(CreateAirlineRequest $request) { @@ -63,7 +62,6 @@ class AirlinesController extends Controller $airlines = $this->airlineRepo->create($input); Flash::success('Airlines saved successfully.'); - return redirect(route('admin.airlines.index')); } @@ -78,7 +76,6 @@ class AirlinesController extends Controller if (empty($airlines)) { Flash::error('Airlines not found'); - return redirect(route('admin.airlines.index')); } @@ -98,7 +95,6 @@ class AirlinesController extends Controller if (empty($airline)) { Flash::error('Airline not found'); - return redirect(route('admin.airlines.index')); } @@ -121,14 +117,12 @@ class AirlinesController extends Controller if (empty($airlines)) { Flash::error('Airlines not found'); - return redirect(route('admin.airlines.index')); } $airlines = $this->airlineRepo->update($request->all(), $id); Flash::success('Airlines updated successfully.'); - return redirect(route('admin.airlines.index')); } @@ -143,14 +137,12 @@ class AirlinesController extends Controller if (empty($airlines)) { Flash::error('Airlines not found'); - return redirect(route('admin.airlines.index')); } $this->airlineRepo->delete($id); Flash::success('Airlines deleted successfully.'); - return redirect(route('admin.airlines.index')); } } diff --git a/app/Http/Controllers/Admin/AirportController.php b/app/Http/Controllers/Admin/AirportController.php index eeb0eaf8..402e2778 100644 --- a/app/Http/Controllers/Admin/AirportController.php +++ b/app/Http/Controllers/Admin/AirportController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Admin; use App\Http\Requests\CreateAirportRequest; +use App\Http\Requests\ImportRequest; use App\Http\Requests\UpdateAirportRequest; use App\Interfaces\Controller; use App\Models\Airport; @@ -87,7 +88,6 @@ class AirportController extends Controller $this->airportRepo->create($input); Flash::success('Airport saved successfully.'); - return redirect(route('admin.airports.index')); } @@ -102,7 +102,6 @@ class AirportController extends Controller if (empty($airport)) { Flash::error('Airport not found'); - return redirect(route('admin.airports.index')); } @@ -122,7 +121,6 @@ class AirportController extends Controller if (empty($airport)) { Flash::error('Airport not found'); - return redirect(route('admin.airports.index')); } @@ -145,7 +143,6 @@ class AirportController extends Controller if (empty($airport)) { Flash::error('Airport not found'); - return redirect(route('admin.airports.index')); } @@ -155,7 +152,6 @@ class AirportController extends Controller $this->airportRepo->update($attrs, $id); Flash::success('Airport updated successfully.'); - return redirect(route('admin.airports.index')); } @@ -203,21 +199,24 @@ class AirportController extends Controller * @param Request $request * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View * @throws \League\Csv\Exception + * @throws \Illuminate\Validation\ValidationException */ public function import(Request $request) { $logs = [ 'success' => [], - 'failed' => [], + 'errors' => [], ]; if ($request->isMethod('post')) { + ImportRequest::validate($request); + $path = Storage::putFileAs( 'import', $request->file('csv_file'), 'airports' ); $path = storage_path('app/'.$path); - Log::info('Uploaded flights import file to '.$path); + Log::info('Uploaded airports import file to '.$path); $logs = $this->importSvc->importAirports($path); } @@ -227,13 +226,12 @@ class AirportController extends Controller } /** - * @param Airport|null $airport + * @param Airport $airport * @return mixed */ - protected function return_expenses_view(?Airport $airport) + protected function return_expenses_view(Airport $airport) { $airport->refresh(); - return view('admin.airports.expenses', [ 'airport' => $airport, ]); diff --git a/app/Http/Controllers/Admin/ExpenseController.php b/app/Http/Controllers/Admin/ExpenseController.php index 5a98a7d9..e6385957 100644 --- a/app/Http/Controllers/Admin/ExpenseController.php +++ b/app/Http/Controllers/Admin/ExpenseController.php @@ -2,15 +2,20 @@ namespace App\Http\Controllers\Admin; +use App\Http\Requests\ImportRequest; use App\Interfaces\Controller; use App\Models\Enums\ExpenseType; use App\Models\Expense; use App\Repositories\AirlineRepository; use App\Repositories\ExpenseRepository; +use App\Services\ExportService; +use App\Services\ImportService; use Flash; use Illuminate\Http\Request; +use Log; use Prettus\Repository\Criteria\RequestCriteria; use Response; +use Storage; /** * Class ExpenseController @@ -19,19 +24,23 @@ use Response; class ExpenseController extends Controller { private $airlineRepo, - $expenseRepo; + $expenseRepo, + $importSvc; /** * expensesController constructor. * @param AirlineRepository $airlineRepo * @param ExpenseRepository $expenseRepo + * @param ImportService $importSvc */ public function __construct( AirlineRepository $airlineRepo, - ExpenseRepository $expenseRepo + ExpenseRepository $expenseRepo, + ImportService $importSvc ) { $this->airlineRepo = $airlineRepo; $this->expenseRepo = $expenseRepo; + $this->importSvc = $importSvc; } /** @@ -157,14 +166,62 @@ class ExpenseController extends Controller if (empty($expenses)) { Flash::error('Expense not found'); - return redirect(route('admin.expenses.index')); } $this->expenseRepo->delete($id); Flash::success('Expense deleted successfully.'); - return redirect(route('admin.expenses.index')); } + + /** + * Run the airport exporter + * @param Request $request + * @return \Symfony\Component\HttpFoundation\BinaryFileResponse + * @throws \League\Csv\Exception + */ + public function export(Request $request) + { + $exporter = app(ExportService::class); + $expenses = $this->expenseRepo->all(); + + $path = $exporter->exportExpenses($expenses); + return response() + ->download($path, 'expenses.csv', [ + 'content-type' => 'text/csv', + ]) + ->deleteFileAfterSend(true); + } + + /** + * + * @param Request $request + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @throws \League\Csv\Exception + * @throws \Illuminate\Validation\ValidationException + */ + public function import(Request $request) + { + $logs = [ + 'success' => [], + 'errors' => [], + ]; + + if ($request->isMethod('post')) { + ImportRequest::validate($request); + + $path = Storage::putFileAs( + 'import', $request->file('csv_file'), 'expenses' + ); + + $path = storage_path('app/'.$path); + Log::info('Uploaded expenses import file to '.$path); + $logs = $this->importSvc->importExpenses($path); + } + + return view('admin.expenses.import', [ + 'logs' => $logs, + ]); + } } diff --git a/app/Http/Controllers/Admin/SubfleetController.php b/app/Http/Controllers/Admin/SubfleetController.php index cde48558..6b46578a 100644 --- a/app/Http/Controllers/Admin/SubfleetController.php +++ b/app/Http/Controllers/Admin/SubfleetController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Admin; use App\Http\Requests\CreateSubfleetRequest; +use App\Http\Requests\ImportRequest; use App\Http\Requests\UpdateSubfleetRequest; use App\Interfaces\Controller; use App\Models\Airline; @@ -245,15 +246,18 @@ class SubfleetController extends Controller * @param Request $request * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View * @throws \League\Csv\Exception + * @throws \Illuminate\Validation\ValidationException */ public function import(Request $request) { $logs = [ 'success' => [], - 'failed' => [], + 'errors' => [], ]; if ($request->isMethod('post')) { + ImportRequest::validate($request); + $path = Storage::putFileAs( 'import', $request->file('csv_file'), 'subfleets' ); diff --git a/app/Http/Requests/CreateAircraftRequest.php b/app/Http/Requests/CreateAircraftRequest.php index 353832d4..09d7b4cf 100644 --- a/app/Http/Requests/CreateAircraftRequest.php +++ b/app/Http/Requests/CreateAircraftRequest.php @@ -9,21 +9,21 @@ class CreateAircraftRequest extends FormRequest { /** * Determine if the user is authorized to make this request. - * * @return bool */ - public function authorize() + public function authorize(): bool { return true; } /** * Get the validation rules that apply to the request. - * * @return array */ - public function rules() + public function rules(): array { - return Aircraft::$rules; + $rules = Aircraft::$rules; + $rules['registration'] .= '|unique:aircraft'; + return $rules; } } diff --git a/app/Http/Requests/ImportRequest.php b/app/Http/Requests/ImportRequest.php new file mode 100644 index 00000000..a1c45632 --- /dev/null +++ b/app/Http/Requests/ImportRequest.php @@ -0,0 +1,36 @@ + 'required|file', + ]; + + /** + * @param Request $request + * @throws \Illuminate\Validation\ValidationException + */ + public static function validate(Request $request) + { + \Validator::make($request->all(), static::$rules)->validate(); + } + + public function authorize(): bool + { + return true; + } + + public function rules(): array + { + return static::$rules; + } +} diff --git a/app/Interfaces/Enum.php b/app/Interfaces/Enum.php index d580f1d1..eaa93773 100644 --- a/app/Interfaces/Enum.php +++ b/app/Interfaces/Enum.php @@ -8,8 +8,9 @@ namespace App\Interfaces; */ abstract class Enum { - protected static $labels = []; protected static $cache = []; + protected static $codes = []; + protected static $labels = []; /** * @var integer @@ -59,6 +60,31 @@ abstract class Enum return $labels; } + /** + * Get the numeric value from a string code + * @param $code + * @return mixed|null + */ + public static function getFromCode($code) + { + $code = strtoupper($code); + if(!array_key_exists($code, static::$codes)) { + return null; + } + + return static::$codes[$code]; + } + + /** + * Convert the integer value into one of the codes + * @param $value + * @return false|int|string + */ + public static function convertToCode($value) + { + return array_search($value, static::$codes, true); + } + /** * Select box entry items * @param bool $add_blank diff --git a/app/Interfaces/ImportExport.php b/app/Interfaces/ImportExport.php index 7ed5e668..e11dfd5b 100644 --- a/app/Interfaces/ImportExport.php +++ b/app/Interfaces/ImportExport.php @@ -3,6 +3,9 @@ namespace App\Interfaces; use App\Models\Airline; +use Illuminate\Validation\ValidationException; +use Log; +use Validator; /** * Common functionality used across all of the importers @@ -11,7 +14,10 @@ use App\Models\Airline; class ImportExport { public $assetType; - public $status; + public $status = [ + 'success' => [], + 'errors' => [], + ]; /** * Hold the columns for the particular table @@ -40,6 +46,54 @@ class ImportExport return static::$columns; } + /** + * Do a basic check that the number of columns match + * @param $row + * @return bool + */ + public function checkColumns($row): bool + { + return \count($row) === \count($this->getColumns()); + } + + /** + * Bubble up an error to the interface that we need to stop + * @param $error + * @param $e + * @throws ValidationException + */ + protected function throwError($error, \Exception $e = null): void + { + Log::error($error); + if ($e) { + Log::error($e->getMessage()); + } + + $validator = Validator::make([], []); + $validator->errors()->add('csv_file', $error); + throw new ValidationException($validator); + } + + /** + * Add to the log messages for this importer + * @param $msg + */ + public function log($msg): void + { + $this->status['success'][] = $msg; + Log::info($msg); + } + + /** + * Add to the error log for this import + * @param $msg + */ + public function errorLog($msg): void + { + $this->status['errors'][] = $msg; + Log::error($msg); + } + /** * Set a key-value pair to an array * @param $kvp_str diff --git a/app/Models/Aircraft.php b/app/Models/Aircraft.php index a3467c2f..f3ca28ef 100644 --- a/app/Models/Aircraft.php +++ b/app/Models/Aircraft.php @@ -51,8 +51,9 @@ class Aircraft extends Model * Validation rules */ public static $rules = [ - 'subfleet_id' => 'required', - 'name' => 'required', + 'subfleet_id' => 'required', + 'name' => 'required', + 'registration' => 'required', ]; /** diff --git a/app/Models/Enums/ExpenseType.php b/app/Models/Enums/ExpenseType.php index 46991a30..9fdb26dc 100644 --- a/app/Models/Enums/ExpenseType.php +++ b/app/Models/Enums/ExpenseType.php @@ -19,4 +19,10 @@ class ExpenseType extends Enum ExpenseType::DAILY => 'Daily', ExpenseType::MONTHLY => 'Monthly', ]; + + protected static $codes = [ + 'F' => ExpenseType::FLIGHT, + 'D' => ExpenseType::DAILY, + 'M' => ExpenseType::MONTHLY, + ]; } diff --git a/app/Models/Enums/FlightType.php b/app/Models/Enums/FlightType.php index 562a5671..96db4ad0 100644 --- a/app/Models/Enums/FlightType.php +++ b/app/Models/Enums/FlightType.php @@ -20,30 +20,9 @@ class FlightType extends Enum FlightType::CHARTER => 'Charter', ]; - /** - * Return value from P, C or H - * @param $code - * @return int - */ - public static function getFromCode($code): int - { - if(is_numeric($code)) { - return (int) $code; - } - - $code = strtolower($code); - if($code === 'p') { - return self::PASSENGER; - } - - if ($code === 'c') { - return self::CARGO; - } - - if($code === 'h') { - return self::CHARTER; - } - - return self::PASSENGER; - } + protected static $codes = [ + 'P' => FlightType::PASSENGER, + 'C' => FlightType::CARGO, + 'H' => FlightType::CHARTER, + ]; } diff --git a/app/Routes/admin.php b/app/Routes/admin.php index a4097bf4..104b910b 100644 --- a/app/Routes/admin.php +++ b/app/Routes/admin.php @@ -25,6 +25,8 @@ Route::group([ Route::resource('aircraft', 'AircraftController'); # expenses + Route::get('expenses/export', 'ExpenseController@export')->name('expenses.export'); + Route::match(['get', 'post'], 'expenses/import', 'ExpenseController@import')->name('expenses.import'); Route::resource('expenses', 'ExpenseController'); # fares diff --git a/app/Services/ExportService.php b/app/Services/ExportService.php index fd3f202c..066e180d 100644 --- a/app/Services/ExportService.php +++ b/app/Services/ExportService.php @@ -6,6 +6,7 @@ use App\Interfaces\ImportExport; use App\Interfaces\Service; use App\Services\ImportExport\AircraftExporter; use App\Services\ImportExport\AirportExporter; +use App\Services\ImportExport\ExpenseExporter; use App\Services\ImportExport\FlightExporter; use Illuminate\Support\Collection; use League\Csv\CharsetConverter; @@ -84,6 +85,18 @@ class ExportService extends Service return $this->runExport($airports, $exporter); } + /** + * Export all of the airports + * @param Collection $expenses + * @return mixed + * @throws \League\Csv\CannotInsertRecord + */ + public function exportExpenses($expenses) + { + $exporter = new ExpenseExporter(); + return $this->runExport($expenses, $exporter); + } + /** * Export all of the flights * @param Collection $flights diff --git a/app/Services/ImportExport/AircraftImporter.php b/app/Services/ImportExport/AircraftImporter.php index f15c6afd..7965a012 100644 --- a/app/Services/ImportExport/AircraftImporter.php +++ b/app/Services/ImportExport/AircraftImporter.php @@ -52,7 +52,6 @@ class AircraftImporter extends ImportExport public function import(array $row, $index): bool { $subfleet = $this->getSubfleet($row['subfleet']); - $row['subfleet_id'] = $subfleet->id; # Generate a hex code @@ -76,11 +75,11 @@ class AircraftImporter extends ImportExport try { $aircraft->save(); } catch(\Exception $e) { - $this->status = 'Error in row '.$index.': '.$e->getMessage(); + $this->errorLog('Error in row '.$index.': '.$e->getMessage()); return false; } - $this->status = 'Imported '.$row['registration'].' '.$row['name']; + $this->log('Imported '.$row['registration'].' '.$row['name']); return true; } } diff --git a/app/Services/ImportExport/AirportImporter.php b/app/Services/ImportExport/AirportImporter.php index 2bd510f1..aee4a2ca 100644 --- a/app/Services/ImportExport/AirportImporter.php +++ b/app/Services/ImportExport/AirportImporter.php @@ -47,11 +47,11 @@ class AirportImporter extends ImportExport try { $airport->save(); } catch(\Exception $e) { - $this->status = 'Error in row '.$index.': '.$e->getMessage(); + $this->errorLog('Error in row '.$index.': '.$e->getMessage()); return false; } - $this->status = 'Imported ' . $row['icao']; + $this->log('Imported '.$row['icao']); return true; } } diff --git a/app/Services/ImportExport/ExpenseExporter.php b/app/Services/ImportExport/ExpenseExporter.php new file mode 100644 index 00000000..a819dea5 --- /dev/null +++ b/app/Services/ImportExport/ExpenseExporter.php @@ -0,0 +1,72 @@ +{$col}; + } + + if($ret['airline']) { + $ret['airline'] = $expense->airline->icao; + } + + $ret['type'] = ExpenseType::convertToCode($ret['type']); + + // For the different expense types, instead of exporting + // the ID, export a specific column + if ($expense->ref_class === Expense::class) { + $ret['ref_class'] = ''; + $ret['ref_class_id'] = ''; + } else { + $obj = $expense->getReference(); + if(!$obj) { // bail out + return $ret; + } + + if ($expense->ref_class === Aircraft::class) { + $ret['ref_class_id'] = $obj->registration; + } elseif ($expense->ref_class === Airport::class) { + $ret['ref_class_id'] = $obj->icao; + } elseif ($expense->ref_class === Subfleet::class) { + $ret['ref_class_id'] = $obj->type; + } + } + + // And convert the ref_class into the shorter name + $ret['ref_class'] = str_replace('App\Models\\', '', $ret['ref_class']); + + return $ret; + } +} diff --git a/app/Services/ImportExport/ExpenseImporter.php b/app/Services/ImportExport/ExpenseImporter.php new file mode 100644 index 00000000..92ec0ece --- /dev/null +++ b/app/Services/ImportExport/ExpenseImporter.php @@ -0,0 +1,113 @@ +getAirline($row['airline'])->id; + } + + # Figure out what this is referring to + $row = $this->getRefClassInfo($row); + + $row['type'] = ExpenseType::getFromCode($row['type']); + + $expense = Expense::firstOrNew([ + 'name' => $row['name'], + ], $row); + + try { + $expense->save(); + } catch (\Exception $e) { + $this->errorLog('Error in row '.$index.': '.$e->getMessage()); + return false; + } + + $this->log('Imported '.$row['name']); + return true; + } + + /** + * See if this expense refers to a ref_class + * @param array $row + * @return array + */ + protected function getRefClassInfo(array $row) + { + $row['ref_class'] = trim($row['ref_class']); + + // class from import is being saved as the name of the model only + // prepend the full class path so we can search it out + if (\strlen($row['ref_class']) > 0) { + if (substr_count($row['ref_class'], 'App\Models\\') === 0) { + $row['ref_class'] = 'App\Models\\'.$row['ref_class']; + } + } else { + $row['ref_class'] = Expense::class; + return $row; + } + + $class = $row['ref_class']; + $id = $row['ref_class_id']; + $obj = null; + + if ($class === Aircraft::class) { + Log::info('Trying to import expense on aircraft, registration: ' . $id); + $obj = Aircraft::where('registration', $id)->first(); + } elseif ($class === Airport::class) { + Log::info('Trying to import expense on airport, icao: ' . $id); + $obj = Airport::where('icao', $id)->first(); + } elseif ($class === Subfleet::class) { + Log::info('Trying to import expense on subfleet, type: ' . $id); + $obj = Subfleet::where('type', $id)->first(); + } else { + $this->errorLog('Unknown/unsupported Expense class: '.$class); + } + + if(!$obj) { + return $row; + } + + $row['ref_class_id'] = $obj->id; + return $row; + } +} diff --git a/app/Services/ImportExport/FlightExporter.php b/app/Services/ImportExport/FlightExporter.php index 71ffb371..68ecb18c 100644 --- a/app/Services/ImportExport/FlightExporter.php +++ b/app/Services/ImportExport/FlightExporter.php @@ -3,6 +3,7 @@ namespace App\Services\ImportExport; use App\Interfaces\ImportExport; +use App\Models\Enums\FlightType; use App\Models\Flight; /** @@ -38,6 +39,8 @@ class FlightExporter extends ImportExport $ret['airline'] = $ret['airline']->icao; $ret['distance'] = $ret['distance']->toNumber(); + $ret['flight_type'] = FlightType::convertToCode($ret['flight_type']); + $ret['fares'] = $this->getFares($flight); $ret['fields'] = $this->getFields($flight); $ret['subfleets'] = $this->getSubfleets($flight); diff --git a/app/Services/ImportExport/FlightImporter.php b/app/Services/ImportExport/FlightImporter.php index 173126bd..4ce014f7 100644 --- a/app/Services/ImportExport/FlightImporter.php +++ b/app/Services/ImportExport/FlightImporter.php @@ -68,7 +68,7 @@ class FlightImporter extends ImportExport * @param int $index * @return bool */ - public function import(array $row, $index) + public function import(array $row, $index): bool { // Get the airline ID from the ICAO code $airline = $this->getAirline($row['airline']); @@ -89,7 +89,7 @@ class FlightImporter extends ImportExport try { $flight->save(); } catch (\Exception $e) { - $this->status = 'Error in row '.$index.': '.$e->getMessage(); + $this->errorLog('Error in row '.$index.': '.$e->getMessage()); return false; } @@ -97,7 +97,7 @@ class FlightImporter extends ImportExport $this->processFares($flight, $row['fares']); $this->processFields($flight, $row['fields']); - $this->status = 'Imported row '.$index; + $this->log('Imported row '.$index); return true; } diff --git a/app/Services/ImportExport/SubfleetImporter.php b/app/Services/ImportExport/SubfleetImporter.php index 937562a5..b383e909 100644 --- a/app/Services/ImportExport/SubfleetImporter.php +++ b/app/Services/ImportExport/SubfleetImporter.php @@ -29,14 +29,9 @@ class SubfleetImporter extends ImportExport * @param int $index * @return bool */ - public function import(array $row, $index) + public function import(array $row, $index): bool { $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([ @@ -46,11 +41,11 @@ class SubfleetImporter extends ImportExport try { $subfleet->save(); } catch(\Exception $e) { - $this->status = 'Error in row '.$index.': '.$e->getMessage(); + $this->errorLog('Error in row '.$index.': '.$e->getMessage()); return false; } - $this->status = 'Imported ' . $row['type']; + $this->log('Imported '.$row['type']); return true; } } diff --git a/app/Services/ImportService.php b/app/Services/ImportService.php index f1bc95a9..5989f3f5 100644 --- a/app/Services/ImportService.php +++ b/app/Services/ImportService.php @@ -5,12 +5,18 @@ namespace App\Services; use App\Interfaces\ImportExport; use App\Interfaces\Service; use App\Models\Airport; +use App\Models\Expense; use App\Repositories\FlightRepository; use App\Services\ImportExport\AircraftImporter; use App\Services\ImportExport\AirportImporter; +use App\Services\ImportExport\ExpenseImporter; use App\Services\ImportExport\FlightImporter; use App\Services\ImportExport\SubfleetImporter; +use Illuminate\Validation\ValidationException; +use League\Csv\Exception; use League\Csv\Reader; +use Log; +use Validator; /** * Class ImportService @@ -29,51 +35,76 @@ class ImportService extends Service } /** - * @param $csv_file - * @return Reader - * @throws \League\Csv\Exception + * @param $error + * @param $e + * @throws ValidationException */ - public function openCsv($csv_file) + protected function throwError($error, \Exception $e= null): void { - $reader = Reader::createFromPath($csv_file); - $reader->setDelimiter(','); - $reader->setEnclosure('"'); + Log::error($error); + if($e) { + Log::error($e->getMessage()); + } - return $reader; + $validator = Validator::make([], []); + $validator->errors()->add('csv_file', $error); + throw new ValidationException($validator); } /** - * Run the actual importer + * @param $csv_file + * @return Reader + * @throws ValidationException + */ + public function openCsv($csv_file) + { + try { + $reader = Reader::createFromPath($csv_file); + $reader->setDelimiter(','); + $reader->setEnclosure('"'); + return $reader; + } catch (Exception $e) { + $this->throwError('Error opening CSV: '.$e->getMessage(), $e); + } + } + + /** + * Run the actual importer, pass in one of the Import classes which implements + * the ImportExport interface * @param Reader $reader * @param ImportExport $importer * @return array + * @throws ValidationException */ protected function runImport(Reader $reader, ImportExport $importer): array { - $import_report = [ - 'success' => [], - 'failed' => [], - ]; - $cols = $importer->getColumns(); $first_header = $cols[0]; + $first = true; $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) { + if ($first) { + $first = false; + + if($row[$first_header] !== $first_header) { + $this->throwError('CSV file doesn\'t seem to match import type'); + } + continue; } - $success = $importer->import($row, $offset); - if ($success) { - $import_report['success'][] = $importer->status; - } else { - $import_report['failed'][] = $importer->status; + // Do a sanity check on the number of columns first + if (!$importer->checkColumns($row)) { + $importer->errorLog('Number of columns in row doesn\'t match'); + continue; } + + $importer->import($row, $offset); } - return $import_report; + return $importer->status; } /** @@ -82,6 +113,7 @@ class ImportService extends Service * @param bool $delete_previous * @return mixed * @throws \League\Csv\Exception + * @throws ValidationException */ public function importAircraft($csv_file, bool $delete_previous = true) { @@ -120,6 +152,28 @@ class ImportService extends Service return $this->runImport($reader, $importer); } + /** + * Import expenses + * @param string $csv_file + * @param bool $delete_previous + * @return mixed + * @throws \League\Csv\Exception + */ + public function importExpenses($csv_file, bool $delete_previous = true) + { + if ($delete_previous) { + Expense::truncate(); + } + + $reader = $this->openCsv($csv_file); + if (!$reader) { + return false; + } + + $importer = new ExpenseImporter(); + return $this->runImport($reader, $importer); + } + /** * Import flights * @param string $csv_file diff --git a/resources/views/admin/aircraft/import.blade.php b/resources/views/admin/aircraft/import.blade.php index 0d10c53a..6a1da74f 100644 --- a/resources/views/admin/aircraft/import.blade.php +++ b/resources/views/admin/aircraft/import.blade.php @@ -1,5 +1,5 @@ @extends('admin.app') @section('title', 'Import Aircraft') @section('content') - @include('admin.shared.import', ['route' => 'admin.aircraft.import']) + @include('admin.common.import', ['route' => 'admin.aircraft.import']) @endsection diff --git a/resources/views/admin/airports/import.blade.php b/resources/views/admin/airports/import.blade.php index f66aa08d..c65c0737 100644 --- a/resources/views/admin/airports/import.blade.php +++ b/resources/views/admin/airports/import.blade.php @@ -1,5 +1,5 @@ @extends('admin.app') @section('title', 'Import Airports') @section('content') - @include('admin.shared.import', ['route' => 'admin.airports.import']) + @include('admin.common.import', ['route' => 'admin.airports.import']) @endsection diff --git a/resources/views/admin/common/import.blade.php b/resources/views/admin/common/import.blade.php new file mode 100644 index 00000000..9023439d --- /dev/null +++ b/resources/views/admin/common/import.blade.php @@ -0,0 +1,37 @@ +
{{ $errors->first('csv_file') }}
+{{ $line }}
+ @endforeach + @endif + + @if($logs['errors']) +{{ $line }}
+ @endforeach + @endif +{{ $line }}
- @endforeach - -{{ $line }}
- @endforeach -