通过 RESTful API 在 Acumatica 中获取联系人

Get Contact(s) in Acumatica via RESTful API

@HB_ACUMATICA、等

过去几个月我一直致力于将客户的 FileMaker 数据库与 Acumatica 集成。我能够访问 (get/put) 各种表(实体),例如 PROJECT 和 CUSTOMER,但 CONTACT 实体总是会产生错误。例如,

https://mydomain.acumatica.com/entity/Default/18.200.001/Customer[工作正常]

https://mydomain.acumatica.com/entity/Default/18.200.001/Contact[总是报错500]

**[编辑:上面的例子当然是不完整的,除非试图找到 'all' 联系人记录。在测试中,我指定了实际的联系人 ID,如

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

其中 'Nobody' 是一个真实的联系人 ID...或者让我相信...请参阅下面的回答]**

我在文档中到处查看,它表明 "Contact" 是实体的正确名称。我做错了什么?

非常感谢。 -- 埃里克

这是一个尚未修复的已知问题。网络服务调用返回的错误信息是:

{
    "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()"
}

获取字段AddressValidated时出现错误。但是,请求甚至没有返回该字段。 编辑它仅在激活地址验证功能时返回

作为解决方法,我所做的是通过 ContactID 获取单个联系人,然后复制返回的所有字段名称。然后我将这些字段放在请求的 select 子句中,该子句指定应返回哪个字段。这似乎具有不涉及 AddressValidated 的副作用并且调用成功:

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

正如 Samvel Petrosov 提到的,您还可以扩展端点并从那里删除 AddressValidated 字段(这似乎不适用于来自默认端点的字段,仅用于自定义字段):

使用这种方法,您必须将 URL 中的端点更改为扩展端点名称(本例中为 'DefaultPlus'):

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

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

啊哈!谢谢大家提供的可能有用的信息,这些信息可能会派上用场。然而,这些问题和我对没有正确做某事的担忧结果证明是 'red herrings.' 再一次,Acumatica 在遵循自己的惯例方面奇怪的不一致让我感到困惑。

我试图以与 elsewhwere 相同的格式执行请求,例如 https://mydomain.acumatica.com/entity/Default/18.200.001/Customer/ACME001(其中 'ACME001' 是真实的客户 ID),工作正常,一切正常。然而, https://mydomain.acumatica.com/entity/Default/18.200.001/Contact/Nobody(其中 'Nobody' 是真实的联系人 ID)不起作用。

为什么?尽管我持怀疑态度并做出更好的判断,但 30 年的数据库编程经验告诉我,Acumatica 的 'ContactID' 不是唯一记录标识符的明智实现……我相信别人告诉我的。就在元素属性检查器中,它说标记为 "Contact ID" 的字段的真实名称确实是 'ContactID.'

当我最终尝试另一种形式的查询时,

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

我终于收到了向我展示真相的有效载荷: 不幸的是,在别处被标识为 'ContactID' 并且根据惯例合理地被视为唯一标识符的实际上是 sensibly-named 'DisplayName.' 真正的 'ContactID,' 正如人们所期望的那样,是真正的唯一 ID。

Acumatica 是一个奇妙的工具。恕我直言,这只是一种真正不同的动物,有很多头(或者有些可能是尾巴)。


编辑 HB_ACUMATICA

查看 Contact DAC 中的 Contact.ContactID 字段定义,它确实被声明为 Integer 字段并且没有自定义属性 return String 屏幕显示值:

#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

仅凭此 DAC 定义,屏幕上显示的字段是 String 类型而不是 Integer 确实不连贯。对此行为的解释是 Contact Screen 使用 ContactMaint 图,它使用 CacheAttached 机制重新定义 Contact.ContactID DAC 字段:

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

请注意,Contact.ContactID 字段重新定义添加了一个 ContactSelector 属性,该属性在 Contact DAC 中不存在。快速查看该属性会发现它正在使用 PXSelectorDescription 字段在屏幕上显示 DisplayName 字符串而不是 Integer 值。此替换仅用于在绑定到 ContactMaint 图形的屏幕中显示目的,所有数据库操作仍然基于 Integer。 ContactSelector 摘录:

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);         
}

REST Web 服务调用未使用 Contact DAC 而不是 ContactMaint 图表,这就是它需要 Integer 而不是 String 值的原因。