Passed
Push — task/save-profile-snapshot-tim... ( 1fc4b9...99c853 )
by Yonathan
04:11
created

JobApplication::replicateAndSaveExperience()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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