// services/callReport/resolveTarget.js // Parse and resolve /callreport targets: store, user email, or phone number. import webex from '../../integrations/webex/WebexClient.js'; import { logger } from '../../utils/logger.js'; import { DISPLAY_TIMEZONE } from '../../utils/time.js'; import { resolveNumberAssignment, resolvePersonLocation, } from '../callTest/locationResolver.js'; import { buildScopedCallFilter } from './filterCallsByTarget.js'; import { getDectNetworksForPerson, getPersonDetails, getPersonIdByEmail, } from '../phoneService.js'; import { resolveStoreForCallReport } from './storeContext.js'; import { parseCallReportTarget, parseStoreNumFromLocationName } from './parseTarget.js'; const LOG_SCOPE = 'callreport:target'; async function resolveLocationDetails(locationId) { if (!locationId) return null; return webex.request('GET', `telephony/config/locations/${locationId}`).catch(() => null); } async function resolveTimezone({ personId, locationId, locationDetails }) { let timeZone = DISPLAY_TIMEZONE; try { const profile = personId ? await webex.request('GET', `telephony/config/people/${personId}`).catch(() => null) : null; const location = locationDetails || (locationId ? await resolveLocationDetails(locationId) : null); timeZone = profile?.timeZone || location?.timeZone || location?.timezone || timeZone; } catch (err) { logger(LOG_SCOPE, `Timezone lookup failed: ${err.message}`, 'debug'); } return timeZone; } function buildStoreContextFromLocation({ kind, label, storeNum, personId, locationId, locationName, dialNumber, timeZone, email, filter, }) { return { kind, label, storeNum, personId: personId || null, locationId, locationName, dialNumber, timeZone, email: email || null, filter: filter || null, }; } async function resolveStoreTarget(parsed) { const store = await resolveStoreForCallReport(parsed.storeNum); return buildStoreContextFromLocation({ kind: 'store', label: `Store ${store.storeNum}`, storeNum: store.storeNum, personId: store.personId, locationId: store.locationId, locationName: store.locationName, dialNumber: store.dialNumber, timeZone: store.timeZone, email: store.email, filter: null, }); } async function resolveNumberTarget(parsed) { const assignment = await resolveNumberAssignment(parsed.phoneNumber); if (!assignment?.locationId) { throw new Error(`No Webex location found for number ${parsed.phoneNumber}`); } const locationDetails = await resolveLocationDetails(assignment.locationId); const dialNumber = locationDetails?.callingLineId?.phoneNumber || locationDetails?.phoneNumber || null; const storeNum = parseStoreNumFromLocationName(assignment.locationName); const timeZone = await resolveTimezone({ personId: assignment.owner?.id, locationId: assignment.locationId, locationDetails, }); const filter = buildScopedCallFilter({ kind: 'number', phoneNumber: parsed.phoneNumber, personId: assignment.owner?.id || null, extension: assignment.extension, locationMain: dialNumber, }); return buildStoreContextFromLocation({ kind: 'number', label: parsed.phoneNumber, storeNum, personId: assignment.owner?.id || null, locationId: assignment.locationId, locationName: assignment.locationName, dialNumber, timeZone, email: null, filter, }); } async function resolveUserTarget(parsed) { const personId = await getPersonIdByEmail(parsed.email); if (!personId) { throw new Error(`No Webex person found for ${parsed.email}`); } const person = await getPersonDetails(personId); let locationId = null; let locationName = null; let ownedNumbers = []; const personLoc = await resolvePersonLocation(personId); if (personLoc) { locationId = personLoc.locationId; locationName = personLoc.locationName; ownedNumbers = personLoc.ownedNumbers; } else { const dectNets = await getDectNetworksForPerson(personId); if (dectNets.length > 0) { locationId = dectNets[0].locationId && dectNets[0].locationId !== '—' ? dectNets[0].locationId : null; locationName = dectNets[0].locationName && dectNets[0].locationName !== '—' ? dectNets[0].locationName : null; } } if (!locationId || !locationName) { throw new Error(`No Webex Calling location found for ${parsed.email}`); } const locationDetails = await resolveLocationDetails(locationId); const dialNumber = locationDetails?.callingLineId?.phoneNumber || locationDetails?.phoneNumber || null; const storeNum = parseStoreNumFromLocationName(locationName); const timeZone = await resolveTimezone({ personId, locationId, locationDetails }); const filter = buildScopedCallFilter({ kind: 'user', email: parsed.email, personId, person, ownedNumbers, locationMain: dialNumber, }); if (ownedNumbers.length > 1) { logger( LOG_SCOPE, `User ${parsed.email} has numbers at multiple locations; using ${locationName}`, 'warn', ); } return buildStoreContextFromLocation({ kind: 'user', label: parsed.email, storeNum, personId, locationId, locationName, dialNumber, timeZone, email: parsed.email, filter, }); } /** * @param {string} token raw first argument * @returns {Promise} unified call report target context */ export async function resolveCallReportTarget(token) { const parsed = parseCallReportTarget(token); if (!parsed) { throw new Error('Invalid target — use a store number (2–4 digits), email, or phone number'); } if (parsed.kind === 'store') return resolveStoreTarget(parsed); if (parsed.kind === 'number') return resolveNumberTarget(parsed); return resolveUserTarget(parsed); }