Passed
Push — feature/job-status-transitions ( fcd539 )
by Tristan
05:41
created

JobStatusController::legalTransition()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nc 1
nop 2
1
<?php
2
3
namespace App\Http\Controllers;
4
5
use App\Exceptions\StateMachineException;
6
use App\Models\JobPoster;
7
use App\Models\JobPosterStatusHistory;
8
use App\Models\Lookup\JobPosterStatus;
9
use App\Models\User;
10
use Illuminate\Http\Request;
11
12
class JobStatusController extends Controller
13
{
14
    protected $transition_graph = [
15
        'states' => [
16
            [
17
                'name' => 'draft',
18
                'metadata' => ['owner' => 'manager']
19
            ],
20
            [
21
                'name' => 'review_manager',
22
                'metadata' => ['owner' => 'manager']
23
            ],
24
            [
25
                'name' => 'review_hr',
26
                'metadata' => ['owner' => 'hr']
27
            ],
28
            [
29
                'name' => 'translation',
30
                'metadata' => ['owner' => 'admin']
31
            ],
32
            [
33
                'name' => 'final_review_manager',
34
                'metadata' => ['owner' => 'manager']
35
            ],
36
            [
37
                'name' => 'final_review_hr',
38
                'metadata' => ['owner' => 'hr']
39
            ],
40
            [
41
                'name' => 'pending_approval',
42
                'metadata' => ['owner' => 'hr']
43
            ],
44
            [
45
                'name' => 'approved',
46
                'metadata' => ['owner' => 'admin']
47
            ],
48
            [
49
                'name' => 'published',
50
                'metadata' => ['owner' => 'admin']
51
            ],
52
            [
53
                'name' => 'completed',
54
                'metadata' => ['owner' => 'admin']
55
            ]
56
        ],
57
        'transitions' => [
58
            'send_to_hr' => [
59
                'from' => ['draft', 'review_manager'],
60
                'to' => 'review_hr'
61
            ],
62
            'send_to_manager' => [
63
                'from' => ['review_hr'],
64
                'to' => 'review_manager'
65
            ],
66
            'send_to_translation' => [
67
                'from' => ['review_hr', 'final_review_hr', 'pending_approval'],
68
                'to' => 'translation'
69
            ],
70
            'send_to_manager_final' => [
71
                'from' => ['translation', 'final_review_hr', 'pending_approval'],
72
                'to' => 'final_review_manager'
73
            ],
74
            'send_to_hr_final' => [
75
                'from' => ['final_review_manager'],
76
                'to' => 'final_review_hr',
77
            ],
78
            'submit_for_approval' => [
79
                'from' => ['final_review_manager'],
80
                'to' => 'pending_approval'
81
            ],
82
            'approve' => [
83
                'from' => ['pending_approval'],
84
                'to' => 'approved'
85
            ],
86
            'publish' => [
87
                'from' => ['approved'],
88
                'to' => 'published'
89
            ],
90
            'complete' => [
91
                'from' => ['published'],
92
                'to' => 'completed'
93
            ]
94
        ]
95
    ];
96
97
    protected function stateMetadata(string $state)
98
    {
99
        $graph = $this->transition_graph;
100
        $stateObj = collect($graph['states'])->firstWhere('name', $state);
0 ignored issues
show
Bug introduced by
The function collect 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

100
        $stateObj = /** @scrutinizer ignore-call */ collect($graph['states'])->firstWhere('name', $state);
Loading history...
101
        return $stateObj !== null
102
            ? $stateObj['metadata']
103
            : [];
104
    }
105
106
    protected function legalTransition(string $from, string $to): bool
107
    {
108
        $graph = $this->transition_graph;
109
        $transitions = collect($graph['transitions']);
0 ignored issues
show
Bug introduced by
The function collect 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

109
        $transitions = /** @scrutinizer ignore-call */ collect($graph['transitions']);
Loading history...
110
        return $transitions->some(function ($transition) use ($from, $to) {
111
            return $transition['to'] === $to && collect($transition['from'])->contains($from);
0 ignored issues
show
Bug introduced by
The function collect 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

111
            return $transition['to'] === $to && /** @scrutinizer ignore-call */ collect($transition['from'])->contains($from);
Loading history...
112
        });
113
    }
114
115
    protected function userOwnsState(User $user, string $state): bool
116
    {
117
        $owner = $this->stateMetadata($state)['owner'];
118
        return $user->isAdmin()
119
            || $owner === 'manager' && $user->isManager()
120
            || $owner === 'hr' && $user->isHrAdvisor()
121
            || $owner === 'admin' && $user->isAdmin();
122
    }
123
124
    protected function canTransition(User $user, string $from, string $to): bool
125
    {
126
        return $this->userOwnsState($user, $from) && $this->legalTransition($from, $to);
127
    }
128
129
    protected function transitionJobStatus(Request $request, JobPoster $job, string $to)
130
    {
131
        $user = $request->user();
132
        $fromStatus = $job->job_poster_status;
133
        $from = $fromStatus->name;
134
135
        // Ensure state transition is legal.
136
        if (!$this->canTransition($user, $from, $to)) {
137
            throw new StateMachineException('Illegal state transition');
138
        }
139
140
        // Save new status on job.
141
        $toStatus = JobPosterStatus::where('name', $to)->first();
142
        $job->job_poster_status_id = $toStatus->id;
143
        $job->save();
144
145
        // Save transition history.
146
        $transition = new JobPosterStatusHistory();
147
        $transition->job_poster_id = $job->id;
148
        $transition->user_id = $user->id;
149
        $transition->from_job_poster_status_id = $fromStatus->id;
150
        $transition->to_job_poster_status_id = $toStatus->id;
151
        $transition->save();
152
153
        return $request->ajax()
154
            ? response()->json(['status' => 'ok'])
0 ignored issues
show
Bug introduced by
The function response 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

154
            ? /** @scrutinizer ignore-call */ response()->json(['status' => 'ok'])
Loading history...
155
            : back();
0 ignored issues
show
Bug introduced by
The function back 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

155
            : /** @scrutinizer ignore-call */ back();
Loading history...
156
    }
157
158
    public function setJobStatus(Request $request, JobPoster $jobPoster)
159
    {
160
        $status = $request->input('status');
161
        return $this->transitionJobStatus($request, $jobPoster, $status);
162
    }
163
}
164