-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: sync new S1 exam passes to roster for home members (#3565)
- Loading branch information
Showing
9 changed files
with
305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
<?php | ||
|
||
namespace App\Console\Commands\Roster; | ||
|
||
use App\Models\Cts\Member; | ||
use App\Models\Mship\Account; | ||
use App\Models\Mship\State; | ||
use App\Models\Roster; | ||
use App\Repositories\Cts\ExamResultRepository; | ||
use Illuminate\Console\Command; | ||
|
||
class CheckForNewS1ExamPasses extends Command | ||
{ | ||
/** | ||
* The name and signature of the console command. | ||
* | ||
* @var string | ||
*/ | ||
protected $signature = 'roster:check-new-s1-exams'; | ||
|
||
/** | ||
* The console command description. | ||
* | ||
* @var string | ||
*/ | ||
protected $description = 'Make a query to CTS for new exam passes and add them to the roster if applicable.'; | ||
|
||
/** | ||
* Execute the console command. | ||
*/ | ||
public function handle(ExamResultRepository $repository) | ||
{ | ||
$recentSuccessfulS1Exams = $repository->getRecentPassedExamsOfType('OBS'); | ||
|
||
foreach ($recentSuccessfulS1Exams as $exam) { | ||
$ctsMember = Member::where('id', $exam->student_id)->first(); | ||
$coreAccount = Account::find($ctsMember->cid); | ||
|
||
if (! $coreAccount) { | ||
$this->error("Could not find account for student ID {$exam->student_id}."); | ||
|
||
continue; | ||
} | ||
|
||
if (! $coreAccount->hasState(State::findByCode('DIVISION'))) { | ||
$this->error("Account {$coreAccount->id} does not have the DIVISION state."); | ||
|
||
continue; | ||
} | ||
|
||
Roster::upsert(['account_id' => $coreAccount->id], uniqueBy: ['account_id']); | ||
|
||
$this->info("Added account {$coreAccount->id} to the roster."); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<?php | ||
|
||
namespace App\Models\Cts; | ||
|
||
use Illuminate\Database\Eloquent\Factories\HasFactory; | ||
use Illuminate\Database\Eloquent\Model; | ||
|
||
class PracticalResult extends Model | ||
{ | ||
use HasFactory; | ||
|
||
protected $connection = 'cts'; | ||
|
||
public $timestamps = false; | ||
|
||
public const PASSED = 'P'; | ||
|
||
public const FAILED = 'F'; | ||
|
||
protected $casts = [ | ||
'date' => 'datetime', | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?php | ||
|
||
namespace App\Repositories\Cts; | ||
|
||
use App\Models\Cts\PracticalResult; | ||
use Illuminate\Support\Collection; | ||
|
||
class ExamResultRepository | ||
{ | ||
public function getRecentPassedExamsOfType(string $type, int $daysConsideredRecent = 3): Collection | ||
{ | ||
return PracticalResult::where('result', PracticalResult::PASSED) | ||
->where('exam', $type) | ||
->where('date', '>=', now()->subDays($daysConsideredRecent)) | ||
->get(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
|
||
namespace Database\Factories\Cts; | ||
|
||
use App\Models\Cts\Member; | ||
use Illuminate\Database\Eloquent\Factories\Factory; | ||
|
||
/** | ||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Cts\PracticalResult> | ||
*/ | ||
class PracticalResultFactory extends Factory | ||
{ | ||
/** | ||
* Define the model's default state. | ||
* | ||
* @return array<string, mixed> | ||
*/ | ||
public function definition(): array | ||
{ | ||
return [ | ||
'examid' => $this->faker->randomNumber(3), | ||
'student_id' => factory(Member::class)->create()->id, | ||
'exam' => $this->faker->randomElement(['OBS', 'TWR', 'APP', 'CTR']), | ||
'result' => $this->faker->randomElement(['P', 'F']), | ||
'date' => $this->faker->dateTime(), | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
<?php | ||
|
||
namespace Tests\Unit\CTS; | ||
|
||
use App\Models\Cts\Member; | ||
use App\Models\Cts\PracticalResult; | ||
use App\Models\Mship\Account; | ||
use App\Repositories\Cts\ExamResultRepository; | ||
use Tests\TestCase; | ||
|
||
class ExamResultsRepositoryTest extends TestCase | ||
{ | ||
public function test_retrieves_passed_exam_results_of_type() | ||
{ | ||
$account = Account::factory()->create(['id' => 1111111]); | ||
$member = factory(Member::class)->create([ | ||
'cid' => $account->id, | ||
]); | ||
|
||
$examResult = PracticalResult::factory()->create([ | ||
'result' => PracticalResult::PASSED, | ||
'student_id' => $member->id, | ||
'exam' => 'OBS', | ||
'date' => now()->subDays(1), | ||
]); | ||
|
||
// ensure failed result isn't returned | ||
$notSuccessfulPracticalResult = PracticalResult::factory()->create([ | ||
'result' => PracticalResult::FAILED, | ||
'student_id' => $member->id, | ||
'exam' => 'OBS', | ||
]); | ||
|
||
$repository = new ExamResultRepository(); | ||
$result = $repository->getRecentPassedExamsOfType('OBS'); | ||
|
||
$this->assertNotNull($result->where('id', $examResult->id)->first()); | ||
$this->assertNull($result->where('id', $notSuccessfulPracticalResult->id)->first()); | ||
} | ||
|
||
public function test_doesnt_return_non_recent_exam_passes() | ||
{ | ||
$account = Account::factory()->create(['id' => 1111111]); | ||
$member = factory(Member::class)->create([ | ||
'cid' => $account->id, | ||
]); | ||
|
||
$examResult = PracticalResult::factory()->create([ | ||
'result' => PracticalResult::PASSED, | ||
'student_id' => $member->id, | ||
'exam' => 'OBS', | ||
'date' => now()->subDays(4), | ||
]); | ||
|
||
$repository = new ExamResultRepository(); | ||
$result = $repository->getRecentPassedExamsOfType('OBS'); | ||
|
||
$this->assertNull($result->where('id', $examResult->id)->first()); | ||
} | ||
|
||
public function test_only_returns_recent_successful_exams_of_specified_type() | ||
{ | ||
$account = Account::factory()->create(['id' => 1111111]); | ||
$member = factory(Member::class)->create([ | ||
'cid' => $account->id, | ||
]); | ||
|
||
$examResult = PracticalResult::factory()->create([ | ||
'result' => PracticalResult::PASSED, | ||
'student_id' => $member->id, | ||
'exam' => 'TWR', | ||
'date' => now()->subDays(1), | ||
]); | ||
|
||
$notSuccessfulPracticalResult = PracticalResult::factory()->create([ | ||
'result' => PracticalResult::FAILED, | ||
'student_id' => $member->id, | ||
'exam' => 'OBS', | ||
'date' => now()->subDays(1), | ||
]); | ||
|
||
$repository = new ExamResultRepository(); | ||
$result = $repository->getRecentPassedExamsOfType('OBS'); | ||
|
||
$this->assertNull($result->where('id', $examResult->id)->first()); | ||
$this->assertNull($result->where('id', $notSuccessfulPracticalResult->id)->first()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
<?php | ||
|
||
namespace Tests\Unit\Command; | ||
|
||
use App\Models\Cts\Member; | ||
use App\Models\Cts\PracticalResult; | ||
use App\Models\Mship\Account; | ||
use App\Models\Mship\State; | ||
use App\Models\Roster; | ||
use Illuminate\Support\Facades\Artisan; | ||
use Tests\TestCase; | ||
|
||
class AddNewS1ToRosterCommandTest extends TestCase | ||
{ | ||
public function test_detects_recent_s1_exams_and_adds_when_not_on_roster_home_member() | ||
{ | ||
$account = Account::factory()->create(['id' => 1111111]); | ||
$account->addState(State::findByCode('DIVISION')); | ||
|
||
$ctsMember = factory(Member::class)->create(['cid' => $account->id]); | ||
PracticalResult::factory()->create(['student_id' => $ctsMember->id, 'result' => PracticalResult::PASSED, 'exam' => 'OBS', 'date' => now()->subDays(1)]); | ||
|
||
Artisan::call('roster:check-new-s1-exams'); | ||
|
||
$this->assertDatabaseHas('roster', [ | ||
'account_id' => $account->id, | ||
]); | ||
} | ||
|
||
public function test_maintains_roster_when_already_on_roster() | ||
{ | ||
$account = Account::factory()->create(['id' => 1111111]); | ||
$account->addState(State::findByCode('DIVISION')); | ||
|
||
$ctsMember = factory(Member::class)->create(['cid' => $account->id]); | ||
PracticalResult::factory()->create(['student_id' => $ctsMember->id, 'result' => PracticalResult::PASSED, 'exam' => 'OBS', 'date' => now()->subDays(1)]); | ||
|
||
Roster::create(['account_id' => $account->id]); | ||
|
||
Artisan::call('roster:check-new-s1-exams'); | ||
|
||
$this->assertDatabaseHas('roster', [ | ||
'account_id' => $account->id, | ||
]); | ||
} | ||
|
||
public function test_does_not_add_when_not_on_roster_and_missing_division_state() | ||
{ | ||
$account = Account::factory()->create(['id' => 1111111]); | ||
$account->addState(State::findByCode('VISITING')); | ||
|
||
$ctsMember = factory(Member::class)->create(['cid' => $account->id]); | ||
PracticalResult::factory()->create(['student_id' => $ctsMember->id, 'result' => PracticalResult::PASSED, 'exam' => 'OBS', 'date' => now()->subDays(1)]); | ||
|
||
Artisan::call('roster:check-new-s1-exams'); | ||
|
||
$this->assertDatabaseMissing('roster', [ | ||
'account_id' => $account->id, | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters