Passed
Push — feature/job-streams ( 1adae6 )
by Tristan
06:05
created

JobPoster::job_skill_level()   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
c 0
b 0
f 0
dl 0
loc 3
rs 10
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\JobSaved;
11
use Backpack\CRUD\app\Models\Traits\CrudTrait;
12
use Illuminate\Notifications\Notifiable;
13
use Illuminate\Support\Facades\App;
14
use Illuminate\Support\Facades\Lang;
15
use Jenssegers\Date\Date;
16
17
use Spatie\Translatable\HasTranslations;
18
19
/**
20
 * Class JobPoster
21
 *
22
 * @property int $id
23
 * @property int $job_term_id
24
 * @property string $chosen_lang
25
 * @property int $term_qty
26
 * @property \Jenssegers\Date\Date $open_date_time
27
 * @property \Jenssegers\Date\Date $close_date_time
28
 * @property \Jenssegers\Date\Date $start_date_time
29
 * @property int $department_id
30
 * @property int $province_id
31
 * @property int $salary_min
32
 * @property int $salary_max
33
 * @property int $noc
34
 * @property int $classification_id
35
 * @property int $classification_level
36
 * @property int $security_clearance_id
37
 * @property int $language_requirement_id
38
 * @property boolean $remote_work_allowed
39
 * @property int $manager_id
40
 * @property boolean $internal_only
41
 * @property int $team_size
42
 * @property array $work_env_features This should be an array of boolean flags for features, ie json of shape {[feature: string]: boolean}
43
 * @property int $fast_vs_steady
44
 * @property int $horizontal_vs_vertical
45
 * @property int $experimental_vs_ongoing
46
 * @property int $citizen_facing_vs_back_office
47
 * @property int $collaborative_vs_independent
48
 * @property int $telework_allowed_frequency_id
49
 * @property int $flexible_hours_frequency_id
50
 * @property int $travel_requirement_id
51
 * @property int $overtime_requirement_id
52
 * @property string $process_number
53
 * @property int $priority_clearance_number
54
 * @property int $job_poster_status_id
55
 * @property \Jenssegers\Date\Date $loo_issuance_date
56
 * @property int $talent_stream_id
57
 * @property int $talent_stream_category_id
58
 * @property int $job_skill_level_id
59
 * @property \Jenssegers\Date\Date $created_at
60
 * @property \Jenssegers\Date\Date $updated_at
61
 *
62
 * @property int $submitted_applications_count
63
 *
64
 * @property \App\Models\Lookup\Department $department
65
 * @property \App\Models\Lookup\JobTerm $job_term
66
 * @property \App\Models\Lookup\LanguageRequirement $language_requirement
67
 * @property \App\Models\Manager $manager
68
 * @property \App\Models\Lookup\Province $province
69
 * @property \App\Models\Lookup\SecurityClearance $security_clearance
70
 * @property \Illuminate\Database\Eloquent\Collection $criteria
71
 * @property \Illuminate\Database\Eloquent\Collection $job_applications
72
 * @property \Illuminate\Database\Eloquent\Collection $job_poster_key_tasks
73
 * @property \Illuminate\Database\Eloquent\Collection $job_poster_questions
74
 * @property \Illuminate\Database\Eloquent\Collection $job_poster_translations
75
 * @property \Illuminate\Database\Eloquent\Collection $submitted_applications
76
 * @property \Illuminate\Database\Eloquent\Collection $hr_advisors
77
 * @property \App\Models\Lookup\Frequency $telework_allowed_frequency
78
 * @property \App\Models\Lookup\Frequency $flexible_hours_frequency
79
 * @property \App\Models\Lookup\JobPosterStatus $job_poster_status
80
 * @property \App\Models\Lookup\TalentStream|null $talent_stream
81
 * @property \App\Models\Lookup\TalentStreamCategory|null $talent_stream_category
82
 * @property \App\Models\Lookup\JobSkillLevel|null $job_skill_level
83
 *
84
 * Localized Properties:
85
 * @property string $city
86
 * @property string $title
87
 * @property string $dept_impact
88
 * @property string $team_impact
89
 * @property string $hire_impact
90
 * @property string $division
91
 * @property string $education
92
 * @property string $work_env_description
93
 * @property string $culture_summary
94
 * @property string $culture_special
95
 *
96
 * Methods
97
 * @method boolean isOpen()
98
 * @method boolean isClosed()
99
 * @method boolean isVisibleToHr()
100
 * @method boolean isPublic()
101
 * @method boolean isEditable()
102
 * @method string timeRemaining()
103
 * @method mixed[] toApiArray()
104
 *
105
 * Computed Properties
106
 * @property string|null $classification_code
107
 * @property string|null $classification_message
108
 */
109
class JobPoster extends BaseModel
110
{
111
    use CrudTrait;
112
    use HasTranslations;
113
    use Notifiable;
114
115
    const DATE_FORMAT = [
116
        'en' => 'M jS, Y',
117
        'fr' => 'd M Y',
118
    ];
119
    const TIME_FORMAT = [
120
        'en' => 'h:i A T',
121
        'fr' => 'H \h i T',
122
    ];
123
    const TIMEZONE = 'America/Toronto';
124
125
    /**
126
     * @var string[] $translatable
127
     */
128
    public $translatable = [
129
        'city',
130
        'title',
131
        'dept_impact',
132
        'team_impact',
133
        'hire_impact',
134
        'division',
135
        'education',
136
        'work_env_description',
137
        'culture_summary',
138
        'culture_special',
139
    ];
140
141
    /**
142
     * @var string[] $casts
143
     */
144
    protected $casts = [
145
        'job_term_id' => 'int',
146
        'department_id' => 'int',
147
        'province_id' => 'int',
148
        'salary_min' => 'int',
149
        'salary_max' => 'int',
150
        'noc' => 'int',
151
        'classification_id' => 'int',
152
        'classification_level' => 'int',
153
        'security_clearance_id' => 'int',
154
        'language_requirement_id' => 'int',
155
        'remote_work_allowed' => 'boolean',
156
        'manager_id' => 'int',
157
        'internal_only' => 'boolean',
158
        'team_size' => 'int',
159
        'work_env_features' => 'array',
160
        'fast_vs_steady' => 'int',
161
        'horizontal_vs_vertical' => 'int',
162
        'experimental_vs_ongoing' => 'int',
163
        'citizen_facing_vs_back_office' => 'int',
164
        'collaborative_vs_independent' => 'int',
165
        'telework_allowed_frequency_id' => 'int',
166
        'flexible_hours_frequency_id' => 'int',
167
        'travel_requirement_id' => 'int',
168
        'overtime_requirement_id' => 'int',
169
        'process_number' => 'string',
170
        'priority_clearance_number' => 'int'
171
    ];
172
173
    /**
174
     * @var string[] $dates
175
     */
176
    protected $dates = [
177
        'open_date_time',
178
        'close_date_time',
179
        'start_date_time',
180
        'loo_issuance_date',
181
    ];
182
183
    /**
184
     * @var string[] $fillable
185
     */
186
    protected $fillable = [
187
        'job_term_id',
188
        'chosen_lang',
189
        'term_qty',
190
        'open_date_time',
191
        'close_date_time',
192
        'start_date_time',
193
        'department_id',
194
        'province_id',
195
        'salary_min',
196
        'salary_max',
197
        'noc',
198
        'security_clearance_id',
199
        'language_requirement_id',
200
        'remote_work_allowed',
201
        'internal_only',
202
        'team_size',
203
        'work_env_features',
204
        'fast_vs_steady',
205
        'horizontal_vs_vertical',
206
        'experimental_vs_ongoing',
207
        'citizen_facing_vs_back_office',
208
        'collaborative_vs_independent',
209
        'telework_allowed_frequency_id',
210
        'flexible_hours_frequency_id',
211
        'travel_requirement_id',
212
        'overtime_requirement_id',
213
        'process_number',
214
        'priority_clearance_number',
215
        'loo_issuance_date',
216
        'classification_id',
217
        'classification_level',
218
        'city',
219
        'title',
220
        'dept_impact',
221
        'team_impact',
222
        'hire_impact',
223
        'division',
224
        'education',
225
        'work_env_description',
226
        'culture_summary',
227
        'culture_special',
228
        'talent_stream_id',
229
        'talent_stream_category_id',
230
        'job_skill_level_id',
231
        'job_poster_status_id', // This really shouldn't be mass-editable, but its necesary for the admin crud portal to set it.
232
    ];
233
234
    /**
235
     * The attributes that should be visible in arrays.
236
     * In this case, it blocks loaded relations from appearing.
237
     *
238
     * @var array
239
     */
240
    protected $visible = [
241
        'id',
242
        'manager_id',
243
        'chosen_lang',
244
        'term_qty',
245
        'open_date_time',
246
        'close_date_time',
247
        'start_date_time',
248
        'department_id',
249
        'province_id',
250
        'salary_min',
251
        'salary_max',
252
        'noc',
253
        'security_clearance_id',
254
        'language_requirement_id',
255
        'remote_work_allowed',
256
        'team_size',
257
        'work_env_features',
258
        'fast_vs_steady',
259
        'horizontal_vs_vertical',
260
        'experimental_vs_ongoing',
261
        'citizen_facing_vs_back_office',
262
        'collaborative_vs_independent',
263
        'telework_allowed_frequency_id',
264
        'flexible_hours_frequency_id',
265
        'travel_requirement_id',
266
        'overtime_requirement_id',
267
        'process_number',
268
        'priority_clearance_number',
269
        'loo_issuance_date',
270
        'classification_id',
271
        'classification_level',
272
        'city',
273
        'title',
274
        'dept_impact',
275
        'team_impact',
276
        'hire_impact',
277
        'division',
278
        'education',
279
        'work_env_description',
280
        'culture_summary',
281
        'culture_special',
282
        'job_poster_status_id',
283
        'talent_stream_id',
284
        'talent_stream_category_id',
285
        'job_skill_level_id',
286
        'created_at',
287
    ];
288
289
    /**
290
     * The accessors to append to the model's array form.
291
     *
292
     * @var string[] $appends
293
     */
294
    protected $appends = [
295
        'classification_code',
296
        'classification_message',
297
    ];
298
299
    /**
300
     * Eager loaded relationships by default.
301
     *
302
     * @var string[] $with
303
     */
304
    protected $with = [
305
        'criteria',
306
        'manager'
307
    ];
308
309
    /**
310
     * @var mixed[] $dispatchesEvents
311
     */
312
    protected $dispatchesEvents = [
313
        'saved' => JobSaved::class,
314
    ];
315
316
    // @codeCoverageIgnoreStart
317
    public function department() // phpcs:ignore
318
    {
319
        return $this->belongsTo(\App\Models\Lookup\Department::class);
320
    }
321
322
    public function job_term() // phpcs:ignore
323
    {
324
        return $this->belongsTo(\App\Models\Lookup\JobTerm::class);
325
    }
326
327
    public function language_requirement() // phpcs:ignore
328
    {
329
        return $this->belongsTo(\App\Models\Lookup\LanguageRequirement::class);
330
    }
331
332
    public function manager() // phpcs:ignore
333
    {
334
        return $this->belongsTo(\App\Models\Manager::class);
335
    }
336
337
    public function province() // phpcs:ignore
338
    {
339
        return $this->belongsTo(\App\Models\Lookup\Province::class);
340
    }
341
342
    public function security_clearance() // phpcs:ignore
343
    {
344
        return $this->belongsTo(\App\Models\Lookup\SecurityClearance::class);
345
    }
346
347
    public function criteria() // phpcs:ignore
348
    {
349
        return $this->hasMany(\App\Models\Criteria::class);
350
    }
351
352
    public function hr_advisors() // phpcs:ignore
353
    {
354
        return $this->belongsToMany(
355
            \App\Models\HrAdvisor::class,
356
            'claimed_jobs'
357
        );
358
    }
359
360
    public function job_applications() // phpcs:ignore
361
    {
362
        return $this->hasMany(\App\Models\JobApplication::class);
363
    }
364
365
    public function job_poster_key_tasks() // phpcs:ignore
366
    {
367
        return $this->hasMany(\App\Models\JobPosterKeyTask::class)->orderBy('order', 'asc');
368
    }
369
370
    public function job_poster_questions() // phpcs:ignore
371
    {
372
        return $this->hasMany(\App\Models\JobPosterQuestion::class);
373
    }
374
375
    public function telework_allowed_frequency() // phpcs:ignore
376
    {
377
        return $this->belongsTo(\App\Models\Lookup\Frequency::class);
378
    }
379
380
    public function flexible_hours_frequency() // phpcs:ignore
381
    {
382
        return $this->belongsTo(\App\Models\Lookup\Frequency::class);
383
    }
384
385
    public function travel_requirement() // phpcs:ignore
386
    {
387
        return $this->belongsTo(\App\Models\Lookup\TravelRequirement::class);
388
    }
389
390
    public function overtime_requirement() // phpcs:ignore
391
    {
392
        return $this->belongsTo(\App\Models\Lookup\OvertimeRequirement::class);
393
    }
394
395
    public function classification() // phpcs:ignore
396
    {
397
        return $this->belongsTo(\App\Models\Classification::class);
398
    }
399
400
    public function comments() // phpcs:ignore
401
    {
402
        return $this->hasMany(\App\Models\Comment::class);
403
    }
404
405
    public function job_poster_status() // phpcs:ignore
406
    {
407
        return $this->belongsTo(\App\Models\Lookup\JobPosterStatus::class);
408
    }
409
410
    public function job_poster_status_histories() // phpcs:ignore
411
    {
412
        return $this->hasMany(\App\Models\JobPosterStatusHistory::class);
413
    }
414
415
    public function talent_stream() // phpcs:ignore
416
    {
417
        return $this->belongsTo(\App\Models\Lookup\TalentStream::class);
418
    }
419
420
    public function talent_stream_category() // phpcs:ignore
421
    {
422
        return $this->belongsTo(\App\Models\Lookup\TalentStreamCategory::class);
423
    }
424
425
    public function job_skill_level() // phpcs:ignore
426
    {
427
        return $this->belongsTo(\App\Models\Lookup\JobSkillLevel::class);
428
    }
429
430
    // @codeCoverageIgnoreEnd
431
    /* Artificial Relations */
432
433
    /**
434
     * Get all of the Job Applications submitted to this
435
     * Job Poster.
436
     *
437
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
438
     */
439
    public function submitted_applications() // phpcs:ignore
440
    {
441
        return $this->hasMany(\App\Models\JobApplication::class)->whereDoesntHave('application_status', function ($query): void {
442
            $query->where('name', 'draft');
443
        });
444
    }
445
446
    /* Overrides */
447
448
    /**
449
     * Retrieve the model for a bound value.
450
     * Seems to be a useful workaround for providing submitted_applications_count
451
     * to any bound routes that receive a jobPoster instance without using the
452
     * withCount property on the model itself.
453
     * See https://github.com/laravel/framework/issues/23957 for more info.
454
     *
455
     * @param mixed $value Value used to retrieve the model instance.
456
     *
457
     * @return \Illuminate\Database\Eloquent\Model|null
458
     */
459
    public function resolveRouteBinding($value) // phpcs:ignore
460
    {
461
        return $this->withCount('submitted_applications')->where('id', $value)->first() ?? abort(404);
0 ignored issues
show
Bug introduced by
The function abort was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

461
        return $this->withCount('submitted_applications')->where('id', $value)->first() ?? /** @scrutinizer ignore-call */ abort(404);
Loading history...
462
    }
463
464
    /* Methods */
465
466
    public function submitted_applications_count() //phpcs:ignore
467
    {
468
        return $this->submitted_applications()->count();
469
    }
470
471
    /**
472
     * Formatted and localized date and time the Job Poster closes.
473
     *
474
     * @return string[]
475
     */
476
    public function applyBy(): array
477
    {
478
        $localCloseDate = new Date($this->close_date_time); // This initializes the date object in UTC time.
479
        $localCloseDate->setTimezone(new \DateTimeZone(self::TIMEZONE)); // Then set the time zone for display.
480
        $displayDate = [
481
            'date' => $localCloseDate->format(self::DATE_FORMAT[App::getLocale()]),
482
            'time' => $localCloseDate->format(self::TIME_FORMAT[App::getLocale()])
483
        ];
484
485
        if (App::isLocale('fr')) {
486
            $displayDate['time'] = str_replace(['EST', 'EDT'], ['HNE', 'HAE'], $displayDate['time']);
487
        }
488
489
        return $displayDate;
490
    }
491
492
    /**
493
     * Return whether the Job is Open or Closed.
494
     * Used by the Admin Portal JobPosterCrudController.
495
     *
496
     * @return string
497
     */
498
    public function displayStatus(): string
499
    {
500
        return $this->isOpen() ? 'Open' : 'Closed';
501
    }
502
503
    /**
504
     * Check if a Job Poster is open for applications.
505
     *
506
     * @return boolean
507
     */
508
    public function isOpen(): bool
509
    {
510
        return $this->job_poster_status->key === 'live';
511
    }
512
513
    /**
514
     * Check if a Job Poster is closed for applications.
515
     *
516
     * @return boolean
517
     */
518
    public function isClosed(): bool
519
    {
520
        return ($this->job_poster_status->key === 'assessment'
521
            || $this->job_poster_status->key === 'completed');
522
    }
523
524
    /**
525
     * Return true if this job should be visible to hr advisors.
526
     * It should become visible after Manager has requested a review.
527
     *
528
     * @return boolean
529
     */
530
    public function isVisibleToHr()
531
    {
532
        return $this->job_poster_status->key !== 'draft';
533
    }
534
535
    /**
536
     * Check if a Job Poster is open to public view.
537
     *
538
     * @return boolean
539
     */
540
    public function isPublic(): bool
541
    {
542
        return ($this->job_poster_status->key == 'live'
543
            || $this->job_poster_status->key == 'assessment'
544
            || $this->job_poster_status->key == 'completed');
545
    }
546
547
    /**
548
     * Check if a Job Poster is theoretically editable.
549
     *
550
     * @return boolean
551
     */
552
    public function isEditable(): bool
553
    {
554
        // Admins, at least, should be able to edit the job up until the public can see it.
555
        return !$this->isPublic();
556
    }
557
558
    /**
559
     * Calculate the remaining time a Job Poster is open.
560
     *
561
     * @return string
562
     */
563
    public function timeRemaining(): string
564
    {
565
        $interval = $this->close_date_time->diff(Date::now());
566
567
        $d = $interval->d;
568
        $h = $interval->h;
569
        $m = $interval->i;
570
        $s = $interval->s;
571
572
        if ($d > 0) {
573
            $unit = 'day';
574
            $count = $d;
575
        } elseif ($h > 0) {
576
            $unit = 'hour';
577
            $count = $h;
578
        } elseif ($m > 0) {
579
            $unit = 'minute';
580
            $count = $m;
581
        } else {
582
            $unit = 'second';
583
            $count = $s;
584
        }
585
586
        $key = "common/time.$unit";
587
588
        return Lang::choice($key, $count);
589
    }
590
591
592
    /**
593
     * The database model stores a foreign id to the classification table,
594
     * but to simplify the API, this model simply returns the key as classification_code.
595
     *
596
     * @return string|null
597
     */
598
    public function getClassificationCodeAttribute()
599
    {
600
        if ($this->classification_id !== null) {
601
            return $this->classification->key;
602
        }
603
        return null;
604
    }
605
606
    /**
607
     *
608
     * Get the full government classification message.
609
     *
610
     * @return string|null
611
     */
612
    public function getClassificationMessageAttribute()
613
    {
614
        if ($this->classification_id !== null && $this->classification_level !== null) {
615
            return $this->classification->key . '-0' . $this->classification_level;
616
        }
617
        return null;
618
    }
619
}
620