Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Claim verification model #10434

Merged
merged 15 commits into from
May 23, 2024
11 changes: 11 additions & 0 deletions api/app/Enums/ClaimVerificationResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace App\Enums;

enum ClaimVerificationResult
{
case ACCEPTED;
case REJECTED;
case UNVERIFIED;
// a null value represents "unclaimed"
}
12 changes: 11 additions & 1 deletion api/app/GraphQL/Mutations/SubmitApplication.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace App\GraphQL\Mutations;

use App\Enums\ApplicationStep;
use App\Enums\ArmedForcesStatus;
use App\Enums\ClaimVerificationResult;
use App\Enums\PoolCandidateStatus;
use App\GraphQL\Validators\Mutation\SubmitApplicationValidator;
use App\Models\PoolCandidate;
Expand All @@ -21,7 +23,7 @@ public function __invoke($_, array $args)
{
// grab the specific application
// submit to validator the PoolCandidate model
$application = PoolCandidate::find($args['id']);
$application = PoolCandidate::find($args['id'])->load('user');
$submitValidator = new SubmitApplicationValidator($application);
// We haven't saved the signature yet, so manually add it to the array
$application['signature'] = $args['signature'];
Expand All @@ -37,6 +39,14 @@ public function __invoke($_, array $args)
$application->pool_candidate_status = PoolCandidateStatus::NEW_APPLICATION->name;
$application->setInsertSubmittedStepAttribute(ApplicationStep::REVIEW_AND_SUBMIT->name);

// claim verification
if ($application->user->armed_forces_status == ArmedForcesStatus::VETERAN->name) {
$application->veteran_verification = ClaimVerificationResult::UNVERIFIED->name;
}
if ($application->user->has_priority_entitlement) {
$application->priority_verification = ClaimVerificationResult::UNVERIFIED->name;
}

// need to save application before setting application snapshot since fields have yet to be saved to the database.
$application->save();

Expand Down
10 changes: 10 additions & 0 deletions api/app/Models/PoolCandidate.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
* @property Illuminate\Support\Carbon $removed_at
* @property string $removal_reason
* @property string $removal_reason_other
* @property string $veteran_verification
* @property Illuminate\Support\Carbon $veteran_verification_expiry
* @property string $priority_verification
* @property Illuminate\Support\Carbon $priority_verification_expiry
*/
class PoolCandidate extends Model
{
Expand All @@ -72,6 +76,8 @@ class PoolCandidate extends Model
'placed_at' => 'datetime',
'final_decision_at' => 'datetime',
'removed_at' => 'datetime',
'veteran_verification_expiry' => 'date',
'priority_verification_expiry' => 'date',
];

/**
Expand All @@ -91,6 +97,10 @@ class PoolCandidate extends Model
'pool_candidate_status',
'submitted_steps',
'education_requirement_option',
'veteran_verification',
'veteran_verification_expiry',
'priority_verification',
'priority_verification_expiry',
];

protected $touches = ['user'];
Expand Down
10 changes: 10 additions & 0 deletions api/app/Providers/GraphQLServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use App\Enums\CandidateRemovalReason;
use App\Enums\CandidateSuspendedFilter;
use App\Enums\CitizenshipStatus;
use App\Enums\ClaimVerificationResult;
use App\Enums\DirectiveForms\AdvertisementType;
use App\Enums\DirectiveForms\AdvertisingPlatform;
use App\Enums\DirectiveForms\ContractAuthority;
Expand Down Expand Up @@ -134,6 +135,15 @@ static function (): EnumType {
]);
}
);
$typeRegistry->registerLazy(
'ClaimVerificationResult',
static function (): EnumType {
return new EnumType([
'name' => 'ClaimVerificationResult',
'values' => array_column(ClaimVerificationResult::cases(), 'name'),
]);
}
);
$typeRegistry->registerLazy(
'ArmedForcesStatus',
static function (): EnumType {
Expand Down
28 changes: 28 additions & 0 deletions api/app/Rules/PriorityVerificationExpiryRequirement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace App\Rules;

use App\Enums\ClaimVerificationResult;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class PriorityVerificationExpiryRequirement implements ValidationRule
{
/**
* Run the validation rule.
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
// an accepted status for priority verification must be attached to a non-null expiration
if ($value['priorityVerification'] == ClaimVerificationResult::ACCEPTED->name && ! $value['priorityVerificationExpiry']) {
$fail('AcceptedPriorityRequiresExpiry');
}

// in any non-ACCEPTED cases no expiration should be present
if (($value['veteranVerification'] != ClaimVerificationResult::ACCEPTED->name && ! is_null($value['veteranVerificationExpiry'])) ||
($value['priorityVerification'] != ClaimVerificationResult::ACCEPTED->name && ! is_null($value['priorityVerificationExpiry']))
) {
$fail('NoExpirationForThisResult');
}
}
}
22 changes: 22 additions & 0 deletions api/database/factories/PoolCandidateFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
namespace Database\Factories;

use App\Enums\ApplicationStep;
use App\Enums\ArmedForcesStatus;
use App\Enums\AssessmentResultType;
use App\Enums\CandidateRemovalReason;
use App\Enums\ClaimVerificationResult;
use App\Enums\EducationRequirementOption;
use App\Enums\PoolCandidateStatus;
use App\Models\AssessmentResult;
Expand Down Expand Up @@ -161,6 +163,26 @@ public function configure()
]);
$poolCandidate->educationRequirementWorkExperiences()->sync([$experience->id]);
}

// claim verification
if ($poolCandidate->user->armed_forces_status == ArmedForcesStatus::VETERAN->name) {
$vetVerification = $this->faker->randomElement(array_column(ClaimVerificationResult::cases(), 'name'));
$vetExpiryBoolean = $vetVerification == ClaimVerificationResult::ACCEPTED->name && $this->faker->boolean();

$poolCandidate->update([
'veteran_verification' => $vetVerification,
'veteran_verification_expiry' => $vetExpiryBoolean ? $this->faker->dateTimeBetween('6 months', '24 months') : null,
]);
}
if ($poolCandidate->user->has_priority_entitlement) {
$priorityVerification = $this->faker->randomElement(array_column(ClaimVerificationResult::cases(), 'name'));
$priorityExpiryBoolean = $priorityVerification == ClaimVerificationResult::ACCEPTED->name;

$poolCandidate->update([
'priority_verification' => $priorityVerification,
'priority_verification_expiry' => $priorityExpiryBoolean ? $this->faker->dateTimeBetween('6 months', '24 months') : null,
]);
}
});
}

Expand Down
4 changes: 3 additions & 1 deletion api/database/factories/UserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ public function definition()
'accepted_operational_requirements' => $this->faker->optional->randomElements(array_column(OperationalRequirement::cases(), 'name'), 2),
'gov_employee_type' => $isGovEmployee ? $this->faker->randomElement(GovEmployeeType::cases())->name : null,
'citizenship' => $this->faker->randomElement(CitizenshipStatus::cases())->name,
'armed_forces_status' => $this->faker->randomElement(ArmedForcesStatus::cases())->name,
'armed_forces_status' => $this->faker->boolean() ?
ArmedForcesStatus::NON_CAF->name
: $this->faker->randomElement(ArmedForcesStatus::cases())->name,
Comment on lines -122 to +124
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before 2/3 of users were current/past armed forces which seemed a bit egregiously high. This small tweak just halves it.

'has_priority_entitlement' => $hasPriorityEntitlement,
'priority_number' => $hasPriorityEntitlement ? $this->faker->word() : null,
'indigenous_declaration_signature' => $isDeclared ? $this->faker->firstName() : null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

use App\Models\PoolCandidate;
use Illuminate\Database\Eloquent\Builder;
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::table('pool_candidates', function (Blueprint $table) {
$table->string('veteran_verification')->nullable();
$table->date('veteran_verification_expiry')->nullable();
$table->string('priority_verification')->nullable();
$table->date('priority_verification_expiry')->nullable();
});

PoolCandidate::whereHas('user', function (Builder $query) {
$query->where('armed_forces_status', 'VETERAN');
})->update(['veteran_verification' => 'UNVERIFIED']);
PoolCandidate::whereHas('user', function (Builder $query) {
$query->where('has_priority_entitlement', true);
})->update(['priority_verification' => 'UNVERIFIED']);
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('pool_candidates', function (Blueprint $table) {
$table->dropColumn('veteran_verification');
$table->dropColumn('veteran_verification_expiry');
$table->dropColumn('priority_verification');
$table->dropColumn('priority_verification_expiry');
});
}
};
31 changes: 31 additions & 0 deletions api/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,20 @@ type PoolCandidate {
@rename(attribute: "placed_at")
@canRoot(ability: "viewStatus")
placedDepartment: Department @belongsTo @canRoot(ability: "viewStatus")

# claim verification
veteranVerification: ClaimVerificationResult
@rename(attribute: "veteran_verification")
@canRoot(ability: "viewNotes")
veteranVerificationExpiry: Date
@rename(attribute: "veteran_verification_expiry")
@canRoot(ability: "viewNotes")
priorityVerification: ClaimVerificationResult
@rename(attribute: "priority_verification")
@canRoot(ability: "viewNotes")
priorityVerificationExpiry: Date
@rename(attribute: "priority_verification_expiry")
@canRoot(ability: "viewNotes")
}

type PoolCandidateWithSkillCount {
Expand Down Expand Up @@ -1182,6 +1196,17 @@ input UpdatePoolCandidateStatusInput {
status: PoolCandidateStatus @rename(attribute: "pool_candidate_status")
}

input UpdatePoolCandidateClaimVerificationInput {
veteranVerification: ClaimVerificationResult
@rename(attribute: "veteran_verification")
veteranVerificationExpiry: Date
@rename(attribute: "veteran_verification_expiry")
priorityVerification: ClaimVerificationResult
@rename(attribute: "priority_verification")
priorityVerificationExpiry: Date
@rename(attribute: "priority_verification_expiry")
}

input PlaceCandidateInput {
placementType: PlacementType!
departmentId: UUID!
Expand Down Expand Up @@ -1734,6 +1759,12 @@ type Mutation {
model: "PoolCandidate"
injectArgs: true
)
updatePoolCandidateClaimVerification(
petertgiles marked this conversation as resolved.
Show resolved Hide resolved
id: UUID!
poolCandidate: UpdatePoolCandidateClaimVerificationInput!
@rules(apply: ["App\\Rules\\PriorityVerificationExpiryRequirement"])
@spread
): PoolCandidate @update @guard @canFind(ability: "updateNotes", find: "id")
updatePoolCandidateNotes(id: UUID!, notes: String): PoolCandidate
@update
@guard
Expand Down
18 changes: 18 additions & 0 deletions api/storage/app/lighthouse-schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ type Mutation {
createPoolCandidateAsAdmin(poolCandidate: CreatePoolCandidateAsAdminInput!): PoolCandidate
updatePoolCandidateStatus(id: UUID!, poolCandidate: UpdatePoolCandidateStatusInput!): PoolCandidate
togglePoolCandidateBookmark(id: ID!): Boolean
updatePoolCandidateClaimVerification(id: UUID!, poolCandidate: UpdatePoolCandidateClaimVerificationInput!): PoolCandidate
updatePoolCandidateNotes(id: UUID!, notes: String): PoolCandidate
removeCandidate(id: UUID!, removalReason: CandidateRemovalReason!, removalReasonOther: String): PoolCandidate
reinstateCandidate(id: UUID!): PoolCandidate
Expand Down Expand Up @@ -457,6 +458,10 @@ type PoolCandidate {
finalDecisionAt: DateTime
placedAt: DateTime
placedDepartment: Department
veteranVerification: ClaimVerificationResult
veteranVerificationExpiry: Date
priorityVerification: ClaimVerificationResult
priorityVerificationExpiry: Date
}

type PoolCandidateWithSkillCount {
Expand Down Expand Up @@ -1035,6 +1040,13 @@ input UpdatePoolCandidateStatusInput {
status: PoolCandidateStatus
}

input UpdatePoolCandidateClaimVerificationInput {
veteranVerification: ClaimVerificationResult
veteranVerificationExpiry: Date
priorityVerification: ClaimVerificationResult
priorityVerificationExpiry: Date
}

input PlaceCandidateInput {
placementType: PlacementType!
departmentId: UUID!
Expand Down Expand Up @@ -2083,6 +2095,12 @@ enum CitizenshipStatus {
OTHER
}

enum ClaimVerificationResult {
ACCEPTED
REJECTED
UNVERIFIED
}

enum ArmedForcesStatus {
VETERAN
MEMBER
Expand Down
45 changes: 45 additions & 0 deletions api/tests/Feature/PoolApplicationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use App\Enums\ArmedForcesStatus;
use App\Enums\AssessmentStepType;
use App\Enums\ClaimVerificationResult;
use App\Enums\EducationRequirementOption;
use App\Enums\PoolCandidateStatus;
use App\Enums\PoolLanguage;
Expand Down Expand Up @@ -35,6 +36,7 @@

use function PHPUnit\Framework\assertEquals;
use function PHPUnit\Framework\assertNotNull;
use function PHPUnit\Framework\assertSame;

class PoolApplicationTest extends TestCase
{
Expand Down Expand Up @@ -1075,4 +1077,47 @@ public function testApplicationSubmitEducationRequirement(): void
'signature' => 'sign',
]);
}

public function testApplicationSubmissionClaimVerification(): void
{
$newPool = Pool::factory()->create([
'closing_date' => Carbon::now()->addDays(1),
'advertisement_language' => PoolLanguage::ENGLISH->name,
]);
$newPool->essentialSkills()->sync([]);

// not veteran, has priority
$this->applicantUser->armed_forces_status = ArmedForcesStatus::MEMBER->name;
$this->applicantUser->has_priority_entitlement = true;
$this->applicantUser->priority_number = 'abc';
$this->applicantUser->save();

$newPoolCandidate = PoolCandidate::factory()->create([
'user_id' => $this->applicantUser->id,
'pool_id' => $newPool->id,
'pool_candidate_status' => PoolCandidateStatus::DRAFT->name,
'submitted_at' => null,
]);

$educationExperience = EducationExperience::factory()->create(['user_id' => $newPoolCandidate->user_id]);
$newPoolCandidate->education_requirement_option = EducationRequirementOption::EDUCATION->name;
$newPoolCandidate->educationRequirementEducationExperiences()->sync([$educationExperience->id]);
$newPoolCandidate->save();

$this->actingAs($this->applicantUser, 'api')
->graphQL(
$this->submitMutationDocument,
[
'id' => $newPoolCandidate->id,
'sig' => 'sign',
]
)->assertJsonFragment([
'signature' => 'sign',
]);
$newPoolCandidate->refresh();

// assert verification defaults filled in upon submitting application
assertSame($newPoolCandidate->veteran_verification, null);
assertSame($newPoolCandidate->priority_verification, ClaimVerificationResult::UNVERIFIED->name);
}
}
Loading