Passed
Push — feature/timeline-profile-page ( b6c64d...749ad5 )
by Tristan
10:59 queued 04:25
created

JobApplication::saveProfileSnapshotTimeline()   A

Complexity

Conditions 4
Paths 1

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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