Tuesday, May 31, 2022

PostAdj Journal in D365FO using x++

Create and post the Adj Journal in D365FO using x++.


private void createPostAdjJournal(ItemId            _itemid,
                                        InventLocationId  _warehouse,
                                        WMSLocationId     _location,
                                        Qty               _qty,
                                        WHSLicensePlateId  _licensePlateId,
                                        InventBatchId     _batch)
    {
        InventJournalTable              inventJournalTable;
        InventJournalTrans              inventJournalTrans;
        InventJournalNameId             inventJournalName;
        InventDim                       inventDim;
        JournalCheckPost                journalCheckPost;
        WHSLicensePlate                 whsLicensePlate;
        InventLocation                  inventLocation;

        ttsbegin;

        whsLicensePlate     = WHSLicensePlate::createLicensePlate(_licensePlateId, true, "");
        inventJournalName   =  InventJournalName::standardJournalName(InventJournalType::LossProfit);

        inventJournalTable.clear();
        inventJournalTable.initFromInventJournalName(InventJournalName::find(inventJournalName));
        inventJournalTable.insert();

        //<Creation of journal Line>
        inventJournalTrans.clear();
        inventJournalTrans.initFromInventJournalTable(inventJournalTable);
        inventJournalTrans.TransDate = DateTimeUtil::getSystemDate(DateTimeUtil::getUserPreferredTimeZone());
        inventJournalTrans.ItemId    = _itemid;
        inventJournalTrans.initFromInventTable(InventTable::find(_itemid));
        inventJournalTrans.Qty       = _qty;

        inventDim.wMSLocationId      = _location;
        inventDim.InventLocationId   = 'ware house';
        inventDim.InventSiteId       = InventLocation::find('ware house').InventSiteId;
        inventDim.InventSizeId       = 'Blank';  //TODO
        inventDim.LicensePlateId     = whsLicensePlate.LicensePlateId;
        inventDim.inventBatchId      = _batch;

        //you can add here as many inventory dimensions as required
        inventJournalTrans.InventDimId = inventDim::findOrCreate(inventDim).inventDimId;
        inventJournalTrans.insert();

        //<Post Journal>
        journalCheckPost = InventJournalCheckPost::newPostJournal(inventJournalTable);

        journalCheckPost.runOperation();

        ttscommit;

        info(strFmt('Journal %1 has been posted',inventJournalTable.JournalId));
    }
Keep Daxing!!

Release the Product using Data entity in D365FO using x++

 Release the Product  using Data entity in D365FO using x++.


    public void createReleasedProduct(ItemId _itemNum, ItemName _itemName)
    {
        EcoResReleasedProductCreationV2Entity   releasedProduct;
        WHSInventTable                          whsTable;
        WHSUOMSeqGroupId                        unitSequenceId;
        InventItemGroupItem                     inventItemgroupItem;
        ItemGroupId                             itemGroupId;

        releasedProduct.ItemNumber                      = _itemNum;
        releasedProduct.ProductName                     = _itemName;
        releasedProduct.ProductNumber                   = _itemNum;
        releasedProduct.ProductSearchName               = _itemName;
        releasedProduct.VariantConfigurationTechnology  = EcoResVariantConfigurationTechnologyType::PredefinedVariants;
        releasedProduct.ProductType                     = EcoResProductType::Item;
        releasedProduct.ProductSubType                  = EcoResProductSubtype::ProductMaster;
        releasedProduct.ProductDimensionGroupName       = 'Product dimension group name';
        releasedProduct.ItemModelGroupId                = 'Item model group';
        releasedProduct.StorageDimensionGroupName       = 'Storage dimension group';
        releasedProduct.TrackingDimensionGroupName      = 'Tracking dimension group';
        releasedProduct.InventoryReservationHierarchyName = 'ReservationHierarchy';
        releasedProduct.SalesUnitSymbol                 = 'Unit';
        releasedProduct.PurchaseUnitSymbol              = 'Unit';
        releasedProduct.InventoryUnitSymbol             = 'Unit';
        releasedProduct.BOMUnitSymbol                   = 'Unit';
        // releasedProduct.ProductGroupId                  = 'ProductGroupId';

        if (releasedProduct.validateWrite())
        {
            releasedProduct.insert();
        }

        whsTable = WHSInventTable::find(_itemNum, true);

        if (!whsTable)
        {
            whsTable.ItemId         = _itemNum;
            whsTable.FilterChanged  = NoYes::Yes;
            whsTable.UOMSeqGroupId  = unitSequenceId;
            whsTable.insert();
        }
        else
        {
            whsTable.UOMSeqGroupId = unitSequenceId;
            whsTable.update();
        }

        inventItemgroupItem = InventItemgroupItem::findByItemIdLegalEntity(_itemNum);

        if (!inventItemgroupItem)
        {
            inventItemgroupItem.ItemId              = _itemNum;
            inventItemgroupItem.ItemGroupId         = itemGroupId;
            inventItemgroupItem.ItemDataAreaId      = curExt();
            inventItemgroupItem.ItemGroupDataAreaId = curExt();
            inventItemgroupItem.insert();
        }
    }

Dynamic query in D365FO

 Dynamic query in D365FO.

In dynamics we have 2 types of queries.

  • Static query
  • Dynamic query

Static queries we can create in AOT and it's less code. But If we apply the range for that query If we use that query in any where It will apply same range.

Dynamic queries we are creating in run time and apply the ranges the ranges in run time. Below are the query classes for dynamic query.

  • Query
  • QueryBuildDataSource
  • QueryBuildRange
  • QueryRun
  • QueryBuildFieldList
  • QueryFilter
  • QueryBuildDynaLink
  • QueryBuildLink

Query:

    Query query = new Query();
    query.addDataSource(tableNum(MyTable));

-------------------------------------------------------------------------------------
QueryBuildDataSource:

Way 1:
    Query query = new Query(); QueryBuildDataSource CustGroupQbds,custTableQbds; custTableQbds = query.addDataSource(tableNum(CustTable)); CustGroupQbds = custTableQbds.addDataSource(tableNum(CustGroup)); CustGroupQbds.relations(true); CustGroupQbds.joinMode(JoinMode::ExistsJoin);     
    // OR we can usw below one also CustGroupQbds.addDynalink(fieldNum(CustTable, CustGroup), fieldNum(CustGroup, CustGroup)); CustGroupQbds.addDynalink('Parent field id','Child field id');

Way 2:
    SysQuery::findOrCreateDataSource('Query', tableNum('Table')); QueryBuildDataSource batchbQbds = SysQuery::findOrCreateDataSource('Query', tableNum('Table'),//New table Id tableNum('parent table'));//existing parent table Id batchbQbds.relations(true); batchbQbds.joinMode(JoinMode::ExistsJoin);

    Example:
    Query query = new Query(); QueryBuildDataSource batchbQbds; query.addDataSource(tableNum(CustTable)); batchbQbds = SysQuery::findOrCreateDataSource(query, tableNum(CustGroup),//New table Id tableNum(CustTable));//existing parent table Id batchbQbds.relations(true); batchbQbds.joinMode(JoinMode::ExistsJoin);

------------------------------------------------------------------------------------------------------------------
QueryBuildRange:

Way 1:
    Query query = new Query(); QueryBuildDataSource custTableQbds; QueryBuildRange qbr; custTableQbds = query.addDataSource(tableNum(CustTable)); custTableQbds.addRange(FieldNum(CustTable, AccountNum)); qbr.value("Value"); query.addDataSource(tableNum(CustTable)).addRange(FieldNum(CustTable,
                                                            AccountNum)).value("Value"); 
        qr.status(RangeStatus::Locked);   // To lock

Way 2:

SysQuery::findOrCreateRange('this.queryBuildDataSource()', fieldNum(Mytable, Field)).value('Value');

Example: Query query = new Query(); query.addDataSource(tableNum(CustTable)); SysQuery::findOrCreateRange(query.dataSourceNo(1), fieldNum(CustTable, CustGroup)).value('1001');

Clear ranges: query.addDataSource(tableNum(CustTable)).clearRange(FieldNum(CustTable, AccountNum)); query.addDataSource(tableNum(CustTable)).clearRanges();
---------------------------------------------------------------------------------------
QueryRun:

QueryRun qr = new QueryRun ("query"); CustTable custTable; while (qr.next()) { custTable = qr.GetNo(1); } qr.prompt() // Open the window in UI.
-----------------------------------------------------------------------------------------------------------------------
QueryBuildFieldList:

QueryBuildFieldList qbdList = qbds.fields(); qbdList.addField(fieldNum(CustTable, CreditMax), SelectionField::Sum); qbdList.addField(fieldnum(CustTable, RecId), SelectionField::Count);

-----------------------------------------------------------------------------------------------------------------------
QueryFilter:

    Query query = new Query(); QueryBuildDataSource custTableQbds; QueryFilter accountNumFilter; custTableQbds = query.addDataSource(tablenum(custTable)); accountNumFilter = query.addQueryFilter(custTableQbds, 'AccountNum'); accountNumFilter.value('Value');

-----------------------------------------------------------------------------------------------------------------------

QueryBuildLink:

    Query query = new Query(); QueryBuildLink qbdLink; QueryBuildDataSource custTableQbds, CustGroupQbds; custTableQbds = query.addDataSource(tablenum(custTable)); CustGroupQbds = custTableQbds.addDataSource(tableNum(CustGroup)); qbdLink = CustGroupQbds.addLink(fieldNum(CustTable, CustGroup), fieldNum(CustGroup, CustGroup)); -----------------------------or--------------------------- CustGroupQbds.range(true); CustGroupQbds.joinMode(JoinMode::InnerJoin);  

    -----------------------------or---------------------------     

    query.dataSourceTable(tableNum(CustGroup)).addDynalink( fieldNum(CustGroup, CustGroup), CustTable,//Parent table fieldNum(CustTable, CustGroup));//Parent table field

--------------------------------------------------------------------------------------

Query add sort field:

qbds.addSortField(fieldNum(custTable, AccountNum), SortOrder::Descending);


-------------------------------------------------------------------------------------

Query add group by:

    Query q = new Query(); QueryBuildDataSource qbds; QueryRun qr; QueryBuildFieldList qbfl; CustSalesOpenLines custSalesOpenLines; qbds = q.addDataSource(tableNum(CustSalesOpenLines)); qbds.firstOnly(true); qbfl = qbds.fields(); qbfl.addField(fieldNum(CustSalesOpenLines, SalesId)); qbfl.addField(fieldNum(CustSalesOpenLines, LineAmount), SelectionField::Sum); qbds.addGroupByField(fieldNum(CustSalesOpenLines, SalesId)); q.addHavingFilter(qbds, fieldStr(CustSalesOpenLines, LineAmount), AggregateFunction::Sum).value('>0');


Multiple Ranges:
  QueryBuildDataSource ledgerJournalTrans_DS = _contract.getQuery().dataSourceTable(tableNum(LedgerJournalTrans));

    QueryBuildRange qbr = SysQuery::findOrCreateRange(ledgerJournalTrans_DS, fieldNum(LedgerJournalTrans, DataAreaId));

    qbr.value(strFmt("(%1.%2 == %3) || (%1.%4 == %3)",
				ledgerJournalTrans_DS.name(),
				fieldStr(LedgerJournalTrans, AccountType), 
				enum2int(LedgerJournalACType::Bank),
				fieldStr(LedgerJournalTrans, OffsetAccountType)));

	qbr.status(RangeStatus::Hidden);
	queryRun = new queryRun(_contract.getQuery());

Keep daxing!!

How to send range value for dynamic query in D365FO using X++

Today we see how to send range value for dynamic query in D365FO using X++.
As per requirements we need to send different ranges. Mostly all ranges are covered below.

    Query               query = new Query();
    QueryBuildRange     qbr;

    qbr = query.addDataSource(tableNum(CustTable)).addRange(fieldNum(CustTable, CustGroup));
        
    qbr.status(RangeStatus::Locked); // To lock

    qbr.value('Value');//If record not found It will filter remaing records.
    qbr.value('4000..4050');//between operator
    qbr.value(queryValue('Value'));//Search with only sending value.
    qbr.value(SysQuery::value('Value'));//Search with only sending value.

    //like operator
    qbr.value(SysQuery::valueLike('Value'));

    //Filter without the sending value
    qbr.value(SysQuery::valueNot('Value'));

    //Retrive all records
    qbr.value(SysQuery::valueUnlimited());

    //Retrive Null records
    qbr.value(SysQuery::valueEmptyString());

    //Retrive Not Null records
    qbr.value(SysQuery::valueNotEmptyString());

    //To give range
    qbr.value(SysQuery::range(fromDate, toDate));

    //To find total number of records available in the resulting query
    info(strFmt('%1',SysQuery::countTotal(queryRun)));

    //find total number of Datasource available in the resulting query
    info(strFmt('%1',SysQuery::countLoops(queryRun)));
Multiple Ranges:
QueryBuildDataSource ledgerJournalTrans_DS = _contract.getQuery().dataSourceTable(tableNum(LedgerJournalTrans));

QueryBuildRange qbr = SysQuery::findOrCreateRange(ledgerJournalTrans_DS, fieldNum(LedgerJournalTrans, DataAreaId));

qbr.value(strFmt("(%1.%2 == %3) || (%1.%4 == %3)",
			ledgerJournalTrans_DS.name(),
			fieldStr(LedgerJournalTrans, AccountType), 
			enum2int(LedgerJournalACType::Bank),
			fieldStr(LedgerJournalTrans, OffsetAccountType)));

qbr.status(RangeStatus::Hidden);
queryRun = new queryRun(_contract.getQuery());

Keep Daxing!!

How to add data sources/ranges to query in D365Fo using x++.

Hi guys, Today we see how to add data sources/ranges to query in D365Fo using x++.


Range creation:

SysQuery::findOrCreateRange('this.queryBuildDataSource()',
                            fieldNum(Mytable, Field)).value('Value');

Example:

    Query       query = new Query();

    query.addDataSource(tableNum(CustTable));

    SysQuery::findOrCreateRange(query.dataSourceNo(1),
                                fieldNum(CustTable, CustGroup)).value('1001');

    QueryRun    qr = new QueryRun(query);

    Info(qr.toString());
    //Output : SELECT * FROM CustTable(CustTable_1) WHERE ((CustGroup = N'1001'))

    info (qr.query().dataSourceNo(1).toString());
    //Output : Query object db91850: SELECT * FROM CustTable(CustTable_1)
    //WHERE ((CustGroup = N'1001'))

----------------------------------------------------------------------------------

Adding to Datasource to query:

    SysQuery::findOrCreateDataSource('Query', tableNum('Table'));
    
    QueryBuildDataSource batchbQbds = SysQuery::findOrCreateDataSource('Query',
                                    tableNum('Table'),//New table Id
    tableNum('parent table'));//existing parent table Id
    batchbQbds.relations(true);
    batchbQbds.joinMode(JoinMode::ExistsJoin);

Example:

    Query                   query = new Query();
    QueryBuildDataSource    batchbQbds;
    query.addDataSource(tableNum(CustTable));

    batchbQbds = SysQuery::findOrCreateDataSource(query,
                            tableNum(CustGroup),//New table Id
    tableNum(CustTable));//existing parent table Id
    batchbQbds.relations(true);
    batchbQbds.joinMode(JoinMode::ExistsJoin);

    QueryRun    qr = new QueryRun(query);

    Info(qr.toString());
    // Query object 19c57b50: SELECT * FROM CustTable(CustTable_1)
    //  EXISTS JOIN * FROM CustGroup(CustGroup_1)
    //  WHERE CustTable.CustGroup = CustGroup.CustGroup

---------------------------------------------------------------------- Adding to Datasource name to query:

    QueryBuildDataSource    qbd = SysQuery::findOrCreateDataSourceByName(
                                'query',
                                'Data source name',// New table name
                                tableNum(Table),//Table Id
                                workerQbds.name(),// Parent datasource name
                                workerQbds.table());// Parent datasource Table Id
    qbd.relations(true);
    qbd.joinMode(JoinMode::InnerJoin);

Example:
    Query                   query = new Query();
    QueryBuildDataSource    batchbQbds = query.addDataSource(tableNum(CustTable));
    QueryBuildDataSource    qbd = SysQuery::findOrCreateDataSourceByName(
                                query,
                                'Test1',// New table name
    tableNum(CustGroup),//Table Id
    batchbQbds.name(),// Parent datasource name
    batchbQbds.table());// Parent datasource Table Id
    qbd.relations(true);
    qbd.joinMode(JoinMode::InnerJoin);

    QueryRun    qr = new QueryRun(query);

    // info (qr.query().dataSourceNo(1).toString());
    Info (qr.toString());
    //SELECT * FROM CustTable(CustTable_1)
    //JOIN * FROM CustGroup(Test1) ON
    //CustTable.CustGroup = CustGroup.CustGroup
------------------------------------------------------------------
Return query as string:
    Info(qr.toString());
    //Output : SELECT * FROM CustTable(CustTable_1) WHERE ((CustGroup = N'1001'))

    info (qr.query().dataSourceNo(1).toString());
    //Output : Query object db91850: SELECT * FROM CustTable(CustTable_1)
    //WHERE ((CustGroup = N'1001'))
------------------------------------------------------------------------------
Total number of records available in query:
    info(strFmt('Records - %1',SysQuery::countTotal(qr)));
Total number of loops in query:
    info(strFmt('Datasource - %1',SysQuery::countLoops(qr)));


Keep Daxing!!


Monday, May 23, 2022

How to add On hand list in custom form using x++

Today we see how to add On hand list in custom form using x++ in D365FO. I got a requirement on adding  "On hand list" form to custom form. If we add this form directly system will not open this form because while opening this form they have written few logics for filtering the data based on item.


So we need add our custom table in those methods. In the below example I am using site and warehouse forms. Just check the code. 

1st we need to write the code in "InventDim" table.

[ExtensionOf(tableStr(InventDim))]
final static class InventDim_Extension
{
    static public void formQueryAddDynalinkByQuery(
                Query           _query,
                Args            _args,
                boolean         _linkInventDim)
    {
        QueryBuildDatasource 	inventDimDS, siteDs, inventLocationDS;
        InventLocation      	inventLocation;
        InventSite          	inventSite;

        switch (_args.dataset())
        {
            case tableNum(InventLocation):
                inventLocation      = _args.record();
                inventDimDS         = _query.dataSourceTable(tableNum(InventDim));
                inventLocationDS    = inventDimDS.addDataSource(tableNum(InventLocation));

                inventLocationDS.addLink(fieldNum(InventDim, InventLocationId),
                                fieldNum(InventLocation, InventLocationId));
                inventLocationDS.joinMode(JoinMode::ExistsJoin);
                inventLocationDS.addRange(fieldNum(InventLocation, InventLocationId)).value(
                                                inventLocation.InventLocationId);

                inventDimDS.addSortField(fieldNum(InventDim, InventLocationId));
                break;

            case tableNum(InventSite):
                inventSite  = _args.record();
                inventDimDS = _query.dataSourceTable(tableNum(InventDim));
                siteDs      = inventDimDS.addDataSource(tableNum(InventSite));

                siteDs.addLink(fieldNum(InventDim, InventSiteId),fieldNum(InventSite, SiteId));
                siteDs.joinMode(JoinMode::ExistsJoin);
                siteDs.addRange(fieldNum(InventSite, SiteId)).value(inventSite.SiteId);

                inventDimDS.addSortField(fieldNum(InventDim, InventSiteId));
                break;
        }

        next formQueryAddDynalinkByQuery(_query,_args,_linkInventDim);
    }
}

2nd we need to write the code in "InventOnHandItem" form.

[ExtensionOf(formstr(InventOnHandItem))]
final class PIDInventOnHandItemForm_Extension
{
    public void init()
    {
        next init();

        if (this.args().record() && this.args().dataset() == tableNum(InventLocation))
        {
            InventSum_DS.autoSearch(true);
            isFormOpenedWithDynalink = true;
        }

        if (this.args().record() && this.args().dataset() == tableNum(InventSite))
        {
            InventSum_DS.autoSearch(true);
            isFormOpenedWithDynalink = true;
        }
    }
}


Keep Daxing!!


Create excel file and send through email in d365FO

 Hi guys, today we see how to generate excel file and send that file through email using x++ in D365FO.


using System.IO; using OfficeOpenXml; using OfficeOpenXml.Style; using OfficeOpenXml.Table; class SendEmailProcessSevice extends SysOperationServiceBase { Filename fileName; SysEmailSystemTable sysEmailTable; SysEmailMessagesystemTable messageTable; // sending Email with attachment private void sendEmail(System.IO.MemoryStream attachmentStream) { if (attachmentStream != null) { var user = xUserInfo::find(); Map sysMailers = SysMailerFactory::getMailers(); sysEmailTable = SysEmailSystemTable::find('TestEmail'); select firstonly messageTable where messageTable.EmailId == sysEmailTable.EmailId; str s = date2Str(today(), 321, DateDay::Digits2, DateSeparator::Hyphen, // separator1 DateMonth::Digits2, DateSeparator::Hyphen, // separator2 DateYear::Digits4 ); messageBuilder.setSubject(messageTable.Subject + '-' + s); messageBuilder.setBody(messageTable.Mail); messageBuilder.setFrom(sysEmailTable.SenderAddr); var message = messageBuilder.getMessage(); messageBuilder.addAttachment(attachmentStream,fileName); SysIMailer mailer = sysMailers.lookup('SMTP'); SysMailerSMTP smtp = new SysMailerSMTP(); if (mailer is SysIMailerNonInteractive) { SysIMailerNonInteractive nonInteractiveMailer = mailer; messageSent = nonInteractiveMailer.sendNonInteractive(message); } else if (mailer is SysIMailerInteractive) { SysIMailerInteractive interactiveMailer = mailer; messageSent = interactiveMailer.sendInteractive(message);// messageBuilder.getMessage(); } } else { warning("File is empty."); } } // creating excel file public void fileCreation(MyContract contract) { Query query = new Query(); MemoryStream memoryStream = new MemoryStream(); QueryRun qr; RowNumber row = 1; query.addDataSource(tablenum(Mytable)).addRange(fieldnum(Mytable, Feild1)).value(queryValue(contract.parmtype))); using (var package = new ExcelPackage(memoryStream)) { var worksheets = package.get_Workbook().get_Worksheets(); var worksheet = worksheets.Add('Test Records'); var cells = worksheet.get_Cells(); OfficeOpenXml.ExcelRange cell = cells.get_Item(row, 1); cell.set_Value('Field1'); cell = cells.get_Item(row,2); cell.set_Value('Field2'); cell = cells.get_Item(row,3); cell.set_Value('Field3'); qr = new QueryRun(query); while (qr.next()) { Mytable = qr.get(tableNum(Mytable)); row++; cell = null; cell = cells.get_item(row, 1); cell.set_Value(Mytable.Field1); cell = cells.get_item(row, 2); cell.set_Value(Mytable.Field2); cell = cells.get_item(row, 3); cell.set_Value(Mytable.Field3); } package.save(); file::SendFileToUser(memoryStream,fileName); memoryStream.Seek(0, System.IO.SeekOrigin::Begin); this.sendEmail(memoryStream); } } }


Keep Daxing !!

How to send an email in D365FO using x++

 In D365FO requirements most of them need to send  the email. So for that we can have multiple ways. Please check the below code.


Simple Way:

public void sendEmail() { var builder = new SysMailerMessageBuilder(); builder.addTo("To email"); builder.setFrom("sender email"); builder.setSubject("Subject"); builder.setBody("Body"); var message = builder.getMessage(); SysMailerFactory::getNonInteractiveMailer().sendNonInteractive(message); }


With template:

public void sendEmail() { SysEmailTable sysEmailTable = SysEmailTable::find('TestEmail');//Get from template. SysEmailMessageTable sysEmailMessageTable = SysEmailMessageTable::find(
                            sysEmailTable.EmailId, sysEmailTable.DefaultLanguage); var messageBuilder = new SysMailerMessageBuilder(); messageBuilder.addTo("To email"); messageBuilder.setFrom(sysEmailTable.SenderAddr);//, sysEmailTable.SenderName); messageBuilder.setSubject(sysEmailMessageTable.Subject); messageBuilder.setBody(sysEmailMessageTable.Mail); var message = builder.getMessage(); //SysMailerFactory::getNonInteractiveMailer().sendNonInteractive(message); SysIMailer mailer = sysMailers.lookup('SMTP'); if (mailer is SysIMailerNonInteractive) { SysIMailerNonInteractive nonInteractiveMailer = mailer; nonInteractiveMailer.sendNonInteractive(messageBuilder.getMessage()); } else if (mailer is SysIMailerInteractive) { SysIMailerInteractive interactiveMailer = mailer; interactiveMailer.sendInteractive(messageBuilder.getMessage()); } }

--------------------send mail to multiple users-------------

private void sendEmail() { SysEmailSystemTable sysEmailTable; SysEmailMessagesystemTable emailMessageTable; var user = xUserInfo::find(); Map sysMailers = SysMailerFactory::getMailers(); container conCCEmails, conToEmails; MyTable myTable; sysEmailTable = SysEmailSystemTable::find('TestEmail');//Get from template. select firstonly emailMessageTable where emailMessageTable.EmailId == sysEmailTable.EmailId; //emailMessageTable = SysEmailMessageTable::find(sysEmailTable.EmailId, // sysEmailTable.DefaultLanguage); select * from myTable; conCCEmails = str2con(myTable.CCEmailFR,';');// adding emails in CC; ConToEmails = str2con(myTable.ToEmail,';');// To emails. var messageBuilder = new SysMailerMessageBuilder(); for (int i=1; i<=conLen(conCCEmails); i++) { messageBuilder.addCc(conPeek(conCCEmails, i)); } for (int i=1; i<=conLen(ConToEmails); i++) { messageBuilder.addTo(conPeek(ConToEmails, i)); } str curDate = date2Str(today(), 321, DateDay::Digits2, DateSeparator::Hyphen, DateMonth::Digits2, DateSeparator::Hyphen, DateYear::Digits4 ); // We can use below one also /*var messageBuilder = new SysMailerMessageBuilder(); messageBuilder.addTo(myTable.ToEmail) .setSubject(emailMessageTable.Subject) .setBody(emailMessageTable.Mail);*/ //get subject from template messageBuilder.setSubject(emailMessageTable.Subject + '-' + curDate); messageBuilder.setBody(emailMessageTable.Mail);//get body from template messageBuilder.setFrom(sysEmailTable.SenderAddr);// Sender email var message = messageBuilder.getMessage(); messageBuilder.addAttachment(attachmentStream,fileName);//Attach file // We can use below one also //SysMailerFactory::getNonInteractiveMailer().sendNonInteractive(message); SysIMailer mailer; SysMailerSMTP smtp = new SysMailerSMTP(); mailer = sysMailers.lookup('SMTP'); if (mailer is SysIMailerNonInteractive) { SysIMailerNonInteractive nonInteractiveMailer = mailer; nonInteractiveMailer.sendNonInteractive(messageBuilder.getMessage()); } else if (mailer is SysIMailerInteractive) { SysIMailerInteractive interactiveMailer = mailer; interactiveMailer.sendInteractive(messageBuilder.getMessage()); } }

Using Email processing table:


public void sendEmail() { SysEmailTable sysEmailTable = SysEmailTable::find('TestEmail'); SysEmailMessageTable sysEmailMessageTable; SysOutgoingEmailTable sysoutgoingEmailTable; //SysOutgoingEmailData outgoingEmailData; sysEmailMessageTable = SysEmailMessageTable::find(sysEmailTable.EmailId, sysEmailTable.DefaultLanguage); sysoutgoingEmailTable.EmailItemId = EventInbox::nextEventId(); sysoutgoingEmailTable.TemplateId = sysEmailTable.EmailId; sysoutgoingEmailTable.Sender = sysEmailTable.SenderAddr; sysoutgoingEmailTable.SenderName = sysEmailTable.SenderName; sysoutgoingEmailTable.Recipient = 'receiver@test.com'; sysoutgoingEmailTable.Subject = sysEmailMessageTable.Subject; sysoutgoingEmailTable.Message = sysEmailMessageTable.Mail; sysoutgoingEmailTable.Priority = sysEmailTable.Priority; sysoutgoingEmailTable.WithRetries = true; sysoutgoingEmailTable.RetryNum = 0; sysoutgoingEmailTable.UserId = curuserid(); sysoutgoingEmailTable.Status = SysEmailStatus::Unsent; sysoutgoingEmailTable.LatestStatusChangeDateTime = DateTimeUtil::getSystemDateTime(); sysoutgoingEmailTable.insert(); }

Need to run below job.

System administration>Periodic tasks>Email processing>Email distributor batch.

With placeholder:

public void sendEmail() { SysEmailTable sysEmailTable = SysEmailTable::find('TestEmail'); SysEmailMessageTable sysEmailMessageTable = SysEmailMessageTable::find(sysEmailTable.EmailId, sysEmailTable.DefaultLanguage); Map placeholder = new Map(Types::String, Types::String); placeholder.insert("Name", sysEmailTable.SenderName); placeholder.insert("Date", strFmt("%1", DateTimeUtil::getSystemDateTime())); str body = SysEmailMessage::stringExpand(sysEmailMessageTable.Mail, placeholder); str subject = SysEmailMessage::stringExpand(sysEmailMessageTable.Subject, placeholder); var builder = new SysMailerMessageBuilder(); builder.addTo("receiver@test.com"); builder.setFrom(sysEmailTable.SenderAddr);

builder.setSubject(subject); builder.setBody(body); var message = builder.getMessage(); SysMailerFactory::getNonInteractiveMailer().sendNonInteractive(message); }


Keep daxing!!

update custom fields in BankAccountTrans from LedgerJournalTrans during the posting of journal in D365FO

 update custom fields in BankAccountTrans from LedgerJournalTrans during the posting of the journal in D365FO. Here I am writing the code in the insert method for this I have used a pre-event handler. Check the below code.


class BankAccountTrans_Events
{
    [PreHandlerFor(tableStr(BankAccountTrans), tableMethodStr(BankAccountTrans, insert))]
    public static void BankAccountTrans_Pre_insert(XppPrePostArgs args)
    {
        BankAccountTrans    bankAccountTrans = Args.getThis();
        LedgerJournalTrans  ledgerJournalTrans;

        if (bankAccountTrans.SourceRecId && bankAccountTrans.SourceTableId 
                && bankAccountTrans.SourceTableId == tableNum(LedgerJournalTrans))
        {
            select firstonly1 ledgerJournalTrans
                where ledgerJournalTrans.RecId == bankAccountTrans.SourceRecId;

            bankAccountTrans.NewField1 = ledgerJournalTrans.NewField1;
            bankAccountTrans.NewField2 = ledgerJournalTrans.NewField2;
        }
    }
}


Keep daxing!!


Ref : https://allaboutdynamic.com/2018/06/23/d365-ax7-update-custom-fields-in-bankaccounttrans-from-ledgerjournaltrans/

Update Custom Fields In CustTrans/VendTrans From LedgerJournalTrans during the posting of journal in D365FO.

 For Update Custom Fields In CustTrans/VendTrans From LedgerJournalTrans during the posting of the journal in D365FO. We have 2 ways.


Way 1:

For Vend trans:

[ExtensionOf(classStr(VendVoucherJournal))]
final class VendVoucherJournal_Extension
{
    protected void initCustVendTrans(CustVendTrans custVendTrans, 
                                        LedgerVoucher _ledgerPostingJournal, 
                                        boolean _useSubLedger = false)
    {
        LedgerJournalTrans    ledgerJournalTrans;
        VendTrans             vendTrans;

        next initCustVendTrans(custVendTrans, _ledgerPostingJournal, _useSubLedger);

        if (common.TableId == tablenum(LedgerJournalTrans))
        {
            ledgerJournalTrans = common;
            vendTrans = custVendTrans;


            // Add new fields here.
            vendTrans.NewField1 = ledgerJournalTrans.NewField1;

            custVendTrans = vendTrans;

        }
    }

}


For cust trans:

[ExtensionOf(classStr(CustVoucherJournal))]
final class CustVoucherJournal_Extension
{
    protected void initCustVendTrans(
                                CustVendTrans custVendTrans,
                                LedgerVoucher _ledgerPostingJournal,
                                boolean _useSubLedger = false)
    {
        LedgerJournalTrans ledgerJournalTrans;
        recId              recId;
        CustTrans          custTrans;


        next initCustVendTrans(custVendTrans, _ledgerPostingJournal, _useSubLedger);

        if (common.TableId == tableNum(LedgerJournalTrans))
        {
            recId               = common.RecId;
            ledgerJournalTrans  = LedgerJournalTrans::findRecId(recId, false);
            //ledgerJournalTrans = common;

            custTrans = custVendTrans;

            // Add new fields here.
            custTrans.NewField1 = ledgerJournalTrans.NewField1;

            custVendTrans = custTrans;// need to reassign the buffer
        }
    }
}



Way 2:

[ExtensionOf(classStr(CustVendVoucher))]
final class CustVendVoucher_Extension
{

    public void post(
        LedgerVoucher _ledgerPostingJournal,
        CustVendTrans _custVendTrans,
        NoYes _approval,
        UnknownNoYes _euroTriangulation,
        boolean _withHoldTaxType,
        boolean _useSubLedger)
    {

        LedgerJournalTrans  ledgerJournalTransLocal;
        VendTrans           vendTrans;
        CustTrans           custTrans;

        next post(_ledgerPostingJournal, _custVendTrans, 
                    _approval, _euroTriangulation, 
                    _withHoldTaxType, _useSubLedger);

        if (common.TableId == tableNum(LedgerJournalTrans))
        {
            ledgerJournalTransLocal = common;

            if (_custVendTrans.tableid == tableNum(vendTrans))
            {
                ttsbegin;

                select forupdate vendTrans
                    where vendTrans.RecId == _custVendTrans.RecId;

                if (vendTrans.RecId)
                {
                    vendTrans.NewField1 = ledgerJournalTransLocal.NewField1;
                    vendTrans.Update();
                }
                ttscommit;
            }
            else if (_custVendTrans.tableid == tableNum(CustTrans))
            {
                ttsbegin;

                select forupdate custTrans
                    where custTrans.RecId == _custVendTrans.RecId;

                if (custTrans.RecId)
                {
                    custTrans.NewField1 = ledgerJournalTransLocal.NewField1;
                    custTrans.Update();
                }
                ttscommit;
            }
        }
    }
}


Ref : update-custom-fields-in-custtrans-vendtrans-from-ledgerjournaltrans-during-the-posting-of-journal


Keep Daxing!!

Sunday, May 22, 2022

Get Application Environment link IN D365FO

 I got a requirement to get the current application environment and I need to pass to some other integration. Just check the below code.


public static void getEnvironmentAddr() {    
    var env;

    env = Microsoft.Dynamics.ApplicationPlatform.Environment.EnvironmentFactory::GetApplicationEnvironment();

    Info(strFmt("%1", env.Infrastructure.FullyQualifiedDomainName));               

Info(strFmt("%1", env.Infrastructure.HostUrl));// with https

}

Keep daxing!!

Open table browser in D365FO

If we want to run the class or open the table browser from UI we need the link. Please check the below links.


 Run the class from UI:

    Url/?mi=SysClassRunner&prt=initial&debug=vs%2CconfirmExit
            &cls='RunnableClass or class name'&cmp=USMF

    URL : https://'System link'.axcloud.dynamics.com


 Open table browser from UI:
    URL/?mi=SysTableBrowser&prt=initial&limitednav=true
        &TableName="table name"&cmp=usmf




Keep daxing!!

Add inner node while creating JSON file using x++

Hi guys, while creating the JSON file we need to add an inner/separate node in most of the requirements. Just check the below 2 examples. Here I am using the "JsonTextWriter" class. WriteStartArray()WriteEndArray()methods used to create inner node in JSON file.


Example 1:

JSON:

{

"CustGroup": "10", "Name": "Wholesales customers", "CustomerDetails": [ { "CustAccount": "US-003", "CustomerName": "Wholesales1" }, { "CustAccount": "US-004", "CustomerName": "Wholesales2" }, { "CustAccount": "US-006", "CustomerName": "Wholesales3" } ] }

Code:


public static void main(Args _args) { System.IO.StringWriter stringWriter; Newtonsoft.Json.JsonTextWriter jsonWriter; CustTable custTable; CustGroup custGroup; stringWriter = new System.IO.StringWriter(); jsonWriter = new Newtonsoft.Json.JsonTextWriter(stringWriter); select firstonly custGroup; str jsonString = ""; jsonWriter.WriteStartObject();// add '{' jsonWriter.WritePropertyName("CustGroup"); jsonWriter.WriteValue(custGroup.CustGroup); jsonWriter.WritePropertyName("Name"); jsonWriter.WriteValue(custGroup.Name); // print customer which is attached to cust group. jsonWriter.WritePropertyName("CustomerDetails"); jsonWriter.WriteStartArray();// add '[' while select custTable where custTable.CustGroup == custGroup.CustGroup { jsonWriter.WriteStartObject();// add '{' jsonWriter.WritePropertyName("CustAccount"); jsonWriter.WriteValue(custTable.AccountNum); jsonWriter.WritePropertyName("CustomerName"); jsonWriter.WriteValue(custTable.name()); jsonWriter.WriteEndObject();// add '}' } jsonWriter.WriteEndArray();// add ']' jsonWriter.WriteEndObject();// add '}' jsonString = stringWriter.ToString(); Info(strFmt("%1", stringWriter.ToString())); }

Example 2:

JSON:

{ "CustGroup": "10", "Name": "Wholesales customers", "CustomerDetails": { "CustAccount": "US-003", "CustomerName": "Wholesales3" } }

Code:

public static void main(Args _args) { System.IO.StringWriter stringWriter; Newtonsoft.Json.JsonTextWriter jsonWriter; CustTable custTable; CustGroup custGroup; stringWriter = new System.IO.StringWriter(); jsonWriter = new Newtonsoft.Json.JsonTextWriter(stringWriter); select firstonly custGroup; str jsonString = ""; jsonWriter.WriteStartObject();// add '{' jsonWriter.WritePropertyName("CustGroup"); jsonWriter.WriteValue(custGroup.CustGroup); jsonWriter.WritePropertyName("Name"); jsonWriter.WriteValue(custGroup.Name); // print customer which is attached to cust group. jsonWriter.WritePropertyName("CustomerDetails"); select custTable where custTable.CustGroup == custGroup.CustGroup; jsonWriter.WriteStartObject();// add '{' jsonWriter.WritePropertyName("CustAccount"); jsonWriter.WriteValue(custTable.AccountNum); jsonWriter.WritePropertyName("CustomerName"); jsonWriter.WriteValue(custTable.name()); jsonWriter.WriteEndObject();// add '}' jsonWriter.WriteEndObject();// add '}' jsonString = stringWriter.ToString(); Info(strFmt("%1", stringWriter.ToString())); }


Keep daxing!!

Tuesday, May 10, 2022

How to Serialize JSON file in d365FO

Hi guys, Today we see how to Serialize JSON files in d365FO.

For this, I am using the contract class.


Contract class:

[DataContractAttribute]
public class MyTestContract
{
    str     id;
    Name    name;


    [DataMemberAttribute('id')]
    public str parmId(str _id = id)
    {
        id = _id;

        return id;
    }

    [DataMemberAttribute('Name')]
    public str parmName(Name _name = name)
    {
        name = _name;

        return name;
    }

}

Main method:

For the single record:

 public static str getJson(Id  _id, Name  _name)

{
    MyTestContract    myTestContract = new MyTestContract();

    myTestContract.parmId(_id);
    myTestContract.parmName(_name);

    return FormJsonSerializer::serializeClass(myTestContract);// creates json file
}

Output:

{"id":"Test 001","Name":"Testing"}


For multiple records:

 public static void getJson()

{
    str             json;
    CustGroup       custGroup;
    List            resultSet = new List(Types::Class);

    while select custGroup
    {
        MyTestContract    myTestContract = new MyTestContract();

        myTestContract.parmId(custGroup.CustGroup);
        myTestContract.parmName(custGroup.Name);

        resultSet.addEnd(myTestContract);
    }

    json = FormJsonSerializer::serializeClass(resultSet);
 
    info(strFmt("%1", json));
}

Output:

[{"id":"Test 001","Name":"Testing1"},
{"id":"Test 002","Name":"Testing2"},
{"id":"Test 003","Name":"Testing3"},
{"id":"Test 004","Name":"Testing4"},
{"id":"Test 005","Name":"Testing5"}]
Keep daxing!!



An exception occured when deserializing a parameters - Exception occurred when parsing and deserializing parameter 'record' - 'Parameter 'record' is not found within the request content body

I  have created the custom service while testing from postman I am getting below error.

"An exception occured when deserializing a parameters - Exception occurred when parsing and deserializing parameter 'record' - 'Parameter 'record' is not found within the request content body"


Used JSON File:

{

        "ID": "D0001",

        "EffectiveDate": "2022-06-10",

        "IsTrue": "No",

        ''DueDate" :"2022-07-10"

}


My service class method:

public str getUpdate(MyImportContract contract) { try {     ttsbegin; infolog.clear(); contract.parmType();

       // My code

   }

}

Solution:

In the above example, I am directly passing element values I am not giving the reference i.e the reason I .am getting the above error. To rectify this follow the below steps.

Step 1: Need to change the JSON file format.

Step 2: Need to change the logic in the service method.


Getting a single record:

For a single record, no need to change the logic just change the JSON format.

JSON:

{

        "record" : {

                            "ID": "D0001",

                            "EffectiveDate": "2022-06-10",

                            "IsTrue": "No",

                            ''DueDate" :"2022-07-10"

         }

}


// record is a contract class buffer in the service method parameter.

    public str getUpdate(MyImportContract record)


Getting multiple records(list of records):

For this need to change the JSON file format and need to change logic. Need to use the list as a parameter for getting multiple records.

JSON:

{

    "record": [

        {

            "ID": "140127-000003",

            "EffectiveDate": "2022-06-05",

            "IsTrue": "Yes",

            "DueDate": "2022-06-05T13:49:51.141Z"

        }

    ]

}


Logic:

[AifCollectionType('record', Types::Class, classStr(MyContract))] public str getLotUpdate(list record) { try { ttsbegin; ListEnumerator listEnum = record.getEnumerator(); while(listEnum.moveNext()) { infolog.clear(); contract = listEnum.current(); // Logic } } }


Keep Daxing!!