Reputation: 13
@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
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
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):
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