Skip to content

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:

Setting::get('key', 'default_value')
Setting::set('key', 'value')

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

  1. Decide on the key name (snake_case, e.g., invoice_due_days).
  2. Add a default value in the seeder (database/seeders/SettingsSeeder.php):
    Setting::set('invoice_due_days', '7');
    
  3. Add the setting to the appropriate section in SettingsController::index() so it is passed to the frontend as a prop.
  4. Add the form field to the corresponding tab in Settings/Index.jsx.
  5. Add the key to the SettingsController::update() validation rules and Setting::set() call.
  6. 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:

$profile = CompanyProfile::first();
// or via helper
$profile = CompanyProfile::instance();

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:

  1. Validates the file (max 2MB, mime types: jpg/png/svg/webp).
  2. Stores it under storage/app/public/logos/ via Storage::disk('public')->put().
  3. Deletes the old logo file if one existed.
  4. Saves the new path to company_profiles.logo_path.
  5. 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).