Commit a3cb80f0 by Hannah Zahra

Fifth Update

parent 90f032c8
...@@ -57,6 +57,10 @@ public function up() ...@@ -57,6 +57,10 @@ public function up()
$table->unsignedInteger('rejected_by')->nullable(); $table->unsignedInteger('rejected_by')->nullable();
$table->foreign('rejected_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('rejected_by')->references('id')->on('users')->nullOnDelete();
// Reject reason
$table->string('reject_reason', 500)->nullable();
// Claim status (workflow) // Claim status (workflow)
$table->enum('status', [ $table->enum('status', [
'Diproses', 'Diproses',
...@@ -88,6 +92,8 @@ public function down() ...@@ -88,6 +92,8 @@ public function down()
$table->dropForeign(['rejected_by']); $table->dropForeign(['rejected_by']);
$table->dropColumn('rejected_by'); $table->dropColumn('rejected_by');
$table->dropColumn('reject_reason');
}); });
// Drop the 'claims' table entirely // Drop the 'claims' table entirely
......
...@@ -371,7 +371,19 @@ ...@@ -371,7 +371,19 @@
<td>RM {{ number_format($totalAmount, 2) }}</td> <td>RM {{ number_format($totalAmount, 2) }}</td>
<td> <td>
@foreach($projectClaims->pluck('status')->unique() as $status) @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 @endforeach
</td> </td>
<td> <td>
...@@ -409,7 +421,19 @@ ...@@ -409,7 +421,19 @@
<td>{{ number_format($claim->total_mileage, 1) }}</td> <td>{{ number_format($claim->total_mileage, 1) }}</td>
<td>RM {{ number_format($claim->total_claim_amount, 2) }}</td> <td>RM {{ number_format($claim->total_claim_amount, 2) }}</td>
<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>
<td> <td>
{{-- Always show Lihat --}} {{-- Always show Lihat --}}
...@@ -525,6 +549,17 @@ ...@@ -525,6 +549,17 @@
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script> <script>
$(document).ready(function() { $(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 // Toggle Summary Section
$('#summaryToggle').on('click', function() { $('#summaryToggle').on('click', function() {
$(this).toggleClass('active'); $(this).toggleClass('active');
......
...@@ -58,6 +58,7 @@ ...@@ -58,6 +58,7 @@
<th>Lain-lain (RM)</th> <th>Lain-lain (RM)</th>
<th>Status</th> <th>Status</th>
<th>Jumlah (RM)</th> <th>Jumlah (RM)</th>
<th>Tindakan</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
...@@ -70,7 +71,7 @@ ...@@ -70,7 +71,7 @@
@foreach($claims as $c) @foreach($claims as $c)
@php @php
$total += $c->total_claim_amount; $total += $c->total_claim;
$totalMileage += $c->total_mileage ?? 0; $totalMileage += $c->total_mileage ?? 0;
$totalToll += $c->toll_amount ?? 0; $totalToll += $c->toll_amount ?? 0;
$totalOthers += $c->others_amount ?? 0; $totalOthers += $c->others_amount ?? 0;
...@@ -85,9 +86,51 @@ ...@@ -85,9 +86,51 @@
<td>{{ number_format($c->toll_amount ?? 0, 2) }}</td> <td>{{ number_format($c->toll_amount ?? 0, 2) }}</td>
<td>{{ number_format($c->others_amount ?? 0, 2) }}</td> <td>{{ number_format($c->others_amount ?? 0, 2) }}</td>
<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>
<td>RM {{ number_format($c->total_claim_amount, 2) }}</td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>
...@@ -118,5 +161,145 @@ ...@@ -118,5 +161,145 @@
<a href="{{ route('mileage::applications.index', ['month' => $month]) }}" class="ui grey button"> <a href="{{ route('mileage::applications.index', ['month' => $month]) }}" class="ui grey button">
<i class="arrow left icon"></i> Kembali <i class="arrow left icon"></i> Kembali
</a> </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> </div>
@endsection @endsection
...@@ -85,6 +85,12 @@ ...@@ -85,6 +85,12 @@
gap: 1rem; gap: 1rem;
margin-top: 1rem; margin-top: 1rem;
} }
.approval-actions .ui.button {
height: 42px;
text-align: center;
}
</style> </style>
<div class="ui container"> <div class="ui container">
...@@ -113,6 +119,7 @@ ...@@ -113,6 +119,7 @@
<th>Lain-lain (RM)</th> <th>Lain-lain (RM)</th>
<th>Status</th> <th>Status</th>
<th>Jumlah (RM)</th> <th>Jumlah (RM)</th>
<th>Tindakan</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
...@@ -143,6 +150,31 @@ ...@@ -143,6 +150,31 @@
</span> </span>
</td> </td>
<td>{{ number_format($claim->total_claim_amount, 2) }}</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> </tr>
@if(!empty($claim->others_description)) @if(!empty($claim->others_description))
...@@ -196,16 +228,15 @@ ...@@ -196,16 +228,15 @@
</button> </button>
</form> </form>
{{-- Reject All --}} {{-- Reject All with Modal Trigger --}}
<form action="{{ route('mileage::projectManager.rejectMonth') }}" method="POST" style="display:inline;"> <button
@csrf type="button"
<input type="hidden" name="project_id" value="{{ $project->id }}"> class="ui red button reject-btn"
<input type="hidden" name="user_id" value="{{ $user->id }}"> data-project="{{ $project->id }}"
<input type="hidden" name="month" value="{{ $month }}"> data-user="{{ $user->id }}"
<button type="submit" class="ui red button"> data-month="{{ $month }}">
<i class="times icon"></i> Tolak Semua <i class="times icon"></i> Tolak Semua
</button> </button>
</form>
</div> </div>
@endif @endif
</div> </div>
...@@ -246,7 +277,7 @@ ...@@ -246,7 +277,7 @@
} }
// now match the total (no recomputation) // now match the total (no recomputation)
$finalTotal = $totalClaimAmount + $totalToll + $totalOthers - $penaltyAmount; $finalTotal = $totalClaimAmount - $penaltyAmount;
@endphp @endphp
<div class="ui segment"> <div class="ui segment">
...@@ -309,4 +340,77 @@ ...@@ -309,4 +340,77 @@
</p> </p>
</div> </div>
</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 @endsection
...@@ -39,6 +39,8 @@ function () { ...@@ -39,6 +39,8 @@ function () {
Route::get('/show/{projectId}/{userId}', [ProjectManagerController::class, 'show'])->name('.show'); Route::get('/show/{projectId}/{userId}', [ProjectManagerController::class, 'show'])->name('.show');
Route::post('/approve-month', [ProjectManagerController::class, 'approveMonth'])->name('.approveMonth'); Route::post('/approve-month', [ProjectManagerController::class, 'approveMonth'])->name('.approveMonth');
Route::post('/reject-month', [ProjectManagerController::class, 'rejectMonth'])->name('.rejectMonth'); 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 // Project Director
...@@ -47,6 +49,8 @@ function () { ...@@ -47,6 +49,8 @@ function () {
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('/approve-month', [ProjectDirectorController::class, 'approveMonth'])->name('.approveMonth');
Route::post('/reject-month', [ProjectDirectorController::class, 'rejectMonth'])->name('.rejectMonth'); 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 // Finance Department
......
...@@ -107,43 +107,45 @@ public function update(Request $request, $id) ...@@ -107,43 +107,45 @@ public function update(Request $request, $id)
{ {
$claim = Claim::findOrFail($id); $claim = Claim::findOrFail($id);
// Check if any required DB fields are null before allowing update // Validate input
$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
$request->validate([ $request->validate([
'claim_date' => 'required|date', 'claim_date' => 'required|date',
'distance_from' => 'required|string', 'distance_from' => 'required|string|max:255',
'distance_to' => 'required|string', 'distance_to' => 'required|string|max:255',
'description' => 'required|string', 'description' => 'required|string|max:255',
'total_mileage' => 'required|numeric|min:0', 'total_mileage' => 'required|numeric|min:0',
'toll_amount' => 'nullable|numeric|min:0', 'toll_amount' => 'nullable|numeric|min:0',
'others_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->update([
'claim_date' => $request->claim_date, 'claim_date' => $request->claim_date,
'distance_from' => $request->distance_from, 'distance_from' => $request->distance_from,
'distance_to' => $request->distance_to, 'distance_to' => $request->distance_to,
'description' => $request->description, 'description' => $request->description,
'total_mileage' => $request->total_mileage, 'total_mileage' => $totalMileage,
'toll_amount' => $request->toll_amount, 'toll_amount' => $toll,
'others_amount' => $request->others_amount, 'others_amount' => $others,
'others_details' => $request->others_details, '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.'); return redirect()->back()->with('success', 'Tuntutan berjaya dikemaskini.');
......
...@@ -167,6 +167,7 @@ public function rejectMonth(Request $request) ...@@ -167,6 +167,7 @@ public function rejectMonth(Request $request)
if (!in_array($projectId, $allowedProjectIds)) { if (!in_array($projectId, $allowedProjectIds)) {
return back()->with('error', 'Anda tidak dibenarkan melakukan tindakan ini untuk projek terpilih.'); 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 // update only claims that belong to this project and month and are Disokong
$rejectedCount = Claim::where('project_id', $projectId) $rejectedCount = Claim::where('project_id', $projectId)
...@@ -176,13 +177,77 @@ public function rejectMonth(Request $request) ...@@ -176,13 +177,77 @@ public function rejectMonth(Request $request)
->update([ ->update([
'status' => 'Ditolak', 'status' => 'Ditolak',
'rejected_by' => $director->id, 'rejected_by' => $director->id,
'reject_reason' => $rejectReason,
'updated_at' => now(), '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 . '.'); 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) ...@@ -141,49 +141,51 @@ public function approveMonth(Request $request)
*/ */
public function rejectMonth(Request $request) public function rejectMonth(Request $request)
{ {
$manager = Auth::user(); $request->validate([
$monthLabel = $request->input('month'); 'project_id' => 'required|integer',
$projectId = $request->input('project_id'); 'user_id' => 'required|integer',
'month' => 'required|string',
'reject_reason' => 'required|string|max:500',
]);
if (!$projectId) { $month = Carbon::createFromFormat('Y-m', $request->month);
return back()->with('error', 'Sila pilih projek untuk ditolak.'); $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();
// validate month format (expects YYYY-MM) foreach ($claims as $claim) {
try { $claim->status = 'Ditolak';
if (!preg_match('/^\d{4}-\d{2}$/', $monthLabel)) { $claim->rejected_by = auth()->id();
throw new \Exception('Invalid month format'); $claim->reject_reason = $request->reject_reason;
$claim->save();
} }
$parsedMonth = Carbon::createFromFormat('Y-m', $monthLabel);
} catch (\Exception $e) { return redirect()->back()->with('success', 'Semua tuntutan telah ditolak dengan sebab: ' . $request->reject_reason);
return back()->with('error', 'Format bulan tidak sah. Sila pilih semula bulan dalam format YYYY-MM.');
} }
// ensure manager oversees this project public function approveClaim($id)
$allowedProjectIds = ProjectTeam::where('pt_staff_id', $manager->employeeCode) {
->where('role_id', 1) $claim = Claim::findOrFail($id);
->pluck('fk_project_id') $claim->status = 'Disokong';
->toArray(); $claim->verified_by = auth()->id();
$claim->save();
if (!in_array($projectId, $allowedProjectIds)) { return back()->with('success', 'Tuntutan telah disahkan.');
return back()->with('error', 'Anda tidak dibenarkan melakukan tindakan ini untuk projek terpilih.');
} }
// update only claims for this project and month with status 'Diproses' public function rejectClaim(Request $request, $id)
$rejectedCount = Claim::where('project_id', $projectId) {
->whereMonth('claim_date', $parsedMonth->month) $request->validate(['reject_reason' => 'required|string|max:500']);
->whereYear('claim_date', $parsedMonth->year)
->where('status', 'Diproses')
->update([
'status' => 'Ditolak',
'rejected_by' => $manager->id,
'updated_at' => now(),
]);
if ($rejectedCount > 0) { $claim = Claim::findOrFail($id);
return back()->with('success', $rejectedCount . ' tuntutan telah ditolak untuk projek ini bagi bulan ' . $monthLabel . '.'); $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