Customer Relationship Management (CRM) software, now referred to more often as Customer Experience (CX) software, tends to be customized or extended more often than HCM, ERP, or SCM applications. The reason: enterprise business processes in the customer experience realm tend to be non-standard and more diverse across organizations. With wide diversity, it is impossible for CX software applications in their out-of-the-box (OOTB) states to support organizations’ sales support processes. OOTB for CX will seldom be workable for enterprises; more often it will be necessary to customize, extend, and integrate CX applications. Multi-faceted support for extensibility and integration has become a must-have set of features for vendors contending in the CX software space.
Oracle Sales Cloud (OSC) fully supports integrations at a number of levels with other Oracle cloud software products and with third-party software. Integration can be accomplished through API’s and/or through UI mash-ups, depending on requirements. Integration makes it possible for salespeople to access features, functions, and data in multiple products as if they were interacting with only one application. Integration, if done correctly, maximizes user productivity. Forcing users into context switches – requiring them to shell out of one application to look up a piece of data or go through a workflow in another application – lessens productivity. Sales representatives and managers are far too busy for a fragmented, unproductive user experience.
This post provides a blueprint for integrating Sales Cloud with Microsoft SharePoint, specifically around hooking SharePoint’s powerful document management functions into Sales Cloud business processes. For organizations with existing investments in both Sales Cloud and SharePoint, this integration can reap major benefits. If an organization has standardized on the SharePoint platform, it makes sense to leverage it in as many ways as possible. The goal of this integration is to allow users in sales roles to create and maintain a library of document artifacts linked to (and directly accessible from) Sales Cloud opportunities, accounts, contacts, leads, and other CX objects. Because Sales Cloud supports extending its native UI in addition to adding and modifying back-end processes, the integration will allow users to very efficiently create and populate SharePoint folders with document content as they are creating or editing Sales Cloud business objects such as opportunities or contacts. The obvious benefit is that the user experience becomes seamless across the two applications, versus the more cumbersome and error-prone approach of requiring a sales representative or manager to create an opportunity in Sales Cloud, and then log in to SharePoint to create a folder in the correct place and populate it with documents.
There are two or three components required to support this integration, depending on Sales Cloud version (pre- or post-Rel 11):
The remainder of this post will cover each of these three components in detail.
This integration was built, deployed, and tested with Microsoft SharePoint 2013. SharePoint 2013 exposes a rich set of API’s available for integration projects, notably in the area of document management. Only one example, creating a folder in a specified location, is covered here in detail. For details of other RESTful services, the most complete reference for the SharePoint 2013 REST API is here: https://msdn.microsoft.com/en-us/library/office/jj860569.aspx
There are a few lightweight setup tasks required to prepare the SharePoint server and target site for this integration with Sales Cloud:
For organizations that are using SharePoint, more often than not user credentials will be managed in Microsoft Active Directory (AD). With or without AD, specific enterprise guidelines can be followed when setting up these types of accounts. There are no additional special requirements other than making sure this user has full access to the SharePoint sales team site.
Most organizations set up team site locations in SharePoint for exclusive use by specific departments and/or business units. For this integration the assumption is that a team site has been set up for the Sales organization, and that this site will be the top-level SharePoint entry point for employees in the Sales organization. While it is possible to use the default "Documents" link for the Sales Cloud integration, this link supports only basic document management features. If enhanced features (versioning, sharing, co-authoring, check-in/out, local sync) are desired, it is possible to add the SharePoint Document Library application to the Sales team site. After this application has been added to the team site (it has been named “Sales Cloud Documents” in this example), the Document Library top-level page looks like this after four sub-folders for each of the supported Sales Cloud object types have been added to the document library structure:
When fully operational, the integration will programmatically create sub-folders under one of the four object type folders, depending on which object is active in the Sales Cloud UI. Users will be able to access and interact with content contained within this folder hierarchy either from SharePoint or from within Sales Cloud.
It is necessary to note the REST endpoints for each of these folder locations, as that location is passed in the request payload when creating subfolders for Sales Cloud objects as they are created. The format of this SharePoint site’s folders for each object (which will be dynamically constructed in Groovy script) is:
http://<hostname>/sales/_api/Web/GetFolderByServerRelativeUrl('/sales/Documents/' + <objecttype>)
Like other applications that are mindful of and protect against click-jacking exploits, the default behavior of SharePoint 2013 prevents SharePoint pages from being embedded in iFrames hosted by cross-domain applications. But, the default behavior can be circumvented if required (as it is for the integration with Sales Cloud). This workaround should of course be used with caution.
By default Sharepoint 2013 sends the X-Frame-Options: SAMEORIGIN header with its pages. This prevents client agents from embedding any SharePoint pages hosted in origins other than where SharePoint is being hosted. To override this default behavior site-wide, a directive needs to be added to one or more of the master page templates for the site. In SharePoint 2013, there are two active top-level master page templates:
The master page layout template can be changed in the Site Settings page for the site. The applicable link is “Change the Look” for changing from seattle to oslo or vice versa. This also exposes several CSS options for each template. The page edits necessary for both master page templates are identical:
<head runat="server"> <meta name="GENERATOR" content="Microsoft SharePoint" /> <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=10"/> <meta http-equiv="Expires" content="0" /> <SharePoint:SPPinnedSiteTile runat="server" TileUrl="/_layouts/15/images/SharePointMetroAppTile.png" TileColor="#0072C6" /> <SharePoint:RobotsMetaTag runat="server"/> <SharePoint:PageTitle runat="server"><asp:ContentPlaceHolder id="PlaceHolderPageTitle" runat="server"><SharePoint:ProjectProperty Property="Title" runat="server" /></asp:ContentPlaceHolder></SharePoint:PageTitle> <SharePoint:SPShortcutIcon runat="server" IconUrl="/_layouts/15/images/favicon.ico?rev=23" /> <SharePoint:StartScript runat="server" /> <SharePoint:CssLink runat="server" Version="15"/> <SharePoint:CacheManifestLink runat="server"/> <SharePoint:ScriptLink language="javascript" name="core.js" OnDemand="true" runat="server" Localizable="false" /> <SharePoint:ScriptLink language="javascript" name="menu.js" OnDemand="true" runat="server" Localizable="false" /> <SharePoint:ScriptLink language="javascript" name="callout.js" OnDemand="true" runat="server" Localizable="false" /> <SharePoint:ScriptLink language="javascript" name="sharing.js" OnDemand="true" runat="server" Localizable="false" /> <SharePoint:ScriptLink language="javascript" name="suitelinks.js" OnDemand="true" runat="server" Localizable="false" /> <SharePoint:CustomJSUrl runat="server" /> <SharePoint:SoapDiscoveryLink runat="server" /> <SharePoint:AjaxDelta id="DeltaPlaceHolderAdditionalPageHead" Container="false" runat="server"> <asp:ContentPlaceHolder id="PlaceHolderAdditionalPageHead" runat="server" /> <SharePoint:DelegateControl runat="server" ControlId="AdditionalPageHead" AllowMultipleControls="true" /> <asp:ContentPlaceHolder id="PlaceHolderBodyAreaClass" Visible="true" runat="server" /> </SharePoint:AjaxDelta> <SharePoint:CssRegistration Name="Themable/corev15.css" runat="server" /> <WebPartPages:AllowFraming runat=”server” /> </head>
Adding the “WebPartPages:AllowFraming” directive to the active master page template’s header (line 26 in the HTML code fragment above) tells SharePoint not to send out the “X-Frame-Options: SAMEORIGIN” header for any of the site’s content, which allows pages to be embedded in Sales Cloud without cross-origin-related issues.
In SharePoint 2013 there are a number of ways to modify a site’s page layout. In addition to modifying page templates and CSS, it is also possible to change specific pages. In general, for embedded content the best practice is to remove navigation, which dedicates all of the embedded page real estate to content. After all, navigation to Sales-Cloud-object-specific content is being done programmatically, so including it in embedded SharePoint pages would be redundant.
SharePoint also supports the concept of Device Channels, a means by which SharePoint can serve up content in alternative formats based on user device (browser or cell phone for example). Device Channels can also be keyed off of HTTP headers, if that is the desired behavior. This option theoretically could be leveraged to display content with navigation when users access the site from SharePoint, or without navigation when site content is embedded within another application.
Fortunately, there is an even simpler option to display pages without added chrome and navigation frames. Including the “isdlg=1” (case-sensitive in SharePoint 2013) query parameter in the page URL will direct SharePoint to serve up content only, which is exactly the desired behavior for this integration.
NOTE: the wrapper application is only required for pre-Rel12 versions of Sales Cloud. Details are included here for legacy purposes and to provide this as a generic example of how to build such an application for other purposes. This specific application becomes extraneous as Sales Cloud instances are upgraded to Release 12 and later, when support for outgoing calls to REST resources becomes available. With one fewer component, obviously the integration will become much simpler and easier to deploy and maintain.
The wrapper application, developed in Oracle’s Application Development Framework (ADF), is an example of how to leverage JAX-WS annotations and the Jersey library reference implementation to make quick work of translating SOAP calls to REST calls, and vice versa. It is structured as a single ADF project; there is no need for a UI or business components project, nor is there a need for the typical ADF multi-project MVC application.
There are three different types of Java classes in the application: a SOAP service class, a REST client class, and Plain Old Java Objects (POJO’s) to support JSON to XML transformation and vice versa. The general flow is perhaps best described by a simple diagram:
The workhorse Java class is the JAX-WS-annotated SOAP web service, “SharepointSoapSvc”:
package oracle.sample.jcssx.sharepoint.service; import java.io.IOException; import javax.jws.WebMethod; import javax.jws.WebService; import oracle.sample.jcssx.sharepoint.pojo.AddFolderResp; import oracle.sample.jcssx.sharepoint.pojo.FileResultsResponse; import oracle.sample.jcssx.sharepoint.util.RestClient; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.map.JsonMappingException; import org.codehaus.jackson.map.ObjectMapper; @WebService(serviceName = "SharepointSoapSvc") public class SharepointSoapSvc { private RestClient rc; public SharepointSoapSvc() { super(); rc = new RestClient(); } private Object parseRestResp(String resp, Class<?> classX) { System.out.println("parseRestResp: resp input parm is " + resp); ObjectMapper mapper = new ObjectMapper(); Object obj = null; try { obj = mapper.readValue(resp, classX); } catch (JsonParseException jpe) { jpe.printStackTrace(); } catch (JsonMappingException jme) { jme.printStackTrace(); } catch (IOException ioe) { ioe.printStackTrace(); } return obj; } @WebMethod() public FileResultsResponse getDocs() { String rsp = rc.getDocs(); return (FileResultsResponse) parseRestResp(rsp, FileResultsResponse.class); } @WebMethod public AddFolderResp addFolder(String folderName) { String rsp = rc.addFolder(folderName); return (AddFolderResp) parseRestResp(rsp, AddFolderResp.class); } }
The “@WebService” annotation exposes the SharepointSoapSvc class as a SOAP JAX-WS service. Within the service there are two annotated @WebMethods, getDocs() and addFolder(). The exposed addFolder() method is the one called by Sales Cloud to add a folder to the SharePoint repository. A passed-in String parameter tells SharePoint (eventually, as we will see) what name to attach to the created folder. (The getDoc() method is not an active part of the current basic integration covered in this post, but is included here for illustrative purposes.)
The exposed WebMethods complete their job by transforming the responses returned by the REST calls into SOAP-compatible formats that can be interpreted correctly by the calling application.
One topic that deserves detailed attention is how Jackson libraries are leveraged to transform JSON-formatted REST responses to SOAP-compatible XML formats for consumption by the application calling the SOAP web service. Here is the JSON format for the SharePoint REST response to the addFolder request:
{ "d": { "__metadata": { "id": "http://<SP Host Name>/sales/_api/Web/GetFolderByServerRelativeUrl('/sales/Sales Cloud Documents/Opportunities/TestRest007')", "uri": "http://<SP Host Name>/sales/_api/Web/GetFolderByServerRelativeUrl('/sales/Sales%20Cloud%20Documents/Opportunities/TestRest007')", "type": "SP.Folder" }, "Files": { "__deferred": { "uri": "http://<SP Host Name>/sales/_api/Web/GetFolderByServerRelativeUrl('/sales/Sales%20Cloud%20Documents/Opportunities/TestRest007')/Files" } }, "ListItemAllFields": { "__deferred": { "uri": "http://<SP Host Name>/sales/_api/Web/GetFolderByServerRelativeUrl('/sales/Sales%20Cloud%20Documents/Opportunities/TestRest007')/ListItemAllFields" } }, "ParentFolder": { "__deferred": { "uri": "http://<SP Host Name>/sales/_api/Web/GetFolderByServerRelativeUrl('/sales/Sales%20Cloud%20Documents/Opportunities/TestRest007')/ParentFolder" } }, "Properties": { "__deferred": { "uri": "http://<SP Host Name>/sales/_api/Web/GetFolderByServerRelativeUrl('/sales/Sales%20Cloud%20Documents/Opportunities/TestRest007')/Properties" } }, "Folders": { "__deferred": { "uri": "http://<SP Host Name>/sales/_api/Web/GetFolderByServerRelativeUrl('/sales/Sales%20Cloud%20Documents/Opportunities/TestRest007')/Folders" } }, "ItemCount": 0, "Name": "TestRest007", "ServerRelativeUrl": "/sales/Sales Cloud Documents/Opportunities/TestRest007", "WelcomePage": "" } }
Note that the addFolder method returns an object with a custom complex class type of “AddFolderResp”. This POJO class, and a class upon which it depends, “AddFolderBody”, is structured to reflect the format the JSON response getting returned by the SharePoint REST call:
package oracle.sample.jcssx.sharepoint.pojo; public class AddFolderResp { private AddFolderBody d; public AddFolderResp() { super(); } public void setD(AddFolderBody d) { this.d = d; } public AddFolderBody getD() { return d; } }
Here is the AddFolderBody class:
package oracle.sample.jcssx.sharepoint.pojo; import org.codehaus.jackson.annotate.JsonProperty; public class AddFolderBody { @JsonProperty("__metadata") private Metadata metadata; @JsonProperty("Files") private DeferredUri files; @JsonProperty("ListItemAllFields") private DeferredUri listItemAllFields; @JsonProperty("ParentFolder") private DeferredUri parentFolder; @JsonProperty("Properties") private DeferredUri properties; @JsonProperty("Folders") private DeferredUri folders; @JsonProperty("ItemCount") private Integer itemCount; @JsonProperty("Name") private String name; @JsonProperty("ServerRelativeUrl") private String serverRelativeUrl; @JsonProperty("WelcomePage") private String welcomePage; public AddFolderBody() { super(); } @JsonProperty("__metadata") public void setMetadata(Metadata metadata) { this.metadata = metadata; } @JsonProperty("__metadata") public Metadata getMetadata() { return metadata; } @JsonProperty("Files") public void setFiles(DeferredUri files) { this.files = files; } @JsonProperty("Files") public DeferredUri getFiles() { return files; } @JsonProperty("ListItemAllFields") public void setListItemAllFields(DeferredUri listItemAllFields) { this.listItemAllFields = listItemAllFields; } @JsonProperty("ListItemAllFields") public DeferredUri getListItemAllFields() { return listItemAllFields; } @JsonProperty("ParentFolder") public void setParentFolder(DeferredUri parentFolder) { this.parentFolder = parentFolder; } @JsonProperty("ParentFolder") public DeferredUri getParentFolder() { return parentFolder; } @JsonProperty("Properties") public void setProperties(DeferredUri properties) { this.properties = properties; } @JsonProperty("Properties") public DeferredUri getProperties() { return properties; } @JsonProperty("Folders") public void setFolders(DeferredUri folders) { this.folders = folders; } @JsonProperty("Folders") public DeferredUri getFolders() { return folders; } @JsonProperty("ItemCount") public void setItemCount(Integer itemCount) { this.itemCount = itemCount; } @JsonProperty("ItemCount") public Integer getItemCount() { return itemCount; } @JsonProperty("Name") public void setName(String name) { this.name = name; } @JsonProperty("Name") public String getName() { return name; } @JsonProperty("ServerRelativeUrl") public void setServerRelativeUrl(String serverRelativeUrl) { this.serverRelativeUrl = serverRelativeUrl; } @JsonProperty("ServerRelativeUrl") public String getServerRelativeUrl() { return serverRelativeUrl; } @JsonProperty("WelcomePage") public void setWelcomePage(String welcomePage) { this.welcomePage = welcomePage; } @JsonProperty("WelcomePage") public String getWelcomePage() { return welcomePage; } }
The JsonProperty annotations in the class work as hints to the Jackson parsing engine. Not all of them are required in all cases; they are included here by way of example. At any rate, detailed discussion of the Jackson libraries is somewhat out of scope here. For more information, refer to: https://github.com/FasterXML/jackson-docs
The exposed SOAP methods in the SharepointSoapSvc class call into methods contained in the REST client class “RestClient”:
package oracle.sample.jcssx.sharepoint.util; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; import com.sun.jersey.api.client.filter.LoggingFilter; import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; import com.sun.jersey.core.util.MultivaluedMapImpl; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Properties; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import oracle.sample.jcssx.sharepoint.pojo.GetContextWebInformationResp; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.jaxrs.JacksonJsonProvider; import org.codehaus.jackson.map.JsonMappingException; import org.codehaus.jackson.map.ObjectMapper; public class RestClient { private Properties props = Config.getProperties(); private Client client; private static final String URIEXT = "/_api/Web"; private static final String URIDOCS = "/GetFolderByServerRelativeUrl('Documents')/Files"; private static final String URIFOLDERADD = "/Folders/add"; public RestClient() { super(); ClientConfig cc = new DefaultClientConfig(); cc.getClasses().add(JacksonJsonProvider.class); client = Client.create(cc); HTTPBasicAuthFilter authFilter = new HTTPBasicAuthFilter(props.getProperty("user1name"), props.getProperty("user1pw")); client.addFilter(authFilter); } public String getXRequestDigestValue() { ObjectMapper mapper = new ObjectMapper(); GetContextWebInformationResp resp = null; WebResource resource = client.resource(props.getProperty("SharePointHost") + "/_api/contextinfo"); resource.header("Accept", "application/json; odata=verbose"); String response = resource .accept(MediaType.APPLICATION_JSON_TYPE) .header("Accept", "application/json; odata=verbose") .header("Content-Length", 0) .type(MediaType.APPLICATION_JSON_TYPE) .post(String.class); System.out.println("String contextinfo resp: " + response); try { resp = mapper.readValue(response, GetContextWebInformationResp.class); } catch (JsonParseException jpe) { jpe.printStackTrace(); } catch (JsonMappingException jme) { jme.printStackTrace(); } catch (IOException ioe) { ioe.printStackTrace(); } System.out.println("FormDigestValue: " + resp.getD().getGetContextWebInformation().getFormDigestValue()); return resp.getD().getGetContextWebInformation().getFormDigestValue(); } public String getDocs() { WebResource resource = client.resource(props.getProperty("SharePointHost") + "/" + props.getProperty("SharePointSite") + URIEXT + URIDOCS); String response = resource .accept(MediaType.APPLICATION_JSON_TYPE) .header("Accept", "application/json; odata=verbose") .type(MediaType.APPLICATION_JSON_TYPE) .get(String.class); return response; } public String addFolder(String folderRef) { String xRequestDigest = getXRequestDigestValue(); WebResource resource = client.resource(props.getProperty("SharePointHost") + "/" + props.getProperty("SharePointSite") + URIEXT + URIFOLDERADD + "('" + folderRef + "')"); String response = resource .accept(MediaType.APPLICATION_JSON_TYPE) .header("Accept", "application/json; odata=verbose") .header("X-RequestDigest", xRequestDigest) .header("Content-Length", 0) .type(MediaType.APPLICATION_JSON_TYPE) .post(String.class); return response; } }
The class constructor creates a Jersey REST client object, adding a JacksonJsonProvider class to the client config for parsing convenience. A BASIC authentication filter is also added to the client. In this example, a super-user or admin-user authentication pattern is used: hard-coded user credentials are kept in a configuration utility and are passed in to the authentication filter. (A future blog post will explore alternatives to this super-user BASIC authentication pattern.)
Other methods in the RestClient class follow the typical Jersey REST client implementation, more or less. A WebResource is instantiated, utilizing any context-specific passed-in parameters to build a URI compatible with the SharePoint REST service specifications. With the WebResource instantiated, Jersey GET or POST and related methods for the resource initially add required/optional header data, and then make the REST requests. Responses from the REST calls are harvested and get returned to the SOAP service methods that called into the REST client methods.
There is one wrinkle specific to and required by SharePoint in the code flow of the RestClient methods called by the SOAP service. For authentication purposes, SharePoint 2013 requires an “X-RequestDigest” header value be added to incoming REST calls. This requirement is handled by the “getXRequestDigestValue()” method (see above code sample). Microsoft documentation for this header value is located here: https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.webcontrols.formdigest.aspx
The Sales Cloud Rel 11 development tasks to build what is covered in the SharePoint integration are summarized here:
println 'Entering CreateSharepointFolder Global Function' def folderPrefix = 'Sales%20Cloud%20Documents/' + objectType + '/' def objFolder = objectName.replaceAll(' ', '') def respMap = adf.webServices.SharepointRestWrapper.addFolder(folderPrefix + objFolder) println 'Response: ' + respMap println 'Exiting CreateSharepointFolder Global Function'
println 'Entering Sharepoint subtab URL definition script' def url = 'https://slc05mgq.us.oracle.com/oscdocs/Documents/Forms/SalesCloudAllItems.aspx?isdlg=1&RootFolder=' def folderPrefix = '%2Fsales%2FSales%20Cloud%20Documents%2F' + objectType + '%2F' def objFolder = objectName.replaceAll(' ', '') url += folderPrefix url += objFolder println 'URL for this object\'s Sharepoint folder is ' + url return url
adf.util.createSharePointFolderSOAP('Opportunities', Name)
return adf.util.getSharePointFolderUrl('Opportunities', Name)
The result of the integration for a test opportunity named ‘TestFromSalesCloudSOAP’ (after the opportunity is created) looks like this when the custom SharePoint subtab is selected:
This covers all of the required steps in Application Composer for building the SharePoint integration features. More detailed information on Sales Cloud Application Composer is available here: http://docs.oracle.com/cloud/latest/salescs_gs/OACEX/OACEX.pdf
As mentioned earlier, with Release 12 of Sales Cloud it is now possible to make outgoing calls to external REST services directly instead of having to develop, deploy, and integrate a go-between SOAP-to-REST wrapper application.
Most of the logic for implementing the REST version of the Sales Cloud side of the integration is similar to the SOAP implementation summarized above. But there is work unique to Rel 12 to construct the REST endpoints dynamically in Groovy script, and also to add the X-RequestHeader header that is required for SharePoint to recognize and process REST POST requests.
// straight REST implementation def xReq = adf.webServices.SharepointRestApiContextInfoXML def xReq1 = adf.webServices.SharepointRestApiAddFolder3 try { def xResp = xReq.POST() println ('Response: ' + xResp) def digestVal = xResp.FormDigestValue println ('xResp.FormDigestValue: ' + digestVal) def httpHdrs1 = ['X-RequestDigest':digestVal,'Content-Encoding':'gzip','Vary':'Accept-Encoding'] def FolderRef = '\'/sales/OracleSalesCloudDocs/' + objectType + '/' + objectName + '\'' println ('FolderRef: ' + FolderRef) xReq1.requestHTTPHeaders = httpHdrs1 def xResp1 = xReq1.POST(FolderRef, '') println ( "Req Headers: " + xReq1.requestHTTPHeaders) println ('xResp1 Response: ' + xResp1) } catch (Exception e) { println ( "Headers:" + xReq1.responseHTTPHeaders) println ( "-----------------------------------------------------") println ( "Status:" + xReq1.statusCode) println ( "-----------------------------------------------------") println ( "Error:" + e) println ( "-----------------------------------------------------") }
This function makes an initial call to the REST contextInfo endpoint to retrieve the X-RequestDigest value. When that step completes, the key/value pair is added to the HTTP header for the second REST call, which makes the REST call to create a SharePoint folder specific to the active object type and object name.
Sales support processes normally generate a lot of documents at various stages. If salespeople are given faster and more efficient means of maintaining sales-related documentation, everyone wins. Organizations benefit when salespeople can maintain libraries of supporting documents for various workflows as they are involved in the workflows. By integrating Oracle Sales Cloud and Microsoft SharePoint to support sales and customer relationship processes, enterprises can realize additional value from both software applications. This value comes in the form of giving sales representatives and sales managers a more efficient, simpler, and less error-prone means of managing documents that need to be tied to business objects, whether they be sales opportunities, accounts, contacts, or activities in Oracle Sales Cloud.
Previous Post
Next Post