Initial commit

This commit is contained in:
gsinghpal
2026-02-22 01:22:18 -05:00
commit 5200d5baf0
2394 changed files with 386834 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { PhoneField } from "@web/views/fields/phone/phone_field";
import { rpcBus } from "@web/core/network/rpc";
patch(PhoneField.prototype, {
onClickCall(ev) {
if (window.RCAdapter) {
ev.preventDefault();
ev.stopPropagation();
const phoneNumber = this.props.record.data[this.props.name];
if (phoneNumber) {
window.RCAdapter.clickToCall(phoneNumber, true);
}
return;
}
return super.onClickCall(...arguments);
},
});
rpcBus.addEventListener("RPC:RESPONSE", (ev) => {
const { result } = ev.detail;
if (!result || typeof result !== "object" || !window.RCAdapter) {
return;
}
const infos = result.infos;
if (!infos || !infos.rc_action || !infos.rc_phone_number) {
return;
}
const { rc_action, rc_phone_number } = infos;
if (rc_action === "call") {
window.RCAdapter.clickToCall(rc_phone_number, true);
} else if (rc_action === "sms") {
window.RCAdapter.clickToSMS(rc_phone_number);
}
});

View File

@@ -0,0 +1,86 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { session } from "@web/session";
import { rpc } from "@web/core/network/rpc";
const RC_ADAPTER_URL = "https://apps.ringcentral.com/integration/ringcentral-embeddable/latest/adapter.js";
let rcWidgetLoaded = false;
async function loadRcWidget() {
if (rcWidgetLoaded) return;
let config;
try {
config = await rpc("/ringcentral/widget-config", {});
} catch {
return;
}
if (!config || !config.enabled || !config.client_id) return;
rcWidgetLoaded = true;
const script = document.createElement("script");
const params = new URLSearchParams({
clientId: config.client_id,
appServer: config.app_server || "https://platform.ringcentral.com",
disableInactiveTabCallEvent: "1",
});
script.src = `${RC_ADAPTER_URL}?${params.toString()}`;
document.head.appendChild(script);
window.addEventListener("message", (e) => {
if (!e.data) return;
switch (e.data.type) {
case "rc-login-status-notify":
window.__rcLoggedIn = e.data.loggedIn;
break;
case "rc-active-call-notify":
window.__rcActiveCall = e.data.call;
break;
case "rc-call-end-notify":
_logCallToOdoo(e.data.call);
break;
case "rc-adapter-syncPresence":
window.__rcPresence = {
dndStatus: e.data.dndStatus,
userStatus: e.data.userStatus,
telephonyStatus: e.data.telephonyStatus,
};
window.dispatchEvent(new CustomEvent("rc-presence-changed", {
detail: window.__rcPresence,
}));
break;
}
});
}
async function _logCallToOdoo(call) {
if (!call) return;
try {
await rpc("/web/dataset/call_kw", {
model: "rc.call.history",
method: "create",
args: [{
rc_session_id: String(call.sessionId || ""),
direction: (call.direction || "").toLowerCase() === "inbound" ? "inbound" : "outbound",
from_number: call.from || "",
to_number: call.to || "",
duration: call.duration || 0,
status: call.duration > 0 ? "answered" : "missed",
}],
kwargs: {},
});
} catch {
// Silently fail -- cron will catch it
}
}
registry.category("services").add("rc_phone_widget", {
start() {
loadRcWidget();
},
});

View File

@@ -0,0 +1,89 @@
/** @odoo-module **/
import { Component, useState, onMounted } from "@odoo/owl";
import { registry } from "@web/core/registry";
class RcSystrayItem extends Component {
static template = "fusion_ringcentral.SystrayItem";
static props = {};
setup() {
this.state = useState({
status: "offline",
visible: false,
});
onMounted(() => {
this._checkWidgetReady();
window.addEventListener("rc-presence-changed", (ev) => {
this._updatePresence(ev.detail);
});
});
}
_checkWidgetReady() {
const interval = setInterval(() => {
if (window.RCAdapter) {
this.state.visible = true;
clearInterval(interval);
}
}, 2000);
setTimeout(() => clearInterval(interval), 30000);
}
_updatePresence(presence) {
if (!presence) return;
this.state.visible = true;
const userStatus = (presence.userStatus || "").toLowerCase();
const dndStatus = (presence.dndStatus || "").toLowerCase();
if (dndStatus === "donotacceptanycalls") {
this.state.status = "dnd";
} else if (userStatus === "busy" || presence.telephonyStatus === "Ringing") {
this.state.status = "busy";
} else if (userStatus === "available") {
this.state.status = "available";
} else {
this.state.status = "offline";
}
}
get statusColor() {
const colors = {
available: "text-success",
busy: "text-danger",
dnd: "text-warning",
offline: "text-muted",
};
return colors[this.state.status] || "text-muted";
}
get statusTitle() {
const titles = {
available: "Available",
busy: "Busy",
dnd: "Do Not Disturb",
offline: "Offline",
};
return titles[this.state.status] || "RingCentral";
}
onClick() {
if (window.RCAdapter) {
const frame = document.querySelector("#rc-widget-adapter-frame");
if (frame) {
const isMinimized = frame.style.display === "none" ||
frame.closest("[style*='display: none']");
if (isMinimized) {
window.RCAdapter.setMinimized(false);
} else {
window.RCAdapter.setMinimized(true);
}
}
}
}
}
registry.category("systray").add("fusion_ringcentral.SystrayItem", {
Component: RcSystrayItem,
}, { sequence: 5 });

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<templates xml:space="preserve">
<t t-name="fusion_ringcentral.SystrayItem">
<div class="o_nav_entry" t-if="state.visible" t-on-click="onClick"
t-att-title="statusTitle" role="button" style="cursor: pointer;">
<i class="fa fa-phone fa-fw" t-att-class="statusColor"/>
</div>
</t>
</templates>