Passed
Push — task/application-handle-step-s... ( 5a9035...2b5b8c )
by Yonathan
04:05
created

JobApplication::security_clearance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * Created by Reliese Model.
5
 * Date: Thu, 12 Jul 2018 22:39:27 +0000.
6
 */
7
8
namespace App\Models;
9
10
use App\Events\ApplicationRetrieved;
11
use App\Events\ApplicationSaved;
12
use App\Models\Applicant;
13
use App\Models\ApplicationReview;
14
use App\Models\Lookup\JobApplicationStep;
15
use App\Services\Validation\ApplicationTimelineValidator;
16
use App\Services\Validation\ApplicationValidator;
17
use App\Services\Validation\StrategicResponseApplicationValidator;
18
use Illuminate\Notifications\Notifiable;
19
use App\Traits\TalentCloudCrudTrait as CrudTrait;
20
use Illuminate\Support\Facades\Log;
21
22
/**
23
 * Class JobApplication
24
 *
25
 * @property int $id
26
 * @property int $job_poster_id
27
 * @property int $application_status_id
28
 * @property int $citizenship_declaration_id
29
 * @property int $veteran_status_id
30
 * @property int $preferred_language_id
31
 * @property int $applicant_id
32
 * @property int $applicant_snapshot_id
33
 * @property string $submission_signature
34
 * @property string $submission_date
35
 * @property boolean $experience_saved
36
 * @property boolean $language_requirement_confirmed
37
 * @property boolean $language_test_confirmed
38
 * @property boolean $education_requirement_confirmed
39
 * @property string $user_name
40
 * @property string $user_email
41
 * @property int $version_id
42
 * @property string $director_name
43
 * @property string $director_title
44
 * @property string $director_email
45
 * @property string $reference_name
46
 * @property string $reference_title
47
 * @property string $reference_email
48
 * @property string $gov_email
49
 * @property boolean $physical_office_willing
50
 * @property int $security_clearance_id
51
 * @property boolean $share_with_managers
52
 * @property \Jenssegers\Date\Date $created_at
53
 * @property \Jenssegers\Date\Date $updated_at
54
 *
55
 * @property \App\Models\Applicant $applicant
56
 * @property \App\Models\Applicant $applicant_snapshot
57
 * @property \App\Models\Lookup\ApplicationStatus $application_status
58
 * @property \App\Models\Lookup\CitizenshipDeclaration $citizenship_declaration
59
 * @property \App\Models\Lookup\VeteranStatus $veteran_status
60
 * @property \App\Models\Lookup\PreferredLanguage $preferred_language
61
 * @property \App\Models\JobPoster $job_poster
62
 * @property \Illuminate\Database\Eloquent\Collection $job_application_answers
63
 * @property \Illuminate\Database\Eloquent\Collection $skill_declarations
64
 * @property \App\Models\ApplicationReview $application_review
65
 * @property \Illuminate\Database\Eloquent\Collection $degrees
66
 * @property \Illuminate\Database\Eloquent\Collection $courses
67
 * @property \Illuminate\Database\Eloquent\Collection $work_experiences
68
 * @property \Illuminate\Database\Eloquent\Collection $references
69
 * @property \Illuminate\Database\Eloquent\Collection $work_samples
70
 * @property \Illuminate\Database\Eloquent\Collection $projects
71
 * @property \App\Models\JobApplicationVersion $job_application_version
72
 * @property \App\Models\Lookup\SecurityClearance $security_clearance
73
 *
74
 * Version 2 application models.
75
 * @property \Illuminate\Database\Eloquent\Collection $experiences_work
76
 * @property \Illuminate\Database\Eloquent\Collection $experiences_personal
77
 * @property \Illuminate\Database\Eloquent\Collection $experiences_education
78
 * @property \Illuminate\Database\Eloquent\Collection $experiences_award
79
 * @property \Illuminate\Database\Eloquent\Collection $experiences_community
80
 * @property \Illuminate\Database\Eloquent\Collection $touched_application_steps
81
 */
82
class JobApplication extends BaseModel
83
{
84
    // Trait for Backpack.
85
    use CrudTrait;
86
87
    use Notifiable;
88
89
    protected $dispatchesEvents = [
90
        'retrieved' => ApplicationRetrieved::class,
91
        'saved' => ApplicationSaved::class,
92
    ];
93
94
    protected $casts = [
95
        'job_poster_id' => 'int',
96
        'application_status_id' => 'int',
97
        'citizenship_declaration_id' => 'int',
98
        'veteran_status_id' => 'int',
99
        'preferred_language_id' => 'int',
100
        'applicant_id' => 'int',
101
        'applicant_snapshot_id' => 'int',
102
        'submission_signature' => 'string',
103
        'submission_date' => 'string',
104
        'experience_saved' => 'boolean',
105
        'language_requirement_confirmed' => 'boolean',
106
        'language_test_confirmed' => 'boolean',
107
        'education_requirement_confirmed' => 'boolean',
108
        'version_id' => 'int',
109
        'director_name' => 'string',
110
        'director_title' => 'string',
111
        'director_email' => 'string',
112
        'reference_name' => 'string',
113
        'reference_title' => 'string',
114
        'reference_email' => 'string',
115
        'gov_email' => 'string',
116
        'physical_office_willing' => 'boolean',
117
        'security_clearance_id' => 'int',
118
        'share_with_managers' => 'boolean',
119
    ];
120
    protected $fillable = [
121
        'citizenship_declaration_id',
122
        'veteran_status_id',
123
        'preferred_language_id',
124
        'language_requirement_confirmed',
125
        'language_test_confirmed',
126
        'education_requirement_confirmed',
127
        'version_id',
128
        'veteran_status_id',
129
        'preferred_language_id',
130
        'submission_signature',
131
        'submission_date',
132
        'experience_saved',
133
        'director_name',
134
        'director_title',
135
        'director_email',
136
        'reference_name',
137
        'reference_title',
138
        'reference_email',
139
        'gov_email',
140
        'physical_office_willing',
141
        'security_clearance_id',
142
        'share_with_managers',
143
    ];
144
145
    /**
146
     * The accessors to append to the model's array/json form.
147
     *
148
     * @var array
149
     */
150
    protected $appends = ['meets_essential_criteria'];
151
152
    protected function createApplicantSnapshot($applicant_id)
153
    {
154
        $applicant = Applicant::where('id', $applicant_id)->firstOrFail();
155
156
        $snapshot = $applicant->replicate();
0 ignored issues
show
Unused Code introduced by
The assignment to $snapshot is dead and can be removed.
Loading history...
157
    }
158
159
    public function applicant()
160
    {
161
        return $this->belongsTo(\App\Models\Applicant::class);
162
    }
163
164
    public function applicant_snapshot() //phpcs:ignore
165
    {
166
        return $this->belongsTo(\App\Models\Applicant::class, 'applicant_snapshot_id');
167
    }
168
169
    public function application_status() //phpcs:ignore
170
    {
171
        return $this->belongsTo(\App\Models\Lookup\ApplicationStatus::class);
172
    }
173
174
    public function citizenship_declaration() //phpcs:ignore
175
    {
176
        return $this->belongsTo(\App\Models\Lookup\CitizenshipDeclaration::class);
177
    }
178
179
    public function veteran_status() //phpcs:ignore
180
    {
181
        return $this->belongsTo(\App\Models\Lookup\VeteranStatus::class);
182
    }
183
184
    public function preferred_language() //phpcs:ignore
185
    {
186
        return $this->belongsTo(\App\Models\Lookup\PreferredLanguage::class);
187
    }
188
189
    public function job_poster() //phpcs:ignore
190
    {
191
        return $this->belongsTo(\App\Models\JobPoster::class);
192
    }
193
194
    public function job_application_answers() //phpcs:ignore
195
    {
196
        return $this->hasMany(\App\Models\JobApplicationAnswer::class);
197
    }
198
199
    public function skill_declarations() //phpcs:ignore
200
    {
201
        return $this->morphMany(\App\Models\SkillDeclaration::class, 'skillable');
202
    }
203
204
    public function application_review() //phpcs:ignore
205
    {
206
        return $this->hasOne(ApplicationReview::class);
207
    }
208
209
    public function degrees()
210
    {
211
        return $this->morphMany(\App\Models\Degree::class, 'degreeable')->orderBy('end_date', 'desc');
212
    }
213
214
    public function courses()
215
    {
216
        return $this->morphMany(\App\Models\Course::class, 'courseable')->orderBy('end_date', 'desc');
217
    }
218
219
    public function work_experiences() //phpcs:ignore
220
    {
221
        return $this->morphMany(\App\Models\WorkExperience::class, 'experienceable')->orderBy('end_date', 'desc');
222
    }
223
224
    public function references()
225
    {
226
        return $this->morphMany(\App\Models\Reference::class, 'referenceable');
227
    }
228
229
    public function projects()
230
    {
231
        return $this->morphMany(\App\Models\Project::class, 'projectable');
232
    }
233
234
    public function work_samples() //phpcs:ignore
235
    {
236
        return $this->morphMany(\App\Models\WorkSample::class, 'work_sampleable');
237
    }
238
239
    public function job_application_version() //phpcs:ignore
240
    {
241
        return $this->hasOne(\App\Models\JobApplicationVersion::class, 'version_id');
242
    }
243
244
    public function security_clearance() //phpcs:ignore
245
    {
246
        return $this->belongsTo(\App\Models\Lookup\SecurityClearance::class);
247
    }
248
249
    // Version 2 application models.
250
    public function experiences_work() //phpcs:ignore
251
    {
252
        return $this->morphMany(\App\Models\ExperienceWork::class, 'experienceable')
253
            ->orderBy('end_date', 'desc');
254
    }
255
256
    public function experiences_personal() //phpcs:ignore
257
    {
258
        return $this->morphMany(\App\Models\ExperiencePersonal::class, 'experienceable')
259
            ->orderBy('end_date', 'desc');
260
    }
261
262
    public function experiences_education() //phpcs:ignore
263
    {
264
        return $this->morphMany(\App\Models\ExperienceEducation::class, 'experienceable')
265
            ->orderBy('end_date', 'desc');
266
    }
267
268
    public function experiences_award() //phpcs:ignore
269
    {
270
        return $this->morphMany(\App\Models\ExperienceAward::class, 'experienceable');
271
    }
272
273
    public function experiences_community() //phpcs:ignore
274
    {
275
        return $this->morphMany(\App\Models\ExperienceCommunity::class, 'experienceable')
276
            ->orderBy('end_date', 'desc');
277
    }
278
279
    public function touched_application_steps() //phpcs:ignore
280
    {
281
        return $this->belongsToMany(
282
            \App\Models\Lookup\JobApplicationStep::class,
283
            'touched_application_steps',
284
            'job_application_id',
285
            'step_id'
286
        )->withPivot('touched');
287
    }
288
289
    /**
290
     * Return either 'complete', 'incomplete' or 'error', depending on the
291
     * status of the requested section.
292
     *
293
     * @param  string $section Should be one of:
294
     *                              'basics'
295
     *                              'experience'
296
     *                              'essential_skills'
297
     *                              'asset_skills'
298
     *                              'preview'
299
     *
300
     * @return string $status   'complete', 'incomplete' or 'error'
301
     */
302
    public function getSectionStatus(string $section)
303
    {
304
        // TODO: determine whether sections are complete or invalid
305
        $jobPoster = $this->job_poster;
306
        $validator = $jobPoster->isInStrategicResponseDepartment()
307
            ? new StrategicResponseApplicationValidator()
308
            : new ApplicationValidator();
309
        $status = 'incomplete';
310
        switch ($section) {
311
            case 'basics':
312
                if ($validator->basicsComplete($this)) {
313
                    $status = 'complete';
314
                }
315
                break;
316
            case 'experience':
317
                if ($validator->experienceComplete($this)) {
318
                    $status = 'complete';
319
                }
320
                break;
321
            case 'essential_skills':
322
                if ($validator->essentialSkillsComplete($this)) {
323
                    $status = 'complete';
324
                }
325
                break;
326
            case 'asset_skills':
327
                if ($validator->assetSkillsComplete($this)) {
328
                    $status = 'complete';
329
                }
330
                break;
331
            case 'preview':
332
                if ($validator->basicsComplete($this) &&
333
                    $validator->experienceComplete($this) &&
334
                    $validator->essentialSkillsComplete($this) &&
335
                    $validator->assetSkillsComplete($this)
336
                ) {
337
                    $status = 'complete';
338
                }
339
                break;
340
            case 'confirm':
341
                if ($validator->affirmationComplete($this)) {
342
                    $status = 'complete';
343
                }
344
                break;
345
            default:
346
                $status = 'error';
347
                break;
348
        }
349
        return $status;
350
    }
351
352
    /**
353
     * Check if the status of the application is 'draft'
354
     *
355
     * @return boolean
356
     */
357
    public function isDraft(): bool
358
    {
359
        return $this->application_status->name === 'draft';
360
    }
361
362
    /**
363
     * Returns true if this meets all the HARD SKILL essential criteria.
364
     * That means it has attached an SkillDeclaration for each essential criterion,
365
     * with a level at least as high as the required level.
366
     * NOTE: If this application is in draft status, it will use
367
     *  SkillDeclarations from the the applicants profile for this check.
368
     *
369
     * @return boolean
370
     */
371
    public function meetsEssentialCriteria(): bool
372
    {
373
        $essentialCriteria = $this->job_poster->criteria->filter(
374
            function ($value, $key) {
375
                return $value->criteria_type->name == 'essential'
376
                    && $value->skill->skill_type->name == 'hard';
377
            }
378
        );
379
        $source = $this->isDraft() ? $this->applicant : $this;
380
        foreach ($essentialCriteria as $criterion) {
381
            $skillDeclaration = $source->skill_declarations->where('skill_id', $criterion->skill_id)->first();
382
            if ($skillDeclaration === null ||
383
                $skillDeclaration->skill_level_id < $criterion->skill_level_id
384
            ) {
385
                return false;
386
            }
387
        }
388
        return true;
389
    }
390
391
    /**
392
     * Accessor for meetsEssentialCriteria function, which
393
     * allows this value to be automatically appended to array/json representation.
394
     *
395
     * @return boolean
396
     */
397
    public function getMeetsEssentialCriteriaAttribute(): bool
398
    {
399
        return $this->meetsEssentialCriteria();
400
    }
401
402
    /**
403
     * Save copies of all relevant profile data to this application.
404
     *
405
     *
406
     * @return void
407
     */
408
    public function saveProfileSnapshot(): void
409
    {
410
        $applicant = $this->applicant->fresh();
411
412
        $this->user_name = $applicant->user->full_name;
413
        $this->user_email = $applicant->user->email;
414
        $this->save();
415
416
        // Delete previous snapshot.
417
        $this->degrees()->delete();
418
        $this->courses()->delete();
419
        $this->work_experiences()->delete();
420
        $this->projects()->delete();
421
        $this->references()->delete();
422
        $this->work_samples()->delete();
423
        $this->skill_declarations()->delete();
424
425
        $this->degrees()->saveMany($applicant->degrees->map->replicate());
426
        $this->courses()->saveMany($applicant->courses->map->replicate());
427
        $this->work_experiences()->saveMany($applicant->work_experiences->map->replicate());
428
429
        $copyWithHistory = function ($model) {
430
            return [
431
                'old' => $model,
432
                'new' => $model->replicate()
433
            ];
434
        };
435
436
        $projectMap = $applicant->projects->map($copyWithHistory);
437
        $referenceMap = $applicant->references->map($copyWithHistory);
438
        $workSampleMap = $applicant->work_samples->map($copyWithHistory);
439
        $skillDeclarationMap = $applicant->skill_declarations->map($copyWithHistory);
440
441
        // First link new projects, references, work samples and skill declarations to this application.
442
        $this->projects()->saveMany($projectMap->pluck('new'));
443
        $this->references()->saveMany($referenceMap->pluck('new'));
444
        $this->work_samples()->saveMany($workSampleMap->pluck('new'));
445
        $this->skill_declarations()->saveMany($skillDeclarationMap->pluck('new'));
446
447
        $findNewFromOld = function ($mapping, $old) {
448
            $matchingItem = $mapping->first(function ($value) use ($old) {
449
                return $value['old']->id === $old->id;
450
            });
451
            return $matchingItem['new'];
452
        };
453
454
        // Replicate copies shallow attributes, but not relationships. We have to copy those ourselves.
455
        $findNewReferenceFromOld = function ($old) use ($findNewFromOld, $referenceMap) {
456
            return $findNewFromOld($referenceMap, $old);
457
        };
458
459
        $findNewSkillDeclarationFromOld = function ($old) use ($findNewFromOld, $skillDeclarationMap) {
460
            return $findNewFromOld($skillDeclarationMap, $old);
461
        };
462
463
        // Link projects and references.
464
        foreach ($projectMap as $item) {
465
            $old = $item['old'];
466
            $newProj = $item['new'];
467
            $newReferences = $old->references->map($findNewReferenceFromOld);
468
            $newProj->references()->sync($newReferences);
469
        }
470
471
        // Link references and skills.
472
        foreach ($referenceMap as $item) {
473
            $old = $item['old'];
474
            $newRef = $item['new'];
475
            $newSkillDecs = $old->skill_declarations->map($findNewSkillDeclarationFromOld);
476
            $newRef->skill_declarations()->sync($newSkillDecs);
477
        }
478
479
        // Link work samples and skills.
480
        foreach ($workSampleMap as $item) {
481
            $old = $item['old'];
482
            $newSample = $item['new'];
483
            $newSkillDecs = $old->skill_declarations->map($findNewSkillDeclarationFromOld);
484
            $newSample->skill_declarations()->sync($newSkillDecs);
485
        }
486
    }
487
488
    /**
489
     * Save copies of Experiences and its linked skills (ExperienceSkills) to this application.
490
     *
491
     * @return void
492
     */
493
    public function saveProfileSnapshotTimeline(): void
494
    {
495
        $this->refresh();
496
        $applicant = $this->applicant->fresh();
497
498
        $this->user_name = $applicant->user->full_name;
499
        $this->user_email = $applicant->user->email;
500
        $this->save();
501
502
        $deleteExperiences = function ($experiences) {
503
            foreach ($experiences as $experience) {
504
                $experience->delete();
505
            }
506
        };
507
508
        // Delete experiences in previous snapshot.
509
        $deleteExperiences($this->experiences_award);
510
        $deleteExperiences($this->experiences_community);
511
        $deleteExperiences($this->experiences_education);
512
        $deleteExperiences($this->experiences_personal);
513
        $deleteExperiences($this->experiences_work);
514
515
        $replicateAndSaveExperience = function ($experiences, $experience_type) {
516
            // Iterate through applicant experiences, replicate the experience, and save to the application.
517
            foreach ($experiences as $experience) {
518
                $experienceCopy = $experience->replicate();
519
                $this->{$experience_type}()->save($experienceCopy);
520
                // Iterate through original experience experienceSkills list, replicate it, and save to the new copy.
521
                foreach ($experience->experience_skills as $experienceSkill) {
522
                    $experienceSkillCopy = $experienceSkill->replicate();
523
                    $experienceCopy->experience_skills()->save($experienceSkillCopy);
524
                }
525
            }
526
        };
527
528
        $replicateAndSaveExperience($applicant->experiences_award, 'experiences_award');
529
        $replicateAndSaveExperience($applicant->experiences_community, 'experiences_community');
530
        $replicateAndSaveExperience($applicant->experiences_education, 'experiences_education');
531
        $replicateAndSaveExperience($applicant->experiences_personal, 'experiences_personal');
532
        $replicateAndSaveExperience($applicant->experiences_work, 'experiences_work');
533
    }
534
535
    /**
536
     * Attach steps to new application (version 2).
537
     *
538
     * @return void
539
    */
540
    public function attachSteps(): void
541
    {
542
        if ($this->touched_application_steps->isEmpty()) {
543
            $this->touched_application_steps()->attach(JobApplicationStep::where('name', 'basic')->first());
544
            $this->touched_application_steps()->attach(JobApplicationStep::where('name', 'experience')->first());
545
            $this->touched_application_steps()->attach(JobApplicationStep::where('name', 'skills')->first());
546
            $this->touched_application_steps()->attach(JobApplicationStep::where('name', 'fit')->first());
547
            $this->touched_application_steps()->attach(JobApplicationStep::where('name', 'review')->first());
548
            $this->touched_application_steps()->attach(JobApplicationStep::where('name', 'submission')->first());
549
        };
550
    }
551
552
    /**
553
     * Calculates and returns an associative array of application steps (version 2) with the value equal
554
     * to it's status ('default', 'complete', 'error').
555
     *
556
     * @return string $jobApplicationSteps
557
     */
558
    public function jobApplicationSteps(): array
559
    {
560
        $setState = function (bool $touched, bool $isValid) {
561
            return !$touched ? 'default' : ($isValid ? 'complete' : 'error');
562
        };
563
564
        $validator = new ApplicationTimelineValidator();
565
566
        $basicValidator = $validator->basicsComplete($this);
567
        $experienceValidator = $validator->experienceComplete($this);
568
        $skillsValidator = $validator->skillsComplete($this);
569
        $fitValidator = $validator->fitComplete($this);
570
        $reviewValidator = $validator->validateComplete($this);
571
        $submissionValidator = $validator->affirmationComplete($this);
572
573
        $basicTouched = $this->touched_application_steps->where('name', 'basic')->first()->pivot->touched;
574
        $experienceTouched = $this->touched_application_steps->where('name', 'experience')->first()->pivot->touched;
575
        $skillsTouched = $this->touched_application_steps->where('name', 'skills')->first()->pivot->touched;
576
        $fitTouched = $this->touched_application_steps->where('name', 'fit')->first()->pivot->touched;
577
        $reviewTouched = $this->touched_application_steps->where('name', 'review')->first()->pivot->touched;
578
        $submissionTouched = $this->touched_application_steps->where('name', 'submission')->first()->pivot->touched;
579
580
        $jobApplicationSteps = [
581
            'basic' => $setState($basicTouched, $basicValidator),
582
            'experience' => $setState($experienceTouched, $experienceValidator),
583
            'skills' => $setState($skillsTouched, $skillsValidator),
584
            'fit' => $setState($fitTouched, $fitValidator),
585
            'review' => $setState($reviewTouched, $reviewValidator),
586
            'submission' => $setState($submissionTouched, $submissionValidator)
587
        ];
588
589
        return $jobApplicationSteps;
590
    }
591
}
592