Files
kitchen-agent/abandoned-call-poller.js
odzugkoev 26badf8e6a initial
2026-05-01 10:48:11 -04:00

164 lines
4.8 KiB
JavaScript

const config = require('./config');
const apiClient = require('./api-client');
function parseOptionalNonNegativeNumber(value) {
if (value === undefined || value === null || value === '') return null;
const parsed = Number(value);
if (!Number.isFinite(parsed) || parsed < 0) return null;
return parsed;
}
class AbandonedCallPoller {
constructor(database, printer) {
this.db = database;
this.printer = printer;
this.intervalId = null;
this.isPolling = false;
}
async start() {
console.log('Starting abandoned call poller...');
this.poll();
this.scheduleNextPoll();
}
scheduleNextPoll() {
const appConfig = config.getAll();
const interval = parseInt(appConfig.abandonedCallPollingInterval, 10) || 30000;
if (this.intervalId) {
clearTimeout(this.intervalId);
}
this.intervalId = setTimeout(() => {
this.poll();
this.scheduleNextPoll();
}, interval);
}
async poll() {
if (this.isPolling) return;
this.isPolling = true;
try {
const appConfig = config.getAll();
if (!appConfig.authToken || !appConfig.selectedBotId) {
this.isPolling = false;
return;
}
if (apiClient.isTokenNearExpiry(appConfig.tokenExpiry, 7)) {
const refreshed = await apiClient.ensureValidToken();
if (!refreshed) {
this.isPolling = false;
return;
}
Object.assign(appConfig, config.getAll());
}
const result = await apiClient.getAbandonedCalls(
appConfig.authToken,
appConfig.selectedBotId,
{ limit: 200 }
);
if (result.error) {
console.error('Abandoned calls poll error:', result.message);
this.isPolling = false;
return;
}
const calls = result.calls || [];
for (const call of calls) {
this.db.cacheAbandonedCall(call.id, call);
}
// Also fetch callback queue for the management page cache
const queueResult = await apiClient.getAbandonedCallbackQueue(
appConfig.authToken,
appConfig.selectedBotId,
50
);
if (!queueResult.error && queueResult.queue) {
for (const call of queueResult.queue) {
this.db.cacheAbandonedCall(call.id, call);
}
}
// Print new abandoned calls that haven't been printed yet
await this.printNewAbandonedCalls(calls, appConfig);
// Clean old cache periodically
this.db.cleanOldAbandonedCallCache(7);
} catch (error) {
console.error('Abandoned call poll error:', error.message);
}
this.isPolling = false;
}
async printNewAbandonedCalls(calls, appConfig) {
const printerConfigs = this.db.getAbandonedCallPrinters();
if (!printerConfigs || printerConfigs.length === 0) return;
const configuredCooldown = parseOptionalNonNegativeNumber(appConfig.abandonedCallPrintCooldown);
const cooldownSeconds = configuredCooldown === null ? 0 : configuredCooldown;
const minScoreForPrint = parseOptionalNonNegativeNumber(appConfig.abandonedCallMinScoreForPrint);
let lastPrintTime = this.db.getLastAbandonedCallPrintTime();
for (const call of calls) {
if (!call || !call.id) continue;
if (this.db.hasAbandonedCallPrint(call.id)) continue;
const score = Number(call.callback_score) || 0;
if (minScoreForPrint !== null && score < minScoreForPrint) {
console.log(`Abandoned call #${call.id}: skipping print (score ${score} below configured minimum ${minScoreForPrint})`);
continue;
}
const now = Math.floor(Date.now() / 1000);
if (lastPrintTime && (now - lastPrintTime) < cooldownSeconds) {
console.log(`Abandoned call #${call.id}: skipping print (cooldown active)`);
continue;
}
try {
const result = await this.printer.printAbandonedCallReceipt(call, printerConfigs);
const printedCount = result && typeof result.successCount === 'number'
? result.successCount
: (result && result.success ? printerConfigs.length : 0);
if (result && result.success && printedCount > 0) {
this.db.addAbandonedCallPrint(call.id, printedCount);
lastPrintTime = Math.floor(Date.now() / 1000);
console.log(`Abandoned call #${call.id}: printed on ${printedCount} printer(s)`);
} else {
const message = result && result.error ? result.error : 'No printers succeeded';
console.error(`Abandoned call #${call.id}: print failed: ${message}`);
}
} catch (err) {
console.error(`Abandoned call #${call.id}: print failed:`, err.message);
}
}
}
stop() {
if (this.intervalId) {
clearTimeout(this.intervalId);
this.intervalId = null;
}
console.log('Abandoned call poller stopped');
}
restart() {
this.stop();
this.start();
}
}
module.exports = AbandonedCallPoller;