done
This commit is contained in:
161
database.js
161
database.js
@@ -100,6 +100,30 @@ class DatabaseManager {
|
||||
)
|
||||
`);
|
||||
|
||||
// Abandoned call print tracking
|
||||
this.db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS abandoned_call_prints (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
abandoned_call_id INTEGER UNIQUE NOT NULL,
|
||||
printed_at INTEGER NOT NULL,
|
||||
printer_count INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
`);
|
||||
|
||||
// Abandoned calls local cache
|
||||
this.db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS abandoned_calls_cache (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
remote_id INTEGER UNIQUE NOT NULL,
|
||||
data TEXT NOT NULL,
|
||||
callback_status TEXT,
|
||||
callback_priority TEXT,
|
||||
callback_score INTEGER DEFAULT 0,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
)
|
||||
`);
|
||||
|
||||
// Create indexes
|
||||
this.db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status);
|
||||
@@ -108,6 +132,9 @@ class DatabaseManager {
|
||||
CREATE INDEX IF NOT EXISTS idx_print_queue_status ON print_queue(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_printers_enabled ON printers(is_enabled);
|
||||
CREATE INDEX IF NOT EXISTS idx_printers_default ON printers(is_default);
|
||||
CREATE INDEX IF NOT EXISTS idx_abandoned_prints_call_id ON abandoned_call_prints(abandoned_call_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_abandoned_cache_remote ON abandoned_calls_cache(remote_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_abandoned_cache_status ON abandoned_calls_cache(callback_status);
|
||||
`);
|
||||
|
||||
// Initialize default config values if not exists
|
||||
@@ -115,6 +142,9 @@ class DatabaseManager {
|
||||
|
||||
// Migrate old printer config to new table if needed
|
||||
this.migrateOldPrinterConfig();
|
||||
|
||||
// Add print_abandoned_calls column to printers if missing
|
||||
this.migratePrintersAddAbandonedCalls();
|
||||
}
|
||||
|
||||
setConfigDefaults() {
|
||||
@@ -474,9 +504,9 @@ class DatabaseManager {
|
||||
header_text, footer_text,
|
||||
business_name, business_address, business_phone, business_website, business_email,
|
||||
business_contact_size, show_customer_info, show_order_items, show_prices, show_timestamps,
|
||||
logo_path, logo_max_width_dots, created_at, updated_at
|
||||
logo_path, logo_max_width_dots, print_abandoned_calls, created_at, updated_at
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
`);
|
||||
|
||||
@@ -509,6 +539,7 @@ class DatabaseManager {
|
||||
config.show_timestamps !== false ? 1 : 0,
|
||||
config.logo_path || null,
|
||||
config.logo_max_width_dots || null,
|
||||
config.print_abandoned_calls !== false ? 1 : 0,
|
||||
now,
|
||||
now
|
||||
);
|
||||
@@ -531,7 +562,7 @@ class DatabaseManager {
|
||||
header_text = ?, footer_text = ?,
|
||||
business_name = ?, business_address = ?, business_phone = ?, business_website = ?, business_email = ?,
|
||||
business_contact_size = ?, show_customer_info = ?, show_order_items = ?, show_prices = ?, show_timestamps = ?,
|
||||
logo_path = ?, logo_max_width_dots = ?, updated_at = ?
|
||||
logo_path = ?, logo_max_width_dots = ?, print_abandoned_calls = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
`);
|
||||
|
||||
@@ -564,6 +595,7 @@ class DatabaseManager {
|
||||
config.show_timestamps !== false ? 1 : 0,
|
||||
config.logo_path || null,
|
||||
config.logo_max_width_dots || null,
|
||||
config.print_abandoned_calls !== false ? 1 : 0,
|
||||
now,
|
||||
id
|
||||
);
|
||||
@@ -647,6 +679,7 @@ class DatabaseManager {
|
||||
show_timestamps: row.show_timestamps === 1,
|
||||
logo_path: row.logo_path,
|
||||
logo_max_width_dots: row.logo_max_width_dots,
|
||||
print_abandoned_calls: row.print_abandoned_calls == null ? true : row.print_abandoned_calls === 1,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at
|
||||
};
|
||||
@@ -710,6 +743,128 @@ class DatabaseManager {
|
||||
}
|
||||
}
|
||||
|
||||
migratePrintersAddAbandonedCalls() {
|
||||
try {
|
||||
const cols = this.db.pragma('table_info(printers)');
|
||||
const hasCol = cols.some(c => c.name === 'print_abandoned_calls');
|
||||
if (!hasCol) {
|
||||
this.db.exec('ALTER TABLE printers ADD COLUMN print_abandoned_calls INTEGER NOT NULL DEFAULT 1');
|
||||
console.log('Migration: added print_abandoned_calls column to printers');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Migration error (print_abandoned_calls):', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
getAbandonedCallPrinters() {
|
||||
const rows = this.db.prepare(
|
||||
'SELECT * FROM printers WHERE is_enabled = 1 AND print_abandoned_calls = 1 ORDER BY is_default DESC, name ASC'
|
||||
).all();
|
||||
return rows.map(row => this.mapPrinterRow(row));
|
||||
}
|
||||
|
||||
// Abandoned call print tracking
|
||||
hasAbandonedCallPrint(abandonedCallId) {
|
||||
const row = this.db.prepare(
|
||||
'SELECT 1 FROM abandoned_call_prints WHERE abandoned_call_id = ? LIMIT 1'
|
||||
).get(abandonedCallId);
|
||||
return !!row;
|
||||
}
|
||||
|
||||
addAbandonedCallPrint(abandonedCallId, printerCount = 0) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
try {
|
||||
this.db.prepare(
|
||||
'INSERT OR IGNORE INTO abandoned_call_prints (abandoned_call_id, printed_at, printer_count) VALUES (?, ?, ?)'
|
||||
).run(abandonedCallId, now, printerCount);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
getLastAbandonedCallPrintTime() {
|
||||
const row = this.db.prepare(
|
||||
'SELECT printed_at FROM abandoned_call_prints ORDER BY printed_at DESC LIMIT 1'
|
||||
).get();
|
||||
return row ? row.printed_at : 0;
|
||||
}
|
||||
|
||||
// Abandoned calls cache
|
||||
cacheAbandonedCall(remoteId, data) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const json = typeof data === 'string' ? data : JSON.stringify(data);
|
||||
const callbackStatus = (typeof data === 'object' ? data.callback_status : null) || null;
|
||||
const callbackPriority = (typeof data === 'object' ? data.callback_priority : null) || null;
|
||||
const callbackScore = (typeof data === 'object' ? Number(data.callback_score) : 0) || 0;
|
||||
this.db.prepare(`
|
||||
INSERT INTO abandoned_calls_cache (remote_id, data, callback_status, callback_priority, callback_score, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(remote_id) DO UPDATE SET
|
||||
data = excluded.data,
|
||||
callback_status = excluded.callback_status,
|
||||
callback_priority = excluded.callback_priority,
|
||||
callback_score = excluded.callback_score,
|
||||
updated_at = excluded.updated_at
|
||||
`).run(remoteId, json, callbackStatus, callbackPriority, callbackScore, now, now);
|
||||
}
|
||||
|
||||
getCachedAbandonedCalls(options = {}) {
|
||||
let query = 'SELECT * FROM abandoned_calls_cache WHERE 1=1';
|
||||
const params = [];
|
||||
if (options.status) {
|
||||
query += ' AND callback_status = ?';
|
||||
params.push(options.status);
|
||||
}
|
||||
if (options.priority) {
|
||||
query += ' AND callback_priority = ?';
|
||||
params.push(options.priority);
|
||||
}
|
||||
query += ' ORDER BY callback_score DESC, updated_at DESC';
|
||||
if (options.limit) {
|
||||
query += ' LIMIT ?';
|
||||
params.push(options.limit);
|
||||
}
|
||||
const rows = this.db.prepare(query).all(...params);
|
||||
return rows.map(row => {
|
||||
try { return JSON.parse(row.data); } catch (_) { return null; }
|
||||
}).filter(Boolean);
|
||||
}
|
||||
|
||||
updateCachedCallbackStatus(remoteId, status) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
this.db.prepare(
|
||||
'UPDATE abandoned_calls_cache SET callback_status = ?, updated_at = ? WHERE remote_id = ?'
|
||||
).run(status, now, remoteId);
|
||||
}
|
||||
|
||||
getPendingAbandonedCallCount() {
|
||||
const row = this.db.prepare(
|
||||
"SELECT COUNT(*) as count FROM abandoned_calls_cache WHERE callback_status IN ('queued', 'deferred')"
|
||||
).get();
|
||||
return row ? row.count : 0;
|
||||
}
|
||||
|
||||
getAbandonedCallStats() {
|
||||
const row = this.db.prepare(`
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN callback_status IN ('queued','deferred') THEN 1 ELSE 0 END) as pending,
|
||||
SUM(CASE WHEN callback_status = 'converted' THEN 1 ELSE 0 END) as converted,
|
||||
SUM(CASE WHEN callback_status = 'dismissed' THEN 1 ELSE 0 END) as dismissed
|
||||
FROM abandoned_calls_cache
|
||||
`).get();
|
||||
return {
|
||||
total: (row && row.total) || 0,
|
||||
pending: (row && row.pending) || 0,
|
||||
converted: (row && row.converted) || 0,
|
||||
dismissed: (row && row.dismissed) || 0
|
||||
};
|
||||
}
|
||||
|
||||
cleanOldAbandonedCallCache(maxAgeDays = 7) {
|
||||
const cutoff = Math.floor(Date.now() / 1000) - (maxAgeDays * 86400);
|
||||
this.db.prepare('DELETE FROM abandoned_calls_cache WHERE updated_at < ?').run(cutoff);
|
||||
this.db.prepare('DELETE FROM abandoned_call_prints WHERE printed_at < ?').run(cutoff);
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.db) {
|
||||
this.db.close();
|
||||
|
||||
Reference in New Issue
Block a user