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

[Feat] Backend for employee profile #12334

Merged
merged 17 commits into from
Dec 31, 2024
Merged
1 change: 1 addition & 0 deletions .vscode/project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Laratrust
Laravel
Lcobucci
licences
MENTEE
Métis
Mi'kmaw
Michif
Expand Down
18 changes: 18 additions & 0 deletions api/app/Enums/ExecCoaching.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace App\Enums;

use App\Traits\HasLocalization;

enum ExecCoaching
{
use HasLocalization;

case COACHING;
case LEARNING;

Copy link
Contributor

Choose a reason for hiding this comment

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

Exec coaching status

  • Not participating
  • Have a coach
  • Is a coach
  • Both coaching/learning

same question as Mentorship status below
Are we not storing Not participating as sep enum value?

Copy link
Member Author

Choose a reason for hiding this comment

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

I envisioned it being an array.

  • [] = not participating
  • null = not provided

Copy link
Contributor

Choose a reason for hiding this comment

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

that works as well!

public static function getLangFilename(): string
{
return 'exec_coaching';
}
}
18 changes: 18 additions & 0 deletions api/app/Enums/Mentorship.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace App\Enums;

use App\Traits\HasLocalization;

enum Mentorship
{
use HasLocalization;

case MENTOR;
case MENTEE;
mnigh marked this conversation as resolved.
Show resolved Hide resolved

public static function getLangFilename(): string
{
return 'mentorship';
}
}
19 changes: 19 additions & 0 deletions api/app/Enums/MoveInterest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace App\Enums;

use App\Traits\HasLocalization;

enum MoveInterest
{
use HasLocalization;

case ABOVE_LEVEL;
case AT_LEVEL;
case BELOW_LEVEL;

public static function getLangFilename(): string
{
return 'move_interest';
}
}
20 changes: 20 additions & 0 deletions api/app/Enums/OrganizationTypeInterest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Enums;

use App\Traits\HasLocalization;

enum OrganizationTypeInterest
{
use HasLocalization;

case CURRENT;
case OTHER_DEPARTMENT;
case OTHER_AGENCY;
case OTHER_CROWN_CORP;

public static function getLangFilename(): string
{
return 'organization_type_interest';
}
}
65 changes: 65 additions & 0 deletions api/app/GraphQL/Validators/UpdateEmployeeProfileInputValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace App\GraphQL\Validators;

use App\Enums\ExecCoaching;
use App\Enums\Mentorship;
use App\Enums\MoveInterest;
use App\Enums\OrganizationTypeInterest;
use App\Models\WorkStream;
use Database\Helpers\ApiErrorEnums;
use Illuminate\Validation\Rule;
use Nuwave\Lighthouse\Validation\Validator;

final class UpdateEmployeeProfileInputValidator extends Validator
{
/**
* Return the validation rules.
*
* @return array<string, array<mixed>>
*/
public function rules(): array
{
$communityId = $this->arg('dreamRoleCommunity.connect');
$workStreams = $communityId ? WorkStream::where('community_id', $communityId)->get('id')->pluck('id') : [];

return [
'organizationTypeInterest' => ['nullable'],
'organizationTypeInterest.*' => [Rule::in(array_column(OrganizationTypeInterest::cases(), 'name'))],
'moveInterest' => ['nullable'],
'moveInterest.*' => [Rule::in(array_column(MoveInterest::cases(), 'name'))],
'mentorshipStatus' => ['nullable'],
'mentorshipStatus.*' => [Rule::in(array_column(Mentorship::cases(), 'name'))],
'mentorshipInterest' => ['nullable'],
'mentorshipInterest.*' => [Rule::in(array_column(Mentorship::cases(), 'name'))],
'execInterest' => ['nullable', 'boolean'],
'execCoachingStatus' => ['nullable'],
'execCoachingStatus.*' => [Rule::in(array_column(ExecCoaching::cases(), 'name'))],
'execCoachingInterest' => ['nullable'],
'execCoachingInterest.*' => [Rule::in(array_column(ExecCoaching::cases(), 'name'))],

'dreamRoleTitle' => ['nullable', 'string'],
'dreamRoleAdditionalInformation' => ['nullable', 'string'],
'dreamRoleCommunity.connect' => ['uuid', 'exists:communities,id'],
'dreamRoleClassification.connect' => ['uuid', 'exists:classifications,id'],
'dreamRoleWorkStream.connect' => ['prohibited_if:dreamRoleCommunity,null', 'uuid', 'exists:work_streams,id', Rule::in($workStreams)],
'dreamRoleDepartments.sync.*' => ['uuid', 'exists:departments,id'],

'aboutYou' => ['nullable', 'string'],
'careerGoals' => ['nullable', 'string'],
'learningGoals' => ['nullable', 'string'],
'workStyle' => ['nullable', 'string'],
];
}

public function messages(): array
{
return [
'dreamRoleCommunity.connect.exists' => ApiErrorEnums::COMMUNITY_NOT_FOUND,
'dreamRoleClassification.connect.exists' => ApiErrorEnums::CLASSIFICATION_NOT_FOUND,
'dreamRoleWorkStream.connect.exists' => ApiErrorEnums::WORK_STREAM_NOT_FOUND,
'dreamRoleWorkStream.connect.in' => ApiErrorEnums::WORK_STREAM_NOT_IN_COMMUNITY,
'dreamRoleDepartments.sync.*.exists' => ApiErrorEnums::DEPARTMENT_NOT_FOUND,
];
}
}
73 changes: 73 additions & 0 deletions api/app/Models/EmployeeProfile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

/**
* Class EmployeeProfile
*
* @property string $id
* @property ?array $career_planning_organization_type_interest
* @property ?array $career_planning_move_interest
* @property ?array $career_planning_mentorship_status
* @property ?array $career_planning_mentorship_interest
* @property ?bool $career_planning_exec_interest
* @property ?array $career_planning_exec_coaching_status
* @property ?array $career_planning_exec_coaching_interest
* @property string $career_planning_about_you
* @property string $career_planning_career_goals
* @property string $career_planning_learning_goals
* @property string $career_planning_work_style
* @property string $dream_role_title
* @property string $dream_role_additional_information
*/
class EmployeeProfile extends Model
{
protected $table = 'users';

protected $keyType = 'string';

protected $casts = [
'career_planning_organization_type_interest' => 'array',
'career_planning_move_interest' => 'array',
'career_planning_mentorship_status' => 'array',
'career_planning_mentorship_interest' => 'array',
'career_planning_exec_interest' => 'boolean',
'career_planning_exec_coaching_status' => 'array',
'career_planning_exec_coaching_interest' => 'array',
];

/** @return BelongsTo<Community, $this> */
public function dreamRoleCommunity(): BelongsTo
{
return $this->belongsTo(Community::class, 'dream_role_community_id');
}

/** @return BelongsTo<Classification, $this> */
public function dreamRoleClassification(): BelongsTo
{
return $this->belongsTo(Classification::class, 'dream_role_classification_id');
}

/** @return BelongsTo<WorkStream, $this> */
public function dreamRoleWorkStream(): BelongsTo
{
return $this->belongsTo(WorkStream::class, 'dream_role_work_stream_id');
}

/** @return BelongsToMany<Department, $this> */
public function dreamRoleDepartments(): BelongsToMany
{
return $this->belongsToMany(Department::class, 'department_user_dream_role', 'user_id', 'department_id');
}

/** @return HasOne<User, $this> */
public function userPublicProfile(): HasOne
{
return $this->hasOne(User::class, 'id')->select(['email', 'firstName', 'lastName']);
}
}
6 changes: 6 additions & 0 deletions api/app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Support\Arr;
Expand Down Expand Up @@ -303,6 +304,11 @@ public function poolCandidateSearchRequests(): HasMany
return $this->hasMany(PoolCandidateSearchRequest::class);
}

public function employeeProfile(): HasOne
{
return $this->hasOne(EmployeeProfile::class, 'id');
}

// This method will add the specified skills to UserSkills if they don't exist yet.
public function addSkills($skill_ids)
{
Expand Down
25 changes: 25 additions & 0 deletions api/app/Policies/EmployeeProfilePolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace App\Policies;

use App\Models\EmployeeProfile;
use App\Models\User;

class EmployeeProfilePolicy
{
/**
* Determine whether the user can view the model.
*/
public function view(User $user, EmployeeProfile $employeeProfile): bool
{
return $user->isAbleTo('view-own-employeeProfile') && $employeeProfile->id === $user->id;
}

/**
* Determine whether the user can update the model.
*/
public function update(User $user, EmployeeProfile $employeeProfile): bool
{
return $user->isAbleTo('update-own-employeeProfile') && $employeeProfile->id === $user->id;
}
}
13 changes: 13 additions & 0 deletions api/config/rolepermission.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
'user' => 'user',
'userBasicInfo' => 'userBasicInfo',
'userSub' => 'userSub',
'employeeProfile' => 'employeeProfile',
'applicantProfile' => 'applicantProfile',
'draftPool' => 'draftPool',
'publishedPool' => 'publishedPool',
Expand Down Expand Up @@ -243,6 +244,15 @@
'fr' => 'Mettre à jour son propre profil de candidat',
],

'view-own-employeeProfile' => [
'en' => 'View any Employee Profile',
'fr' => 'Visionner tout profil de candidat',
],
'update-own-employeeProfile' => [
'en' => 'Update any Employee Profile',
'fr' => 'Visionner tout profil de candidat',
],

'view-team-draftPool' => [
'en' => 'View draft Pools in this Team',
'fr' => 'Voir les bassins de brouillons dans cette équipe',
Expand Down Expand Up @@ -880,6 +890,9 @@
'user' => [
'own' => ['view', 'update'],
],
'employeeProfile' => [
'own' => ['view', 'update'],
],
'publishedPool' => [
'any' => ['view'],
],
Expand Down
10 changes: 10 additions & 0 deletions api/database/factories/CommunityFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Database\Factories;

use App\Models\Community;
use App\Models\WorkStream;
use Illuminate\Database\Eloquent\Factories\Factory;

class CommunityFactory extends Factory
Expand Down Expand Up @@ -74,4 +75,13 @@ public function withCommunityAdmins(?array $userIds = null)
}
});
}

public function withWorkStreams(?int $min = 1, ?int $max = 3)
{
$count = $this->faker->numberBetween($min, $max);

return $this->afterCreating(function (Community $community) use ($count) {
WorkStream::factory()->count($count)->create(['community_id' => $community->id]);
});
}
}
41 changes: 41 additions & 0 deletions api/database/factories/UserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
use App\Enums\CitizenshipStatus;
use App\Enums\EstimatedLanguageAbility;
use App\Enums\EvaluatedLanguageAbility;
use App\Enums\ExecCoaching;
use App\Enums\GovEmployeeType;
use App\Enums\IndigenousCommunity;
use App\Enums\Language;
use App\Enums\Mentorship;
use App\Enums\MoveInterest;
use App\Enums\NotificationFamily;
use App\Enums\OperationalRequirement;
use App\Enums\OrganizationTypeInterest;
use App\Enums\PositionDuration;
use App\Enums\ProvinceOrTerritory;
use App\Models\AwardExperience;
Expand Down Expand Up @@ -208,6 +212,43 @@ public function asGovEmployee($isGovEmployee = true)
});
}

public function withEmployeeProfile()
{
return $this->afterCreating(function (User $user) {
$community = Community::inRandomOrder()->first();
if (is_null($community)) {
$community = Community::factory()->withWorkStreams()->create();
}
$classification = Classification::inRandomOrder()->first();
if (is_null($classification)) {
$classification = Classification::factory()->create();
}
$workStream = $this->faker->randomElement($community->workStreams);
$departments = Department::inRandomOrder()->limit($this->faker->numberBetween(1, 3))->get();

$user->employeeProfile->dreamRoleDepartments()->sync($departments);

$user->employeeProfile()->update([
'career_planning_organization_type_interest' => $this->faker->randomElements(array_column(OrganizationTypeInterest::cases(), 'name'), null),
'career_planning_move_interest' => $this->faker->randomElements(array_column(MoveInterest::cases(), 'name'), null),
'career_planning_mentorship_status' => $this->faker->optional(weight: 70)->randomElements(array_column(Mentorship::cases(), 'name'), null),
'career_planning_mentorship_interest' => $this->faker->optional(weight: 70)->randomElements(array_column(Mentorship::cases(), 'name'), null),
'career_planning_exec_interest' => $this->faker->boolean(),
'career_planning_exec_coaching_status' => $this->faker->optional(weight: 80)->randomElements(array_column(ExecCoaching::cases(), 'name'), null),
'career_planning_exec_coaching_interest' => $this->faker->optional(weight: 80)->randomElements(array_column(ExecCoaching::cases(), 'name'), null),
'career_planning_about_you' => $this->faker->paragraph(),
'career_planning_career_goals' => $this->faker->paragraph(),
'career_planning_learning_goals' => $this->faker->paragraph(),
'career_planning_work_style' => $this->faker->paragraph(),
'dream_role_title' => $this->faker->words(3, true),
'dream_role_additional_information' => $this->faker->paragraph(),
'dream_role_community_id' => $community->id,
'dream_role_classification_id' => $classification->id,
'dream_role_work_stream_id' => $workStream->id,
]);
});
}

public function configure()
{
return $this->afterCreating(function (User $user) {
Expand Down
Loading
Loading