[8.x] feat: Filament Import Export (#2136)
* add migrations * add setting to choose which importers to use * bump filament * enable database notifications * add AirportImporter and AirportExporter * move the untrash code to beforeUpdate * fix validation rules * add AircraftImporter and AircraftExporter * fix phpstan * add ExpenseImporter and ExpenseExporter * add FareImporter and FareExporter * add FlightImporter and FlightExporter * add SubfleetImporter and SubfleetExporter * change how relationships are processed in FlightImporter * restore records if they are trashed after import * phpstan and types * fix packages * remove foreign keys * apply some of the fixes suggested by CodeRabbit * fix docstring * re add the fk * bump filament
This commit is contained in:
parent
8fdd34d2ea
commit
acd38c9fa8
@ -29,6 +29,8 @@ class ExportAction extends Action
|
||||
|
||||
$this->label('Export to CSV');
|
||||
|
||||
$this->visible(!config('phpvms.use_queued_filament_imports'));
|
||||
|
||||
$this->action(function (array $arguments): ?BinaryFileResponse {
|
||||
if (!isset($arguments['resourceTitle']) || !$arguments['exportType']) {
|
||||
$this->failure();
|
||||
|
||||
@ -27,6 +27,8 @@ class ImportAction extends Action
|
||||
|
||||
$this->label('Import from CSV');
|
||||
|
||||
$this->visible(!config('phpvms.use_queued_filament_imports'));
|
||||
|
||||
$this->schema(function (array $arguments): array {
|
||||
$schema = [
|
||||
FileUpload::make('importFile')->acceptedFileTypes(['text/csv'])->disk('local')->directory('import'),
|
||||
|
||||
51
app/Filament/Exports/AircraftExporter.php
Normal file
51
app/Filament/Exports/AircraftExporter.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Exports;
|
||||
|
||||
use App\Models\Aircraft;
|
||||
use Filament\Actions\Exports\ExportColumn;
|
||||
use Filament\Actions\Exports\Exporter;
|
||||
use Filament\Actions\Exports\Models\Export;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class AircraftExporter extends Exporter
|
||||
{
|
||||
protected static ?string $model = Aircraft::class;
|
||||
|
||||
public static function getColumns(): array
|
||||
{
|
||||
return [
|
||||
ExportColumn::make('subfleet.type'),
|
||||
ExportColumn::make('icao'),
|
||||
ExportColumn::make('iata'),
|
||||
ExportColumn::make('airport.id'),
|
||||
ExportColumn::make('hub.id'),
|
||||
ExportColumn::make('landing_time'),
|
||||
ExportColumn::make('name'),
|
||||
ExportColumn::make('registration'),
|
||||
ExportColumn::make('fin'),
|
||||
ExportColumn::make('hex_code'),
|
||||
ExportColumn::make('selcal'),
|
||||
ExportColumn::make('dow'),
|
||||
ExportColumn::make('mtow'),
|
||||
ExportColumn::make('mlw'),
|
||||
ExportColumn::make('zfw'),
|
||||
ExportColumn::make('simbrief_type'),
|
||||
ExportColumn::make('fuel_onboard'),
|
||||
ExportColumn::make('flight_time'),
|
||||
ExportColumn::make('status'),
|
||||
ExportColumn::make('state'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getCompletedNotificationBody(Export $export): string
|
||||
{
|
||||
$body = 'Your aircraft export has completed and '.Number::format($export->successful_rows).' '.str('row')->plural($export->successful_rows).' exported.';
|
||||
|
||||
if (($failedRowsCount = $export->getFailedRowsCount()) !== 0) {
|
||||
$body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to export.';
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
47
app/Filament/Exports/AirportExporter.php
Normal file
47
app/Filament/Exports/AirportExporter.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Exports;
|
||||
|
||||
use App\Models\Airport;
|
||||
use Filament\Actions\Exports\ExportColumn;
|
||||
use Filament\Actions\Exports\Exporter;
|
||||
use Filament\Actions\Exports\Models\Export;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class AirportExporter extends Exporter
|
||||
{
|
||||
protected static ?string $model = Airport::class;
|
||||
|
||||
public static function getColumns(): array
|
||||
{
|
||||
return [
|
||||
ExportColumn::make('iata'),
|
||||
ExportColumn::make('icao'),
|
||||
ExportColumn::make('name'),
|
||||
ExportColumn::make('location'),
|
||||
ExportColumn::make('region'),
|
||||
ExportColumn::make('country'),
|
||||
ExportColumn::make('timezone'),
|
||||
ExportColumn::make('hub'),
|
||||
ExportColumn::make('notes'),
|
||||
ExportColumn::make('lat'),
|
||||
ExportColumn::make('lon'),
|
||||
ExportColumn::make('elevation'),
|
||||
ExportColumn::make('ground_handling_cost'),
|
||||
ExportColumn::make('fuel_100ll_cost'),
|
||||
ExportColumn::make('fuel_jeta_cost'),
|
||||
ExportColumn::make('fuel_mogas_cost'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getCompletedNotificationBody(Export $export): string
|
||||
{
|
||||
$body = 'Your airport export has completed and '.Number::format($export->successful_rows).' '.str('row')->plural($export->successful_rows).' exported.';
|
||||
|
||||
if (($failedRowsCount = $export->getFailedRowsCount()) !== 0) {
|
||||
$body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to export.';
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
64
app/Filament/Exports/ExpenseExporter.php
Normal file
64
app/Filament/Exports/ExpenseExporter.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Exports;
|
||||
|
||||
use App\Models\Aircraft;
|
||||
use App\Models\Airport;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Subfleet;
|
||||
use Filament\Actions\Exports\ExportColumn;
|
||||
use Filament\Actions\Exports\Exporter;
|
||||
use Filament\Actions\Exports\Models\Export;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class ExpenseExporter extends Exporter
|
||||
{
|
||||
protected static ?string $model = Expense::class;
|
||||
|
||||
public static function getColumns(): array
|
||||
{
|
||||
return [
|
||||
ExportColumn::make('airline.icao'),
|
||||
ExportColumn::make('name'),
|
||||
ExportColumn::make('amount'),
|
||||
ExportColumn::make('type'),
|
||||
ExportColumn::make('flight_type'),
|
||||
ExportColumn::make('charge_to_user'),
|
||||
ExportColumn::make('multiplier'),
|
||||
ExportColumn::make('active'),
|
||||
|
||||
ExportColumn::make('ref_model_type')
|
||||
->formatStateUsing(function (Expense $record): string {
|
||||
return $record->ref_model ? $record->ref_model_type : '';
|
||||
}),
|
||||
|
||||
ExportColumn::make('ref_model_id')
|
||||
->formatStateUsing(function (Expense $record): string {
|
||||
if ($record->ref_model instanceof Aircraft) {
|
||||
return $record->ref_model->registration;
|
||||
}
|
||||
|
||||
if ($record->ref_model instanceof Airport) {
|
||||
return $record->ref_model->icao;
|
||||
}
|
||||
|
||||
if ($record->ref_model instanceof Subfleet) {
|
||||
return $record->ref_model->type;
|
||||
}
|
||||
|
||||
return '';
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getCompletedNotificationBody(Export $export): string
|
||||
{
|
||||
$body = 'Your expense export has completed and '.Number::format($export->successful_rows).' '.str('row')->plural($export->successful_rows).' exported.';
|
||||
|
||||
if (($failedRowsCount = $export->getFailedRowsCount()) !== 0) {
|
||||
$body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to export.';
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
39
app/Filament/Exports/FareExporter.php
Normal file
39
app/Filament/Exports/FareExporter.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Exports;
|
||||
|
||||
use App\Models\Fare;
|
||||
use Filament\Actions\Exports\ExportColumn;
|
||||
use Filament\Actions\Exports\Exporter;
|
||||
use Filament\Actions\Exports\Models\Export;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class FareExporter extends Exporter
|
||||
{
|
||||
protected static ?string $model = Fare::class;
|
||||
|
||||
public static function getColumns(): array
|
||||
{
|
||||
return [
|
||||
ExportColumn::make('code'),
|
||||
ExportColumn::make('name'),
|
||||
ExportColumn::make('price'),
|
||||
ExportColumn::make('cost'),
|
||||
ExportColumn::make('capacity'),
|
||||
ExportColumn::make('type'),
|
||||
ExportColumn::make('notes'),
|
||||
ExportColumn::make('active'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getCompletedNotificationBody(Export $export): string
|
||||
{
|
||||
$body = 'Your fare export has completed and '.Number::format($export->successful_rows).' '.str('row')->plural($export->successful_rows).' exported.';
|
||||
|
||||
if (($failedRowsCount = $export->getFailedRowsCount()) !== 0) {
|
||||
$body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to export.';
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
160
app/Filament/Exports/FlightExporter.php
Normal file
160
app/Filament/Exports/FlightExporter.php
Normal file
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Exports;
|
||||
|
||||
use App\Models\Enums\Days;
|
||||
use App\Models\Flight;
|
||||
use App\Support\Utils;
|
||||
use Filament\Actions\Exports\ExportColumn;
|
||||
use Filament\Actions\Exports\Exporter;
|
||||
use Filament\Actions\Exports\Models\Export;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class FlightExporter extends Exporter
|
||||
{
|
||||
protected static ?string $model = Flight::class;
|
||||
|
||||
public static function getColumns(): array
|
||||
{
|
||||
return [
|
||||
ExportColumn::make('airline.icao'),
|
||||
ExportColumn::make('flight_number'),
|
||||
ExportColumn::make('callsign'),
|
||||
ExportColumn::make('route_code'),
|
||||
ExportColumn::make('route_leg'),
|
||||
ExportColumn::make('dpt_airport.icao'),
|
||||
ExportColumn::make('arr_airport.icao'),
|
||||
ExportColumn::make('alt_airport.icao'),
|
||||
ExportColumn::make('dpt_time'),
|
||||
ExportColumn::make('arr_time'),
|
||||
ExportColumn::make('level'),
|
||||
ExportColumn::make('distance'),
|
||||
ExportColumn::make('flight_time'),
|
||||
ExportColumn::make('flight_type'),
|
||||
ExportColumn::make('load_factor'),
|
||||
ExportColumn::make('load_factor_variance'),
|
||||
ExportColumn::make('route'),
|
||||
ExportColumn::make('pilot_pay'),
|
||||
ExportColumn::make('notes'),
|
||||
ExportColumn::make('days')
|
||||
->formatStateUsing(fn (Flight $record): string => self::getDays($record)),
|
||||
ExportColumn::make('start_date'),
|
||||
ExportColumn::make('end_date'),
|
||||
ExportColumn::make('active'),
|
||||
ExportColumn::make('event.id'),
|
||||
ExportColumn::make('user.id'),
|
||||
ExportColumn::make('owner_type'),
|
||||
ExportColumn::make('owner_id'),
|
||||
ExportColumn::make('subfleets')
|
||||
->formatStateUsing(fn (Flight $record): string => self::getSubfleets($record)),
|
||||
ExportColumn::make('fares')
|
||||
->formatStateUsing(fn (Flight $record): string => self::getFares($record)),
|
||||
ExportColumn::make('fields')
|
||||
->formatStateUsing(fn (Flight $record): string => self::getFields($record)),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getCompletedNotificationBody(Export $export): string
|
||||
{
|
||||
$body = 'Your flight export has completed and '.Number::format($export->successful_rows).' '.str('row')->plural($export->successful_rows).' exported.';
|
||||
|
||||
if (($failedRowsCount = $export->getFailedRowsCount()) !== 0) {
|
||||
$body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to export.';
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the days string
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getDays(Flight $flight)
|
||||
{
|
||||
$days_str = '';
|
||||
|
||||
if ($flight->on_day(Days::MONDAY)) {
|
||||
$days_str .= '1';
|
||||
}
|
||||
|
||||
if ($flight->on_day(Days::TUESDAY)) {
|
||||
$days_str .= '2';
|
||||
}
|
||||
|
||||
if ($flight->on_day(Days::WEDNESDAY)) {
|
||||
$days_str .= '3';
|
||||
}
|
||||
|
||||
if ($flight->on_day(Days::THURSDAY)) {
|
||||
$days_str .= '4';
|
||||
}
|
||||
|
||||
if ($flight->on_day(Days::FRIDAY)) {
|
||||
$days_str .= '5';
|
||||
}
|
||||
|
||||
if ($flight->on_day(Days::SATURDAY)) {
|
||||
$days_str .= '6';
|
||||
}
|
||||
|
||||
if ($flight->on_day(Days::SUNDAY)) {
|
||||
$days_str .= '7';
|
||||
}
|
||||
|
||||
return $days_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any custom fares that have been made to this flight
|
||||
*/
|
||||
private static function getFares(Flight $flight): string
|
||||
{
|
||||
$fares = [];
|
||||
foreach ($flight->fares as $fare) {
|
||||
$fare_export = [];
|
||||
if ($fare->pivot->price) {
|
||||
$fare_export['price'] = $fare->pivot->price;
|
||||
}
|
||||
|
||||
if ($fare->pivot->cost) {
|
||||
$fare_export['cost'] = $fare->pivot->cost;
|
||||
}
|
||||
|
||||
if ($fare->pivot->capacity) {
|
||||
$fare_export['capacity'] = $fare->pivot->capacity;
|
||||
}
|
||||
|
||||
$fares[$fare->code] = $fare_export;
|
||||
}
|
||||
|
||||
return Utils::objectToMultiString($fares);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse all of the subfields
|
||||
*/
|
||||
private static function getFields(Flight $flight): string
|
||||
{
|
||||
$ret = [];
|
||||
foreach ($flight->field_values as $field) {
|
||||
$ret[$field->name] = $field->value;
|
||||
}
|
||||
|
||||
return Utils::objectToMultiString($ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the list of subfleets that are associated here
|
||||
*/
|
||||
private static function getSubfleets(Flight $flight): string
|
||||
{
|
||||
$subfleets = [];
|
||||
foreach ($flight->subfleets as $subfleet) {
|
||||
$subfleets[] = $subfleet->type;
|
||||
}
|
||||
|
||||
return Utils::objectToMultiString($subfleets);
|
||||
}
|
||||
}
|
||||
94
app/Filament/Exports/SubfleetExporter.php
Normal file
94
app/Filament/Exports/SubfleetExporter.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Exports;
|
||||
|
||||
use App\Models\Subfleet;
|
||||
use App\Support\Utils;
|
||||
use Filament\Actions\Exports\ExportColumn;
|
||||
use Filament\Actions\Exports\Exporter;
|
||||
use Filament\Actions\Exports\Models\Export;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class SubfleetExporter extends Exporter
|
||||
{
|
||||
protected static ?string $model = Subfleet::class;
|
||||
|
||||
public static function getColumns(): array
|
||||
{
|
||||
return [
|
||||
ExportColumn::make('airline.icao'),
|
||||
ExportColumn::make('hub.name'),
|
||||
ExportColumn::make('type'),
|
||||
ExportColumn::make('simbrief_type'),
|
||||
ExportColumn::make('name'),
|
||||
ExportColumn::make('cost_block_hour'),
|
||||
ExportColumn::make('cost_delay_minute'),
|
||||
ExportColumn::make('fuel_type'),
|
||||
ExportColumn::make('ground_handling_multiplier'),
|
||||
ExportColumn::make('cargo_capacity'),
|
||||
ExportColumn::make('fuel_capacity'),
|
||||
ExportColumn::make('gross_weight'),
|
||||
ExportColumn::make('fares')->formatStateUsing(fn (Subfleet $record): string => self::getFares($record)),
|
||||
ExportColumn::make('ranks')->formatStateUsing(fn (Subfleet $record): string => self::getRanks($record)),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getCompletedNotificationBody(Export $export): string
|
||||
{
|
||||
$body = 'Your subfleet export has completed and '.Number::format($export->successful_rows).' '.str('row')->plural($export->successful_rows).' exported.';
|
||||
|
||||
if (($failedRowsCount = $export->getFailedRowsCount()) !== 0) {
|
||||
$body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to export.';
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any custom fares that have been made to this subfleet
|
||||
*/
|
||||
private static function getFares(Subfleet $subfleet): string
|
||||
{
|
||||
$fares = [];
|
||||
foreach ($subfleet->fares as $fare) {
|
||||
$fare_export = [];
|
||||
if ($fare->pivot->price !== null) {
|
||||
$fare_export['price'] = $fare->pivot->price;
|
||||
}
|
||||
|
||||
if ($fare->pivot->cost !== null) {
|
||||
$fare_export['cost'] = $fare->pivot->cost;
|
||||
}
|
||||
|
||||
if ($fare->pivot->capacity !== null) {
|
||||
$fare_export['capacity'] = $fare->pivot->capacity;
|
||||
}
|
||||
|
||||
$fares[$fare->code] = $fare_export;
|
||||
}
|
||||
|
||||
return Utils::objectToMultiString($fares);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any ranks that have been linked to this subfleet
|
||||
*/
|
||||
private static function getRanks(Subfleet $subfleet): string
|
||||
{
|
||||
$ranks = [];
|
||||
foreach ($subfleet->ranks as $rank) {
|
||||
$rank_export = [];
|
||||
if ($rank->pivot->acars_pay !== null) {
|
||||
$rank_export['acars_pay'] = $rank->pivot->acars_pay;
|
||||
}
|
||||
|
||||
if ($rank->pivot->manual_pay !== null) {
|
||||
$rank_export['manual_pay'] = $rank->pivot->manual_pay;
|
||||
}
|
||||
|
||||
$ranks[$rank->id] = $rank_export;
|
||||
}
|
||||
|
||||
return Utils::objectToMultiString($ranks);
|
||||
}
|
||||
}
|
||||
122
app/Filament/Imports/AircraftImporter.php
Normal file
122
app/Filament/Imports/AircraftImporter.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Imports;
|
||||
|
||||
use App\Models\Aircraft;
|
||||
use App\Models\Enums\AircraftState;
|
||||
use App\Support\ICAO;
|
||||
use App\Support\Units\Mass;
|
||||
use Filament\Actions\Imports\ImportColumn;
|
||||
use Filament\Actions\Imports\Importer;
|
||||
use Filament\Actions\Imports\Models\Import;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
/**
|
||||
* @property Aircraft $record
|
||||
*/
|
||||
class AircraftImporter extends Importer
|
||||
{
|
||||
protected static ?string $model = Aircraft::class;
|
||||
|
||||
public static function getColumns(): array
|
||||
{
|
||||
return [
|
||||
ImportColumn::make('subfleet')
|
||||
->requiredMapping()
|
||||
->relationship(resolveUsing: 'type')
|
||||
->rules(['required']),
|
||||
ImportColumn::make('icao')
|
||||
->rules(['max:4']),
|
||||
ImportColumn::make('iata')
|
||||
->rules(['max:4']),
|
||||
ImportColumn::make('airport')
|
||||
->guess(['airport_id'])
|
||||
->relationship(),
|
||||
ImportColumn::make('hub')
|
||||
->guess(['hub_id'])
|
||||
->relationship(),
|
||||
ImportColumn::make('landing_time')
|
||||
->rules(['nullable', 'datetime']),
|
||||
ImportColumn::make('name')
|
||||
->requiredMapping()
|
||||
->rules(['required']),
|
||||
ImportColumn::make('registration'),
|
||||
ImportColumn::make('fin'),
|
||||
ImportColumn::make('hex_code'),
|
||||
ImportColumn::make('selcal'),
|
||||
ImportColumn::make('dow')
|
||||
->fillRecordUsing(function (Aircraft $record, ?int $state): void {
|
||||
$record->dow = $state > 0 ? Mass::make((float) $state, setting('units.weight')) : null;
|
||||
})
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('mtow')
|
||||
->fillRecordUsing(function (Aircraft $record, ?int $state): void {
|
||||
$record->mtow = $state > 0 ? Mass::make((float) $state, setting('units.weight')) : null;
|
||||
})
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('mlw')
|
||||
->fillRecordUsing(function (Aircraft $record, ?int $state): void {
|
||||
$record->mlw = $state > 0 ? Mass::make((float) $state, setting('units.weight')) : null;
|
||||
})
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('zfw')
|
||||
->fillRecordUsing(function (Aircraft $record, ?int $state): void {
|
||||
$record->zfw = $state > 0 ? Mass::make((float) $state, setting('units.weight')) : null;
|
||||
})
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('simbrief_type'),
|
||||
ImportColumn::make('fuel_onboard')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('flight_time')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('status')
|
||||
->requiredMapping()
|
||||
->rules(['required']),
|
||||
ImportColumn::make('state')
|
||||
->numeric()
|
||||
->rules(['required', 'integer']),
|
||||
];
|
||||
}
|
||||
|
||||
public function resolveRecord(): Aircraft
|
||||
{
|
||||
return Aircraft::withTrashed()->firstOrNew([
|
||||
'registration' => $this->data['registration'],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function beforeSave(): void
|
||||
{
|
||||
if (!$this->record->hex_code) {
|
||||
$this->record->hex_code = ICAO::createHexCode();
|
||||
}
|
||||
|
||||
if (!array_key_exists('state', $this->data) || $this->data['state'] === null) {
|
||||
$this->record->state = AircraftState::PARKED;
|
||||
}
|
||||
}
|
||||
|
||||
protected function beforeUpdate(): void
|
||||
{
|
||||
if ($this->record->trashed()) {
|
||||
$this->record->restore();
|
||||
}
|
||||
}
|
||||
|
||||
public static function getCompletedNotificationBody(Import $import): string
|
||||
{
|
||||
$body = 'Your aircraft import has completed and '.Number::format($import->successful_rows).' '.str('row')->plural($import->successful_rows).' imported.';
|
||||
|
||||
if (($failedRowsCount = $import->getFailedRowsCount()) !== 0) {
|
||||
$body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to import.';
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
95
app/Filament/Imports/AirportImporter.php
Normal file
95
app/Filament/Imports/AirportImporter.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Imports;
|
||||
|
||||
use App\Models\Airport;
|
||||
use Filament\Actions\Imports\ImportColumn;
|
||||
use Filament\Actions\Imports\Importer;
|
||||
use Filament\Actions\Imports\Models\Import;
|
||||
use Illuminate\Support\Number;
|
||||
use Illuminate\Validation\Rule;
|
||||
use League\ISO3166\ISO3166;
|
||||
|
||||
/**
|
||||
* @property Airport $record
|
||||
*/
|
||||
class AirportImporter extends Importer
|
||||
{
|
||||
protected static ?string $model = Airport::class;
|
||||
|
||||
public static function getColumns(): array
|
||||
{
|
||||
return [
|
||||
ImportColumn::make('iata'),
|
||||
ImportColumn::make('icao')
|
||||
->fillRecordUsing(function (Airport $record, string $state): void {
|
||||
$record->id = $state;
|
||||
$record->icao = $state;
|
||||
})
|
||||
->requiredMapping()
|
||||
->rules(['required', 'max:4']),
|
||||
ImportColumn::make('name')
|
||||
->requiredMapping()
|
||||
->rules(['required']),
|
||||
ImportColumn::make('location'),
|
||||
ImportColumn::make('region'),
|
||||
ImportColumn::make('country')
|
||||
->fillRecordUsing(function (Airport $record, string $state): void {
|
||||
$record->country = strtolower($state);
|
||||
})
|
||||
->rules([Rule::in(array_column((new ISO3166())->all(), 'alpha2'))]),
|
||||
ImportColumn::make('timezone'),
|
||||
ImportColumn::make('hub')
|
||||
->requiredMapping()
|
||||
->boolean()
|
||||
->rules(['required', 'boolean']),
|
||||
ImportColumn::make('notes'),
|
||||
ImportColumn::make('lat')
|
||||
->numeric(decimalPlaces: 5)
|
||||
->rules(['numeric']),
|
||||
ImportColumn::make('lon')
|
||||
->numeric(decimalPlaces: 5)
|
||||
->rules(['numeric']),
|
||||
ImportColumn::make('elevation')
|
||||
->integer()
|
||||
->rules(['integer', 'nullable']),
|
||||
ImportColumn::make('ground_handling_cost')
|
||||
->numeric()
|
||||
->rules(['numeric', 'nullable']),
|
||||
ImportColumn::make('fuel_100ll_cost')
|
||||
->numeric()
|
||||
->rules(['numeric', 'nullable']),
|
||||
ImportColumn::make('fuel_jeta_cost')
|
||||
->numeric()
|
||||
->rules(['numeric', 'nullable']),
|
||||
ImportColumn::make('fuel_mogas_cost')
|
||||
->numeric()
|
||||
->rules(['numeric', 'nullable']),
|
||||
];
|
||||
}
|
||||
|
||||
public function resolveRecord(): Airport
|
||||
{
|
||||
return Airport::withTrashed()->firstOrNew([
|
||||
'id' => $this->data['icao'],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function beforeUpdate(): void
|
||||
{
|
||||
if ($this->record->trashed()) {
|
||||
$this->record->restore();
|
||||
}
|
||||
}
|
||||
|
||||
public static function getCompletedNotificationBody(Import $import): string
|
||||
{
|
||||
$body = 'Your airport import has completed and '.Number::format($import->successful_rows).' '.str('row')->plural($import->successful_rows).' imported.';
|
||||
|
||||
if (($failedRowsCount = $import->getFailedRowsCount()) !== 0) {
|
||||
$body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to import.';
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
112
app/Filament/Imports/ExpenseImporter.php
Normal file
112
app/Filament/Imports/ExpenseImporter.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Imports;
|
||||
|
||||
use App\Models\Aircraft;
|
||||
use App\Models\Airport;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Subfleet;
|
||||
use Filament\Actions\Imports\Exceptions\RowImportFailedException;
|
||||
use Filament\Actions\Imports\ImportColumn;
|
||||
use Filament\Actions\Imports\Importer;
|
||||
use Filament\Actions\Imports\Models\Import;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class ExpenseImporter extends Importer
|
||||
{
|
||||
protected static ?string $model = Expense::class;
|
||||
|
||||
public static function getColumns(): array
|
||||
{
|
||||
return [
|
||||
ImportColumn::make('airline')
|
||||
->relationship(resolveUsing: 'icao'),
|
||||
ImportColumn::make('name')
|
||||
->requiredMapping()
|
||||
->rules(['required']),
|
||||
ImportColumn::make('amount')
|
||||
->requiredMapping()
|
||||
->numeric()
|
||||
->rules(['required', 'integer']),
|
||||
ImportColumn::make('type')
|
||||
->requiredMapping()
|
||||
->rules(['required']),
|
||||
ImportColumn::make('flight_type'),
|
||||
ImportColumn::make('charge_to_user')
|
||||
->boolean()
|
||||
->rules(['boolean']),
|
||||
ImportColumn::make('multiplier')
|
||||
->boolean()
|
||||
->rules(['boolean']),
|
||||
ImportColumn::make('active')
|
||||
->boolean()
|
||||
->rules(['boolean']),
|
||||
ImportColumn::make('ref_model_type')
|
||||
->guess(['ref_model']),
|
||||
ImportColumn::make('ref_model_id'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function beforeFill(): void
|
||||
{
|
||||
if (!array_key_exists('ref_model_type', $this->data) || $this->data['ref_model_type'] == '') {
|
||||
$this->data['ref_model_type'] = Expense::class;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!str_contains($this->data['ref_model_type'], 'App\Models\\')) {
|
||||
$this->data['ref_model_type'] = 'App\Models\\'.$this->data['ref_model_type'];
|
||||
}
|
||||
|
||||
$class = $this->data['ref_model_type'];
|
||||
$id = $this->data['ref_model_id'];
|
||||
|
||||
if ($class === Aircraft::class) {
|
||||
Log::info('Trying to import expense on aircraft, registration: '.$id);
|
||||
|
||||
if (is_numeric($id)) {
|
||||
$obj = Aircraft::where('id', $id)->first();
|
||||
} else {
|
||||
$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);
|
||||
if (is_numeric($id)) {
|
||||
$obj = Subfleet::where('id', $id)->first();
|
||||
} else {
|
||||
$obj = Subfleet::where('type', $id)->first();
|
||||
}
|
||||
} else {
|
||||
throw new RowImportFailedException('Unknown ref_model_type: '.$this->data['ref_model_type']);
|
||||
}
|
||||
|
||||
if (!$obj) {
|
||||
throw new RowImportFailedException('Could not find '.$this->data['ref_model_type'].' with id '.$id);
|
||||
}
|
||||
|
||||
$this->data['ref_model_id'] = $obj->id;
|
||||
}
|
||||
|
||||
public function resolveRecord(): Expense
|
||||
{
|
||||
return Expense::firstOrNew([
|
||||
'name' => $this->data['name'],
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getCompletedNotificationBody(Import $import): string
|
||||
{
|
||||
$body = 'Your expense import has completed and '.Number::format($import->successful_rows).' '.str('row')->plural($import->successful_rows).' imported.';
|
||||
|
||||
if ($failedRowsCount = $import->getFailedRowsCount()) {
|
||||
$body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to import.';
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
72
app/Filament/Imports/FareImporter.php
Normal file
72
app/Filament/Imports/FareImporter.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Imports;
|
||||
|
||||
use App\Models\Fare;
|
||||
use Filament\Actions\Imports\ImportColumn;
|
||||
use Filament\Actions\Imports\Importer;
|
||||
use Filament\Actions\Imports\Models\Import;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
/**
|
||||
* @property Fare $record
|
||||
*/
|
||||
class FareImporter extends Importer
|
||||
{
|
||||
protected static ?string $model = Fare::class;
|
||||
|
||||
public static function getColumns(): array
|
||||
{
|
||||
return [
|
||||
ImportColumn::make('code')
|
||||
->requiredMapping()
|
||||
->rules(['required']),
|
||||
ImportColumn::make('name')
|
||||
->requiredMapping()
|
||||
->rules(['required']),
|
||||
ImportColumn::make('price')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('cost')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('capacity')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('type')
|
||||
->requiredMapping()
|
||||
->numeric()
|
||||
->rules(['required', 'integer']),
|
||||
ImportColumn::make('notes'),
|
||||
ImportColumn::make('active')
|
||||
->requiredMapping()
|
||||
->boolean()
|
||||
->rules(['required', 'boolean']),
|
||||
];
|
||||
}
|
||||
|
||||
public function resolveRecord(): Fare
|
||||
{
|
||||
return Fare::withTrashed()->firstOrNew([
|
||||
'code' => $this->data['code'],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function beforeUpdate(): void
|
||||
{
|
||||
if ($this->record->trashed()) {
|
||||
$this->record->restore();
|
||||
}
|
||||
}
|
||||
|
||||
public static function getCompletedNotificationBody(Import $import): string
|
||||
{
|
||||
$body = 'Your fare import has completed and '.Number::format($import->successful_rows).' '.str('row')->plural($import->successful_rows).' imported.';
|
||||
|
||||
if (($failedRowsCount = $import->getFailedRowsCount()) !== 0) {
|
||||
$body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to import.';
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
310
app/Filament/Imports/FlightImporter.php
Normal file
310
app/Filament/Imports/FlightImporter.php
Normal file
@ -0,0 +1,310 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Imports;
|
||||
|
||||
use App\Models\Airport;
|
||||
use App\Models\Enums\Days;
|
||||
use App\Models\Fare;
|
||||
use App\Models\Flight;
|
||||
use App\Models\Subfleet;
|
||||
use App\Services\AirportService;
|
||||
use App\Services\FareService;
|
||||
use App\Services\FlightService;
|
||||
use App\Support\Utils;
|
||||
use Filament\Actions\Imports\Exceptions\RowImportFailedException;
|
||||
use Filament\Actions\Imports\ImportColumn;
|
||||
use Filament\Actions\Imports\Importer;
|
||||
use Filament\Actions\Imports\Models\Import;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
/**
|
||||
* @property Flight $record
|
||||
*/
|
||||
class FlightImporter extends Importer
|
||||
{
|
||||
protected static ?string $model = Flight::class;
|
||||
|
||||
public static function getColumns(): array
|
||||
{
|
||||
return [
|
||||
ImportColumn::make('airline')
|
||||
->requiredMapping()
|
||||
->relationship(resolveUsing: 'icao')
|
||||
->rules(['required']),
|
||||
|
||||
ImportColumn::make('flight_number')
|
||||
->requiredMapping()
|
||||
->numeric()
|
||||
->rules(['required', 'integer']),
|
||||
|
||||
ImportColumn::make('callsign')
|
||||
->rules(['max:4']),
|
||||
|
||||
ImportColumn::make('route_code')
|
||||
->rules(['max:5']),
|
||||
|
||||
ImportColumn::make('route_leg')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
|
||||
ImportColumn::make('dpt_airport')
|
||||
->requiredMapping()
|
||||
->fillRecordUsing(function (Flight $record, string $state): void {
|
||||
$record->dpt_airport_id = self::processAirport($state)->id;
|
||||
})
|
||||
->rules(['required']),
|
||||
|
||||
ImportColumn::make('arr_airport')
|
||||
->requiredMapping()
|
||||
->fillRecordUsing(function (Flight $record, string $state): void {
|
||||
$record->arr_airport_id = self::processAirport($state)->id;
|
||||
})
|
||||
->rules(['required']),
|
||||
|
||||
ImportColumn::make('alt_airport')
|
||||
->fillRecordUsing(function (Flight $record, ?string $state): void {
|
||||
if ($state) {
|
||||
$record->alt_airport_id = self::processAirport($state)->id;
|
||||
}
|
||||
}),
|
||||
|
||||
ImportColumn::make('dpt_time')
|
||||
->rules(['max:10']),
|
||||
|
||||
ImportColumn::make('arr_time')
|
||||
->rules(['max:10']),
|
||||
|
||||
ImportColumn::make('level')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
|
||||
ImportColumn::make('distance')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
|
||||
ImportColumn::make('flight_time')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
|
||||
ImportColumn::make('flight_type')
|
||||
->requiredMapping()
|
||||
->rules(['required', 'max:1']),
|
||||
|
||||
ImportColumn::make('load_factor')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
|
||||
ImportColumn::make('load_factor_variance')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
|
||||
ImportColumn::make('route')
|
||||
->fillRecordUsing(function (Flight $record, ?string $state): void {
|
||||
$record->route = strtoupper($state);
|
||||
}),
|
||||
|
||||
ImportColumn::make('pilot_pay')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
|
||||
ImportColumn::make('notes'),
|
||||
|
||||
ImportColumn::make('days')
|
||||
->fillRecordUsing(function (Flight $record, ?string $state) {
|
||||
if ($state) {
|
||||
$record->days = self::setDays($state);
|
||||
}
|
||||
}),
|
||||
|
||||
ImportColumn::make('start_date')
|
||||
->rules(['nullable', 'date']),
|
||||
|
||||
ImportColumn::make('end_date')
|
||||
->rules(['nullable', 'date']),
|
||||
|
||||
ImportColumn::make('active')
|
||||
->requiredMapping()
|
||||
->boolean()
|
||||
->rules(['required', 'boolean']),
|
||||
|
||||
ImportColumn::make('event')
|
||||
->relationship(),
|
||||
|
||||
ImportColumn::make('user')
|
||||
->relationship(),
|
||||
|
||||
ImportColumn::make('owner_type')
|
||||
->rules(['max:191']),
|
||||
|
||||
ImportColumn::make('owner_id')
|
||||
->rules(['max:36']),
|
||||
|
||||
ImportColumn::make('subfleets')
|
||||
->fillRecordUsing(function (): void {}),
|
||||
|
||||
ImportColumn::make('fares')
|
||||
->fillRecordUsing(function (): void {}),
|
||||
|
||||
ImportColumn::make('fields')
|
||||
->fillRecordUsing(function (): void {}),
|
||||
];
|
||||
}
|
||||
|
||||
public function resolveRecord(): Flight
|
||||
{
|
||||
return Flight::withTrashed()->firstOrNew([
|
||||
'flight_number' => $this->data['flight_number'],
|
||||
'dpt_airport_id' => $this->data['dpt_airport'],
|
||||
'arr_airport_id' => $this->data['arr_airport'],
|
||||
'route_code' => $this->data['route_code'],
|
||||
'route_leg' => $this->data['route_leg'],
|
||||
'days' => self::setDays($this->data['days']),
|
||||
], [
|
||||
'id' => Utils::generateNewId(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getCompletedNotificationBody(Import $import): string
|
||||
{
|
||||
$body = 'Your flight import has completed and '.Number::format($import->successful_rows).' '.str('row')->plural($import->successful_rows).' imported.';
|
||||
|
||||
if ($failedRowsCount = $import->getFailedRowsCount()) {
|
||||
$body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to import.';
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
protected function beforeUpdate(): void
|
||||
{
|
||||
if ($this->record->trashed()) {
|
||||
$this->record->restore();
|
||||
}
|
||||
}
|
||||
|
||||
protected function afterSave(): void
|
||||
{
|
||||
if (array_key_exists('subfleets', $this->data) && $this->data['subfleets'] !== '') {
|
||||
$this->processSubfleets($this->record, $this->data['subfleets']);
|
||||
}
|
||||
|
||||
if (array_key_exists('fares', $this->data) && $this->data['fares'] !== '') {
|
||||
$this->processFares($this->record, $this->data['fares']);
|
||||
}
|
||||
|
||||
if (array_key_exists('fields', $this->data) && $this->data['fields'] !== '') {
|
||||
$this->processFields($this->record, $this->data['fields']);
|
||||
}
|
||||
}
|
||||
|
||||
private static function setDays(string $state): int
|
||||
{
|
||||
if (!$state) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$days = [];
|
||||
if (str_contains($state, '1')) {
|
||||
$days[] = Days::MONDAY;
|
||||
}
|
||||
|
||||
if (str_contains($state, '2')) {
|
||||
$days[] = Days::TUESDAY;
|
||||
}
|
||||
|
||||
if (str_contains($state, '3')) {
|
||||
$days[] = Days::WEDNESDAY;
|
||||
}
|
||||
|
||||
if (str_contains($state, '4')) {
|
||||
$days[] = Days::THURSDAY;
|
||||
}
|
||||
|
||||
if (str_contains($state, '5')) {
|
||||
$days[] = Days::FRIDAY;
|
||||
}
|
||||
|
||||
if (str_contains($state, '6')) {
|
||||
$days[] = Days::SATURDAY;
|
||||
}
|
||||
|
||||
if (str_contains($state, '7')) {
|
||||
$days[] = Days::SUNDAY;
|
||||
}
|
||||
|
||||
return Days::getDaysMask($days);
|
||||
}
|
||||
|
||||
private function processSubfleets(Flight $flight, $col): void
|
||||
{
|
||||
$count = 0;
|
||||
$subfleets = Utils::parseMultiColumnValues($col);
|
||||
foreach ($subfleets as $subfleet_type) {
|
||||
$subfleet_type = trim($subfleet_type);
|
||||
if ($subfleet_type === '' || $subfleet_type === '0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$subfleet = Subfleet::firstOrCreate(
|
||||
['type' => $subfleet_type],
|
||||
[
|
||||
'name' => $subfleet_type,
|
||||
'airline_id' => $flight->airline_id,
|
||||
]
|
||||
);
|
||||
|
||||
$subfleet->save();
|
||||
|
||||
// sync
|
||||
$flight->subfleets()->syncWithoutDetaching([$subfleet->id]);
|
||||
$count++;
|
||||
}
|
||||
|
||||
Log::info('Subfleets added/processed: '.$count);
|
||||
}
|
||||
|
||||
private function processFares(Flight $flight, ?string $state): void
|
||||
{
|
||||
$fares = Utils::parseMultiColumnValues($state);
|
||||
foreach ($fares as $fare_code => $fare_attributes) {
|
||||
if (\is_int($fare_code)) {
|
||||
$fare_code = $fare_attributes;
|
||||
$fare_attributes = [];
|
||||
}
|
||||
|
||||
$fare = Fare::firstOrCreate(['code' => $fare_code], ['name' => $fare_code]);
|
||||
app(FareService::class)->setForFlight($flight, $fare, $fare_attributes);
|
||||
$fare->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse all of the subfields
|
||||
*/
|
||||
private function processFields(Flight $flight, ?string $col): void
|
||||
{
|
||||
$pass_fields = [];
|
||||
$fields = Utils::parseMultiColumnValues($col);
|
||||
|
||||
foreach ($fields as $field_name => $field_value) {
|
||||
$pass_fields[] = [
|
||||
'name' => $field_name,
|
||||
'value' => $field_value,
|
||||
];
|
||||
}
|
||||
|
||||
app(FlightService::class)->updateCustomFields($flight, $pass_fields);
|
||||
}
|
||||
|
||||
private static function processAirport(string $id): Airport
|
||||
{
|
||||
$airport = app(AirportService::class)->lookupAirportIfNotFound($id);
|
||||
|
||||
if (!$airport) {
|
||||
throw new RowImportFailedException('Could not find airport '.$id);
|
||||
}
|
||||
|
||||
return $airport;
|
||||
}
|
||||
}
|
||||
136
app/Filament/Imports/SubfleetImporter.php
Normal file
136
app/Filament/Imports/SubfleetImporter.php
Normal file
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Imports;
|
||||
|
||||
use App\Models\Fare;
|
||||
use App\Models\Rank;
|
||||
use App\Models\Subfleet;
|
||||
use App\Services\FareService;
|
||||
use App\Services\FleetService;
|
||||
use App\Support\Utils;
|
||||
use Filament\Actions\Imports\ImportColumn;
|
||||
use Filament\Actions\Imports\Importer;
|
||||
use Filament\Actions\Imports\Models\Import;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
/**
|
||||
* @property Subfleet $record
|
||||
*/
|
||||
class SubfleetImporter extends Importer
|
||||
{
|
||||
protected static ?string $model = Subfleet::class;
|
||||
|
||||
public static function getColumns(): array
|
||||
{
|
||||
return [
|
||||
ImportColumn::make('airline')
|
||||
->relationship(resolveUsing: 'icao'),
|
||||
ImportColumn::make('hub')
|
||||
->relationship(),
|
||||
ImportColumn::make('type')
|
||||
->requiredMapping()
|
||||
->rules(['required']),
|
||||
ImportColumn::make('simbrief_type'),
|
||||
ImportColumn::make('name')
|
||||
->requiredMapping()
|
||||
->rules(['required']),
|
||||
ImportColumn::make('cost_block_hour')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('cost_delay_minute')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('fuel_type')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('ground_handling_multiplier')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('cargo_capacity')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('fuel_capacity')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('gross_weight')
|
||||
->numeric()
|
||||
->rules(['nullable', 'integer']),
|
||||
ImportColumn::make('fares')
|
||||
->fillRecordUsing(function (): void {}),
|
||||
ImportColumn::make('ranks')
|
||||
->fillRecordUsing(function (): void {}),
|
||||
];
|
||||
}
|
||||
|
||||
protected function beforeUpdate(): void
|
||||
{
|
||||
if ($this->record->trashed()) {
|
||||
$this->record->restore();
|
||||
}
|
||||
}
|
||||
|
||||
protected function afterSave(): void
|
||||
{
|
||||
if (array_key_exists('fares', $this->data) && $this->data['fares'] !== '') {
|
||||
$this->processFares($this->record, $this->data['fares']);
|
||||
}
|
||||
|
||||
if (array_key_exists('ranks', $this->data) && $this->data['ranks'] !== '') {
|
||||
$this->processRanks($this->record, $this->data['ranks']);
|
||||
}
|
||||
}
|
||||
|
||||
public function resolveRecord(): Subfleet
|
||||
{
|
||||
return Subfleet::withTrashed()->firstOrNew([
|
||||
'type' => $this->data['type'],
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getCompletedNotificationBody(Import $import): string
|
||||
{
|
||||
$body = 'Your subfleet import has completed and '.Number::format($import->successful_rows).' '.str('row')->plural($import->successful_rows).' imported.';
|
||||
|
||||
if (($failedRowsCount = $import->getFailedRowsCount()) !== 0) {
|
||||
$body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to import.';
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse all of the fares in the multi-format
|
||||
*/
|
||||
private function processFares(Subfleet $subfleet, $col): void
|
||||
{
|
||||
$fares = Utils::parseMultiColumnValues($col);
|
||||
foreach ($fares as $fare_code => $fare_attributes) {
|
||||
if (\is_int($fare_code)) {
|
||||
$fare_code = $fare_attributes;
|
||||
$fare_attributes = [];
|
||||
}
|
||||
|
||||
$fare = Fare::firstOrCreate(['code' => $fare_code], ['name' => $fare_code]);
|
||||
app(FareService::class)->setForSubfleet($subfleet, $fare, $fare_attributes);
|
||||
$fare->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse all of the rakns in the multi-format
|
||||
*/
|
||||
private function processRanks(Subfleet $subfleet, $col): void
|
||||
{
|
||||
$ranks = Utils::parseMultiColumnValues($col);
|
||||
foreach ($ranks as $rank_id => $rank_attributes) {
|
||||
if (!\is_array($rank_attributes)) {
|
||||
$rank_id = $rank_attributes;
|
||||
$rank_attributes = [];
|
||||
}
|
||||
|
||||
$rank = Rank::firstOrCreate(['id' => $rank_id], ['name' => 'Imported rank '.$rank_id]);
|
||||
app(FleetService::class)->addSubfleetToRank($subfleet, $rank, $rank_attributes);
|
||||
$rank->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,11 +2,15 @@
|
||||
|
||||
namespace App\Filament\Resources\Airports\Pages;
|
||||
|
||||
use App\Filament\Actions\ExportAction;
|
||||
use App\Filament\Actions\ImportAction;
|
||||
use App\Filament\Actions\ExportAction as OldExportAction;
|
||||
use App\Filament\Actions\ImportAction as OldImportAction;
|
||||
use App\Filament\Exports\AirportExporter;
|
||||
use App\Filament\Imports\AirportImporter;
|
||||
use App\Filament\Resources\Airports\AirportResource;
|
||||
use App\Models\Enums\ImportExportType;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\ExportAction;
|
||||
use Filament\Actions\ImportAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
|
||||
@ -17,11 +21,20 @@ class ListAirports extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
ExportAction::make('export')
|
||||
OldExportAction::make('export')
|
||||
->arguments(['resourceTitle' => 'airports', 'exportType' => ImportExportType::AIRPORT]),
|
||||
|
||||
ImportAction::make('import')
|
||||
OldImportAction::make('import')
|
||||
->arguments(['resourceTitle' => 'airports', 'importType' => ImportExportType::AIRPORT]),
|
||||
|
||||
ImportAction::make('import')
|
||||
->visible(config('phpvms.use_queued_filament_imports'))
|
||||
->importer(AirportImporter::class),
|
||||
|
||||
ExportAction::make('export')
|
||||
->visible(config('phpvms.use_queued_filament_imports'))
|
||||
->exporter(AirportExporter::class),
|
||||
|
||||
CreateAction::make()
|
||||
->icon(Heroicon::OutlinedPlusCircle),
|
||||
];
|
||||
|
||||
@ -2,11 +2,15 @@
|
||||
|
||||
namespace App\Filament\Resources\Expenses\Pages;
|
||||
|
||||
use App\Filament\Actions\ExportAction;
|
||||
use App\Filament\Actions\ImportAction;
|
||||
use App\Filament\Actions\ExportAction as OldExportAction;
|
||||
use App\Filament\Actions\ImportAction as OldImportAction;
|
||||
use App\Filament\Exports\ExpenseExporter;
|
||||
use App\Filament\Imports\ExpenseImporter;
|
||||
use App\Filament\Resources\Expenses\ExpenseResource;
|
||||
use App\Models\Enums\ImportExportType;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\ExportAction;
|
||||
use Filament\Actions\ImportAction;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
|
||||
@ -17,12 +21,20 @@ class ManageExpenses extends ManageRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
ExportAction::make('export')
|
||||
OldExportAction::make('export')
|
||||
->arguments(['resourceTitle' => 'expenses', 'exportType' => ImportExportType::EXPENSES]),
|
||||
|
||||
ImportAction::make('import')
|
||||
OldImportAction::make('import')
|
||||
->arguments(['resourceTitle' => 'expenses', 'importType' => ImportExportType::EXPENSES]),
|
||||
|
||||
ImportAction::make('import')
|
||||
->visible(config('phpvms.use_queued_filament_imports'))
|
||||
->importer(ExpenseImporter::class),
|
||||
|
||||
ExportAction::make('export')
|
||||
->visible(config('phpvms.use_queued_filament_imports'))
|
||||
->exporter(ExpenseExporter::class),
|
||||
|
||||
CreateAction::make()
|
||||
->icon(Heroicon::OutlinedPlusCircle),
|
||||
];
|
||||
|
||||
@ -2,11 +2,15 @@
|
||||
|
||||
namespace App\Filament\Resources\Fares\Pages;
|
||||
|
||||
use App\Filament\Actions\ExportAction;
|
||||
use App\Filament\Actions\ImportAction;
|
||||
use App\Filament\Actions\ExportAction as OldExportAction;
|
||||
use App\Filament\Actions\ImportAction as OldImportAction;
|
||||
use App\Filament\Exports\FareExporter;
|
||||
use App\Filament\Imports\FareImporter;
|
||||
use App\Filament\Resources\Fares\FareResource;
|
||||
use App\Models\Enums\ImportExportType;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\ExportAction;
|
||||
use Filament\Actions\ImportAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
|
||||
@ -17,12 +21,20 @@ class ListFares extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
ExportAction::make('export')
|
||||
OldExportAction::make('export')
|
||||
->arguments(['resourceTitle' => 'fares', 'exportType' => ImportExportType::FARES]),
|
||||
|
||||
ImportAction::make('import')
|
||||
OldImportAction::make('import')
|
||||
->arguments(['resourceTitle' => 'fares', 'importType' => ImportExportType::FARES]),
|
||||
|
||||
ImportAction::make('import')
|
||||
->visible(config('phpvms.use_queued_filament_imports'))
|
||||
->importer(FareImporter::class),
|
||||
|
||||
ExportAction::make('export')
|
||||
->visible(config('phpvms.use_queued_filament_imports'))
|
||||
->exporter(FareExporter::class),
|
||||
|
||||
CreateAction::make()
|
||||
->icon(Heroicon::OutlinedPlusCircle),
|
||||
];
|
||||
|
||||
@ -2,11 +2,15 @@
|
||||
|
||||
namespace App\Filament\Resources\Flights\Pages;
|
||||
|
||||
use App\Filament\Actions\ExportAction;
|
||||
use App\Filament\Actions\ImportAction;
|
||||
use App\Filament\Actions\ExportAction as OldExportAction;
|
||||
use App\Filament\Actions\ImportAction as OldImportAction;
|
||||
use App\Filament\Exports\FlightExporter;
|
||||
use App\Filament\Imports\FlightImporter;
|
||||
use App\Filament\Resources\Flights\FlightResource;
|
||||
use App\Models\Enums\ImportExportType;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\ExportAction;
|
||||
use Filament\Actions\ImportAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
|
||||
@ -17,12 +21,20 @@ class ListFlights extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
ExportAction::make('export')
|
||||
OldExportAction::make('export')
|
||||
->arguments(['resourceTitle' => 'flights', 'exportType' => ImportExportType::FLIGHTS]),
|
||||
|
||||
ImportAction::make('import')
|
||||
OldImportAction::make('import')
|
||||
->arguments(['resourceTitle' => 'flights', 'importType' => ImportExportType::FLIGHTS]),
|
||||
|
||||
ImportAction::make('import')
|
||||
->visible(config('phpvms.use_queued_filament_imports'))
|
||||
->importer(FlightImporter::class),
|
||||
|
||||
ExportAction::make('export')
|
||||
->visible(config('phpvms.use_queued_filament_imports'))
|
||||
->exporter(FlightExporter::class),
|
||||
|
||||
CreateAction::make()
|
||||
->icon(Heroicon::OutlinedPlusCircle),
|
||||
];
|
||||
|
||||
@ -2,11 +2,15 @@
|
||||
|
||||
namespace App\Filament\Resources\Subfleets\Pages;
|
||||
|
||||
use App\Filament\Actions\ExportAction;
|
||||
use App\Filament\Actions\ImportAction;
|
||||
use App\Filament\Actions\ExportAction as OldExportAction;
|
||||
use App\Filament\Actions\ImportAction as OldImportAction;
|
||||
use App\Filament\Exports\SubfleetExporter;
|
||||
use App\Filament\Imports\SubfleetImporter;
|
||||
use App\Filament\Resources\Subfleets\SubfleetResource;
|
||||
use App\Models\Enums\ImportExportType;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\ExportAction;
|
||||
use Filament\Actions\ImportAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
|
||||
@ -17,12 +21,20 @@ class ListSubfleets extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
ExportAction::make('export')
|
||||
OldExportAction::make('export')
|
||||
->arguments(['resourceTitle' => 'subfleets', 'exportType' => ImportExportType::SUBFLEETS]),
|
||||
|
||||
ImportAction::make('import')
|
||||
OldImportAction::make('import')
|
||||
->arguments(['resourceTitle' => 'subfleets', 'importType' => ImportExportType::SUBFLEETS]),
|
||||
|
||||
ImportAction::make('import')
|
||||
->visible(config('phpvms.use_queued_filament_imports'))
|
||||
->importer(SubfleetImporter::class),
|
||||
|
||||
ExportAction::make('export')
|
||||
->visible(config('phpvms.use_queued_filament_imports'))
|
||||
->exporter(SubfleetExporter::class),
|
||||
|
||||
CreateAction::make()
|
||||
->icon(Heroicon::OutlinedPlusCircle),
|
||||
];
|
||||
|
||||
@ -2,12 +2,16 @@
|
||||
|
||||
namespace App\Filament\Resources\Subfleets\RelationManagers;
|
||||
|
||||
use App\Filament\Actions\ExportAction;
|
||||
use App\Filament\Actions\ImportAction;
|
||||
use App\Filament\Actions\ExportAction as OldExportAction;
|
||||
use App\Filament\Actions\ImportAction as OldImportAction;
|
||||
use App\Filament\Exports\AircraftExporter;
|
||||
use App\Filament\Imports\AircraftImporter;
|
||||
use App\Filament\Resources\Subfleets\Resources\Aircraft\AircraftResource;
|
||||
use App\Filament\Resources\Subfleets\Resources\Aircraft\Tables\AircraftTable;
|
||||
use App\Models\Enums\ImportExportType;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\ExportAction;
|
||||
use Filament\Actions\ImportAction;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
@ -22,18 +26,26 @@ class AircraftRelationManager extends RelationManager
|
||||
{
|
||||
return AircraftTable::configure($table)
|
||||
->headerActions([
|
||||
ExportAction::make('export')
|
||||
OldExportAction::make('export')
|
||||
->arguments([
|
||||
'resourceTitle' => 'aircraft',
|
||||
'exportType' => ImportExportType::AIRCRAFT,
|
||||
]),
|
||||
|
||||
ImportAction::make('import')
|
||||
OldImportAction::make('import')
|
||||
->arguments([
|
||||
'resourceTitle' => 'aircraft',
|
||||
'importType' => ImportExportType::AIRCRAFT,
|
||||
]),
|
||||
|
||||
ImportAction::make('import')
|
||||
->visible(config('phpvms.use_queued_filament_imports'))
|
||||
->importer(AircraftImporter::class),
|
||||
|
||||
ExportAction::make('export')
|
||||
->visible(config('phpvms.use_queued_filament_imports'))
|
||||
->exporter(AircraftExporter::class),
|
||||
|
||||
CreateAction::make()
|
||||
->icon(Heroicon::OutlinedPlusCircle),
|
||||
]);
|
||||
|
||||
@ -358,12 +358,12 @@ class Flight extends Model
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'id', 'user_id');
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function event(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Event::class, 'id', 'event_id');
|
||||
return $this->belongsTo(Event::class);
|
||||
}
|
||||
|
||||
public function owner(): MorphTo
|
||||
|
||||
@ -103,6 +103,7 @@ class AdminPanelProvider extends PanelProvider
|
||||
->unsavedChangesAlerts()
|
||||
->spa(hasPrefetching: config('phpvms.use_prefetching_in_admin', false))
|
||||
->errorNotifications()
|
||||
->databaseNotifications()
|
||||
->viteTheme('resources/css/filament/admin/theme.css');
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@ use App\Models\Airport;
|
||||
use App\Repositories\AirportRepository;
|
||||
use App\Support\Metar;
|
||||
use App\Support\Units\Distance;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use League\Geotools\Coordinate\Coordinate;
|
||||
use League\Geotools\Geotools;
|
||||
@ -96,10 +95,9 @@ class AirportService extends Service
|
||||
/**
|
||||
* Lookup an airport and save it if it hasn't been found
|
||||
*
|
||||
* @param string $icao
|
||||
* @return Model|null
|
||||
* @param string $icao
|
||||
*/
|
||||
public function lookupAirportIfNotFound($icao)
|
||||
public function lookupAirportIfNotFound($icao): ?Airport
|
||||
{
|
||||
$icao = strtoupper($icao);
|
||||
$airport = $this->airportRepo->findWithoutFail($icao);
|
||||
|
||||
@ -123,4 +123,135 @@ class Utils
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static function parseMultiColumnValues(string $field): array|string
|
||||
{
|
||||
$ret = [];
|
||||
$split_values = explode(';', $field);
|
||||
|
||||
// No multiple values in here, just a straight value
|
||||
if (\count($split_values) === 1) {
|
||||
if (trim($split_values[0]) === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (str_contains($split_values[0], '?')) {
|
||||
// This contains the query string, which turns it into a multi-level array
|
||||
$query_str = explode('?', $split_values[0]);
|
||||
$parent = trim($query_str[0]);
|
||||
|
||||
$children = [];
|
||||
$kvp = explode('&', trim($query_str[1]));
|
||||
foreach ($kvp as $items) {
|
||||
if ($items === '' || $items === '0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::kvpToArray($items, $children);
|
||||
}
|
||||
|
||||
$ret[$parent] = $children;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
if (str_contains($split_values[0], '=')) {
|
||||
$ret = [];
|
||||
self::kvpToArray($split_values[0], $ret);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// This is not a query string, return it back untouched
|
||||
return [$split_values[0]];
|
||||
}
|
||||
|
||||
foreach ($split_values as $value) {
|
||||
$value = trim($value);
|
||||
if ($value === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// This isn't in the query string format, so it's
|
||||
// just a straight key-value pair set
|
||||
if (!str_contains($value, '?')) {
|
||||
self::kvpToArray($value, $ret);
|
||||
|
||||
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) {
|
||||
if ($items === '' || $items === '0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::kvpToArray($items, $children);
|
||||
}
|
||||
|
||||
$ret[$parent] = $children;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public static function kvpToArray($kvp_str, array &$arr): void
|
||||
{
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
public static function objectToMultiString(object|array $obj): object|string
|
||||
{
|
||||
if (!\is_array($obj)) {
|
||||
return $obj;
|
||||
}
|
||||
|
||||
$ret_list = [];
|
||||
foreach ($obj as $key => $val) {
|
||||
if (is_numeric($key) && !\is_array($val)) {
|
||||
$ret_list[] = $val;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = trim($key);
|
||||
|
||||
if (!\is_array($val)) {
|
||||
$val = trim($val);
|
||||
$ret_list[] = "{$key}={$val}";
|
||||
} else {
|
||||
$q = [];
|
||||
foreach ($val as $subkey => $subval) {
|
||||
$q[] = is_numeric($subkey) ? $subval : "{$subkey}={$subval}";
|
||||
}
|
||||
|
||||
$q = implode('&', $q);
|
||||
$ret_list[] = $q === '' || $q === '0' ? $key : "{$key}?{$q}";
|
||||
}
|
||||
}
|
||||
|
||||
return implode(';', $ret_list);
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
"symfony/http-client": "^7.2",
|
||||
"symfony/yaml": "^7.2",
|
||||
"psr/container": "1.1.1",
|
||||
"composer/composer": "^2.7.7",
|
||||
"composer/composer": "^2.9.3",
|
||||
"composer/installers": "^2.3.0",
|
||||
"laravel/framework": "^v12.19",
|
||||
"arrilot/laravel-widgets": "^3.14.0",
|
||||
@ -75,7 +75,7 @@
|
||||
"spatie/laravel-ignition": "^2.9.1",
|
||||
"kyslik/column-sortable": "^7.0",
|
||||
"jlorente/laravel-data-migrations": "^2.0",
|
||||
"filament/filament": "^4.1",
|
||||
"filament/filament": "^4.5.2",
|
||||
"flowframe/laravel-trend": "^0.4.0",
|
||||
"bezhansalleh/filament-shield": "^4.0.2",
|
||||
"spatie/laravel-backup": "^9.2.9",
|
||||
|
||||
1730
composer.lock
generated
1730
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -147,4 +147,9 @@ return [
|
||||
* Whether to use prefetching in the admin panel (can use a lot of bandwidth)
|
||||
*/
|
||||
'use_prefetching_in_admin' => env('USE_PREFETCHING_IN_ADMIN', false),
|
||||
|
||||
/**
|
||||
* Whether to use the built-in filament import system (relies on laravel queue worker)
|
||||
*/
|
||||
'use_queued_filament_imports' => env('USE_QUEUED_FILAMENT_IMPORTS', false),
|
||||
];
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class() extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
$table->integer('pending_jobs');
|
||||
$table->integer('failed_jobs');
|
||||
$table->longText('failed_job_ids');
|
||||
$table->mediumText('options')->nullable();
|
||||
$table->integer('cancelled_at')->nullable();
|
||||
$table->integer('created_at');
|
||||
$table->integer('finished_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('job_batches');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class() extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('imports', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->timestamp('completed_at')->nullable();
|
||||
$table->string('file_name');
|
||||
$table->string('file_path');
|
||||
$table->string('importer');
|
||||
$table->unsignedInteger('processed_rows')->default(0);
|
||||
$table->unsignedInteger('total_rows');
|
||||
$table->unsignedInteger('successful_rows')->default(0);
|
||||
$table->unsignedInteger('user_id');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('imports');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class() extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('exports', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->timestamp('completed_at')->nullable();
|
||||
$table->string('file_disk');
|
||||
$table->string('file_name')->nullable();
|
||||
$table->string('exporter');
|
||||
$table->unsignedInteger('processed_rows')->default(0);
|
||||
$table->unsignedInteger('total_rows');
|
||||
$table->unsignedInteger('successful_rows')->default(0);
|
||||
$table->unsignedInteger('user_id');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('exports');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class() extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('failed_import_rows', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->json('data');
|
||||
$table->foreignId('import_id')->constrained()->cascadeOnDelete();
|
||||
$table->text('validation_error')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('failed_import_rows');
|
||||
}
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
function c({livewireId:s}){return{areAllCheckboxesChecked:!1,checkboxListOptions:[],search:"",visibleCheckboxListOptions:[],init(){this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.$nextTick(()=>{this.checkIfAllCheckboxesAreChecked()}),Livewire.hook("commit",({component:e,commit:t,succeed:i,fail:o,respond:h})=>{i(({snapshot:r,effect:l})=>{this.$nextTick(()=>{e.id===s&&(this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked())})})}),this.$watch("search",()=>{this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked()})},checkIfAllCheckboxesAreChecked(){this.areAllCheckboxesChecked=this.visibleCheckboxListOptions.length===this.visibleCheckboxListOptions.filter(e=>e.querySelector("input[type=checkbox]:checked, input[type=checkbox]:disabled")).length},toggleAllCheckboxes(){this.checkIfAllCheckboxesAreChecked();let e=!this.areAllCheckboxesChecked;this.visibleCheckboxListOptions.forEach(t=>{let i=t.querySelector("input[type=checkbox]");i.disabled||(i.checked=e,i.dispatchEvent(new Event("change")))}),this.areAllCheckboxesChecked=e},updateVisibleCheckboxListOptions(){this.visibleCheckboxListOptions=this.checkboxListOptions.filter(e=>["",null,void 0].includes(this.search)||e.querySelector(".fi-fo-checkbox-list-option-label")?.innerText.toLowerCase().includes(this.search.toLowerCase())?!0:e.querySelector(".fi-fo-checkbox-list-option-description")?.innerText.toLowerCase().includes(this.search.toLowerCase()))}}}export{c as default};
|
||||
function c({livewireId:s}){return{areAllCheckboxesChecked:!1,checkboxListOptions:[],search:"",visibleCheckboxListOptions:[],init(){this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.$nextTick(()=>{this.checkIfAllCheckboxesAreChecked()}),Livewire.hook("commit",({component:e,commit:t,succeed:i,fail:o,respond:h})=>{i(({snapshot:r,effect:l})=>{this.$nextTick(()=>{e.id===s&&(this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked())})})}),this.$watch("search",()=>{this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked()})},checkIfAllCheckboxesAreChecked(){this.areAllCheckboxesChecked=this.visibleCheckboxListOptions.length===this.visibleCheckboxListOptions.filter(e=>e.querySelector("input[type=checkbox]:checked, input[type=checkbox]:disabled")).length},toggleAllCheckboxes(){this.checkIfAllCheckboxesAreChecked();let e=!this.areAllCheckboxesChecked;this.visibleCheckboxListOptions.forEach(t=>{let i=t.querySelector("input[type=checkbox]");i.disabled||i.checked!==e&&(i.checked=e,i.dispatchEvent(new Event("change")))}),this.areAllCheckboxesChecked=e},updateVisibleCheckboxListOptions(){this.visibleCheckboxListOptions=this.checkboxListOptions.filter(e=>["",null,void 0].includes(this.search)||e.querySelector(".fi-fo-checkbox-list-option-label")?.innerText.toLowerCase().includes(this.search.toLowerCase())?!0:e.querySelector(".fi-fo-checkbox-list-option-description")?.innerText.toLowerCase().includes(this.search.toLowerCase()))}}}export{c as default};
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default};
|
||||
function n({initialHeight:e,shouldAutosize:i,state:h}){return{state:h,wrapperEl:null,init(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=e+"rem")},resize(){if(this.$el.scrollHeight<=0)return;let t=this.$el.style.height;this.$el.style.height="0px";let r=this.$el.scrollHeight;this.$el.style.height=t;let l=parseFloat(e)*parseFloat(getComputedStyle(document.documentElement).fontSize),s=Math.max(r,l)+"px";this.wrapperEl.style.height!==s&&(this.wrapperEl.style.height=s)},setUpResizeObserver(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{n as default};
|
||||
|
||||
@ -1 +1 @@
|
||||
var i=()=>({isSticky:!1,enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1}});export{i as default};
|
||||
var i=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let e=this.$el.parentElement;e&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(e),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let e=this.$el.parentElement;if(!e)return;let t=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=e.offsetWidth+parseInt(t.marginInlineStart,10)*-1+parseInt(t.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});export{i as default};
|
||||
|
||||
@ -1 +1 @@
|
||||
function u({activeTab:a,isTabPersistedInQueryString:e,livewireId:h,tab:o,tabQueryStringKey:s}){return{tab:o,init(){let t=this.getTabs(),i=new URLSearchParams(window.location.search);e&&i.has(s)&&t.includes(i.get(s))&&(this.tab=i.get(s)),this.$watch("tab",()=>this.updateQueryString()),(!this.tab||!t.includes(this.tab))&&(this.tab=t[a-1]),Livewire.hook("commit",({component:r,commit:f,succeed:c,fail:l,respond:b})=>{c(({snapshot:d,effect:m})=>{this.$nextTick(()=>{if(r.id!==h)return;let n=this.getTabs();n.includes(this.tab)||(this.tab=n[a-1]??this.tab)})})})},getTabs(){return this.$refs.tabsData?JSON.parse(this.$refs.tabsData.value):[]},updateQueryString(){if(!e)return;let t=new URL(window.location.href);t.searchParams.set(s,this.tab),history.replaceState(null,document.title,t.toString())}}}export{u as default};
|
||||
function I({activeTab:w,isScrollable:f,isTabPersistedInQueryString:m,livewireId:g,tab:T,tabQueryStringKey:c}){return{boundResizeHandler:null,isScrollable:f,resizeDebounceTimer:null,tab:T,withinDropdownIndex:null,withinDropdownMounted:!1,init(){let t=this.getTabs(),e=new URLSearchParams(window.location.search);m&&e.has(c)&&t.includes(e.get(c))&&(this.tab=e.get(c)),this.$watch("tab",()=>this.updateQueryString()),(!this.tab||!t.includes(this.tab))&&(this.tab=t[w-1]),Livewire.hook("commit",({component:n,commit:d,succeed:r,fail:h,respond:u})=>{r(({snapshot:p,effect:i})=>{this.$nextTick(()=>{if(n.id!==g)return;let s=this.getTabs();s.includes(this.tab)||(this.tab=s[w-1]??this.tab)})})}),f||(this.boundResizeHandler=this.debouncedUpdateTabsWithinDropdown.bind(this),window.addEventListener("resize",this.boundResizeHandler),this.updateTabsWithinDropdown())},calculateAvailableWidth(t){let e=window.getComputedStyle(t);return Math.floor(t.clientWidth)-Math.ceil(parseFloat(e.paddingLeft))*2},calculateContainerGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap))},calculateDropdownIconWidth(t){let e=t.querySelector(".fi-icon");return Math.ceil(e.clientWidth)},calculateTabItemGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap)||8)},calculateTabItemPadding(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.paddingLeft))+Math.ceil(parseFloat(e.paddingRight))},findOverflowIndex(t,e,n,d,r,h){let u=t.map(i=>Math.ceil(i.clientWidth)),p=t.map(i=>{let s=i.querySelector(".fi-tabs-item-label"),a=i.querySelector(".fi-badge"),o=Math.ceil(s.clientWidth),l=a?Math.ceil(a.clientWidth):0;return{label:o,badge:l,total:o+(l>0?d+l:0)}});for(let i=0;i<t.length;i++){let s=u.slice(0,i+1).reduce((b,y)=>b+y,0),a=i*n,o=p.slice(i+1),l=o.length>0,W=l?Math.max(...o.map(b=>b.total)):0,D=l?r+W+d+h+n:0;if(s+a+D>e)return i}return-1},get isDropdownButtonVisible(){return this.withinDropdownMounted?this.withinDropdownIndex===null?!1:this.getTabs().findIndex(e=>e===this.tab)<this.withinDropdownIndex:!0},getTabs(){return this.$refs.tabsData?JSON.parse(this.$refs.tabsData.value):[]},updateQueryString(){if(!m)return;let t=new URL(window.location.href);t.searchParams.set(c,this.tab),history.replaceState(null,document.title,t.toString())},debouncedUpdateTabsWithinDropdown(){clearTimeout(this.resizeDebounceTimer),this.resizeDebounceTimer=setTimeout(()=>this.updateTabsWithinDropdown(),150)},async updateTabsWithinDropdown(){this.withinDropdownIndex=null,this.withinDropdownMounted=!1,await this.$nextTick();let t=this.$el.querySelector(".fi-tabs"),e=t.querySelector(".fi-tabs-item:last-child"),n=Array.from(t.children).slice(0,-1),d=n.map(a=>a.style.display);n.forEach(a=>a.style.display=""),t.offsetHeight;let r=this.calculateAvailableWidth(t),h=this.calculateContainerGap(t),u=this.calculateDropdownIconWidth(e),p=this.calculateTabItemGap(n[0]),i=this.calculateTabItemPadding(n[0]),s=this.findOverflowIndex(n,r,h,p,i,u);n.forEach((a,o)=>a.style.display=d[o]),s!==-1&&(this.withinDropdownIndex=s),this.withinDropdownMounted=!0},destroy(){this.boundResizeHandler&&window.removeEventListener("resize",this.boundResizeHandler),clearTimeout(this.resizeDebounceTimer)}}}export{I as default};
|
||||
|
||||
@ -1 +1 @@
|
||||
(()=>{var d=()=>({isSticky:!1,enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1}});var m=function(n,e,i){let t=n;if(e.startsWith("/")&&(i=!0,e=e.slice(1)),i)return e;for(;e.startsWith("../");)t=t.includes(".")?t.slice(0,t.lastIndexOf(".")):null,e=e.slice(3);return["",null,void 0].includes(t)?e:["",null,void 0].includes(e)?t:`${t}.${e}`},u=n=>{let e=Alpine.findClosest(n,i=>i.__livewire);if(!e)throw"Could not find Livewire component in DOM tree.";return e.__livewire};document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:n})=>({handleFormValidationError(e){e.detail.livewireId===n&&this.$nextTick(()=>{let i=this.$el.querySelector("[data-validation-error]");if(!i)return;let t=i;for(;t;)t.dispatchEvent(new CustomEvent("expand")),t=t.parentNode;setTimeout(()=>i.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})}})),window.Alpine.data("filamentSchemaComponent",({path:n,containerPath:e,isLive:i,$wire:t})=>({$statePath:n,$get:(r,l)=>t.$get(m(e,r,l)),$set:(r,l,a,o=null)=>(o??(o=i),t.$set(m(e,r,a),l,o)),get $state(){return t.$get(n)}})),window.Alpine.data("filamentActionsSchemaComponent",d),Livewire.hook("commit",({component:n,commit:e,respond:i,succeed:t,fail:r})=>{t(({snapshot:l,effects:a})=>{a.dispatches?.forEach(o=>{if(!o.params?.awaitSchemaComponent)return;let s=Array.from(n.el.querySelectorAll(`[wire\\:partial="schema-component::${o.params.awaitSchemaComponent}"]`)).filter(c=>u(c)===n);if(s.length!==1){if(s.length>1)throw`Multiple schema components found with key [${o.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${n.id}-${o.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(o.name,{detail:o.params}))},{once:!0})}})})})});})();
|
||||
(()=>{var d=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let t=this.$el.parentElement;t&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(t),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let t=this.$el.parentElement;if(!t)return;let e=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=t.offsetWidth+parseInt(e.marginInlineStart,10)*-1+parseInt(e.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});var u=function(t,e,n){let i=t;if(e.startsWith("/")&&(n=!0,e=e.slice(1)),n)return e;for(;e.startsWith("../");)i=i.includes(".")?i.slice(0,i.lastIndexOf(".")):null,e=e.slice(3);return["",null,void 0].includes(i)?e:["",null,void 0].includes(e)?i:`${i}.${e}`},c=t=>{let e=Alpine.findClosest(t,n=>n.__livewire);if(!e)throw"Could not find Livewire component in DOM tree.";return e.__livewire};document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:t})=>({handleFormValidationError(e){e.detail.livewireId===t&&this.$nextTick(()=>{let n=this.$el.querySelector("[data-validation-error]");if(!n)return;let i=n;for(;i;)i.dispatchEvent(new CustomEvent("expand")),i=i.parentNode;setTimeout(()=>n.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})},isStateChanged(e,n){if(e===void 0)return!1;try{return JSON.stringify(e)!==JSON.stringify(n)}catch{return e!==n}}})),window.Alpine.data("filamentSchemaComponent",({path:t,containerPath:e,$wire:n})=>({$statePath:t,$get:(i,s)=>n.$get(u(e,i,s)),$set:(i,s,a,o=!1)=>n.$set(u(e,i,a),s,o),get $state(){return n.$get(t)}})),window.Alpine.data("filamentActionsSchemaComponent",d),Livewire.hook("commit",({component:t,commit:e,respond:n,succeed:i,fail:s})=>{i(({snapshot:a,effects:o})=>{o.dispatches?.forEach(r=>{if(!r.params?.awaitSchemaComponent)return;let l=Array.from(t.el.querySelectorAll(`[wire\\:partial="schema-component::${r.params.awaitSchemaComponent}"]`)).filter(h=>c(h)===t);if(l.length!==1){if(l.length>1)throw`Multiple schema components found with key [${r.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${t.id}-${r.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(r.name,{detail:r.params}))},{once:!0})}})})})});})();
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user