Passed
Push — dev ( 72e705...fb30f4 )
by Chris
05:36 queued 13s
created

JobPoster::setPublishedAttribute()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 11
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 3
nc 4
nop 1
crap 3
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 \Jenssegers\Date\Date $created_at
57
 * @property \Jenssegers\Date\Date $updated_at
58
 *
59
 * @property int $submitted_applications_count
60
 *
61
 * @property \App\Models\Lookup\Department $department
62
 * @property \App\Models\Lookup\JobTerm $job_term
63
 * @property \App\Models\Lookup\LanguageRequirement $language_requirement
64
 * @property \App\Models\Manager $manager
65
 * @property \App\Models\Lookup\Province $province
66
 * @property \App\Models\Lookup\SecurityClearance $security_clearance
67
 * @property \Illuminate\Database\Eloquent\Collection $criteria
68
 * @property \Illuminate\Database\Eloquent\Collection $job_applications
69
 * @property \Illuminate\Database\Eloquent\Collection $job_poster_key_tasks
70
 * @property \Illuminate\Database\Eloquent\Collection $job_poster_questions
71
 * @property \Illuminate\Database\Eloquent\Collection $job_poster_translations
72
 * @property \Illuminate\Database\Eloquent\Collection $submitted_applications
73
 * @property \Illuminate\Database\Eloquent\Collection $hr_advisors
74
 * @property \App\Models\Lookup\Frequency $telework_allowed_frequency
75
 * @property \App\Models\Lookup\Frequency $flexible_hours_frequency
76
 * @property \App\Models\Lookup\JobPosterStatus $job_poster_status
77
 *
78
 * Localized Properties:
79
 * @property string $city
80
 * @property string $title
81
 * @property string $dept_impact
82
 * @property string $team_impact
83
 * @property string $hire_impact
84
 * @property string $division
85
 * @property string $education
86
 * @property string $work_env_description
87
 * @property string $culture_summary
88
 * @property string $culture_special
89
 *
90
 * Methods
91
 * @method boolean isOpen()
92
 * @method boolean isClosed()
93
 * @method boolean isVisibleToHr()
94
 * @method boolean isPublic()
95
 * @method boolean isEditable()
96
 * @method string timeRemaining()
97
 * @method mixed[] toApiArray()
98
 *
99
 * Computed Properties
100
 * @property string|null $classification_code
101
 * @property string|null $classification_message
102
 */
103
class JobPoster extends BaseModel
104
{
105
    use CrudTrait;
106
    use HasTranslations;
107
    use Notifiable;
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[] $translatable
121
     */
122
    public $translatable = [
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
        'internal_only' => '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
        'process_number' => 'string',
164
        'priority_clearance_number' => 'int'
165
    ];
166
167
    /**
168
     * @var string[] $dates
169
     */
170
    protected $dates = [
171
        'open_date_time',
172
        'close_date_time',
173
        'start_date_time',
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
        'internal_only',
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
        'city',
213
        'title',
214
        'dept_impact',
215
        'team_impact',
216
        'hire_impact',
217
        'division',
218
        'education',
219
        'work_env_description',
220
        'culture_summary',
221
        'culture_special',
222
        'job_poster_status_id', // This really shouldn't be mass-editable, but its necesary for the admin crud portal to set it.
223
    ];
224
225
    /**
226
     * The attributes that should be visible in arrays.
227
     * In this case, it blocks loaded relations from appearing.
228
     *
229
     * @var array
230
     */
231
    protected $visible = [
232
        'id',
233
        'manager_id',
234
        'chosen_lang',
235
        'term_qty',
236
        'open_date_time',
237
        'close_date_time',
238
        'start_date_time',
239
        'department_id',
240
        'province_id',
241
        'salary_min',
242
        'salary_max',
243
        'noc',
244
        'security_clearance_id',
245
        'language_requirement_id',
246
        'remote_work_allowed',
247
        'team_size',
248
        'work_env_features',
249
        'fast_vs_steady',
250
        'horizontal_vs_vertical',
251
        'experimental_vs_ongoing',
252
        'citizen_facing_vs_back_office',
253
        'collaborative_vs_independent',
254
        'telework_allowed_frequency_id',
255
        'flexible_hours_frequency_id',
256
        'travel_requirement_id',
257
        'overtime_requirement_id',
258
        'process_number',
259
        'priority_clearance_number',
260
        'loo_issuance_date',
261
        'classification_id',
262
        'classification_level',
263
        'city',
264
        'title',
265
        'dept_impact',
266
        'team_impact',
267
        'hire_impact',
268
        'division',
269
        'education',
270
        'work_env_description',
271
        'culture_summary',
272
        'culture_special',
273
        'job_poster_status_id',
274
        'created_at',
275
    ];
276
277
    /**
278
     * The accessors to append to the model's array form.
279
     *
280
     * @var string[] $appends
281
     */
282
    protected $appends = [
283
        'classification_code',
284
        'classification_message',
285
    ];
286
287
    /**
288
     * Eager loaded relationships by default.
289
     *
290
     * @var string[] $with
291
     */
292
    protected $with = [
293
        'criteria',
294
        'manager'
295
    ];
296
297
    /**
298
     * @var mixed[] $dispatchesEvents
299
     */
300
    protected $dispatchesEvents = [
301
        'saved' => JobSaved::class,
302
    ];
303
304
    // @codeCoverageIgnoreStart
305
    public function department() // phpcs:ignore
306
    {
307
        return $this->belongsTo(\App\Models\Lookup\Department::class);
308
    }
309
310
    public function job_term() // phpcs:ignore
311
    {
312
        return $this->belongsTo(\App\Models\Lookup\JobTerm::class);
313
    }
314
315
    public function language_requirement() // phpcs:ignore
316
    {
317
        return $this->belongsTo(\App\Models\Lookup\LanguageRequirement::class);
318
    }
319
320
    public function manager() // phpcs:ignore
321
    {
322
        return $this->belongsTo(\App\Models\Manager::class);
323
    }
324
325
    public function province() // phpcs:ignore
326
    {
327
        return $this->belongsTo(\App\Models\Lookup\Province::class);
328
    }
329
330
    public function security_clearance() // phpcs:ignore
331
    {
332
        return $this->belongsTo(\App\Models\Lookup\SecurityClearance::class);
333
    }
334
335
    public function criteria() // phpcs:ignore
336
    {
337
        return $this->hasMany(\App\Models\Criteria::class);
338
    }
339
340
    public function hr_advisors() // phpcs:ignore
341
    {
342
        return $this->belongsToMany(
343
            \App\Models\HrAdvisor::class,
344
            'claimed_jobs'
345
        );
346
    }
347
348
    public function job_applications() // phpcs:ignore
349 10
    {
350
        return $this->hasMany(\App\Models\JobApplication::class);
351 10
    }
352 10
353
    public function job_poster_key_tasks() // phpcs:ignore
354 1
    {
355
        return $this->hasMany(\App\Models\JobPosterKeyTask::class)->orderBy('order', 'asc');
356
    }
357
358
    public function job_poster_questions() // phpcs:ignore
359
    {
360
        return $this->hasMany(\App\Models\JobPosterQuestion::class);
361
    }
362
363
    public function telework_allowed_frequency() // phpcs:ignore
364
    {
365
        return $this->belongsTo(\App\Models\Lookup\Frequency::class);
366
    }
367
368 45
    public function flexible_hours_frequency() // phpcs:ignore
369
    {
370 45
        return $this->belongsTo(\App\Models\Lookup\Frequency::class);
371 24
    }
372 40
373 1
    public function travel_requirement() // phpcs:ignore
374
    {
375 45
        return $this->belongsTo(\App\Models\Lookup\TravelRequirement::class);
376 45
    }
377
378
    public function overtime_requirement() // phpcs:ignore
379 6
    {
380
        return $this->belongsTo(\App\Models\Lookup\OvertimeRequirement::class);
381 6
    }
382
383
    public function classification() // phpcs:ignore
384
    {
385
        return $this->belongsTo(\App\Models\Classification::class);
386
    }
387
388
    public function comments() // phpcs:ignore
389 1
    {
390
        return $this->hasMany(\App\Models\Comment::class);
391 1
    }
392 1
393
    public function job_poster_status() // phpcs:ignore
394 1
    {
395 1
        return $this->belongsTo(\App\Models\Lookup\JobPosterStatus::class);
396
    }
397
398 1
    public function job_poster_status_histories() // phpcs:ignore
399
    {
400
        return $this->hasMany(\App\Models\JobPosterStatusHistory::class);
401
    }
402 1
403
    // @codeCoverageIgnoreEnd
404
    /* Artificial Relations */
405
406
    /**
407
     * Get all of the Job Applications submitted to this
408
     * Job Poster.
409
     *
410
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
411
     */
412
    public function submitted_applications() // phpcs:ignore
413
    {
414
        return $this->hasMany(\App\Models\JobApplication::class)->whereDoesntHave('application_status', function ($query): void {
415
            $query->where('name', 'draft');
416
        });
417
    }
418
419
    /* Overrides */
420
421 6
    /**
422
     * Retrieve the model for a bound value.
423 6
     * Seems to be a useful workaround for providing submitted_applications_count
424 6
     * to any bound routes that receive a jobPoster instance without using the
425 6
     * withCount property on the model itself.
426
     * See https://github.com/laravel/framework/issues/23957 for more info.
427
     *
428
     * @param mixed $value Value used to retrieve the model instance.
429
     *
430
     * @return \Illuminate\Database\Eloquent\Model|null
431
     */
432
    public function resolveRouteBinding($value) // phpcs:ignore
433 4
    {
434
        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

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