25 — Settings¶
Overview¶
PyroRadius settings are managed through two mechanisms: a generic key-value store (settings table via the Setting model) for system-wide configuration, and dedicated models for structured data (CompanyProfile, HelplineNumber, PaymentMethod). The Settings/Index.jsx page exposes all of these through tabbed sections.
Settings Architecture¶
The Setting Model¶
File: app/Models/Setting.php
The settings table stores arbitrary key-value pairs for the entire system. The Setting facade/model provides a static helper for retrieval:
Table schema:
CREATE TABLE settings (
id BIGSERIAL PRIMARY KEY,
key VARCHAR(191) NOT NULL UNIQUE,
value TEXT NULL,
created_at TIMESTAMP,
updated_at TIMESTAMP
);
Settings are not dealer-scoped — they are system-wide. Only super_admin can modify settings. Dealers can read certain display settings (like company_name and brand_color) via shared config injected into the Inertia page props.
How Setting::get() Works¶
// Retrieve with default
$enabled = Setting::get('whatsapp_enabled', '0');
// Set a value (creates or updates the row)
Setting::set('whatsapp_enabled', '1');
Values are always stored as strings. Cast to int/bool at the call site as needed:
$graceDays = (int) Setting::get('grace_period_days', '3');
$isEnabled = Setting::get('whatsapp_enabled', '0') === '1';
How to Add a New Setting¶
- Decide on the key name (snake_case, e.g.,
invoice_due_days). - Add a default value in the seeder (
database/seeders/SettingsSeeder.php): - Add the setting to the appropriate section in
SettingsController::index()so it is passed to the frontend as a prop. - Add the form field to the corresponding tab in
Settings/Index.jsx. - Add the key to the
SettingsController::update()validation rules andSetting::set()call. - Call
Setting::get('invoice_due_days', '7')wherever the business logic needs it.
All Known Settings Keys¶
WhatsApp Settings¶
| Key | Purpose | Default | Values |
|---|---|---|---|
whatsapp_enabled |
Master toggle for WhatsApp notifications | 0 |
0 or 1 |
whatsapp_driver |
Which WhatsApp backend to use | log |
openwa or log |
whatsapp_contact |
Admin/support WhatsApp number for replies | null |
E.164 format e.g. 923001234567 |
whatsapp_api_url |
Base URL of the OpenWA API instance | null |
URL string |
whatsapp_api_key |
API key/token for OpenWA | null |
String |
whatsapp_session_id |
OpenWA session identifier | default |
String |
When whatsapp_driver is log, all outgoing messages are written to storage/logs/whatsapp.log instead of being sent. This is used for local development and testing.
Billing Settings¶
| Key | Purpose | Default | Values |
|---|---|---|---|
grace_period_days |
Days after expiry before customer is marked expired and disconnected | 3 |
Integer string |
invoice_auto_generate |
Whether GenerateMonthlyInvoices runs automatically | 1 |
0 or 1 |
invoice_due_days |
Days after invoice creation before it is marked overdue | 7 |
Integer string |
invoice_prefix |
Prefix for auto-generated invoice numbers | INV |
String |
receipt_prefix |
Prefix for payment receipt numbers | RCP |
String |
RADIUS Settings¶
| Key | Purpose | Default | Values |
|---|---|---|---|
radius_secret |
Shared secret used between FreeRADIUS and PyroRadius for health checks | null |
String |
radius_host |
FreeRADIUS server host for health check | 127.0.0.1 |
IP or hostname |
radius_port |
FreeRADIUS auth port | 1812 |
Integer string |
General / Display Settings¶
| Key | Purpose | Default | Values |
|---|---|---|---|
company_name |
Company name shown in UI headers and templates | PyroNet |
String |
logo_path |
Path to uploaded logo (relative to storage) | null |
String |
brand_color |
Primary brand color applied to UI accents | #E95420 |
Hex color string |
timezone |
System timezone for scheduling and display | Asia/Karachi |
PHP timezone string |
currency_symbol |
Currency symbol shown in invoices | Rs. |
String |
date_format |
Date format for display in UI | d M Y |
PHP date format string |
CompanyProfile Model¶
File: app/Models/CompanyProfile.php
One row per system (singleton pattern). Stores branding and contact information used on invoices, WhatsApp templates, and the UI header.
CREATE TABLE company_profiles (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
tagline VARCHAR(500) NULL,
logo_path VARCHAR(500) NULL,
brand_color VARCHAR(20) NULL DEFAULT '#E95420',
address TEXT NULL,
city VARCHAR(100) NULL,
phone VARCHAR(50) NULL,
email VARCHAR(191) NULL,
website VARCHAR(255) NULL,
created_at TIMESTAMP,
updated_at TIMESTAMP
);
Accessed via:
The CompanyProfile data is injected into every Inertia page via HandleInertiaRequests middleware so components can access usePage().props.company anywhere.
Logo Upload¶
The logo is uploaded via a multipart form POST to SettingsController::updateCompanyProfile(). The controller:
- Validates the file (max 2MB, mime types: jpg/png/svg/webp).
- Stores it under
storage/app/public/logos/viaStorage::disk('public')->put(). - Deletes the old logo file if one existed.
- Saves the new path to
company_profiles.logo_path. - The stored URL is available as
Storage::url($profile->logo_path).
The logo is displayed in the sidebar header, invoices, and WhatsApp notification headers (as a URL embedded in templates).
brand_color and How It Affects the UI¶
The brand_color setting (and CompanyProfile::brand_color) is injected as a CSS custom property on the root element via the main layout blade:
<style>
:root {
--brand-color: {{ $brandColor }};
--brand-color-hover: {{ $brandColorDarker }};
}
</style>
Tailwind CSS utility classes that reference --brand-color are used on:
- Sidebar active item highlight
- Primary buttons (
bg-brand) - Badge accents
- Invoice header bar
- WhatsApp template preview header
Changing brand_color in Settings takes effect immediately on the next page load (no rebuild needed — it is server-rendered into the <style> block).
HelplineNumber Model¶
File: app/Models/HelplineNumber.php
Dealer-specific support contact numbers shown in WhatsApp notification templates and optionally on the customer portal. Multiple numbers can be registered per dealer.
CREATE TABLE helpline_numbers (
id BIGSERIAL PRIMARY KEY,
dealer_id BIGINT NOT NULL REFERENCES users(id),
label VARCHAR(100) NOT NULL, -- e.g. "Support", "Billing", "Technical"
number VARCHAR(50) NOT NULL, -- E.164 format
is_whatsapp BOOLEAN NOT NULL DEFAULT false,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP,
updated_at TIMESTAMP
);
When a WhatsApp notification is sent to a customer, the template fetches HelplineNumber::where('dealer_id', $customer->dealer_id)->orderBy('sort_order')->get() and appends the numbers to the message footer.
PaymentMethod Model¶
File: app/Models/PaymentMethod.php
Defines the accepted payment methods shown on invoices and WhatsApp payment-due templates.
CREATE TABLE payment_methods (
id BIGSERIAL PRIMARY KEY,
dealer_id BIGINT NOT NULL REFERENCES users(id),
name VARCHAR(100) NOT NULL, -- e.g. "Jazz Cash", "Easypaisa", "Bank Transfer"
detail TEXT NULL, -- Account number, IBAN, or instructions
sort_order INTEGER NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP,
updated_at TIMESTAMP
);
Payment methods are dealer-scoped so each dealer can configure their own collection accounts. They appear on the invoice PDF footer and in the WhatsApp payment reminder template.
Settings/Index.jsx Page Sections¶
The Settings page uses a tab/sidebar navigation. Each section POSTs to its own endpoint to avoid overwriting unrelated settings.
Tab 1 — General¶
Fields: company_name, timezone, currency_symbol, date_format.
POST to PATCH /settings/general.
Tab 2 — Company Profile¶
Fields: name, tagline, address, city, phone, email, website, logo upload, brand_color (color picker).
POST to POST /settings/company-profile (multipart for logo upload).
Sub-section: Helpline Numbers — inline CRUD table (add/edit/delete rows, drag to reorder via sort_order).
Sub-section: Payment Methods — inline CRUD table.
Tab 3 — Billing¶
Fields: grace_period_days, invoice_auto_generate (toggle), invoice_due_days, invoice_prefix, receipt_prefix.
POST to PATCH /settings/billing.
Tab 4 — WhatsApp¶
Fields: whatsapp_enabled (master toggle), whatsapp_driver (select: openwa / log), whatsapp_contact, whatsapp_api_url, whatsapp_api_key, whatsapp_session_id.
A Test Message button sends a test WhatsApp message to whatsapp_contact using the current driver.
POST to PATCH /settings/whatsapp.
Tab 5 — RADIUS¶
Fields: radius_secret, radius_host, radius_port.
A Test RADIUS button fires HealthController::radiusCheck() and displays the result inline.
POST to PATCH /settings/radius.
Permissions Required¶
| Action | Permission |
|---|---|
| View settings | settings.view or super_admin |
| Edit general / billing / RADIUS | settings.edit or super_admin |
| Edit company profile | settings.edit or super_admin |
| Upload logo | settings.edit or super_admin |
| Manage helpline numbers | settings.edit or super_admin (dealer-scoped) |
| Manage payment methods | settings.edit or super_admin (dealer-scoped) |
Dealers can manage their own helpline numbers and payment methods but cannot touch system-wide settings (general, billing, RADIUS, WhatsApp).