Developer forum

Forum » Integration » Import contacts from BC that are not associated with a customer

Import contacts from BC that are not associated with a customer

Roald Haahr
Reply

Hi,

We have a case where we need to import contacts from BC that are not associated with a customer. However, it seems that calling BC with Request 1 will only return contacts that are associated with a customer. Is it possible to extend the list returned from BC with these extra contacts, e.g. by changing a default filter in the codeunit's event subscriber OnBeforeGetContactsRequest? If that is possible, how is it done? If not, I guess we will have to expose in an OData-feed

Also, we tried to call Request 2, which returned data, but I cannot find any documentation on it. Is it safe to use or should we stick to Request 1?

Request 1
<GetEcomData><tables><Customers includeContacts="true" type="all" /></tables></GetEcomData>

Request 2
<GetEcomData><tables><Contacts type="all" /></tables></GetEcomData>

DW v9.15.15
Codeunit v1.2.0.36

Kind regards,
Roald


Replies

 
Dmitriy Benyuk Dynamicweb Employee
Dmitriy Benyuk
Reply
This post has been marked as an answer

Hi Roald,

both requests are  identical in terms of returning the Contacts, the only difference is that 
<GetEcomData><tables><Contacts type="all" /></tables></GetEcomData>
also exports the SalesPersons by default so maybe that is what you get in the response, so it is not empty.

Current api doesn't allow to skip this restriction for importing contacts without a customer association.

But you can use the OnBeforeGetContactsRequest event subscriber that is used for <GetEcomData><tables><Contacts type="all" /></tables></GetEcomData> request and implement your own way of getting and returning the contacts, so override ours functionality, if needed I can provide some sample code.

BR, Dmitrij

Votes for this answer: 1
 
Roald Haahr
Reply

Hi Dmitriy,

Alright, thank you for the clarification :)

Some sample code sounds great, so please provide that. Then I can send the BC partner in the right direction.

Kind regards,
Roald

 
Dmitriy Benyuk Dynamicweb Employee
Dmitriy Benyuk
Reply
This post has been marked as an answer

Here it is:

    [EventSubscriber(ObjectType::Codeunit, Codeunit::DynamicwebUsersPublisher, 'OnBeforeGetContactsRequest', '', true, true)]
    procedure OnBeforeGetContactsRequest(var requestDoc: XmlDocument; var stopExecution: Boolean; var responseRootNode: XmlNode);
    var
        pXmlElement: XmlElement;
    begin
        // Modify requestDoc request xml to your needs

        // Set stopExecution := true to cancel base application processing the request        
        // stopExecution := true;

        // If stopExecution set to true you can Set custom xml for the response output        
        // otherwise base app will append its output to responseDoc

        stopExecution := true;
        ProcessContactsRequest(responseRootNode);
    end;

    local procedure ProcessContactsRequest(var xmlRootNode: XmlNode)
    var
        contact: Record Contact;
        pXmlElement: XmlElement;        
        contactTableXmlNode: XmlNode;
        contactNode: XmlNode;
    begin        
        if (contact.FINDSET(false, false)) then begin            
            //add <table tableName='AccessUser'> xml node
            DynamicwebXmlHelper.AddElement(xmlRootNode, 'table', '', '', contactTableXmlNode);
            DynamicwebXmlHelper.AddAttribute(contactTableXmlNode, 'tableName', 'AccessUser');
            repeat
                DynamicwebXmlHelper.AddElement(contactTableXmlNode, 'item', '', '', contactNode);
                DynamicwebXmlHelper.AddAttribute(contactNode, 'table', 'AccessUser');                
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserUserName', Contact."E-Mail");
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserExternalID', Contact."No.");
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserName', Contact.Name);
                
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserEmail', Contact."E-Mail");
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserPhone', Contact."Phone No.");
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserFax', Contact."Fax No.");
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserAddress', Contact.Address);
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserAddress2', Contact."Address 2");
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserZip', Contact."Post Code");
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserCity', Contact.City);
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserCountry', Contact."Country/Region Code");
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserCountryCode', Contact."Country/Region Code");
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserAddressTitle', 'Contact address');
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserCompany', Contact."Company Name");
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserActive', 'true');
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserWeb', Contact."Home Page");
                DynamicwebXmlHelper.AddField(contactNode, 'AccessUserGroups', 'Customers');
            until contact.NEXT = 0;
        end;
    end;

Votes for this answer: 1
 
Roald Haahr
Reply

Thanks Dmitriy,

I will pass it on to the BC partner.

 
Matthias Sebastian Sort Dynamicweb Employee
Matthias Sebastian Sort
Reply

Hi Roald,

I am not completely sure why you need the Codeunit for fetching your contacts, as BC has a well defined API called OData, and we have a Provider where you can batch import/export everything from the OData API.

If you can settle with a batch job, then this could be a solution for you where you don't need to contact any BC partner, as you can do everything directly in Dynamicweb.

BC has a default API where you can fetch the "contact".. so you basically add another endpoint in your backend with this format https://api.businesscentral.dynamics.com/v2.0/{TENANTID}/{ENVIROMENT}/api/v2.0/companies({COMPANYID})/contacts you need to attach an authentication, so just reuse the one used for your Liveintegration.

Then you make an activity, where you use the OData Provider as source, select your endpoint under the "Predefined endpoint" and choose the "Mode" you like (it is explained under the options inside the dropdownlist), and for you destination you choose User Provider (or what ever you like)...

On your table mapping, from contacts to AccessUser (or where you want to map the contacts to), you add a conditional where companyNumber = (empty string)

BR

Matthias Sort

 
Roald Haahr
Reply

Hi Matthias,

Thank you for the suggestion. I just tested and I can indeed get the contacts I need from that endpoint. However, the contacts that we are getting from the codeunit right now has a bunch of custom fields that are calculated when the <Contacts> request is called, so we will have to involve the BC partner one way or the other.

Do you know if it is possible to add extra values to the contacts in the response from the OData endpoint? And if that is possible, which of the two ways, is the "right" way to do it?

Kind regards,
Roald

 
Rasmus Sanggaard Dynamicweb Employee
Rasmus Sanggaard
Reply

Hi Roald,

If you need custom fields or fields not present in the standard API, then you have to expose the page you want in "web services" in BC and use that specific v4 endpoint in DW.

We have an old video here showing you how to do it: https://doc.dynamicweb.com/documentation-9/integration/integration-framework/data-integration-activities/odata-provider-old/odata-v4-how-to - keep in mind that the end of the video if showing the old way of doing mappings in DW via the old OData provider :-)

So in BC/OData you don't expand with the columns you want, you will get all present in the endpoint via Metadata.  

 

BR, Rasmus Sanggaard 

 
Roald Haahr
Reply

Thank you all for the suggestions :) We have decided to go with the extension of OnBeforeGetContactsRequest in the codeunit.

On that node, Dmitriy, I know I asked how to get contacts not associated with a customer, but I would also like to have contacts associated with contacts in the feed, so how do we ensure that all contacts are in the feed, and how do we add <column columnName="AccessUserCustomerNumber"><![CDATA[]]></column> to the XML?

Kind regards,
Roald

 
Dmitriy Benyuk Dynamicweb Employee
Dmitriy Benyuk
Reply

Hi Roald,

For AccessUserCustomerNumber you can use the code like that for each of the contact in the loop:

var ContactBusiness: Record "Contact Business Relation";
 
                     ContactBusiness.SETRANGE("Contact No.", Contact."Company No.");
                    if ContactBusiness.FINDFIRST then
                         DynamicwebXmlHelper.AddField(contactNode, 'AccessUserCustomerNumber',  ContactBusiness."No.");

Not sure what contacts you want to get (associated with contacts in the feed is not clear for me). But you can use diffent filtering statements in the custom code.

BR, Dmitrij
 
Roald Haahr
Reply

Thank you Dmitriy,

I have obviously been to hasty in my request. What I meant to say is

"... I would also like to have contacts associated with customers in the returned XML"

 
Dmitriy Benyuk Dynamicweb Employee
Dmitriy Benyuk
Reply

Ok, in our Plugin code that filters out the contacts not associated to companies/customers this code is used:
                    ContactBusiness.SETRANGE("Contact No.", Contact."Company No.");
                    if ContactBusiness.FINDFIRST then
                        if Customer.GET(ContactBusiness."No.") then begin
                            if (Contact."Company Name" = Customer.Name) then
                                if CompanyContact.GET(Contact."Company No.") then begin

So it checks like this chain: Contact->"Contact Business Relation"->Customer, then also validates on name matching (Contact."Company Name" = Customer.Name).

So maybe for your setup this filter statement can be somehow simplified, smth like that:
                        if Customer.GET(Contact."Company No.") then
                               DynamicwebXmlHelper.AddField(contactNode, 'AccessUserCustomerNumber',  Customer."No.");
Or that:
                    ContactBusiness.SETRANGE("Contact No.", Contact."Company No.");
                    if ContactBusiness.FINDFIRST then
                        if Customer.GET(ContactBusiness."No.") then
                              DynamicwebXmlHelper.AddField(contactNode, 'AccessUserCustomerNumber',  Customer."No.");

 
Roald Haahr
Reply

Hi Dmitriy,

As the code is almost identical to the plugin code, can we request that it is made possible in the standard plugin. The ERP partner mentions that it will hard to manage with updates to the plugin. So he is requesting that we either get an extra event extension to add these contacts to the mix or that the logic is added directly to the plugin by you guys at Dynamicweb.

Say we send the request below, where we introduce a new parameter includeOrphanedContacts with false as the default value. Then the code in your plugin would generate the list based on that, including the orphaned contacts in the response. Here I am assuming that contacts not associated with a customer can be be described as orphans.

<GetEcomData Qty="100"><tables><Contacts type="all" importSalesPeople="false" includeOrphanedContacts="true" /></tables></GetEcomData>

Is that possible?

Kind regards,
Roald

 
Dmitriy Benyuk Dynamicweb Employee
Dmitriy Benyuk
Reply

Hi Roald,
I am not sure that this would be an option, since this looks like a new feature request. But I can suggest you to look towards the new approach using Odata instead.
So the idea is to publish your "custom" page that can expose the "Contacts" entities + calculated fields as a web service. Then you can map that new entity to Dynamicweb entity in the Data Integration job. Here is a sample code that makes a custom page for the customers attached, so similar can be done for the Contact page + its custom fields.
There is more information on how to make a page and expose it as a web service:
https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-page-object
https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/webservices/publish-web-service
https://demiliani.com/2019/06/12/dynamics-365-business-central-using-odata-v4-bound-actions/

BR, Dmitrij

 
Rasmus Sanggaard Dynamicweb Employee
Rasmus Sanggaard
Reply

Hi

Yes, we are not extending the code unit for now as the technology is in end of life circle. Before going all in and creating your own pages with code, I would try my approach and just exposing the Customer Card in Web Services as an OData V4 endpoint. I would think you will get everything you need.

BR, Rasmus Sanggaard

 
Roald Haahr
Reply

Hi Rasmus and Dmitriy,

Thank you for your suggestions. I will ask the ERP partner to look into it.

Should we be concerned about the technology being in end of life circle? Does this mean that have to replace the batch integrations on all of our Dynamicweb projects with OData integrations soon? And how should we approach batch integrations on new projects? As In should we avoid to use the kind of standard batch integrations that Dynamicweb offers and replace them with OData or something else?

Kind regards,
Roald

 
Rasmus Sanggaard Dynamicweb Employee
Rasmus Sanggaard
Reply

Hi,

We are going a bit off-topic here. If we should continue down the path of discussing SOAP vs. OData, then we should start a new thread.

But yes, SOAP is being deprecated by Microsoft: https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/webservices/soap-web-services

So, at some point in the future it will be necessary to switch to OData or something else. We are working on a Live Integration on OData, but for batch jobs, it is best to go OData now. The OData provider IS part of our standard batch integration and have been for some years now. 

BR, Rasmus Sanggaard

 

 

You must be logged in to post in the forum