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

+16
View File
@@ -10,6 +10,10 @@ import type {
ExtensionUpdate,
ListResponse,
NumberKind,
OutboundRouteCreate,
OutboundRouteDto,
OutboundRouteUpdate,
RouteTestResult,
SipCredentials,
TrunkCreate,
TrunkDto,
@@ -97,3 +101,15 @@ export const didsApi = {
update: (id: string, d: DidUpdate) => api<DidDto>('PATCH', `/api/dids/${id}`, d),
remove: (id: string) => api<{ ok: true }>('DELETE', `/api/dids/${id}`),
};
export const routesApi = {
list: () =>
api<ListResponse<OutboundRouteDto>>('GET', '/api/outbound-routes').then((r) => r.items),
create: (r: OutboundRouteCreate) => api<OutboundRouteDto>('POST', '/api/outbound-routes', r),
update: (id: string, r: OutboundRouteUpdate) =>
api<OutboundRouteDto>('PATCH', `/api/outbound-routes/${id}`, r),
remove: (id: string) => api<{ ok: true }>('DELETE', `/api/outbound-routes/${id}`),
reorder: (ids: string[]) => api<{ ok: true }>('POST', '/api/outbound-routes/reorder', { ids }),
test: (extensionId: string, number: string) =>
api<RouteTestResult>('POST', '/api/outbound-routes/test', { extensionId, number }),
};