Customer Portal
The Rental Billing customer portal is a React single page application served at
/rental-portal and built from the rental-portal folder of this app.
Feature map
| Feature | Frontend | Backend |
|---|---|---|
| Public property browser | /properties, PropertyCard, PropertyRow |
api.properties.get_available_properties |
| Property details and gallery | /properties/:property |
api.portal.property_details.get_property_details |
| Unit booking | /properties/:property/book |
api.portal.bookings.* |
| My bookings | /bookings |
api.portal.bookings.* |
| My properties / tenancy | /my-properties |
api.portal.properties.get_my_properties |
| Invoices, orders and payments | /invoices |
api.portal.payments.* |
| Support tickets and replies | property and booking support dialogs | api.portal.support.* |
| Notifications | /settings/notifications |
standard Frappe notification log |
| Profile and password | /app/user/<user> |
standard Frappe user pages |
| Admin user list | /users |
standard User APIs |
| Generic doctype list/form | /app/:doctype(/:id) |
standard Frappe metadata and CRUD |
Authentication
| Flow | Implementation |
|---|---|
| Sign in | frappe-react-sdk useFrappeAuth (Frappe session login) |
| Sign up | frappe.core.doctype.user.user.sign_up (creates a Website User and emails a verification link) |
| Forgot password | frappe.core.doctype.user.user.reset_password |
| Change password | frappe.core.doctype.user.user.update_password |
| Logout | Frappe session logout |
The portal never stores or transmits passwords itself; credential handling is
delegated to Frappe. Sign-up is available only when
Website Settings → Disable Signup is unchecked.
utility_billing/www/rental-portal.py embeds the standard Frappe session boot
so the SPA receives frappe.boot and the session user without an extra call.
Portal roles and capabilities
utility_billing.api.portal.context.get_portal_context resolves the portal role
and the capabilities used by navigation and guards:
Guest– anonymous visitor; may browse properties.Tenant– a user with theCustomerorTenantrole linked through
Customer.portal_users.Property Owner/Landlord,Property Manager,Utility Customer– role
based, reserved for their dashboards.
Guests receive browse_properties only. Authenticated portal roles currently
receive the full capability map (browse_properties, view_dashboard,
view_my_units, manage_profile, manage_notifications).
Tenant linkage and access control
A tenant's records resolve through standard ERPNext documents:
User (Customer.portal_users) → Customer → Contract (party_type = Customer)
→ Contract Utility Property Item → Utility Property
get_my_properties classifies allocations as:
| State | Rule |
|---|---|
Reserved |
active allocation on a Contract that is not signed |
Active |
active, signed allocation that has not ended |
History |
inactive allocation or an ended lease |
Property details, invoices, orders, payments, meter readings and support
tickets are always scoped to the caller's own Customers and the requested
property/document. Client-supplied ownership, amounts and statuses are never
trusted.
Bookings
A portal booking is a draft Utility Service Request created for the caller's
Customer. It carries the property row, lease dates and tenant notes.
- The unit stays publicly available until staff submit the request.
- A second pending booking for the same unit is rejected; the tenant is sent to
the existing request. - Tenants can modify dates, extend or cancel a pending/current booking according
to the same ownership rules. - Submitting the request creates the Contract; signing it moves the unit from
ReservedtoActive.
The portal booking form reads defaults from
get_booking_options: property, monthly rent, deposit, lease dates and any
pending booking.
Invoices, orders and payments
/invoices lists every submitted, payable Sales Invoice and Sales Order of the
signed-in Customer.
- An invoice is paid for its outstanding amount.
- A Sales Order is paid as an advance for its unbilled balance.
- The portal asks the selected gateway controller for a checkout URL and
redirects the customer. It does not create Payment Entries or Payment
Requests itself; the gateway records the payment on the reference document. - A
Payment Gateway Accountmust exist for the document's company. The demo
data does not create one.
Support tickets
Customers raise standard ERPNext Issue documents from:
- a booking (
create_support_issue), or - an active/reserved property (
create_property_support_issue).
Each ticket stores utility_property, utility_service_request (when known),
the Customer, the company and via_customer_portal. Replies are Comments on the
Issue and are rendered with the document timeline. Status counts (open, replied,
resolved) come from the Issue status.
Property details and related documents
| Viewer | Sees |
|---|---|
| Guest / unrelated user | details of available units only |
| Tenant of the unit (current or past) | details plus tenancy summary, account totals and related documents |
| Anybody else | PermissionError → "occupied or reserved, not linked to your account" |
Related documents are resolved through:
| Document | Reached through |
|---|---|
| Meter Reading | Meter Reading.property |
| Utility Service Request | header utility_property or requested_properties rows |
| Sales Invoice | Sales Invoice Item.meter_reading, Sales Invoice Meter Reading.meter_reading or utility_service_request |
| Sales Order | Sales Order Item.meter_reading, Sales Order Meter Reading.meter_reading or utility_service_request |
| Payment Entry | Payment Entry Reference rows pointing at those invoices/orders |
API surface
| Endpoint | Guest | Purpose |
|---|---|---|
utility_billing.api.portal.context.get_portal_context |
yes | session/role/capability bootstrap |
utility_billing.api.properties.get_available_properties |
yes | public property browser |
utility_billing.api.portal.property_details.get_property_details |
yes | property details; own tenancy/documents when applicable |
utility_billing.api.portal.properties.get_my_properties |
no | tenant allocations |
utility_billing.api.portal.bookings.get_booking_options |
no | booking defaults and pending booking |
utility_billing.api.portal.bookings.create_booking |
no | create a draft booking |
utility_billing.api.portal.bookings.get_my_bookings |
no | current/history bookings |
utility_billing.api.portal.bookings.modify_booking |
no | change pending/current booking dates |
utility_billing.api.portal.bookings.extend_booking |
no | extend a booking |
utility_billing.api.portal.bookings.cancel_booking |
no | cancel a booking |
utility_billing.api.portal.support.get_property_support |
no | tickets for a property |
utility_billing.api.portal.support.get_booking_support |
no | tickets for a booking |
utility_billing.api.portal.support.get_support_issue |
no | one ticket and timeline |
utility_billing.api.portal.support.create_support_issue |
no | raise from a booking |
utility_billing.api.portal.support.create_property_support_issue |
no | raise from a property |
utility_billing.api.portal.support.reply_to_support_issue |
no | customer reply |
utility_billing.api.portal.payments.get_payment_gateways |
no | configured gateway accounts |
utility_billing.api.portal.payments.get_customer_documents |
no | invoices, orders and totals |
utility_billing.api.portal.payments.initiate_payment |
no | gateway checkout URL |
Generic doctype browsing (/app routes)
The portal reuses metadata-driven DocTypeList / DocTypeForm components for
/app/:doctype(/:id) pages. They use standard Frappe endpoints:
frappe.desk.form.load.getdoctypefrappe.desk.reportview.getandget_countfrappe.desk.form.load.getdocfrappe.client.save/frappe.desk.form.save.savedocsfrappe.desk.search.search_linkfrappe.model.workflow.*
Slug mapping lives in src/lib/doctype-map.ts. Users without permission are
redirected by PermissionGuard to the forbidden page.
Notifications and appearance
- Notification feed and unread badge use the standard notification log.
- Preferences are handled by
/app/notification-settings/<user>. - The theme customiser supports light/dark mode, 16 estate presets, per-mode
token overrides, opt-in gradients, radius, sidebar variant and layout. - Theme selections persist in
localStorageunderrental-portal:theme.
Frontend structure
rental-portal/src
├── app/ # auth, dashboard, properties, bookings, invoices, support pages
├── components/
│ ├── properties/ # card, row, media, status and layout components
│ ├── payments/ # pay button and gateway dialog
│ ├── support/ # support list/create/detail/reply/timeline
│ ├── doctype/ + fields/ # generic Frappe metadata renderers
│ ├── theme-customizer/ # presets, colours, gradients, layout
│ └── ui/ # shared primitives
├── config/routes.tsx # route tree and command-search metadata
├── contexts/ # user, portal role, notifications
├── lib/ # Frappe helpers, doctype slugs, formatting
└── types/ # shared payloads
Demo accounts
The demo generator creates 12 Website Users linked to Customers. See
Demo Data for every email. All demo accounts use the password
Demo@12345; use them for staging and training only.
Tests
utility_billing/utility_billing/tests/test_portal_roles.py covers role,
capability and allocation classification rules:
bench --site <site> run-tests --module utility_billing.utility_billing.tests.test_portal_roles