Retire /buildStore, rename /migrateStore -> /finalizeStore, harden flows
Some checks are pending
CI / verify (push) Waiting to run
Some checks are pending
CI / verify (push) Waiting to run
- Delete /buildStore command + flow (dead since phone numbers now have to be purchased before provisioning); drop the only cascading dead helper (updateLocationRouteGroup) that was only called by that flow - Rename /migrateStore -> /finalizeStore. "Migrate" was a leftover from the legacy-system era; the command finishes what stage started - Rename buildStoreInfoCard -> storeConfirmationCard (misleading name since all flows share it); rename btnBuildStore/btnBadInfo -> btnConfirm/btnCancel to match - Fix the double-write of storeInfo.extension: siw.js sets a 5XXXX default and both command handlers were overriding it (stage to 8XXXX, finalize back to 5XXXX). Drop both overrides; single source of truth - Add /finalizeStore preflight that runs findWebexLocation in parallel with findWebexUser; if the location doesn't exist, bail before showing the confirmation card with an actionable "Run /stageStore first" line - Add post-run next-step summaries: stage points at /finalizeStore <n>, finalize confirms the location is live - Update README (commands, mermaid) and deviceCustomizations header Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
7a9f5c96f8
commit
b5610acc8e
12 changed files with 75 additions and 165 deletions
33
README.md
33
README.md
|
|
@ -79,10 +79,10 @@ the flag Node throws `SELF_SIGNED_CERT_IN_CHAIN` even for Google's public
|
||||||
certs. Trust is delegated to that proxy by policy, so the scoped bypass is
|
certs. Trust is delegated to that proxy by policy, so the scoped bypass is
|
||||||
consistent across all proxied traffic.
|
consistent across all proxied traffic.
|
||||||
|
|
||||||
Once the agent is connected, the bot logs `Remote agent connected` and any
|
Once the agent is connected, the bot logs `Remote agent connected` and
|
||||||
`/buildStore`, `/stageStore`, `/migrateStore` command will succeed. If the
|
`/stageStore` and `/finalizeStore` will succeed. If the agent is not
|
||||||
agent is not connected, those commands fail immediately with
|
connected, those commands fail immediately with a preflight message
|
||||||
`No remote SIW agent connected` rather than silently timing out.
|
rather than silently timing out mid-flow.
|
||||||
|
|
||||||
### Deploying the agent
|
### Deploying the agent
|
||||||
|
|
||||||
|
|
@ -127,17 +127,22 @@ for the full operator guide (upgrades, troubleshooting, coexistence with
|
||||||
|
|
||||||
Registered in [src/commands](src/commands):
|
Registered in [src/commands](src/commands):
|
||||||
|
|
||||||
- `/buildStore <storeNumber>` — full green-field build (create location, calling,
|
- `/stageStore <storeNumber>` — creates the Webex location, enables calling,
|
||||||
greeting, attach user, license cleanup).
|
configures the schedule, greeting, music-on-hold, voicemail, voice portal,
|
||||||
- `/stageStore <storeNumber>` — pre-migration setup: same as buildStore but
|
and attaches the Webex Calling license to the store user. Everything
|
||||||
without phone-number attachment or licensing cleanup.
|
except the phone number, which has to be purchased separately in Control Hub.
|
||||||
- `/migrateStore <storeNumber>` — cut-over for a staged store: attach the phone
|
- `/finalizeStore <storeNumber>` — cut-over for a previously staged store:
|
||||||
number, set caller ID, create the auto-attendant, finalize licensing.
|
attaches the purchased phone number, sets caller ID, creates the
|
||||||
|
auto-attendant, and finalizes user licensing. Preflights that the store
|
||||||
|
was actually staged first.
|
||||||
- `/storeinfo <storeNumber>` — show current Webex info for the store user
|
- `/storeinfo <storeNumber>` — show current Webex info for the store user
|
||||||
(`ae<5-digit>@ae.com`).
|
(`ae<5-digit>@ae.com`).
|
||||||
- `/userinfo <email>` — show current Webex info for any user by email.
|
- `/userinfo <email>` — show current Webex info for any user by email.
|
||||||
- `/help` — bot's own help output.
|
- `/help` — bot's own help output.
|
||||||
|
|
||||||
|
Typical provisioning workflow: `/stageStore 499` → purchase a phone number
|
||||||
|
in Control Hub → `/finalizeStore 499`.
|
||||||
|
|
||||||
Card confirmations post `attachmentAction` events, dispatched in
|
Card confirmations post `attachmentAction` events, dispatched in
|
||||||
[src/commands/attachmentActions.js](src/commands/attachmentActions.js).
|
[src/commands/attachmentActions.js](src/commands/attachmentActions.js).
|
||||||
|
|
||||||
|
|
@ -153,7 +158,7 @@ src/
|
||||||
webex/ all Webex API calls, one module per resource family
|
webex/ all Webex API calls, one module per resource family
|
||||||
integrations/ SIW / Twilio / Google
|
integrations/ SIW / Twilio / Google
|
||||||
cards/ Adaptive Card builders
|
cards/ Adaptive Card builders
|
||||||
flows/ multi-step provisioning (build, stage, migrate, ...)
|
flows/ multi-step provisioning (stage, finalize)
|
||||||
commands/ framework.hears handlers + attachmentAction dispatch
|
commands/ framework.hears handlers + attachmentAction dispatch
|
||||||
scripts/ one-off maintenance scripts (911 CSV, phone fix-up)
|
scripts/ one-off maintenance scripts (911 CSV, phone fix-up)
|
||||||
greetings/ WAV files uploaded as location announcements
|
greetings/ WAV files uploaded as location announcements
|
||||||
|
|
@ -164,12 +169,12 @@ Data flow for a store provisioning:
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
User[Webex user] -->|/buildStore 1234| Commands[commands/*]
|
User[Webex user] -->|/stageStore 1234| Commands[commands/*]
|
||||||
Commands --> SIW[integrations/siw.js]
|
Commands --> SIW[integrations/siw.js]
|
||||||
Commands --> Users[webex/users.js]
|
Commands --> Users[webex/users.js]
|
||||||
Commands --> Card[cards/storeInfoCard.js]
|
Commands --> Card[cards/storeConfirmationCard.js]
|
||||||
Card -->|confirm| Attachment[attachmentActions.js]
|
Card -->|confirm| Attachment[attachmentActions.js]
|
||||||
Attachment --> Flow[flows/buildStore.js]
|
Attachment --> Flow[flows/stageStore.js]
|
||||||
Flow --> Locations[webex/locations.js]
|
Flow --> Locations[webex/locations.js]
|
||||||
Flow --> Devices[webex/devices.js]
|
Flow --> Devices[webex/devices.js]
|
||||||
Flow --> Announce[webex/announcements.js]
|
Flow --> Announce[webex/announcements.js]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
/**
|
/**
|
||||||
* Build the confirmation card shown before build/stage/migrate operations.
|
* Build the confirmation card shown before stage/finalize operations.
|
||||||
* `action` is what the "Yes" button posts back to the bot (buildStore, stageStore, migrateStore).
|
* `action` is what the "Yes" button posts back to the bot (e.g. "stageStore",
|
||||||
|
* "finalizeStore"). The attachmentAction handler switches on that value.
|
||||||
*/
|
*/
|
||||||
export function buildStoreInfoCard(storeInfo, userInfo, action) {
|
export function storeConfirmationCard(storeInfo, userInfo, action) {
|
||||||
const storeFacts = [
|
const storeFacts = [
|
||||||
{ title: 'Name', value: storeInfo.name },
|
{ title: 'Name', value: storeInfo.name },
|
||||||
{ title: 'Brand', value: storeInfo.brand },
|
{ title: 'Brand', value: storeInfo.brand },
|
||||||
|
|
@ -52,13 +53,13 @@ export function buildStoreInfoCard(storeInfo, userInfo, action) {
|
||||||
{
|
{
|
||||||
type: 'Action.Submit',
|
type: 'Action.Submit',
|
||||||
title: 'Yes',
|
title: 'Yes',
|
||||||
id: 'btnBuildStore',
|
id: 'btnConfirm',
|
||||||
data: { action, storeInfo, userInfo },
|
data: { action, storeInfo, userInfo },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'Action.Submit',
|
type: 'Action.Submit',
|
||||||
title: 'No',
|
title: 'No',
|
||||||
id: 'btnBadInfo',
|
id: 'btnCancel',
|
||||||
data: { action: 'deleteCard' },
|
data: { action: 'deleteCard' },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { logger } from '../logger.js';
|
import { logger } from '../logger.js';
|
||||||
import { buildStoreLocation } from '../flows/buildStore.js';
|
|
||||||
import { stageStoreLocation } from '../flows/stageStore.js';
|
import { stageStoreLocation } from '../flows/stageStore.js';
|
||||||
import { migrateStoreLocation } from '../flows/migrateStore.js';
|
import { finalizeStoreLocation } from '../flows/finalizeStore.js';
|
||||||
import { getWebexDeviceDetail } from '../webex/devices.js';
|
import { getWebexDeviceDetail } from '../webex/devices.js';
|
||||||
|
|
||||||
async function runFlow(bot, trigger, verb, fn) {
|
async function runFlow(bot, trigger, verb, fn) {
|
||||||
|
|
@ -36,14 +35,11 @@ export function register(framework) {
|
||||||
const action = trigger.attachmentAction.inputs.action;
|
const action = trigger.attachmentAction.inputs.action;
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'buildStore':
|
|
||||||
await runFlow(bot, trigger, 'Building', buildStoreLocation);
|
|
||||||
break;
|
|
||||||
case 'stageStore':
|
case 'stageStore':
|
||||||
await runFlow(bot, trigger, 'Staging', stageStoreLocation);
|
await runFlow(bot, trigger, 'Staging', stageStoreLocation);
|
||||||
break;
|
break;
|
||||||
case 'migrateStore':
|
case 'finalizeStore':
|
||||||
await runFlow(bot, trigger, 'Migrating store', migrateStoreLocation);
|
await runFlow(bot, trigger, 'Finalizing', finalizeStoreLocation);
|
||||||
break;
|
break;
|
||||||
case 'showDevice':
|
case 'showDevice':
|
||||||
await showDevices(bot, trigger);
|
await showDevices(bot, trigger);
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { logger } from '../logger.js';
|
|
||||||
import { getStoreInfo } from '../integrations/siw.js';
|
|
||||||
import { findWebexUser } from '../webex/users.js';
|
|
||||||
import { buildStoreInfoCard } from '../cards/storeInfoCard.js';
|
|
||||||
import { parseStoreNumber, requireAgent, storeEmail } from './helpers.js';
|
|
||||||
|
|
||||||
export function register(framework) {
|
|
||||||
framework.hears(
|
|
||||||
/\/buildstore/i,
|
|
||||||
async (bot, trigger) => {
|
|
||||||
logger.info(`${trigger.person.displayName} ran the buildStore command.`);
|
|
||||||
if (!requireAgent(bot)) return;
|
|
||||||
const storeNumber = parseStoreNumber(trigger);
|
|
||||||
if (!storeNumber) {
|
|
||||||
bot.say('Usage: `/buildStore <storeNumber>` — e.g. `/buildStore 499`');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const storeInfo = await getStoreInfo(storeNumber);
|
|
||||||
const userInfo = await findWebexUser(storeEmail(storeNumber));
|
|
||||||
const card = buildStoreInfoCard(storeInfo, userInfo, 'buildStore');
|
|
||||||
bot.sendCard(card, 'Please use another client');
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('buildStore command failed:', error);
|
|
||||||
bot.say('markdown', `Error running /buildstore:\n\`\`\`\n${error.message}\n\`\`\``);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'**/buildStore** <storeNumber> - Builds a store location for Webex Calling (New and remodels).',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +1,49 @@
|
||||||
import { logger } from '../logger.js';
|
import { logger } from '../logger.js';
|
||||||
import { getStoreInfo } from '../integrations/siw.js';
|
import { getStoreInfo } from '../integrations/siw.js';
|
||||||
import { findWebexUser } from '../webex/users.js';
|
import { findWebexUser } from '../webex/users.js';
|
||||||
import { buildStoreInfoCard } from '../cards/storeInfoCard.js';
|
import { findWebexLocation } from '../webex/locations.js';
|
||||||
|
import { storeConfirmationCard } from '../cards/storeConfirmationCard.js';
|
||||||
import { parseStoreNumber, requireAgent, storeEmail } from './helpers.js';
|
import { parseStoreNumber, requireAgent, storeEmail } from './helpers.js';
|
||||||
|
|
||||||
export function register(framework) {
|
export function register(framework) {
|
||||||
framework.hears(
|
framework.hears(
|
||||||
/\/migratestore/i,
|
/\/finalizestore/i,
|
||||||
async (bot, trigger) => {
|
async (bot, trigger) => {
|
||||||
logger.info(`${trigger.person.displayName} ran the migrateStore command.`);
|
logger.info(`${trigger.person.displayName} ran the finalizeStore command.`);
|
||||||
if (!requireAgent(bot)) return;
|
if (!requireAgent(bot)) return;
|
||||||
const storeNumber = parseStoreNumber(trigger);
|
const storeNumber = parseStoreNumber(trigger);
|
||||||
if (!storeNumber) {
|
if (!storeNumber) {
|
||||||
bot.say('Usage: `/migrateStore <storeNumber>` — e.g. `/migrateStore 499`');
|
bot.say('Usage: `/finalizeStore <storeNumber>` — e.g. `/finalizeStore 499`');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const storeInfo = await getStoreInfo(storeNumber);
|
const storeInfo = await getStoreInfo(storeNumber);
|
||||||
storeInfo.extension = `5${String(storeNumber).padStart(4, '0')}`;
|
const [userInfo, locationMatches] = await Promise.all([
|
||||||
const userInfo = await findWebexUser(storeEmail(storeNumber));
|
findWebexUser(storeEmail(storeNumber)),
|
||||||
const card = buildStoreInfoCard(storeInfo, userInfo, 'migrateStore');
|
findWebexLocation(storeInfo.name),
|
||||||
|
]);
|
||||||
|
// Preflight: if /stageStore was never run, the location won't
|
||||||
|
// exist in Webex, and every downstream mutation would fail.
|
||||||
|
// Bail out here with an actionable message instead of showing
|
||||||
|
// a confirmation card that's guaranteed to blow up on click.
|
||||||
|
if (!locationMatches.length) {
|
||||||
|
bot.say(
|
||||||
|
'markdown',
|
||||||
|
`No Webex location found for **${storeInfo.name}**. ` +
|
||||||
|
`Run \`/stageStore ${storeNumber}\` first.`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const card = storeConfirmationCard(storeInfo, userInfo, 'finalizeStore');
|
||||||
bot.sendCard(card, 'Please use another client');
|
bot.sendCard(card, 'Please use another client');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('migrateStore command failed:', error);
|
logger.error('finalizeStore command failed:', error);
|
||||||
bot.say(
|
bot.say(
|
||||||
'markdown',
|
'markdown',
|
||||||
`Error running /migratestore:\n\`\`\`\n${error.message}\n\`\`\``,
|
`Error running /finalizeStore:\n\`\`\`\n${error.message}\n\`\`\``,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'**/migrateStore** <storeNumber> - Completes the store migration for an open store.',
|
'**/finalizeStore** <storeNumber> - Finalizes a staged store: attaches the phone number, sets caller ID, creates the auto-attendant, and cleans up licensing.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { logger } from '../logger.js';
|
import { logger } from '../logger.js';
|
||||||
import { getStoreInfo } from '../integrations/siw.js';
|
import { getStoreInfo } from '../integrations/siw.js';
|
||||||
import { findWebexUser } from '../webex/users.js';
|
import { findWebexUser } from '../webex/users.js';
|
||||||
import { buildStoreInfoCard } from '../cards/storeInfoCard.js';
|
import { storeConfirmationCard } from '../cards/storeConfirmationCard.js';
|
||||||
import { parseStoreNumber, requireAgent, storeEmail } from './helpers.js';
|
import { parseStoreNumber, requireAgent, storeEmail } from './helpers.js';
|
||||||
|
|
||||||
export function register(framework) {
|
export function register(framework) {
|
||||||
|
|
@ -17,15 +17,14 @@ export function register(framework) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const storeInfo = await getStoreInfo(storeNumber);
|
const storeInfo = await getStoreInfo(storeNumber);
|
||||||
storeInfo.extension = `8${String(storeNumber).padStart(4, '0')}`;
|
|
||||||
const userInfo = await findWebexUser(storeEmail(storeNumber));
|
const userInfo = await findWebexUser(storeEmail(storeNumber));
|
||||||
const card = buildStoreInfoCard(storeInfo, userInfo, 'stageStore');
|
const card = storeConfirmationCard(storeInfo, userInfo, 'stageStore');
|
||||||
bot.sendCard(card, 'Please use another client');
|
bot.sendCard(card, 'Please use another client');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('stageStore command failed:', error);
|
logger.error('stageStore command failed:', error);
|
||||||
bot.say('markdown', `Error running /stagestore:\n\`\`\`\n${error.message}\n\`\`\``);
|
bot.say('markdown', `Error running /stagestore:\n\`\`\`\n${error.message}\n\`\`\``);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'**/stageStore** <storeNumber> - Stages a store location for Webex Calling (Pre-migration).',
|
'**/stageStore** <storeNumber> - Stages a store location for Webex Calling (everything except the phone number).',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
import {
|
|
||||||
addPhoneNumbersToLocation,
|
|
||||||
createLocation,
|
|
||||||
enableLocationForCalling,
|
|
||||||
updateInternalDialing,
|
|
||||||
updateLocationOutgoingPermission,
|
|
||||||
updateLocationRouteGroup,
|
|
||||||
updateLocationVoicemail,
|
|
||||||
updateLocationVoicePortal,
|
|
||||||
updateMusicOnHold,
|
|
||||||
} from '../webex/locations.js';
|
|
||||||
import { scheduleStoreDeviceSettings } from '../webex/devices.js';
|
|
||||||
import { createAllHoursSchedule } from '../webex/schedules.js';
|
|
||||||
import { uploadGreeting } from '../webex/announcements.js';
|
|
||||||
import { addWebexCallingToStoreUser, normalizeStoreUserLicenses } from '../webex/licensing.js';
|
|
||||||
import { updateUserCallExperience } from '../webex/users.js';
|
|
||||||
import { logger } from '../logger.js';
|
|
||||||
import { runStep } from './stepRunner.js';
|
|
||||||
import { greetingForBrand } from './greetingSelector.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* "Build store" — full green-field provisioning: create location, calling,
|
|
||||||
* greeting, user attach, license cleanup.
|
|
||||||
*/
|
|
||||||
export async function buildStoreLocation(bot, locationInfo, userInfo) {
|
|
||||||
logger.info(`Building location ${locationInfo.name}`);
|
|
||||||
const location = await createLocation(locationInfo);
|
|
||||||
bot.say(
|
|
||||||
'markdown',
|
|
||||||
`<blockquote class='success'>Created location ${location.name}.</blockquote>`,
|
|
||||||
);
|
|
||||||
|
|
||||||
await runStep(
|
|
||||||
bot,
|
|
||||||
'Enabled location for Webex Calling',
|
|
||||||
() => enableLocationForCalling(location),
|
|
||||||
{ critical: true },
|
|
||||||
);
|
|
||||||
await runStep(bot, 'Updated location Webex Calling connection', () =>
|
|
||||||
updateLocationRouteGroup(location),
|
|
||||||
);
|
|
||||||
await runStep(bot, 'Added phone numbers to location', () =>
|
|
||||||
addPhoneNumbersToLocation(location, locationInfo.phoneNumber),
|
|
||||||
);
|
|
||||||
await runStep(bot, 'Updated internal dialing', () => updateInternalDialing(location));
|
|
||||||
await runStep(bot, 'Updated location outgoing permission', () =>
|
|
||||||
updateLocationOutgoingPermission(location),
|
|
||||||
);
|
|
||||||
await runStep(bot, 'Updated music on hold', () => updateMusicOnHold(location));
|
|
||||||
await runStep(bot, 'Updated location voicemail', () => updateLocationVoicemail(location));
|
|
||||||
await runStep(bot, 'Updated location voice portal', () =>
|
|
||||||
updateLocationVoicePortal(location, locationInfo.vpExtension),
|
|
||||||
);
|
|
||||||
await runStep(bot, 'Created a schedule', () => createAllHoursSchedule(location));
|
|
||||||
await runStep(bot, 'Scheduled device changes', () => scheduleStoreDeviceSettings(location));
|
|
||||||
|
|
||||||
const { file, fileName } = greetingForBrand(locationInfo);
|
|
||||||
const greeting = await runStep(bot, 'Succeeded uploading greeting', () =>
|
|
||||||
uploadGreeting(location, file, fileName),
|
|
||||||
);
|
|
||||||
if (greeting) location.announcementId = greeting.id;
|
|
||||||
|
|
||||||
await runStep(bot, 'Updated store user', () =>
|
|
||||||
addWebexCallingToStoreUser(location, userInfo, locationInfo.extension),
|
|
||||||
);
|
|
||||||
await runStep(bot, 'Updated user call application experience', () =>
|
|
||||||
updateUserCallExperience(userInfo),
|
|
||||||
);
|
|
||||||
await runStep(bot, 'Fixed Licenses', () => normalizeStoreUserLicenses(userInfo));
|
|
||||||
|
|
||||||
bot.say('Build Complete!');
|
|
||||||
return location;
|
|
||||||
}
|
|
||||||
|
|
@ -17,16 +17,21 @@ import { logger } from '../logger.js';
|
||||||
import { runStep } from './stepRunner.js';
|
import { runStep } from './stepRunner.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "Migrate store" — finish the cut-over for a store that was previously staged.
|
* "Finalize store" — finish the cut-over for a store that was previously staged.
|
||||||
* Attach the phone number, set caller ID, create the auto-attendant, and clean
|
* Attach the phone number, set caller ID, create the auto-attendant, and clean
|
||||||
* up the user's licensing/voicemail.
|
* up the user's licensing/voicemail.
|
||||||
|
*
|
||||||
|
* Precondition: the location already exists in Webex (i.e. `/stageStore` has
|
||||||
|
* been run). The command handler preflight rejects unstaged stores before we
|
||||||
|
* get here, but we defensively re-check because the flow can also be invoked
|
||||||
|
* directly from the attachmentAction submission.
|
||||||
*/
|
*/
|
||||||
export async function migrateStoreLocation(bot, locationInfo, userInfo) {
|
export async function finalizeStoreLocation(bot, locationInfo, userInfo) {
|
||||||
logger.info(`Migrating location ${locationInfo.name}`);
|
logger.info(`Finalizing location ${locationInfo.name}`);
|
||||||
const matches = await findWebexLocation(locationInfo.name);
|
const matches = await findWebexLocation(locationInfo.name);
|
||||||
if (!matches.length) {
|
if (!matches.length) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`No existing Webex location found for ${locationInfo.name}. Did you stage first?`,
|
`No existing Webex location found for ${locationInfo.name}. Run /stageStore first.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const location = matches[0];
|
const location = matches[0];
|
||||||
|
|
@ -70,6 +75,6 @@ export async function migrateStoreLocation(bot, locationInfo, userInfo) {
|
||||||
|
|
||||||
await runStep(bot, 'Fixed Licenses', () => normalizeStoreUserLicenses(userInfo));
|
await runStep(bot, 'Fixed Licenses', () => normalizeStoreUserLicenses(userInfo));
|
||||||
|
|
||||||
bot.say('Migration Complete!');
|
bot.say('markdown', `**Finalize complete for ${locationInfo.name}.** Location is now live.`);
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,11 @@ export async function stageStoreLocation(bot, locationInfo, userInfo) {
|
||||||
addWebexCallingToStoreUser(location, userInfo, locationInfo.extension),
|
addWebexCallingToStoreUser(location, userInfo, locationInfo.extension),
|
||||||
);
|
);
|
||||||
|
|
||||||
bot.say('Staging Complete!');
|
bot.say(
|
||||||
|
'markdown',
|
||||||
|
`**Staging complete for ${locationInfo.name}.** ` +
|
||||||
|
`Next: purchase a phone number in Webex Control Hub, then run ` +
|
||||||
|
`\`/finalizeStore ${locationInfo.storeNumber}\`.`,
|
||||||
|
);
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,8 @@ import { config } from './config.js';
|
||||||
import { logger } from './logger.js';
|
import { logger } from './logger.js';
|
||||||
import { isAccessTokenExpiring, refreshAccessToken } from './webex/auth.js';
|
import { isAccessTokenExpiring, refreshAccessToken } from './webex/auth.js';
|
||||||
import { startWebSocketServer, stopWebSocketServer } from './services/websocket.js';
|
import { startWebSocketServer, stopWebSocketServer } from './services/websocket.js';
|
||||||
import { register as registerBuildStore } from './commands/buildStore.js';
|
|
||||||
import { register as registerStageStore } from './commands/stageStore.js';
|
import { register as registerStageStore } from './commands/stageStore.js';
|
||||||
import { register as registerMigrateStore } from './commands/migrateStore.js';
|
import { register as registerFinalizeStore } from './commands/finalizeStore.js';
|
||||||
import { register as registerStoreInfo } from './commands/storeInfo.js';
|
import { register as registerStoreInfo } from './commands/storeInfo.js';
|
||||||
import { register as registerUserInfo } from './commands/userInfo.js';
|
import { register as registerUserInfo } from './commands/userInfo.js';
|
||||||
import { register as registerAttachmentActions } from './commands/attachmentActions.js';
|
import { register as registerAttachmentActions } from './commands/attachmentActions.js';
|
||||||
|
|
@ -21,9 +20,8 @@ framework.on('log', (msg) => {
|
||||||
logger.info(msg);
|
logger.info(msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
registerBuildStore(framework);
|
|
||||||
registerStageStore(framework);
|
registerStageStore(framework);
|
||||||
registerMigrateStore(framework);
|
registerFinalizeStore(framework);
|
||||||
registerStoreInfo(framework);
|
registerStoreInfo(framework);
|
||||||
registerUserInfo(framework);
|
registerUserInfo(framework);
|
||||||
registerAttachmentActions(framework);
|
registerAttachmentActions(framework);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Per-device-family calling customizations applied to every store location
|
// Per-device-family calling customizations applied to every store location
|
||||||
// as part of buildStore / stageStore. Sent verbatim to
|
// as part of stageStore. Sent verbatim to
|
||||||
// POST /telephony/config/jobs/devices/callDeviceSettings under `customizations`.
|
// POST /telephony/config/jobs/devices/callDeviceSettings under `customizations`.
|
||||||
//
|
//
|
||||||
// This is intentionally a data-only module so device config changes are a
|
// This is intentionally a data-only module so device config changes are a
|
||||||
|
|
|
||||||
|
|
@ -65,17 +65,6 @@ export async function addPhoneNumbersToLocation(location, phoneNumber) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Point the location at its route group (used before phone numbers exist).
|
|
||||||
*/
|
|
||||||
export async function updateLocationRouteGroup(location) {
|
|
||||||
const routeGroup = routeGroupFor(location);
|
|
||||||
const body = {
|
|
||||||
connection: { id: routeGroup.id, type: 'ROUTE_GROUP' },
|
|
||||||
};
|
|
||||||
return webexJson('PUT', `/telephony/config/locations/${encodeURIComponent(location.id)}`, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the location's caller-ID/external-caller-ID after phone numbers exist.
|
* Set the location's caller-ID/external-caller-ID after phone numbers exist.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue