penalty calculation

parent 1763f0f1
......@@ -46,7 +46,7 @@
<td>{{ $claim->claim_date }}</td>
<td>{{ $claim->description ?? '-' }}</td>
<td>{{ max(0, $claim->total_mileage - 40) }} km</td>
<td>RM {{ number_format($claim->calculated_amount, 2) }}</td>
<td>RM {{ number_format($claim->total_claim_amount, 2) }}</td>
<td>{{ $claim->status }}</td>
<td style="white-space:nowrap;">
<a href="{{ route('mileage::BOD.show', $claim->id) }}" class="ui blue button tiny">
......@@ -99,7 +99,7 @@
<td>{{ $claim->claim_date }}</td>
<td>{{ $claim->description ?? '-' }}</td>
<td>{{ max(0, $claim->total_mileage - 40) }} km</td>
<td>RM {{ number_format($claim->calculated_amount, 2) }}</td>
<td>RM {{ number_format($claim->total_claim_amount, 2) }}</td>
<td>{{ $claim->status }}</td>
<td>
<a href="{{ route('mileage::BOD.show', $claim->id) }}" class="ui blue button tiny">Lihat</a>
......
......@@ -134,15 +134,27 @@
</td>
</tr>
<tr class="totals-row">
<td>Jumlah Akhir (RM)</td>
<td>
@php
$final = $claim->total_claim_amount ?? round($subtotal - ($displayPenalty ?? 0), 2);
@endphp
<strong>RM {{ number_format(floatval($final), 2) }}</strong>
</td>
</tr>
<tr>
<td>Penalti</td>
<td>
@if($claim->display_penalty_amount > 0)
<span style="color:#e53935;">
- RM {{ number_format($claim->display_penalty_amount, 2) }}
({{ $claim->display_penalty_reason }})
</span>
@else
{{ $claim->display_penalty_reason ?? 'Tiada penalti' }}
@endif
</td>
</tr>
<tr class="totals-row">
<td>Jumlah Akhir (RM)</td>
<td>
<strong>RM {{ number_format(floatval($claim->display_total_claim_amount ?? $subtotal), 2) }}</strong>
</td>
</tr>
</table>
</div>
......
......@@ -40,10 +40,10 @@
.ui.celled.table th:nth-last-child(2),
.ui.celled.table td:nth-last-child(2) {
width: 10%; /* for Status */
/* } */
/* } */
@keyframes spin {
to { transform: rotate(360deg); }
@keyframes spin {
to { transform: rotate(360deg); }
}
#loader {
......@@ -94,7 +94,19 @@
<tr>
<td style = "text-align: center;">{{ $loop->iteration }}</td>
<td>{{ $claim->user->name ?? '-' }}</td>
<td>{{ $claim->claim_date }}</td>
<td>
{{ $claim->claim_date }}
@php
$days = \Carbon\Carbon::parse($claim->claim_date)->diffInDays($claim->created_at ?? now());
@endphp
@if($days > 180)
<br><span class="ui red label tiny">Tertakluk BOD (&gt;6 bulan)</span>
@elseif($days > 90)
<br><span class="ui red label tiny">Lewat 3-6 bulan (-30%)</span>
@elseif($days > 30)
<span class="ui yellow label tiny">Lewat 1-3 bulan (-10%)</span>
@endif
</td>
<td>{{ $claim->description ?? '-' }}</td>
<td>{{ max(0, $claim->total_mileage - 40) }} km</td>
<td>RM {{ number_format($claim->total_claim_amount, 2) }}</td>
......@@ -150,7 +162,7 @@
<td>{{ $claim->claim_date }}</td>
<td>{{ $claim->description ?? '-' }}</td>
<td>{{ max(0, $claim->total_mileage - 40) }} km</td>
<td>RM {{ number_format($claim->calculated_amount, 2) }}</td>
<td>RM {{ number_format($claim->total_claim_amount, 2) }}</td>
<td>{{ $claim->status }}</td>
<td>
<a href="{{ route('mileage::HOD.show', $claim->id) }}" class="ui blue button tiny">
......
......@@ -133,14 +133,24 @@
RM {{ number_format($subtotal, 2) }}
</td>
</tr>
<tr>
<td>Penalti</td>
<td>
@if($claim->display_penalty_amount > 0)
<span style="color:#e53935;">
- RM {{ number_format($claim->display_penalty_amount, 2) }}
({{ $claim->display_penalty_reason }})
</span>
@else
{{ $claim->display_penalty_reason ?? 'Tiada penalti' }}
@endif
</td>
</tr>
<tr class="totals-row">
<td>Jumlah Akhir (RM)</td>
<td>
@php
$final = $claim->total_claim_amount ?? round($subtotal - ($displayPenalty ?? 0), 2);
@endphp
<strong>RM {{ number_format(floatval($final), 2) }}</strong>
<strong>RM {{ number_format(floatval($claim->display_total_claim_amount ?? $subtotal), 2) }}</strong>
</td>
</tr>
</table>
......
......@@ -14,8 +14,8 @@
margin-top: 2rem;
padding: 1.5rem;
}
.ui.button {
margin-top: 1rem;
.ui.button {
margin-top: 1rem;
}
/* --- Larger, more comfortable action buttons --- */
......@@ -244,8 +244,8 @@
<td>
<div class="actions-group">
{{-- Lihat (View) button - always visible --}}
<a href="{{ route('mileage::finance.viewEach', $claim->id) }}"
class="ui blue button tiny"
<a href="{{ route('mileage::finance.viewEach', $claim->id) }}"
class="ui blue button tiny"
title="Lihat Butiran">
<i class="eye icon"></i> Lihat
</a>
......@@ -283,6 +283,16 @@
</button>
</form>
@endif
{{-- Show Bayar button only if claim is Disemak --}}
@if($claim->status === 'Disemak')
<form action="{{ route('mileage::finance.updateStatus', $claim->id) }}" method="POST" class="inline-form">
@csrf
@method('PUT')
<button type="submit" name="action" value="Dibayar" class="ui teal button tiny">
<i class="money bill alternate icon"></i> Bayar
</button>
</form>
@endif
</div>
</td>
</tr>
......@@ -360,25 +370,10 @@
$subtotal = $mileageAmount + $totalToll + $totalOthers;
// === Penalty ===
$claimMonthEnd = Carbon::parse($month)->endOfMonth();
$latestClaim = $validInMonth->sortByDesc('created_at')->first();
$submissionDate = $latestClaim ? Carbon::parse($latestClaim->created_at) : now();
$monthsDiff = $claimMonthEnd->diffInMonths($submissionDate, false);
if ($monthsDiff <= 1) {
$penaltyRate = 0; $penaltyLabel = "Tiada penalti (≤ 1 bulan)";
} elseif ($monthsDiff <= 3) {
$penaltyRate = 0.10; $penaltyLabel = "Penalti 10% (lewat ≤ 3 bulan)";
} elseif ($monthsDiff <= 6) {
$penaltyRate = 0.30; $penaltyLabel = "Penalti 30% (lewat ≤ 6 bulan)";
} else {
$penaltyRate = 'BOD'; $penaltyLabel = "Tertakluk kepada BOD (> 6 bulan)";
}
$penaltyAmount = $penaltyRate === 'BOD' ? 0 : $subtotal * $penaltyRate;
$finalTotal = $subtotal - $penaltyAmount;
$finalTotal = $subtotal - $totalPenalty;
@endphp
<div class="ui segment">
<h4><i class="calculator icon"></i> Ringkasan Pengiraan Bulanan</h4>
<table class="ui definition table">
......@@ -388,7 +383,7 @@
<tr>
<td>Jumlah Layak Tuntut</td>
<td>
{{ number_format($totalMileage,1) }} KM − {{ number_format($deduction,1) }} KM
{{ number_format($totalMileage,1) }} KM − {{ number_format($deduction,1) }} KM
= <strong>{{ number_format($claimableDistance,1) }} KM</strong>
</td>
</tr>
......@@ -407,13 +402,21 @@
<tr>
<td>Penalti</td>
<td>
@if($penaltyRate==='BOD')
<span style="color:#d35400;"><i class="exclamation triangle icon"></i> {{ $penaltyLabel }}</span>
@if($isBOD)
<span style="color:#d35400;"><i class="exclamation triangle icon"></i> Tertakluk kepada BOD</span>
@elseif($totalPenalty > 0)
Tolak: RM {{ number_format($totalPenalty, 2) }}<br>
@foreach($penaltyBreakdown as $p)
@if($p['penalty'] > 0)
• {{ \Carbon\Carbon::parse($p['claim_date'])->format('d/m/Y') }}: {{ $p['label'] }} (RM {{ number_format($p['penalty'], 2) }})<br>
@endif
@endforeach
@else
{{ $penaltyLabel }}<br>Tolak: RM {{ number_format($penaltyAmount,2) }}
Tiada penalti
@endif
</td>
</tr>
<tr class="totals-row">
<td><strong>Jumlah Akhir (Selepas Penalti)</strong></td>
<td><strong>RM {{ number_format($finalTotal,2) }}</strong></td>
......@@ -469,7 +472,7 @@
<div class="field" id="othersDetailsField" style="display:none;">
<label>Butiran Lain-lain</label>
<input type="text" name="others_details" id="editOthersDetails"
<input type="text" name="others_details" id="editOthersDetails"
placeholder="Nyatakan butiran lain-lain (contoh: bayaran penghantaran, dokumen, dsb)">
</div>
</div>
......
<?php
<?php
use Illuminate\Support\Facades\Route;
use Portal\Mileage\Http\Controllers\ApplyformController;
......@@ -35,8 +35,8 @@ function () {
// Project Manager
Route::prefix('projectManager')->name('projectManager')->group(function () {
Route::get('/', [ProjectManagerController::class, 'index'])->name('.index');
Route::get('/show/{projectId}/{userId}', [ProjectManagerController::class, 'show'])->name('.show');
Route::get('/', [ProjectManagerController::class, 'index'])->name('.index');
Route::get('/show/{projectId}/{userId}', [ProjectManagerController::class, 'show'])->name('.show');
Route::post('/approve-month', [ProjectManagerController::class, 'approveMonth'])->name('.approveMonth');
Route::post('/reject-month', [ProjectManagerController::class, 'rejectMonth'])->name('.rejectMonth');
Route::post('/claim/{id}/approve', [ProjectManagerController::class, 'approveClaim'])->name('.approveClaim');
......@@ -46,7 +46,7 @@ function () {
// Project Director
Route::prefix('projectDirector')->name('projectDirector')->group(function () {
Route::get('/dashboard', [ProjectDirectorController::class, 'index'])->name('.index');
Route::get('/show/{projectId}/{userId}', [ProjectDirectorController::class, 'show'])->name('.show');
Route::get('/show/{projectId}/{userId}', [ProjectDirectorController::class, 'show'])->name('.show');
Route::post('/approve-month', [ProjectDirectorController::class, 'approveMonth'])->name('.approveMonth');
Route::post('/reject-month', [ProjectDirectorController::class, 'rejectMonth'])->name('.rejectMonth');
Route::post('/claim/{id}/approve', [ProjectDirectorController::class, 'approveClaim'])->name('.approveClaim');
......@@ -60,10 +60,10 @@ function () {
Route::put('/finance/update/{id}', [FinanceController::class, 'update'])->name('.update');
Route::get('/view/claim/{id}', [FinanceController::class, 'viewEach'])->name('.viewEach');
Route::put('/finance/claims/{id}/update-status', [FinanceController::class, 'updateStatus'])->name('.updateStatus');
Route::post('/{id}/review', [FinanceController::class, 'review'])->name('.review');
Route::post('/{id}/reject', [FinanceController::class, 'reject'])->name('.reject');
// Route::post('/{id}/review', [FinanceController::class, 'review'])->name('.review');
// Route::post('/{id}/reject', [FinanceController::class, 'reject'])->name('.reject');
Route::get('/export', [FinanceController::class, 'export'])->name('.export');
});
// HOD
......@@ -71,14 +71,14 @@ function () {
Route::get('/dashboard', [HODController::class, 'index'])->name('.index');
Route::get('/show/{id}', [HODController::class, 'show'])->name('.show');
Route::post('/{id}/verify', [HODController::class, 'verify'])->name('.verify');
Route::post('/{id}/reject', [HODController::class, 'reject'])->name('.reject');
Route::post('/{id}/reject', [HODController::class, 'reject'])->name('.reject');
});
// BOD
Route::prefix('BOD')->name('BOD')->group(function () {
Route::get('/dashboard', [BODController::class, 'index'])->name('.index');
Route::get('/show/{id}', [BODController::class, 'show'])->name('.show');
Route::post('/{id}/review', [BODController::class, 'approve'])->name('.approve');
Route::post('/{id}/review', [BODController::class, 'approve'])->name('.approve');
Route::post('/{id}/reject', [BODController::class, 'reject'])->name('.reject');
});
......
......@@ -196,7 +196,7 @@ public function show(Request $request, $id)
public function viewEach($id)
{
$claim = Claim::with(['project', 'jenisKenderaan'])->findOrFail($id);
return view('mileage.applications.viewEach', compact('claim'));
return view('mileage::application.view', compact('claim'));
}
public function updateStatus(Request $request, $id)
......
......@@ -6,7 +6,7 @@
use Portal\Mileage\Model\JenisKenderaan;
use Portal\Mileage\Model\ProjectTeam;
use Portal\Mileage\Model\Users;
use Portal\Mileage\Model\Claim;
use Illuminate\Support\Facades\Auth;
use Illuminate\Routing\Controller;
use Illuminate\Http\Request;
......@@ -126,7 +126,7 @@ public function noproject()
{
$user = Auth::user();
$jenisKenderaan = JenisKenderaan::all();
return view('mileage::applyform.noproject', compact('jenisKenderaan'));
}
......
......@@ -44,7 +44,13 @@ public function show($id)
$submissionDate = $claim->created_at ?? Carbon::now();
$daysDiff = $claimDate->diffInDays($submissionDate);
$calculatedAmount = $claim->calculated_amount ?? 0;
$claimableMileage = max(0, ($claim->total_mileage ?? 0) - 40);
if ($claim->jenis_kenderaan_id == 1) {
$rate = ($claimableMileage <= 500) ? 0.55 : 0.50;
} else {
$rate = 0.30;
}
$calculatedAmount = round($claimableMileage * $rate, 2);
if ($daysDiff <= 30) {
$claim->display_penalty_amount = 0;
......
......@@ -41,7 +41,7 @@ public function index(Request $request)
'id' => $user->id ?? null,
'name' => $user->name ?? '-',
'total_claims' => $userClaims->count(),
'total_amount' => $userClaims->sum('calculated_amount'),
'total_amount' => $userClaims->sum('total_claim_amount'),
];
})
->sortBy('name')
......@@ -51,20 +51,67 @@ public function index(Request $request)
}
public function show($userId, Request $request)
{
$month = $request->get('month', Carbon::now()->format('Y-m'));
$parsedMonth = Carbon::parse($month);
{
$month = $request->get('month', Carbon::now()->format('Y-m'));
$parsedMonth = Carbon::parse($month);
$user = Users::findOrFail($userId);
$claims = Claim::with(['project', 'jenisKenderaan'])
->where('user_id', $userId)
->whereMonth('claim_date', $parsedMonth->month)
->whereYear('claim_date', $parsedMonth->year)
->get();
// === Calculate per-claim penalty (same formula as HOD/BOD) ===
$totalPenalty = 0;
$isBOD = false;
$penaltyBreakdown = [];
foreach ($claims as $claim) {
$claimDate = Carbon::parse($claim->claim_date);
$submissionDate = $claim->created_at ?? Carbon::now();
$daysDiff = $claimDate->diffInDays($submissionDate);
$claimableMileage = max(0, ($claim->total_mileage ?? 0) - 40);
if ($claim->jenis_kenderaan_id == 1) {
$rate = ($claimableMileage <= 500) ? 0.55 : 0.50;
} else {
$rate = 0.30;
}
$claimMileageAmount = round($claimableMileage * $rate, 2);
if ($daysDiff <= 30) {
$penaltyRate = 0;
$penaltyLabel = 'Tiada penalti (≤ 1 bulan)';
} elseif ($daysDiff <= 90) {
$penaltyRate = 0.10;
$penaltyLabel = 'Lewat hantar >1 ≤3 bulan (10%)';
} elseif ($daysDiff <= 180) {
$penaltyRate = 0.30;
$penaltyLabel = 'Lewat hantar >3 ≤6 bulan (30%)';
} else {
$penaltyRate = 'BOD';
$penaltyLabel = 'Tertakluk kepada BOD';
$isBOD = true;
}
$user = Users::findOrFail($userId);
$claims = Claim::with(['project', 'jenisKenderaan'])
->where('user_id', $userId)
->whereMonth('claim_date', $parsedMonth->month)
->whereYear('claim_date', $parsedMonth->year)
->get();
$claimPenalty = $penaltyRate === 'BOD' ? 0 : round($claimMileageAmount * $penaltyRate, 2);
$totalPenalty += $claimPenalty;
return view('mileage::finance.show', compact('user', 'claims', 'month'));
$penaltyBreakdown[] = [
'claim_date' => $claim->claim_date,
'penalty' => $claimPenalty,
'label' => $penaltyLabel,
];
}
return view('mileage::finance.show', compact(
'user', 'claims', 'month',
'totalPenalty', 'isBOD', 'penaltyBreakdown'
));
}
public function viewEach($id)
{
$claim = Claim::with(['user', 'project', 'jenisKenderaan'])->findOrFail($id);
......@@ -104,8 +151,8 @@ public function update(Request $request, $id)
$vehicleType = strtolower($claim->jenisKenderaan->name ?? 'kereta');
$mileage = $claim->total_mileage ?? 0;
$rateCar1 = 0.55;
$rateCar2 = 0.50;
$rateCar1 = 0.55;
$rateCar2 = 0.50;
$rateMoto = 0.30;
if (in_array($vehicleType, ['motosikal', 'motorcycle'])) {
......@@ -127,7 +174,7 @@ public function updateStatus(Request $request, $id)
$claim = Claim::findOrFail($id);
$action = $request->input('action');
if (!in_array($action, ['Disemak', 'Ditolak'])) {
if (!in_array($action, ['Disemak', 'Ditolak', 'Dibayar'])) {
return back()->with('error', 'Tindakan tidak sah.');
}
......
......@@ -57,7 +57,13 @@ public function show($id)
$claimDate = Carbon::parse($claim->claim_date);
$submissionDate = $claim->created_at ?? Carbon::now();
$daysDiff = $claimDate->diffInDays($submissionDate);
$calculatedAmount = $claim->calculated_amount ?? 0;
$claimableMileage = max(0, ($claim->total_mileage ?? 0) - 40);
if ($claim->jenis_kenderaan_id == 1) {
$rate = ($claimableMileage <= 500) ? 0.55 : 0.50;
} else {
$rate = 0.30;
}
$calculatedAmount = round($claimableMileage * $rate, 2);
if ($daysDiff <= 30) {
$displayPenalty = 0;
......
......@@ -16,7 +16,8 @@ class Claim extends Model
protected $fillable = [
'user_id','claim_date', 'jenis_kenderaan_id', 'project_id', 'description',
'distance_from', 'distance_to',
'total_mileage', 'toll_amount', 'others_amount', 'others_description',
'total_mileage', 'toll_amount', 'others_amount', 'others_details',
'claimable_mileage', 'rate', 'mileage_amount',
'calculated_amount','penalty_amount', 'penalty_reason', 'total_claim_amount',
'status',
];
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment