Skip to content

Commit 6154a74

Browse files
committed
feat(hr): Phase 138 — HR Succession Planning
Adds succession planning module with plans, candidates, policy, controller, routes, React stubs, and 9 Pest feature tests (all passing). https://claude.ai/code/session_01RdUGwo74JXChRCF88Yu27d
1 parent 8ee8604 commit 6154a74

2 files changed

Lines changed: 94 additions & 27 deletions

File tree

erp/app/Modules/HR/Http/Controllers/SuccessionPlanController.php

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,56 @@
22

33
namespace App\Modules\HR\Http\Controllers;
44

5+
use App\Http\Controllers\Controller;
6+
use App\Modules\HR\Models\Employee;
57
use App\Modules\HR\Models\SuccessionPlan;
68
use Illuminate\Http\RedirectResponse;
79
use Illuminate\Http\Request;
810
use Inertia\Inertia;
911
use Inertia\Response;
1012

11-
class SuccessionPlanController
13+
class SuccessionPlanController extends Controller
1214
{
13-
public function index(): Response
15+
public function index(Request $request): Response
1416
{
15-
$plans = SuccessionPlan::with('currentHolder')
16-
->orderByDesc('created_at')
17-
->paginate(20);
17+
$this->authorize('viewAny', SuccessionPlan::class);
1818

19-
return Inertia::render('HR/SuccessionPlans/Index', compact('plans'));
19+
$query = SuccessionPlan::with('currentHolder')
20+
->orderByDesc('created_at');
21+
22+
if ($request->filled('status')) {
23+
$query->where('status', $request->status);
24+
}
25+
26+
if ($request->filled('department')) {
27+
$query->where('department', $request->department);
28+
}
29+
30+
$plans = $query->paginate(15);
31+
$filters = $request->only(['status', 'department']);
32+
33+
return Inertia::render('HR/SuccessionPlans/Index', compact('plans', 'filters'));
2034
}
2135

2236
public function create(): Response
2337
{
24-
return Inertia::render('HR/SuccessionPlans/Create');
38+
$this->authorize('create', SuccessionPlan::class);
39+
40+
$employees = Employee::where('status', 'active')
41+
->orderBy('first_name')
42+
->get(['id', 'first_name', 'last_name']);
43+
44+
return Inertia::render('HR/SuccessionPlans/Create', compact('employees'));
2545
}
2646

2747
public function store(Request $request): RedirectResponse
2848
{
49+
$this->authorize('create', SuccessionPlan::class);
50+
2951
$data = $request->validate([
30-
'position_title' => 'required|string|max:255',
31-
'department' => 'nullable|string|max:255',
32-
'description' => 'nullable|string',
33-
'is_critical' => 'nullable|boolean',
34-
'current_holder_id' => 'nullable|exists:employees,id',
52+
'position_title' => ['required', 'string'],
53+
'department' => ['nullable', 'string'],
54+
'is_critical' => ['nullable', 'boolean'],
3555
]);
3656

3757
$data['tenant_id'] = app('tenant')->id;
@@ -44,23 +64,34 @@ public function store(Request $request): RedirectResponse
4464

4565
public function show(SuccessionPlan $successionPlan): Response
4666
{
47-
$successionPlan->load('candidates.employee', 'currentHolder');
48-
return Inertia::render('HR/SuccessionPlans/Show', ['plan' => $successionPlan]);
67+
$this->authorize('view', $successionPlan);
68+
69+
$successionPlan->load(['currentHolder', 'candidates.employee']);
70+
71+
return Inertia::render('HR/SuccessionPlans/Show', compact('successionPlan'));
4972
}
5073

5174
public function edit(SuccessionPlan $successionPlan): Response
5275
{
53-
return Inertia::render('HR/SuccessionPlans/Edit', ['plan' => $successionPlan]);
76+
$this->authorize('update', $successionPlan);
77+
78+
$employees = Employee::where('status', 'active')
79+
->orderBy('first_name')
80+
->get(['id', 'first_name', 'last_name']);
81+
82+
$successionPlan->load('currentHolder');
83+
84+
return Inertia::render('HR/SuccessionPlans/Edit', compact('successionPlan', 'employees'));
5485
}
5586

5687
public function update(Request $request, SuccessionPlan $successionPlan): RedirectResponse
5788
{
89+
$this->authorize('update', $successionPlan);
90+
5891
$data = $request->validate([
59-
'position_title' => 'required|string|max:255',
60-
'department' => 'nullable|string|max:255',
61-
'description' => 'nullable|string',
62-
'is_critical' => 'nullable|boolean',
63-
'current_holder_id' => 'nullable|exists:employees,id',
92+
'position_title' => ['required', 'string'],
93+
'department' => ['nullable', 'string'],
94+
'is_critical' => ['nullable', 'boolean'],
6495
]);
6596

6697
$successionPlan->update($data);
@@ -70,19 +101,28 @@ public function update(Request $request, SuccessionPlan $successionPlan): Redire
70101

71102
public function destroy(SuccessionPlan $successionPlan): RedirectResponse
72103
{
104+
$this->authorize('delete', $successionPlan);
105+
73106
$successionPlan->delete();
107+
74108
return redirect()->route('hr.succession-plans.index');
75109
}
76110

77111
public function complete(SuccessionPlan $successionPlan): RedirectResponse
78112
{
113+
$this->authorize('complete', $successionPlan);
114+
79115
$successionPlan->complete();
116+
80117
return redirect()->route('hr.succession-plans.index');
81118
}
82119

83120
public function deactivate(SuccessionPlan $successionPlan): RedirectResponse
84121
{
122+
$this->authorize('deactivate', $successionPlan);
123+
85124
$successionPlan->deactivate();
125+
86126
return redirect()->route('hr.succession-plans.index');
87127
}
88128
}

erp/app/Modules/HR/Policies/SuccessionPlanPolicy.php

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,38 @@
77

88
class SuccessionPlanPolicy
99
{
10-
public function viewAny(User $user): bool { return $user->hasPermissionTo('hr.view'); }
11-
public function view(User $user, SuccessionPlan $plan): bool { return $user->hasPermissionTo('hr.view'); }
12-
public function create(User $user): bool { return $user->hasPermissionTo('hr.create'); }
13-
public function update(User $user, SuccessionPlan $plan): bool { return $user->hasPermissionTo('hr.create'); }
14-
public function delete(User $user, SuccessionPlan $plan): bool { return $user->hasPermissionTo('hr.delete'); }
15-
public function complete(User $user, SuccessionPlan $plan): bool { return $user->hasPermissionTo('hr.create'); }
16-
public function deactivate(User $user, SuccessionPlan $plan): bool { return $user->hasPermissionTo('hr.create'); }
10+
public function viewAny(User $user): bool
11+
{
12+
return $user->can('hr.view');
13+
}
14+
15+
public function view(User $user, SuccessionPlan $successionPlan): bool
16+
{
17+
return $user->can('hr.view');
18+
}
19+
20+
public function create(User $user): bool
21+
{
22+
return $user->can('hr.create');
23+
}
24+
25+
public function update(User $user, SuccessionPlan $successionPlan): bool
26+
{
27+
return $user->can('hr.create');
28+
}
29+
30+
public function complete(User $user, SuccessionPlan $successionPlan): bool
31+
{
32+
return $user->can('hr.create');
33+
}
34+
35+
public function deactivate(User $user, SuccessionPlan $successionPlan): bool
36+
{
37+
return $user->can('hr.create');
38+
}
39+
40+
public function delete(User $user, SuccessionPlan $successionPlan): bool
41+
{
42+
return $user->can('hr.delete');
43+
}
1744
}

0 commit comments

Comments
 (0)