Entity Relationships
This document describes the relationships between all models in the OPBX system.
Core Relationships
Organization (Tenant)
Organization
├── hasMany: User[]
├── hasMany: Extension[]
├── hasMany: DidNumber[]
├── hasMany: RingGroup[]
├── hasMany: ConferenceRoom[]
├── hasMany: BusinessHoursSchedule[]
├── hasMany: CallLog[]
├── hasOne: CloudonixSettings
└── hasMany: CallDetailRecord[]
User
User
├── belongsTo: Organization
├── hasOne: Extension (user type)
└── hasMany: PlatformAuditLog[] (as platform manager)
Extension (Central Entity)
Extension
├── belongsTo: Organization
├── belongsTo: User (for USER type)
├── belongsTo: AiAssistant (for AI_ASSISTANT type)
└── hasOne: AiLoadBalancer (via configuration)
PBX Resource Relationships
RingGroup
RingGroup
├── belongsTo: Organization
├── hasMany: RingGroupMember[]
├── belongsTo: Extension (fallback)
├── belongsTo: RingGroup (fallback)
├── belongsTo: IvrMenu (fallback)
└── belongsTo: AiAssistant (fallback)
DidNumber (DID)
DidNumber
├── belongsTo: Organization
└── hasDynamic: Extension | RingGroup | IvrMenu | AiAssistant
(based on routing_type configuration)
ConferenceRoom
ConferenceRoom
└── belongsTo: Organization
AI Feature Relationships
AiAssistant
AiAssistant
├── belongsTo: Organization
├── belongsTo: User (creator)
├── belongsTo: User (updater)
└── hasMany: Extension[]
AiAssistantLoadBalancer
AiAssistantLoadBalancer
├── belongsTo: Organization
├── hasMany: AiAssistantLoadBalancerMember[]
├── belongsTo: Extension (fallback)
├── belongsTo: RingGroup (fallback)
├── belongsTo: IvrMenu (fallback)
└── belongsTo: AiAssistant (fallback)
AiAssistantLoadBalancerMember (Join)
AiAssistantLoadBalancerMember
├── belongsTo: AiAssistantLoadBalancer
└── belongsTo: AiAssistant
Call Management Relationships
CallLog
CallLog
├── belongsTo: Organization
├── belongsTo: DidNumber
├── belongsTo: Extension
└── belongsTo: RingGroup
CallDetailRecord
CallDetailRecord
└── belongsTo: Organization
Recording
Recording
├── belongsTo: Organization
├── belongsTo: User (creator)
└── belongsTo: User (updater)
Configuration Relationships
IvrMenu
IvrMenu
├── belongsTo: Organization
└── hasMany: IvrMenuOption[]
IvrMenuOption (Join)
IvrMenuOption
├── belongsTo: IvrMenu
└── hasDynamic: Extension | RingGroup | IvrMenu | AiAssistant
(based on destination_type)
BusinessHoursSchedule
BusinessHoursSchedule
├── belongsTo: Organization
├── hasMany: BusinessHoursScheduleDay[]
└── hasMany: BusinessHoursException[]
CloudonixSettings
CloudonixSettings
└── belongsTo: Organization
Security Relationships
InboundBlacklist
InboundBlacklist
└── belongsTo: Organization
OutboundWhitelist
OutboundWhitelist
└── belongsTo: Organization
Multi-Tenancy
All models (except CallDetailRecord and PlatformAuditLog) use the OrganizationScope global scope:
#[ScopedBy([OrganizationScope::class])]
class Model extends Model
{
// Automatically filtered by organization_id
}
This ensures complete data isolation between organizations.
Relationship Diagram
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Organization │◄────┤ User │◄────┤ Extension │
│ (Tenant) │ │ (has role) │ │ (has type) │
└────────┬────────┘ └─────────────────┘ └────────┬────────┘
│ │
│ ┌─────────────────┐ ┌─────────────────┐│
├───►│ DidNumber │ │ AiAssistant │◄┘
│ │ (routes to) │ │ (AI type) │
│ └─────────────────┘ └─────────────────┘
│
│ ┌─────────────────┐ ┌─────────────────┐
├───►│ RingGroup │ │ ConferenceRoom │
│ │ (has members) │ │ │
│ └─────────────────┘ └─────────────────┘
│
│ ┌─────────────────┐ ┌─────────────────┐
├───►│ IvrMenu │ │ CallLog/CDR │
│ │ (has options) │ │ │
│ └─────────────────┘ └─────────────────┘
│
│ ┌─────────────────┐ ┌─────────────────┐
└───►│ Settings │ │ Security Lists │
│ (Cloudonix) │ │ (Black/White) │
└─────────────────┘ └─────────────────┘
Eager Loading Recommendations
When querying related data, use eager loading to avoid N+1 problems:
// Load users with their extensions
$users = User::with('extension')->get();
// Load ring groups with members and extensions
$ringGroups = RingGroup::with([
'members.extension.user',
'fallbackExtension'
])->get();
// Load call logs with all related entities
$calls = CallLog::with([
'didNumber',
'extension.user',
'ringGroup'
])->get();