Skip to content

Commit 14012ea

Browse files
committed
feat(hr): Phase 119 — Overtime Requests & Approval
https://claude.ai/code/session_01RdUGwo74JXChRCF88Yu27d
1 parent 0948e15 commit 14012ea

9 files changed

Lines changed: 376 additions & 0 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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\OvertimeRequest;
8+
use Illuminate\Http\RedirectResponse;
9+
use Illuminate\Http\Request;
10+
use Inertia\Inertia;
11+
use Inertia\Response;
12+
13+
class OvertimeRequestController extends Controller
14+
{
15+
public function index(): Response
16+
{
17+
$this->authorize('viewAny', OvertimeRequest::class);
18+
$requests = OvertimeRequest::with('employee')
19+
->where('tenant_id', app('tenant')->id)
20+
->latest()
21+
->paginate(20);
22+
return Inertia::render('HR/OvertimeRequests/Index', compact('requests'));
23+
}
24+
25+
public function store(Request $request): RedirectResponse
26+
{
27+
$this->authorize('create', OvertimeRequest::class);
28+
$validated = $request->validate([
29+
'employee_id' => 'required|exists:employees,id',
30+
'work_date' => 'required|date',
31+
'hours' => 'required|numeric|min:0.5|max:24',
32+
'rate_multiplier'=> 'sometimes|numeric|min:1',
33+
'reason' => 'nullable|string',
34+
]);
35+
$validated['tenant_id'] = app('tenant')->id;
36+
OvertimeRequest::create($validated);
37+
return back()->with('success', 'Overtime request submitted.');
38+
}
39+
40+
public function show(OvertimeRequest $overtimeRequest): Response
41+
{
42+
$this->authorize('view', $overtimeRequest);
43+
return Inertia::render('HR/OvertimeRequests/Show', compact('overtimeRequest'));
44+
}
45+
46+
public function approve(OvertimeRequest $overtimeRequest): RedirectResponse
47+
{
48+
$this->authorize('update', $overtimeRequest);
49+
$overtimeRequest->approve(auth()->id());
50+
return back()->with('success', 'Overtime request approved.');
51+
}
52+
53+
public function reject(Request $request, OvertimeRequest $overtimeRequest): RedirectResponse
54+
{
55+
$this->authorize('update', $overtimeRequest);
56+
$validated = $request->validate(['reason' => 'required|string']);
57+
$overtimeRequest->reject($validated['reason']);
58+
return back()->with('success', 'Overtime request rejected.');
59+
}
60+
61+
public function cancel(OvertimeRequest $overtimeRequest): RedirectResponse
62+
{
63+
$this->authorize('update', $overtimeRequest);
64+
$overtimeRequest->cancel();
65+
return back()->with('success', 'Overtime request cancelled.');
66+
}
67+
68+
public function destroy(OvertimeRequest $overtimeRequest): RedirectResponse
69+
{
70+
$this->authorize('delete', $overtimeRequest);
71+
$overtimeRequest->delete();
72+
return back()->with('success', 'Overtime request deleted.');
73+
}
74+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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 OvertimeRequest extends Model
11+
{
12+
use BelongsToTenant, SoftDeletes;
13+
14+
protected $attributes = [
15+
'status' => 'pending',
16+
'rate_multiplier' => 1.5,
17+
];
18+
19+
protected $fillable = [
20+
'tenant_id', 'employee_id', 'work_date', 'hours', 'rate_multiplier',
21+
'reason', 'status', 'approved_by', 'approved_at', 'rejection_reason',
22+
];
23+
24+
protected $casts = [
25+
'work_date' => 'date',
26+
'hours' => 'decimal:2',
27+
'rate_multiplier' => 'decimal:2',
28+
'approved_at' => 'datetime',
29+
];
30+
31+
public function employee(): BelongsTo
32+
{
33+
return $this->belongsTo(Employee::class);
34+
}
35+
36+
public function approver(): BelongsTo
37+
{
38+
return $this->belongsTo(\App\Models\User::class, 'approved_by');
39+
}
40+
41+
public function approve(int $userId): void
42+
{
43+
$this->update([
44+
'status' => 'approved',
45+
'approved_by' => $userId,
46+
'approved_at' => now(),
47+
]);
48+
}
49+
50+
public function reject(string $reason): void
51+
{
52+
$this->update([
53+
'status' => 'rejected',
54+
'rejection_reason' => $reason,
55+
]);
56+
}
57+
58+
public function cancel(): void
59+
{
60+
$this->update(['status' => 'cancelled']);
61+
}
62+
63+
public function getTotalPayAttribute(): float
64+
{
65+
return (float) $this->hours * (float) $this->rate_multiplier;
66+
}
67+
68+
public function getIsPendingAttribute(): bool
69+
{
70+
return $this->status === 'pending';
71+
}
72+
73+
public function getIsApprovedAttribute(): bool
74+
{
75+
return $this->status === 'approved';
76+
}
77+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace App\Modules\HR\Policies;
4+
5+
use App\Models\User;
6+
use App\Modules\HR\Models\OvertimeRequest;
7+
8+
class OvertimeRequestPolicy
9+
{
10+
public function viewAny(User $user): bool
11+
{
12+
return $user->hasPermissionTo('hr.view');
13+
}
14+
15+
public function view(User $user, OvertimeRequest $overtimeRequest): bool
16+
{
17+
return $user->hasPermissionTo('hr.view');
18+
}
19+
20+
public function create(User $user): bool
21+
{
22+
return $user->hasPermissionTo('hr.create');
23+
}
24+
25+
public function update(User $user, OvertimeRequest $overtimeRequest): bool
26+
{
27+
return $user->hasPermissionTo('hr.create');
28+
}
29+
30+
public function delete(User $user, OvertimeRequest $overtimeRequest): bool
31+
{
32+
return $user->hasPermissionTo('hr.delete');
33+
}
34+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
use App\Modules\HR\Policies\EmployeeExitPolicy;
7171
use App\Modules\HR\Models\EmployeePositionChange;
7272
use App\Modules\HR\Policies\PositionChangePolicy;
73+
use App\Modules\HR\Models\OvertimeRequest;
74+
use App\Modules\HR\Policies\OvertimeRequestPolicy;
7375
use App\Modules\HR\Models\SalaryGrade;
7476
use App\Modules\HR\Policies\SalaryGradePolicy;
7577
use Illuminate\Support\Facades\Gate;
@@ -127,5 +129,6 @@ public function boot(): void
127129
Gate::policy(EmployeeExit::class, EmployeeExitPolicy::class);
128130
Gate::policy(EmployeePositionChange::class, PositionChangePolicy::class);
129131
Gate::policy(SalaryGrade::class, SalaryGradePolicy::class);
132+
Gate::policy(OvertimeRequest::class, OvertimeRequestPolicy::class);
130133
}
131134
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,12 @@
249249
Route::middleware(['web', 'auth', 'verified'])->prefix('hr')->name('hr.')->group(function () {
250250
Route::resource('salary-grades', SalaryGradeController::class)->except(['create', 'edit']);
251251
});
252+
253+
// Overtime Requests
254+
use App\Modules\HR\Http\Controllers\OvertimeRequestController;
255+
Route::middleware(['web', 'auth', 'verified'])->prefix('hr')->name('hr.')->group(function () {
256+
Route::post('overtime-requests/{overtimeRequest}/approve', [OvertimeRequestController::class, 'approve'])->name('overtime-requests.approve');
257+
Route::post('overtime-requests/{overtimeRequest}/reject', [OvertimeRequestController::class, 'reject'])->name('overtime-requests.reject');
258+
Route::post('overtime-requests/{overtimeRequest}/cancel', [OvertimeRequestController::class, 'cancel'])->name('overtime-requests.cancel');
259+
Route::resource('overtime-requests', OvertimeRequestController::class)->only(['index', 'store', 'show', 'destroy']);
260+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration {
8+
public function up(): void
9+
{
10+
Schema::create('overtime_requests', function (Blueprint $table) {
11+
$table->id();
12+
$table->unsignedBigInteger('tenant_id');
13+
$table->unsignedBigInteger('employee_id');
14+
$table->date('work_date');
15+
$table->decimal('hours', 5, 2);
16+
$table->decimal('rate_multiplier', 4, 2)->default(1.5);
17+
$table->text('reason')->nullable();
18+
$table->string('status')->default('pending'); // pending/approved/rejected/cancelled
19+
$table->unsignedBigInteger('approved_by')->nullable();
20+
$table->timestamp('approved_at')->nullable();
21+
$table->text('rejection_reason')->nullable();
22+
$table->timestamps();
23+
$table->softDeletes();
24+
});
25+
}
26+
27+
public function down(): void
28+
{
29+
Schema::dropIfExists('overtime_requests');
30+
}
31+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from 'react';
2+
3+
export default function OvertimeRequestIndex() {
4+
return <div>Overtime Requests</div>;
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from 'react';
2+
3+
export default function OvertimeRequestShow() {
4+
return <div>Overtime Request</div>;
5+
}

0 commit comments

Comments
 (0)