Passed
Push — feature/strategic-response-app... ( 5747ec )
by Tristan
04:54
created

JobApplication::security_clearance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * Created by Reliese Model.
5
 * Date: Thu, 12 Jul 2018 22:39:27 +0000.
6
 */
7
8
namespace App\Models;
9
10
use App\Events\ApplicationRetrieved;
11
use App\Events\ApplicationSaved;
12
use App\Models\Applicant;
13
use App\Models\ApplicationReview;
14
use App\Services\Validation\ApplicationValidator;
15
use App\Services\Validation\StrategicResponseApplicationValidator;
16
use Illuminate\Notifications\Notifiable;
17
18
/**
19
 * Class JobApplication
20
 *
21
 * @property int $id
22
 * @property int $job_poster_id
23
 * @property int $application_status_id
24
 * @property int $citizenship_declaration_id
25
 * @property int $veteran_status_id
26
 * @property int $preferred_language_id
27
 * @property int $applicant_id
28
 * @property int $applicant_snapshot_id
29
 * @property string $submission_signature
30
 * @property string $submission_date
31
 * @property boolean $experience_saved
32
 * @property boolean $language_requirement_confirmed
33
 * @property string $user_name
34
 * @property string $user_email
35
 * @property int $version_id
36
 * @property string $director_name
37
 * @property string $director_title
38
 * @property string $director_email
39
 * @property string $reference_name
40
 * @property string $reference_title
41
 * @property string $reference_email
42
 * @property string $gov_email
43
 * @property boolean $physical_office_willing
44
 * @property int $security_clearance_id
45
 * @property \Jenssegers\Date\Date $created_at
46
 * @property \Jenssegers\Date\Date $updated_at
47
 *
48
 * @property \App\Models\Applicant $applicant
49
 * @property \App\Models\Applicant $applicant_snapshot
50
 * @property \App\Models\Lookup\ApplicationStatus $application_status
51
 * @property \App\Models\Lookup\CitizenshipDeclaration $citizenship_declaration
52
 * @property \App\Models\Lookup\VeteranStatus $veteran_status
53
 * @property \App\Models\Lookup\PreferredLanguage $preferred_language
54
 * @property \App\Models\JobPoster $job_poster
55
 * @property \Illuminate\Database\Eloquent\Collection $job_application_answers
56
 * @property \Illuminate\Database\Eloquent\Collection $skill_declarations
57
 * @property \App\Models\ApplicationReview $application_review
58
 * @property \Illuminate\Database\Eloquent\Collection $degrees
59
 * @property \Illuminate\Database\Eloquent\Collection $courses
60
 * @property \Illuminate\Database\Eloquent\Collection $work_experiences
61
 * @property \Illuminate\Database\Eloquent\Collection $references
62
 * @property \Illuminate\Database\Eloquent\Collection $work_samples
63
 * @property \Illuminate\Database\Eloquent\Collection $projects
64
 * @property \App\Models\JobApplicationVersion $job_application_version
65
 * @property \App\Models\Lookup\SecurityClearance $security_clearance
66
 *
67
 * Version 2 application models.
68
 * @property \Illuminate\Database\Eloquent\Collection $experiences_work
69
 * @property \Illuminate\Database\Eloquent\Collection $experiences_personal
70
 * @property \Illuminate\Database\Eloquent\Collection $experiences_education
71
 * @property \Illuminate\Database\Eloquent\Collection $experiences_award
72
 * @property \Illuminate\Database\Eloquent\Collection $experiences_community
73
 */
74
class JobApplication extends BaseModel
75
{
76
77
    use Notifiable;
78
79
    protected $dispatchesEvents = [
80
        'retrieved' => ApplicationRetrieved::class,
81
        'saved' => ApplicationSaved::class,
82
    ];
83
84
    protected $casts = [
85
        'job_poster_id' => 'int',
86
        'application_status_id' => 'int',
87
        'citizenship_declaration_id' => 'int',
88
        'veteran_status_id' => 'int',
89
        'preferred_language_id' => 'int',
90
        'applicant_id' => 'int',
91
        'applicant_snapshot_id' => 'int',
92
        'submission_signature' => 'string',
93
        'submission_date' => 'string',
94
        'experience_saved' => 'boolean',
95
        'language_requirement_confirmed' => 'boolean',
96
        'version_id' => 'int',
97
        'director_name' => 'string',
98
        'director_title' => 'string',
99
        'director_email' => 'string',
100
        'reference_name' => 'string',
101
        'reference_title' => 'string',
102
        'reference_email' => 'string',
103
        'gov_email' => 'string',
104
        'physical_office_willing' => 'boolean',
105
        'security_clearance_id' => 'int',
106
    ];
107
    protected $fillable = [
108
        'citizenship_declaration_id',
109
        'language_requirement_confirmed',
110
        'veteran_status_id',
111
        'preferred_language_id',
112
        'submission_signature',
113
        'submission_date',
114
        'experience_saved',
115
        'director_name',
116
        'director_title',
117
        'director_email',
118
        'reference_name',
119
        'reference_title',
120
        'reference_email',
121
        'gov_email',
122
        'physical_office_willing',
123
        'security_clearance_id',
124
    ];
125
126
    /**
127
     * The accessors to append to the model's array/json form.
128
     *
129
     * @var array
130
     */
131
    protected $appends = ['meets_essential_criteria'];
132
133
    protected function createApplicantSnapshot($applicant_id)
134
    {
135
        $applicant = Applicant::where('id', $applicant_id)->firstOrFail();
136
137
        $snapshot = $applicant->replicate();
0 ignored issues
show
Unused Code introduced by
The assignment to $snapshot is dead and can be removed.
Loading history...
138
    }
139
140
    public function applicant()
141
    {
142
        return $this->belongsTo(\App\Models\Applicant::class);
143
    }
144
145
    public function applicant_snapshot() //phpcs:ignore
146
    {
147
        return $this->belongsTo(\App\Models\Applicant::class, 'applicant_snapshot_id');
148
    }
149
150
    public function application_status() //phpcs:ignore
151
    {
152
        return $this->belongsTo(\App\Models\Lookup\ApplicationStatus::class);
153
    }
154
155
    public function citizenship_declaration() //phpcs:ignore
156
    {
157
        return $this->belongsTo(\App\Models\Lookup\CitizenshipDeclaration::class);
158
    }
159
160
    public function veteran_status() //phpcs:ignore
161
    {
162
        return $this->belongsTo(\App\Models\Lookup\VeteranStatus::class);
163
    }
164
165
    public function preferred_language() //phpcs:ignore
166
    {
167
        return $this->belongsTo(\App\Models\Lookup\PreferredLanguage::class);
168
    }
169
170
    public function job_poster() //phpcs:ignore
171
    {
172
        return $this->belongsTo(\App\Models\JobPoster::class);
173
    }
174
175
    public function job_application_answers() //phpcs:ignore
176
    {
177
        return $this->hasMany(\App\Models\JobApplicationAnswer::class);
178
    }
179
180
    public function skill_declarations() //phpcs:ignore
181
    {
182
        return $this->morphMany(\App\Models\SkillDeclaration::class, 'skillable');
183
    }
184
185
    public function application_review() //phpcs:ignore
186
    {
187
        return $this->hasOne(ApplicationReview::class);
188
    }
189
190
    public function degrees()
191
    {
192
        return $this->morphMany(\App\Models\Degree::class, 'degreeable')->orderBy('end_date', 'desc');
193
    }
194
195
    public function courses()
196
    {
197
        return $this->morphMany(\App\Models\Course::class, 'courseable')->orderBy('end_date', 'desc');
198
    }
199
200
    public function work_experiences() //phpcs:ignore
201
    {
202
        return $this->morphMany(\App\Models\WorkExperience::class, 'experienceable')->orderBy('end_date', 'desc');
203
    }
204
205
    public function references()
206
    {
207
        return $this->morphMany(\App\Models\Reference::class, 'referenceable');
208
    }
209
210
    public function projects()
211
    {
212
        return $this->morphMany(\App\Models\Project::class, 'projectable');
213
    }
214
215
    public function work_samples() //phpcs:ignore
216
    {
217
        return $this->morphMany(\App\Models\WorkSample::class, 'work_sampleable');
218
    }
219
220
    public function job_application_version() //phpcs:ignore
221
    {
222
        return $this->hasOne(\App\Models\JobApplicationVersion::class);
223
    }
224
225
    public function security_clearance() //phpcs:ignore
226
    {
227
        return $this->belongsTo(\App\Models\Lookup\SecurityClearance::class);
228
    }
229
230
    // Version 2 application models.
231
    public function experiences_work() //phpcs:ignore
232
    {
233
        return $this->morphMany(\App\Models\ExperienceWork::class, 'experienceable')
234
            ->orderBy('end_date', 'desc');
235
    }
236
237
    public function experiences_personal() //phpcs:ignore
238
    {
239
        return $this->morphMany(\App\Models\ExperiencePersonal::class, 'experienceable')
240
            ->orderBy('end_date', 'desc');
241
    }
242
243
    public function experiences_education() //phpcs:ignore
244
    {
245
        return $this->morphMany(\App\Models\ExperienceEducation::class, 'experienceable')
246
            ->orderBy('end_date', 'desc');
247
    }
248
249
    public function experiences_award() //phpcs:ignore
250
    {
251
        return $this->morphMany(\App\Models\ExperienceAward::class, 'experienceable');
252
    }
253
254
    public function experiences_community() //phpcs:ignore
255
    {
256
        return $this->morphMany(\App\Models\ExperienceCommunity::class, 'experienceable')
257
            ->orderBy('end_date', 'desc');
258
    }
259
260
    /**
261
     * Return either 'complete', 'incomplete' or 'error', depending on the
262
     * status of the requested section.
263
     *
264
     * @param  string $section Should be one of:
265
     *                              'basics'
266
     *                              'experience'
267
     *                              'essential_skills'
268
     *                              'asset_skills'
269
     *                              'preview'
270
     *
271
     * @return string $status   'complete', 'incomplete' or 'error'
272
     */
273
    public function getSectionStatus(string $section)
274
    {
275
        // TODO: determine whether sections are complete or invalid
276
        $jobPoster = $this->job_poster;
277
        $validator = $jobPoster->isInStrategicResponseDepartment()
278
            ? new StrategicResponseApplicationValidator()
279
            : new ApplicationValidator();
280
        $status = 'incomplete';
281
        switch ($section) {
282
            case 'basics':
283
                if ($validator->basicsComplete($this)) {
284
                    $status = 'complete';
285
                }
286
                break;
287
            case 'experience':
288
                if ($validator->experienceComplete($this)) {
289
                    $status = 'complete';
290
                }
291
                break;
292
            case 'essential_skills':
293
                if ($validator->essentialSkillsComplete($this)) {
294
                    $status = 'complete';
295
                }
296
                break;
297
            case 'asset_skills':
298
                if ($validator->assetSkillsComplete($this)) {
299
                    $status = 'complete';
300
                }
301
                break;
302
            case 'preview':
303
                if (
304
                    $validator->basicsComplete($this) &&
305
                    $validator->experienceComplete($this) &&
306
                    $validator->essentialSkillsComplete($this) &&
307
                    $validator->assetSkillsComplete($this)
308
                ) {
309
                    $status = 'complete';
310
                }
311
                break;
312
            case 'confirm':
313
                if ($validator->affirmationComplete($this)) {
314
                    $status = 'complete';
315
                }
316
                break;
317
            default:
318
                $status = 'error';
319
                break;
320
        }
321
        return $status;
322
    }
323
324
    /**
325
     * Check if the status of the application is 'draft'
326
     *
327
     * @return boolean
328
     */
329
    public function isDraft(): bool
330
    {
331
        return $this->application_status->name === 'draft';
332
    }
333
334
    /**
335
     * Returns true if this meets all the HARD SKILL essential criteria.
336
     * That means it has attached an SkillDeclaration for each essential criterion,
337
     * with a level at least as high as the required level.
338
     * NOTE: If this application is in draft status, it will use
339
     *  SkillDeclarations from the the applicants profile for this check.
340
     *
341
     * @return boolean
342
     */
343
    public function meetsEssentialCriteria(): bool
344
    {
345
        $essentialCriteria = $this->job_poster->criteria->filter(
346
            function ($value, $key) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

346
            function ($value, /** @scrutinizer ignore-unused */ $key) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
347
                return $value->criteria_type->name == 'essential'
348
                    && $value->skill->skill_type->name == 'hard';
349
            }
350
        );
351
        $source = $this->isDraft() ? $this->applicant : $this;
352
        foreach ($essentialCriteria as $criterion) {
353
            $skillDeclaration = $source->skill_declarations->where('skill_id', $criterion->skill_id)->first();
354
            if (
355
                $skillDeclaration === null ||
356
                $skillDeclaration->skill_level_id < $criterion->skill_level_id
357
            ) {
358
                return false;
359
            }
360
        }
361
        return true;
362
    }
363
364
    /**
365
     * Accessor for meetsEssentialCriteria function, which
366
     * allows this value to be automatically appended to array/json representation.
367
     *
368
     * @return boolean
369
     */
370
    public function getMeetsEssentialCriteriaAttribute(): bool
371
    {
372
        return $this->meetsEssentialCriteria();
373
    }
374
375
    /**
376
     * Save copies of all relevant profile data to this application.
377
     *
378
     *
379
     * @return void
380
     */
381
    public function saveProfileSnapshot(): void
382
    {
383
        $applicant = $this->applicant->fresh();
384
385
        $this->user_name = $applicant->user->full_name;
386
        $this->user_email = $applicant->user->email;
387
        $this->save();
388
389
        // Delete previous snapshot.
390
        $this->degrees()->delete();
391
        $this->courses()->delete();
392
        $this->work_experiences()->delete();
393
        $this->projects()->delete();
394
        $this->references()->delete();
395
        $this->work_samples()->delete();
396
        $this->skill_declarations()->delete();
397
398
        $this->degrees()->saveMany($applicant->degrees->map->replicate());
399
        $this->courses()->saveMany($applicant->courses->map->replicate());
400
        $this->work_experiences()->saveMany($applicant->work_experiences->map->replicate());
401
402
        $copyWithHistory = function ($model) {
403
            return [
404
                'old' => $model,
405
                'new' => $model->replicate()
406
            ];
407
        };
408
409
        $projectMap = $applicant->projects->map($copyWithHistory);
410
        $referenceMap = $applicant->references->map($copyWithHistory);
411
        $workSampleMap = $applicant->work_samples->map($copyWithHistory);
412
        $skillDeclarationMap = $applicant->skill_declarations->map($copyWithHistory);
413
414
        // First link new projects, references, work samples and skill declarations to this application.
415
        $this->projects()->saveMany($projectMap->pluck('new'));
416
        $this->references()->saveMany($referenceMap->pluck('new'));
417
        $this->work_samples()->saveMany($workSampleMap->pluck('new'));
418
        $this->skill_declarations()->saveMany($skillDeclarationMap->pluck('new'));
419
420
        $findNewFromOld = function ($mapping, $old) {
421
            $matchingItem = $mapping->first(function ($value) use ($old) {
422
                return $value['old']->id === $old->id;
423
            });
424
            return $matchingItem['new'];
425
        };
426
427
        // Replicate copies shallow attributes, but not relationships. We have to copy those ourselves.
428
        $findNewReferenceFromOld = function ($old) use ($findNewFromOld, $referenceMap) {
429
            return $findNewFromOld($referenceMap, $old);
430
        };
431
432
        $findNewSkillDeclarationFromOld = function ($old) use ($findNewFromOld, $skillDeclarationMap) {
433
            return $findNewFromOld($skillDeclarationMap, $old);
434
        };
435
436
        // Link projects and references.
437
        foreach ($projectMap as $item) {
438
            $old = $item['old'];
439
            $newProj = $item['new'];
440
            $newReferences = $old->references->map($findNewReferenceFromOld);
441
            $newProj->references()->sync($newReferences);
442
        }
443
444
        // Link references and skills.
445
        foreach ($referenceMap as $item) {
446
            $old = $item['old'];
447
            $newRef = $item['new'];
448
            $newSkillDecs = $old->skill_declarations->map($findNewSkillDeclarationFromOld);
449
            $newRef->skill_declarations()->sync($newSkillDecs);
450
        }
451
452
        // Link work samples and skills.
453
        foreach ($workSampleMap as $item) {
454
            $old = $item['old'];
455
            $newSample = $item['new'];
456
            $newSkillDecs = $old->skill_declarations->map($findNewSkillDeclarationFromOld);
457
            $newSample->skill_declarations()->sync($newSkillDecs);
458
        }
459
    }
460
}
461