outbound: routes with failover and caller ID, test box, Outbound rules page

Outbound routes CRUD, reorder and POST /test (mirrors the dialplan:
911 and internal numbers first, then the first matching enabled route,
class check, usable trunks, caller ID priority route > user > default).
The first trunk creates the default rules: toll-free first (because
1NXXNXXXXXX also matches 1800...), US/Canada, international disabled.

Generator: Asterisk orders patterns in one context by specificity, not
by file order, so each route gets its own sp-or-{id} context and
sp-outbound-routes includes them in position order (spec 03 §6.12
updated). sp-route-{id}: class check -> ss-noservice, caller ID
priority, per-trunk GROUP channel limit, failover on CHANUNAVAIL or
CONGESTION, all-circuits-busy-now when every trunk is full or off.

UI: Outbound rules page (↑/↓ reorder, pattern rows with help, ordered
trunk list, caller ID override, test box) and a Calling tab on users
(calling permission, outbound caller ID).

trunk-test.ts: 101 dials 3125550000 -> the fake provider receives
13125550000 with the DID in Remote-Party-ID (From user is the
sub-account), two-way audio; a caller without a class hears
ss-noservice. scripts/e2e/m3-t3.mjs covers the UI. With real voip.ms
still to do: whether per-call caller ID is honored (06 §4).

Task: M3-T3
This commit is contained in:
iamdoubz committed 2026-10-17 20:04:35 -05:00
1 parent c117d8bb1d
commit 4496f6b89a
33 files changed
+1686 -70

No files matched your search

+2
View File
@@ -14,6 +14,7 @@ import { STATIC_DIALPLAN } from './dialplan/static.ts';
import { renderGlobals, renderLocalNumbers } from './dialplan/local.ts';
import { PENDING_CONTEXTS } from './dialplan/pending.ts';
import { renderDids, renderTrunkEntries } from './dialplan/inbound.ts';
import { renderOutbound } from './dialplan/outbound.ts';
export { renderTransports };
@@ -46,6 +47,7 @@ const BODIES: Partial<Record<GeneratedFileName, (snap: Snapshot) => string>> = {
renderLocalNumbers(snap),
renderTrunkEntries(snap),
renderDids(snap),
renderOutbound(snap),
PENDING_CONTEXTS,
]
.filter(Boolean)