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