Let me first emphasize that this is simply a proof-of-concept at the moment. I only wrote it over the last few hours and it's only had preliminary testing in the lab.
However I hope it starts as a good starting point and others who need this solution and will provide some tweaks and/or constructive feedback.
What this script does: tests to see if the dialed number is local, and if so, sends it to the "from-outside" context instead of the external trunk.
What this script does not do:
- Proper manipulation of the call to make it look external for inter-tenant dialing. Caution: this may impact call detail/billing records but since all the calls are local this may not be an issue for you.
- CallerID etc. is not set properly (thought this is certainly do-able by adding some code to the script)
- Completely untested for international numbers (though if you can successfully create the standardized dial-patterns described in step 2 that match the DIDs in the file created in step 3, it should work fine)
---
Step 1: Create a custom script in the script library using the following information:
Name: custom-dialout-1-trunk
For "Script can be used for:", check only "Outbound routes".
Descriptive Name: Check local then dialout 1 trunk
Script Description: Test for local DID first, then dialout
PBX Commands:
exten => s,1,GotoIf($["${MACRO_EXTEN}" = "s"]?dial)
exten => s,n,Set(__DIALED_NUMBER=${MACRO_EXTEN})
exten => s,n(dial),Gosub(dids_local,${ARG3}${MACRO_EXTEN:${ARG4}},1)
exten => s,n,Macro(tl-dialout-base,${ARG1},${ARG2},${ARG3},${ARG4},${ARG5})
Step 2: Change your outbound routes to use the script we just created.
"Associated Script: custom-dialout-1-trunk" and standardise your outbound dial patterns to a fixed number of digits, in this case 10, eg:
_1NXXNXXXXXX strip 1 digit. eg. 14445551212 becomes 4445551212.
_NXXXXXX prepend local area code. eg. 5551212 becomes 3335551212
_NXXNXXXXXX (no change)
Step 3: Make a file called "dids_local.conf" which contains all local DIDs.
This one-line linux script will do it. Make sure you are in the '/etc/asterisk' directory first.
# echo "[dids_local]" > dids_local.conf ; for DID in $(grep -e '^\[' dids.txt | sed -e 's/\[\|\]//g') ; do echo "exten => $DID,1,Goto(from-outside,\${EXTEN},1)" ; done >> dids_local.conf
NOTE: You should run this script regularly (nightly?) to update the DID list and asterisk will have to reloaded if there are any changes.
Step 4: Include the file in extensions.conf just below the "globals" section. eg:
[globals]
OFFICE_OPEN_OVERRIDE=
....
....
#include dids_local.conf
[from-outside]
...
Step 5: Reload Asterisk and test results.
this is soooo much simpler
this is soooo much simpler when TL just has to make sure the call is in E.164 format when it gets delivered to its SBC.
you have also not addressed
you have also not addressed the fact that the code for MTE is shared with STE and there is no dids.txt in STE, they get their inbound routes input manually as patterns and as 's'. Therefore this code contribution, assuming you can ever get it fixed to solve all the other criteria, will always have to be post-production and not part of the STE/MTE core product as it currently exists.
I'm not sure why you think
I'm not sure why you think this is a "code contribution"? Why would I give my code to Thirdlane? Are they going to pay me to fix their bugs? If Thirdlane was open source then I'd contribute it, but since Thirdlane demands license fees and has the worlds worst support they can fix their own software.
I posted this code for two reasons:
a) to help individual users who are in need of a solution.
b) just to show it can be done.
As I said it's simply a proof-of-concept that I put together in a few hours so it would need more work before going into production.
That being said, it for certain works in NANPA (which is a good starting point) and think it can work in all the situations you mention.
--
For single tenant, you can match on the "inbound routes" (assuming you have one for each DID). They are in the file inbound_source.include which works just as well with the one-line script I provided.
--
If you are required to send all calls as 11 digits, but receive all calls as 10 digits, then you can simply modify the dids_local.conf file so the extensions are 11 digits and it can manipulate the DID to match whatever format your local DIDs are in Thridlane. e.g.
If your tenant is in area code 303 and dials seven digits:
_NXXNXXX , then prepend 1303
And dids_local:
exten => 13035551212,1,Goto(from-outside,\${EXTEN:1},1)
---
A similar strategy should also work international style numbers but I'd need some specific examples to work with.
Basically the principle is; manipulate the dialed number to the standard format your provider accepts, then create dids_local to match that format and manipulate the number back to the format your local DIDs are in.
--
"you have not addressed area's where 7 digit local numbers span more than 1 area code such as the 468 lata."
Can you elaborate on that? How can you have 7 digit phone numbers in more than one area code?
--
"you have not addressed the issue of concurrent call limitation being enforced for both the sender and the recipient"
It's using the same thirdlane code for inbound so it should be counting calls on the inbound side. I'd have to check where that's counted on the outbound side. But in any case, since the call is completely local and call-limits are usually for the purpose of restricting outside channel usage this is not likely a major drawback for most people.
--
"you have not addressed the issue of ensuring both customers get a CDR record for the call tied to both account numbers."
It appears to log exactly the same thing in the Asterisk CDR as before so in that regard it remains unchanged. In other words, if current Thridlane logging isn't good enough then this obviously won't fix it.
with a gateway there would be
with a gateway there would be two separate calls logged as CDR for the on-net call so each leg has its own seperate call record. One the outgoing customer placed, and one the recipient received. The keeps you from having an occurence where tenant A calls tenant B but only tenant B has the call show up under his account code. Later a subpoena is served for tenant A's call records which are suddenly missing the calls to tenant B.
--
in the 462 lata there are overlapping area codes. For example 502 and 812. If you live in, for example, the louisville rate center, there are a dozen other rate centers that are deemed 'local' calls and are reachable via 7 digit patterns that include both teh 812 and 502 area codes. This of course required control of the NXX so that there are no identical NXX's in any rate center that are local despite having a different area code. To deal with the issue, I modified the fixlocalprefix agi script that came with freepbx to prepend the correct area code to the call and send it to my gateway in proper E.164 format.
see here : http://www.localcallingguide.com/lprefix.php?exch=069640&dir=1 for a list of all possible local exchanges.
This problem repeats itself in a lot of 'tri state areas' of moderate population roughly the size of louisville where they have not yet mandated 10digit
Script updates
I've done some additional work and put the call to dial local in a better place. Though this requires two custom scripts it makes much more sense to make the call from dial-base. There is also a correction to the linux command line script to add the invalid extension "Return".
Don't be intimidated by the length of this post, it's really just copy & pasting 3 scripts and making a small change to your outbound routes and a one line change to extensions.conf . Quite simple for anyone familiar with Thirdlane.
I'm now testing this in production and so far it's working fine.
This actually works much better than I originally thought because the script looks for a local match based on your outbound routes but BEFORE any manipulation done by the trunk which would cause it not to match your DIDs.
For example, in our situation all outbound calls to the gateway are in 10 digit format plus our gateway needs a 9 prepended. All our inbound DIDs are 10 digits.
The outbound route coverts NXXXXXX to NXXNXXXXXX, then the trunk converts it to 9NXXNXXXXXX.
The script tests for a local match of NXXNXXXXXX which matches our DID format and if none is found it sends it to the trunk which adds the "9" for outbound dialing. Works perfect.
Here is the revised version of the instructions.
Step 1, create the following two scripts (give them whatever descriptive name and description you like).
First script (used for Outbound Routes): custom-dialout-1-trunk
exten => s,1,GotoIf($["${MACRO_EXTEN}" = "s"]?dial)
exten => s,n,Set(__DIALED_NUMBER=${MACRO_EXTEN})
exten => s,n(dial),Macro(custom-dialout-base,${ARG1},${ARG2},${ARG3},${ARG4},${ARG5})
Second script (Used for Other): custom-dialout-base (*THIS MUST BE NAMED EXACTLY*)
; first argument is the timeout, second is the first trunk
exten => s,1,Set(status=${DB(TL/TENANT/${tenant}/status)})
exten => s,n,GotoIf($["${status}" != "0"]?enabled)
exten => s,n,Congestion(10)
exten => s,n,Hangup
exten => s,n(enabled),GotoIf($["${TL_ENABLE_MAXCALLS_CHECK}" != "1"]?allclear)
exten => s,n,Set(MAXCALLS=${DB(TL/TENANT/${tenant}/maxcalls)})
exten => s,n,GotoIf($["${MAXCALLS}" = ""]?allclear)
exten => s,n,Set(GROUP(callpaths)=${tenant})
exten => s,n,NoOp(GROUP_COUNT = ${GROUP_COUNT(${tenant}@callpaths)})
exten => s,n,GotoIf($[${GROUP_COUNT(${tenant}@callpaths)} > ${MAXCALLS}]?allbusy:allclear)
exten => s,n(allbusy),Playback(tl/all-circuits-busy)
exten => s,n,Hangup
exten => s,n(allclear),Set(i=2)
;;; username on the originating trunk or device is required for this to work
exten => s,n,Macro(tl-set-myvariables)
exten => s,n,SetAMAFlags(billing)
;; call recording
exten => s,n,Set(RECORD=${DB(TL/${MYID}/RECORD)})
exten => s,n,GotoIf($["${RECORD}" = ""]?done_checkrecord)
exten => s,n,GotoIf($["${RECORD}" = "2"]?check_onetouch)
exten => s,n,GotoIf($["${RECORDING_FORMAT}" != ""]?record)
exten => s,n,Set(RECORDING_FORMAT=wav)
exten => s,n(record),MixMonitor(out-${DIALED_NUMBER}-${STRFTIME(,,%F-%H-%M-%S)}-${MYEXTENSION}${TL_DASH}${tenant}.${RECORDING_FORMAT},b)
exten => s,n,Goto(done_checkrecord)
exten => s,n(check_onetouch),Set(RECORD_OPTIONS=W)
exten => s,n,Set(TOUCH_MONITOR_FORMAT=${RECORDING_FORMAT})
exten => s,n,Set(TOUCH_MONITOR=out-${DIALED_NUMBER}-${STRFTIME(,,%F-%H-%M-%S)}-${MYEXTENSION}${TL_DASH}${tenant})
exten => s,n(done_checkrecord),NoOp(RECORD=${RECORD})
exten => s,n,NoOp(TOUCH_MONITOR=${TOUCH_MONITOR})
exten => s,n,NoOp(RECORD_OPTIONS=${RECORD_OPTIONS})
exten => s,n,NoOp(RECORDING_FORMAT=${RECORDING_FORMAT})
;; check which caller id to use - tenant/company level or individual
exten => s,n,Set(CALLERID(name)="${DB(TL/TENANT/${tenant}/calleridname)}")
;;; if not multi-tenant - bypass some code
exten => s,n,GotoIf($["${tenant}" = ""]?calleridallowed)
exten => s,n,Set(temp=${DB(TL/TENANT/${tenant}/allowaccountcode)})
exten => s,n,GotoIf($["${temp}" != "0"]?accountcodeallowed)
exten => s,n,Set(CDR(accountcode)=${DB(TL/TENANT/${tenant}/accountcode)})
;; set using tenant level caller id
exten => s,n(accountcodeallowed),Set(CALLERID(num)=${DB(TL/TENANT/${tenant}/callerid)})
;; now check if override is allowed
exten => s,n,Set(temp=${DB(TL/TENANT/${tenant}/allowcallerid)})
exten => s,n,GotoIf($["${temp}" != "0"]?calleridallowed)
exten => s,n,Goto(onetrunk,1)
;; individual external caller id
exten => s,n(calleridallowed),NoOp(CLIMYID=${CLIMYID})
exten => s,n,NoOp(MYID=${MYID})
exten => s,n,Set(CLI=${DB(TL/${CLIMYID}/callerid)})
exten => s,n,GotoIf($["${CLI}" != ""]?chkcallfwd)
exten => s,n,Set(CLI=${DB(TL/${MYID}/callerid)})
exten => s,n,GotoIf($["${CLI}" != ""]?chkcallfwd)
exten => s,n,Set(CLI=${DB(TL/TENANT/${tenant}/callerid)})
exten => s,n,NoOp(CLI=${CLI})
exten => s,n(chkcallfwd),NoOp(MYID=${MYID})
exten => s,n,NoOp(INCOMINGCLI=${INCOMINGCLI})
exten => s,n,NoOp(CALLFWD=${CALLFWD})
exten => s,n,GotoIf($["${CALLFWD}" = "" & "${FOLLOWME}" != "1"]?setcalleridasabove)
exten => s,n,Set(CDR(accountcode)=${tenant})
exten => s,n,NoOp(CFRETAINCLI=TL/${tenant}${TL_DASH}${ORIG_EXTEN}/CFRETAINCLI)
exten => s,n,GotoIf($["${DB(TL/${tenant}${TL_DASH}${ORIG_EXTEN}/CFRETAINCLI)}" = ""]?setcalleridasabove)
;;; check if globally allowed to set caller id to original
exten => s,n,Set(ALLOW_INCOMINGCLI=${DB(TL/allow_original_callerid)})
exten => s,n,GotoIf($["${ALLOW_INCOMINGCLI}" = "0"]?setcalleridasabove)
exten => s,n,GotoIf($["${INCOMINGCLI}" = ""]?setcalleridasabove)
exten => s,n,Set(CLI=${INCOMINGCLI})
exten => s,n(setcalleridasabove),GotoIf($["${CLI}" = ""]?nocallerid)
exten => s,n,Set(CALLERID(num)=${CLI})
exten => s,n(nocallerid),Set(temp=${DB(TL/${MYID}/calleridname)})
exten => s,n,GotoIf($["${temp}" = ""]?onetrunk,1)
exten => s,n,Set(CALLERID(name)="${temp}")
exten => s,n,Goto(onetrunk,1)
exten => onetrunk,1,Set(FULLNAME=${ARG${i}})
exten => onetrunk,n,Set(TRUNK=${CUT(FULLNAME,/,2)})
exten => onetrunk,n,GotoIf($["${LEN(${TRUNK})}" = "0"]?failed,1)
exten => onetrunk,n,Set(TRUNK_STATUS=${TRUNK_STATUS_${TRUNK}})
exten => onetrunk,n,GotoIf($["${TRUNK_STATUS}" = "0"]?next,1)
exten => onetrunk,n,Set(ROUTE_PREPEND=${ARG$[${i} + 1]})
exten => onetrunk,n,Set(ROUTE_STRIP=${ARG$[${i} + 2]})
exten => onetrunk,n,Set(ROUTE_OPTIONS=${ARG$[${i} + 3]}${RECORD_OPTIONS})
exten => onetrunk,n,Set(NUMBER_TO_DIAL=${ROUTE_PREPEND}${DIALED_NUMBER:${ROUTE_STRIP}})
exten => onetrunk,n,Set(TRUNK_DIALSTRING=${TRUNK_DIALSTRING_${TRUNK}})
exten => onetrunk,n,Set(TRUNK_NAME=${TRUNK_NAME_${TRUNK}})
exten => onetrunk,n,Set(TRUNK_PROTOCOL=${TRUNK_PROTOCOL_${TRUNK}})
exten => onetrunk,n,Set(TRUNK_STRIP=${TRUNK_STRIP_${TRUNK}})
exten => onetrunk,n,Set(TRUNK_PREPEND=${TRUNK_PREPEND_${TRUNK}})
exten => onetrunk,n,Set(CALLERID(num)=${TRUNK_CLI_PREPEND_${TRUNK}}${CALLERID(num):${TRUNK_CLI_STRIP_${TRUNK}}})
exten => onetrunk,n,GotoIf($["${TRUNK_DIALSTRING}" != ""]?dial-CUSTOM,1)
exten => onetrunk,n,Gosub(dids_local,${NUMBER_TO_DIAL},1)
exten => onetrunk,n,Goto(dial-${TRUNK_PROTOCOL},1)
;;SIP
;;;; add SIP headers
exten => dial-SIP,1,Set(headernum=1)
exten => dial-SIP,n(getoneheader),GotoIf(${DB_EXISTS(TL/${TRUNK}/headers/${headernum})}?:routeoptions)
exten => dial-SIP,n,Set(HEADER=${DB(TL/${TRUNK}/headers/${headernum})})
exten => dial-SIP,n,SipAddHeader(${EVAL(${HEADER})})
exten => dial-SIP,n,Set(headernum=$[${headernum} + 1])
exten => dial-SIP,n,Goto(getoneheader)
exten => dial-SIP,n(routeoptions),Dial(SIP/${TRUNK_PREPEND}${NUMBER_TO_DIAL:${TRUNK_STRIP}}@${TRUNK_NAME},${ARG1},${ROUTE_OPTIONS})
exten => dial-SIP,n,Goto(dial-${DIALSTATUS},1)
;;IAX
exten => dial-IAX,1,Dial(IAX2/${TRUNK_NAME}/${TRUNK_PREPEND}${NUMBER_TO_DIAL:${TRUNK_STRIP}},${ARG1},${ROUTE_OPTIONS})
exten => dial-IAX,n,Goto(dial-${DIALSTATUS},1)
;;ZAP
exten => dial-ZAP,1,Set(CIDNAME="${CALLERID(name)}")
exten => dial-ZAP,n,Set(CALLERID(name)=)
exten => dial-ZAP,n,Dial(ZAP/${TRUNK_NAME}/${TRUNK_PREPEND}${NUMBER_TO_DIAL:${TRUNK_STRIP}},${ARG1},${ROUTE_OPTIONS})
exten => dial-ZAP,n,Set(CALLERID(name)="${CIDNAME}")
exten => dial-ZAP,n,Goto(dial-${DIALSTATUS},1)
;;DAHDI
exten => dial-DAHDI,1,Set(CIDNAME="${CALLERID(name)}")
exten => dial-DAHDI,n,Set(CALLERID(name)=)
exten => dial-DAHDI,n,Dial(DAHDI/${TRUNK_NAME}/${TRUNK_PREPEND}${NUMBER_TO_DIAL:${TRUNK_STRIP}},${ARG1},${ROUTE_OPTIONS})
exten => dial-DAHDI,n,Set(CALLERID(name)="${CIDNAME}")
exten => dial-DAHDI,n,Goto(dial-${DIALSTATUS},1)
;;CUSTOM
exten => dial-CUSTOM,1,Set(DS1=${CUT(TRUNK_DIALSTRING,$,1)})
exten => dial-CUSTOM,n,Set(DS2=${CUT(TRUNK_DIALSTRING,$,2)})
exten => dial-CUSTOM,n,Dial(${DS1}${NUMBER_TO_DIAL}${DS2},${ARG1},${ROUTE_OPTIONS})
exten => dial-CUSTOM,n,Goto(dial-${DIALSTATUS},1)
exten => dial-CHANUNAVAIL,1,Goto(next,1)
exten => dial-CONGESTION,1,Goto(next,1)
exten => dial-BUSY,1,Goto(busy,1)
exten => dial-CANCEL,1,Goto(done,1)
exten => dial-NOANSWER,1,Goto(done,1)
exten => dial-ANSWER,1,Goto(done,1)
exten => next,1,Set(i=$[${i} + 4])
exten => next,2,Goto(onetrunk,1)
exten => done,1,NoOp(done)
exten => busy,1,PlayTones(busy)
exten => busy,2,Busy(10)
exten => busy,3,Hangup
exten => failed,1,PlayTones(congestion)
exten => failed,2,Congestion(10)
exten => failed,3,Hangup
Step 2: Change your outbound routes to use the script we just created.
"Associated Script: custom-dialout-1-trunk" and standardise your outbound dial patterns to a fixed number of digits, in this case 10, eg:
_1NXXNXXXXXX strip 1 digit. eg. 14445551212 becomes 4445551212.
_NXXXXXX prepend local area code. eg. 5551212 becomes 3335551212
_NXXNXXXXXX (no change)
Step 3: Make a file called "dids_local.conf" which contains all local DIDs.
This one-line linux script will do it. Make sure you are in the '/etc/asterisk' directory first.
echo "[dids_local]" > dids_local.conf ; for DID in $(grep -e '^\[' dids.txt | sed -e 's/\[\|\]//g') ; do echo "exten => $DID,1,Goto(from-outside,\${EXTEN},1)" ; done >> dids_local.conf ; echo "exten => i,1,Return" >> dids_local.conf
NOTE: You should run this script regularly (nightly?) to update the DID list and asterisk will have to be reloaded if there are any changes.
Step 4: Include the file in extensions.conf just below the "globals" section. eg:
[globals]
OFFICE_OPEN_OVERRIDE=
....
....
#include dids_local.conf
[from-outside]
...
Step 5: Reload Asterisk and test results.
"with a gateway there would
"with a gateway there would be two separate calls logged as CDR for the on-net call so each leg has its own seperate call record. One the outgoing customer placed, and one the recipient received. The keeps you from having an occurence where tenant A calls tenant B but only tenant B has the call show up under his account code. Later a subpoena is served for tenant A's call records which are suddenly missing the calls to tenant B."
Just to clarify, using the scripts above, if A calls B, the call is logged under A's records so no problem. However, if B's records are subpoenaed, you have to be sure to account for the inter-tenant dialing when producing the call detail records. The record of the call is there so it does meet legal requirements, you just have to be careful.
Actually the bigger problem would be that it doesn't show up in B's CDR report when they view it in Thirdlane. Since the CDR code is internal to Thirdlane I don't have any solution for that but it wouldn't be hard to fix.
"in the 462 lata there are overlapping area codes. For example 502 and 812..."
So basically they've done an overlay but not enforced 10 digit dialing. The scripts above would still work assuming you send and receive calls to your provider in the same format.
For example, if you are sending 7 digits for local calls, then your local DIDs would have to be 7 digits as well.
Or you could modify the custom-dial-base script to accommodate that situation by calling dids_local 3 times, once for each of your local area codes. e.g.
exten => onetrunk,n,Gosub(dids_local,462${NUMBER_TO_DIAL},1)
exten => onetrunk,n,Gosub(dids_local,812${NUMBER_TO_DIAL},1)
exten => onetrunk,n,Gosub(dids_local,502${NUMBER_TO_DIAL},1)
Ultimately I'm sure there will always be some odd-ball situations where it doesn't work but for the vast majority of situations it will work or can be made to work with a few small changes.
Correction
Just a small correction to the above. I was mistaken, in fact, call detail does work just fine. It shows call detail for both sides in the thirdlane call history portal.
just thought i'd check in nd
just thought i'd check in nd see how this was working for you now that you've had a week and a half or so?.... that is, if you have implemented it on a working switch
This does nothing to address customers outside of NANPA. More than half of the thirdlane customers are in Europe. They are not all 10-digit numbers. Within a single european country, for example, there are some numbers that are 8 digits and other 9 digits.
you have not addressed situations where carriers require you to send e.164 format or for that matter just a pure 11 digit destination (some of those exist also)
you have not addressed situations where countries require you to send a single 0 when calling within the country and two 0's when calling outside the country yet their local DID's do not include either the single zero or the double zero. There are many cases where a single MTE system has customers in several countries and they would inevitably dial 00 to reach that customer.
you have not addressed area's where 7 digit local numbers span more than 1 area code such as the 468 lata.
you have not addressed the issue of concurrent call limitation being enforced for both the sender and the recipient
you have not addressed the issue of ensuring both customers get a CDR record for the call tied to both account numbers. This is not a matter of convenience. I assume by your blatant disregard of non-NANPA customer's needs that your living in North America. Therefore you have a Legal CALEA and CPNI requirement to have accurate CDR records.