Alle Beispiele
Preisliste · Gruppierte Liste mit Zwischensummen
Ein Report-Start-Band mit dem Titel, ein Tabellenkopf, ein Detailband, eine Gruppe pro Kategorie mit eigenem Kopf und Fuss, eine summierte Zwischensumme, ein Report-Ende-Band und ein Filterformular.
Gruppen
Aggregate
Filter
Flow
/reports/price_list/run
Was hier typisch ist
- Ein reportStart()-Band für den Titel und ein reportEnd()-Band für die Zusammenfassung — einmal pro Report
- Ein Tabellenkopf, danach ein Detailband, das pro Datensatz einmal gerendert wird
- Eine Gruppe pro Kategorie mit eigenen Kopf- und Fuss-Bändern
- Eine Zwischensumme pro Gruppe via aggSum()
- Ein Filterformular mit typisierten Controls (Text, Enum, Number, Relation)
Modul-Struktur
app/Reports/PriceList/
├── Report.php # bands, groups, filters
├── Source.php # data + field definitions
└── views/
├── report-start.blade.php
├── grid-head.blade.php
├── row.blade.php
├── category-head.blade.php
├── category-foot.blade.php
└── report-end.blade.php
Dateien
<?php
declare(strict_types=1);
namespace App\Reports\PriceList;
use App\Reports\Layout\CompanyLayout;
use Guggach\Reports\Definition\ReportBuilder;
use Guggach\Reports\Filters\FilterBuilder;
use Guggach\Reports\Report as BaseReport;
use Guggach\Reports\Sources\ReportSource;
final class Report extends BaseReport
{
public function key(): string
{
return 'price_list';
}
public function name(): string
{
return 'Preisliste';
}
public function layout(): string
{
return CompanyLayout::class;
}
public function source(): ReportSource
{
return new Source;
}
/**
* Demonstrate the fluent control description: operator listbox for the
* number, multiselect for the enum and a searchable dual-list for the
* relation (id/FK) field.
*/
public function filterControls(): array
{
return FilterBuilder::for($this->source())
->field('price')->operators(['=', '>=', '<=', 'between', 'in'])
->field('category')->multiselect()
->field('supplier')->picker(valueField: 'id', labelField: 'label')
->build();
}
public function define(ReportBuilder $r): void
{
// Module-relative view names resolve to app/Reports/PriceList/views/*.
// Report start/end run once per report; the grid header introduces the
// table and the detail band renders one row per record.
$r->reportStart(view: 'report-start');
$r->gridHeader(view: 'grid-head');
$r->detail(view: 'row');
$r->reportEnd(view: 'report-end');
// Grouped by category (data is sorted by category) with a subtotal.
$r->group('category', function ($g): void {
$g->header(view: 'category-head');
$g->footer(view: 'category-foot');
$g->aggSum('price', as: 'category_total');
});
}
}
<?php
declare(strict_types=1);
namespace App\Reports\PriceList;
use Guggach\Reports\Sources\ReportField;
use Guggach\Reports\Sources\ReportSource;
/**
* Data source of the price list. Report-local on purpose (spec 7.1): a source
* is never shared between reports. Filters are applied in memory here; a
* database-backed variant would push them down instead.
*/
final class Source extends ReportSource
{
public function records(): iterable
{
return $this->applyFilters([
['sku' => 'A-100', 'name' => 'Kaffeemaschine', 'category' => 'Küche', 'supplier' => 'Nordwind AG', 'price' => 129.00],
['sku' => 'B-200', 'name' => 'Wasserkocher', 'category' => 'Küche', 'supplier' => 'Nordwind AG', 'price' => 39.90],
['sku' => 'C-300', 'name' => 'Toaster', 'category' => 'Büro', 'supplier' => 'Berger GmbH', 'price' => 59.50],
['sku' => 'D-400', 'name' => 'Mixer', 'category' => 'Büro', 'supplier' => 'Conti AG', 'price' => 89.00],
]);
}
public function fields(): array
{
return [
new ReportField('sku', 'SKU', type: 'string'),
new ReportField('name', 'Bezeichnung', type: 'string'),
new ReportField('category', 'Kategorie', type: 'enum', options: ['Küche', 'Büro']),
new ReportField('supplier', 'Lieferant', type: 'relation', options: [
'Nordwind AG', 'Berger GmbH', 'Conti AG', 'Delta Handels',
'Egger & Co', 'Fuchs AG', 'Graf GmbH', 'Huber AG',
]),
new ReportField('price', 'Preis', type: 'number'),
];
}
}
<h1 style="margin:0 0 12px">{{ $ctx->report->name() }}</h1>
<table style="width:100%; border-collapse:collapse">
<thead>
<tr style="border-bottom:2px solid #111">
<th style="text-align:left">SKU</th>
<th style="text-align:left">Bezeichnung</th>
<th style="text-align:right">Preis (CHF)</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid #ddd">
<td>{{ $ctx->sku }}</td>
<td>{{ $ctx->name }}</td>
<td style="text-align:right">{{ number_format((float) $ctx->price, 2, '.', "'") }}</td>
</tr>
<tr style="font-weight:600; background:#f3f4f6">
<td colspan="2">Summe {{ $ctx->currentGroup()['key'] }}</td>
<td style="text-align:right">{{ number_format((float) $ctx->aggregates['category_total'], 2, '.', "'") }}</td>
</tr>
</tbody>
</table>
<p style="margin-top:12px; color:#555">{{ $ctx->total }} Positionen</p>
<?php
declare(strict_types=1);
namespace App\Reports\Layout;
use Guggach\Reports\Definition\PageSetup;
use Guggach\Reports\Layouts\Layout;
final class CompanyLayout extends Layout
{
public function view(): string
{
return 'reports.layouts.company';
}
public function pageSetup(): PageSetup
{
return PageSetup::a4()->portrait()->marginsMm(24, 18, 24, 18);
}
}