Completed
Push — feature/screening-plan ( 46ccc1...3f87fd )
by Tristan
14:07 queued 07:47
created

JobController::store()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3.0261

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 27
rs 9.7998
c 0
b 0
f 0
ccs 12
cts 14
cp 0.8571
cc 3
nc 4
nop 2
crap 3.0261
1
<?php
2
0 ignored issues
show
Coding Style introduced by
Missing file doc comment
Loading history...
3
namespace App\Http\Controllers;
4
5
use Illuminate\Support\Facades\Lang;
6
use Illuminate\Support\Facades\Auth;
7
use Illuminate\Http\RedirectResponse;
8
use Illuminate\Http\Request;
9
use Illuminate\Http\Response;
10
use Illuminate\View\View;
11
use App\Http\Controllers\Controller;
12
use Carbon\Carbon;
13
use App\Models\JobPoster;
14
use App\Models\Lookup\JobTerm;
15
use App\Models\Lookup\Province;
16
use App\Models\Lookup\SecurityClearance;
17
use App\Models\Lookup\LanguageRequirement;
18
use App\Models\Lookup\CitizenshipDeclaration;
19
use App\Models\Lookup\Department;
20
use App\Models\Lookup\SkillLevel;
21
use App\Models\Lookup\CriteriaType;
22
use App\Models\Lookup\VeteranStatus;
23
use App\Models\JobApplication;
24
use App\Models\Criteria;
25
use App\Models\Skill;
26
use App\Models\JobPosterQuestion;
27
use App\Models\JobPosterKeyTask;
28
use App\Services\Validation\JobPosterValidator;
29
use Jenssegers\Date\Date;
30
31
class JobController extends Controller
0 ignored issues
show
Coding Style introduced by
Missing doc comment for class JobController
Loading history...
32
{
33
    /**
34
     * Display a listing of JobPosters.
35
     *
36
     * @return \Illuminate\Http\Response
37
     */
38
    public function index()
39
    {
40
        $now = Carbon::now();
41
        //Find published jobs that are currently open for applications
42
        $jobs = JobPoster::where('open_date_time', '<=', $now)
43
            ->where('close_date_time', '>=', $now)
44
            ->where('published', true)
45
            ->get();
46
        $jobs->load('manager.work_environment');
47
        return view('applicant/job_index', ['job_index' => Lang::get('applicant/job_index'),
1 ignored issue
show
Bug Best Practice introduced by
The expression return view('applicant/j...ex'), 'jobs' => $jobs)) returns the type Illuminate\View\View which is incompatible with the documented return type Illuminate\Http\Response.
Loading history...
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
48
            'jobs' => $jobs]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
49
    }
50
51
    /**
52
     * Display a listing of a manager's JobPosters.
53
     *
54
     * @return \Illuminate\Http\Response
55
     */
56 4
    public function managerIndex()
57
    {
58 4
        $manager = Auth::user()->manager;
0 ignored issues
show
Bug introduced by
Accessing manager on the interface Illuminate\Contracts\Auth\Authenticatable suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
59
60 4
        $veteran_applications = [];
61 4
        $citizen_applications = [];
62 4
        $other_applications = [];
63
64 4
        foreach($manager->job_posters as $job) {
0 ignored issues
show
Coding Style introduced by
Expected "foreach (...) {\n"; found "foreach(...) {\n"
Loading history...
65 4
            $job->submitted_applications->load(['veteran_status', 'citizenship_declaration']);
66 4
            $veteran_applications[$job->id] = $job->submitted_applications->filter(function($application) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
67
                return $application->veteran_status->name !== "none" &&
68
                        $application->citizenship_declaration->name === "citizen";
69 4
            });
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
70 4
            $citizen_applications[$job->id] = $job->submitted_applications->filter(function($application) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
71
                return $application->veteran_status->name === "none" &&
72
                        $application->citizenship_declaration->name === "citizen";
73 4
            });
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
74 4
            $other_applications[$job->id] = $job->submitted_applications->filter(function($application) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
75
                return $application->citizenship_declaration->name !== "citizen";
76 4
            });
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
77
        }
78
79 4
        return view('manager/job_index', [
1 ignored issue
show
Bug Best Practice introduced by
The expression return view('manager/job...> $other_applications)) returns the type Illuminate\View\View which is incompatible with the documented return type Illuminate\Http\Response.
Loading history...
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
80 4
            "manager_job_index" => [
81
                "title" => "My Job Posts"
82
            ],
83 4
            'jobs' => $manager->job_posters,
84 4
            'veteran_applications' => $veteran_applications,
85 4
            'citizen_applications' => $citizen_applications,
86 4
            'other_applications' => $other_applications,
87
        ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
88
    }
89
90
    /**
91
     * Display the specified job poster.
92
     *
93
     * @param \Illuminate\Http\Request $request   Incoming request object.
94
     * @param \App\Models\JobPoster    $jobPoster Job Poster object.
95
     *
96
     * @return \Illuminate\View\View
97
     */
98
    public function show(Request $request, JobPoster $jobPoster) : View
99
    {
100
        //TODO: Improve workplace photos, and reference them in template direction from WorkEnvironment model
101
        $workplacePhotos = [];
102
        foreach ($jobPoster->manager->work_environment->workplace_photo_captions as $photoCaption) {
103
            $workplacePhotos[] = [
104
                'description' => $photoCaption->description,
105
                'url' => '/images/user.png'
106
            ];
107
        }
108
109
        //TODO: replace route('manager.show',manager.id) in templates with link using slug
110
111
        $criteria = [
112
            'essential' => $jobPoster->criteria->filter(
113
                function ($value, $key) {
114
                    return $value->criteria_type->name == 'essential';
115
                }
116
            ),
117
            'asset' => $jobPoster->criteria->filter(
118
                function ($value, $key) {
119
                    return $value->criteria_type->name == 'asset';
120
                }
121
            ),
122
        ];
123
124
        return view(
125
            'applicant/job_post',
126
            [
127
                'job_post' => Lang::get('applicant/job_post'),
128
                'manager' => $jobPoster->manager,
129
                'manager_profile_photo_url' => '/images/user.png', //TODO get real photo
130
                'team_culture' => $jobPoster->manager->team_culture,
131
                'work_environment' => $jobPoster->manager->work_environment,
132
                'workplace_photos' => $workplacePhotos,
133
                'job' => $jobPoster,
134
                'criteria' => $criteria,
135
                'skill_template' => Lang::get('common/skills'),
136
            ]
137
        );
138
    }
139
140
    /**
141
     * Display the form for creating a new Job Poster
142
     *
143
     * @param \Illuminate\Http\Request $request Incoming request object.
144
     *
145
     * @return \Illuminate\View\View Job Create view
146
     */
147
    public function create(Request $request) : View
148
    {
149
        return $this->populateCreateView($request);
150
    }
151
152
    /**
153
     * Display the form for editing an existing Job Poster
154
     *
155
     * @param \Illuminate\Http\Request $request   Incoming request object.
156
     * @param \App\Models\JobPoster    $jobPoster Job Poster object.
157
     *
158
     * @return \Illuminate\View\View Job Create view
159
     */
160 2
    public function edit(Request $request, JobPoster $jobPoster) : View
161
    {
162 2
        return $this->populateCreateView($request, $jobPoster);
163
    }
164
165
    /**
166
     * Get the manager from the request object and check if creating or editing
167
     *
168
     * @param \Illuminate\Http\Request $request   Incoming request object.
169
     * @param \App\Models\JobPoster    $jobPoster Optional Job Poster object.
170
     *
171
     * @return \Illuminate\View\View Job Create view
172
     */
173 2
    public function populateCreateView(Request $request, JobPoster $jobPoster = null) : View
174
    {
175 2
        $manager = $request->user() ? $request->user()->manager : null;
176 2
        if (isset($jobPoster)) {
177 2
            $job = $jobPoster;
178 2
            $route = ['manager.jobs.store', $jobPoster];
179 2
            $jobHeading = 'manager/job_edit';
180
        } else {
181
            $job = [];
182
            $route = ['manager.jobs.store'];
183
            $jobHeading = 'manager/job_create';
184
        }
185
186 2
        $skillLangs = Lang::get('common/skills');
187
188 2
        $softSkills = Skill::whereHas(
189 2
            'skill_type',
190 2
            function ($query) {
191 2
                $query->where('name', '=', 'soft');
192 2
            }
193 2
        )->get()
194 2
            ->mapWithKeys(
195 2
                function ($skill) use ($skillLangs) {
196
                    return [
197 2
                        $skill->id => $skillLangs['skills'][$skill->name]['name']
198
                    ];
199 2
                }
200
            )
201 2
            ->all();
202
203 2
        $hardSkills = Skill::whereHas(
204 2
            'skill_type',
205 2
            function ($query) {
206 2
                $query->where('name', '=', 'hard');
207 2
            }
208 2
        )->get()
209 2
            ->mapWithKeys(
210 2
                function ($skill) use ($skillLangs) {
211
                    return [
212 2
                        $skill->id => $skillLangs['skills'][$skill->name]['name']
213
                    ];
214 2
                }
215
            )
216 2
            ->all();
217
218 2
        asort($softSkills, SORT_LOCALE_STRING);
219 2
        asort($hardSkills, SORT_LOCALE_STRING);
220
221
        $skills = [
222
            'essential' => [
223 2
                'hard' => $hardSkills,
224 2
                'soft' => $softSkills
225
            ],
226
            'asset' => [
227 2
                'hard' => $hardSkills,
228 2
                'soft' => $softSkills
229
            ]
230
        ];
231
232 2
        $skillLevelCollection = SkillLevel::all();
233
234 2
        $skillLevels = array();
235
236 2
        $skillLevels['hard'] = $skillLevelCollection->mapWithKeys(
237 2
            function ($skillLevel) use ($skillLangs) {
238 2
                return [$skillLevel->id => $skillLangs['skill_levels']['hard'][$skillLevel->name]];
239 2
            }
240 2
        )->all();
241
242 2
        $skillLevels['soft'] = $skillLevelCollection->mapWithKeys(
243 2
            function ($skillLevel) use ($skillLangs) {
244 2
                return [$skillLevel->id => $skillLangs['skill_levels']['soft'][$skillLevel->name]];
245 2
            }
246 2
        )->all();
247
248 2
        return view(
249 2
            'manager/job_create',
250
            [
251 2
                'job_heading' => Lang::get($jobHeading),
252 2
                'manager' => $manager,
253 2
                'provinces' => Province::all(),
254 2
                'departments' => Department::all(),
255 2
                'language_requirments' => LanguageRequirement::all(),
256 2
                'security_clearances' => SecurityClearance::all(),
257 2
                'job' => $job,
258 2
                'form_action_url' => route(/** @scrutinizer ignore-type */ ...$route), // phpcs:ignore
0 ignored issues
show
Bug introduced by
$route is expanded, but the parameter $name of route() does not expect variable arguments. ( Ignorable by Annotation )

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

258
                'form_action_url' => route(/** @scrutinizer ignore-type */ /** @scrutinizer ignore-type */ ...$route), // phpcs:ignore
Loading history...
259 2
                'skills' => $skills,
260 2
                'skill_levels' => $skillLevels,
261 2
                'skill_template' => $skillLangs,
262
            ]
263
        );
264
    }
265
266
    /**
267
     * Create a new resource in storage
268
     *
269
     * @param \Illuminate\Http\Request $request   Incoming request object.
270
     * @param \App\Models\JobPoster    $jobPoster Optional Job Poster object.
271
     *
272
     * @return \Illuminate\Http\RedirectResponse A redirect to the Job Index
273
     */
274 2
    public function store(Request $request, JobPoster $jobPoster = null) : RedirectResponse
275
    {
276
        // Don't allow edits for published Job Posters
277
        // Also check auth while we're at it
278 2
        if (isset($jobPoster)) {
279
            $this->authorize('update', $jobPoster);
280
            JobPosterValidator::validateUnpublished($jobPoster);
281
        } else {
282 2
            $this->authorize('create', JobPoster::class);
283
        }
284
285 2
        $input = $request->input();
286
287 2
        $job = (isset($jobPoster) ? $jobPoster : new JobPoster());
288
289 2
        $job->manager_id = $request->user()->manager->id;
290 2
        $job->published = ($input['submit'] == 'publish');
291
292 2
        $this->fillAndSaveJobPoster($input, $job);
293
294 2
        $this->fillAndSaveJobPosterTasks($input, $job, isset($jobPoster));
295
296 2
        $this->fillAndSaveJobPosterQuestions($input, $job, isset($jobPoster));
297
298 2
        $this->fillAndSaveJobPosterCriteria($input, $job, isset($jobPoster));
299
300 2
        return redirect(route('manager.jobs.index'));
301
    }
302
303
    /**
304
     * Fill Job Poster model's properties and save
305
     *
306
     * @param mixed[]               $input     Field values.
307
     * @param \App\Models\JobPoster $jobPoster Job Poster object.
308
     *
309
     * @return void
310
     */
311 2
    protected function fillAndSaveJobPoster(array $input, JobPoster $jobPoster)
312
    {
313 2
        $jobPoster->fill(
314
            [
315 2
                'job_term_id' => JobTerm::where('name', 'month')->firstOrFail()->id,
316 2
                'term_qty' => $input['term_qty'],
317 2
                'open_date_time' => new Date($input['open_date'] . $input['open_time']),
318 2
                'close_date_time' => new Date($input['close_date'] . $input['close_time']),
319 2
                'start_date_time' => new Date($input['start_date_time']),
320 2
                'department_id' => $input['department'],
321 2
                'province_id' => $input['province'],
322 2
                'salary_min' => $input['salary_min'],
323 2
                'salary_max' => $input['salary_max'],
324 2
                'noc' => $input['noc'],
325 2
                'classification' => $input['classification'],
326 2
                'security_clearance_id' => $input['security_clearance'],
327 2
                'language_requirement_id' => $input['language_requirement'],
328 2
                'remote_work_allowed' => (isset($input['remote_work_allowed']) ? $input['remote_work_allowed'] : false),
329
                'en' => [
330 2
                    'city' => $input['city'],
331 2
                    'title' => $input['title']['en'],
332 2
                    'impact' => $input['impact']['en'],
333 2
                    'branch' => $input['branch']['en'],
334 2
                    'division' => $input['division']['en'],
335 2
                    'education' => $input['education']['en'],
336
                ],
337
                'fr' => [
338 2
                    'city' => $input['city'],
339 2
                    'title' => $input['title']['fr'],
340 2
                    'impact' => $input['impact']['fr'],
341 2
                    'branch' => $input['branch']['fr'],
342 2
                    'division' => $input['division']['fr'],
343 2
                    'education' => $input['education']['fr'],
344
                ],
345
            ]
346
        );
347 2
        $jobPoster->save();
348 2
    }
349
350
    /**
351
     * Fill Job Poster's tasks and save
352
     *
353
     * @param mixed[]               $input     Field values.
354
     * @param \App\Models\JobPoster $jobPoster Job Poster object.
355
     * @param boolean               $replace   Remove existing relationships.
356
     *
357
     * @return void
358
     */
359 2
    protected function fillAndSaveJobPosterTasks(array $input, JobPoster $jobPoster, bool $replace)
360
    {
361 2
        if ($replace) {
362
            $jobPoster->job_poster_key_tasks()->delete();
363
        }
364
365 2
        if (!array_key_exists('task', $input) || !is_array($input['task'])) {
366 2
            return;
367
        }
368
369
        foreach ($input['task'] as $task) {
370
            $jobPosterTask = new JobPosterKeyTask();
371
            $jobPosterTask->job_poster_id = $jobPoster->id;
372
            $jobPosterTask->fill(
373
                [
374
                    'en' => [
375
                        'description' => $task['en']
376
                    ],
377
                    'fr' => [
378
                        'description' => $task['fr']
379
                    ]
380
                ]
381
            );
382
            $jobPosterTask->save();
383
        }
384
    }
385
386
    /**
387
     * Fill Job Poster's questions and save
388
     *
389
     * @param mixed[]               $input     Field values.
390
     * @param \App\Models\JobPoster $jobPoster Job Poster object.
391
     * @param boolean               $replace   Remove existing relationships.
392
     *
393
     * @return void
394
     */
395 2
    protected function fillAndSaveJobPosterQuestions(array $input, JobPoster $jobPoster, bool $replace)
396
    {
397 2
        if ($replace) {
398
            $jobPoster->job_poster_questions()->delete();
399
        }
400
401 2
        if (!array_key_exists('question', $input) || !is_array($input['question'])) {
402 2
            return;
403
        }
404
405
        foreach ($input['question'] as $question) {
406
            $jobQuestion = new JobPosterQuestion();
407
            $jobQuestion->job_poster_id = $jobPoster->id;
408
            $jobQuestion->fill(
409
                [
410
                    'en' => [
411
                        'question' => $question['question']['en'],
412
                        'description' => $question['description']['en']
413
                    ],
414
                    'fr' => [
415
                        'question' => $question['question']['fr'],
416
                        'description' => $question['description']['fr']
417
                    ]
418
                ]
419
            );
420
            $jobQuestion->save();
421
        }
422
    }
423
424
    /**
425
     * Fill Job Poster's criteria and save
426
     *
427
     * @param mixed[]               $input     Field values.
428
     * @param \App\Models\JobPoster $jobPoster Job Poster object.
429
     * @param boolean               $replace   Remove existing relationships.
430
     *
431
     * @return void
432
     */
433 2
    protected function fillAndSaveJobPosterCriteria(array $input, JobPoster $jobPoster, bool $replace)
434
    {
435 2
        if ($replace) {
436
            $jobPoster->criteria()->delete();
437
        }
438
439 2
        if (!array_key_exists('criteria', $input) || !is_array($input['criteria'])) {
440 2
            return;
441
        }
442
443
        $criteria = $input['criteria'];
444
445
        $combinedCriteria = [];
446
        if (isset($criteria['old'])) {
447
            $combinedCriteria = array_replace_recursive($combinedCriteria, $criteria['old']);
448
        }
449
        if (isset($criteria['new'])) {
450
            $combinedCriteria = array_replace_recursive($combinedCriteria, $criteria['new']);
451
        }
452
453
        if (! empty($combinedCriteria)) {
454
            foreach ($combinedCriteria as $criteriaType => $criteriaTypeInput) {
455
                foreach ($criteriaTypeInput as $skillType => $skillTypeInput) {
456
                    foreach ($skillTypeInput as $criteriaInput) {
457
                        $criteria = new Criteria();
458
                        $criteria->job_poster_id = $jobPoster->id;
459
                        $criteria->fill(
460
                            [
461
                                'criteria_type_id' => CriteriaType::where('name', $criteriaType)->firstOrFail()->id,
462
                                'skill_id' => $criteriaInput['skill_id'],
463
                                'skill_level_id' => $criteriaInput['skill_level_id'],
464
                                'en' => [
465
                                    'description' => $criteriaInput['description']['en'],
466
                                ],
467
                                'fr' => [
468
                                    'description' => $criteriaInput['description']['fr'],
469
                                ],
470
                            ]
471
                        );
472
                        $criteria->save();
473
                    }
474
                }
475
            }
476
        }
477
    }
478
}
479