Skip to content

Commit 8bb7231

Browse files
committed
feat(hr): Phase 141 — HR Employee Mentorship Programs
Add mentorship lifecycle: active → completed/paused/cancelled, session logging, progress tracking, and policy-gated CRUD + action routes. https://claude.ai/code/session_01RdUGwo74JXChRCF88Yu27d
1 parent fc6ed3f commit 8bb7231

11 files changed

Lines changed: 512 additions & 0 deletions

File tree

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
<?php
2+
3+
namespace App\Modules\HR\Http\Controllers;
4+
5+
use App\Http\Controllers\Controller;
6+
use App\Modules\HR\Models\Employee;
7+
use App\Modules\HR\Models\MentorshipProgram;
8+
use Illuminate\Http\RedirectResponse;
9+
use Illuminate\Http\Request;
10+
use Inertia\Inertia;
11+
use Inertia\Response;
12+
13+
class MentorshipProgramController extends Controller
14+
{
15+
public function index(Request $request): Response
16+
{
17+
$this->authorize('viewAny', MentorshipProgram::class);
18+
19+
$query = MentorshipProgram::with(['mentor', 'mentee'])
20+
->orderByDesc('created_at');
21+
22+
if ($request->filled('status')) {
23+
$query->where('status', $request->status);
24+
}
25+
26+
$programs = $query->paginate(15);
27+
$filters = $request->only(['status']);
28+
29+
return Inertia::render('HR/MentorshipPrograms/Index', compact('programs', 'filters'));
30+
}
31+
32+
public function create(): Response
33+
{
34+
$this->authorize('create', MentorshipProgram::class);
35+
36+
$employees = Employee::where('status', 'active')
37+
->orderBy('first_name')
38+
->get(['id', 'first_name', 'last_name']);
39+
40+
return Inertia::render('HR/MentorshipPrograms/Create', compact('employees'));
41+
}
42+
43+
public function store(Request $request): RedirectResponse
44+
{
45+
$this->authorize('create', MentorshipProgram::class);
46+
47+
$data = $request->validate([
48+
'mentor_id' => ['required', 'exists:employees,id'],
49+
'mentee_id' => ['required', 'exists:employees,id'],
50+
'title' => ['required', 'string'],
51+
'start_date' => ['required', 'date'],
52+
'objectives' => ['nullable', 'string'],
53+
'end_date' => ['nullable', 'date'],
54+
'status' => ['nullable', 'string'],
55+
'meeting_frequency' => ['nullable', 'string'],
56+
'sessions_planned' => ['nullable', 'integer'],
57+
'notes' => ['nullable', 'string'],
58+
]);
59+
60+
$data['tenant_id'] = app('tenant')->id;
61+
$data['created_by'] = auth()->id();
62+
63+
MentorshipProgram::create($data);
64+
65+
return redirect()->route('hr.mentorship-programs.index');
66+
}
67+
68+
public function show(MentorshipProgram $mentorshipProgram): Response
69+
{
70+
$this->authorize('view', $mentorshipProgram);
71+
72+
$mentorshipProgram->load(['mentor', 'mentee']);
73+
74+
return Inertia::render('HR/MentorshipPrograms/Show', compact('mentorshipProgram'));
75+
}
76+
77+
public function edit(MentorshipProgram $mentorshipProgram): Response
78+
{
79+
$this->authorize('update', $mentorshipProgram);
80+
81+
$employees = Employee::where('status', 'active')
82+
->orderBy('first_name')
83+
->get(['id', 'first_name', 'last_name']);
84+
85+
$mentorshipProgram->load(['mentor', 'mentee']);
86+
87+
return Inertia::render('HR/MentorshipPrograms/Edit', compact('mentorshipProgram', 'employees'));
88+
}
89+
90+
public function update(Request $request, MentorshipProgram $mentorshipProgram): RedirectResponse
91+
{
92+
$this->authorize('update', $mentorshipProgram);
93+
94+
$data = $request->validate([
95+
'mentor_id' => ['required', 'exists:employees,id'],
96+
'mentee_id' => ['required', 'exists:employees,id'],
97+
'title' => ['required', 'string'],
98+
'start_date' => ['required', 'date'],
99+
'objectives' => ['nullable', 'string'],
100+
'end_date' => ['nullable', 'date'],
101+
'status' => ['nullable', 'string'],
102+
'meeting_frequency' => ['nullable', 'string'],
103+
'sessions_planned' => ['nullable', 'integer'],
104+
'notes' => ['nullable', 'string'],
105+
]);
106+
107+
$mentorshipProgram->update($data);
108+
109+
return redirect()->route('hr.mentorship-programs.index');
110+
}
111+
112+
public function destroy(MentorshipProgram $mentorshipProgram): RedirectResponse
113+
{
114+
$this->authorize('delete', $mentorshipProgram);
115+
116+
$mentorshipProgram->delete();
117+
118+
return redirect()->route('hr.mentorship-programs.index');
119+
}
120+
121+
public function complete(MentorshipProgram $mentorshipProgram): RedirectResponse
122+
{
123+
$this->authorize('complete', $mentorshipProgram);
124+
125+
$mentorshipProgram->complete();
126+
127+
return redirect()->route('hr.mentorship-programs.index');
128+
}
129+
130+
public function pause(MentorshipProgram $mentorshipProgram): RedirectResponse
131+
{
132+
$this->authorize('pause', $mentorshipProgram);
133+
134+
$mentorshipProgram->pause();
135+
136+
return redirect()->route('hr.mentorship-programs.index');
137+
}
138+
139+
public function resume(MentorshipProgram $mentorshipProgram): RedirectResponse
140+
{
141+
$this->authorize('resume', $mentorshipProgram);
142+
143+
$mentorshipProgram->resume();
144+
145+
return redirect()->route('hr.mentorship-programs.index');
146+
}
147+
148+
public function cancel(MentorshipProgram $mentorshipProgram): RedirectResponse
149+
{
150+
$this->authorize('cancel', $mentorshipProgram);
151+
152+
$mentorshipProgram->cancel();
153+
154+
return redirect()->route('hr.mentorship-programs.index');
155+
}
156+
157+
public function logSession(MentorshipProgram $mentorshipProgram): RedirectResponse
158+
{
159+
$this->authorize('logSession', $mentorshipProgram);
160+
161+
$mentorshipProgram->logSession();
162+
163+
return redirect()->route('hr.mentorship-programs.index');
164+
}
165+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
namespace App\Modules\HR\Models;
4+
5+
use App\Modules\Core\Traits\BelongsToTenant;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
8+
use Illuminate\Database\Eloquent\SoftDeletes;
9+
10+
class MentorshipProgram extends Model
11+
{
12+
use BelongsToTenant;
13+
use SoftDeletes;
14+
15+
protected $fillable = [
16+
'tenant_id',
17+
'mentor_id',
18+
'mentee_id',
19+
'program_number',
20+
'title',
21+
'objectives',
22+
'start_date',
23+
'end_date',
24+
'status',
25+
'meeting_frequency',
26+
'sessions_completed',
27+
'sessions_planned',
28+
'notes',
29+
'created_by',
30+
];
31+
32+
protected $attributes = [
33+
'status' => 'active',
34+
'meeting_frequency' => 'monthly',
35+
'sessions_completed' => 0,
36+
'sessions_planned' => 0,
37+
];
38+
39+
protected $casts = [
40+
'start_date' => 'date',
41+
'end_date' => 'date',
42+
'sessions_completed' => 'integer',
43+
'sessions_planned' => 'integer',
44+
];
45+
46+
// Relations
47+
48+
public function mentor(): BelongsTo
49+
{
50+
return $this->belongsTo(Employee::class, 'mentor_id');
51+
}
52+
53+
public function mentee(): BelongsTo
54+
{
55+
return $this->belongsTo(Employee::class, 'mentee_id');
56+
}
57+
58+
// Status methods
59+
60+
public function complete(): void
61+
{
62+
$this->status = 'completed';
63+
$this->save();
64+
}
65+
66+
public function pause(): void
67+
{
68+
$this->status = 'paused';
69+
$this->save();
70+
}
71+
72+
public function resume(): void
73+
{
74+
$this->status = 'active';
75+
$this->save();
76+
}
77+
78+
public function cancel(): void
79+
{
80+
$this->status = 'cancelled';
81+
$this->save();
82+
}
83+
84+
public function logSession(): void
85+
{
86+
$this->sessions_completed++;
87+
$this->save();
88+
}
89+
90+
public function generateProgramNumber(): string
91+
{
92+
return 'MP-' . date('Y') . '-' . str_pad((string) $this->id, 5, '0', STR_PAD_LEFT);
93+
}
94+
95+
// Accessors
96+
97+
public function getIsActiveAttribute(): bool
98+
{
99+
return $this->status === 'active';
100+
}
101+
102+
public function getIsCompletedAttribute(): bool
103+
{
104+
return $this->status === 'completed';
105+
}
106+
107+
public function getProgressPercentAttribute(): int
108+
{
109+
if ($this->sessions_planned > 0) {
110+
return (int) min(100, round(($this->sessions_completed / $this->sessions_planned) * 100));
111+
}
112+
113+
return 0;
114+
}
115+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace App\Modules\HR\Policies;
4+
5+
use App\Models\User;
6+
use App\Modules\HR\Models\MentorshipProgram;
7+
8+
class MentorshipProgramPolicy
9+
{
10+
public function viewAny(User $user): bool
11+
{
12+
return $user->can('hr.view');
13+
}
14+
15+
public function view(User $user, MentorshipProgram $mentorshipProgram): 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, MentorshipProgram $mentorshipProgram): bool
26+
{
27+
return $user->can('hr.create');
28+
}
29+
30+
public function complete(User $user, MentorshipProgram $mentorshipProgram): bool
31+
{
32+
return $user->can('hr.create');
33+
}
34+
35+
public function pause(User $user, MentorshipProgram $mentorshipProgram): bool
36+
{
37+
return $user->can('hr.create');
38+
}
39+
40+
public function resume(User $user, MentorshipProgram $mentorshipProgram): bool
41+
{
42+
return $user->can('hr.create');
43+
}
44+
45+
public function cancel(User $user, MentorshipProgram $mentorshipProgram): bool
46+
{
47+
return $user->can('hr.create');
48+
}
49+
50+
public function logSession(User $user, MentorshipProgram $mentorshipProgram): bool
51+
{
52+
return $user->can('hr.create');
53+
}
54+
55+
public function delete(User $user, MentorshipProgram $mentorshipProgram): bool
56+
{
57+
return $user->can('hr.delete');
58+
}
59+
}

erp/app/Modules/HR/Providers/HRServiceProvider.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@
9090
use App\Modules\HR\Models\SuccessionPlan;
9191
use App\Modules\HR\Models\SuccessionCandidate;
9292
use App\Modules\HR\Policies\SuccessionPlanPolicy;
93+
use App\Modules\HR\Models\MentorshipProgram;
94+
use App\Modules\HR\Policies\MentorshipProgramPolicy;
9395
use Illuminate\Support\Facades\Gate;
9496
use Illuminate\Support\ServiceProvider;
9597

@@ -155,5 +157,6 @@ public function boot(): void
155157
Gate::policy(EmployeeGoal::class, EmployeeGoalPolicy::class);
156158
Gate::policy(SuccessionPlan::class, SuccessionPlanPolicy::class);
157159
Gate::policy(SuccessionCandidate::class, SuccessionPlanPolicy::class);
160+
Gate::policy(MentorshipProgram::class, MentorshipProgramPolicy::class);
158161
}
159162
}

erp/app/Modules/HR/routes/hr.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,14 @@
320320
Route::post('succession-plans/{succession_plan}/deactivate', [SuccessionPlanController::class, 'deactivate'])->name('succession-plans.deactivate');
321321
Route::resource('succession-plans', SuccessionPlanController::class);
322322
});
323+
324+
// Mentorship Programs
325+
use App\Modules\HR\Http\Controllers\MentorshipProgramController;
326+
Route::middleware(['web', 'auth', 'verified'])->prefix('hr')->name('hr.')->group(function () {
327+
Route::post('mentorship-programs/{mentorship_program}/complete', [MentorshipProgramController::class, 'complete'])->name('mentorship-programs.complete');
328+
Route::post('mentorship-programs/{mentorship_program}/pause', [MentorshipProgramController::class, 'pause'])->name('mentorship-programs.pause');
329+
Route::post('mentorship-programs/{mentorship_program}/resume', [MentorshipProgramController::class, 'resume'])->name('mentorship-programs.resume');
330+
Route::post('mentorship-programs/{mentorship_program}/cancel', [MentorshipProgramController::class, 'cancel'])->name('mentorship-programs.cancel');
331+
Route::post('mentorship-programs/{mentorship_program}/log-session',[MentorshipProgramController::class, 'logSession'])->name('mentorship-programs.log-session');
332+
Route::resource('mentorship-programs', MentorshipProgramController::class);
333+
});

0 commit comments

Comments
 (0)