使用扩展在税务集成中添加数据字段
本文说明如何使用 X++ 扩展在税务集成中添加数据字段。 这些字段可以扩展到税务服务的税务数据模型,并用于确定税码。 有关详细信息,请参阅在税务配置中添加数据字段。
数据模型
数据模型中的数据由对象承载并由类实现。
下面是主要对象的列表:
- AxClass/TaxIntegrationDocumentObject
- AxClass/TaxIntegrationLineObject
- AxClass/TaxIntegrationTaxLineObject
下图显示了这些对象之间的关系。
]
单据对象可以包含许多行对象。 每个对象都包含税务服务的元数据。
-
TaxIntegrationDocumentObject
具有originAddress
元数据(包含有关源地址的信息)和includingTax
元数据(指示行金额是否包含销售税)。 -
TaxIntegrationLineObject
具有itemId
、quantity
和categoryId
元数据。
注释
TaxIntegrationLineObject
还实施费用对象。
集成流
流中的数据由活动处理。
关键活动
- AxClass/TaxIntegrationCalculationActivityOnDocument
- AxClass/TaxIntegrationCurrencyExchangeActivityOnDocument
- AxClass/TaxIntegrationDataPersistenceActivityOnDocument
- AxClass/TaxIntegrationDataRetrievalActivityOnDocument
- AxClass/TaxIntegrationSettingRetrievalActivityOnDocument
活动按以下顺序运行:
- 设置检索
- 数据检索
- 计算服务
- 币种汇兑
- 数据持久性
例如,在计算服务之前扩展数据检索。
数据检索活动
数据检索活动从数据库中检索数据。 不同事务的适配器可用于从不同的事务表中检索数据:
- AxClass/TaxIntegrationPurchTableDataRetrieval
- AxClass/TaxIntegrationPurchParmTableDataRetrieval
- AxClass/TaxIntegrationPurchREQTableDataRetrieval
- AxClass/TaxIntegrationPurchRFQTableDataRetrieval
- AxClass/TaxIntegrationVendInvoiceInfoTableDataRetrieval
- AxClass/TaxIntegrationSalesTableDataRetrieval
- AxClass/TaxIntegrationSalesParmDataRetrieval
在这些数据检索活动中,将数据从数据库复制到 TaxIntegrationDocumentObject
和 TaxIntegrationLineObject
。 因为所有这些活动扩展相同的抽象模板类,所以它们具有通用方法。
计算服务活动
计算服务活动是税务服务和税务集成之间的链接。 此活动负责以下功能:
- 构造请求。
- 将请求过帐到税务服务。
- 从税务服务获得响应。
- 分析响应。
添加到请求的数据字段将与其他元数据一起过帐。
扩展实施
本部分提供说明如何实施扩展的详细步骤。 它使用成本中心和项目财务维度作为示例。
步骤 1. 在对象类中添加数据变量
对象类包含数据变量和数据的 getter/setter 方法。 将数据字段添加到 TaxIntegrationDocumentObject
或 TaxIntegrationLineObject
,具体取决于字段的级别。 以下示例使用行级别,文件名是 TaxIntegrationLineObject_Extension.xpp
。
注释
如果要添加的数据字段位于文档级别,将文件名更改为 TaxIntegrationDocumentObject_Extension.xpp
。
[ExtensionOf(classStr(TaxIntegrationLineObject))]
final class TaxIntegrationLineObject_Extension
{
private OMOperatingUnitNumber costCenter;
private ProjId projectId;
/// <summary>
/// Gets a costCenter.
/// </summary>
/// <returns>The cost center.</returns>
public final OMOperatingUnitNumber getCostCenter()
{
return this.costCenter;
}
/// <summary>
/// Sets the cost center.
/// </summary>
/// <param name = "_value">The cost center.</param>
public final void setCostCenter(OMOperatingUnitNumber _value)
{
this.costCenter = _value;
}
/// <summary>
/// Gets a project ID.
/// </summary>
/// <returns>The project ID.</returns>
public final ProjId getProjectId()
{
return this.projectId;
}
/// <summary>
/// Sets the project ID.
/// </summary>
/// <param name = "_value">The project ID.</param>
public final void setProjectId(ProjId _value)
{
this.projectId = _value;
}
}
成本中心和项目将添加为私有变量。 为这些数据字段创建 getter 和 setter 方法以处理数据。
步骤 2. 从数据库检索数据
指定事务,并扩展适当的适配器类以检索数据。 例如,如果您使用采购订单事务,必须扩展 TaxIntegrationPurchTableDataRetrieval
和 TaxIntegrationVendInvoiceInfoTableDataRetrieval
。
注释
TaxIntegrationPurchParmTableDataRetrieval
继承自 TaxIntegrationPurchTableDataRetrieval
。 它不应该更改,除非 purchTable
和 purchParmTable
表的逻辑不同。
如果应该为所有事务添加数据字段,扩展所有 DataRetrieval
类。
因为所有数据检索活动扩展相同的模板类,类结构、变量和方法类似。
protected TaxIntegrationDocumentObject document;
/// <summary>
/// Copies to the document.
/// </summary>
protected abstract void copyToDocument()
{
// It is recommended to implement as:
//
// this.copyToDocumentByDefault();
// this.copyToDocumentFromHeaderTable();
// this.copyAddressToDocument();
}
/// <summary>
/// Copies to the current line of the document.
/// </summary>
/// <param name = "_line">The current line of the document.</param>
protected abstract void copyToLine(TaxIntegrationLineObject _line)
{
// It is recommended to implement as:
//
// this.copyToLineByDefault(_line);
// this.copyToLineFromLineTable(_line);
// this.copyQuantityAndTransactionAmountToLine(_line);
// this.copyAddressToLine(_line);
// this.copyToLineFromHeaderTable(_line);
}
以下示例显示使用 PurchTable
表时的基础结构。
public class TaxIntegrationPurchTableDataRetrieval extends TaxIntegrationAbstractDataRetrievalTemplate
{
protected PurchTable purchTable;
protected PurchLine purchLine;
// Query builder methods
[Replaceable]
protected SysDaQueryObject getDocumentQueryObject()
[Replaceable]
protected SysDaQueryObject getLineQueryObject()
[Replaceable]
protected SysDaQueryObject getDocumentChargeQueryObject()
[Replaceable]
protected SysDaQueryObject getLineChargeQueryObject()
// Data retrieval methods
protected void copyToDocument()
protected void copyToDocumentFromHeaderTable()
protected void copyToLine(TaxIntegrationLineObject _line)
protected void copyToLineFromLineTable(TaxIntegrationLineObject _line)
...
}
当调用 CopyToDocument
方法时,this.purchTable
缓冲已存在。 此方法的目的是使用在 DocumentObject
类中创建的 setter 方法将所有所需数据从 this.purchTable
复制到 document
对象。
同样,this.purchLine
缓冲已存在于 CopyToLine
方法中。 此方法的目的是使用在 LineObject
类中创建的 setter 方法将所有所需数据从 this.purchLine
复制到 _line
对象。
最直接的方法是扩展 CopyToDocument
和 CopyToLine
方法。 但是,我们建议您首先尝试 copyToDocumentFromHeaderTable
和 copyToLineFromLineTable
方法。 如果它们不按您的要求工作,请实施自己的方法,并在 CopyToDocument
和 CopyToLine
中调用它。 在以下三种常见情况下,您可能会使用此方法:
必填字段在
PurchTable
或PurchLine
中。 在这种情况下,您可以扩展copyToDocumentFromHeaderTable
和copyToLineFromLineTable
。 下面是示例代码。/// <summary> /// Copies to the current line of the document from. /// </summary> /// <param name = "_line">The current line of the document.</param> protected void copyToLineFromLineTable(TaxIntegrationLineObject _line) { next copyToLineFromLineTable(_line); // if we already added XXX in TaxIntegrationLineObject _line.setXXX(this.purchLine.XXX); }
所需数据不在事务的默认表中。 但是,默认表存在一些联接关系,并且大多数行上都需要该字段。 在这种情况下,替换
getDocumentQueryObject
或getLineObject
以通过联接关系查询表。 在以下示例中,现在交货字段在行级别与销售订单集成。public class TaxIntegrationSalesTableDataRetrieval { protected MCRSalesLineDropShipment mcrSalesLineDropShipment; /// <summary> /// Gets the query for the lines of the document. /// </summary> /// <returns>The query for the lines of the document</returns> [Replaceable] protected SysDaQueryObject getLineQueryObject() { return SysDaQueryObjectBuilder::from(this.salesLine) .where(this.salesLine, fieldStr(SalesLine, SalesId)).isEqualToLiteral(this.salesTable.SalesId) .outerJoin(this.mcrSalesLineDropShipment) .where(this.mcrSalesLineDropShipment, fieldStr(MCRSalesLineDropShipment, SalesLine)).isEqualTo(this.salesLine, fieldStr(SalesLine, RecId)) .toSysDaQueryObject(); } }
在此示例中,声明
mcrSalesLineDropShipment
缓冲,并在getLineQueryObject
中定义查询。 查询使用关系MCRSalesLineDropShipment.SalesLine == SalesLine.RecId
。 当在这种情况下扩展时,可以将getLineQueryObject
替换为您自己的构造查询对象。 但是,请注意以下几点:- 因为
getLineQueryObject
方法的返回值是SysDaQueryObject
,因此您必须使用 SysDa 方法构造此对象。 - 无法删除现有表。
- 因为
所需数据通过复杂的联接关系与事务表相关,或者该关系不是一对一 (1:1),而是一对多 (1:N)。 在这种情况下,事情变得有些复杂。 这种情况适用于财务维度的示例。
在这种情况下,您可以实施自己的方法来检索数据。 下面是
TaxIntegrationPurchTableDataRetrieval_Extension.xpp
文件中的示例代码。[ExtensionOf(classStr(TaxIntegrationPurchTableDataRetrieval))] final class TaxIntegrationPurchTableDataRetrieval_Extension { private const str CostCenterKey = 'CostCenter'; private const str ProjectKey = 'Project'; /// <summary> /// Copies to the current line of the document from. /// </summary> /// <param name = "_line">The current line of the document.</param> protected void copyToLineFromLineTable(TaxIntegrationLineObject _line) { Map dimensionAttributeMap = this.getDimensionAttributeMapByDefaultDimension(this.purchline.DefaultDimension); if (dimensionAttributeMap.exists(CostCenterKey)) { _line.setCostCenter(dimensionAttributeMap.lookup(CostCenterKey)); } if (dimensionAttributeMap.exists(ProjectKey)) { _line.setProjectId(dimensionAttributeMap.lookup(ProjectKey)); } next copyToLineFromLineTable(_line); } private Map getDimensionAttributeMapByDefaultDimension(RefRecId _defaultDimension) { DimensionAttribute dimensionAttribute; DimensionAttributeValue dimensionAttributeValue; DimensionAttributeValueSetItem dimensionAttributeValueSetItem; Map ret = new Map(Types::String, Types::String); select Name, RecId from dimensionAttribute join dimensionAttributeValue where dimensionAttributeValue.dimensionAttribute == dimensionAttribute.RecId join DimensionAttributeValueSetItem where DimensionAttributeValueSetItem.DimensionAttributeValue == DimensionAttributeValue.RecId && DimensionAttributeValueSetItem.DimensionAttributeValueSet == _defaultDimension; while(dimensionAttribute.RecId) { ret.insert(dimensionAttribute.Name, dimensionAttributeValue.DisplayValue); next dimensionAttribute; } return ret; } }
步骤 3. 将数据添加到请求
扩展 copyToTaxableDocumentHeaderWrapperFromTaxIntegrationDocumentObject
或 copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine
以将数据添加到请求。 下面是 TaxIntegrationCalculationActivityOnDocument_CalculationService_Extension.xpp
文件中的示例代码。
[ExtensionOf(classStr(TaxIntegrationCalculationActivityOnDocument_CalculationService))]
final static class TaxIntegrationCalculationActivityOnDocument_CalculationService_Extension
{
// Define the field name in the request
private const str IOCostCenter = 'Cost Center';
private const str IOProject = 'Project';
// private const str IOEnumExample = 'Enum Example';
/// <summary>
/// Copies to <c>TaxableDocumentLineWrapper</c> from <c>TaxIntegrationLineObject</c> by line.
/// </summary>
/// <param name = "_destination"><c>TaxableDocumentLineWrapper</c>.</param>
/// <param name = "_source"><c>TaxIntegrationLineObject</c>.</param>
protected static void copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine(Microsoft.Dynamics.TaxCalculation.ApiContracts.TaxableDocumentLineWrapper _destination, TaxIntegrationLineObject _source)
{
next copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine(_destination, _source);
// Set the field we need to integrated for tax service
_destination.SetField(IOCostCenter, _source.getCostCenter());
_destination.SetField(IOProject, _source.getProjectId());
// If the field to be extended is an enum type, use enum2Symbol to convert an enum variable exampleEnum of ExampleEnumType to a string
// _destination.SetField(IOEnumExample, enum2Symbol(enumNum(ExampleEnumType), _source.getExampleEnum()));
}
}
在此代码中,_destination
是用于生成请求的包装器对象,_source
是 TaxIntegrationLineObject
对象。
注释
定义在请求中用作 private const str 的字段名称。 此字符串应与在税务配置中添加数据字段一文中添加的节点名称(而不是标签)完全相同。
使用 SetField 方法设置 copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine 方法中的字段。 第二个参数的数据类型应为字符串。 如果数据类型不是字符串,请将其转换为字符串。 如果数据类型为 X++ 枚举类型,我们建议您使用 enum2Symbol 方法将枚举值转换为字符串。 税务配置中添加的枚举值应与枚举名称完全相同。 以下是枚举值、标签和名称之间的差异列表。
- 枚举的名称是代码中的一个符号名称。 enum2Symbol() 可将枚举值转换为它的名称。
- 枚举的值是整数。
- 枚举的标签可能因首选语言而异。 enum2Str() 可将枚举值转换为它的标签。
模型依赖项
要成功构建项目,请将以下引用模型添加到模型依赖项中:
- ApplicationPlatform
- ApplicationSuite
- 税引擎
- 维度(如果使用财务维度)
- 代码中引用的其他必要模型
验证
完成前面的步骤后,您可以验证您的更改。
- 在 Finance 中,转到应付帐款,将 &debug=vs%2CconfirmExit& 添加到 URL。 例如,
https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=DEMF&mi=PurchTableListPage&debug=vs%2CconfirmExit&
。 最后的 & 是必不可少的。 - 打开采购订单页面,选择新建创建采购订单。
- 设置自定义字段的值,然后选择销售税。 将自动下载带有前缀 TaxServiceTroubleshootingLog 的故障排除文件。 此文件包含过帐到税款计算服务的交易信息。
- 检查添加的自定义字段是否存在于税务服务计算输入 JSON 部分,以及值是否正确。 如果值不正确,请仔细检查此文档中的步骤。
文件示例:
===Tax service calculation input JSON:===
{
"TaxableDocument": {
"Header": [
{
"Lines": [
{
"Line Type": "Normal",
"Item Code": "",
"Item Type": "Item",
"Quantity": 0.0,
"Amount": 1000.0,
"Currency": "EUR",
"Transaction Date": "2022-1-26T00:00:00",
...
/// The new fields added at line level
"Cost Center": "003",
"Project": "Proj-123"
}
],
"Amount include tax": true,
"Business Process": "Journal",
"Currency": "",
"Vendor Account": "DE-001",
"Vendor Invoice Account": "DE-001",
...
// The new fields added at header level, no new fields in this example
...
}
]
},
}
...
附录
本附录显示了用于在行级别集成财务维度成本中心和项目的完整示例代码。
TaxIntegrationLineObject_Extension.xpp
[ExtensionOf(classStr(TaxIntegrationLineObject))]
final class TaxIntegrationLineObject_Extension
{
private OMOperatingUnitNumber costCenter;
private ProjId projectId;
/// <summary>
/// Gets a costCenter.
/// </summary>
/// <returns>The cost center.</returns>
public final OMOperatingUnitNumber getCostCenter()
{
return this.costCenter;
}
/// <summary>
/// Sets the cost center.
/// </summary>
/// <param name = "_value">The cost center.</param>
public final void setCostCenter(OMOperatingUnitNumber _value)
{
this.costCenter = _value;
}
/// <summary>
/// Gets a project ID.
/// </summary>
/// <returns>The project ID.</returns>
public final ProjId getProjectId()
{
return this.projectId;
}
/// <summary>
/// Sets the project ID.
/// </summary>
/// <param name = "_value">The project ID.</param>
public final void setProjectId(ProjId _value)
{
this.projectId = _value;
}
}
TaxIntegrationPurchTableDataRetrieval_Extension.xpp
[ExtensionOf(classStr(TaxIntegrationPurchTableDataRetrieval))]
final class TaxIntegrationPurchTableDataRetrieval_Extension
{
private const str CostCenterKey = 'CostCenter';
private const str ProjectKey = 'Project';
/// <summary>
/// Copies to the current line of the document from.
/// </summary>
/// <param name = "_line">The current line of the document.</param>
protected void copyToLineFromLineTable(TaxIntegrationLineObject _line)
{
Map dimensionAttributeMap = this.getDimensionAttributeMapByDefaultDimension(this.purchline.DefaultDimension);
if (dimensionAttributeMap.exists(CostCenterKey))
{
_line.setCostCenter(dimensionAttributeMap.lookup(CostCenterKey));
}
if (dimensionAttributeMap.exists(ProjectKey))
{
_line.setProjectId(dimensionAttributeMap.lookup(ProjectKey));
}
next copyToLineFromLineTable(_line);
}
private Map getDimensionAttributeMapByDefaultDimension(RefRecId _defaultDimension)
{
DimensionAttribute dimensionAttribute;
DimensionAttributeValue dimensionAttributeValue;
DimensionAttributeValueSetItem dimensionAttributeValueSetItem;
Map ret = new Map(Types::String, Types::String);
select Name, RecId from dimensionAttribute
join dimensionAttributeValue
where dimensionAttributeValue.dimensionAttribute == dimensionAttribute.RecId
join DimensionAttributeValueSetItem
where DimensionAttributeValueSetItem.DimensionAttributeValue == DimensionAttributeValue.RecId
&& DimensionAttributeValueSetItem.DimensionAttributeValueSet == _defaultDimension;
while(dimensionAttribute.RecId)
{
ret.insert(dimensionAttribute.Name, dimensionAttributeValue.DisplayValue);
next dimensionAttribute;
}
return ret;
}
}
TaxIntegrationCalculationActivityOnDocument_CalculationService_Extension.xpp
[ExtensionOf(classStr(TaxIntegrationCalculationActivityOnDocument_CalculationService))]
final static class TaxIntegrationCalculationActivityOnDocument_CalculationService_Extension
{
// Define the field name in the request
private const str IOCostCenter = 'Cost Center';
private const str IOProject = 'Project';
/// <summary>
/// Copies to <c>TaxableDocumentLineWrapper</c> from <c>TaxIntegrationLineObject</c> by line.
/// </summary>
/// <param name = "_destination"><c>TaxableDocumentLineWrapper</c>.</param>
/// <param name = "_source"><c>TaxIntegrationLineObject</c>.</param>
protected static void copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine(Microsoft.Dynamics.TaxCalculation.ApiContracts.TaxableDocumentLineWrapper _destination, TaxIntegrationLineObject _source)
{
next copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine(_destination, _source);
// Set the field we need to integrated for tax service
_destination.SetField(IOCostCenter, _source.getCostCenter());
_destination.SetField(IOProject, _source.getProjectId());
}
}