Pages

Showing posts with label axapta. Show all posts
Showing posts with label axapta. Show all posts

Thursday, June 11, 2026

Unable to Login to Dataverse using cached credentials - D365 F&O Unified Environment

  


If you repeatedly receive this error when trying to connect to your Dynamics 365 Finance & Operations Unified environment, it means your local credential cache is likely corrupt.


Solution:

Close Visual Studio, delete the extension credential cache at...

%AppData%\Roaming\Microsoft\CRMDeveloperToolKit

 


Then relaunch Visual Studio and connect again. You'll be prompted to authenticate and a new credential cache will be created.

Thursday, October 17, 2013

How to create a separate transaction using UserConnection to ensure your transaction is not rolled back at a higher level

Sometimes, you may have a need to have some code commit the SQL call it is making, regardless of what is happening with the code that called it, and the easiest way to do that is by creating a separate user transaction.

The reason I needed to do this was I have a static method MyCode::SendEmail(...) that would send an email and drop a log-record into a table to let me know that the email was sent.  So if it was called by some code in a transaction, and then for whatever reason, that transaction had to be rolled back, I still wanted a record of that email being sent...because it was actually sent.

So to do this, you just create a new UserConnection(), do userConnection.ttsBegin(), and then assign your table to that connection via table.setConnection(userConnection), and then a userConnection.ttsCommit() at the end.  See my attached sample job.



static void HowToCreateSeperateTransaction(Args _args)
{
    Table1          table1;
    boolean         doAbort = true;
    UserConnection  userConnection = new UserConnection();
    ;

    delete_from table1;
    
    ttsbegin;
    
    // This row "1" will only appear when the trans is not aborted
    table1.Field1 = '1';
    table1.insert();

    // This row "2" should always appear no matter what
    userConnection.ttsbegin();
    table1.clear();
    table1.setConnection(userConnection);
    table1.Field1 = '2';
    table1.insert();
    userConnection.ttscommit();

    if (doAbort)
        ttsabort;
    else
        ttscommit;
    
    new SysTableBrowser().run(tableNum(Table1));
}

Wednesday, October 9, 2013

How to find what projects an object from the AOT exists in

Where I work, we are required to create a new, numbered project for every modification request.  The numbered projects have corresponding documentation for the intended purpose.

So when I later want to find out what projects an AOT object exists in, I needed a way to search inside every project.  I wrote this proof of concept job on how to find in what projects a specific object exists.  Enjoy!


static void FindWhatProjectsObjectExistsIn(Args _args)
{
    ProjectNode         pn;
    ProjectListNode     projectListNode;

    TreeNode            tn, tn2;
    TreeNodeIterator    tni, tni2;

    // Object we are searching for
    TreeNode            tnSearch = TreeNode::findNode(@'\Forms\SalesTable');
    ;

    projectListNode = SysTreeNode::getSharedProject();
    tni = projectListNode.AOTiterator();

    tn = tni.next();

    while (tn)
    {
        pn = tn; // ProjectNode inherits TreeNode
        pn = pn.loadForInspection();

        tni2 = pn.AOTiterator();

        tn2 = tni2.next();

        while (tn2)
        {
            if (tn2.treeNodePath() == tnSearch.treeNodePath())
                info(strfmt("Found in shared project %1", tn.AOTname()));

            // info(tn2.applObjectType()); // Returns the type (Form/Class/Table/Etc)
            // info(tn2.AOTname()); // Returns the object name
            // info(tn2.treeNodePath()); // Returns the object path
            
            tn2 = tni2.next();
        }

        tn = tni.next();
    }
}

Friday, September 27, 2013

How to post a ledger transaction with help from LedgerJournalEngine

This is just some sample code I put together for somebody asking how to post customer payment line(s) using LedgerJournalEngine* classes.

LedgerJournalEngine* classes are mostly used by the forms to do work and execute code before/after events and datasource actions.

It may make more sense to just complete all of the LedgerJournalTrans fields, but using the engine can't really hurt and might be more helpful in some cases.


static void Job81(Args _args)
{
    LedgerJournalEngine_CustPayment ledgerJournalEngine;
    LedgerJournalTable              ledgerJournalTable;
    LedgerJournalTrans              ledgerJournalTrans;
    NumberSeq                       numberSeq;
    Voucher                         voucher;
    ;

    // This just selects the header you are inserting into
    select firstonly ledgerJournalTable where ledgerJournalTable.JournalNum == 'GB 0056226';

    if (!ledgerJournalTable)
        throw error ("Unable to find journal table record");


    ledgerJournalTrans.initValue();

    numberSeq = NumberSeq::newGetNumFromCode(ledgerJournalTable.VoucherSeries);

    if (numberSeq)
    {
        ledgerJournalTrans.Voucher      = numberSeq.num();
        voucher                         = ledgerJournalTrans.Voucher;
    }

    ledgerJournalTrans.JournalNum       = ledgerJournalTable.JournalNum;
    ledgerJournalTrans.TransDate        = SystemDateGet();
    ledgerJournalTrans.AccountType      = LedgerjournalACType::Cust;
    ledgerJournalTrans.AccountNum       = '100003';

    ledgerJournalEngine = LedgerJournalEngine::construct(LedgerJournalType::CustPayment);
    ledgerJournalEngine.newJournalActive(ledgerJournalTable);
    ledgerJournalEngine.accountModified(ledgerJournalTrans);
    ledgerJournalTrans.AmountCurCredit  = 10;
    ledgerJournalTrans.OffsetAccountType    = ledgerJournalTable.OffsetAccountType;
    ledgerJournalTrans.OffsetAccount        = ledgerJournalTable.OffsetAccount;
    ledgerJournalTrans.CurrencyCode         = CompanyInfo::standardCurrency();
    ledgerJournalEngine.currencyModified(ledgerJournalTrans);
    ledgerJournalTrans.insert();

    if (numberSeq   && ledgerJournalTrans.Voucher   == voucher)
    {
        numberSeq.used();
    }
    else
    {
        if (numberSeq)
            numberSeq.abort();
    }

    info("Done");
}

Thursday, September 26, 2013

How to properly send emails with the built in AX framework and templates

I often see developers reinventing the wheel or using copy/paste code to send emails from X++, when Microsoft has included a very robust emailing framework to use.  It's especially important to use once you start growing and sending many emails.  This is article is focused on AX 2009, but I'd like to think it applies to AX 2012.

A few of the features that I specifically like, that I think make it more valuable than simple X++ email sending routines are:

  • Traceability - You can choose if you want a static version of an email to be retained in the database.  This easily lets you know what emails were sent, when, and the contents of the message
  • Retry schedule - You can setup a retry schedule in case there are failures
  • Templates - You can setup HTML or XSLT templates where you pass arguments that are plugged into the subject/body. Developers can stay concerned with technical aspects and leave the look of the email to functional users.  There is also a 'pretty' editor if functional users don't want to learn HTML.
  • Multiple language support - The framework is designed around multiple languages and labels
  • Ease of use - Once you understand the framework, attachments, priority, traceability, etc., it becomes extremely easy to use all over the system and helps reduce failure points

Relevant configuration/setup forms:
  • Administration>Setup>E-mail Parameters
    • Here you setup your SMTP parameters that are specific to your organization
  • Administration>Periodic>E-mail processing>Retry Schedule
    • Here you setup your retry schedule
  • Administration>Periodic>E-mail processing>E-mail sending status
    • When you set emails to be 'traceable', they are queued here.  Take note of the checkbox to show sent emails
  • Administration>Periodic>E-mail processing>Batch
    • This batch is what will send emails that are awaiting in the above queue
    • This lets you control when emails get sent.  Some light customization and you could modify this if you wanted specific sending times for different groups of emails
Email templates:

Basic>Setup>E-mail Templates
  • The hierarchy is you define your sender/priority and optional batch group, then set your various email/language templates
  • If you need a different priority/sender/batch group, you need to create a new emailId (header section)
Take note of the General tab at the top, where you can set a senders priority and batch group.  If you are frequently changing email priority from the same sender, you can just create "Sender" and "SenderHigh" for example, then switch between those two.

The bottom section is where you can create the actual email templates for the different languages.  In the subject, you can put a default subject, or a custom replaceable "variable" such as %subject% or %AnythingYouWant%.

There are two available layouts, HTML and XSLT.  I will be working with HTML, but I will comment on XSLT.

After clicking "Template", you see where you actually type up the email template you want to use.

The variables need to start and end with the % sign, and they can be anything you want.

Once these basic setups are done, the code is easy and very re-usable.  One thing to note about the "traceable" parameter is that when you set it to false, one of two things will happen.  If your email sender has a batch group, then it will create a batch job and immediately execute, or it will just immediately send the email if it does not.











Here is a copy/paste version of the code:

static void SendEmailIntoQueueSimple(Args _args)
{
    SysEmailTable   sysEmailTable;
    Map             mappings        = new Map(Types::String, Types::String);
    // str             xml;
    ;

    // Build your variable/text mappings
    mappings.insert('subject', 'My Subject Here');
    mappings.insert('word1', 'This replaces word1');
    mappings.insert('word2', 'This replaces word2');
    mappings.insert('body', 'This will replace body with this string and this ' +
                            SysLabel::labelId2String(literalstr('@SYS25058'), 'en-us'));
    mappings.insert('TheseCanBeAnythingYouWant', 'More text');

    sysEmailTable = SysEmailTable::find('Custom');


    /*
    // If you were using XSLT, here is an example of how to build the XML
    // but you would most likely create your own static method
    xml = EventActionEmail::createEmailParameterXml(mappings);
    */

    SysEmailTable::sendMail(sysEmailTable.EmailId,
                            'en-us', // Chosen language
                            'WhoYouSendThisTo@fakemail.com', // Who you're sending the email to
                            mappings, // Your variable mappings
                            '', // Location of file attachment (server/client matters) or none
                            '' , // XML if you're using XSLT
                            true, // Traceable or not?
                            'admin', // Sending user
                            true); // Use retries?

    info("Done");
}

This is the actual email I received, where you can see the substitutions were done.




Hopefully, this post will be informative, and if I left anything out, please comment.  Happy DAX'ing!