Files
Nyan Lin Paing 0e55e36cea
PHP Tests / php-tests (push) Has been cancelled
Add Bookings & Revenue reporting module
New modules/reporting Filament page: filterable bookings table (travel
date range, status, route, channel) with CSV/Excel export. Report
columns include booking ref, route, passenger name/count, price,
best-payment status/amount, and driver info, plus a TOTAL row summing
passenger count, price, and payment amount in both export formats.

- BookingsRevenueExport backs both CSV and XLSX via maatwebsite/excel
  ^4.0 (the only version compatible with PHP 8.5; 3.1.x caps
  phpoffice/phpspreadsheet below 8.5).
- CSV export writes a UTF-8 BOM so non-Latin passenger names (Burmese)
  open correctly in Excel.
- New view_reports permission (super_admin/admin/support) gates the
  page; new indexes on bookings.travel_date/status/created_by_channel
  and payments.completed_at support the report's filters.
2026-08-23 22:32:49 +07:00

90 lines
3.7 KiB
PHP

<?php
use App\Models\User;
use Livewire\Livewire;
use Maatwebsite\Excel\Excel as ExcelFormat;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Booking\Enums\BookingStatus;
use Modules\Booking\Models\Booking;
use Modules\Reporting\Exports\BookingsRevenueExport;
use Modules\Reporting\Filament\Pages\BookingsRevenueReport;
use PhpOffice\PhpSpreadsheet\IOFactory;
use Spatie\Permission\Models\Permission;
beforeEach(function () {
Permission::findOrCreate('view_reports', 'web');
$this->admin = User::factory()->create()->givePermissionTo(['view_reports']);
$this->actingAs($this->admin);
});
test('it renders for a user with view_reports', function () {
Livewire::test(BookingsRevenueReport::class)->assertOk();
});
test('a user without view_reports cannot access it', function () {
$this->actingAs(User::factory()->create());
expect(BookingsRevenueReport::canAccess())->toBeFalse();
});
test('it narrows results by travel date range and status', function () {
$inRange = Booking::factory()->create(['travel_date' => today(), 'status' => BookingStatus::Confirmed]);
$outOfRange = Booking::factory()->create(['travel_date' => today()->addMonths(2), 'status' => BookingStatus::Confirmed]);
$wrongStatus = Booking::factory()->create(['travel_date' => today(), 'status' => BookingStatus::Cancelled]);
Livewire::test(BookingsRevenueReport::class)
->fillForm([
'travel_date_from' => today()->toDateString(),
'travel_date_to' => today()->toDateString(),
'status' => BookingStatus::Confirmed->value,
], 'filtersForm')
->assertCanSeeTableRecords([$inRange])
->assertCanNotSeeTableRecords([$outOfRange, $wrongStatus]);
});
test('exporting csv triggers a download', function () {
Excel::fake();
Booking::factory()->create();
Livewire::test(BookingsRevenueReport::class)->callAction('exportCsv');
Excel::assertDownloaded('bookings-revenue-'.now()->format('Y-m-d').'.csv');
});
test('exporting excel triggers a download', function () {
Excel::fake();
Booking::factory()->create();
Livewire::test(BookingsRevenueReport::class)->callAction('exportXlsx');
Excel::assertDownloaded('bookings-revenue-'.now()->format('Y-m-d').'.xlsx');
});
test('the export includes passenger name/count columns and a total row', function () {
$a = Booking::factory()->create(['passenger_name' => 'Jane Doe', 'price' => 10000]);
$a->vehicleOptions()->create(['vehicle_option' => 'back_seat', 'passenger_count' => 2, 'unit_price' => 5000, 'line_total' => 10000]);
$b = Booking::factory()->create(['passenger_name' => 'John Roe', 'price' => 15000]);
$b->vehicleOptions()->create(['vehicle_option' => 'back_seat', 'passenger_count' => 3, 'unit_price' => 5000, 'line_total' => 15000]);
$export = new BookingsRevenueExport(Booking::query()->with(['route.fromDestination', 'route.toDestination', 'payments', 'vehicleOptions']));
$path = storage_path('app/test-bookings-revenue.xlsx');
file_put_contents($path, Excel::raw($export, ExcelFormat::XLSX));
$sheet = IOFactory::load($path)->getActiveSheet();
unlink($path);
expect($sheet->getCell('F1')->getValue())->toBe('Passenger Name')
->and($sheet->getCell('G1')->getValue())->toBe('Passenger Count')
->and([$sheet->getCell('F2')->getValue(), $sheet->getCell('F3')->getValue()])->toContain('Jane Doe', 'John Roe');
$totalRow = $sheet->getHighestRow();
expect($sheet->getCell("A{$totalRow}")->getValue())->toBe('TOTAL')
->and((int) $sheet->getCell("G{$totalRow}")->getValue())->toBe(5) // 2 + 3 passengers
->and((float) $sheet->getCell("H{$totalRow}")->getValue())->toBe(25000.0); // 10000 + 15000 price
});