@extends('layouts.app') @section('title', $service->service_number) @section('content')

{{ $service->service_number }}

{{ $service->customer->display_name }} · {{ $service->status }} · {{ $service->plan?->name }} @if ($service->is_online) · online @endif

@if ($service->status === 'pending' || $service->status === 'provisioning') @endif @if ($service->status === 'active' || $service->status === 'grace') @endif @if ($service->status === 'suspended')
@csrf
@endif @if ($service->is_online)
@csrf
@endif
@include('components.kpi', ['label' => 'Monthly charge', 'value' => money($service->effective_price, $service->customer->currency), 'variant' => 'good', 'href' => '#billing']) @include('components.kpi', ['label' => 'Outstanding', 'value' => money($service->outstanding, $service->customer->currency), 'variant' => $service->outstanding > 0 ? 'bad' : 'good', 'href' => '#billing']) @include('components.kpi', ['label' => 'Next billing', 'value' => $service->next_billing_date?->format('d M Y') ?? '—', 'href' => '#billing']) @include('components.kpi', ['label' => 'Session uptime', 'value' => $service->currentSession?->duration_label ?? '—', 'variant' => $service->is_online ? 'good' : '', 'href' => '#sessions']) @include('components.kpi', ['label' => 'Data this period', 'value' => human_bytes($usage['total_bytes']), 'variant' => 'info', 'href' => '#usage']) @include('components.kpi', ['label' => 'Quota used', 'value' => $service->quota_used_percent !== null ? percent($service->quota_used_percent) : 'Unlimited', 'variant' => ($service->quota_used_percent ?? 0) > 90 ? 'warn' : 'info', 'href' => '#usage'])
@if ($service->provisioning_status !== 'active' && $service->status !== 'cancelled')
Provisioning state: {{ title_case_words($service->provisioning_status) }}. The RADIUS record is {{ $service->radius_synced ? 'pushed' : 'not yet pushed' }}. Activate the service once the technician has signed off the installation.
@endif
Overview Sessions Usage Billing ({{ $invoices->count() }}) AAA / RADIUS History
{{-- ============ OVERVIEW ============ --}}

Connection details

{{ title_case_words($service->service_type) }}
Plan
{{ $service->plan?->name }}
Speed profile
{{ $service->speedProfile?->name ?? 'Inherited from plan' }}
Download / upload
{{ $service->download_kbps ? round($service->download_kbps/1024).' / '.round($service->upload_kbps/1024).' Mbps' : '—' }}
Monthly quota
{{ $service->monthly_quota_gb ? $service->monthly_quota_gb.' GB' : 'Unlimited' }}
Static IP
{{ $service->static_ip ?: 'Dynamic' }}
IP pool
{{ $service->ipPool?->name ?: '—' }}
MAC address
{{ $service->mac_address ?: '—' }}
NAS / BNG
{{ $service->nas_ip ?: 'Auto-detect' }}
Priority
{{ $service->speedProfile?->priority ?? 8 }} (1 = highest)

Installation

@if ($service->onu) ONU {{ $service->onu->optical_health }} @endif
Address
{{ $service->installation_address }}
City / region
{{ trim(($service->city ?? '').' '.($service->region ?? '')) ?: '—' }}
Coordinates
{{ $service->latitude && $service->longitude ? $service->latitude.', '.$service->longitude : '—' }}
OLT
{{ $service->olt_name ?: ($service->onu?->pon?->olt?->name ?? '—') }}
PON port
{{ $service->pon_port ?: ($service->onu?->pon?->pon_port ?? '—') }}
Splitter
{{ $service->splitter ?: '—' }}
CPE model
{{ $service->cpe_model ?: '—' }}
CPE serial
{{ $service->cpe_serial ?: '—' }}
CPE MAC
{{ $service->cpe_mac ?: '—' }}
@if ($service->onu)
Rx / Tx power
{{ $service->onu->rx_power_dbm }} / {{ $service->onu->tx_power_dbm }} dBm
Distance
{{ $service->onu->distance_km }} km
@endif

Billing calendar

Billing period
{{ title_case_words($service->billing_period) }}
Billing cycle
{{ title_case_words($service->billing_cycle) }}
List price
{{ money($service->price, $service->customer->currency) }}
@if ($service->discount_amount > 0)
Discount
−{{ money($service->discount_amount, $service->customer->currency) }} @if ($service->discount_ends_on)until {{ $service->discount_ends_on->format('d M Y') }}@endif
@endif
Effective price
{{ money($service->effective_price, $service->customer->currency) }}
Add-ons
{{ money($service->addons_total, $service->customer->currency) }}/month
Start date
{{ $service->start_date->format('d M Y') }}
Next billing
{{ $service->next_billing_date?->format('d M Y') ?: 'Not scheduled' }}
Payment due
{{ $service->next_due_date?->format('d M Y') ?: '—' }}
Auto renew
{{ $service->auto_renew ? 'Enabled' : 'Disabled' }}
Contract
{{ $service->contract_months ? $service->contract_months.' months — '.title_case_words($service->contract_status) : 'No contract' }} @if ($service->contract_ends_on)
Ends {{ $service->contract_ends_on->format('d M Y') }}
@endif
SLA tier
{{ title_case_words($service->sla_tier) }}

Quick actions

RADIUS username {{ $service->username }}
AAA sync {{ $service->radius_synced ? 'synced '.($service->radius_synced_at?->diffForHumans() ?? '') : 'not synced' }}
@csrf

Active add-ons
@forelse ($service->addons as $addon)
{{ $addon->addon?->name }} {{ money($addon->price, $service->customer->currency) }}
@empty

No add-ons attached.

@endforelse

Internal notes

@csrf
@if ($notes->isNotEmpty())
@foreach ($notes as $note)
{{ $note->user?->name ?? 'System' }} {{ $note->type }}
{{ $note->created_at->format('d M Y H:i') }}
{{ $note->note }}
@endforeach
@else

No notes recorded yet.

@endif
{{-- ============ SESSIONS ============ --}}

RADIUS sessions

@csrf
@forelse ($sessions as $session) @empty @endforelse
StartedStoppedDurationIPNAS DownUpTotalTerminated
@if ($session->is_online)@endif {{ $session->started_at?->format('d M H:i') ?: '—' }} {{ $session->stopped_at?->format('d M H:i') ?: '—' }} {{ $session->duration_label }} {{ $session->framed_ip ?: '—' }} {{ $session->nas_ip }} {{ human_bytes($session->input_octets) }} {{ human_bytes($session->output_octets) }} {{ human_bytes($session->total_octets) }} {{ $session->terminate_cause ?: '—' }}
@include('components.empty-state', ['icon' => '⏱', 'title' => 'No sessions recorded', 'message' => 'Sessions appear once the NAS sends RADIUS accounting (radacct).'])
@if ($sessions->hasPages())
{{ $sessions->links('partials.pagination') }}
@endif

Authentication log

From FreeRADIUS radpostauth
@forelse ($authLogs as $log) @empty @endforelse
WhenResultNASAssigned IP
{{ $log->created_at->format('d M Y H:i:s') }} {{ $log->reply }} {{ $log->nas_ip ?: '—' }} {{ $log->framed_ip ?: '—' }}
@include('components.empty-state', ['icon' => '🔒', 'title' => 'No authentication attempts logged'])
{{-- ============ USAGE ============ --}}
@include('components.kpi', ['label' => 'Total this period', 'value' => human_bytes($usage['total_bytes']), 'variant' => 'info', 'href' => '#']) @include('components.kpi', ['label' => 'Download', 'value' => human_bytes($usage['input_bytes']), 'href' => '#']) @include('components.kpi', ['label' => 'Upload', 'value' => human_bytes($usage['output_bytes']), 'href' => '#']) @include('components.kpi', ['label' => 'Online time', 'value' => $usage['online_hours'].' h', 'delta' => $usage['sessions'].' sessions', 'href' => '#'])

Daily consumption — last 30 days

@include('components.chart', [ 'series' => collect($usageSeries)->map(fn ($p) => [ 'label' => \Carbon\Carbon::parse($p['date'])->format('d M'), 'value' => $p['bytes'], ])->all(), 'type' => 'bar', 'format' => 'bytes', 'height' => 210, ])
{{-- ============ BILLING ============ --}}

Invoices for this service

@forelse ($invoices as $invoice) @empty @endforelse
InvoicePeriodIssuedDue TotalBalanceStatus
{{ $invoice->invoice_number }} {{ $invoice->period_start?->format('d M') ?? '—' }} – {{ $invoice->period_end?->format('d M Y') ?? '—' }} {{ $invoice->issue_date->format('d M Y') }} {{ $invoice->due_date->format('d M Y') }} {{ money($invoice->total, $invoice->currency) }} {{ money($invoice->balance, $invoice->currency) }} {{ $invoice->status }} View @if ($invoice->balance > 0) Collect @endif
@include('components.empty-state', ['icon' => '📄', 'title' => 'No invoices yet'])
{{-- ============ AAA / RADIUS ============ --}}

AAA record

@csrf
@if ($service->radiusUser)
Username
{{ $service->radiusUser->username }}
Auth type
{{ $service->radiusUser->auth_type }}
Realm
{{ $service->radiusUser->realm ?: '—' }}
Group
{{ $service->radiusUser->groupname ?: '—' }}
Framed pool
{{ $service->radiusUser->framed_pool ?: '—' }}
Static IP
{{ $service->radiusUser->static_ip ?: '—' }}
Status
{{ $service->radiusUser->status }}
Last sync
{{ $service->radiusUser->last_sync_status }} {{ $service->radiusUser->last_synced_at?->diffForHumans() }}
@if ($service->radiusUser->last_sync_error)
Error
{{ $service->radiusUser->last_sync_error }}
@endif
@else @include('components.empty-state', ['icon' => '🔒', 'title' => 'No AAA record', 'message' => 'Push the record to FreeRADIUS to allow authentication.', 'action' => '
'.csrf_field().'
']) @endif

CoA / Disconnect history

All →
@forelse ($coaHistory as $req) @empty @endforelse
WhenTypePurposeNASResult
{{ $req->created_at->format('d M H:i') }} {{ $req->type }} {{ title_case_words($req->purpose) }} {{ $req->nas_ip }} {{ $req->status }} @if ($req->response_detail)
{{ \Illuminate\Support\Str::limit($req->response_detail, 60) }}
@endif
@include('components.empty-state', ['icon' => '⚡', 'title' => 'No CoA or DM requests sent'])
{{-- ============ HISTORY ============ --}}

Status transitions

@if ($history->isNotEmpty())
@foreach ($history as $entry)
{{ title_case_words($entry->from_status ?? 'new') }} → {{ title_case_words($entry->to_status) }}
{{ $entry->created_at->format('d M Y H:i') }} @if ($entry->user) · {{ $entry->user->name }} @endif
@if ($entry->reason)
{{ $entry->reason }}
@endif
@endforeach
@else

No transitions recorded.

@endif

Plan & service changes

@forelse ($changes as $change) @empty @endforelse
WhenTypeChangeProratedStatus
{{ $change->created_at->format('d M Y') }} {{ title_case_words($change->change_type) }} {{ $change->old_values['plan'] ?? '—' }} → {{ $change->new_values['plan'] ?? '—' }} {{ (float) $change->prorated_amount > 0 ? money($change->prorated_amount) : '—' }} {{ $change->status }}
@include('components.empty-state', ['icon' => '⇅', 'title' => 'No changes made'])

Cancellation

@if (in_array($service->status, ['cancelled', 'terminated']))
{{ title_case_words($service->status) }} on {{ $service->cancelled_at?->format('d M Y H:i') }}. Reason: {{ $service->cancellationReason?->reason ?: 'Not specified' }}. @if ($service->cancellation_notes)
{{ $service->cancellation_notes }}
@endif
@else
@csrf
@endif
{{-- ============ MODALS ============ --}} @endsection @push('scripts') @endpush