; Asterisk Dynamic Multi-Tenant Dialplan ; DID/route lookups via func_odbc from PostgreSQL [general] static=yes writeprotect=no clearglobalvars=no [globals] ; Defaults — overridden per-tenant via DB lookups DEFAULT_VM_CONTEXT=default ; ============================================================================ ; [from-trunk] — Inbound DID calls from providers via Kamailio ; ============================================================================ [from-trunk] ; Inbound call: look up tenant by DID, then route exten => _+X.,1,NoOp(Inbound trunk call to ${EXTEN}) same => n,Set(TENANT=${ODBC_TENANT_BY_DID(${EXTEN})}) same => n,GotoIf($["${TENANT}" = ""]?no-tenant,${EXTEN},1) same => n,Set(CDR(accountcode)=${TENANT}) same => n,Set(ROUTE=${ODBC_INBOUND_ROUTE(${EXTEN})}) same => n,GotoIf($["${ROUTE}" = ""]?no-route,${EXTEN},1) same => n,Set(DEST_TYPE=${CUT(ROUTE,|,1)}) same => n,Set(DEST_ID=${CUT(ROUTE,|,2)}) same => n,Set(DEST_DATA=${CUT(ROUTE,|,3)}) same => n,Goto(route-${DEST_TYPE},${EXTEN},1) exten => _X.,1,NoOp(Inbound trunk call to ${EXTEN} - no + prefix) same => n,Set(TENANT=${ODBC_TENANT_BY_DID(+${EXTEN})}) same => n,GotoIf($["${TENANT}" = ""]?no-tenant,${EXTEN},1) same => n,Set(CDR(accountcode)=${TENANT}) same => n,Set(ROUTE=${ODBC_INBOUND_ROUTE(+${EXTEN})}) same => n,GotoIf($["${ROUTE}" = ""]?no-route,${EXTEN},1) same => n,Set(DEST_TYPE=${CUT(ROUTE,|,1)}) same => n,Set(DEST_ID=${CUT(ROUTE,|,2)}) same => n,Set(DEST_DATA=${CUT(ROUTE,|,3)}) same => n,Goto(route-${DEST_TYPE},${EXTEN},1) ; ============================================================================ ; Destination handlers — routed by destination_type from inbound_routes ; ============================================================================ ; Route to extension [route-extension] exten => _X.,1,NoOp(Route to extension ${DEST_DATA}) same => n,Dial(PJSIP/${DEST_DATA},30,tTr) same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy) same => n,VoiceMail(${DEST_DATA}@${TENANT},u) same => n,Hangup() same => n(busy),VoiceMail(${DEST_DATA}@${TENANT},b) same => n,Hangup() ; Route to queue [route-queue] exten => _X.,1,NoOp(Route to queue ${DEST_DATA}) same => n,Queue(${DEST_DATA},tTr,,,300) same => n,Hangup() ; Route to IVR [route-ivr] exten => _X.,1,NoOp(Route to IVR ${DEST_DATA}) same => n,Goto(ivr-${DEST_DATA},s,1) same => n,Hangup() ; Route to voicemail [route-voicemail] exten => _X.,1,NoOp(Route to voicemail ${DEST_DATA}) same => n,Answer() same => n,VoiceMail(${DEST_DATA}@${TENANT},u) same => n,Hangup() ; Route to ring group [route-ring_group] exten => _X.,1,NoOp(Route to ring group ${DEST_DATA}) same => n,Dial(PJSIP/${DEST_DATA},30,tTr) same => n,Hangup() ; Route to conference [route-conference] exten => _X.,1,NoOp(Route to conference ${DEST_DATA}) same => n,Answer() same => n,ConfBridge(${DEST_DATA}) same => n,Hangup() ; Route to external number [route-external] exten => _X.,1,NoOp(Route to external number ${DEST_DATA}) same => n,Dial(PJSIP/${DEST_DATA}@kamailio-out,60,tTr) same => n,Hangup() ; ============================================================================ ; [outbound] — Outbound calls from extensions ; ============================================================================ [outbound] ; Outbound: 9 + number (strip 9, look up trunk by tenant) exten => _9.,1,NoOp(Outbound call from ${CHANNEL(endpoint)} to ${EXTEN:1}) same => n,Set(CDR(accountcode)=${CHANNEL(accountcode)}) same => n,Set(TRUNK_INFO=${ODBC_OUTBOUND_ROUTE(${CDR(accountcode)},${EXTEN:1})}) same => n,GotoIf($["${TRUNK_INFO}" = ""]?no-trunk,${EXTEN},1) same => n,Set(TRUNK_ID=${CUT(TRUNK_INFO,|,1)}) same => n,Dial(PJSIP/+${EXTEN:1}@kamailio-out,60,tTr) same => n,Hangup() ; International: 00 + country code exten => _900.,1,NoOp(International call to +${EXTEN:3}) same => n,Set(CDR(accountcode)=${CHANNEL(accountcode)}) same => n,Set(TRUNK_INFO=${ODBC_OUTBOUND_ROUTE(${CDR(accountcode)},+${EXTEN:3})}) same => n,GotoIf($["${TRUNK_INFO}" = ""]?no-trunk,${EXTEN},1) same => n,Dial(PJSIP/+${EXTEN:3}@kamailio-out,60,tTr) same => n,Hangup() ; ============================================================================ ; [internal] — Internal extension-to-extension calls ; ============================================================================ [internal] ; Local extensions (4-digit) exten => _XXXX,1,NoOp(Internal call to ${EXTEN}) same => n,Dial(PJSIP/${EXTEN},30,tTr) same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy) same => n,VoiceMail(${EXTEN}@${CHANNEL(accountcode)},u) same => n,Hangup() same => n(busy),VoiceMail(${EXTEN}@${CHANNEL(accountcode)},b) same => n,Hangup() ; Cross-server extension calls via Kamailio exten => _XXXXX,1,NoOp(Cross-server call to ${EXTEN}) same => n,Dial(PJSIP/${EXTEN}@kamailio-out,30,tTr) same => n,Hangup() ; Echo test exten => 600,1,Answer() same => n,Echo() same => n,Hangup() ; Music on hold test exten => 601,1,Answer() same => n,MusicOnHold(default,60) same => n,Hangup() ; Check voicemail exten => *97,1,Answer() same => n,VoiceMailMain(${CALLERID(num)}@${CHANNEL(accountcode)}) same => n,Hangup() ; Voicemail direct deposit exten => *98,1,Answer() same => n,VoiceMailMain(${EXTEN}@${CHANNEL(accountcode)},s) same => n,Hangup() ; Include outbound dialing include => outbound ; ============================================================================ ; [from-kamailio] — Entry point for Kamailio-routed calls ; ============================================================================ [from-kamailio] include => internal include => from-trunk ; ============================================================================ ; Error handlers ; ============================================================================ [no-tenant] exten => _X.,1,NoOp(No tenant found for DID ${EXTEN}) same => n,Log(WARNING,No tenant found for DID ${EXTEN}) same => n,Playback(ss-noservice) same => n,Hangup() [no-route] exten => _X.,1,NoOp(No route configured for DID ${EXTEN}) same => n,Log(WARNING,No inbound route for DID ${EXTEN} in tenant ${TENANT}) same => n,Playback(ss-noservice) same => n,Hangup() [no-trunk] exten => _X.,1,NoOp(No outbound trunk found) same => n,Log(WARNING,No outbound trunk for ${EXTEN} in tenant ${CDR(accountcode)}) same => n,Playback(ss-noservice) same => n,Hangup()