IntelligentDB
IntelligentDB

Reputation: 13

Get Contact(s) in Acumatica via RESTful API

@HB_ACUMATICA, et al,

I've been working on integrating a client's FileMaker database with Acumatica for the past few months. I'm able to access (get/put) various tables (entities), such as PROJECT and CUSTOMER, but the CONTACT entity always produces an error. For example,

https://mydomain.acumatica.com/entity/Default/18.200.001/Customer [works fine]

https://mydomain.acumatica.com/entity/Default/18.200.001/Contact [always an error 500]

**[EDIT: The above examples are, of course, incomplete, unless trying to find 'all' Contact records. In testing, I was specifying actual Contact IDs, as in,

https://mydomain.acumatica.com/entity/Default/18.200.001/Customer/Nobody

where 'Nobody' is a real Contact ID... or so I was led to believe... see my answer below]**

Everywhere I have looked in the documentation, it indicates "Contact" is the proper name for the Entity. What am I doing wrong?

Much thanks. -- Erik

Upvotes: 0

Views: 902

Answers (2)

IntelligentDB
IntelligentDB

Reputation: 13

WRONG: Data Field is 'Display Name' — 'ContactID' is 102155, not 'Wegweiser, Erik'

Aha! Thank you, all for the potentially useful information that may come in handy down the line. However, these issues and my concern about not doing something correctly turned out to be 'red herrings.' Once again, Acumatica's strange inconsistency in following its own conventions had befuddled me.

I was attempting to execute a request in the same format as elsewhwere, e.g. https://mydomain.acumatica.com/entity/Default/18.200.001/Customer/ACME001 (where 'ACME001' is a real Customer ID), works fine and all is good. Whereas, https://mydomain.acumatica.com/entity/Default/18.200.001/Contact/Nobody (where 'Nobody' is a real Contact ID) does not work.

Why? Despite my skepticism and the better judgment that 30 years of database programming experience tells me that Acumatica's 'ContactID' is not a wise implementation for a unique record identifier... I believed what I was told. Right there in the Element Properties inspector, it says that the real name of the field, labeled "Contact ID" is indeed, 'ContactID.'

When I eventually tried another form of query,

https://mydomain.acumatica.com/entity/Default/18.200.001/Contact?$filter=LastName eq 'Wegweiser'&$select=FirstName,ContactID

I finally received the payload that showed me the truth: Unfortunately, what was elsewhere identified as 'ContactID' and, according to convention, reasonably taken as the unique identifier, is actually the sensibly-named 'DisplayName.' The real 'ContactID,' as one would expect, is the true unique ID.

Acumatica is a wondrous tool. It's just a real different animal with many heads (or some might be tails), iMHO.


EDIT by HB_ACUMATICA

Looking at Contact.ContactID field definition in Contact DAC it is indeed declared as an Integer field and doesn't have a custom attribute that would return a String display value on screen:

#region ContactID
public abstract class contactID : IBqlField { }

[PXDBIdentity(IsKey = true)]
[PXUIField(DisplayName = "Contact ID", Visibility = PXUIVisibility.Invisible)]
[PXPersonalDataWarning]
public virtual Int32? ContactID { get; set; }
#endregion

With this DAC definition alone it is indeed incoherent that the field shown on screen is of String type instead of Integer. The explanation to this behavior is that the Contact Screen uses the ContactMaint graph which redefines the Contact.ContactID DAC field using the CacheAttached mechanism:

[PXUIField(DisplayName = "Contact ID")]
[ContactSelector(true, typeof(ContactTypesAttribute.person), typeof(ContactTypesAttribute.employee))]
[PXMergeAttributes(Method = MergeMethod.Merge)]
public virtual void Contact_ContactID_CacheAttached(PXCache sender) { }

Notice that the Contact.ContactID field redefinition adds a ContactSelector attribute that wasn't present in Contact DAC. A quick look at that attribute reveals it is using the Description field of PXSelector to show DisplayName string on screen instead of the Integer value. This substitution is only for display purpose in screens bounded to ContactMaint graph, all database operation remains Integer based. ContactSelector excerpt:

public ContactSelectorAttribute(bool showContactsWithNullEmail, params Type[] contactTypes)
    : base(GetQuery(typeof(Contact.contactID), showContactsWithNullEmail, contactTypes))
{
    if (contactTypes == null || contactTypes.Length == 0)
        throw new ArgumentNullException(nameof(contactTypes));

    DescriptionField = typeof(Contact.displayName);         
}

The REST web service call isn't using the Contact DAC instead of ContactMaint graph, that's why it requires an Integer instead of a String value.

Upvotes: 0

Hugues Beauséjour
Hugues Beauséjour

Reputation: 8278

It is a known issue that hasn't been fixed yet. The error message returned by the web service call is:

{
    "message": "An error has occurred.",
    "exceptionMessage": "Optimization cannot be performed.The following fields cause the error:\r\nAddressValidated: View AddressCurrent has BQL delegate\r\n",
    "exceptionType": "PX.Api.ContractBased.OptimizedExport.CannotOptimizeException",
    "stackTrace": "   at PX.Api.ContractBased.OptimizedExport.NotWorkingOptimizedExportProvider.get_CanOptimize() in C:\\Bld2\\AC-FULL2017R21-JOB1\\sources\\NetTools\\PX.Api.ContractBased\\OptimizedExport\\NotWorkingOptimizedExportProvider.cs:line 84\r\n   at PX.Api.ContractBased.EntityService.GetList(ISystemContract systemContract, String version, String name, EntityImpl entity, Boolean returnFullEntities, CbOperationContext operationContext, Boolean ignoreValueFields, PXGraph graph) in C:\\Bld2\\AC-FULL2017R21-JOB1\\sources\\NetTools\\PX.Api.ContractBased\\EntityService.cs:line 116\r\n   at PX.Api.ContractBased.Soap.SoapFacadeBase.GetListImpl(Entity entity, Boolean returnFullEntities) in C:\\Bld2\\AC-FULL2017R21-JOB1\\sources\\NetTools\\PX.Api.ContractBased\\Soap\\SoapFacadeBase.cs:line 83\r\n   at PX.Api.ContractBased.SystemContracts.V2.RestController.GetList(String objectName, String select, String filter, String expand, String custom, Nullable`1 skip, Nullable`1 top) in C:\\Bld2\\AC-FULL2017R21-JOB1\\sources\\NetTools\\PX.Api.ContractBased\\SystemContracts\\V2\\RestController.cs:line 247\r\n   at lambda_method(Closure , Object , Object[] )\r\n   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters)\r\n   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__5.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__5.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Filters.ActionFilterAttribute.<ExecuteActionFilterAsyncCore>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Filters.AuthorizationFilterAttribute.<ExecuteAuthorizationFilterAsyncCore>d__2.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Controllers.ExceptionFilterResult.<ExecuteAsync>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Web.Http.Controllers.ExceptionFilterResult.<ExecuteAsync>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"
}

The error occurs when fetching field AddressValidated. However that field isn't even returned by the request. EDIT it is returned only when the Address Validation feature is activated

As a workaround what I did is fetch a single contact by ContactID and then copy all the field name returned. I then put those fields in the select clause of the request which specifies which field should be returned. This appears to have the side effect of not involving AddressValidated and the call succeeded:

https://mydomain.acumatica.com/entity/Default/18.200.001/?$select=Active,AddressIsSameAsInAccount,BusinessAccount,CompanyName,ContactClass,ContactID,ContactMethod,ConvertedBy,DateOfBirth,DisplayName,DoNotCall,DoNotEmail,DoNotFax,DoNotMail,Duplicate,DuplicateFound,Email,Fax,FaxType,FirstName,Gender,Image,JobTitle,LanguageOrLocale,LastIncomingActivity,LastName,LastOutgoingActivity,MaritalStatus,MiddleName,NoMarketing,NoMassMail,Owner,OwnerEmployeeName,ParentAccount,Phone1,Phone1Type,Phone2,Phone2Type,Phone3,Phone3Type,QualificationDate,Reason,Source,SourceCampaign,SpouseOrPartnerName,Status,Synchronize,Title,Type,WebSite,Workgroup,WorkgroupDescription

As Samvel Petrosov mentioned you can also extend the endpoint and remove the AddressValidated field from there (this doesn't seem to apply for fields coming from default endpoint, only use for customized fields): enter image description here

With that approach you would have to change the endpoint in the URL to the extended endpoint name ('DefaultPlus' in this example):

https://mydomain.acumatica.com/entity/DefaultPlus/18.200.001/Contact

Upvotes: 1

Related Questions