Dodawanie pól danych w integracji podatków przy użyciu rozszerzeń
W tym artykule opisano, jak używać rozszerzeń X++ w celu dodawania pól danych w integracji podatków. Te pola mogą być rozszerzone na model danych podatkowych usługi podatkowej i używane do określania kodów podatków. Aby uzyskać więcej informacji, zobacz temat Dodawanie pól danych w konfiguracjach podatków.
Model danych
Dane w modelu danych są realizowane przez obiekty i implementowane przez klasy.
Poniżej znajduje się lista dużych objektów:
- AxClass/TaxIntegrationDocumentObject
- AxClass/TaxIntegrationLineObject
- AxClass/TaxIntegrationTaxLineObject
Na poniższej ilustracji pokazano, w jaki sposób są one powiązane.
]
Obiekt Dokumentu może zawierać wiele obiektów Wiersza. Każdy obiekt zawiera metadane usługi podatków.
-
TaxIntegrationDocumentObject
zawiera metadaneoriginAddress
, które zawierają informacje o adresie źródłowym, oraz metadaneincludingTax
, które wskazują, czy kwota wiersza zawiera podatek. -
TaxIntegrationLineObject
ma metadaneitemId
,quantity
icategoryId
.
Banknot
TaxIntegrationLineObject
implementuje również obiekty Opłat.
Przepływ integracji
Działaniami manipulują dane w przepływie.
Kluczowe działania
- AxClass/TaxIntegrationCalculationActivityOnDocument
- AxClass/TaxIntegrationCurrencyExchangeActivityOnDocument
- AxClass/TaxIntegrationDataPersistenceActivityOnDocument
- AxClass/TaxIntegrationDataRetrievalActivityOnDocument
- AxClass/TaxIntegrationSettingRetrievalActivityOnDocument
Działania są uruchamiane w następującej kolejności:
- Ustawianie pobierania
- Pobieranie danych
- Usługa obliczania
- Kurs wymiany waluty
- Utrwalanie danych
Na przykład rozszerzenie Pobierania danych przed Usługą obliczania.
Działania pobierania danych
Działania Pobierania danych pobierają dane z bazy danych. Adaptery dla różnych transakcji są dostępne do pobierania danych z różnych tabel transakcji:
- AxClass/TaxIntegrationPurchTableDataRetrieval
- AxClass/TaxIntegrationPurchParmTableDataRetrieval
- AxClass/TaxIntegrationPurchREQTableDataRetrieval
- AxClass/TaxIntegrationPurchRFQTableDataRetrieval
- AxClass/TaxIntegrationVendInvoiceInfoTableDataRetrieval
- AxClass/TaxIntegrationSalesTableDataRetrieval
- AxClass/TaxIntegrationSalesParmDataRetrieval
W tych działaniach Pobierania danych dane są kopiowane z bazy danych do TaxIntegrationDocumentObject
i TaxIntegrationLineObject
. Ponieważ wszystkie te działania rozszerzają tę samą klasę szablonu abstrakcyjnego, mają wspólne metody.
Czynności związane z usługami obliczeniowymi
Działanie Usługa obliczania jest łączem między usługą podatkową a integracją podatkową. To działanie jest odpowiedzialne za następujące funkcje:
- Skonstruuj żądanie.
- Księguj wniosek do usługi podatkowej.
- Pobierz odpowiedź od usługi podatkowej.
- Analiza odpowiedzi.
Pole danych, które dodasz do żądania, zostanie zaksięgowane razem z innymi metadanymi.
Implementacja rozszerzenia
Ta sekcja zawiera szczegółowe kroki, które zawierają informacje dotyczące implementacji rozszerzenia. Jako przykłady używane są wymiary finansowe Centrum kosztów i Projekt.
Krok 1. Dodaj zmienną danych do klasy obiektu
Klasa obiektów zawiera zmienną danych oraz metody getter/setter dla danych. W zależności od poziomu pola należy dodać pole TaxIntegrationDocumentObject
lub TaxIntegrationLineObject
do wybranego pola lub do jego poziomu. W poniższym przykładzie użyto poziomu wiersza i nazwy TaxIntegrationLineObject_Extension.xpp
:
Banknot
Jeśli dodajene pole danych jest na poziomie dokumentu, zmień nazwę pliku na 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;
}
}
Centrum kosztów i Projekt są dodawane jako zmienne prywatne. Aby operować danymi, należy utworzyć metody getter i setter dla tych pól danych.
Krok 2. Pobierz dane z bazy danych
Określ transakcję i rozszerz odpowiednie klasy adaptera, aby pobrać dane. Na przykład w przypadku użycia transakcji zamówienia zakupu należy rozszerzyć TaxIntegrationPurchTableDataRetrieval
i TaxIntegrationVendInvoiceInfoTableDataRetrieval
.
Banknot
TaxIntegrationPurchParmTableDataRetrieval
jest dziedziczone po TaxIntegrationPurchTableDataRetrieval
. Nie należy zmieniać tej zmiany, chyba że logika tabel purchTable
i purchParmTable
różni się.
Jeśli pole danych powinno być dodane dla wszystkich transakcji, należy rozszerzać wszystkie klasy DataRetrieval
.
Ponieważ wszystkie działania Pobierania danych rozszerzają tę samą klasę szablonu, struktury klas, zmienne i metody są podobne.
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);
}
W poniższym przykładzie pokazano podstawową strukturę podczas używania tabeli 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)
...
}
Podczas wywoływania metody CopyToDocument
bufor this.purchTable
już istnieje. Celem tej metody jest skopiowanie wszystkich wymaganych danych z this.purchTable
do obiektu document
przy użyciu metody ustawień utworzonej w klasie DocumentObject
.
Podobnie bufor this.purchLine
już istnieje w metodzie CopyToLine
. Celem tej metody jest skopiowanie wszystkich wymaganych danych z this.purchLine
do obiektu _line
przy użyciu metody ustawień utworzonej w klasie LineObject
.
Najbardziej jednoznacznym podejściem jest rozszerzanie metod CopyToDocument
i CopyToLine
. Najpierw jednak zaleca się najpierw użycie metod copyToDocumentFromHeaderTable
i copyToLineFromLineTable
. Jeśli nie działają w wymagany sposób, implementuj swoją własną metodę, a następnie wywołaj ją w CopyToDocument
i CopyToLine
. Istnieją trzy typowe sytuacje, w których można użyć tego podejścia:
Wymagane pole jest w
PurchTable
lubPurchLine
. W tej sytuacji można rozszerzaćcopyToDocumentFromHeaderTable
icopyToLineFromLineTable
. Oto przykładowy kod./// <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); }
Wymagane dane nie są w domyślnej tabeli transakcji. Istnieją jednak pewne relacje sprzężenia z tabelą domyślną, a pole jest wymagane w większości wierszy. W tej sytuacji należy zastąpić
getDocumentQueryObject
lubgetLineObject
na kwerendę przez relację sprzężenia. W poniższym przykładzie pole Dostarcz teraz jest zintegrowane z zamówieniem sprzedaży na poziomie wiersza.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(); } }
W tym przykładzie bufor
mcrSalesLineDropShipment
jest zadeklarowany, a kwerenda jest zdefiniowana wgetLineQueryObject
. Kwerenda używa relacjiMCRSalesLineDropShipment.SalesLine == SalesLine.RecId
. Podczas rozszerzania tej sytuacji można zastąpićgetLineQueryObject
własnym skonstruowanym obiektem kwerendy. Warto jednak pamiętać o następujących punktów:- Ponieważ zwracana wartość metody
getLineQueryObject
wynoszącaSysDaQueryObject
, musisz skonstruować ten obiekt przy użyciu metody SysDa. - Nie można usunąć tabeli, która istniała.
- Ponieważ zwracana wartość metody
Wymagane dane są powiązane z tabelą transakcji za pomocą złożonej relacji sprzężenia lub relacja nie jest jeden-do-jednego (1:1), tylko jeden-do-wielu (1:N). W tej sytuacji sytuacja może się nieco skomplikowane. Ta sytuacja ma zastosowanie do przykładu wymiarów finansowych.
W tej sytuacji można zaimplementować własną metodę pobierania danych. Oto przykładowy kod w pliku
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; } }
Krok 3. Dodaj dane do żądania
Rozszerzaj metodę copyToTaxableDocumentHeaderWrapperFromTaxIntegrationDocumentObject
lub copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine
do dodawania danych do żądania. Oto przykładowy kod w pliku 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()));
}
}
W tym kodzie _destination
jest obiektem otoki, który jest używany do generowania żądania, a _source
to obiekt TaxIntegrationLineObject
.
Banknot
Zdefiniuj nazwę pola, która jest używana w żądaniu jako private const str. Ciąg powinien być dokładnie taki sam, jak nazwa węzła (nie etykieta) dodana w artykule Dodawanie pól danych w konfiguracjach podatkowych.
Ustaw pole w metodzie copyToTaxableDocumentLineWrapperFromTaxIntegrationLineObjectByLine, używając metody SetField. Typem danych drugiego parametru musi być ciąg. Jeśli typ danych nie jest ciąg, przekonwertuj go na string. Jeśli typem danych jest X++ typ enum, zalecamy użycie metody enum2Symbol do konwersji wartości wyliczenia na ciąg znaków. Wartość wyliczenia dodana w konfiguracji podatku powinna być dokładnie taka sama jak nazwa wyliczenia. Poniżej znajduje się lista różnic pomiędzy wartością wyliczenia, etykietą i nazwą.
- Nazwa wyliczenia jest nazwą symboliczną w kodzie. enum2Symbol() może przekształcić wartość wyliczenia na jego nazwę.
- Wartość wyliczenia jest liczbą całkowitą.
- Etykieta wyli roku może być różna w różnych preferowanych językach. enum2Str() może przekształcić wartość wyliczeniową na jej etykietę.
Zależność modelu
Aby pomyślnie skompilować projekt, dodaj następujące modele odwołania do zależności modeli:
- ApplicationPlatform
- ApplicationSuite
- Aparat podatków
- Wymiary, jeśli jest używany wymiar finansowy
- Inne potrzebne modele, do których odwołuje się kod
Sprawdzenie poprawności
Po ukończeniu poprzednich kroków możesz sprawdzić poprawność wprowadzonych zmian.
- W polu Finanse przejdź do rozrachunków z dostawcami i dodaj wartość &debug=vs%2CconfirmExit& do adresu URL. Na przykład
https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=DEMF&mi=PurchTableListPage&debug=vs%2CconfirmExit&
. Ostateczne i jest konieczne. - Otwórz stronę Zamówienie zakupu i wybierz Nowość, aby utworzyć zamówienie zakupu.
- Ustaw wartość niestandardowego pola, a następnie wybierz opcję Podatek. Plik rozwiązywania problemów z prefiksem TaxServiceTroubleshootingLog jest pobierany automatycznie. Ten plik zawiera informacje o transakcjach zaksięgowane w usłudze obliczania podatku.
- Sprawdź, czy dodane pole niestandardowe znajduje się w sekcji Dane wejściowe obliczenia usługi podatkowej JSON i czy jego wartość jest poprawna. Jeśli wartość jest błędna, sprawdź dwukrotnie kroki w tym dokumencie.
Przykład pliku:
===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
...
}
]
},
}
...
Dodatek
Ten dodatek pokazuje pełny przykładowy kod integracji wymiarów finansowych Centrum kosztów i Projekt na poziomie wiersza.
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());
}
}