JobApplication::experiences_work()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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