featurecodes: DND, forwarding, pickup, echo, say-ext; /me settings

Feature codes per 03 §6.13: *78/*79 DND, *72<number>/*73 forward always,
**<ext> directed pickup, *8 group pickup (also as a dialplan line, since
pickupexten only catches a phone's own INVITE), *43 echo test, *65 say
my extension; queue login and intercom stay for M8. SpToggle DND/CFU
update user_runtime_state; a *72 target the user's calling permission
doesn't allow is removed from AstDB, audited (forward_rejected) and sent
to the user as a toast (new private live message). Forward targets are
checked the same way everywhere (services/forwards.ts: an existing
internal number, or an outside number some outbound rule allows them).

GET/PATCH /api/me: DND, cfu/cfb/cfna, voicemail email options and PIN;
PostgreSQL first, then AstDB; AstDB resync on connect now includes DND
and forwards. Web: /me/settings (forwarding, DND, voicemail PIN and
email options), a status card on Home, toasts.

e2e m6-t7 with real phones covers every code, the refused forward toast,
and the settings page reaching AstDB; it found the settings forms losing
"Saved." on remount.

Task: M6-T7
This commit is contained in:
iamdoubz committed 2026-10-18 11:19:21 -05:00
1 parent d746772b97
commit 57e0e3dac2
31 files changed
+1270 -33

No files matched your search

+2 -2
View File
@@ -98,13 +98,13 @@ describe.skipIf(!hasTestDb)('office hours and holidays', () => {
it('*311 from a phone (SpToggle TCOVR) updates the database', async () => {
await handleToggle(
{ db: t.db, log: t.app.log },
{ db: t.db, ami: t.ami, live: t.live, log: t.app.log },
{ Event: 'UserEvent', UserEvent: 'SpToggle', Key: 'TCOVR', Id: id, Value: 'open' },
);
const [row] = await t.db.select().from(timeConditions).where(eq(timeConditions.id, id));
expect(row!.overrideState).toBe('open');
await handleToggle(
{ db: t.db, log: t.app.log },
{ db: t.db, ami: t.ami, live: t.live, log: t.app.log },
{ Event: 'UserEvent', UserEvent: 'SpToggle', Key: 'TCOVR', Id: id, Value: 'auto' },
);
expect((await req('GET', `/api/time-conditions/${id}`)).json().overrideState).toBeNull();