Commit a3cb80f0 by Hannah Zahra

Fifth Update

parent 90f032c8
......@@ -57,6 +57,10 @@ public function up()
$table->unsignedInteger('rejected_by')->nullable();
$table->foreign('rejected_by')->references('id')->on('users')->nullOnDelete();
// Reject reason
$table->string('reject_reason', 500)->nullable();
// Claim status (workflow)
$table->enum('status', [
'Diproses',
......@@ -88,6 +92,8 @@ public function down()
$table->dropForeign(['rejected_by']);
$table->dropColumn('rejected_by');
$table->dropColumn('reject_reason');
});
// Drop the 'claims' table entirely
......
......@@ -371,7 +371,19 @@
<td>RM {{ number_format($totalAmount, 2) }}</td>
<td>
@foreach($projectClaims->pluck('status')->unique() as $status)
<span class="status-label status-{{ $status }}">{{ $status }}</span>
@if($status === 'Ditolak')
<span class="status-label status-Ditolak"
data-tooltip="Tekan butang 'Lihat' untuk lihat butiran tuntutan ini."
data-inverted
data-position="top center"
style="cursor: help;">
{{ $status }}
</span>
@else
<span class="status-label status-{{ $status }}">
{{ $status }}
</span>
@endif
@endforeach
</td>
<td>
......@@ -409,7 +421,19 @@
<td>{{ number_format($claim->total_mileage, 1) }}</td>
<td>RM {{ number_format($claim->total_claim_amount, 2) }}</td>
<td>
<span class="status-label status-{{ $claim->status }}">{{ $claim->status }}</span>
@if($claim->status === 'Ditolak')
<span class="status-label status-Ditolak"
data-tooltip="{{ $claim->reject_reason ?? 'Tiada sebab diberikan.' }}"
data-inverted
data-position="top center"
style="cursor: help;">
{{ $claim->status }}
</span>
@else
<span class="status-label status-{{ $claim->status }}">
{{ $claim->status }}
</span>
@endif
</td>
<td>
{{-- Always show Lihat --}}
......@@ -525,6 +549,17 @@
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
$(document).ready(function() {
// Initialize Semantic/Fomantic UI tooltips
$('[data-tooltip]').popup({
variation: 'very wide',
hoverable: true,
position: 'top center',
delay: {
show: 100,
hide: 200
}
});
// Toggle Summary Section
$('#summaryToggle').on('click', function() {
$(this).toggleClass('active');
......
......@@ -58,6 +58,7 @@
<th>Lain-lain (RM)</th>
<th>Status</th>
<th>Jumlah (RM)</th>
<th>Tindakan</th>
</tr>
</thead>
<tbody>
......@@ -70,7 +71,7 @@
@foreach($claims as $c)
@php
$total += $c->total_claim_amount;
$total += $c->total_claim;
$totalMileage += $c->total_mileage ?? 0;
$totalToll += $c->toll_amount ?? 0;
$totalOthers += $c->others_amount ?? 0;
......@@ -85,9 +86,51 @@
<td>{{ number_format($c->toll_amount ?? 0, 2) }}</td>
<td>{{ number_format($c->others_amount ?? 0, 2) }}</td>
<td>
<span class="status-label status-{{ $c->status }}">{{ $c->status }}</span>
@if($c->status === 'Ditolak')
<span class="status-label status-Ditolak"
data-tooltip="{{ $c->reject_reason ?? 'Tiada sebab diberikan.' }}"
data-inverted
data-position="top center"
style="cursor: help;">
{{ $c->status }}
</span>
@else
<span class="status-label status-{{ $c->status }}">
{{ $c->status }}
</span>
@endif
</td>
<td>
RM {{ number_format($c->total_claim_amount, 2) }}
</td>
<td>
{{-- actions for Diproses --}}
@if($c->status === 'Diproses')
<div style="margin-top:8px;">
<button class="ui yellow tiny button edit-btn"
data-id="{{ $c->id }}"
data-date="{{ $c->claim_date }}"
data-description="{{ $c->description }}"
data-distance-from="{{ $c->distance_from }}"
data-distance-to="{{ $c->distance_to }}"
data-mileage="{{ $c->total_mileage }}"
data-toll="{{ $c->toll_amount }}"
data-others="{{ $c->others_amount }}">
<i class="edit icon"></i> Kemaskini
</button>
<form action="{{ route('mileage::applications.destroy', $c->id) }}"
method="POST" style="display:inline;"
onsubmit="return confirm('Padam tuntutan ini?');">
@csrf
@method('DELETE')
<button type="submit" class="ui red tiny button">
<i class="trash icon"></i> Padam
</button>
</form>
</div>
@endif
</td>
<td>RM {{ number_format($c->total_claim_amount, 2) }}</td>
</tr>
@endforeach
</tbody>
......@@ -118,5 +161,145 @@
<a href="{{ route('mileage::applications.index', ['month' => $month]) }}" class="ui grey button">
<i class="arrow left icon"></i> Kembali
</a>
{{-- Edit Modal --}}
<div class="ui modal" id="editModal">
<i class="close icon"></i>
<div class="header">Kemaskini Tuntutan</div>
<div class="content">
<form id="editForm" method="POST" action="">
@csrf
@method('PUT')
<div class="ui form">
<div class="field required">
<label>Tarikh</label>
<input type="date" name="claim_date" id="editDate" required>
</div>
<div class="two fields">
<div class="field required">
<label>Dari</label>
<input type="text" name="distance_from" id="editFrom" required>
</div>
<div class="field required">
<label>Ke</label>
<input type="text" name="distance_to" id="editTo" required>
</div>
</div>
<div class="field required">
<label>Butiran</label>
<input type="text" name="description" id="editDescription" required>
</div>
<div class="three fields">
<div class="field required">
<label>Jumlah KM</label>
<input type="number" step="0.1" name="total_mileage" id="editMileage" required>
</div>
<div class="field">
<label>Tol (RM)</label>
<input type="number" step="0.01" name="toll_amount" id="editToll">
</div>
<div class="field">
<label>Lain-lain (RM)</label>
<input type="number" step="0.01" name="others_amount" id="editOthers">
</div>
</div>
<div class="field" id="othersDetailsField" style="display:none;">
<label>Butiran Lain-lain</label>
<input type="text" name="others_details" id="editOthersDetails"
placeholder="Nyatakan butiran lain-lain (contoh: bayaran penghantaran, dokumen, dsb)">
</div>
</div>
</form>
</div>
<div class="actions">
<div class="ui cancel button">Batal</div>
<button type="submit" form="editForm" class="ui primary button">Simpan</button>
</div>
</div>
{{-- Edit Modal Script --}}
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
$(document).ready(function() {
// Initialize tooltips for Ditolak statuses
$('[data-tooltip]').popup({
variation: 'very wide',
hoverable: true,
position: 'top center',
delay: {
show: 100,
hide: 200
}
});
// Handle Edit button click
$('.edit-btn').on('click', function() {
const id = $(this).data('id');
const date = $(this).data('date');
const desc = $(this).data('description');
const from = $(this).data('distance-from');
const to = $(this).data('distance-to');
const mileage = $(this).data('mileage');
const toll = $(this).data('toll');
const others = $(this).data('others');
const updateUrl = "{{ route('mileage::applications.update', ':id') }}".replace(':id', id);
$('#editForm').attr('action', updateUrl);
$('#editDate').val(date);
$('#editDescription').val(desc);
$('#editFrom').val(from);
$('#editTo').val(to);
$('#editMileage').val(mileage);
$('#editToll').val(toll);
$('#editOthers').val(others);
// Trigger others field logic
$('#editOthers').trigger('input');
$('#editModal').modal('show');
});
// Show/Hide "Butiran Lain-lain" based on input
$('#editOthers').on('input', function() {
const value = parseFloat($(this).val()) || 0;
if (value > 0) {
$('#othersDetailsField').slideDown();
$('#editOthersDetails').attr('required', true);
} else {
$('#othersDetailsField').slideUp();
$('#editOthersDetails').removeAttr('required');
$('#editOthersDetails').val('');
}
});
// Validate before form submission
$('#editForm').on('submit', function(e) {
const others = parseFloat($('#editOthers').val()) || 0;
const othersDetails = $('#editOthersDetails').val().trim();
if (others > 0 && othersDetails === '') {
e.preventDefault();
alert('Sila isi Butiran Lain-lain kerana jumlah Lain-lain (RM) lebih daripada 0.');
$('#editOthersDetails').focus();
return;
}
let valid = true;
$('#editForm [required]').each(function() {
if ($(this).val().trim() === '') {
valid = false;
$(this).focus();
alert('Sila isi semua ruangan yang diperlukan.');
e.preventDefault();
return false;
}
});
});
});
</script>
</div>
@endsection
......@@ -3,10 +3,23 @@
@section('content')
<style>
body { background-color: #f0f2f5; }
.ui.container { padding: 0 5rem 3rem 2rem; margin-top: 3rem; }
h2.ui.header { font-size: 1.8rem; font-weight: 700; margin-bottom: .5rem; }
.ui.table { background: white; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
body {
background-color: #f0f2f5;
}
.ui.container {
padding: 0 5rem 3rem 2rem;
margin-top: 3rem;
}
h2.ui.header {
font-size: 1.8rem;
font-weight: 700;
margin-bottom: .5rem;
}
.ui.table {
background: white;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.ui.segment {
background: white;
border-radius: 10px;
......@@ -54,6 +67,11 @@
gap: 1rem;
margin-top: 1rem;
}
.approval-actions .ui.button {
height: 42px;
text-align: center;
}
</style>
<div class="ui container">
......@@ -82,6 +100,7 @@
<th>Lain-lain (RM)</th>
<th>Status</th>
<th>Jumlah (RM)</th>
<th>Tindakan</th>
</tr>
</thead>
<tbody>
......@@ -112,24 +131,46 @@
</span>
</td>
<td>{{ number_format($claim->total_claim_amount, 2) }}</td>
<td>
@if($claim->status === 'Disokong')
<form action="{{ route('mileage::projectDirector.approveClaim', $claim->id) }}" method="POST" style="display:inline;">
@csrf
<button type="submit" class="ui green tiny button">
<i class="check icon"></i> Sahkan
</button>
</form>
<button
type="button"
class="ui red tiny button reject-single-btn"
data-claim-id="{{ $claim->id }}"
data-project="{{ $project->id }}"
data-user="{{ $user->id }}"
data-month="{{ $month }}">
<i class="times icon"></i> Tolak
</button>
@else
<span style="color:#999;"></span>
@endif
</td>
</tr>
@if(!empty($claim->others_description))
<tr style="background: #f9f9f9;">
<td colspan="9">
<td colspan="10">
<strong>Butiran Lain-lain:</strong> {{ $claim->others_description }}
</td>
</tr>
@endif
@empty
<tr><td colspan="9" class="center aligned">Tiada tuntutan untuk bulan ini.</td></tr>
<tr><td colspan="10" class="center aligned">Tiada tuntutan untuk bulan ini.</td></tr>
@endforelse
</tbody>
@if($claims->count() > 0)
<tfoot>
<tr>
<th colspan="8" class="right aligned">Jumlah Keseluruhan</th>
<th colspan="9" class="right aligned">Jumlah Keseluruhan</th>
<th>RM {{ number_format($total, 2) }}</th>
</tr>
</tfoot>
......@@ -151,10 +192,8 @@
</span>
</div>
{{-- Only show buttons if there are still claims waiting for Director verification --}}
@if($hasPending)
<div class="approval-actions">
{{-- Approve All --}}
<form action="{{ route('mileage::projectDirector.approveMonth') }}" method="POST" style="display:inline;">
@csrf
<input type="hidden" name="project_id" value="{{ $project->id }}">
......@@ -165,77 +204,89 @@
</button>
</form>
{{-- Reject All --}}
<form action="{{ route('mileage::projectDirector.rejectMonth') }}" method="POST" style="display:inline;">
@csrf
<input type="hidden" name="project_id" value="{{ $project->id }}">
<input type="hidden" name="user_id" value="{{ $user->id }}">
<input type="hidden" name="month" value="{{ $month }}">
<button type="submit" class="ui red button">
<i class="times icon"></i> Tolak Semua
</button>
</form>
<button
type="button"
class="ui red button reject-btn"
data-project="{{ $project->id }}"
data-user="{{ $user->id }}"
data-month="{{ $month }}">
<i class="times icon"></i> Tolak Semua
</button>
</div>
@endif
</div>
@endif
</div>
{{-- Calculation Summary Section --}}
@if($claims->count() > 0)
@php
$totalMileage = $claims->sum('total_mileage');
$totalClaimAmount = $claims->sum('total_claim_amount');
$totalToll = $claims->sum('toll_amount');
$totalOthers = $claims->sum('others_amount');
$latestClaim = $claims->sortByDesc('claim_date')->first();
$penaltyAmount = $latestClaim->penalty_amount ?? 0;
$penaltyReason = $latestClaim->penalty_reason ?? '-';
$vehicleType = strtolower($claims->first()->jenisKenderaan->name ?? 'kereta');
$uniqueDates = $claims->pluck('claim_date')->unique()->count();
$deduction = 40 * $uniqueDates;
$claimableDistance = max(0, $totalMileage - $deduction);
if ($vehicleType === 'motosikal' || $vehicleType === 'motorcycle') {
$rateNote = "Motosikal – RM0.30/km";
} elseif ($claimableDistance <= 500) {
$rateNote = "Kereta – RM0.55/km (≤500km)";
} else {
$secondPartKm = $claimableDistance - 500;
$rateNote = "Kereta – 500km × RM0.55 + " . number_format($secondPartKm, 1) . "km × RM0.50";
}
$finalTotal = $totalClaimAmount + $totalToll + $totalOthers - $penaltyAmount;
@endphp
<div class="ui segment">
<h4><i class="calculator icon"></i> Ringkasan Pengiraan (Mengikut Rekod Sistem)</h4>
<table class="ui definition table">
<tbody>
<tr><td><strong>Jumlah Jarak (Sebenar)</strong></td><td>{{ number_format($totalMileage, 1) }} KM</td></tr>
<tr><td><strong>Jumlah Hari Tuntutan</strong></td><td>{{ $uniqueDates }} hari</td></tr>
<tr><td><strong>Potongan 40KM/Hari</strong></td><td>40 × {{ $uniqueDates }} = {{ $deduction }} KM</td></tr>
<tr><td><strong>Jumlah Jarak Boleh Tuntut</strong></td><td>{{ number_format($claimableDistance, 1) }} KM</td></tr>
<tr><td><strong>Kadar Kiraan</strong></td><td>{{ $rateNote }}</td></tr>
<tr><td><strong>Jumlah Mileage (Dari Sistem)</strong></td><td>RM {{ number_format($totalClaimAmount, 2) }}</td></tr>
<tr><td><strong>Jumlah Tol</strong></td><td>RM {{ number_format($totalToll, 2) }}</td></tr>
<tr><td><strong>Jumlah Lain-lain</strong></td><td>RM {{ number_format($totalOthers, 2) }}</td></tr>
<tr><td><strong>Penalti</strong></td><td>RM {{ number_format($penaltyAmount, 2) }} ({{ $penaltyReason }})</td></tr>
<tr><td><strong><span style="color:#21ba45;">Jumlah Akhir</span></strong></td><td><strong>RM {{ number_format($finalTotal, 2) }}</strong></td></tr>
</tbody>
</table>
</div>
@endif
{{-- Calculation Rule Reminder --}}
<div class="ui segment">
<h4><i class="info circle icon"></i> Seksyen Pengiraan:</h4>
<p>
• Tuntutan bermula selepas 40KM pertama setiap hari.<br>
• Kadar (Kereta): RM0.55/km (&lt;500km), RM0.50/km (&gt;500km).<br>
• Kadar (Motosikal): RM0.30/km.<br>
• Penalti dikenakan sekiranya tuntutan lewat dihantar (&gt;1 bulan).
</p>
{{-- Reject Modal --}}
<div class="ui small modal" id="rejectModal">
<i class="close icon"></i>
<div class="header">Sebab Penolakan</div>
<div class="content">
<form id="rejectForm" method="POST" action="{{ route('mileage::projectDirector.rejectMonth') }}">
@csrf
<input type="hidden" name="project_id" id="rejectProjectId">
<input type="hidden" name="user_id" id="rejectUserId">
<input type="hidden" name="month" id="rejectMonth">
<div class="ui form">
<div class="field required">
<label>Nyatakan sebab tuntutan ini ditolak:</label>
<textarea
name="reject_reason"
id="rejectReason"
rows="3"
placeholder="Contoh: Tuntutan tidak lengkap atau tiada resit sokongan"
required></textarea>
</div>
</div>
</form>
</div>
<div class="actions">
<div class="ui cancel button">Batal</div>
<button type="submit" form="rejectForm" class="ui red button">Hantar Penolakan</button>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
$(document).ready(function() {
// Handle single claim rejection
$('.reject-single-btn').on('click', function() {
const claimId = $(this).data('claim-id');
const projectId = $(this).data('project');
const userId = $(this).data('user');
const month = $(this).data('month');
const action = "{{ route('mileage::projectDirector.rejectClaim', ':id') }}".replace(':id', claimId);
$('#rejectForm').attr('action', action);
$('#rejectProjectId').val(projectId);
$('#rejectUserId').val(userId);
$('#rejectMonth').val(month);
$('#rejectReason').val('');
$('#rejectModal').modal('show');
});
// Handle bulk rejection
$('.reject-btn').on('click', function() {
$('#rejectProjectId').val($(this).data('project'));
$('#rejectUserId').val($(this).data('user'));
$('#rejectMonth').val($(this).data('month'));
$('#rejectReason').val('');
$('#rejectForm').attr('action', "{{ route('mileage::projectDirector.rejectMonth') }}");
$('#rejectModal').modal('show');
});
// Validate reject form
$('#rejectForm').on('submit', function(e) {
const reason = $('#rejectReason').val().trim();
if (reason === '') {
e.preventDefault();
alert('Sila nyatakan sebab penolakan sebelum menghantar.');
$('#rejectReason').focus();
}
});
});
</script>
@endsection
......@@ -85,6 +85,12 @@
gap: 1rem;
margin-top: 1rem;
}
.approval-actions .ui.button {
height: 42px;
text-align: center;
}
</style>
<div class="ui container">
......@@ -113,6 +119,7 @@
<th>Lain-lain (RM)</th>
<th>Status</th>
<th>Jumlah (RM)</th>
<th>Tindakan</th>
</tr>
</thead>
<tbody>
......@@ -143,6 +150,31 @@
</span>
</td>
<td>{{ number_format($claim->total_claim_amount, 2) }}</td>
<td>
{{-- individual approve --}}
@if($claim->status === 'Diproses')
{{-- only show if status = Diproses --}}
<form action="{{ route('mileage::projectManager.approveClaim', $claim->id) }}" method="POST" style="display:inline;">
@csrf
<button type="submit" class="ui green tiny button">
<i class="check icon"></i> Sahkan
</button>
</form>
<button
type="button"
class="ui red tiny button reject-single-btn"
data-claim-id="{{ $claim->id }}"
data-project="{{ $project->id }}"
data-user="{{ $user->id }}"
data-month="{{ $month }}">
<i class="times icon"></i> Tolak
</button>
@else
{{-- show dash for processed claims --}}
<span style="color:#999;"></span>
@endif
</td>
</tr>
@if(!empty($claim->others_description))
......@@ -196,16 +228,15 @@
</button>
</form>
{{-- Reject All --}}
<form action="{{ route('mileage::projectManager.rejectMonth') }}" method="POST" style="display:inline;">
@csrf
<input type="hidden" name="project_id" value="{{ $project->id }}">
<input type="hidden" name="user_id" value="{{ $user->id }}">
<input type="hidden" name="month" value="{{ $month }}">
<button type="submit" class="ui red button">
<i class="times icon"></i> Tolak Semua
</button>
</form>
{{-- Reject All with Modal Trigger --}}
<button
type="button"
class="ui red button reject-btn"
data-project="{{ $project->id }}"
data-user="{{ $user->id }}"
data-month="{{ $month }}">
<i class="times icon"></i> Tolak Semua
</button>
</div>
@endif
</div>
......@@ -246,7 +277,7 @@
}
// now match the total (no recomputation)
$finalTotal = $totalClaimAmount + $totalToll + $totalOthers - $penaltyAmount;
$finalTotal = $totalClaimAmount - $penaltyAmount;
@endphp
<div class="ui segment">
......@@ -309,4 +340,77 @@
</p>
</div>
</div>
{{-- Reject Modal --}}
<div class="ui small modal" id="rejectModal">
<i class="close icon"></i>
<div class="header">Sebab Penolakan</div>
<div class="content">
<form id="rejectForm" method="POST" action="{{ route('mileage::projectManager.rejectMonth') }}">
@csrf
<input type="hidden" name="project_id" id="rejectProjectId">
<input type="hidden" name="user_id" id="rejectUserId">
<input type="hidden" name="month" id="rejectMonth">
<div class="ui form">
<div class="field required">
<label>Nyatakan sebab tuntutan ini ditolak:</label>
<textarea
name="reject_reason"
id="rejectReason"
rows="3"
placeholder="Contoh: Tuntutan tidak lengkap atau tiada resit sokongan"
required></textarea>
</div>
</div>
</form>
</div>
<div class="actions">
<div class="ui cancel button">Batal</div>
<button type="submit" form="rejectForm" class="ui red button">Hantar Penolakan</button>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
$(document).ready(function() {
// Handle single claim rejection
$('.reject-single-btn').on('click', function() {
const claimId = $(this).data('claim-id');
const projectId = $(this).data('project');
const userId = $(this).data('user');
const month = $(this).data('month');
// Change form action dynamically for single reject
const action = "{{ route('mileage::projectManager.rejectClaim', ':id') }}".replace(':id', claimId);
$('#rejectForm').attr('action', action);
// Fill modal hidden fields
$('#rejectProjectId').val(projectId);
$('#rejectUserId').val(userId);
$('#rejectMonth').val(month);
$('#rejectReason').val('');
// Show modal
$('#rejectModal').modal('show');
});
// Show reject modal when button clicked
$('.reject-btn').on('click', function() {
$('#rejectProjectId').val($(this).data('project'));
$('#rejectUserId').val($(this).data('user'));
$('#rejectMonth').val($(this).data('month'));
$('#rejectReason').val(''); // clear textarea
$('#rejectModal').modal('show');
});
// Prevent submit if no reason entered
$('#rejectForm').on('submit', function(e) {
const reason = $('#rejectReason').val().trim();
if (reason === '') {
e.preventDefault();
alert('Sila nyatakan sebab penolakan sebelum menghantar.');
$('#rejectReason').focus();
}
});
});
</script>
@endsection
......@@ -39,6 +39,8 @@ function () {
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');
Route::post('/claim/{id}/reject', [ProjectManagerController::class, 'rejectClaim'])->name('.rejectClaim');
});
// Project Director
......@@ -47,6 +49,8 @@ function () {
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');
Route::post('/claim/{id}/reject', [ProjectDirectorController::class, 'rejectClaim'])->name('.rejectClaim');
});
// Finance Department
......
......@@ -107,43 +107,45 @@ public function update(Request $request, $id)
{
$claim = Claim::findOrFail($id);
// Check if any required DB fields are null before allowing update
$requiredFields = [
'claim_date',
'distance_from',
'distance_to',
'description',
'total_mileage'
];
foreach ($requiredFields as $field) {
if (is_null($claim->$field) || $claim->$field === '') {
return redirect()->back()->with('error', 'Tidak boleh simpan kerana maklumat tuntutan asal tidak lengkap.');
}
}
// Validate new input before updating
// Validate input
$request->validate([
'claim_date' => 'required|date',
'distance_from' => 'required|string',
'distance_to' => 'required|string',
'description' => 'required|string',
'distance_from' => 'required|string|max:255',
'distance_to' => 'required|string|max:255',
'description' => 'required|string|max:255',
'total_mileage' => 'required|numeric|min:0',
'toll_amount' => 'nullable|numeric|min:0',
'others_amount' => 'nullable|numeric|min:0',
'others_details' => 'required_if:others_amount,>,0|string|nullable',
'others_details' => 'nullable|string|max:255',
]);
// If claim data is valid, update it
// Calculate new claim amount using same logic as store()
$totalMileage = (float) $request->total_mileage;
$toll = (float) ($request->toll_amount ?? 0);
$others = (float) ($request->others_amount ?? 0);
// Deduct first 40km
$claimableMileage = $totalMileage > 40 ? $totalMileage - 40 : 0;
// Apply rate logic (same as store)
$rate = $claimableMileage > 500 ? 0.50 : 0.55;
$mileageAmount = $claimableMileage * $rate;
$totalClaim = $mileageAmount + $toll + $others;
// Update the record
$claim->update([
'claim_date' => $request->claim_date,
'distance_from' => $request->distance_from,
'distance_to' => $request->distance_to,
'description' => $request->description,
'total_mileage' => $request->total_mileage,
'toll_amount' => $request->toll_amount,
'others_amount' => $request->others_amount,
'total_mileage' => $totalMileage,
'toll_amount' => $toll,
'others_amount' => $others,
'others_details' => $request->others_details,
'claimable_mileage' => $claimableMileage,
'rate' => $rate,
'mileage_amount' => $mileageAmount,
'total_claim_amount' => $totalClaim,
]);
return redirect()->back()->with('success', 'Tuntutan berjaya dikemaskini.');
......
......@@ -167,6 +167,7 @@ public function rejectMonth(Request $request)
if (!in_array($projectId, $allowedProjectIds)) {
return back()->with('error', 'Anda tidak dibenarkan melakukan tindakan ini untuk projek terpilih.');
}
$rejectReason = $request->input('reject_reason', 'Tiada sebab diberikan.');
// update only claims that belong to this project and month and are Disokong
$rejectedCount = Claim::where('project_id', $projectId)
......@@ -176,13 +177,77 @@ public function rejectMonth(Request $request)
->update([
'status' => 'Ditolak',
'rejected_by' => $director->id,
'reject_reason' => $rejectReason,
'updated_at' => now(),
]);
if ($rejectedCount > 0) {
return back()->with('success', $rejectedCount . ' tuntutan telah ditolak untuk projek ini bagi bulan ' . $monthLabel . '.');
}
return back()->with('warning', 'Tiada tuntutan "Disokong" untuk ditolak bagi projek ini pada bulan ' . $monthLabel . '.');
}
/**
* Approve a single claim.
*/
public function approveClaim($id, Request $request)
{
$director = Auth::user();
$claim = Claim::findOrFail($id);
// Ensure this director has access to the project
$allowedProjectIds = ProjectTeam::where('pt_staff_id', $director->employeeCode)
->where('role_id', 7)
->pluck('fk_project_id')
->toArray();
if (!in_array($claim->project_id, $allowedProjectIds)) {
return back()->with('error', 'Anda tidak dibenarkan untuk meluluskan tuntutan ini.');
}
if ($claim->status !== 'Disokong') {
return back()->with('warning', 'Tuntutan ini tidak berada dalam status Disokong.');
}
$claim->update([
'status' => 'Disahkan',
'approved_by' => $director->id,
'updated_at' => now(),
]);
return back()->with('success', 'Tuntutan pada ' . $claim->claim_date->format('d/m/Y') . ' telah disahkan.');
}
/**
* Reject a single claim with reason.
*/
public function rejectClaim($id, Request $request)
{
$director = Auth::user();
$rejectReason = $request->input('reject_reason', 'Tiada sebab diberikan.');
$claim = Claim::findOrFail($id);
// Ensure this director has access to this project
$allowedProjectIds = ProjectTeam::where('pt_staff_id', $director->employeeCode)
->where('role_id', 7)
->pluck('fk_project_id')
->toArray();
if (!in_array($claim->project_id, $allowedProjectIds)) {
return back()->with('error', 'Anda tidak dibenarkan menolak tuntutan ini.');
}
if ($claim->status !== 'Disokong') {
return back()->with('warning', 'Tuntutan ini tidak berada dalam status Disokong.');
}
$claim->update([
'status' => 'Ditolak',
'rejected_by' => $director->id,
'reject_reason' => $rejectReason,
'updated_at' => now(),
]);
return back()->with('success', 'Tuntutan telah ditolak dengan sebab: ' . $rejectReason);
}
}
......@@ -141,49 +141,51 @@ public function approveMonth(Request $request)
*/
public function rejectMonth(Request $request)
{
$manager = Auth::user();
$monthLabel = $request->input('month');
$projectId = $request->input('project_id');
$request->validate([
'project_id' => 'required|integer',
'user_id' => 'required|integer',
'month' => 'required|string',
'reject_reason' => 'required|string|max:500',
]);
$month = Carbon::createFromFormat('Y-m', $request->month);
$claims = Claim::where('project_id', $request->project_id)
->where('user_id', $request->user_id)
->whereMonth('claim_date', $month->month)
->whereYear('claim_date', $month->year)
->get();
if (!$projectId) {
return back()->with('error', 'Sila pilih projek untuk ditolak.');
foreach ($claims as $claim) {
$claim->status = 'Ditolak';
$claim->rejected_by = auth()->id();
$claim->reject_reason = $request->reject_reason;
$claim->save();
}
// validate month format (expects YYYY-MM)
try {
if (!preg_match('/^\d{4}-\d{2}$/', $monthLabel)) {
throw new \Exception('Invalid month format');
}
$parsedMonth = Carbon::createFromFormat('Y-m', $monthLabel);
} catch (\Exception $e) {
return back()->with('error', 'Format bulan tidak sah. Sila pilih semula bulan dalam format YYYY-MM.');
}
return redirect()->back()->with('success', 'Semua tuntutan telah ditolak dengan sebab: ' . $request->reject_reason);
}
// ensure manager oversees this project
$allowedProjectIds = ProjectTeam::where('pt_staff_id', $manager->employeeCode)
->where('role_id', 1)
->pluck('fk_project_id')
->toArray();
public function approveClaim($id)
{
$claim = Claim::findOrFail($id);
$claim->status = 'Disokong';
$claim->verified_by = auth()->id();
$claim->save();
if (!in_array($projectId, $allowedProjectIds)) {
return back()->with('error', 'Anda tidak dibenarkan melakukan tindakan ini untuk projek terpilih.');
}
return back()->with('success', 'Tuntutan telah disahkan.');
}
// update only claims for this project and month with status 'Diproses'
$rejectedCount = Claim::where('project_id', $projectId)
->whereMonth('claim_date', $parsedMonth->month)
->whereYear('claim_date', $parsedMonth->year)
->where('status', 'Diproses')
->update([
'status' => 'Ditolak',
'rejected_by' => $manager->id,
'updated_at' => now(),
]);
public function rejectClaim(Request $request, $id)
{
$request->validate(['reject_reason' => 'required|string|max:500']);
if ($rejectedCount > 0) {
return back()->with('success', $rejectedCount . ' tuntutan telah ditolak untuk projek ini bagi bulan ' . $monthLabel . '.');
}
$claim = Claim::findOrFail($id);
$claim->status = 'Ditolak';
$claim->rejected_by = auth()->id();
$claim->reject_reason = $request->reject_reason;
$claim->save();
return back()->with('warning', 'Tiada tuntutan "Diproses" untuk ditolak bagi projek ini pada bulan ' . $monthLabel . '.');
return back()->with('success', 'Tuntutan telah ditolak dengan sebab: ' . $request->reject_reason);
}
}
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