Skip to content

Commit

Permalink
Merge branch 'main' into AdditionalFieldDefaultValueSetting
Browse files Browse the repository at this point in the history
  • Loading branch information
DaKingKong committed Oct 23, 2024
2 parents 05f33d6 + 636d0ac commit 144ff9e
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 28 deletions.
12 changes: 12 additions & 0 deletions docs/crm/netsuite.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ Installing the app from the SuiteApp marketplace is a necessary step prior to in

Upon installation of the SuiteApp, a role will be created automatically within your account called "RingCentral Unified CRM Extension." This role has been specially tuned to contain the bare minimum number access level to fully operate the Chrome extension. You can see a detailed list of permissions below if you would like to incorporate these permissions into a custom role of your own.

### For Non-OneWorld License Account Users:
1. Upon installing the SuiteApp from the Marketplace, a new custom role named **"RingCentral Unified CRM Extension"** will be automatically created.
2. This custom role enables Non-OneWorld license account users to access features such as call logs, message logs, and more.

### For OneWorld License Account Users:
1. Upon installing the SuiteApp from the Marketplace, a new custom role named **"RingCentral Unified CRM Extension"** will be automatically created.
2. Users must create a clone of this role by selecting the "Customize" option.
3. While cloning, add the permission **"List -> Subsidiaries"** to the cloned role.
4. The cloned role can now be used to access features such as call logs, message logs, and other functionality.

Note: The role is created by SuiteApp locked by default and cannot be edited. If you wish to make modifications, you may duplicate the role by clicking on "Customize."

### Turn on REST web services

REST Web Services is default to be invisible. We'll need to turn on the feature so to make it selectable for roles. Here's how to do it:
Expand Down
51 changes: 44 additions & 7 deletions src/adapters/bullhorn/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const axios = require('axios');
const moment = require('moment');
const { parsePhoneNumber } = require('awesome-phonenumber');
const { UserModel } = require('../../models/userModel');

function getAuthType() {
return 'oauth';
Expand All @@ -15,13 +16,48 @@ async function getOauthInfo({ tokenUrl }) {
}
}

async function tempMigrateUserId({ existingUser }) {
const bhRestToken = existingUser.platformAdditionalInfo.bhRestToken;
const existingUserId = existingUser.id.replace('-bullhorn', '');
let userInfoResponse
try {
userInfoResponse = await axios.get(`${existingUser.platformAdditionalInfo.restUrl}query/CorporateUser?fields=id,masterUserID&BhRestToken=${bhRestToken}&where=id=${existingUserId}`);
}
catch (e) {
if (isAuthError(e.response.status)) {
existingUser = await refreshSessionToken(existingUser);
userInfoResponse = await axios.get(`${existingUser.platformAdditionalInfo.restUrl}query/CorporateUser?fields=id,masterUserID&BhRestToken=${bhRestToken}&where=id=${existingUserId}`);
}
}
const newUserId = userInfoResponse.data.data[0]?.masterUserID;
if(!!!newUserId){
return null;
}
const newUser = await UserModel.create({
id: `${newUserId}-bullhorn`,
hostname: existingUser.hostname,
timezoneName: existingUser.timezoneName,
timezoneOffset: existingUser.timezoneOffset,
platform: existingUser.platform,
accessToken: existingUser.accessToken,
refreshToken: existingUser.refreshToken,
tokenExpiry: existingUser.tokenExpiry,
platformAdditionalInfo: existingUser.platformAdditionalInfo
});
await existingUser.destroy();
return {
id: newUser.id,
name: userInfoResponse.data.name
}
}

async function getUserInfo({ authHeader, tokenUrl, apiUrl, username }) {
try {
const userLoginResponse = await axios.post(`${apiUrl}/login?version=2.0&access_token=${authHeader.split('Bearer ')[1]}`);
const { BhRestToken: bhRestToken, restUrl } = userLoginResponse.data;
const userInfoResponse = await axios.get(`${restUrl}query/CorporateUser?fields=id,name,timeZoneOffsetEST&BhRestToken=${bhRestToken}&where=username='${username}'`);
const userInfoResponse = await axios.get(`${restUrl}query/CorporateUser?fields=id,name,timeZoneOffsetEST,masterUserID&BhRestToken=${bhRestToken}&where=username='${username}'`);
const userData = userInfoResponse.data.data[0];
const id = `${userData.id.toString()}-bullhorn`;
const id = `${userData.masterUserID.toString()}-bullhorn`;
const name = userData.name;
const timezoneOffset = userData.timeZoneOffsetEST - 5 * 60;
const timezoneName = '';
Expand Down Expand Up @@ -371,7 +407,7 @@ async function createMessageLog({ user, contactInfo, authHeader, message, additi
const noteActions = additionalSubmission.noteActions ?? '';
let userInfoResponse;
try {
userInfoResponse = await axios.get(`${user.platformAdditionalInfo.restUrl}query/CorporateUser?fields=id,name&where=id=${user.id.replace('-bullhorn', '')}`,
userInfoResponse = await axios.get(`${user.platformAdditionalInfo.restUrl}query/CorporateUser?fields=id,name&where=masterUserID=${user.id.replace('-bullhorn', '')}`,
{
headers: {
BhRestToken: user.platformAdditionalInfo.bhRestToken
Expand All @@ -381,7 +417,7 @@ async function createMessageLog({ user, contactInfo, authHeader, message, additi
catch (e) {
if (isAuthError(e.response.status)) {
user = await refreshSessionToken(user);
userInfoResponse = await axios.get(`${user.platformAdditionalInfo.restUrl}query/CorporateUser?fields=id,name&where=id=${user.id.replace('-bullhorn', '')}`,
userInfoResponse = await axios.get(`${user.platformAdditionalInfo.restUrl}query/CorporateUser?fields=id,name&where=masterUserID=${user.id.replace('-bullhorn', '')}`,
{
headers: {
BhRestToken: user.platformAdditionalInfo.bhRestToken
Expand Down Expand Up @@ -456,7 +492,7 @@ async function updateMessageLog({ user, contactInfo, existingMessageLog, message
const existingLogId = existingMessageLog.thirdPartyLogId;
let userInfoResponse;
try {
userInfoResponse = await axios.get(`${user.platformAdditionalInfo.restUrl}query/CorporateUser?fields=id,name&where=id=${user.id.replace('-bullhorn', '')}`,
userInfoResponse = await axios.get(`${user.platformAdditionalInfo.restUrl}query/CorporateUser?fields=id,name&where=masterUserID=${user.id.replace('-bullhorn', '')}`,
{
headers: {
BhRestToken: user.platformAdditionalInfo.bhRestToken
Expand All @@ -466,7 +502,7 @@ async function updateMessageLog({ user, contactInfo, existingMessageLog, message
catch (e) {
if (isAuthError(e.response.status)) {
user = await refreshSessionToken(user);
userInfoResponse = await axios.get(`${user.platformAdditionalInfo.restUrl}query/CorporateUser?fields=id,name&where=id=${user.id.replace('-bullhorn', '')}`,
userInfoResponse = await axios.get(`${user.platformAdditionalInfo.restUrl}query/CorporateUser?fields=id,name&where=masterUserID=${user.id.replace('-bullhorn', '')}`,
{
headers: {
BhRestToken: user.platformAdditionalInfo.bhRestToken
Expand Down Expand Up @@ -580,4 +616,5 @@ exports.updateMessageLog = updateMessageLog;
exports.getCallLog = getCallLog;
exports.findContact = findContact;
exports.createContact = createContact;
exports.unAuthorize = unAuthorize;
exports.unAuthorize = unAuthorize;
exports.tempMigrateUserId = tempMigrateUserId;
2 changes: 1 addition & 1 deletion src/adapters/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@
}
},
"platform": "netsuite",
"contactPageUrl": "https://{hostname}/app/common/entity/{contactType}.nl?id={contactId}",
"contactPageUrl": "https://{hostname}/app/common/entity/{contactType}.nl?id={contactId}&source=ringcentral",
"logPageUrl": "https://{hostname}/app/crm/calendar/call.nl?id={logId}",
"canOpenLogPage": true,
"contactTypes": [
Expand Down
68 changes: 49 additions & 19 deletions src/adapters/netsuite/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ async function getUserInfo({ authHeader, additionalInfo, query }) {
const timezoneOffset = employeResponse.data.time_zone_offset ?? null;
const location = employeResponse.data.location ?? '';
const subsidiaryId = employeResponse.data.subsidiary?.id ?? '';
let oneWorldEnabled;
try {
const checkOneWorldLicenseUrl = `https://${query.hostname.split(".")[0]}.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=customscript_getoneworldlicense_scriptid&deploy=customdeploy_getoneworldlicense_deployid`;
const oneWorldLicenseResponse = await axios.get(checkOneWorldLicenseUrl, {
headers: { 'Authorization': authHeader }
});
oneWorldEnabled = oneWorldLicenseResponse?.data?.oneWorldEnabled;
} catch (e) {
console.log({ message: "Error in getting OneWorldLicense" });
if (subsidiaryId !== undefined && subsidiaryId !== '') {
oneWorldEnabled = true;
}
}
return {
successful: true,
platformUserInfo: {
Expand All @@ -44,6 +57,7 @@ async function getUserInfo({ authHeader, additionalInfo, query }) {
email: employeResponse.data.email,
name: name,
subsidiaryId,
oneWorldEnabled: oneWorldEnabled,
},

},
Expand Down Expand Up @@ -177,16 +191,23 @@ async function findContact({ user, authHeader, phoneNumber, overridingFormat })
async function createCallLog({ user, contactInfo, authHeader, callLog, note, additionalSubmission }) {
try {
const title = callLog.customSubject ?? `${callLog.direction} Call ${callLog.direction === 'Outbound' ? 'to' : 'from'} ${contactInfo.name}`;
const subsidiary = await axios.post(
`https://${user.hostname.split(".")[0]}.suitetalk.api.netsuite.com/services/rest/query/v1/suiteql`,
{
q: `SELECT * FROM Subsidiary WHERE id = ${user?.platformAdditionalInfo?.subsidiaryId}`
},
{
headers: { 'Authorization': authHeader, 'Content-Type': 'application/json', 'Prefer': 'transient' }
});
const timeZone = getTimeZone(subsidiary.data.items[0]?.country, subsidiary.data.items[0]?.state);
const callStartTime = moment(moment(callLog.startTime).toISOString()).tz(timeZone);
const oneWorldEnabled = user?.platformAdditionalInfo?.oneWorldEnabled;
let callStartTime = moment(moment(callLog.startTime).toISOString());
/**
* Users without a OneWorld license do not have access to subsidiaries.
*/
if (oneWorldEnabled !== undefined && oneWorldEnabled === true) {
const subsidiary = await axios.post(
`https://${user.hostname.split(".")[0]}.suitetalk.api.netsuite.com/services/rest/query/v1/suiteql`,
{
q: `SELECT * FROM Subsidiary WHERE id = ${user?.platformAdditionalInfo?.subsidiaryId}`
},
{
headers: { 'Authorization': authHeader, 'Content-Type': 'application/json', 'Prefer': 'transient' }
});
const timeZone = getTimeZone(subsidiary.data.items[0]?.country, subsidiary.data.items[0]?.state);
callStartTime = moment(moment(callLog.startTime).toISOString()).tz(timeZone);
}
const callEndTime = moment(callStartTime).add(callLog.duration, 'seconds');
const formatedStartTime = callStartTime.format('YYYY-MM-DD HH:mm:ss');
const formatedEndTime = callEndTime.format('YYYY-MM-DD HH:mm:ss');
Expand Down Expand Up @@ -486,6 +507,8 @@ async function createContact({ user, authHeader, phoneNumber, newContactName, ne
try {
const nameParts = splitName(newContactName);
let contactId = 0;
const subsidiaryId = user.platformAdditionalInfo?.subsidiaryId;
const oneWorldEnabled = user?.platformAdditionalInfo?.oneWorldEnabled;
switch (newContactType) {
case 'contact':
let companyId = 0;
Expand All @@ -503,12 +526,15 @@ async function createContact({ user, authHeader, phoneNumber, newContactName, ne
companyId = companyInfo.data.items[0].id;
}
else {
let companyPostBody = {
companyName: 'RingCentral_CRM_Extension_Placeholder_Company',
comments: "This company was created automatically by the RingCentral Unified CRM Extension. Feel free to edit, or associate this company's contacts to more appropriate records.",
};
if (oneWorldEnabled !== undefined && oneWorldEnabled === true) {
companyPostBody.subsidiary = { id: subsidiaryId };
}
const createCompany = await axios.post(`https://${user.hostname.split(".")[0]}.suitetalk.api.netsuite.com/services/rest/record/v1/customer`,
{
companyName: 'RingCentral_CRM_Extension_Placeholder_Company',
comments: "This company was created automatically by the RingCentral Unified CRM Extension. Feel free to edit, or associate this company's contacts to more appropriate records.",
subsidiary: { id: user.platformAdditionalInfo?.subsidiaryId }
}
companyPostBody
,
{
headers: { 'Authorization': authHeader, 'Content-Type': 'application/json' }
Expand All @@ -520,9 +546,11 @@ async function createContact({ user, authHeader, phoneNumber, newContactName, ne
middleName: nameParts.middleName,
lastName: nameParts.lastName,
phone: phoneNumber || '',
company: { id: companyId },
subsidiary: { id: user.platformAdditionalInfo?.subsidiaryId }
company: { id: companyId }
};
if (oneWorldEnabled !== undefined && oneWorldEnabled === true) {
contactPayLoad.subsidiary = { id: subsidiaryId };
}
const createContactRes = await axios.post(
`https://${user.hostname.split(".")[0]}.suitetalk.api.netsuite.com/services/rest/record/v1/contact`,
contactPayLoad
Expand Down Expand Up @@ -551,10 +579,12 @@ async function createContact({ user, authHeader, phoneNumber, newContactName, ne
middleName: nameParts.middleName,
lastName: nameParts.lastName.length > 0 ? nameParts.lastName : nameParts.firstName,
phone: phoneNumber || '',
isPerson: true,
subsidiary: { id: user.platformAdditionalInfo?.subsidiaryId }
isPerson: true

};
if (oneWorldEnabled !== undefined && oneWorldEnabled === true) {
customerPayLoad.subsidiary = { id: subsidiaryId };
}
try {
const createCustomerRes = await axios.post(
`https://${user.hostname.split(".")[0]}.suitetalk.api.netsuite.com/services/rest/record/v1/customer`,
Expand Down
18 changes: 17 additions & 1 deletion src/core/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@ const oauth = require('../lib/oauth');
const { UserModel } = require('../models/userModel');
const Op = require('sequelize').Op;

async function tempMigrateBullhornUserId({ oldUserId }) {
const existingUser = await UserModel.findOne({
where: {
id: oldUserId,
platform: 'bullhorn'
}
});
if (existingUser) {
const platformModule = require(`../adapters/bullhorn`);
const newUserInfo = await platformModule.tempMigrateUserId({ existingUser });
return newUserInfo;
}
return null;
}

async function onOAuthCallback({ platform, hostname, tokenUrl, callbackUri, apiUrl, username, query }) {
const platformModule = require(`../adapters/${platform}`);
const oauthInfo = await platformModule.getOauthInfo({ tokenUrl, hostname, rcAccountId: query.rcAccountId });
Expand Down Expand Up @@ -123,4 +138,5 @@ async function saveUserInfo({ platformUserInfo, platform, hostname, accessToken,
}

exports.onOAuthCallback = onOAuthCallback;
exports.onApiKeyLogin = onApiKeyLogin;
exports.onApiKeyLogin = onApiKeyLogin;
exports.tempMigrateBullhornUserId = tempMigrateBullhornUserId;
30 changes: 30 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,36 @@ app.get('/hostname', async function (req, res) {
res.status(500).send(e);
}
})

app.get('/temp-bullhorn-migrate-userId', async function (req, res) {
try {
const jwtToken = req.query.jwtToken;
if (!!jwtToken) {
const { id: userId, platform } = jwt.decodeJwt(jwtToken);
const userInfo = await authCore.tempMigrateBullhornUserId({ oldUserId: userId });
if (!!userInfo) {
const jwtToken = jwt.generateJwt({
id: userInfo.id.toString(),
platform: platform
});
res.status(200).send({ jwtToken, name: userInfo.name });
}
else {
res.status(200).send();
}
}
else {
res.status(400).send('Please go to Settings and authorize CRM platform');
success = false;
}
console.log('Event: bullhorn user id migrate')
}
catch (e) {
console.log(`platform: bullhorn \n${e.stack}`);
res.status(400).send(e);
}
})

app.get('/oauth-callback', async function (req, res) {
const requestStartTime = new Date().getTime();
let platformName = null;
Expand Down

0 comments on commit 144ff9e

Please sign in to comment.