Schema, model, Filament, cron, and visibility-semantics foundations required by the RouteForge change. Four phases delivered as one coherent change. Verified against four spec files (flight-bundles, flight-time-storage, subfleet-capability, flight-visibility). Phase 1.1 \xe2\x80\x94 Flight time columns - flights.departure_time, flights.arrival_time TIME NULL - Flight model accessors (Hi-format) + mutators (FlightTimeParser) - FlightTimeParser supports 9 formats + Z/L/tz suffix stripping - Migration backfills inline via chunked raw DB::table->update() - Legacy dpt_time/arr_time VARCHAR columns preserved for one release Phase 1.2 \xe2\x80\x94 Subfleet capability columns - subfleets.cruise_speed, max_range_nm, route_types (CSV VARCHAR(64)) - FlightTypesCast splits/sorts/dedupes/logs-on-invalid - routeforge defaults in config/phpvms.php - SubfleetForm Operational Capability section Phase 1.3 \xe2\x80\x94 Flight Bundles - flight_bundles table + flights.bundle_id NOT NULL FK - Default bundle seeded inline; existing flights backfilled - FlightBundle model + factory + BundleObserver (queued recompute) - FlightBundleResource as sole Flights nav entry (slug=flights, icon=OutlinedMap, sort=2) - Nested FlightResource under FlightBundleResource (slug=flight, parentResource, create+edit only, no nav entry) - FlightsRelationManager with relatedResource for full-page row actions - FlightForm: bundle selector removed (route-bound), date inputs hidden when parent bundle owns dates, XSS-safe placeholder link - Date columns stored as TIMESTAMP UTC, UI converts to local tz - Soft-deleting a bundle leaves children non-deleted; visibility recompute sets visible=false (no cascade delete) - Spatie Shield permissions: flight_bundle.* seeded Phase 1.4 \xe2\x80\x94 Visibility semantics - flights.active renamed to flights.enabled (admin source of truth) - flights.visible is cron-managed combined state - SetActiveFlights cron deleted; SetVisibleFlights cron added - Two-pass bulk SQL UPDATE scoped by bundle, chunks of 500 - RecomputeBundleVisibility queued job dispatched on bundle save/restore - Flight::scopeVisible added; scopeActive retained as plain alias - Pilot-facing read sites use ->visible() (AirportController fix) - FlightResource JSON + FlightExporter CSV expose both enabled+active - Four-state status badge (Disabled / Disabled by Bundle / Enabled Out of Window / Enabled & In Window) replaces Visible toggle - 5 visibility indexes added Verification - 469 Pest tests passed (2058 assertions) - phpstan level 5 clean (646 files) - pint --test clean - rector --dry-run clean - All four spec files PASS (28 requirements + 36 scenarios verified by kimi-spec-reviewer) Migration path documented in docs/UPGRADING.md and openspec/changes/ schema-modernization-for-routeforge/.
82 lines
2.5 KiB
PHP
82 lines
2.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Enums\FlightType;
|
|
use App\Filament\Resources\Subfleets\Pages\EditSubfleet;
|
|
use App\Models\Subfleet;
|
|
use Database\Seeders\ShieldSeeder;
|
|
use Illuminate\Support\Collection;
|
|
use Livewire\Livewire;
|
|
|
|
it('renders the operational capability section with new fields', function (): void {
|
|
$this->seed(ShieldSeeder::class);
|
|
|
|
$admin = createAdminUser();
|
|
$subfleet = Subfleet::factory()->create();
|
|
|
|
Livewire::test(EditSubfleet::class, ['record' => $subfleet->id])
|
|
->assertSuccessful()
|
|
->assertSee(__('filament.subfleets.sections.operational_capability'))
|
|
->assertSee('cruise_speed')
|
|
->assertSee('max_range_nm')
|
|
->assertSee('route_types');
|
|
});
|
|
|
|
it('persists capability values through the cast', function (): void {
|
|
$this->seed(ShieldSeeder::class);
|
|
|
|
$admin = createAdminUser();
|
|
$subfleet = Subfleet::factory()->create();
|
|
|
|
Livewire::test(EditSubfleet::class, ['record' => $subfleet->id])
|
|
->set('data.cruise_speed', 450)
|
|
->set('data.max_range_nm', 3200)
|
|
->set('data.route_types', [FlightType::SCHED_PAX->value, FlightType::CHARTER_PAX_ONLY->value])
|
|
->call('save')
|
|
->assertHasNoFormErrors()
|
|
->assertNotified();
|
|
|
|
$subfleet->refresh();
|
|
|
|
expect($subfleet->cruise_speed)->toBe(450)
|
|
->and($subfleet->max_range_nm)->toBe(3200)
|
|
->and($subfleet->route_types)->toBeInstanceOf(Collection::class)
|
|
->and($subfleet->route_types)->toHaveCount(2)
|
|
->and($subfleet->route_types->contains(FlightType::SCHED_PAX))->toBeTrue()
|
|
->and($subfleet->route_types->contains(FlightType::CHARTER_PAX_ONLY))->toBeTrue();
|
|
|
|
$this->assertDatabaseHas('subfleets', [
|
|
'id' => $subfleet->id,
|
|
'cruise_speed' => 450,
|
|
'max_range_nm' => 3200,
|
|
'route_types' => 'C,J',
|
|
]);
|
|
});
|
|
|
|
it('persists empty route_types selection as null', function (): void {
|
|
$this->seed(ShieldSeeder::class);
|
|
|
|
$admin = createAdminUser();
|
|
$subfleet = Subfleet::factory()->create([
|
|
'route_types' => collect([FlightType::SCHED_PAX]),
|
|
]);
|
|
|
|
Livewire::test(EditSubfleet::class, ['record' => $subfleet->id])
|
|
->set('data.cruise_speed')
|
|
->set('data.max_range_nm')
|
|
->set('data.route_types', [])
|
|
->call('save')
|
|
->assertHasNoFormErrors()
|
|
->assertNotified();
|
|
|
|
$subfleet->refresh();
|
|
|
|
expect($subfleet->route_types)->toBeNull();
|
|
|
|
$this->assertDatabaseHas('subfleets', [
|
|
'id' => $subfleet->id,
|
|
'route_types' => null,
|
|
]);
|
|
});
|