v2.1: settings SMS fields + HD Ticket/Contact/Lead form JS: hd_ticket.js

This commit is contained in:
Zac Gaetano 2026-05-12 00:12:52 -04:00
parent cdcb621a91
commit b1a00c8bd9

View file

@ -0,0 +1,184 @@
/**
* HD Ticket form JS adds Voxtelesys click-to-call and SMS controls,
* plus a history sidebar showing recent calls and SMS for this ticket's
* contact.
*
* Loaded via hooks.py:doctype_js. Requires frappe.boot.voxtelesys to be
* populated by the boot_session hook in api/voxtelesys.py.
*/
frappe.ui.form.on("HD Ticket", {
refresh(frm) {
if (!frappe.boot.voxtelesys || !frappe.boot.voxtelesys.enabled) return;
const phone = _resolveTicketPhone(frm);
// ---- Call button ----
if (phone) {
frm.add_custom_button(
`<i class="fa fa-phone"></i> ${__("Call {0}", [phone])}`,
() => {
frappe.call({
method: "voxtelesys_integration.api.voxtelesys.make_outbound_call",
args: {
to: phone,
reference_doctype: frm.doctype,
reference_name: frm.docname,
},
freeze: true,
freeze_message: __("Dialing {0}…", [phone]),
callback(r) {
if (r.message && r.message.ok) {
frappe.show_alert({
message: __("Calling {0}", [phone]),
indicator: "green",
});
frm.reload_doc();
}
},
});
},
__("Voxtelesys")
);
}
// ---- SMS button ----
if (phone && frappe.boot.voxtelesys.sms_enabled !== false) {
frm.add_custom_button(
`<i class="fa fa-comment"></i> ${__("Send SMS")}`,
() => _showSmsDialog(frm, phone),
__("Voxtelesys")
);
}
// ---- History sidebar (calls + SMS) ----
_renderHistory(frm);
},
});
function _resolveTicketPhone(frm) {
// Prefer the linked contact's mobile, fall back to phone, fall back to a
// raw "phone" field if the site has one. We don't try to derive from
// raised_by (email) — that requires an extra lookup we can do later.
if (!frm.doc.contact) return null;
if (frm._vtx_contact_phone) return frm._vtx_contact_phone;
// Synchronously cached on the form via a one-shot lookup
frappe.db
.get_value("Contact", frm.doc.contact, ["mobile_no", "phone"])
.then((r) => {
const m = r.message || {};
const phone = m.mobile_no || m.phone || null;
if (phone) {
frm._vtx_contact_phone = phone;
frm.refresh();
}
});
return null;
}
function _showSmsDialog(frm, defaultTo) {
const d = new frappe.ui.Dialog({
title: __("Send SMS"),
fields: [
{ fieldname: "to", fieldtype: "Data", label: __("To"), default: defaultTo, reqd: 1 },
{
fieldname: "body",
fieldtype: "Small Text",
label: __("Message"),
reqd: 1,
description: __("Max ~1600 chars; Voxtelesys auto-segments longer messages."),
},
],
primary_action_label: __("Send"),
primary_action(values) {
frappe.call({
method: "voxtelesys_integration.api.sms.send_sms",
args: {
to: values.to,
body: values.body,
reference_doctype: frm.doctype,
reference_name: frm.docname,
},
freeze: true,
freeze_message: __("Sending SMS…"),
callback(r) {
if (r.message && r.message.ok) {
d.hide();
frappe.show_alert({
message: __("SMS sent"),
indicator: "green",
});
frm.reload_doc();
}
},
});
},
});
d.show();
}
function _renderHistory(frm) {
if (!frm.fields_dict.voxtelesys_history) {
// No HTML field exists — render in the sidebar instead
const $wrapper = frm.sidebar.sidebar.find(".vtx-history-wrapper");
if ($wrapper.length) return;
frm.sidebar.sidebar.append(
'<div class="form-sidebar-section vtx-history-wrapper">' +
'<div class="sidebar-label">' + __("Voxtelesys History") + "</div>" +
'<div class="vtx-history-content"><i>' + __("Loading…") + "</i></div>" +
"</div>"
);
}
Promise.all([
frappe.call({
method: "voxtelesys_integration.api.voxtelesys.get_linked_call_logs",
args: { doctype: frm.doctype, name: frm.docname },
}),
frappe.call({
method: "voxtelesys_integration.api.sms.get_linked_sms_logs",
args: { doctype: frm.doctype, name: frm.docname },
}),
])
.then(([callsResp, smsResp]) => {
const calls = callsResp.message || [];
const sms = smsResp.message || [];
const $content = frm.sidebar.sidebar.find(".vtx-history-content");
if (!$content.length) return;
if (!calls.length && !sms.length) {
$content.html(`<i class="text-muted">${__("No calls or SMS yet")}</i>`);
return;
}
const rows = [];
calls.forEach((c) => {
const when = c.start_time ? frappe.datetime.prettyDate(c.start_time) : "";
const icon = c.direction === "Outbound" ? "📤" : "📥";
rows.push(`
<div class="vtx-history-row">
<a href="/app/voxtelesys-call-log/${c.name}">
${icon} ${frappe.utils.escape_html(c.from_number || c.to_number || "")}
</a>
<span class="text-muted small">${frappe.utils.escape_html(c.status || "")} · ${when}</span>
</div>`);
});
sms.forEach((s) => {
const when = (s.received_at || s.sent_at) ? frappe.datetime.prettyDate(s.received_at || s.sent_at) : "";
const icon = s.direction === "Outbound" ? "💬↗" : "💬↙";
const preview = (s.body || "").slice(0, 40);
rows.push(`
<div class="vtx-history-row">
<a href="/app/voxtelesys-sms-log/${s.name}">
${icon} ${frappe.utils.escape_html(preview)}${(s.body || "").length > 40 ? "…" : ""}
</a>
<span class="text-muted small">${when}</span>
</div>`);
});
$content.html(rows.join(""));
})
.catch(() => {
const $content = frm.sidebar.sidebar.find(".vtx-history-content");
if ($content.length) $content.html(`<i class="text-muted">${__("Failed to load history")}</i>`);
});
}