Passed
Push — feature/application-versions ( 23b8a1 )
by Grant
11:21 queued 03:56
created

JobApplication::degrees()   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 Illuminate\Notifications\Notifiable;
16
17
/**
18
 * Class JobApplication
19
 *
20
 * @property int $id
21
 * @property int $job_poster_id
22
 * @property int $application_status_id
23
 * @property int $citizenship_declaration_id
24
 * @property int $veteran_status_id
25
 * @property int $preferred_language_id
26
 * @property int $applicant_id
27
 * @property int $applicant_snapshot_id
28
 * @property string $submission_signature
29
 * @property string $submission_date
30
 * @property boolean $experience_saved
31
 * @property boolean $language_requirement_confirmed
32
 * @property string $user_name
33
 * @property string $user_email
34
 * @property int $version_id
35
 * @property \Jenssegers\Date\Date $created_at
36
 * @property \Jenssegers\Date\Date $updated_at
37
 *
38
 * @property \App\Models\Applicant $applicant
39
 * @property \App\Models\Applicant $applicant_snapshot
40
 * @property \App\Models\Lookup\ApplicationStatus $application_status
41
 * @property \App\Models\Lookup\CitizenshipDeclaration $citizenship_declaration
42
 * @property \App\Models\Lookup\VeteranStatus $veteran_status
43
 * @property \App\Models\Lookup\PreferredLanguage $preferred_language
44
 * @property \App\Models\JobPoster $job_poster
45
 * @property \Illuminate\Database\Eloquent\Collection $job_application_answers
46
 * @property \Illuminate\Database\Eloquent\Collection $skill_declarations
47
 * @property \App\Models\ApplicationReview $application_review
48
 * @property \Illuminate\Database\Eloquent\Collection $degrees
49
 * @property \Illuminate\Database\Eloquent\Collection $courses
50
 * @property \Illuminate\Database\Eloquent\Collection $work_experiences
51
 * @property \Illuminate\Database\Eloquent\Collection $references
52
 * @property \Illuminate\Database\Eloquent\Collection $work_samples
53
 * @property \Illuminate\Database\Eloquent\Collection $projects
54
 * @property \App\Models\JobApplicationVersion $job_application_version
55
 */
56
class JobApplication extends BaseModel
57
{
58
59
    use Notifiable;
60
61
    protected $dispatchesEvents = [
62
        'retrieved' => ApplicationRetrieved::class,
63
        'saved' => ApplicationSaved::class,
64
    ];
65
66
    protected $casts = [
67
        'job_poster_id' => 'int',
68
        'application_status_id' => 'int',
69
        'citizenship_declaration_id' => 'int',
70
        'veteran_status_id' => 'int',
71
        'preferred_language_id' => 'int',
72
        'applicant_id' => 'int',
73
        'applicant_snapshot_id' => 'int',
74
        'submission_signature' => 'string',
75
        'submission_date' => 'string',
76
        'experience_saved' => 'boolean',
77
        'language_requirement_confirmed' => 'boolean',
78
        'version_id'
79
    ];
80
    protected $fillable = [
81
        'citizenship_declaration_id',
82
        'language_requirement_confirmed',
83
        'veteran_status_id',
84
        'preferred_language_id',
85
        'submission_signature',
86
        'submission_date',
87
        'experience_saved',
88
    ];
89
90
    /**
91
     * The accessors to append to the model's array/json form.
92
     *
93
     * @var array
94
     */
95
    protected $appends = ['meets_essential_criteria'];
96
97
    protected function createApplicantSnapshot($applicant_id)
98
    {
99
        $applicant = Applicant::where('id', $applicant_id)->firstOrFail();
100
101
        $snapshot = $applicant->replicate();
0 ignored issues
show
Unused Code introduced by
The assignment to $snapshot is dead and can be removed.
Loading history...
102
    }
103
104
    public function applicant()
105
    {
106
        return $this->belongsTo(\App\Models\Applicant::class);
107
    }
108
109
    public function applicant_snapshot() //phpcs:ignore
110
    {
111
        return $this->belongsTo(\App\Models\Applicant::class, 'applicant_snapshot_id');
112
    }
113
114
    public function application_status() //phpcs:ignore
115
    {
116
        return $this->belongsTo(\App\Models\Lookup\ApplicationStatus::class);
117
    }
118
119
    public function citizenship_declaration() //phpcs:ignore
120
    {
121
        return $this->belongsTo(\App\Models\Lookup\CitizenshipDeclaration::class);
122
    }
123
124
    public function veteran_status() //phpcs:ignore
125
    {
126
        return $this->belongsTo(\App\Models\Lookup\VeteranStatus::class);
127
    }
128
129
    public function preferred_language() //phpcs:ignore
130
    {
131
        return $this->belongsTo(\App\Models\Lookup\PreferredLanguage::class);
132
    }
133
134
    public function job_poster() //phpcs:ignore
135
    {
136
        return $this->belongsTo(\App\Models\JobPoster::class);
137
    }
138
139
    public function job_application_answers() //phpcs:ignore
140
    {
141
        return $this->hasMany(\App\Models\JobApplicationAnswer::class);
142
    }
143
144
    public function skill_declarations() //phpcs:ignore
145
    {
146
        return $this->morphMany(\App\Models\SkillDeclaration::class, 'skillable');
147
    }
148
149
    public function application_review() //phpcs:ignore
150
    {
151
        return $this->hasOne(ApplicationReview::class);
152
    }
153
154
    public function degrees()
155
    {
156
        return $this->morphMany(\App\Models\Degree::class, 'degreeable')->orderBy('end_date', 'desc');
157
    }
158
159
    public function courses()
160
    {
161
        return $this->morphMany(\App\Models\Course::class, 'courseable')->orderBy('end_date', 'desc');
162
    }
163
164
    public function work_experiences() //phpcs:ignore
165
    {
166
        return $this->morphMany(\App\Models\WorkExperience::class, 'experienceable')->orderBy('end_date', 'desc');
167
    }
168
169
    public function references()
170
    {
171
        return $this->morphMany(\App\Models\Reference::class, 'referenceable');
172
    }
173
174
    public function projects()
175
    {
176
        return $this->morphMany(\App\Models\Project::class, 'projectable');
177
    }
178
179
    public function work_samples() //phpcs:ignore
180
    {
181
        return $this->morphMany(\App\Models\WorkSample::class, 'work_sampleable');
182
    }
183
184
    public function job_application_version() //phpcs:ignore
185
    {
186
        return $this->hasOne(\App\Models\JobApplicationVersion::class);
187
    }
188
189
    /**
190
     * Return either 'complete', 'incomplete' or 'error', depending on the
191
     * status of the requested section.
192
     *
193
     * @param  string $section Should be one of:
194
     *                              'basics'
195
     *                              'experience'
196
     *                              'essential_skills'
197
     *                              'asset_skills'
198
     *                              'preview'
199
     *
200
     * @return string $status   'complete', 'incomplete' or 'error'
201
     */
202
    public function getSectionStatus(string $section)
203
    {
204
        // TODO: determine whether sections are complete or invalid
205
        $validator = new ApplicationValidator();
206
        $status = 'incomplete';
207
        switch ($section) {
208
            case 'basics':
209
                if ($validator->basicsComplete($this)) {
210
                    $status = 'complete';
211
                }
212
                break;
213
            case 'experience':
214
                if ($validator->experienceComplete($this)) {
215
                    $status = 'complete';
216
                }
217
                break;
218
            case 'essential_skills':
219
                if ($validator->essentialSkillsComplete($this)) {
220
                    $status = 'complete';
221
                }
222
                break;
223
            case 'asset_skills':
224
                if ($validator->assetSkillsComplete($this)) {
225
                    $status = 'complete';
226
                }
227
                break;
228
            case 'preview':
229
                if ($validator->basicsComplete($this) &&
230
                    $validator->experienceComplete($this) &&
231
                    $validator->essentialSkillsComplete($this) &&
232
                    $validator->assetSkillsComplete($this)
233
                ) {
234
                    $status = 'complete';
235
                }
236
                break;
237
            case 'confirm':
238
                if ($validator->affirmationComplete($this)) {
239
                    $status = 'complete';
240
                }
241
                break;
242
            default:
243
                $status = 'error';
244
                break;
245
        }
246
        return $status;
247
    }
248
249
    /**
250
     * Check if the status of the application is 'draft'
251
     *
252
     * @return boolean
253
     */
254
    public function isDraft(): bool
255
    {
256
        return $this->application_status->name === 'draft';
257
    }
258
259
    /**
260
     * Returns true if this meets all the essential criteria.
261
     * That means it has attached an SkillDeclaration for each essential criterion,
262
     * with a level at least as high as the required level.
263
     * NOTE: If this application is in draft status, it will use
264
     *  SkillDeclarations from the the applicants profile for this check.
265
     *
266
     * @return boolean
267
     */
268
    public function meetsEssentialCriteria(): bool
269
    {
270
        $essentialCriteria = $this->job_poster->criteria->filter(
271
            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

271
            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...
272
                return $value->criteria_type->name == 'essential';
273
            }
274
        );
275
        $source = $this->isDraft() ? $this->applicant : $this;
276
        foreach ($essentialCriteria as $criterion) {
277
            $skillDeclaration = $source->skill_declarations->where('skill_id', $criterion->skill_id)->first();
278
            if ($skillDeclaration === null ||
279
                $skillDeclaration->skill_level_id < $criterion->skill_level_id
280
            ) {
281
                return false;
282
            }
283
        }
284
        return true;
285
    }
286
287
    /**
288
     * Accessor for meetsEssentialCriteria function, which
289
     * allows this value to be automatically appended to array/json representation.
290
     *
291
     * @return boolean
292
     */
293
    public function getMeetsEssentialCriteriaAttribute(): bool
294
    {
295
        return $this->meetsEssentialCriteria();
296
    }
297
298
    /**
299
     * Save copies of all relevant profile data to this application.
300
     *
301
     *
302
     * @return void
303
     */
304
    public function saveProfileSnapshot(): void
305
    {
306
        $applicant = $this->applicant->fresh();
307
308
        $this->user_name = $applicant->user->full_name;
309
        $this->user_email = $applicant->user->email;
310
        $this->save();
311
312
        // Delete previous snapshot.
313
        $this->degrees()->delete();
314
        $this->courses()->delete();
315
        $this->work_experiences()->delete();
316
        $this->projects()->delete();
317
        $this->references()->delete();
318
        $this->work_samples()->delete();
319
        $this->skill_declarations()->delete();
320
321
        $this->degrees()->saveMany($applicant->degrees->map->replicate());
322
        $this->courses()->saveMany($applicant->courses->map->replicate());
323
        $this->work_experiences()->saveMany($applicant->work_experiences->map->replicate());
324
325
        $copyWithHistory = function ($model) {
326
            return [
327
                'old' => $model,
328
                'new' => $model->replicate()
329
            ];
330
        };
331
332
333
        $projectMap = $applicant->projects->map($copyWithHistory);
334
        $referenceMap = $applicant->references->map($copyWithHistory);
335
        $workSampleMap = $applicant->work_samples->map($copyWithHistory);
336
        $skillDeclarationMap = $applicant->skill_declarations->map($copyWithHistory);
337
338
        // First link new projects, references, work samples and skill declarations to this application.
339
        $this->projects()->saveMany($projectMap->pluck('new'));
340
        $this->references()->saveMany($referenceMap->pluck('new'));
341
        $this->work_samples()->saveMany($workSampleMap->pluck('new'));
342
        $this->skill_declarations()->saveMany($skillDeclarationMap->pluck('new'));
343
344
        $findNewFromOld = function ($mapping, $old) {
345
            $matchingItem = $mapping->first(function ($value) use ($old) {
346
                return $value['old']->id === $old->id;
347
            });
348
            return $matchingItem['new'];
349
        };
350
351
        // Replicate copies shallow attributes, but not relationships. We have to copy those ourselves.
352
        $findNewReferenceFromOld = function ($old) use ($findNewFromOld, $referenceMap) {
353
            return $findNewFromOld($referenceMap, $old);
354
        };
355
356
        $findNewSkillDeclarationFromOld = function ($old) use ($findNewFromOld, $skillDeclarationMap) {
357
            return $findNewFromOld($skillDeclarationMap, $old);
358
        };
359
360
        // Link projects and references.
361
        foreach ($projectMap as $item) {
362
            $old = $item['old'];
363
            $newProj = $item['new'];
364
            $newReferences = $old->references->map($findNewReferenceFromOld);
365
            $newProj->references()->sync($newReferences);
366
        }
367
368
        // Link references and skills.
369
        foreach ($referenceMap as $item) {
370
            $old = $item['old'];
371
            $newRef = $item['new'];
372
            $newSkillDecs = $old->skill_declarations->map($findNewSkillDeclarationFromOld);
373
            $newRef->skill_declarations()->sync($newSkillDecs);
374
        }
375
376
        // Link work samples and skills.
377
        foreach ($workSampleMap as $item) {
378
            $old = $item['old'];
379
            $newSample = $item['new'];
380
            $newSkillDecs = $old->skill_declarations->map($findNewSkillDeclarationFromOld);
381
            $newSample->skill_declarations()->sync($newSkillDecs);
382
        }
383
    }
384
}
385