parking: parked calls live, on the dashboard and Home, pick up from Home
ci / images (push) Canceled after 0s
ci / check (push) Canceled after 3s

res_parking was configured in M0 (700, slots 701-720, slot hints); this
makes parked calls visible. ParkingTracker loads ParkedCalls and follows
ParkedCall/UnParkedCall/ParkedCallTimeOut/ParkedCallGiveUp; everyone
gets parked.updated/parked.removed. Who parked comes from
ParkerDialString.

A blind transfer to 700 parks without running Park() in the dialplan,
so the live call's "parked" state now comes from those events by
ParkeeLinkedid; the picking-up phone runs ParkedCall() while talking,
which no longer counts as parked.

Web: "Parked calls" on the Dashboard and on Home, where "Pick up 701"
calls the slot like Call back (softphone, or the desk phone first).

CDR fixture park-pickup (recorded: 101 parks an inbound call, 103 picks
it up); the call counts as answered by 101 with 103 in rang. e2e m8-t2
with real phones: park by blind transfer, hint InUse, both lists, pick
up from Home, a second park on 702 (findslot = next) picked up by
dialing it from another phone. Specs 01/04/05 note what was verified.

Task: M8-T2
This commit is contained in:
iamdoubz committed 2026-10-19 04:23:21 -05:00
1 parent 2d9f8ed99e
commit 863a9d121c
21 files changed
+635 -23

No files matched your search

+18 -1
View File
@@ -5,7 +5,7 @@
// 103 Carol auto-answer 104 Dave no phone, no voicemail
// Each scenario's Cdr events go to apps/api/test/fixtures/cdr/<name>.json.
// Queue 550 has Alice (answers); queue 551 has Bob (never answers) and sends callers who wait
// 8 s to his voicemail. Parking arrives with M8-T2.
// 8 s to his voicemail. Park: Alice parks a caller on 700 (→ 701), Carol picks it up.
// Usage: node --conditions=source apps/api/test/asterisk/cdr-record.ts
import { spawnSync } from 'node:child_process';
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
@@ -444,6 +444,23 @@ async function main() {
await sleep(6000);
});
await scenario('park-pickup', async () => {
fromMobile('3125551234');
await waitFor('101 answers', () => !!pbxChannel('PJSIP/101-'));
await sleep(2000);
await ami.action({
Action: 'BlindTransfer',
Channel: pbxChannel('PJSIP/101-')!,
Context: 'sp-from-ext',
Exten: '700',
});
await waitFor('parked on 701', () => /701/.test(cli(PBX, 'parking show default')), 20);
await sleep(2000);
await fromPhone('103', '701', 'Carol');
await waitFor('103 picks up', () => !!pbxChannel('PJSIP/103-'), 20);
await sleep(4000);
});
mkdirSync(OUT, { recursive: true });
for (const [name, evs] of Object.entries(recorded)) {
if (!evs.length) throw new Error(`${name}: no Cdr events`);
+2 -2
View File
@@ -56,8 +56,8 @@ describe.skipIf(!hasTestDb)('call history API', () => {
destination: { type: 'hangup' },
label: n === '1234' ? 'Main line' : null,
});
// The M7 scenarios; queue calls (M8) are checked in cdr.test.ts and queues.test.ts.
for (const f of readdirSync(FIXTURES).filter((f) => !f.startsWith('queue-'))) {
// The M7 scenarios; queue and park calls (M8) are checked in cdr.test.ts.
for (const f of readdirSync(FIXTURES).filter((f) => !/^(queue|park)-/.test(f))) {
const events: AmiMessage[] = JSON.parse(readFileSync(new URL(f, FIXTURES), 'utf8'));
for (const e of events) await t.db.insert(cdr).values(toCdrRow(e));
await aggregateCall(t.db, String(events[0]!.LinkedID));
+11
View File
@@ -143,6 +143,17 @@ const SCENARIOS: Record<string, Expected> = {
rang: ['102'], // rang as an agent, then took the call in his mailbox
talkSeconds: 0,
},
'park-pickup': {
direction: 'inbound',
status: 'answered',
fromNumber: '13125559999',
fromName: 'Mobile',
toNumber: '13125551234',
did: '13125551234',
answeredBy: '101', // parked it on 701; Carol (103) picked it up there
rang: ['101', '103'],
talkSeconds: 4,
},
};
describe.skipIf(!hasTestDb)('call aggregation from recorded CDRs', () => {
+106
View File
@@ -0,0 +1,106 @@
[
{
"Event": "Cdr",
"Privilege": "cdr,all",
"AccountCode": "",
"Source": "103",
"Destination": "701",
"DestinationContext": "sp-from-ext",
"CallerID": "\"Carol\" <103>",
"Channel": "PJSIP/103-00000018",
"DestinationChannel": "",
"LastApplication": "ParkedCall",
"LastData": "default,701",
"StartTime": "2026-10-19 09:06:59",
"AnswerTime": "2026-10-19 09:06:59",
"EndTime": "2026-10-19 09:06:59",
"Duration": "0",
"BillableSeconds": "0",
"Disposition": "ANSWERED",
"AMAFlags": "DOCUMENTATION",
"UniqueID": "1792400819.56",
"UserField": "",
"LinkedID": "1792400815.54",
"sp_did": "",
"sp_direction": "",
"sp_recording": ""
},
{
"Event": "Cdr",
"Privilege": "cdr,all",
"AccountCode": "",
"Source": "13125559999",
"Destination": "101",
"DestinationContext": "sp-local-numbers",
"CallerID": "\"Mobile\" <13125559999>",
"Channel": "PJSIP/trunk-7d3e9a52-1c4b-4f6e-9a0d-2b8c5e1f4a37-00000016",
"DestinationChannel": "PJSIP/101-00000017",
"LastApplication": "Dial",
"LastData": "PJSIP/101/sip:101-0x5987fe8320b0@172.21.0.4:5070,25,tx",
"StartTime": "2026-10-19 09:06:55",
"AnswerTime": "2026-10-19 09:06:55",
"EndTime": "2026-10-19 09:06:57",
"Duration": "2",
"BillableSeconds": "2",
"Disposition": "ANSWERED",
"AMAFlags": "DOCUMENTATION",
"UniqueID": "1792400815.54",
"UserField": "",
"LinkedID": "1792400815.54",
"sp_did": "13125551234",
"sp_direction": "inbound",
"sp_recording": ""
},
{
"Event": "Cdr",
"Privilege": "cdr,all",
"AccountCode": "",
"Source": "13125559999",
"Destination": "101",
"DestinationContext": "sp-local-numbers",
"CallerID": "\"Mobile\" <13125559999>",
"Channel": "PJSIP/trunk-7d3e9a52-1c4b-4f6e-9a0d-2b8c5e1f4a37-00000016",
"DestinationChannel": "",
"LastApplication": "Park",
"LastData": "default:701",
"StartTime": "2026-10-19 09:06:57",
"AnswerTime": "2026-10-19 09:06:57",
"EndTime": "2026-10-19 09:06:59",
"Duration": "2",
"BillableSeconds": "2",
"Disposition": "ANSWERED",
"AMAFlags": "DOCUMENTATION",
"UniqueID": "1792400815.54",
"UserField": "",
"LinkedID": "1792400815.54",
"sp_did": "13125551234",
"sp_direction": "inbound",
"sp_recording": ""
},
{
"Event": "Cdr",
"Privilege": "cdr,all",
"AccountCode": "",
"Source": "13125559999",
"Destination": "101",
"DestinationContext": "sp-local-numbers",
"CallerID": "\"Mobile\" <13125559999>",
"Channel": "PJSIP/trunk-7d3e9a52-1c4b-4f6e-9a0d-2b8c5e1f4a37-00000016",
"DestinationChannel": "PJSIP/103-00000018",
"LastApplication": "Dial",
"LastData": "PJSIP/101/sip:101-0x5987fe8320b0@172.21.0.4:5070,25,tx",
"StartTime": "2026-10-19 09:06:59",
"AnswerTime": "2026-10-19 09:06:59",
"EndTime": "2026-10-19 09:07:03",
"Duration": "4",
"BillableSeconds": "4",
"Disposition": "ANSWERED",
"AMAFlags": "DOCUMENTATION",
"UniqueID": "1792400815.54",
"UserField": "",
"LinkedID": "1792400815.54",
"sp_did": "13125551234",
"sp_direction": "inbound",
"sp_recording": ""
}
]