Water Utility Reference Public

Source Evidence

The evidence behind this guide: which live Water Utility module files were read, which facts were confirmed against the running database rather than assumed, the verification runs that produced the demo figures, and the open questions and gaps found while writing it.

Guide version: r1 Module version: 2.0.0 Updated: 2026-08-23 Estimated time: 7 min 4 views 0% helpful
Reference

Source Evidence

Where every statement in this guide came from. This page exists so a reader can check the guide rather than trust it — and so the next person to maintain it knows what was actually verified against a running system versus what was read off a design document.

????
Why this page exists. Module documentation drifts. Recording what was inspected, when, and how, makes the drift visible: if a file listed below has changed since, the sections of the guide that depend on it are the ones to re-check first.

What was inspected

Guide written against module version 1.4.0. Files read in the live module tree:

AreaReadUsed for
Manifestmodule.jsonVersion, slug, category, declared features, display metadata.
ConfigurationAll 11 files in config/Permissions, sidebar, routes, statuses, settings tab, audit events, API scopes.
Controllers19 files, ~15,500 linesWhich screens exist, which tabs each hub has, and which permission gates each action carries.
Models29 files, ~14,900 linesWhich tables are queried and how balances and aging are derived.
LibrariesWu_bill_calculator, Wu_workflow, Water_utility_smsThe billing arithmetic, tier resolution and validation rules; the status-transition contract.
Migrations7 filesTable shapes, and which settings arrive with the schema.
Jobs4 filesJob slugs and cron schedules.
Views107 files across 17 foldersConfirming each documented screen and tab is really rendered.
Language~1,370 keysThe labels used in this guide match what the UI shows.
Specifications10 documents, ~8,800 lines under specs/Design intent and the reasoning behind decisions — always cross-checked against code.

Facts confirmed against a running database

These were not taken from a document. They were read out of a live schema and a live seeded dataset, because each one is the kind of detail that quietly goes stale.

Claim in this guideHow it was confirmed
27 wu_ tables exist, with their exact columns and typesRead the live schema directly, table by table.
Every table carries a soft-delete flag and the audit blockSame schema read; confirmed on all 27.
38 permission keys in 9 categoriesCounted in config/permissions.php.
14 module settings, and their shipped valuesRead from the live settings table after migration, and cross-checked against the seeding migration.
Twelve status lifecycles and their exact edgesRead from config/statuses.php, which is the single definition the runtime uses.
Job slugs and cron schedulesRead from the job classes themselves, not from the manifest — the manifest's feature flags are documentation, not enforcement.
Demo bill amounts (₱439.00 / ₱235.00 / ₱2,330.00 / ₱235.00) and the run total ₱3,239.00Queried out of the database after seeding, then reconciled against the tier arithmetic by hand.
The commercial bill's five lines and the 13 cu.m billed above the top blockQueried the bill lines directly.
A flagged senior account produces no discount line todayRead the refusal reason recorded in that bill's own snapshot.
Both demo tariff versions pass the module's own validatorRan the validator against the persisted tiers of both versions.

Verification runs behind the demo dataset

The demo seeder documented in this guide was exercised end to end before publication.

CheckResult
First run on an empty district85 rows created across 24 tables.
Immediate second run (idempotence)0 created, 0 changed, 85 already current — and a row-by-row comparison across all 24 tables came back byte-identical, including timestamps.
Coherence of the seeded chain43 assertions, all passing: every account resolves its customer and its tap; every bill resolves its run, account, reading and tariff version; every allocation resolves its receipt and its bill.
Money reconciliationRun totals equal the sum of the bills; each bill's water charge equals the sum of its own water lines; the batch total equals the sum of the posted receipts; no bill is over-allocated.
Removal85 rows soft-deleted, physical row count unchanged — nothing was destroyed.
Second removal0 rows — a clean no-op.
Re-seed after removalThe same 85 rows restored in place, with no duplicates created.
Dry run, both directionsReported the full plan and wrote nothing.

Design decisions this guide relies on

A handful of choices explain most of the module's behaviour. They were confirmed in code, not just in the design documents.

DecisionConsequence you can see
Customers are core records, not a module-owned tableA water customer is the same record as any other customer in the system — which is why the demo has to create six of them.
The tap, not the customer, is the stable entityOwnership transfer is close-and-open. There is no "change owner" button, and that is deliberate.
Readings attach to a meter installation, not to a meterA meter swapped mid-cycle still produces one correct bill, because each installation's span is computed on its own digits and multiplier.
Tariff versions are immutable once approvedNo edit path exists from approved back to draft — a correction is a new version with a later effective date.
The billing engine is deliberately isolated from everythingIt cannot read the database, the settings or the clock. Everything it needs is handed to it, which is what makes a bill reproducible years later from its snapshot alone.
Money defaults are inert, never inventedAn unconfigured senior discount rate produces no line and records why, rather than a plausible-looking guess.
Penalty materialises onceThe nightly job derives a penalty for display but never writes one; it becomes real exactly once, as a line on the next bill. That single-writer arrangement removes the likeliest source of duplicated money.
"Paid" is derived, never storedVoiding a receipt makes its bill outstanding again immediately, with no second place that could disagree.
One status writer for the whole moduleEvery status change is checked against the legal moves, checked against the user's permission, and recorded — there is no back door.
Field Service is optional and fails softService orders always work; the bridge only adds a mirror when it is switched on.

Gaps and open questions found while writing

Recorded honestly, because a guide that only lists what works is not much use to whoever has to run the thing.

FindingWhat it means for you
The senior-citizen discount has no rate setting. The module has an enable flag but no place outside the tariff to put the statutory percentage and ceiling. Configure the rate in your tariff. Until then, flagged accounts get no discount line — and the reason is recorded on each bill, so it is visible rather than silent.
Approving a tariff version starts out held by nobody. The permission is newer than the original set and is granted to no role by default. Appoint a checker deliberately in the Roles editor. Until you do, versions can be drafted but not approved.
No SMS gateway ships with the platform. OTP-by-SMS and text notices need a provider configured first. Email and printed service work regardless.
Meter-reading evidence is not included in the demo data. Deliberate: an evidence row without a real file behind it produces a broken preview. Upload real photos to see that feature working.
Portal credentials are never seeded. Also deliberate — fabricating them would ship a working customer login. Request a live OTP instead.
The demo tariff rates are invented. They exist to show the LWUA tier shape working, not to be billed. Replace them before going live.

How to re-verify this guide

If you are updating these pages after a change to the module, redo this:

  1. Re-read the configuration

    The 11 files in config/ are the fastest source of truth for permissions, screens, routes, statuses and audit events. Most of the reference page comes straight out of them.

  2. Confirm against the live schema

    Table and column lists drift faster than any document. Read them from a migrated database.

  3. Re-seed and re-verify

    Run the demo seeder, then run it again and confirm nothing changed. Re-query the demo bill amounts and update this guide if they moved — a change there means the billing arithmetic changed.

  4. Prefer code over specification

    Where a specification and the code disagree, the code is what your district will actually experience. Say so on this page when you find one.

Next steps

Was this guide helpful?

Report a content problem