Passed
Push — feature/job-builder/tasks ( 53128f )
by Chris
14:35 queued 01:21
created

JobPoster::timeRemaining()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 4.0186

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 19
c 1
b 0
f 0
dl 0
loc 26
ccs 17
cts 19
cp 0.8947
rs 9.6333
cc 4
nc 4
nop 0
crap 4.0186
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 Illuminate\Notifications\Notifiable;
12
use Illuminate\Support\Facades\App;
13
use Illuminate\Support\Facades\Lang;
14
use Jenssegers\Date\Date;
15
use \Backpack\CRUD\CrudTrait;
16
use Astrotomic\Translatable\Translatable;
17
18
/**
19
 * Class JobPoster
20
 *
21
 * @property int $id
22
 * @property int $job_term_id
23
 * @property int $term_qty
24
 * @property \Jenssegers\Date\Date $open_date_time
25
 * @property \Jenssegers\Date\Date $close_date_time
26
 * @property \Jenssegers\Date\Date $start_date_time
27
 * @property \Jenssegers\Date\Date $review_requested_at
28
 * @property \Jessengers\Date\Date $published_at
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 string $classification
35
 * @property string $classification_code
36
 * @property int $classification_level
37
 * @property int $security_clearance_id
38
 * @property int $language_requirement_id
39
 * @property boolean $remote_work_allowed
40
 * @property int $manager_id
41
 * @property boolean $published
42
 * @property int $team_size
43
 * @property array $work_env_features This should be an array of boolean flags for features, ie json of shape {[feature: string]: boolean}
44
 * @property int $fast_vs_steady
45
 * @property int $horizontal_vs_vertical
46
 * @property int $experimental_vs_ongoing
47
 * @property int $citizen_facing_vs_back_office
48
 * @property int $collaborative_vs_independent
49
 * @property int $telework_allowed_frequency_id
50
 * @property int $flexible_hours_frequency_id
51
 * @property \Jenssegers\Date\Date $created_at
52
 * @property \Jenssegers\Date\Date $updated_at
53
 *
54
 * @property int $submitted_applications_count
55
 *
56
 * @property \App\Models\Lookup\Department $department
57
 * @property \App\Models\Lookup\JobTerm $job_term
58
 * @property \App\Models\Lookup\LanguageRequirement $language_requirement
59
 * @property \App\Models\Manager $manager
60
 * @property \App\Models\Lookup\Province $province
61
 * @property \App\Models\Lookup\SecurityClearance $security_clearance
62
 * @property \Illuminate\Database\Eloquent\Collection $criteria
63
 * @property \Illuminate\Database\Eloquent\Collection $job_applications
64
 * @property \Illuminate\Database\Eloquent\Collection $job_poster_key_tasks
65
 * @property \Illuminate\Database\Eloquent\Collection $job_poster_questions
66
 * @property \Illuminate\Database\Eloquent\Collection $job_poster_translations
67
 * @property \Illuminate\Database\Eloquent\Collection $submitted_applications
68
 * @property \App\Models\Lookup\Frequency $telework_allowed_frequency
69
 * @property \App\Models\Lookup\Frequency $flexible_hours_frequency
70
 *
71
 * Localized Properties:
72
 * @property string $city
73
 * @property string $title
74
 * @property string $dept_impact
75
 * @property string $team_impact
76
 * @property string $hire_impact
77
 * @property string $branch
78
 * @property string $division
79
 * @property string $education
80
 * @property string $work_env_description
81
 * @property string $culture_summary
82
 * @property string $culture_special
83
 *
84
 * Methods
85
 * @method boolean isOpen()
86
 * @method string timeRemaining()
87
 * @method mixed[] toApiArray()
88
 */
89
class JobPoster extends BaseModel
90
{
91
    use CrudTrait;
0 ignored issues
show
introduced by
The trait Backpack\CRUD\CrudTrait requires some properties which are not provided by App\Models\JobPoster: $Type, $fakeColumns
Loading history...
92
    use Translatable;
93
    use Notifiable;
0 ignored issues
show
introduced by
The trait Illuminate\Notifications\Notifiable requires some properties which are not provided by App\Models\JobPoster: $email, $phone_number
Loading history...
94
95
    const DATE_FORMAT = [
96
        'en' => 'M jS, Y',
97
        'fr' => 'd M Y',
98
    ];
99
    const TIME_FORMAT = [
100
        'en' => 'h:i A T',
101
        'fr' => 'H \h i T',
102
    ];
103
    const TIMEZONE = 'America/Toronto';
104
105
    /**
106
     * @var string[] $translatedAttributes
107
     */
108
    public $translatedAttributes = [
109
        'city',
110
        'title',
111
        'dept_impact',
112
        'team_impact',
113
        'hire_impact',
114
        'branch',
115
        'division',
116
        'education',
117
        'work_env_description',
118
        'culture_summary',
119
        'culture_special',
120
    ];
121
122
    /**
123
     * @var string[] $casts
124
     */
125
    protected $casts = [
126
        'job_term_id' => 'int',
127
        'department_id' => 'int',
128
        'province_id' => 'int',
129
        'salary_min' => 'int',
130
        'salary_max' => 'int',
131
        'noc' => 'int',
132
        'classification_code' => 'string',
133
        'classification_level' => 'int',
134
        'security_clearance_id' => 'int',
135
        'language_requirement_id' => 'int',
136
        'remote_work_allowed' => 'boolean',
137
        'manager_id' => 'int',
138
        'published' => 'boolean',
139
        'team_size' => 'int',
140
        'work_env_features' => 'array',
141
        'fast_vs_steady' => 'int',
142
        'horizontal_vs_vertical' => 'int',
143
        'experimental_vs_ongoing' => 'int',
144
        'citizen_facing_vs_back_office' => 'int',
145
        'collaborative_vs_independent' => 'int',
146
        'telework_allowed_frequency_id' => 'int',
147
        'flexible_hours_frequency_id' => 'int',
148
    ];
149
150
    /**
151
     * @var string[] $dates
152
     */
153
    protected $dates = [
154
        'open_date_time',
155
        'close_date_time',
156
        'start_date_time',
157
        'review_requested_at',
158
        'published_at'
159
    ];
160
161
    /**
162
     * @var string[] $fillable
163
     */
164
    protected $fillable = [
165
        'job_term_id',
166
        'term_qty',
167
        'open_date_time',
168
        'close_date_time',
169
        'start_date_time',
170
        'department_id',
171
        'province_id',
172
        'salary_min',
173
        'salary_max',
174
        'noc',
175
        'classification',
176
        'classification_code',
177
        'classification_level',
178
        'security_clearance_id',
179
        'language_requirement_id',
180
        'remote_work_allowed',
181
        'published',
182
        'team_size',
183
        'work_env_features',
184
        'fast_vs_steady',
185
        'horizontal_vs_vertical',
186
        'experimental_vs_ongoing',
187
        'citizen_facing_vs_back_office',
188
        'collaborative_vs_independent',
189
        'telework_allowed_frequency_id',
190
        'flexible_hours_frequency_id',
191
    ];
192
193
    /**
194
     * The attributes that should be visible in arrays.
195
     * In this case, it blocks loaded relations from appearing.
196
     *
197
     * @var array
0 ignored issues
show
introduced by
@var annotation of property \App\Models\JobPoster::$visible does not specify type hint for its items.
Loading history...
198
     */
199
    protected $visible = [
200
        'id',
201
        'manager_id',
202
        'term_qty',
203
        'open_date_time',
204
        'close_date_time',
205
        'start_date_time',
206
        'department_id',
207
        'province_id',
208
        'salary_min',
209
        'salary_max',
210
        'noc',
211
        'classification_code',
212
        'classification_level',
213
        'security_clearance_id',
214
        'language_requirement_id',
215
        'remote_work_allowed',
216
        'published_at',
217
        'published',
218
        'review_requested_at',
219
        'team_size',
220
        'work_env_features',
221
        'fast_vs_steady',
222
        'horizontal_vs_vertical',
223
        'experimental_vs_ongoing',
224
        'citizen_facing_vs_back_office',
225
        'collaborative_vs_independent',
226
        'telework_allowed_frequency_id',
227
        'flexible_hours_frequency_id',
228
    ];
229
230
    /**
231
     * @var mixed[] $dispatchesEvents
232
     */
233
    protected $dispatchesEvents = [
234
        'saved' => JobSaved::class,
235
    ];
236
237
    // @codeCoverageIgnoreStart
238
    public function department() // phpcs:ignore
239
    {
240
        return $this->belongsTo(\App\Models\Lookup\Department::class);
241
    }
242
243
    public function job_term() // phpcs:ignore
244
    {
245
        return $this->belongsTo(\App\Models\Lookup\JobTerm::class);
246
    }
247
248
    public function language_requirement() // phpcs:ignore
249
    {
250
        return $this->belongsTo(\App\Models\Lookup\LanguageRequirement::class);
251
    }
252
253
    public function manager() // phpcs:ignore
254
    {
255
        return $this->belongsTo(\App\Models\Manager::class);
256
    }
257
258
    public function province() // phpcs:ignore
259
    {
260
        return $this->belongsTo(\App\Models\Lookup\Province::class);
261
    }
262
263
    public function security_clearance() // phpcs:ignore
264
    {
265
        return $this->belongsTo(\App\Models\Lookup\SecurityClearance::class);
266
    }
267
268
    public function criteria() // phpcs:ignore
269
    {
270
        return $this->hasMany(\App\Models\Criteria::class);
271
    }
272
273
    public function job_applications() // phpcs:ignore
274
    {
275
        return $this->hasMany(\App\Models\JobApplication::class);
276
    }
277
278
    public function job_poster_key_tasks() // phpcs:ignore
279
    {
280
        return $this->hasMany(\App\Models\JobPosterKeyTask::class);
281
    }
282
283
    public function job_poster_questions() // phpcs:ignore
284
    {
285
        return $this->hasMany(\App\Models\JobPosterQuestion::class);
286
    }
287
288
    public function job_poster_translations() // phpcs:ignore
289
    {
290
        return $this->hasMany(\App\Models\JobPosterTranslation::class);
291
    }
292
293
    public function telework_allowed_frequency() // phpcs:ignore
294
    {
295
        return $this->belongsTo(\App\Models\Lookup\Frequency::class);
296
    }
297
298
    public function flexible_hours_frequency() // phpcs:ignore
299
    {
300
        return $this->belongsTo(\App\Models\Lookup\Frequency::class);
301
    }
302
303
    // Artificial Relations
304
305
    /**
306
     * Get all of the Job Applications submitted to this
307
     * Job Poster.
308
     *
309
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
310
     */
311
    public function submitted_applications() // phpcs:ignore
312
    {
313
        return $this->hasMany(\App\Models\JobApplication::class)->whereDoesntHave('application_status', function ($query) : void {
314
            $query->where('name', 'draft');
315
        });
316
    }
317
318
    // Overrides
319
320
    /**
321
     * Retrieve the model for a bound value.
322
     * Seems to be a useful workaround for providing submitted_applications_count
323
     * to any bound routes that receive a jobPoster instance without using the
324
     * withCount property on the model itself.
325
     * See https://github.com/laravel/framework/issues/23957 for more info.
326
     *
327
     * @param mixed $value Value used to retrieve the model instance.
328
     *
329
     * @return \Illuminate\Database\Eloquent\Model|null
330
     */
331
    public function resolveRouteBinding($value) // phpcs:ignore
332
    {
333
        return $this->withCount('submitted_applications')->where('id', $value)->first() ?? abort(404);
334
    }
335
336
    // @codeCoverageIgnoreEnd
337
    // Accessors.
338
339
    /**
340
     * The classification property is deprecated. To ensure
341
     * Twig template consistency, check for populated
342
     * classification_code and classification_level and return
343
     * the combination of those instead.
344
     *
345
     * @param mixed $value Incoming attribute value.
346
     *
347
     * @return string|null
348
     */
349 10
    public function getClassificationAttribute($value)
0 ignored issues
show
introduced by
Method \App\Models\JobPoster::getClassificationAttribute() does not have return type hint for its return value but it should be possible to add it based on @return annotation "string|null".
Loading history...
350
    {
351 10
        if (!empty($this->classification_code) && !empty($this->classification_level)) {
352 10
            return "$this->classification_code-$this->classification_level";
353
        } else {
354 1
            return $value;
355
        }
356
    }
357
358
    // Mutators.
359
360
    /**
361
     * Intercept setting the "published" attribute, and set the
362
     * "published_at" timestamp if true.
363
     *
364
     * @param mixed $value Incoming value for the 'published' attribute.
365
     *
366
     * @return void
367
     */
368 40
    public function setPublishedAttribute($value) : void
369
    {
370 40
        if ($value && $this->open_date_time->isPast()) {
371 23
            $this->attributes['published_at'] = new Date();
372 36
        } elseif ($value && $this->open_date_time->isFuture()) {
373 1
            $this->attributes['published_at'] = $this->open_date_time;
374
        }
375 40
        $this->attributes['published'] = $value;
376 40
    }
377
378
    // Methods
379 6
    public function submitted_applications_count() //phpcs:ignore
380
    {
381 6
        return $this->submitted_applications()->count();
382
    }
383
384
    /**
385
     * Formatted and localized date and time the Job Poster closes.
386
     *
387
     * @return string[]
388
     */
389 1
    public function applyBy() : array
390
    {
391 1
        $localCloseDate = new Date($this->close_date_time); // This initializes the date object in UTC time
392 1
        $localCloseDate->setTimezone(new \DateTimeZone(self::TIMEZONE)); // Then set the time zone for display
393
        $displayDate = [
394 1
            'date' => $localCloseDate->format(self::DATE_FORMAT[App::getLocale()]),
395 1
            'time' => $localCloseDate->format(self::TIME_FORMAT[App::getLocale()])
396
        ];
397
398 1
        if (App::isLocale('fr')) {
399
            $displayDate['time'] = str_replace(['EST', 'EDT'], ['HNE', 'HAE'], $displayDate['time']);
400
        }
401
402 1
        return $displayDate;
403
    }
404
405
    /**
406
     * Return whether the Job is Open or Closed.
407
     * Used by the Admin Portal JobPosterCrudController.
408
     *
409
     * @return string
410
     */
411
    public function displayStatus() : string
412
    {
413
        return $this->isOpen() ? 'Open' : 'Closed';
414
    }
415
416
    /**
417
     * Check if a Job Poster is open for applications.
418
     *
419
     * @return boolean
420
     */
421 6
    public function isOpen() : bool
422
    {
423 6
        return $this->published
424 6
            && $this->open_date_time->isPast()
425 6
            && $this->close_date_time->isFuture();
426
    }
427
428
    /**
429
     * Check if a Job Poster is closed for applications.
430
     *
431
     * @return boolean
432
     */
433 4
    public function isClosed() : bool
434
    {
435 4
        return $this->published
436 4
            && $this->open_date_time->isPast()
437 4
            && $this->close_date_time->isPast();
438
    }
439
440
    /**
441
     * Calculate the remaining time a Job Poster is open.
442
     *
443
     * @return string
444
     */
445 1
    public function timeRemaining() : string
446
    {
447 1
        $interval = $this->close_date_time->diff(Date::now());
448
449 1
        $d = $interval->d;
450 1
        $h = $interval->h;
451 1
        $m = $interval->i;
452 1
        $s = $interval->s;
453
454 1
        if ($d > 0) {
455 1
            $unit = 'day';
456 1
            $count = $d;
457 1
        } elseif ($h > 0) {
458 1
            $unit = 'hour';
459 1
            $count = $h;
460 1
        } elseif ($m > 0) {
461 1
            $unit = 'minute';
462 1
            $count = $m;
463
        } else {
464
            $unit = 'second';
465
            $count = $s;
466
        }
467
468 1
        $key = "common/time.$unit";
469
470 1
        return Lang::choice($key, $count);
471
    }
472
473
    /**
474
     * Return the current status for the Job Poster.
475
     * Possible values are "draft", "submitted", "published" and "closed".
476
     *
477
     * @return string
478
     */
479 3
    public function status() : string
480
    {
481 3
        $status = 'draft';
482 3
        if ($this->isOpen()) {
483 1
            $status = 'published';
484 3
        } elseif ($this->isClosed()) {
485 1
            $status = 'closed';
486 3
        } elseif ($this->review_requested_at !== null) {
487 3
            $status = 'submitted';
488
        } else {
489 1
            $status = 'draft';
490
        }
491
492 3
        return $status;
493
    }
494
495
    /**
496
     * Return the array of values used to represent this object in an api response.
497
     * This array should contain no nested objects (besides translations).
498
     *
499
     * @return mixed[]
500
     */
501 4
    public function toApiArray(): array
502
    {
503 4
        $jobWithTranslations = array_merge($this->toArray(), $this->getTranslationsArray());
504 4
        return $jobWithTranslations;
505
    }
506
}
507