Skip to content

Commit

Permalink
feat: hire candidate (#1279)
Browse files Browse the repository at this point in the history
  • Loading branch information
djaiss committed Aug 27, 2021
1 parent e11fb0e commit 4ef1544
Show file tree
Hide file tree
Showing 22 changed files with 905 additions and 45 deletions.
10 changes: 10 additions & 0 deletions app/Helpers/LogHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,16 @@ public static function processAuditLog(AuditLog $log): string
]);
break;

case 'candidate_hired':
$sentence = trans('account.log_candidate_hired', [
'job_opening_id' => $log->object->{'job_opening_id'},
'job_opening_title' => $log->object->{'job_opening_title'},
'job_opening_reference_number' => $log->object->{'job_opening_reference_number'},
'candidate_id' => $log->object->{'candidate_id'},
'candidate_name' => $log->object->{'candidate_name'},
]);
break;

case 'job_opening_updated':
$sentence = trans('account.log_job_opening_updated', [
'job_opening_id' => $log->object->{'job_opening_id'},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Http\Controllers\Company\Dashboard\HR;

use Carbon\Carbon;
use Inertia\Inertia;
use App\Helpers\DateHelper;
use App\Helpers\ImageHelper;
Expand All @@ -15,6 +16,7 @@
use App\Http\Controllers\Controller;
use App\Models\Company\CandidateStage;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Services\Company\Adminland\JobOpening\HireCandidate;
use App\Services\Company\Adminland\JobOpening\ProcessCandidateStage;
use App\Http\ViewHelpers\Dashboard\HR\DashboardHRCandidatesViewHelper;
use App\Services\Company\Adminland\JobOpening\CreateCandidateStageNote;
Expand Down Expand Up @@ -421,4 +423,96 @@ public function destroyNote(Request $request, int $companyId, int $jobOpeningId,
'data' => true,
], 200);
}

/**
* Show the Hire candidate page.
*
* @param Request $request
* @param integer $companyId
* @param integer $jobOpeningId
* @param integer $candidateId
* @return mixed
*/
public function hire(Request $request, int $companyId, int $jobOpeningId, int $candidateId): mixed
{
$company = InstanceHelper::getLoggedCompany();
$loggedEmployee = InstanceHelper::getLoggedEmployee();

// is this person HR?
if ($loggedEmployee->permission_level > config('officelife.permission_level.hr')) {
return redirect('home');
}

try {
$jobOpening = JobOpening::where('company_id', $company->id)
->with('team')
->with('position')
->with('sponsors')
->findOrFail($jobOpeningId);
} catch (ModelNotFoundException $e) {
return redirect('home');
}

try {
$candidate = Candidate::where('company_id', $company->id)
->findOrFail($candidateId);
} catch (ModelNotFoundException $e) {
return redirect('home');
}

if ($jobOpening->fulfilled || ! $jobOpening->active) {
return redirect('home');
}

$jobOpeningInfo = DashboardHRCandidatesViewHelper::jobOpening($company, $jobOpening);
$candidateInfo = DashboardHRCandidatesViewHelper::candidate($company, $jobOpening, $candidate);

return Inertia::render('Dashboard/HR/JobOpenings/Candidates/Hire', [
'notifications' => NotificationHelper::getNotifications($loggedEmployee),
'jobOpening' => $jobOpeningInfo,
'candidate' => $candidateInfo,
'year' => Carbon::now()->year,
'month' => Carbon::now()->month,
'day' => Carbon::now()->day,
'tab' => 'recruiting',
]);
}

/**
* Actually hire the candidate.
*
* @param Request $request
* @param integer $companyId
* @param integer $jobOpeningId
* @param integer $candidateId
* @return mixed
*/
public function storeHire(Request $request, int $companyId, int $jobOpeningId, int $candidateId)
{
$loggedCompany = InstanceHelper::getLoggedCompany();
$loggedEmployee = InstanceHelper::getLoggedEmployee();

$hiredAt = Carbon::createFromDate(
$request->input('year'),
$request->input('month'),
$request->input('day')
)->format('Y-m-d');

(new HireCandidate)->execute([
'company_id' => $loggedCompany->id,
'author_id' => $loggedEmployee->id,
'job_opening_id' => $jobOpeningId,
'candidate_id' => $candidateId,
'email' => $request->input('email'),
'first_name' => $request->input('first_name'),
'last_name' => $request->input('last_name'),
'hired_at' => $hiredAt,
]);

return response()->json([
'data' => route('dashboard.hr.openings.index', [
'company' => $loggedCompany,
]),
], 201);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,36 @@ class DashboardHRCandidatesViewHelper
*/
public static function jobOpening(Company $company, JobOpening $jobOpening): ?array
{
$team = $jobOpening->team;
$position = $jobOpening->position;

return [
'id' => $jobOpening->id,
'title' => $jobOpening->title,
'description' => StringHelper::parse($jobOpening->description),
'slug' => $jobOpening->slug,
'reference_number' => $jobOpening->reference_number,
'active' => $jobOpening->active,
'fulfilled' => $jobOpening->fulfilled,
'activated_at' => $jobOpening->activated_at ? DateHelper::formatDate($jobOpening->activated_at) : null,
'position' => [
'id' => $position->id,
'title' => $position->title,
'count_employees' => $position->employees()->notLocked()->count(),
'url' => route('hr.positions.show', [
'company' => $company,
'position' => $position,
]),
],
'team' => $team ? [
'id' => $team->id,
'name' => $team->name,
'count' => $team->employees()->notLocked()->count(),
'url' => route('team.show', [
'company' => $company,
'team' => $team,
]),
] : null,
'url' => route('dashboard.hr.openings.show', [
'company' => $company,
'jobOpening' => $jobOpening,
Expand Down Expand Up @@ -71,6 +93,11 @@ public static function candidate(Company $company, JobOpening $jobOpening, Candi
'rejected' => $candidate->rejected,
'created_at' => DateHelper::formatDate($candidate->created_at),
'stages' => $candidateStagesCollection,
'url_hire' => route('dashboard.hr.candidates.hire', [
'company' => $company,
'jobOpening' => $jobOpening,
'candidate' => $candidate,
]),
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ public static function show(Company $company, JobOpening $jobOpening): array
'slug' => $jobOpening->slug,
'reference_number' => $jobOpening->reference_number,
'active' => $jobOpening->active,
'fulfilled' => $jobOpening->fulfilled,
'activated_at' => $jobOpening->activated_at ? DateHelper::formatDate($jobOpening->activated_at) : null,
'page_views' => $jobOpening->page_views,
'recruiting_stage_template_id' => $jobOpening->template->id,
Expand All @@ -215,6 +216,22 @@ public static function show(Company $company, JobOpening $jobOpening): array
'team' => $team,
]),
] : null,
'employee' => $jobOpening->fulfilled ?
($jobOpening->candidateWhoWonTheJob->employee ? [
'id' => $jobOpening->candidateWhoWonTheJob->employee->id,
'name' => $jobOpening->candidateWhoWonTheJob->employee->name,
'avatar' => ImageHelper::getAvatar($jobOpening->candidateWhoWonTheJob->employee, 35),
'position' => (! $jobOpening->candidateWhoWonTheJob->employee->position) ? null : [
'id' => $jobOpening->candidateWhoWonTheJob->employee->position->id,
'title' => $jobOpening->candidateWhoWonTheJob->employee->position->title,
],
'url' => route('employees.show', [
'company' => $company,
'employee' => $jobOpening->candidateWhoWonTheJob->employee,
]),
] : [
'name' => $jobOpening->candidateWhoWonTheJob->employee_name,
]) : null,
'url_public_view' => route('jobs.company.show.incognito', [
'company' => $company->slug,
'job' => $jobOpening->slug,
Expand Down
11 changes: 11 additions & 0 deletions app/Models/Company/Candidate.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Candidate extends Model
protected $fillable = [
'company_id',
'job_opening_id',
'employee_id',
'name',
'email',
'uuid',
Expand Down Expand Up @@ -78,6 +79,16 @@ public function jobOpening()
return $this->belongsTo(JobOpening::class);
}

/**
* Get the employee associated with the candidate.
*
* @return BelongsTo
*/
public function employee()
{
return $this->belongsTo(Employee::class);
}

/**
* Get the job opening associated with the candidate.
*
Expand Down
149 changes: 149 additions & 0 deletions app/Services/Company/Adminland/JobOpening/HireCandidate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<?php

namespace App\Services\Company\Adminland\JobOpening;

use Carbon\Carbon;
use App\Jobs\LogAccountAudit;
use App\Services\BaseService;
use App\Models\Company\Employee;
use App\Models\Company\Candidate;
use App\Models\Company\JobOpening;
use App\Services\Company\Employee\Team\AddEmployeeToTeam;
use App\Services\Company\Employee\HiringDate\SetHiringDate;
use App\Services\Company\Adminland\Employee\AddEmployeeToCompany;

class HireCandidate extends BaseService
{
protected array $data;
protected JobOpening $jobOpening;
protected Candidate $candidate;
protected Employee $employee;
protected Carbon $hiredAt;

/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules(): array
{
return [
'company_id' => 'required|integer|exists:companies,id',
'author_id' => 'required|integer|exists:employees,id',
'job_opening_id' => 'required|integer|exists:job_openings,id',
'candidate_id' => 'required|integer|exists:candidates,id',
'email' => 'required|email|max:255',
'first_name' => 'required|string|max:255',
'last_name' => 'required|string|max:255',
'hired_at' => 'required|date_format:Y-m-d',
];
}

/**
* Hire a candidate:
* - create the employee,
* - mark the job opening as fulfilled
* - deactivate the job opening.
*
* @param array $data
* @return Employee
*/
public function execute(array $data): Employee
{
$this->data = $data;
$this->validate();
$this->createEmployee();
$this->markJobOpeningAsFulfilled();
$this->updateCandidate();
$this->log();

return $this->employee;
}

private function validate(): void
{
$this->validateRules($this->data);

$this->author($this->data['author_id'])
->inCompany($this->data['company_id'])
->asAtLeastHR()
->canExecuteService();

$this->jobOpening = JobOpening::where('company_id', $this->data['company_id'])
->findOrFail($this->data['job_opening_id']);

$this->candidate = Candidate::where('company_id', $this->data['company_id'])
->where('job_opening_id', $this->data['job_opening_id'])
->findOrFail($this->data['candidate_id']);

$this->hiredAt = Carbon::createFromFormat('Y-m-d', $this->data['hired_at']);
}

private function createEmployee(): void
{
$this->employee = (new AddEmployeeToCompany)->execute([
'company_id' => $this->jobOpening->company_id,
'author_id' => $this->author->id,
'email' => $this->data['email'],
'first_name' => $this->data['first_name'],
'last_name' => $this->data['last_name'],
'permission_level' => config('officelife.permission_level.user'),
'send_invitation' => false,
]);

(new SetHiringDate)->execute([
'company_id' => $this->jobOpening->company_id,
'author_id' => $this->author->id,
'employee_id' => $this->employee->id,
'year' => $this->hiredAt->year,
'month' => $this->hiredAt->month,
'day' => $this->hiredAt->day,
]);

$team = $this->jobOpening->team;
if ($team) {
(new AddEmployeeToTeam)->execute([
'company_id' => $this->jobOpening->company_id,
'author_id' => $this->author->id,
'employee_id' => $this->employee->id,
'team_id' => $team->id,
]);
}

$this->employee->position_id = $this->jobOpening->position_id;
$this->employee->save();
}

private function markJobOpeningAsFulfilled(): void
{
$this->jobOpening->active = false;
$this->jobOpening->fulfilled = true;
$this->jobOpening->fulfilled_by_candidate_id = $this->candidate->id;
$this->jobOpening->save();
}

private function updateCandidate(): void
{
$this->candidate->employee_id = $this->employee->id;
$this->candidate->employee_name = $this->employee->name;
$this->candidate->save();
}

private function log(): void
{
LogAccountAudit::dispatch([
'company_id' => $this->data['company_id'],
'action' => 'candidate_hired',
'author_id' => $this->author->id,
'author_name' => $this->author->name,
'audited_at' => Carbon::now(),
'objects' => json_encode([
'job_opening_id' => $this->jobOpening->id,
'job_opening_title' => $this->jobOpening->title,
'job_opening_reference_number' => $this->jobOpening->reference_number,
'candidate_id' => $this->candidate->id,
'candidate_name' => $this->candidate->name,
]),
])->onQueue('low');
}
}

0 comments on commit 4ef1544

Please sign in to comment.