# SnapNBill Backend

Laravel 12 (PHP 8.2) API backend for utility meter-reading and billing (water,
electricity, gas) for multi-tenant service providers managing sites/buildings
with lots (units) and lot owners. Backpack Pro is used for admin CRUD where
applicable.

## Domain model

- `ServiceProvider` — tenant root. Preferences (incl. `timezone`, default
  `Indian/Mauritius`) come from `ServiceProvider::getPreferences()`, merged
  over `ServiceProvider::defaultPreferences()`.
- `Site` → `Lot` (a billable unit) → `LotOwner`.
- `SiteService` links a site+service_type to one of `WaterService` /
  `ElectricityService` / `GasService`, each holding rate config and
  `payment_terms` (days).
- Meter readings flow through two tables:
  - `OpenSession` — an in-progress reading round for a lot/service, created
    via `SessionRepo::addSession()` (Excel upload) or the mobile app.
  - `ArchivedSession` — the closed/billed record, created by
    `SessionRepo::completed()` via a **raw `::insert()`** (bulk insert,
    bypasses Eloquent model events/auto-timestamps) or, for one-off bills,
    `SessionRepo::generateBill()` (adhoc, uses `::create()`).
- `survey_end_date` on `ArchivedSession` is the authoritative "bill date" —
  it's stamped with `date("Y-m-d H:i:s")` (app timezone is UTC, see
  `config/app.php`) at the moment the session is closed. `created_at` is
  **not** reliable for this: because `completed()` bulk-inserts rows carried
  over from `OpenSession` (see `MyCloudLibrary::calculation()`), `created_at`
  reflects when the *reading round was opened*, which can predate the actual
  close date significantly.
- Due-date math is `survey_end_date + service.payment_terms days`, always
  computed by parsing `survey_end_date` as UTC and converting to the tenant's
  `preferences.timezone` *before* adding days (see
  `MyCloudLibrary::getLotPdfData()` and `Export/LotService.php`) — do this
  conversion for any new due-date code path; skipping it can shift the
  computed day near local-midnight boundaries for non-UTC tenants.
- PDFs/exports are generated on demand (`app/Http/Controllers/V1/Export`,
  `app/Http/Controllers/V1/MyCloudLibrary.php`); there is no persisted
  `Invoice`/`Bill` model or `due_date` column — it's always recomputed.

## Conventions

- Multi-table writes use `DB::beginTransaction()`/`commit()`/`rollBack()`
  (see `ServiceProviderRepo::editSite()`).
- Request validation is typically inline `$request->validate([...])` at the
  top of controller actions (see `app/Http/Controllers/V1/Superadmin/ServiceProvider.php`),
  not FormRequest classes.
- `V1` API controllers generally delegate to a `*Repo` class
  (`app/Repository/`) that does the actual DB work and returns
  `['status' => bool, 'message' => ..., 'data' => ...]`, which the controller
  maps to an HTTP response via `createResponse`/`errorResponse`.
- An accounting integration (`AccountingConfig`, `AccountingContrepartie`,
  `AccountingJournalEntry`, `app/Services/Accounting/`) is replacing an
  earlier Vilogi-specific integration (`VilogiConfig`, `VilogiContrepartie`,
  `VilogiJournalEntry`, `VilogiService`) — in progress as of 2026-08.
