JobPoster   B
last analyzed

Complexity

Total Complexity 48

Size/Duplication

Total Lines 520
Duplicated Lines 0 %

Test Coverage

Coverage 92.06%

Importance

Changes 0
Metric Value
wmc 48
eloc 226
c 0
b 0
f 0
dl 0
loc 520
rs 8.5599
ccs 58
cts 63
cp 0.9206

36 Methods

Rating   Name   Duplication   Size   Complexity  
A telework_allowed_frequency() 0 3 1
A province() 0 3 1
A getClassificationMessageAttribute() 0 6 3
A overtime_requirement() 0 3 1
A job_applications() 0 3 1
A job_skill_level() 0 3 1
A language_requirement() 0 3 1
A submitted_applications_count() 0 3 1
A job_term() 0 3 1
A timeRemaining() 0 26 4
A department() 0 3 1
A hr_advisors() 0 5 1
A applyBy() 0 14 2
A classification() 0 3 1
A manager() 0 3 1
A criteria() 0 3 1
A isOpen() 0 3 1
A getClassificationCodeAttribute() 0 6 2
A submitted_applications() 0 4 1
A job_poster_status_histories() 0 3 1
A talent_stream_category() 0 3 1
A job_poster_questions() 0 3 1
A job_poster_status() 0 3 1
A security_clearance() 0 3 1
A talent_stream() 0 3 1
A displayStatus() 0 3 2
A isInStrategicResponseDepartment() 0 3 2
A comments() 0 3 1
A isVisibleToHr() 0 3 1
A flexible_hours_frequency() 0 3 1
A job_poster_key_tasks() 0 3 1
A travel_requirement() 0 3 1
A isPublic() 0 5 3
A isEditable() 0 4 1
A isClosed() 0 4 2
A resolveRouteBinding() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like JobPoster often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use JobPoster, and based on these observations, apply Extract Interface, too.

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

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

629
        return $this->department && $this->department->id === /** @scrutinizer ignore-call */ config('app.strategic_response_department_id');
Loading history...
630
    }
631
}
632