Passed
Push — feature/hr-portal ( 430318...54a87b )
by Tristan
11:31
created

JobPoster::getClassificationMessageAttribute()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
dl 0
loc 6
rs 10
c 1
b 0
f 0
cc 3
nc 2
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 Astrotomic\Translatable\Translatable;
11
12
use Backpack\CRUD\app\Models\Traits\CrudTrait;
13
14
use Jenssegers\Date\Date;
15
16
use Illuminate\Notifications\Notifiable;
17
use Illuminate\Support\Facades\App;
18
use Illuminate\Support\Facades\Lang;
19
20
use App\Events\JobSaved;
21
22
/**
23
 * Class JobPoster
24
 *
25
 * @property int $id
26
 * @property int $job_term_id
27
 * @property string $chosen_lang
28
 * @property int $term_qty
29
 * @property \Jenssegers\Date\Date $open_date_time
30
 * @property \Jenssegers\Date\Date $close_date_time
31
 * @property \Jenssegers\Date\Date $start_date_time
32
 * @property \Jenssegers\Date\Date $review_requested_at
33
 * @property \Jenssegers\Date\Date $published_at
34
 * @property int $department_id
35
 * @property int $province_id
36
 * @property int $salary_min
37
 * @property int $salary_max
38
 * @property int $noc
39
 * @property int $classification_id
40
 * @property int $classification_level
41
 * @property int $security_clearance_id
42
 * @property int $language_requirement_id
43
 * @property boolean $remote_work_allowed
44
 * @property int $manager_id
45
 * @property boolean $published
46
 * @property int $team_size
47
 * @property array $work_env_features This should be an array of boolean flags for features, ie json of shape {[feature: string]: boolean}
48
 * @property int $fast_vs_steady
49
 * @property int $horizontal_vs_vertical
50
 * @property int $experimental_vs_ongoing
51
 * @property int $citizen_facing_vs_back_office
52
 * @property int $collaborative_vs_independent
53
 * @property int $telework_allowed_frequency_id
54
 * @property int $flexible_hours_frequency_id
55
 * @property int $travel_requirement_id
56
 * @property int $overtime_requirement_id
57
 * @property int $process_number
58
 * @property int $priority_clearance_number
59
 * @property \Jenssegers\Date\Date $loo_issuance_date
60
 * @property \Jenssegers\Date\Date $created_at
61
 * @property \Jenssegers\Date\Date $updated_at
62
 *
63
 * @property int $submitted_applications_count
64
 *
65
 * @property \App\Models\Lookup\Department $department
66
 * @property \App\Models\Lookup\JobTerm $job_term
67
 * @property \App\Models\Lookup\LanguageRequirement $language_requirement
68
 * @property \App\Models\Manager $manager
69
 * @property \App\Models\Lookup\Province $province
70
 * @property \App\Models\Lookup\SecurityClearance $security_clearance
71
 * @property \Illuminate\Database\Eloquent\Collection $criteria
72
 * @property \Illuminate\Database\Eloquent\Collection $job_applications
73
 * @property \Illuminate\Database\Eloquent\Collection $job_poster_key_tasks
74
 * @property \Illuminate\Database\Eloquent\Collection $job_poster_questions
75
 * @property \Illuminate\Database\Eloquent\Collection $job_poster_translations
76
 * @property \Illuminate\Database\Eloquent\Collection $submitted_applications
77
 * @property \Illuminate\Database\Eloquent\Collection $hr_advisors
78
 * @property \App\Models\Lookup\Frequency $telework_allowed_frequency
79
 * @property \App\Models\Lookup\Frequency $flexible_hours_frequency
80
 *
81
 * Localized Properties:
82
 * @property string $city
83
 * @property string $title
84
 * @property string $dept_impact
85
 * @property string $team_impact
86
 * @property string $hire_impact
87
 * @property string $division
88
 * @property string $education
89
 * @property string $work_env_description
90
 * @property string $culture_summary
91
 * @property string $culture_special
92
 *
93
 * Methods
94
 * @method boolean isOpen()
95
 * @method string timeRemaining()
96
 * @method mixed[] toApiArray()
97
 *
98
 * Computed Properties
99
 * @property string|null $classification_code
100
 * @property string|null $classification_message
101
 * @property int $job_status_id
102
 */
103
class JobPoster extends BaseModel
104
{
105
    use CrudTrait;
0 ignored issues
show
introduced by
The trait Backpack\CRUD\app\Models\Traits\CrudTrait requires some properties which are not provided by App\Models\JobPoster: $Type, $fakeColumns
Loading history...
106
    use Translatable;
0 ignored issues
show
Bug introduced by
The trait Astrotomic\Translatable\Translatable requires the property $each which is not provided by App\Models\JobPoster.
Loading history...
107
    use Notifiable;
0 ignored issues
show
Bug introduced by
The trait Illuminate\Notifications\Notifiable requires the property $email which is not provided by App\Models\JobPoster.
Loading history...
108
109
    const DATE_FORMAT = [
110
        'en' => 'M jS, Y',
111
        'fr' => 'd M Y',
112
    ];
113
    const TIME_FORMAT = [
114
        'en' => 'h:i A T',
115
        'fr' => 'H \h i T',
116
    ];
117
    const TIMEZONE = 'America/Toronto';
118
119
    /**
120
     * @var string[] $translatedAttributes
121
     */
122
    public $translatedAttributes = [
123
        'city',
124
        'title',
125
        'dept_impact',
126
        'team_impact',
127
        'hire_impact',
128
        'division',
129
        'education',
130
        'work_env_description',
131
        'culture_summary',
132
        'culture_special',
133
    ];
134
135
    /**
136
     * @var string[] $casts
137
     */
138
    protected $casts = [
139
        'job_term_id' => 'int',
140
        'department_id' => 'int',
141
        'province_id' => 'int',
142
        'salary_min' => 'int',
143
        'salary_max' => 'int',
144
        'noc' => 'int',
145
        'classification_id' => 'int',
146
        'classification_level' => 'int',
147
        'security_clearance_id' => 'int',
148
        'language_requirement_id' => 'int',
149
        'remote_work_allowed' => 'boolean',
150
        'manager_id' => 'int',
151
        'published' => 'boolean',
152
        'team_size' => 'int',
153
        'work_env_features' => 'array',
154
        'fast_vs_steady' => 'int',
155
        'horizontal_vs_vertical' => 'int',
156
        'experimental_vs_ongoing' => 'int',
157
        'citizen_facing_vs_back_office' => 'int',
158
        'collaborative_vs_independent' => 'int',
159
        'telework_allowed_frequency_id' => 'int',
160
        'flexible_hours_frequency_id' => 'int',
161
        'travel_requirement_id' => 'int',
162
        'overtime_requirement_id' => 'int',
163
    ];
164
165
    /**
166
     * @var string[] $dates
167
     */
168
    protected $dates = [
169
        'open_date_time',
170
        'close_date_time',
171
        'start_date_time',
172
        'review_requested_at',
173
        'published_at',
174
        'loo_issuance_date',
175
    ];
176
177
    /**
178
     * @var string[] $fillable
179
     */
180
    protected $fillable = [
181
        'job_term_id',
182
        'chosen_lang',
183
        'term_qty',
184
        'open_date_time',
185
        'close_date_time',
186
        'start_date_time',
187
        'department_id',
188
        'province_id',
189
        'salary_min',
190
        'salary_max',
191
        'noc',
192
        'security_clearance_id',
193
        'language_requirement_id',
194
        'remote_work_allowed',
195
        'published',
196
        'team_size',
197
        'work_env_features',
198
        'fast_vs_steady',
199
        'horizontal_vs_vertical',
200
        'experimental_vs_ongoing',
201
        'citizen_facing_vs_back_office',
202
        'collaborative_vs_independent',
203
        'telework_allowed_frequency_id',
204
        'flexible_hours_frequency_id',
205
        'travel_requirement_id',
206
        'overtime_requirement_id',
207
        'process_number',
208
        'priority_clearance_number',
209
        'loo_issuance_date',
210
        'classification_id',
211
        'classification_level',
212
    ];
213
214
    /**
215
     * The attributes that should be visible in arrays.
216
     * In this case, it blocks loaded relations from appearing.
217
     *
218
     * @var array
219
     */
220
    protected $visible = [
221
        'id',
222
        'manager_id',
223
        'chosen_lang',
224
        'term_qty',
225
        'open_date_time',
226
        'close_date_time',
227
        'start_date_time',
228
        'department_id',
229
        'province_id',
230
        'salary_min',
231
        'salary_max',
232
        'noc',
233
        'security_clearance_id',
234
        'language_requirement_id',
235
        'remote_work_allowed',
236
        'published_at',
237
        'published',
238
        'review_requested_at',
239
        'team_size',
240
        'work_env_features',
241
        'fast_vs_steady',
242
        'horizontal_vs_vertical',
243
        'experimental_vs_ongoing',
244
        'citizen_facing_vs_back_office',
245
        'collaborative_vs_independent',
246
        'telework_allowed_frequency_id',
247
        'flexible_hours_frequency_id',
248
        'travel_requirement_id',
249
        'overtime_requirement_id',
250
        'process_number',
251
        'priority_clearance_number',
252
        'loo_issuance_date',
253
        'classification_id',
254
        'classification_level',
255
        'job_status_id'
256
    ];
257
258
    /**
259
     * The accessors to append to the model's array form.
260
     *
261
     * @var string[] $appends
262
     */
263
    protected $appends = [
264
        'classification_code',
265
        'classification_message',
266
        'job_status_id',
267
    ];
268
269
    /**
270
     * @var mixed[] $dispatchesEvents
271
     */
272
    protected $dispatchesEvents = [
273
        'saved' => JobSaved::class,
274
    ];
275
276
    // @codeCoverageIgnoreStart
277
    public function department() // phpcs:ignore
278
    {
279
        return $this->belongsTo(\App\Models\Lookup\Department::class);
280
    }
281
282
    public function job_term() // phpcs:ignore
283
    {
284
        return $this->belongsTo(\App\Models\Lookup\JobTerm::class);
285
    }
286
287
    public function language_requirement() // phpcs:ignore
288
    {
289
        return $this->belongsTo(\App\Models\Lookup\LanguageRequirement::class);
290
    }
291
292
    public function manager() // phpcs:ignore
293
    {
294
        return $this->belongsTo(\App\Models\Manager::class);
295
    }
296
297
    public function province() // phpcs:ignore
298
    {
299
        return $this->belongsTo(\App\Models\Lookup\Province::class);
300
    }
301
302
    public function security_clearance() // phpcs:ignore
303
    {
304
        return $this->belongsTo(\App\Models\Lookup\SecurityClearance::class);
305
    }
306
307
    public function criteria() // phpcs:ignore
308
    {
309
        return $this->hasMany(\App\Models\Criteria::class);
310
    }
311
312
    public function hr_advisors() // phpcs:ignore
313
    {
314
        return $this->belongsToMany(
315
            \App\Models\HrAdvisor::class,
316
            'claimed_jobs'
317
        );
318
    }
319
320
    public function job_applications() // phpcs:ignore
321
    {
322
        return $this->hasMany(\App\Models\JobApplication::class);
323
    }
324
325
    public function job_poster_key_tasks() // phpcs:ignore
326
    {
327
        return $this->hasMany(\App\Models\JobPosterKeyTask::class);
328
    }
329
330
    public function job_poster_questions() // phpcs:ignore
331
    {
332
        return $this->hasMany(\App\Models\JobPosterQuestion::class);
333
    }
334
335
    public function job_poster_translations() // phpcs:ignore
336
    {
337
        return $this->hasMany(\App\Models\JobPosterTranslation::class);
338
    }
339
340
    public function telework_allowed_frequency() // phpcs:ignore
341
    {
342
        return $this->belongsTo(\App\Models\Lookup\Frequency::class);
343
    }
344
345
    public function flexible_hours_frequency() // phpcs:ignore
346
    {
347
        return $this->belongsTo(\App\Models\Lookup\Frequency::class);
348
    }
349
350
    public function travel_requirement() // phpcs:ignore
351
    {
352
        return $this->belongsTo(\App\Models\Lookup\TravelRequirement::class);
353
    }
354
355
    public function overtime_requirement() // phpcs:ignore
356
    {
357
        return $this->belongsTo(\App\Models\Lookup\OvertimeRequirement::class);
358
    }
359
360
    public function classification() // phpcs:ignore
361
    {
362
        return $this->belongsTo(\App\Models\Classification::class);
363
    }
364
365
    public function comments() // phpcs:ignore
366
    {
367
        return $this->hasMany(\App\Models\Comment::class);
368
    }
369
    // @codeCoverageIgnoreEnd
370
    /* Artificial Relations */
371
372
    /**
373
     * Get all of the Job Applications submitted to this
374
     * Job Poster.
375
     *
376
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
377
     */
378
    public function submitted_applications() // phpcs:ignore
379
    {
380
        return $this->hasMany(\App\Models\JobApplication::class)->whereDoesntHave('application_status', function ($query): void {
381
            $query->where('name', 'draft');
382
        });
383
    }
384
385
    /* Overrides */
386
387
    /**
388
     * Retrieve the model for a bound value.
389
     * Seems to be a useful workaround for providing submitted_applications_count
390
     * to any bound routes that receive a jobPoster instance without using the
391
     * withCount property on the model itself.
392
     * See https://github.com/laravel/framework/issues/23957 for more info.
393
     *
394
     * @param mixed $value Value used to retrieve the model instance.
395
     *
396
     * @return \Illuminate\Database\Eloquent\Model|null
397
     */
398
    public function resolveRouteBinding($value) // phpcs:ignore
399
    {
400
        return $this->withCount('submitted_applications')->where('id', $value)->first() ?? abort(404);
401
    }
402
403
    /**
404
     * Intercept setting the "published" attribute, and set the
405
     * "published_at" timestamp if true.
406
     *
407
     * @param mixed $value Incoming value for the 'published' attribute.
408
     *
409
     * @return void
410
     */
411
    public function setPublishedAttribute($value): void
412
    {
413
        if ($value) {
414
            $this->attributes['published_at'] = new Date();
415
        } else {
416
            $this->attributes['published_at'] = null;
417
        }
418
        if ($value === null) {
419
            $value = false;
420
        }
421
        $this->attributes['published'] = $value;
422
    }
423
424
    /* Methods */
425
426
    public function submitted_applications_count() //phpcs:ignore
427
    {
428
        return $this->submitted_applications()->count();
429
    }
430
431
    /**
432
     * Formatted and localized date and time the Job Poster closes.
433
     *
434
     * @return string[]
435
     */
436
    public function applyBy(): array
437
    {
438
        $localCloseDate = new Date($this->close_date_time); // This initializes the date object in UTC time.
439
        $localCloseDate->setTimezone(new \DateTimeZone(self::TIMEZONE)); // Then set the time zone for display.
440
        $displayDate = [
441
            'date' => $localCloseDate->format(self::DATE_FORMAT[App::getLocale()]),
442
            'time' => $localCloseDate->format(self::TIME_FORMAT[App::getLocale()])
443
        ];
444
445
        if (App::isLocale('fr')) {
446
            $displayDate['time'] = str_replace(['EST', 'EDT'], ['HNE', 'HAE'], $displayDate['time']);
447
        }
448
449
        return $displayDate;
450
    }
451
452
    /**
453
     * Return whether the Job is Open or Closed.
454
     * Used by the Admin Portal JobPosterCrudController.
455
     *
456
     * @return string
457
     */
458
    public function displayStatus(): string
459
    {
460
        return $this->isOpen() ? 'Open' : 'Closed';
461
    }
462
463
    /**
464
     * Check if a Job Poster is open for applications.
465
     *
466
     * @return boolean
467
     */
468
    public function isOpen(): bool
469
    {
470
        return $this->published
471
            && $this->open_date_time !== null
472
            && $this->close_date_time !== null
473
            && $this->open_date_time->isPast()
474
            && $this->close_date_time->isFuture();
475
    }
476
477
    /**
478
     * Check if a Job Poster is closed for applications.
479
     *
480
     * @return boolean
481
     */
482
    public function isClosed(): bool
483
    {
484
        return $this->published
485
            && $this->open_date_time !== null
486
            && $this->close_date_time !== null
487
            && $this->open_date_time->isPast()
488
            && $this->close_date_time->isPast();
489
    }
490
491
    /**
492
     * Calculate the remaining time a Job Poster is open.
493
     *
494
     * @return string
495
     */
496
    public function timeRemaining(): string
497
    {
498
        $interval = $this->close_date_time->diff(Date::now());
499
500
        $d = $interval->d;
501
        $h = $interval->h;
502
        $m = $interval->i;
503
        $s = $interval->s;
504
505
        if ($d > 0) {
506
            $unit = 'day';
507
            $count = $d;
508
        } elseif ($h > 0) {
509
            $unit = 'hour';
510
            $count = $h;
511
        } elseif ($m > 0) {
512
            $unit = 'minute';
513
            $count = $m;
514
        } else {
515
            $unit = 'second';
516
            $count = $s;
517
        }
518
519
        $key = "common/time.$unit";
520
521
        return Lang::choice($key, $count);
522
    }
523
524
    /**
525
     * Return the current status for the Job Poster.
526
     * Possible values are "draft", "submitted", "published" and "closed".
527
     *
528
     * @return string
529
     */
530
    public function status(): string
531
    {
532
        $status = 'draft';
533
        if ($this->isOpen()) {
534
            $status = 'published';
535
        } elseif ($this->isClosed()) {
536
            $status = 'closed';
537
        } elseif ($this->review_requested_at !== null) {
538
            $status = 'submitted';
539
        } else {
540
            $status = 'draft';
541
        }
542
543
        return $status;
544
    }
545
546
    /**
547
     * FIXME:
548
     * Return a calculated job status id.
549
     * For now these ids represent the following:
550
     *    1 = Draft
551
     *    2 = Review requested
552
     *    3 = Approved
553
     *    4 = Open
554
     *    5 = Closed
555
     * These statuses needs an immenent refactoring, so I'm not going to create
556
     * a lookup table for them yet.
557
     * TODO: When this is rebuilt, make sure to change matching JobStatus code in
558
     *    resources\assets\js\models\lookupConstants.ts
559
     *
560
     * @return integer
561
     */
562
    public function getJobStatusIdAttribute()
0 ignored issues
show
introduced by
Method \App\Models\JobPoster::getJobStatusIdAttribute() does not have native return type hint for its return value but it should be possible to add it based on @return annotation "integer".
Loading history...
563
    {
564
        $now = new Date();
565
        if ($this->review_requested_at === null) {
566
            return 1; // Draft.
567
        } elseif ($this->published_at === null) {
568
            return 2; // Review requested, but not approved.
569
        } elseif ($this->open_date_time === null || $this->open_date_time >$now) {
570
            return 3; // Approved, but not open.
571
        } elseif ($this->close_date_time === null || $this->close_date_time > $now) {
572
            // Approved and currently open.
573
            return 4; // Open.
574
        } else {
575
            // Published and close date has passed.
576
            return 5; // Closed.
577
        }
578
    }
579
580
    /**
581
     * The database model stores a foreign id to the classification table,
582
     * but to simplify the API, this model simply returns the key as classification_code.
583
     *
584
     * @return string|null
585
     */
586
    public function getClassificationCodeAttribute()
0 ignored issues
show
introduced by
Method \App\Models\JobPoster::getClassificationCodeAttribute() does not have native return type hint for its return value but it should be possible to add it based on @return annotation "string|null".
Loading history...
587
    {
588
        if ($this->classification_id !== null) {
589
            return $this->classification->key;
590
        }
591
        return null;
592
    }
593
594
    /**
595
     *
596
     * Get the full government classification message.
597
     *
598
     * @return string|null
599
     */
600
    public function getClassificationMessageAttribute()
0 ignored issues
show
introduced by
Method \App\Models\JobPoster::getClassificationMessageAttribute() does not have native return type hint for its return value but it should be possible to add it based on @return annotation "string|null".
Loading history...
601
    {
602
        if ($this->classification_id !== null && $this->classification_level !== null) {
603
            return $this->classification->key . '-0' . $this->classification_level;
604
        }
605
        return null;
606
    }
607
608
    /**
609
     * Return the array of values used to represent this object in an api response.
610
     * This array should contain no nested objects (besides translations).
611
     *
612
     * @return mixed[]
613
     */
614
    public function toApiArray(): array
615
    {
616
        $jobWithTranslations = array_merge($this->toArray(), $this->getTranslationsArray());
617
        return $jobWithTranslations;
618
    }
619
}
620