# lib/rollups/

One file per report module. Each registers a builder that recomputes a single
`(shop_id, location_id, order_date)` from the fact tables:

```php
report_register_rollup('daily_summary', function (PDO $db, int $shopId, int $locationId, string $date) {
  $db->prepare("DELETE FROM rpt_daily_summary
                WHERE shop_id = :shop AND location_id = :loc AND order_date = :date")
     ->execute([...]);

  $db->prepare("INSERT INTO rpt_daily_summary (...) SELECT ... FROM rpt_orders WHERE ...")
     ->execute([...]);
});
```

`jobs/aggregate.php` loads every file here and calls each builder inside one
transaction per dirty day.

Two rules a builder must follow:

1. **Delete then insert.** Never `UPDATE`. Recompute has to be idempotent — running
   it twice must produce byte-identical rows, because late refunds and late payout
   fees re-mark days that have already been aggregated.
2. **Registration only — no declarations.** `jobs/aggregate.php` includes these files
   with `require`, not `require_once`, so that a caller which resets the registry (the
   test suite does, for isolation) gets the builders back. A `function` or `class`
   declaration here would fatal on the second include. Put helpers in `lib/`.
3. **Set-based SQL only.** No looping in PHP. A builder that fetches rows into PHP
   to sum them will be the bottleneck of the entire pipeline.

Files land here with their module: `daily_summary.php` (M1), `daily_item.php` (M2),
`daily_category.php` (M4).
