diff --git a/app/Http/Controllers/Atc/EndorsementController.php b/app/Http/Controllers/Atc/EndorsementController.php index bd7909844f..b9b83deb6b 100644 --- a/app/Http/Controllers/Atc/EndorsementController.php +++ b/app/Http/Controllers/Atc/EndorsementController.php @@ -4,30 +4,43 @@ use App\Http\Controllers\BaseController; use App\Models\Atc\PositionGroup; +use App\Models\NetworkData\Atc; +use App\Models\Roster; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\Redirect; class EndorsementController extends BaseController { + const GATWICK_HOURS_REQUIREMENT = 50; + public function getGatwickGroundIndex() { - return Redirect::route('mship.manage.dashboard') - ->withError("We're making some changes to this page, please check back later."); + if (! $this->account->fully_defined || ! $this->account->qualification_atc->isS1) { + return Redirect::route('mship.manage.dashboard') + ->withError('Only S1 rated controllers are eligible for a Gatwick Ground endorsement.'); + } - // $endorsement = PositionGroup::with('conditions')->where('name', 'Gatwick S1 (DEL/GND)')->first(); - // - // $hours = $endorsement->conditions->map(function ($condition) { - // return $condition->progressForUser($this->account); - // }); - // - // if (! $this->account->fully_defined || ! $this->account->qualificationAtc->isS1) { - // return Redirect::route('mship.manage.dashboard') - // ->withError('Only S1 rated controllers are eligible for a Gatwick Ground endorsement.'); - // } - // - // return $this->viewMake('controllers.endorsements.gatwick_ground') - // ->with('endorsment', $endorsement) - // ->with('conditions', $endorsement->conditions) - // ->with('hours', $hours->all()); + // active on roster + $onRoster = Roster::where('account_id', $this->account->id)->exists(); + + // 50 hours on _GND or _DEL + $minutesOnline = $this->account->networkDataAtc() + ->isUK() + ->where(function (Builder $builder) { + $builder->where('facility_type', Atc::TYPE_GND) + ->orWhere('facility_type', Atc::TYPE_DEL); + }) + ->sum('minutes_online'); + + $totalHours = $minutesOnline / 60; + $hoursMet = $totalHours >= self::GATWICK_HOURS_REQUIREMENT; + + return $this->viewMake('controllers.endorsements.gatwick_ground') + ->with('totalHours', $totalHours) + ->with('progress', ($totalHours / self::GATWICK_HOURS_REQUIREMENT) * 100) + ->with('hoursMet', $hoursMet) + ->with('onRoster', $onRoster) + ->with('conditionsMet', $hoursMet && $onRoster); } public function getAreaIndex() diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index f022b25fe1..5c33aa35fa 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -21,6 +21,7 @@ class BaseController extends \Illuminate\Routing\Controller } use DispatchesJobs, RedirectsUsers, ValidatesRequests; + /** @var Account */ protected $account; protected $pageTitle; diff --git a/resources/views/controllers/endorsements/gatwick_ground.blade.php b/resources/views/controllers/endorsements/gatwick_ground.blade.php index 4f93964e66..8127a78e8e 100644 --- a/resources/views/controllers/endorsements/gatwick_ground.blade.php +++ b/resources/views/controllers/endorsements/gatwick_ground.blade.php @@ -7,120 +7,117 @@
  Gatwick Endorsement
- Gatwick is one of the busiest airports on the VATSIM network. Before controlling it, we want to ensure you have the knowledge you need to provide a good service to pilots and get the most from your controlling session.
-
+

+ Gatwick is one of the busiest airports on the VATSIM network. Before controlling it, we want to + ensure you have the knowledge you need to provide a good service to pilots and get the most from + your controlling session. +

Step One

- In order to control Gatwick Ground as an S1, you will need to first meet the requirements outlined on this page.
- The requirements involve you completing a number of hours on various positions around the UK, being a home member of the UK and rated as an S1.
-
+

+ In order to control Gatwick Ground as an S1, you will need to first meet + the requirements outlined on this page. +

+

+ You must be a home member of the UK, be active on the controller roster, be rated as an S1 + and have controlled for 50 hours on UK GMC or GMP positions. +

Step Two

- You will be given access to the 'Gatwick ADC | S1 Endorsement' course. This Moodle course covers Gatwick specific procedures, radiotelephony, and local flight planning restrictions. - There is a quiz at the end of the course with a pass mark of 90% - you must pass this quiz to proceed. - If you do not pass the quiz on your first attempt, there is a study period of seven days for you to review the Moodle course and improve your knowledge before you try again.
- When you have passed the quiz at the end of the Moodle course, you will be prompted to submit another ticket to ATC TRAINING.
-
+

+ You will be given access to the 'Gatwick ADC | S1 Endorsement' course. This Moodle course covers + Gatwick specific procedures, radiotelephony, and local flight planning restrictions. + There is a quiz at the end of the course with a pass mark of 90% - you must pass this quiz to + proceed. +

+

+ If you do not pass the quiz on your first attempt, there is a study period of seven days for you to + review the Moodle course and improve your knowledge before you try again. +

+

+ When you have passed the quiz at the end of the Moodle course, you will be prompted to submit + another ticket to ATC TRAINING. +

Step Three

- One of our Gatwick mentors will take you onto the live network, on either EGKK_GND or EGKK_DEL, and offer you hints and tips as you control You will also have the chance to ask any questions that you have.
- This is not a test and you will not pass or fail, rather it is an opportunity for you to practically apply the skills and knowledge which you have learned through completing the Moodle course.
- You will do this until the mentor deems you ready for the Gatwick ground endorsement. Once granted the endorsement, you will be able to control EGKK_GND and EGKK_DEL on the live network without supervision. +

+ One of our Gatwick mentors will take you onto the live network, on either EGKK_GND or EGKK_DEL, and + offer you hints and tips as you control You will also have the chance to ask any questions that you have. +

+

+ This is not a test and you will not pass or fail, rather it is an opportunity for you to practically + apply the skills and knowledge which you have learned through completing the Moodle course.
+ You will do this until the mentor deems you ready for the Gatwick ground endorsement. Once granted + the endorsement, you will be able to control EGKK_GND and EGKK_DEL on the live network without + supervision. +

-
-
-
  Group One Controlling
-
- Control a total of {{ $conditions[0]->required_hours }} hours on one of the following positions within the last {{ $conditions[0]->within_months }} months. -
    -
  • Manchester (EGCC)
  • -
  • Edinburgh (EGPH)
  • -
  • Stansted (EGSS)
  • -
  • Liverpool (EGGP)
  • -
- @foreach($hours[0] as $icao => $hour) -
- @if($conditions[0]->isMetForUser($_account)) -
{{ round($hour,2) }} Hrs {{ '('. $icao .')' }}
- @else -
{{ ($hour > 0) ? (round($hour,2)).' Hrs ('. $icao .')' : '' }}
- @endif -
- @endforeach -
-
-
-
+
-
  Group Two Controlling
+
  Membership Status
- Control a total of {{ $conditions[1]->required_hours }} hours on one of the following positions within the last {{ $conditions[1]->within_months }} months. -
    -
  • Glasgow (EGPF)
  • -
  • Birmingham (EGBB)
  • -
  • Bristol (EGGD)
  • -
  • Luton (EGGW)
  • -
- @foreach($hours[1] as $icao => $hour) -
- @if($conditions[1]->isMetForUser($_account)) -
{{ round($hour,2) }} Hrs {{ '('. $icao .')' }}
- @else -
{{ ($hour > 0) ? (round($hour,2)).' Hrs ('. $icao .')' : '' }}
- @endif -
- @endforeach + @if($_account->primary_state?->isDivision) +

You are a home member of the UK.

+ @else +

You are not a home member of the UK Division. If you wish to hold + a Gatwick endorsement, apply to transfer to the UK + by {!! link_to_route("visiting.landing", "clicking here") !!}.

+ @endif + + @if($onRoster) +

You are active on the controller roster.

+ @else +

You are not active on the controller roster. If you wish to hold a + Gatwick endorsement you must be active on the roster.

+ @endif
+
-
  Group Three Controlling
+
50 Hours Controlling as an S1
- Control a total of {{ $conditions[2]->required_hours }} hours on one of the following positions within the last {{ $conditions[2]->within_months }} months. -
    -
  • Jersey (EGJJ)
  • -
  • Belfast Aldergrove (EGAA)
  • -
  • Newcastle (EGNT)
  • -
  • East Midlands (EGNX)
  • -
- @foreach($hours[2] as $icao => $hour) -
- @if($conditions[2]->isMetForUser($_account)) -
{{ round($hour,2) }} Hrs {{ '('. $icao .')' }}
- @else -
{{ ($hour > 0) ? (round($hour,2)).' Hrs ('. $icao .')' : '' }}
- @endif +
+ @if($hoursMet) +
+ 50+ Hrs +
+ @endif +
+ {{ (round($totalHours,2)) .' Hrs' }}
- @endforeach +
+
-
-
-
  Membership Status
-
- @if($_account->primary_state->isDivision) - You are a home member of the UK and no further action is required. - @else -

You are not a home member of the UK Division. If you wish to hold a Gatwick endorsement, apply to transfer to the UK by {!! link_to_route("visiting.landing", "clicking here") !!}.

- @endif -
-
-
-
+ +
  Request Moodle Course
- Once you have completed the requirements above, you will be able to press the button below to request access to the Moodle course and progress to Step 2. + Once you have completed the requirements above, you will be able to press the button below to + request access to the Moodle course and progress to Step 2.

- @if($positionGroup->conditionsMetForUser($_account)) - + @if($conditionsMet) + @else diff --git a/tests/Feature/Atc/GatwickEndorsementTest.php b/tests/Feature/Atc/GatwickEndorsementTest.php new file mode 100644 index 0000000000..1946895366 --- /dev/null +++ b/tests/Feature/Atc/GatwickEndorsementTest.php @@ -0,0 +1,186 @@ +getS1Account(); + + factory(Atc::class)->create([ + 'account_id' => $account->id, + 'callsign' => 'EGPH_DEL', + 'minutes_online' => 25 * 60, + 'facility_type' => Atc::TYPE_DEL, + ]); + factory(Atc::class)->create([ + 'account_id' => $account->id, + 'callsign' => 'EGPH_GND', + 'minutes_online' => 25 * 60, + 'facility_type' => Atc::TYPE_GND, + ]); + factory(Atc::class)->create([ + 'account_id' => $account->id, + 'callsign' => 'EGCC_GND', + 'minutes_online' => 5 * 60, + 'facility_type' => Atc::TYPE_GND, + ]); + + $this->actingAs($account->fresh()) + ->get(route(self::ROUTE)) + ->assertStatus(200) + ->assertViewHas('hoursMet', true) + ->assertViewHas('onRoster', true) + ->assertViewHas('conditionsMet', true); + + } + + public function testItFailsFor30Hours() + { + $account = $this->getS1Account(); + + factory(Atc::class)->create([ + 'account_id' => $account->id, + 'callsign' => 'EGPH_DEL', + 'minutes_online' => 25 * 60, + 'facility_type' => Atc::TYPE_DEL, + ]); + factory(Atc::class)->create([ + 'account_id' => $account->id, + 'callsign' => 'EGCC_GND', + 'minutes_online' => 5 * 60, + 'facility_type' => Atc::TYPE_GND, + ]); + + $this->actingAs($account->fresh()) + ->get(route(self::ROUTE)) + ->assertStatus(200) + ->assertViewHas('hoursMet', false) + ->assertViewHas('onRoster', true) + ->assertViewHas('conditionsMet', false); + } + + public function testItFailsForHoursNonUK() + { + $account = $this->getS1Account(); + + factory(Atc::class)->create([ + 'account_id' => $account->id, + 'callsign' => 'EGPH_DEL', + 'minutes_online' => 25 * 60, + 'facility_type' => Atc::TYPE_DEL, + ]); + factory(Atc::class)->create([ + 'account_id' => $account->id, + 'callsign' => 'EGPH_GND', + 'minutes_online' => 10 * 60, + 'facility_type' => Atc::TYPE_GND, + ]); + factory(Atc::class)->create([ + 'account_id' => $account->id, + 'callsign' => 'LFPG_GND', + 'minutes_online' => 500 * 60, + 'facility_type' => Atc::TYPE_GND, + ]); + + $this->actingAs($account->fresh()) + ->get(route(self::ROUTE)) + ->assertStatus(200) + ->assertViewHas('hoursMet', false); + } + + public function testItDetectsNotOnRoster() + { + $account = $this->getS1AccountNotOnRoster(); + + factory(Atc::class)->create([ + 'account_id' => $account->id, + 'callsign' => 'EGPH_DEL', + 'minutes_online' => 25 * 60, + 'facility_type' => Atc::TYPE_DEL, + ]); + factory(Atc::class)->create([ + 'account_id' => $account->id, + 'callsign' => 'EGPH_GND', + 'minutes_online' => 25 * 60, + 'facility_type' => Atc::TYPE_GND, + ]); + factory(Atc::class)->create([ + 'account_id' => $account->id, + 'callsign' => 'EGCC_GND', + 'minutes_online' => 5 * 60, + 'facility_type' => Atc::TYPE_GND, + ]); + + $this->actingAs($account->fresh()) + ->get(route(self::ROUTE)) + ->assertStatus(200) + ->assertViewHas('hoursMet', true) + ->assertViewHas('onRoster', false) + ->assertViewHas('conditionsMet', false); + } + + public function testItRedirectsForNonS1() + { + $account = Account::factory()->create(); + + $this->actingAs($account->fresh()) + ->get(route(self::ROUTE)) + ->assertRedirect(route('mship.manage.dashboard')); + } + + public function testItRedirectsForS2() + { + $account = Account::factory()->create(); + + $qualification = Qualification::code('S2')->first(); + $account->addQualification($qualification); + $account->save(); + + $this->actingAs($account->fresh()) + ->get(route(self::ROUTE)) + ->assertRedirect(route('mship.manage.dashboard')); + } + + private function getS1Account(): Account + { + $account = Account::factory()->create(); + + $qualification = Qualification::code('S1')->first(); + $account->addQualification($qualification); + $account->save(); + + $divisionState = State::findByCode('DIVISION')->firstOrFail(); + $account->addState($divisionState, 'EUR', 'GBR'); + Roster::create(['account_id' => $account->id])->save(); + + return $account; + } + + private function getS1AccountNotOnRoster(): Account + { + $account = Account::factory()->create(); + + $qualification = Qualification::code('S1')->first(); + $account->addQualification($qualification); + $account->save(); + + $divisionState = State::findByCode('DIVISION')->firstOrFail(); + $account->addState($divisionState, 'EUR', 'GBR'); + + return $account; + } +}