0e55e36cea
PHP Tests / php-tests (push) Has been cancelled
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.
90 lines
3.7 KiB
PHP
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
|
|
});
|