wbxcallprov/src/commands/attachmentActions.js
jmcqueen 6b585bf33f
Some checks failed
CI / verify (push) Has been cancelled
Add /provisionPhone for wired desk phones (7841/7821)
New /provisionPhone <storeNumber> command manages the store user's
wired desk phones via the same card pattern as /provisionDect:
multi-select checkbox list that doubles as the display, MACs render
as AA:BB:CC:DD:EE:FF, model dropdown for add (defaults to 7841),
side-by-side Add / Remove-checked actions, and every removal goes
through an explicit confirm card. Adds are idempotent (MACs already
registered are skipped) and bulk MAC input is supported.

DECT handsets are filtered out of the display so /provisionPhone and
/provisionDect coexist cleanly on the same store user without
overlapping responsibilities.

Refactors:
- Extract MAC helpers (normalize/format/display) to src/utils/mac.js
  so both DECT and wired-phone flows share one implementation. dect.js
  re-exports for backward compat with existing consumers.
- Add parseEmailArg helper; migrate /userInfo to use it.

Tests: pure-logic coverage for WIRED_PHONE_MODELS,
isSupportedWiredModel, filterWiredPhones (DECT/model filter), and
parseEmailArg.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 12:39:06 -04:00

72 lines
2.9 KiB
JavaScript

import { logger } from '../logger.js';
import { stageStoreLocation } from '../flows/stageStore.js';
import { finalizeStoreLocation } from '../flows/finalizeStore.js';
import { getWebexDeviceDetail } from '../webex/devices.js';
import { handleDectAction } from './dectActions.js';
import { handlePhoneAction } from './phoneActions.js';
async function runFlow(bot, trigger, verb, fn) {
const { storeInfo, userInfo } = trigger.attachmentAction.inputs;
bot.censor(trigger.attachmentAction.messageId);
bot.say(`${verb} ${storeInfo.name}.`);
try {
await fn(bot, storeInfo, userInfo);
bot.say(`Finished ${verb.toLowerCase()} ${storeInfo.name}.`);
} catch (error) {
logger.error(`${verb} failed for ${storeInfo?.name}:`, error);
bot.say(`Error ${verb.toLowerCase()} ${storeInfo?.name}: ${error.message}`);
}
}
async function showDevices(bot, trigger) {
const deviceIds = (trigger.attachmentAction.inputs.devices ?? '').split(',').filter(Boolean);
for (const deviceId of deviceIds) {
try {
const detail = await getWebexDeviceDetail(deviceId);
bot.say('markdown', `\`\`\` json\n${JSON.stringify(detail, null, 2)}\n\`\`\`\n`);
} catch (error) {
logger.error('Failed to fetch device detail:', error);
bot.say(`Failed to fetch device ${deviceId}: ${error.message}`);
}
}
}
export function register(framework) {
framework.on('attachmentAction', async (bot, trigger) => {
logger.debug('attachmentAction', JSON.stringify(trigger));
const action = trigger.attachmentAction.inputs.action;
// DECT and wired-phone flows each have their own sub-dispatcher
// so this file stays focused on the store lifecycle actions.
if (typeof action === 'string' && action.startsWith('dect-')) {
await handleDectAction(bot, trigger, action);
return;
}
if (typeof action === 'string' && action.startsWith('phone-')) {
await handlePhoneAction(bot, trigger, action);
return;
}
switch (action) {
case 'stageStore':
await runFlow(bot, trigger, 'Staging', stageStoreLocation);
break;
case 'finalizeStore':
await runFlow(bot, trigger, 'Finalizing', finalizeStoreLocation);
break;
case 'showDevice':
await showDevices(bot, trigger);
break;
case 'deleteCard':
bot.censor(trigger.attachmentAction.messageId);
break;
default:
if (trigger.attachmentAction.inputs.responseTo === 'badInfo') {
bot.say(
'If information is not correct, please check Store Info Web or contact the administrator.',
);
}
break;
}
});
}