Merged in izzati-feat-laporan-aging-all (pull request #91)

tambah laporan aging keseluruhan
This commit is contained in:
izzati zulkifli
2025-04-07 08:11:16 +00:00
5 changed files with 435 additions and 0 deletions
@@ -0,0 +1,184 @@
<?php
namespace App\Exports\LaporanKeseluruhan;
use Maatwebsite\Excel\Concerns\FromArray;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithCustomValueBinder;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithStrictNullComparison;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Cell\DefaultValueBinder;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use Maatwebsite\Excel\Concerns\WithColumnFormatting;
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
use Maatwebsite\Excel\Concerns\WithCustomStartCell;
use Maatwebsite\Excel\Events\AfterSheet;
use Maatwebsite\Excel\Concerns\WithEvents;
class AgingAllExportExcel extends DefaultValueBinder implements
FromArray,
WithStrictNullComparison,
WithHeadings,
ShouldAutoSize,
WithCustomValueBinder,
WithColumnFormatting,
WithEvents
{
protected $aging;
public function __construct($aging)
{
$this->aging = $aging;
}
public function array(): array
{
$rows = [];
$totals = [];
foreach ($this->aging as $branch) {
$row = [$branch['cawangan']];
$i = 0;
foreach ($branch['summary_by_tempoh'] as $summary) {
$row[] = $summary['count'];
$row[] = $summary['sum_bakipjm'];
// Totals per column
$totals[$i]['count'] = ($totals[$i]['count'] ?? 0) + $summary['count'];
$totals[$i]['sum_bakipjm'] = ($totals[$i]['sum_bakipjm'] ?? 0) + $summary['sum_bakipjm'];
$i++;
}
// Total per row (cawangan)
$jumlahBil = collect($branch['summary_by_tempoh'])->sum('count');
$jumlahRM = collect($branch['summary_by_tempoh'])->sum('sum_bakipjm');
$row[] = $jumlahBil;
$row[] = $jumlahRM;
// Add to column totals
$totals['jumlah']['count'] = ($totals['jumlah']['count'] ?? 0) + $jumlahBil;
$totals['jumlah']['sum_bakipjm'] = ($totals['jumlah']['sum_bakipjm'] ?? 0) + $jumlahRM;
$rows[] = $row;
}
// Add final total row
$totalRow = ['Jumlah'];
foreach ($totals as $key => $val) {
$totalRow[] = $val['count'];
$totalRow[] = $val['sum_bakipjm'];
}
$rows[] = $totalRow;
return $rows;
}
/**
* Multi-row headings (2 rows)
*/
public function headings(): array
{
$row1 = ['Cawangan'];
$row2 = [''];
if (!empty($this->aging[0]['summary_by_tempoh'])) {
foreach ($this->aging[0]['summary_by_tempoh'] as $summary) {
$row1[] = $summary['label'];
$row1[] = ''; // empty to allow merged cell
$row2[] = 'Bil';
$row2[] = 'RM';
}
}
$row1[] = 'Jumlah';
$row1[] = ''; // for merged "Jumlah" cell
$row2[] = 'Bil';
$row2[] = 'RM';
return [$row1, $row2];
}
public function columnFormats(): array
{
$formats = [];
$colIndex = 2; // Start at column B (Cawangan is A)
$tempohCount = count($this->aging[0]['summary_by_tempoh'] ?? []);
for ($i = 0; $i < $tempohCount; $i++) {
// Bil column
$bilCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($colIndex++);
$formats[$bilCol] = NumberFormat::FORMAT_NUMBER; // ✅ No decimal
// RM column
$rmCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($colIndex++);
$formats[$rmCol] = NumberFormat::FORMAT_NUMBER_COMMA_SEPARATED1; // RM with comma and decimals
}
// Jumlah Bil
$jumlahBilCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($colIndex++);
$formats[$jumlahBilCol] = NumberFormat::FORMAT_NUMBER; // ✅ No decimal
// Jumlah RM
$jumlahRMCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($colIndex++);
$formats[$jumlahRMCol] = NumberFormat::FORMAT_NUMBER_COMMA_SEPARATED1;
return $formats;
}
public function bindValue(Cell $cell, $value)
{
if (is_numeric($value)) {
$cell->setValueExplicit($value, DataType::TYPE_NUMERIC);
return true;
}
return parent::bindValue($cell, $value);
}
/**
* Merge heading cells using AfterSheet event
*/
public function registerEvents(): array
{
return [
AfterSheet::class => function(AfterSheet $event) {
$sheet = $event->sheet->getDelegate();
$colIndex = 2; // Start from column B (A = Cawangan)
$tempohCount = count($this->aging[0]['summary_by_tempoh'] ?? []);
$headingRow = 1; // Change this to 1 for the first row
$rowToMerge = $headingRow;
// Merge each tempoh (e.g. "1 Bulan") across 2 columns: Bil & RM
for ($i = 0; $i < $tempohCount; $i++) {
$startCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($colIndex);
$endCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($colIndex + 1);
$sheet->mergeCells("{$startCol}{$rowToMerge}:{$endCol}{$rowToMerge}");
$colIndex += 2;
}
// Merge "Jumlah" header across 2 columns (Bil & RM)
$startCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($colIndex);
$endCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($colIndex + 1);
$sheet->mergeCells("{$startCol}{$rowToMerge}:{$endCol}{$rowToMerge}");
},
];
}
// Mulakan sel untuk data bermula dari A4 (Baris 3 adalah header)
public function startCell(): string
{
return 'A3'; // Data bermula dari baris 4
}
}
@@ -0,0 +1,84 @@
<?php
namespace App\Http\Controllers\Reports;
use App\Http\Controllers\Controller;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Http;
use Maatwebsite\Excel\Facades\Excel;
use Illuminate\Support\Facades\Auth;
use App\Exports\LaporanKeseluruhan\AgingAllExportExcel;
class AgingController extends Controller
{
public function index(Request $request)
{
$current = new Carbon();
$request->hostname = config('api.url');
// dd($request->hostname );
$kod_cawangan = $request->kod_cawangan;
$action = $request->input('action', 'search');
// dd($action);
$getReportDate = function ($date_string) use ($current) {
if ($date_string == null) {
return $current->toDateString();
}
return $date_string;
};
$request->hostname = config('api.url');
$aging = Http::get($request->hostname . '/api/reports/aging', [
'endDate' => $getReportDate($request->endDate),
])->json();
$endDate = $getReportDate($request->endDate);
if ($action === 'excel') {
$file_title = sprintf("LaporanAging(%s).xlsx", $kod_cawangan, $request->endDate);
return Excel::download(new AgingAllExportExcel($aging), $file_title);
}
$aging = $aging ?? [];
return view('reports.aging.index', compact('aging', 'endDate'));
}
public function show(Request $request)
{
$current = Carbon::now();
$action = $request->input('action', 'search');
$getReportDate = function ($date_string) use ($current) {
return $date_string ? $date_string : $current->toDateString();
};
$hostname = config('api.url');
$response = Http::get("$hostname/api/reports/aging", [
'endDate' => $getReportDate($request->endDate),
]);
$aging = $response->json() ?? []; // Ensure it's an array
// Debugging: Check the response before using it
if (empty($aging)) {
return back()->with('error', 'Failed to fetch data from API.');
}
$endDate = $getReportDate($request->endDate);
if ($action === 'excel') {
// Ensure $kod_cawangan is defined (Modify as per your data structure)
$kod_cawangan = $request->input('kod_cawangan', 'DEFAULT');
$file_title = sprintf("LaporanAging(%s).xlsx", $kod_cawangan, $endDate);
return Excel::download(new AgingAllExportExcel($aging), $file_title);
}
return view('reports.aging.index', compact('aging', 'endDate'));
}
}
@@ -49,6 +49,12 @@
<h2 class="mx-auto text-center font-bold text-2xl uppercase">Laporan Lelongan</h2>
</a>
</div>
<div
class="bg-white ml-2 shadow border border-solid px-6 py-4 rounded-lg hover:bg-yellow-300 hover:transition-all mb-2 cursor-pointer">
<a href="{{ route('reports.aging.index', ['startDate' => now()->toDateString(), 'endDate' => now()->toDateString()]) }}" class="">
<h2 class="mx-auto text-center font-bold text-2xl uppercase">Laporan Aging</h2>
</a>
</div>
</div>
</div>
</div>
@@ -0,0 +1,158 @@
@extends('layouts.app')
@section('content')
<div class="bg-gray-900 opacity-50 hidden fixed inset-0 z-10" id="sidebarBackdrop"></div>
<div id="main-content" class="h-full w-full bg-gray-50 relative overflow-y-auto">
<main>
<div class="pt-6 px-4">
<breadcrumb
:items="[{ 'title': 'Laporan Keseluruhan', 'icon': 'fas fa-users', 'url': '{{ route('reports') }}' }, { 'title': 'Lelongan', 'icon': 'fas fa-users' }]">
</breadcrumb>
<div class="container">
<div class="flex w-full">
<div class="xl:w-3/5 2xl:w-2/3 p-12 mx-auto">
<div class="bg-yellow-600 text-gray-200 text-lg px-6 py-4 rounded-t-lg">
<a href="/reports" class="router-link-active">
<i class="fas fa-backspace text-white"></i>
</a>
<span class="text-center mx-auto text-white font-bold">Laporan Aging</span>
</div>
<form id="formAging" method="GET" action="{{ route('reports.aging.show') }}">
<div class="rounded bg-white shadow p-6">
<div class="pb-6">
<label class="font-semibold text-gray-700 block pb-1">Pilih</label>
<div class="flex items-center">
<label for="tarikhhingga" class="text-gray-600 border border-2 rounded-l px-4 py-2 whitespace-no-wrap"
style="background-color: rgb(249, 250, 251);">Bulan</label>
<input id="tarikhhingga" name="endDate" type="month" value="{{ request()->endDate ?? $endDate }}"
class="border border-yellow-600 rounded-lg px-4 py-3 w-full sm:w-80 bg-white text-gray-800 focus:ring-2 focus:ring-yellow-500 focus:outline-none transition-all duration-300 hover:border-yellow-500" />
<input type="hidden" name="action" id="formAction" value="search">
<button type="submit" onclick="setAction('search')" class="border border-yellow-600 text-yellow-600 font-montserrat py-2 px-8 font-medium rounded-xl hover:bg-yellow-600 hover:text-white transition-all duration-300 ml-5">
<i class="fas fa-magnifying-glass mr-2"></i> Search
</button>
<button type="submit" onclick="setAction('excel')" class="border border-yellow-600 text-yellow-600 py-2 px-8 font-medium rounded-xl hover:bg-yellow-600 hover:text-white transition-all duration-300 ml-2">
<i class="fas fa-file-excel mr-2"></i> Excel
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<div kodcaw="operasi">
<div>
<!---->
<!---->
</div>
<div>
<!---->
<!---->
</div>
<div class="flex justify-between mb-2">
</div>
@if(empty($aging))
<div class="alert alert-warning">
Tiada data dijumpai untuk tarikh {{ $endDate }}.
</div>
@else
<!-- <h1>Summary Report</h1> -->
<div class="overflow-x-auto">
<!-- Create a table with horizontal headers -->
<table class="table-auto w-full border-collapse border border-gray-200">
<thead class="bg-yellow-500 text-white">
<tr>
<th class="px-4 py-2 border">Bil.</th> <!-- Add Bilangan here -->
<th class="px-4 py-2 border">Cawangan</th>
@foreach ($aging[0]['summary_by_tempoh'] as $summary)
<th class="px-4 py-2 border" colspan="2">{{ $summary['label'] }}</th> <!-- Merge Tempoh with Bilangan and Jumlah -->
@endforeach
<th class="px-4 py-2 border" colspan="2">Keseluruhan</th>
</tr>
<tr>
<th class="px-4 py-2 border"> </th> <!-- Empty for Bilangan Row -->
<th class="px-4 py-2 border"> </th> <!-- Empty for Cawangan Row -->
@foreach ($aging[0]['summary_by_tempoh'] as $summary)
<th class="px-4 py-2 border">Bil</th> <!-- Bilangan Column Title -->
<th class="px-4 py-2 border">RM</th> <!-- Jumlah Column Title -->
@endforeach
<th class="px-4 py-2 border">Bil</th>
<th class="px-4 py-2 border">RM</th>
</tr>
</thead>
<tbody>
@php
$counter = 1; // Initialize counter
$totalGrandCountByTempoh = array_fill(0, count($aging[0]['summary_by_tempoh']), 0);
$totalGrandBakipjmByTempoh = array_fill(0, count($aging[0]['summary_by_tempoh']), 0);
@endphp
@foreach ($aging as $branch)
@php
$totalCount = 0;
$totalBakipjm = 0;
@endphp
<tr>
<td class="px-4 py-2 border">{{ $counter++ }}</td> <!-- Display Bilangan (counter) -->
<td class="px-4 py-2 border font-bold">{{ $branch['cawangan'] }}</td>
@foreach ($branch['summary_by_tempoh'] as $index => $summary)
<td class="px-4 py-2 border">{{ $summary['count'] }}</td> <!-- Bilangan Value -->
<td class="px-4 py-2 border">{{ number_format($summary['sum_bakipjm'], 2) }}</td> <!-- Jumlah Value -->
@php
$totalCount += $summary['count']; // Summing up the count
$totalBakipjm += $summary['sum_bakipjm']; // Summing up the Bakipjm
// Adding values to grand totals for each tempoh
$totalGrandCountByTempoh[$index] += $summary['count'];
$totalGrandBakipjmByTempoh[$index] += $summary['sum_bakipjm'];
@endphp
@endforeach
<td class="px-4 py-2 border">{{ $totalCount }}</td> <!-- Total Count for Branch -->
<td class="px-4 py-2 border">{{ number_format($totalBakipjm , 2) }}</td> <!-- Total Bakipjm for Branch -->
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<td class="px-4 py-2 border font-bold" colspan = "2">Total</td>
@foreach ($totalGrandCountByTempoh as $index => $grandCount)
<td class="px-4 py-2 border">{{ $grandCount }}</td> <!-- Grand Total Bil for each Tempoh -->
<td class="px-4 py-2 border">{{ number_format($totalGrandBakipjmByTempoh[$index], 2) }}</td> <!-- Grand Total RM for each Tempoh -->
@endforeach
<td class="px-4 py-2 border">{{ array_sum($totalGrandCountByTempoh) }}</td> <!-- Grand Total for Keseluruhan Bil -->
<td class="px-4 py-2 border">{{ number_format(array_sum($totalGrandBakipjmByTempoh), 2) }}</td> <!-- Grand Total for Keseluruhan RM -->
</tr>
</tfoot>
</table>
</div>
@endif
</div>
</div>
</main>
@include('layouts.footer')
</div>
@endsection
<!-- JavaScript to Change Action -->
<script>
function setAction(action) {
document.getElementById('formAction').value = action;
document.getElementById('formAging').submit();
}
</script>
+3
View File
@@ -6,6 +6,7 @@ use App\Http\Controllers\Reports\AnsuranController;
use App\Http\Controllers\Reports\RingkasanHarianController;
use App\Http\Controllers\Reports\StokAuditController;
use App\Http\Controllers\Reports\LelonganController;
use App\Http\Controllers\Reports\AgingController;
use Illuminate\Support\Facades\Route;
Route::middleware(['roleChecker:admin,akaun,operasi,kpo, null'])->group(function () {
@@ -24,4 +25,6 @@ Route::middleware(['roleChecker:admin,akaun,operasi,kpo, null'])->group(function
Route::get('lelongan/{kod_cawangan}', [LelonganController::class, 'downloadLelonganCawanganExcel'])->name('reports.lelongan.cawangan.excel');
Route::get('laporan-lelong/view', [LelonganController::class, 'show'])->name('reports.lelongan.show');
Route::get('laporan-ansuran/view', [AnsuranController::class, 'show'])->name('reports.ansuran.show');
Route::get('aging/view', [AgingController::class, 'index'])->name('reports.aging.index');
Route::get('laporan-aging/view', [AgingController::class, 'show'])->name('reports.aging.show');
});