使用擴充在稅務整合中新增資料欄位
本文介紹如何使用 X++ 擴充功能在稅務整合中新增資料欄位。 這些欄位可以擴充到稅務服務的稅務資料模型,並用於確定稅碼。 有關詳細信息,請參閱 在稅務配置中新增資料欄位。
資料模型
資料模型中的資料由物件承載,由類別實現。
以下是主要物件的列表:
- AxClass/TaxIntegration文檔對象
- AxClass/TaxIntegration行對象
- AxClass/TaxIntegrationTaxLine對象
下圖顯示這些物件之間的關係。
]
一個 Document 物件可以包含許多 Line 物件。 每個物件都包含稅務服務的中繼資料。
-
TaxIntegrationDocumentObject
有originAddress
元數據,其中包含有關來源地址的信息,以及includingTax
元數據,該元數據指示行金額是否包含銷售稅。 -
TaxIntegrationLineObject
具有itemId
、quantity
和categoryId
元資料。
附註
TaxIntegrationLineObject
也實現了 Charge 物件。
整合流程
流程中的資料由活動操控。
重要活動
- AxClass/TaxIntegration計算ActivityOnDocument
- AxClass/TaxIntegrationCurrencyExchangeActivityOnDocument
- AxClass/TaxIntegrationDataPersistenceActivityOnDocument
- AxClass/TaxIntegration資料擷取ActivityOnDocument
- AxClass/TaxIntegrationSettingRetrievalActivityOnDocument
活動按以下順序執行:
- 設定檢索
- 資料檢索
- 計算服務
- 貨幣兌換
- 資料永續性
例如,在 計算服務 之前擴展 資料擷取。
資料檢索活動
資料檢索 活動從資料庫檢索資料。 不同交易的配接器可用於從不同交易資料表中擷取資料:
- AxClass/TaxIntegrationPurchTable資料檢索
- AxClass/TaxIntegrationPurchParmTable資料檢索
- AxClass/TaxIntegrationPurchREQTable資料檢索
- AxClass/TaxIntegrationPurchRFQTable資料檢索
- AxClass/TaxIntegrationVendInvoiceInfoTable資料檢索
- AxClass/TaxIntegrationSalesTable資料檢索
- AxClass/TaxIntegrationSalesParm資料檢索
在這些 資料擷取 活動中,資料從資料庫複製到 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
緩衝區已經存在。 此方法的目的是使用在 this.purchTable
類別中建立的 setter 方法將所有必需的資料從 document
複製到 DocumentObject
物件。
同樣, this.purchLine
緩衝區已存在於 CopyToLine
方法中。 此方法的目的是使用在 this.purchLine
類別中建立的 setter 方法將所有必需的資料從 _line
複製到 LineObject
物件。
最直接的方法是擴展 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 方法中設定欄位。 第二個參數的資料型別應為 string。 如果資料類型不是 string,則將其轉換為字串。 如果資料類型為 X++ 列舉類型,建議您使用 enum2Symbol 方法轉換列舉值轉換為字串。 稅務配置中新增的列舉值應與列舉名稱完全相同。 以下是列舉值、標籤和名稱之間的差異清單。
- 列舉的名稱是程式碼中的符號名稱。 enum2Symbol() 可以將列舉值轉換為其名稱。
- 該列舉值為整數。
- 列舉的標籤可能因偏好的語言而異。 enum2Str() 可以將列舉值轉換為其標籤。
模型相依性
要成功構建項目,請將以下參考模型添加到模型相依性中:
- ApplicationPlatform
- ApplicationSuite
- 稅務引擎
- 維度,如果使用財務維度
- 程式碼中引用的其他必要模型
驗證
完成前面的步驟後,您可以驗證您的更改。
- 在財務中,請前往 應付帳款 並新增 &debug=vs%2CconfirmExit& 到網址。 例如,
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());
}
}