Dynamics AX Blog - Seite 24

Word-Dokument mit Tabelle per Code erstellen

Microsoft WordAnbei ein einfach gehaltener Job, der ein Word-Dokument mit einer Tabelle per Code erstellt. Das Beispiel legt auch den Rahmen und dessen Farbe der Tabelle fest.
Voraussetzung ist ein lokal installiertes Microsoft Word.

static void CreateWordFileWithTable(Args _args)
{
    COM wordApplication;
    COM wordTables;
    COM wordTable;
    COM wordSelection;
    COM wordTableRows;
    COM wordRange;
    COM wordTableCell;
    COM wordTableCellRange;
    COM wordDocuments;
    COM wordDocument;
    COM wordTableBorders;
    ;
 
    // Initialize Word object and document
    wordApplication = new COM("Word.Application");
    wordDocuments = wordApplication.documents();
    wordDocuments.add();
    wordDocument = wordDocuments.item(1);
 
    wordSelection = wordApplication.selection();
    wordRange = wordSelection.range();
 
    // Get table collection
    wordTables = wordSelection.tables();
 
    // Create table with 3 rows and 5 columns
    wordTable = wordTables.add(wordRange, 3, 5);
 
    // Fill cell: First line, second column
    wordTableCell = wordTable.Cell(1, 2);
    wordTableCellRange = wordTableCell.range();
    wordTableCellRange.text("Hello");

    // Fill cell: Second line, third column
    wordTableCell = wordTable.Cell(2, 3);
    wordTableCellRange = wordTableCell.range();
    wordTableCellRange.text("World");
 
    // Enable table borders
    wordTableBorders = wordTable.borders();
    wordTableBorders.enable(true);

    // Add colored borders
    wordTableBorders.InsideLineStyle(3);
    wordTableBorders.OutsideLineStyle(5);
    wordTableBorders.OutsideColorIndex(2);
 
    // Get table row collection and add a new row
    wordTableRows = wordTable.rows();
    wordTableRows.add();
 
    // Open word
    wordApplication.visible(true);
    wordApplication.finalize();
}

Das per obigem Code erstellte Word-Dokument sieht wie folgt aus:

Screenshot


 
 

AX 2012: Importieren eines Debitoren mit Hilfe des Data import/export frameworks (ODBC)

Mit Hilfe des Data import/export frameworks ist es möglich, Daten aus externen Systemen in Dynamics AX zu importieren. im folgenden möchte ich dies anhand der Anlage eines Debitoren über eine ODBC-Verbindung demonstrieren.

 

ODBC-Benutzer DSN einrichten

Screenshot

 

Erstellen einer Verarbeitungsgruppe

Erstellen einer Verarbeitungsgruppe (Processing group) unter Data import Export framework > Processing Group:

Screenshot


 
 

AX 2012: Subselects über Computed columns abbilden

Mit Hilfe von Computed columns von Views sind auch in Dynamics AX Subselects/Subqueries möglich.

Im folgenden habe ich eine Methode erstellt, die der Methode inventTable.productName() nachempfunden ist.

private static server str compColItemName()
{
    #define.ViewName("ItemNameView")

    #define.DataSourceName("InventTable")
    #define.FieldItemId("itemId")
    #define.FieldProduct("Product")
    str sProduct;
    DictView dictView;
    str translationSQLStr;
    str productSQLStr;

    dictView = new DictView(tableNum(#ViewName));

    sProduct = dictView.computedColumnString
        (#DataSourceName,
        #FieldProduct,
        FieldNameGenerationMode::FieldList,
        true);

    translationSQLStr = strFmt("SELECT Name FROM EcoResProductTranslation WHERE EcoResProductTranslation.Product = %1 AND LanguageId = '%2'", sProduct, CompanyInfo::find().LanguageId);
    productSQLStr = strFmt("SELECT DisplayProductNumber FROM EcoResProduct WHERE EcoResProduct.RecId = %1", sProduct);

    return strFmt("isNUll((%1), (%2))", translationSQLStr, productSQLStr);
}

 
 

Erstellen einer AX<Table>-Klasse

Benötigt man für eine Tabelle eine sog. AX<Table>-Klasse kann man sich diese mit Hilfe der Klasse AxGenerateAxBCClass generieren lassen.

Dazu einfach diese Klasse im AOT per rechter Maustaste aufrufen und dem Assistenten folgen.

Die auf diese Art & Weise generierte Klasse muss unter manchen Umständen noch etwas bearbeitet werden, dennoch geht der Vorgang rascher von der Hand, als die Klasse komplett selbst erstellen zu müssen.

Und wenn sich die Tabelle ändert, beispielweise wenn neue Felder hinzukommen, kann man die AxGenerateAxBCClass einfach nochmals aufrufen und die AX<Table>-Klasse wird entsprechend erweitert.

Wie man solche AX<Table>-Klassen verwendet, habe ich u.a. hier beschrieben.


 
 

Erstellen einer Entität - beispielsweise eines Debitoren - per Code unter Verwendung von AX<Table>-Klassen

Folgender Job soll veranschaulichen, wie man unter Verwendung von sog. AX<Table>-Klassen Entitäten in Dynamics AX per Code anlegen kann, ohne dabei - wie in diesem Beitrag beschrieben - direkt in die Tabellen zu schreiben.

Solche AX<Table>-Klassen stehen bereits für zahlreiche Tabellen von Haus aus zu Verfügung und es ist möglich, sich jederzeit für weitere Tabellen solche Klassen generieren zu lassen (siehe hier).

Vorteile:

  • Pflichtfelder werden geprüft
  • Datensatz wird initalisiert (d.h. die initValue() wird aufgerufen)
  • Feldinhalten werden geprüft

Nachteile:

  • Bei Feldänderung wird die Logik in der modifiedField() nicht aufgerufen
static void CreateCustomerAXClass(Args _args)
{
    AxCustTable axCustTable;
    AxDirPartyTable AxDirPartyTable;
    CustTable CustTable;
    DirPartyTable DirPartyTable;

    AccountNum accountNum     = "4711";
    Name name                 = "Debitor 4711";
    currencyCode currencyCode = "EUR";
    custGroupId custGroupId   = "DINL";
    taxGroup taxGroup         = "DINL";
    languageId languageId     = "de-AT";

    ttsBegin;

    // Create or update Customer
    axCustTable = axCustTable::construct();
    axCustTable.validateInput(true);    
    axCustTable.continueOnError(true);  // Validate fields without stopping error
    
    CustTable = CustTable::find(accountNum, true);
    if(CustTable)
    {
        axCustTable.custTable(CustTable);   // Don't use this for new records to get initValue() called
    }

    axCustTable.parmAccountNum(accountNum);
    axCustTable.parmCustGroup(custGroupId);
    axCustTable.parmTaxGroup(taxGroup);
    axCustTable.parmCurrency(currencyCode);
    
    axCustTable.save();

    // Create or update Global address book
    AxDirPartyTable = AxDirPartyTable::construct();
    AxDirPartyTable.validateInput(true);
    AxDirPartyTable.continueOnError(true);  // Validate fields without stopping error
    
    DirPartyTable = DirPartyTable::findRec(axCustTable.custTable().Party, true);
    if(DirPartyTable)
    {
        AxDirPartyTable.dirPartyTable(DirPartyTable);
    }

    AxDirPartyTable.parmName(name);
    AxDirPartyTable.parmLanguageId(languageId);
    
    AxDirPartyTable.save();

    ttsCommit;
}

 
 

Erstellen einer Entität - beispielsweise eines Debitoren - per Code

Beim Start eines AX-Projektes ist die Übernahme von Daten aus Vorsystemen oft ein Thema. Mittlerweile gibt es zahlreiche Möglichkeiten dies zu tun - beispielswiese das Data import Export Framework (DIXF), ich möchte in diesem Beitrag aber auf die einfachste Variante eingehen, um eine Entität - im Beispiel einen Debitoren - in Dynamics AX per Code anzulegen.

Und diese aus meiner Sicht einfachste Variante ist, mittels X++ Logik einzubinden, welche die Daten direkt in die Tabelle schreibt. Ein solches Stück Code kann wie folgt aussehen:

static void CreateCustomerSimple_I(Args _args)
{
    custTable custTable;
    DirPartyTable dirPartyTable;

    AccountNum accountNum       = "8888";
    currencyCode currencyCode   = "EUR";
    custGroupId custGroupId     = ""; 
    taxGroup taxGroup           = "";       

    // Create or update Customer
    ttsBegin;
    CustTable = CustTable::find(accountNum, true);  // Find existing record for update
    
    custTable.AccountNum = AccountNum;
    custTable.Currency   = currencyCode;
    custTable.CustGroup  = custGroupId;
    custTable.TaxGroup   = taxGroup;

    custTable.write();  // Calls insert() or update()
    ttsCommit;
}

 

Nachteile dieses einfachen Beispiels:

  • Keine Initialisierung des Datensatzes
    Es wird keine Logik aufgerufen, die bestimmte Felder eines neuen Datensatz initalisiert (CustTable.initValue())
  • Kein Prüfung von Pflichtfelder
    Weder jene die im AOT als Mandatory gekennzeichnet sind (beispielsweise CustTable.CustGroup), noch solche die über die Programmlogik nur unter bestimmten Umständen ausgefüllt werden müssen (beispielsweise CustTable.TaxGroup).
  • Keine Prüfung von Inhalten
    Man kann in die Felder - solange es der Datentyp zulässt - irgendwelche Werte reinschreiben, beispielsweise auch Debitorengruppen, die im AX gar nicht vorhanden sind.
  • Auch werden Felder auf Basis anderer Felder nicht initialisert
    Beispielsweise werden normalerweise, wenn man eine Debitorengruppe einträgt, Standardwerte diese Gruppe in den Debitoren übernommen (Feld PaymTermId über die Methode initFromCustGroup())


Vorteile dieser Variante:

  • Das selbe Prinzip kann man für (fast) alle Tabellen verwenden

Da aus meiner Sicht die Nachteile überwiegen, würde ich persönlich die obige Variante nur in Ausnahmefällen verwenden.

Etwas besser ist die folgende Logik/der folgende Job, der prinzipiell immer noch wie oben arbeitet, d.h. wir schreiben nach wie vor direkt in die Tabelle hinein. Allerdings ist dieser Job bereits um einige Prüfungen erweitert worden, sodaß wir hier eher die Chance haben, daß unsere Entität genauso aussieht, wie wenn diese über das entsprechende (Debitoren-)Formular angelegt worden wäre.

Es werden die Methoden initValue(), validateField(), modifiedField() und validateWrite() explizit aufgerufen. Diese wiederrum führen jeweils weitere Logik aus.

static void CreateCustomerSimple_II(Args _args)
{
    custTable custTable;

    AccountNum accountNum     = "8888";
    currencyCode currencyCode = "EUR";
    custGroupId custGroupId   = "DINL";
    taxGroup taxGroup         = "DINL";

        ttsBegin;

        // Create or update Customer
        CustTable = CustTable::find(accountNum, true);

        if( !CustTable)
        {
            CustTable.initValue();
        }

        custTable.AccountNum = AccountNum;
        if( !custTable.validateField(fieldNum(custTable, accountNum)))
        {
            throw error("@SYS96731");
        }
        custTable.modifiedField(fieldNum(custTable, accountNum));

        custTable.Currency = currencyCode;
        if( !custTable.validateField(fieldNum(custTable, Currency)))
        {
            throw error("@SYS96731");
        }
        custTable.modifiedField(fieldNum(custTable, Currency));

        custTable.CustGroup = custGroupId;
        if( !custTable.validateField(fieldNum(custTable, CustGroup)))
        {
            throw error("@SYS96731");
        }
        custTable.modifiedField(fieldNum(custTable, CustGroup));

        custTable.TaxGroup = taxGroup;
        if( !custTable.validateField(fieldNum(custTable, CustGroup)))
        {
            throw error("@SYS96731");
        }
        custTable.modifiedField(fieldNum(custTable, TaxGroup));

        if(custTable.validateWrite())
        {
            custTable.write();  // Calls insert() or update()
        }
        else
        {
            throw error("@SYS96731");
        }
        
        ttsCommit;
}

Wie gesagt, macht dieser Job prinzipiell genau das selbe wie die ganz oben stehende Variante, nur etwas "eleganter". Allerdings haben wir die Entität, nämlich unseren Debitoren, immer noch nicht vollständig angelegt!

Beispielsweise ist im Datenmodell von Dynamics AX 2012 nämlich nicht vorgesehen, daß der Name des Debitoren direkt in der Tabelle CustTable gespeichert wird. Dieser wird im sog. Globalen Adressbuch verspeichert und diesen Umstand berücksichtigt der folgende Job. Auch besitzt dieser nun ein rudimentäres Errorhandling.

static void CreateCustomerSimple_III(Args _args)
{
    custTable custTable;
    DirPartyTable dirPartyTable;

    AccountNum accountNum = "6666";
    Name name = "Debitor 47135";
    currencyCode currencyCode = "EUR";
    custGroupId custGroupId = "DINL";
    taxGroup taxGroup = "DINL";
    languageId languageId = "de-AT";

    try
    {
        ttsBegin;

        // Create or update Customer
        CustTable = CustTable::find(accountNum, true);

        if( !CustTable)
        {
            CustTable.initValue();
        }

        custTable.AccountNum = AccountNum;
        if( !custTable.validateField(fieldNum(custTable, accountNum)))
        {
            throw error("@SYS96731");
        }
        custTable.modifiedField(fieldNum(custTable, accountNum));

        custTable.Currency = currencyCode;
        if( !custTable.validateField(fieldNum(custTable, Currency)))
        {
            throw error("@SYS96731");
        }
        custTable.modifiedField(fieldNum(custTable, Currency));

        custTable.CustGroup = custGroupId;
        if( !custTable.validateField(fieldNum(custTable, CustGroup)))
        {
            throw error("@SYS96731");
        }
        custTable.modifiedField(fieldNum(custTable, CustGroup));

        custTable.TaxGroup = taxGroup;
        if( !custTable.validateField(fieldNum(custTable, CustGroup)))
        {
            throw error("@SYS96731");
        }
        custTable.modifiedField(fieldNum(custTable, TaxGroup));

        if(custTable.validateWrite())
        {
            custTable.write();  // Calls insert() or update()
        }
        else
        {
            throw error("@SYS96731");
        }
        
        // Update party
        dirPartyTable = DirPartyTable::findRec(custTable.Party, true);

        dirPartyTable.Name = name;
        if( !dirPartyTable.validateField(fieldNum(dirPartyTable, Name)))
        {
            throw error("@SYS96731");
        }
        dirPartyTable.modifiedField(fieldNum(dirPartyTable, Name));

        dirPartyTable.NameAlias = name;
        if( !dirPartyTable.validateField(fieldNum(dirPartyTable, NameAlias)))
        {
            throw error("@SYS96731");
        }
        dirPartyTable.modifiedField(fieldNum(dirPartyTable, NameAlias));

        dirPartyTable.LanguageId = languageId;
        if( !dirPartyTable.validateField(fieldNum(dirPartyTable, LanguageId)))
        {
            throw error("@SYS96731");
        }
        dirPartyTable.modifiedField(fieldNum(dirPartyTable, LanguageId));

        if(dirPartyTable.validateWrite())
        {
            dirPartyTable.write();  // Calls insert() or update()
        }
        else
        {
            throw error("@SYS96731");
        }
        

        ttsCommit;
    }
    catch (Exception::Error)
    {
        error("@SYS96731");
    }
}

Letztlich haben alle oben angeführten Jobs zumindest einen Nachteil, versucht man nämlich beispielsweise einen 100 Zeichen langen Text in ein Textfeld einzufügen, welches lt. EDT nur 60 Zeichen fassen kann, so wird dieser Text kommentarlos abgeschnitten. 


 
 

AX 2012: Erstellen einer DefaultDimension mit mehreren Dimensionen

Nachstehend ein Code-Beispiel, wie man eine RecId vom Typ DefaultDimension für mehrere Dimensionen generieren kann.

Im Beispiel werden die Dimension Kostenstelle mit dem Wert "10", die Dimension Abteilung mit dem Wert "IT" und die Dimension Kategorie mit dem Wert "02" zu einer DefaultDimension kombiniert.

Durch den letzten Parameter von findByDimensionAttributeAndValue() wird sichergestellt, daß fehlende Dimensionen angelegt werden (soferne möglich).

static void buildDefaultDimension(Args _args)
{
    dimensionAttributeValueSetStorage dimensionAttributeValueSetStorage;
    dimensionAttribute dimensionAttribute;
    dimensionAttributeValue dimensionAttributeValue;
    dimensionDefault dimensionDefault; 
 
    dimensionAttributeValueSetStorage = new DimensionAttributeValueSetStorage();

    // Kostenstelle
    dimensionAttribute = dimensionAttribute::findByName('Kostenstelle');
    if(dimensionAttribute)
    {
        dimensionAttributeValue = DimensionAttributeValue::findByDimensionAttributeAndValue(dimensionAttribute, '10', false, true);
        dimensionAttributeValueSetStorage.addItem(dimensionAttributeValue);
    }

    // Abteilung
    dimensionAttribute = dimensionAttribute::findByName('Abteilung');
    if(dimensionAttribute)
    {
        dimensionAttributeValue = DimensionAttributeValue::findByDimensionAttributeAndValue(dimensionAttribute, 'IT', false, true);
        dimensionAttributeValueSetStorage.addItem(dimensionAttributeValue);
    }

    // Kategorie
    dimensionAttribute = dimensionAttribute::findByName('Kategorie');
    if(dimensionAttribute)
    {
        dimensionAttributeValue = DimensionAttributeValue::findByDimensionAttributeAndValue(dimensionAttribute, '02', false, true);
        dimensionAttributeValueSetStorage.addItem(dimensionAttributeValue);
    }

    dimensionDefault = dimensionAttributeValueSetStorage.save();

    info(strFmt("Default dimension recId: %1", dimensionDefault));

 

 
 
Seiten « 1 ... 21 22 23 24 25 26 27 ... 53 » 

 

 
 
 
Beiträge des aktuellen Monats
April 2024
MoDiMiDoFrSaSo
1234567
891011121314
15161718192021
22232425262728
2930 
 
© 2006-2024 Heinz Schweda | Impressum | Kontakt | English version | Mobile Version
Diese Webseite verwendet Cookies, um Benutzern einen besseren Service anzubieten. Wenn Sie weiterhin auf der Seite bleiben, stimmen Sie der Verwendung von Cookies zu.  Mehr dazu