Reverses the modern Attribute-method approach: the Flight model no
longer carries dptTime/arrTime accessors. Instead, the canonical
columns are `departure_time` / `arrival_time` (Carbon, datetime:H:i:s
cast); the legacy `dpt_time` / `arr_time` `Hi`-formatted strings are
projected onto the JSON payload only by FlightResource at API response
time.
Why
- Public Attribute methods on a model are queryable surface and shouldn't
be hidden behind protected visibility just to satisfy Larastan's
modelAppends rule. Removing the methods (and the matching `$appends`
entries) sidesteps the rule entirely.
- Keeps the API contract stable (`dpt_time`/`arr_time` still appear in
responses, `departure_time`/`arrival_time` stay hidden) while
internal code paths use the structured columns directly.
Model
- Removed `dptTime()` / `arrTime()` Attribute methods from Flight.
- Removed `dpt_time` / `arr_time` from `$appends`.
- Kept `departure_time` / `arrival_time` in `$hidden` (matches the
contract locked by FlightResourceTimeFieldsTest).
- Dropped the now-unused `FlightTimeParser` import.
API resource
- FlightResource::toArray() now formats `dpt_time` / `arr_time` from
the Carbon columns at response time. BidFlightResource inherits the
projection via parent::toArray().
Form / factory
- FlightForm: TimePicker now binds to `departure_time` / `arrival_time`.
- FlightFactory: default state generates `departure_time` /
`arrival_time` instead of the legacy keys.
Import / export plumbing
- Filament FlightImporter: kept the `dpt_time` / `arr_time` CSV column
headers for backward compat; `fillRecordUsing` now parses via
FlightTimeParser and writes through setAttribute('departure_time'/
'arrival_time') to satisfy PHPStan's strict property typing.
- Filament FlightExporter: same CSV headers, `state()` callbacks
format `Hi` from the structured columns.
- Service FlightImporter: transforms legacy keys to structured keys
in import() before firstOrNew mass-assign.
- Service FlightExporter: overrides the array keys after the column
loop so CSV columns continue to use the legacy header names with
Carbon-derived values.
- LegacyImporter (old phpvms import): parses source `deptime`/`arrtime`
into `departure_time`/`arrival_time` directly.
Tests
- Deleted tests/Unit/Models/FlightTimeAccessorTest.php (covered behavior
that no longer exists on the model).
- FlightResourceTimeFieldsTest now seeds via `departure_time` /
`arrival_time` factory state; assertions on the projected
`dpt_time` / `arr_time` keys remain.
- ImporterTest: end-of-import assertions read
`$flight->departure_time->format('Hi')` instead of `$flight->dpt_time`.
All four gates green; affected suites: 104 tests pass.
Sweep of CodeRabbit + arthurpar06 review items on PR #2215
(feat/route-bundles).
Bug fixes
- FlightsRelationManager: gate OldExport/OldImport actions on
!config('phpvms.use_queued_filament_imports') so they don't render
alongside the queued Filament Import/Export actions.
- FlightTimeBackfiller: move \$parsed++ into each per-field success
branch. Counters were row-vs-field unit mismatched, making the
completion log misleading (parsed=rows but failures=fields).
- FlightTimeBackfillerTest: replace Log::shouldReceive('warning')
->zeroOrMoreTimes() with ->once()->with(...) and Mockery::on(...)
payload matcher; the old expectation asserted nothing. Cast id to
int in the matcher (SQLite returns string IDs).
- FlightListShapeTest: assert both 'enabled' and 'active' keys in
the API contract structure (resource emits both as backward-compat
alias; previously only 'active' was locked in).
H1 — native AsEnumCollection for route_types
- Migration ..._add_capability_columns_to_subfleets_table.php: change
route_types from string(64) to json(). Portable across MySQL,
PostgreSQL and SQLite. Column is new in this PR so no data
migration needed.
- Subfleet model: cast switches from custom FlightTypesCast to
AsEnumCollection::of(FlightType::class).
- Delete app/Casts/FlightTypesCast.php and its unit test.
- Test updates explicitly document the behavior shift: empty selection
now stored as JSON [] rather than collapsed to null. Custom
behaviors lost: auto-dedupe, auto-sort, log+drop on invalid token.
None had consumers; future business logic can decide on the
null-vs-empty semantic.
H9 — split backfill into data migration
- Schema migration ..._add_time_columns_to_flights_table.php no
longer runs the backfill inline; just creates the columns.
- New database/migrations_data/2026_05_19_000000_backfill_flight_times.php
calls FlightTimeBackfiller::run() and is picked up by
MigrationService::runAllDataMigrations() on next admin update.
H2 — command rename to phpvms: prefix
- PreserveHiddenVisibility signature: flights:preserve-hidden-visibility
→ phpvms:preserve-hidden-visibility for consistency with the rest
of the namespace (phpvms:dev-install, :importer, :email-test,
:version).
- Updated refs in console test, docs/UPGRADING.md, and openspec
change-tracking docs (tasks, design, proposal).
H10 — composer scripts
- Add --parallel flag to 'pint' and 'pint:test' so AGENTS.md's
'composer pint --test' command uses multi-threading.
H5 — native badge for FlightForm status entry
- Refactor status_badge TextEntry from manual HtmlString + raw <span>
markup to TextEntry::badge()->color()->state(). Removes the need
for ->html(), manual e() escaping, and hardcoded Tailwind classes;
inherits Filament theming and dark-mode automatically.
R2-N — defense in depth
- Wrap :url substitution in e() in parentBundleOwnedDatesMessage().
FlightBundleResource::getUrl() is safe-by-construction but the
translation places :url in HTML context.
Collateral
- pint --dirty added an explicit ': array' return type to
FlightResource::toArray() (it had only @return array in PHPDoc).
BidFlightResource::toArray() needed the same change for covariance;
PHPStan would otherwise fail with method.childReturnType.
Prior session items (also included in this commit since not yet
committed):
- FlightTypesCast wrote validation + invalid-token logging.
- FlightsTable bundle-disabled badge with eager-loaded bundle.
- FlightImporter (Filament) added exists:flight_bundles,id rule.
- FlightImporter (Service) memoized default bundle id to avoid N+1.
- FlightForm: minDate as closure that skips for edit; fixed static
cache leak by using Laravel's container instead.
- SetVisibleFlights: DB::table → Flight::query() for ORM consistency.
- FlightBundle: #[Scope] attribute on visible scope; hasDates() as
has_dates Attribute accessor.
- FlightBundleTest, SubfleetCapabilityTest, FlightsBulkActionsTest
updated for new behaviors.
- ShieldSeeder dead-code cleanup.
All four pre-PR gates green: pint --test, phpstan, rector --dry-run,
pest on affected paths (180 tests passed).
* wip
* refactor: update enum handling and improve type hints across models
* refactor: deprecate labels and select methods in HasSelect trait for better separation of concerns
* copilot suggestions
* test(setting): characterization tests for setting() helper + API shape
* refactor(setting,expense): add #[Scope] + #[ObservedBy] + casts() + drop dead $rules
* feat(services): add SettingService with cache-aware store/retrieve
* feat(finance): add FinanceService::getExpensesForType
* refactor(setting): migrate setting()/setting_save() helpers to SettingService
* refactor(setting): migrate Api controller, Filament page, importer, test helper to SettingService
* refactor(expense): migrate PirepFinanceService, ExportAction, FinanceTest to FinanceService::getExpensesForType
* refactor(setting,expense): delete SettingRepository and ExpenseRepository
* test(setting): use Setting::byKey scope in characterization test
* refactor(setting): drop redundant Cache::forget in Maintenance page
SettingService::store() now invalidates the per-key cache slot internally
(introduced in d9c94195). Manual Cache::forget calls after setting_save()
became dead code in this branch, mirroring the same cleanup applied to
Filament/Pages/Settings.php in 31aa55da. Drops the now-unused Cache import
as well — caught by deep review.
* fix(setting): increment count in SettingsImporter run
Pre-existing bug. $count was declared but never incremented, so the
import log always read 'Imported 0 settings' regardless of how many
settings the legacy importer actually wrote.
Found during Phase 5 deep review.
* refactor(filament): wrap settings save in DB::transaction
The Filament Settings page writes N setting rows in a foreach loop,
then calls FinanceService::changeJournalCurrencies() which rewrites
journal + journal_transaction rows to a (potentially) new currency.
A throw mid-loop or mid-currency-migration left the DB with half-saved
settings or a half-migrated journal.
Wrap both halves in a single DB::transaction() so a partial failure
rolls back all writes atomically.
Found during Phase 5 deep review.
* refactor(services): declare strict_types=1 across app/Services/
Phase 5 introduced declare(strict_types=1); in SettingService.php only.
This commit applies the same declaration to the remaining 65 service
files for consistency.
65 files migrated. 6 files required minimal signature/cast adjustments
to handle existing caller patterns where strict mode exposed implicit
type coercion at runtime or in phpstan:
- FareService::recalculateFares cast $pivot->capacity to (float)
before floor() (DB returns string).
- Finance/RecurringFinanceService::processExpenses cast
$expense->ref_model to (string) before explode() (nullable column).
- AirportLookup/VaCentralLookup, GeoService: pass $e->getMessage()
to Log::error() instead of the Exception object.
- DatabaseService::time() cast Carbon to (string) (return type contract).
- ModuleService::installModule pass $file->getRealPath() to PharData
and Madzipper::make() (UploadedFile -> string path).
No files reverted. No tests modified.
Found during Phase 5 deep review (convention drift).
* style(setting): align match arms for pint 1.29.1
Pint 1.29.1 aligns => arrows in match expressions under
binary_operator_spaces (with align_single_space_minimal). 1.29.0 did
not. CI installs latest globally and fails on the un-aligned form.
Bumped composer.lock so local matches CI.
* Update app/Services/Finance/RecurringFinanceService.php
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* refactor(installer): add strict types to LoggerTrait
LoggerTrait declared strict_types=1 but methods had no parameter or
return types. Added: comment(string $text): void, info(string $text):
void, error(string $text): void.
FlightImporter:59 was passing a Throwable to error() — switched to
$e->getMessage() to match the new contract. Same pattern applied in
5b2133a8 to VaCentralLookup and GeoService.
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* Fix module menu icon
* Update FlightImporter.php
Closes#1982
Use `0` instead of `null` to follow the `setDays()` function and have a proper match for `firstorNew()`
---------
Co-authored-by: Nabeel S. <99736+nabeelio@users.noreply.github.com>
* Row fixes
* Fix FIN field
* Fix test files
* fix files again for typo
* Again !
* Fix distance calculation
* Attempt to fix FirstOrNew
---------
Co-authored-by: Nabeel S <nabeelio@users.noreply.github.com>
* events
- Event model and database table
- event_id field for flights and pireps (for relationships)
- user_id field for flights (for relationships)
- Flight export/import updates to follow model changes
* StlyCI Fixes
* Another StyleCI Fix :)
* Update 2022_12_27_192218_create_events.php
* Update 2022_12_27_192218_create_events.php
* Update 2022_12_27_192218_create_events.php
use `primary` instead of `increments` as requested
* Update 2022_12_27_192218_create_events.php
---------
Co-authored-by: Nabeel S <nabeelio@users.noreply.github.com>
* Update SubfleetImporter.php
Correction to the import to include Simbrief Code
* Update SubfleetImporter.php
Added in the missing fields HUB-ID and SIMBrief as these are input on the main screen
* Update AircraftImporter.php
Part of the missing data fields in csv import export
* Update FlightImporter.php
Part of the missing fields in csv import and export
* Update AircraftImporter.php
* Update FlightImporter.php
* Update aircraft.csv
Test data amended as part of the missing csv fields
* Update subfleets.csv
Part of the fix for missing fields in csv files used for import/export
* Update flights.csv
* Update FlightImporter.php
* Update subfleets.csv
Removed unused fields
* Update FlightImporter.php
* Update FlightImporter.php
* Update FlightImporter.php
* amended for new csv file layouts
* Handle Route and Level fields too during import.
* Also removed the check for `visible => true` from `firstorNew` 'cause va admin may be importing to update not visible flights too.
(by default all new flights are visible, so no affect on new flights)
Closes#1201
* when importing flights, set subfleet name only if subfleet has been created, do not update existing subfleets
* add tests for flights import regarding subfleets
Co-authored-by: Andreas Palm <ap@ewsp.de>