Sample Logic app using Recurring integration in D365 fo

 Often we need to create data in D365 environment from a 3rd party system. Here this is basic logic app that can help to creates the data by taking the files from SFTP folder and and Creates data in d365.


1. In my case Im taking the trigger point as Recurrence as it should process the files at time on each end of the day. you can also take when file is added.












2. Take the next step and add the scope. our operations will be go on inside the scope as we can determine the logic app run process weather it is failed succeed aborted.








3. Now take the List files in the folder step from SFTP-SSH, this action will retrieve all the files from the specified folder.









4. Now take a for_each loop which will loop the each file which we will get the list in the above step. Pass body of the above step as the input for the for each loop. Add the action Get files content to read the content of the file.













You can take the file content below.



4. Now pass the file content to D365 by using the recurring integration API.  To know more about the RI please visit y blog here.
5. To connect to the D365 we would required a Tenet ID, Client Id, Client secret for the authentication.
for this step I'm creating one more global logic app with HTTP trigger which is used to get trigger the file to D365. because we can not take the request action inside a for_each loop.
6. For the logic app am taking the inputs as below.
{
    "properties": {
        "Audience": {
            "type": "string"
        },
        "CSVInputRequest": {
            "type": "string"
        },
        "InputRequestType": {
            "type": "string"
        },
        "JsonInputRequest": {
            "type": "object"
        },
        "Method": {
            "type": "string"
        },
        "URI": {
            "type": "string"
        }
    },
    "required": [
        "URI"
    ],
    "type": "object"
}
7. with The above inputs we need to create a below step for the web request.




8. Now from the above step we will get the failure and success response as below. 

9. Now go to original app and call the above created logic app and pass the inputs as below.

10. From the above you will get the respose with a message ID. To get the processing status of message queue I have added the below step. take the delay of 3 mins.

11. Now take Until step to get any one of  status added in the condition.

@or(equals(body('GetMessageStatus')?['value'], 'Processed'),equals(body('GetMessageStatus')?['value'], 'ProcessedWithErrors'),equals(body('GetMessageStatus')?['value'], 'PostProcessingError'),equals(body('GetMessageStatus')?['value'], 'PreProcessingError'))









12. Now trigger the getMessagestatus API by using the above created gobal logic app.

13. if it is processed we need to move this to Archive file.


14. if the status other than this we need to move this to error folder.



Follow the process step by step for any host-to-host integration using Logic Apps. Feel free to share your suggestions.

Thanks !!



Upload / read files from the FTP server using X++

In the code below, we can see how files are transferred to an FTP server using X++.

Prerequisites :

FTP Address:  The address used to connect to the FTP server.  
Username:       The authorized username required for logging into the FTP server.  
Password:        The password needed to access the FTP.  
Folder paths:   The locations of folders where files can be placed or read.


    public void sendFileToFTP(System.IO.Stream  _stream)
    {
        System.Text.Encoding 		            getUTF8;
        System.Byte[] 			                bytes;
        System.Object 			                request,response,credential;
        System.Net.FtpWebRequest 	            ftpRequest;
        System.IO.Stream 		                requestStream;
        System.Net.FtpWebResponse 	            ftpResponse;
        System.IO.StreamReader 		            reader;
        System.Char[] 			                chars;
        Str1260 			                    ftpFileName =  "ftp://<Ipaddress>//INPUT/UNPROCESSED/ "+ filename ;  // folder paths
        
        try
        {
        _stream.Position = 0;
        // Encode
        reader 	= new System.IO.StreamReader(_stream);
        getUTF8 = System.Text.Encoding::get_UTF8();
        bytes 	= getUTF8.GetBytes(reader.ReadToEnd());
 
        // Creating request
        request 	= System.Net.WebRequest::Create(new System.Uri(ftpFileName));
        ftpRequest 	= request;
 
        // Creating credentials
        credential = new System.Net.NetworkCredential('UserName', 'Password');
 
        // assign parameters to reuest
        ftpRequest.set_Credentials(credential);
        ftpRequest.set_ContentLength(bytes.get_Length());
        ftpRequest.set_Method("STOR");
     
 
        // Write file to stream
        requestStream = ftpRequest.GetRequestStream();
        requestStream.Write(bytes,0,bytes.get_Length());
        requestStream.Close();
 
        // Get respose
        response = ftpRequest.GetResponse();
        ftpResponse = response;
        info('Uploded.');
        }
        catch
        {
            error("failed");
        }
    }
public static str readFileFromSFTP(FileName _fileName)
    {
        System.Object                   ftpo;
        System.Net.FtpWebRequest        request;
        System.IO.StreamReader          reader;
        System.Net.NetworkCredential    credential;
        System.Net.FtpWebResponse       response;
        Str1260                         text;
        UserId                          usernameFTP;
        Password                        password;
        Str1260                         readingPath;
 
        try
        {
            ttsbegin;
            password     = cryptoblob2str(WinAPIServer::cryptUnProtectData('password'));
            ftpo            = System.Net.WebRequest::Create(@'FolderpathtoReadFie' + @"/" + _fileName);
            request         = ftpo;
 
            credential      = new System.Net.NetworkCredential('UserName', password);
            request.set_Credentials(credential);

            response        = request.GetResponse();
            reader          = new System.IO.StreamReader(response.GetResponseStream());
            text            = reader.ReadToEnd();
            ttscommit;
        }
        catch
        {
            error("Error reading files");
        }
        return text;
   }

Keep Daxing !!

Create and Post Hour journals using X++

In this blog, we will explore how to create and post an hour journal using X++ code. I have taken examples from a staging table, where we can utilize the data to create hour journals using the following method.

  public boolean createHourJournal(DaxHourStaging stagingData)
  {
      ProjJournalTableData            JournalTableData;
      ProjJournalTransData            journalTransData;
      ProjJournalTable                journalTable, journalTableUpdate;
      ProjJournalTrans                journalTrans;
      ProjTable                       projTable;
      ProjInvoiceTable                projInvoiceTable;
      NumberSeq                       numberSeq;
      ProjJournalCheckPost            jourPost;
      ProjQtyEmpl                     qty;
      JournalNumOfLines               numOfLines;
      DataAreaId                      company = '';
      journalNum = '';
      DaxHourStaging  stagingDataHour;
      boolean ret = false;

      while select stagingDataHour
          where stagingDataHour.InvoiceNumber == stagingData.InvoiceNumber
              && stagingDataHour.TransID == stagingData.TransID
              && stagingDataHour.TransactionType == DAXTransactionType::Hour
              && stagingDataHour.DAXProcessedUnprocessed == DAXProcessedUnprocessed::UnProcessed
              && stagingDataHour.Type == DAXType::Invoice
      {
          select crosscompany projTable
              where projTable.DAXTransID == stagingDataHour.TransID;

          changecompany(projTable.DataAreaId)
          {
              company = projTable.DataAreaId;
              if (!journalNum)
              {
                  journalTableData = JournalTableData::newTable(journalTable);
                  journalTransData = journalTableData.journalStatic().newJournalTransData(journalTrans, journalTableData);
                  journalTable.clear();
                  journalTable.JournalId      = journalTableData.nextJournalId();
                  journalTable.JournalType    = ProjJournalType::Hour;
                  journalTable.JournalNameId  = ProjParameters::find().EmplJournalNameId;
                  journalTable.initFromProjJournalName(ProjJournalName::find(journalTable.JournalNameId));
                  journalTable.insert();
                  journalNum = journalTable.JournalId;
              }
              ResourceView            ResResourcesListView;
              ResResourceIdentifier   ResResourceIdentifier;
              ResourceCategoryView    ResourceCategoryView;
              WrkCtrTable             wrkctrTable;
              str                     resourceId = '';
              str                     resourceCompany = '';
              HcmWorker               hcmWorker;
              HcmEmployment           hcmEmployment;
              utcdatetime             now = DateTimeUtil::utcNow();
              CompanyInfo             companyInfo;

              select firstonly ResResourcesListView
                  where ResResourcesListView.ResourceId == stagingDataHour.DAXologyResourceId
                      && ResResourcesListView.ResourceCompanyId == projTable.DataAreaId;

              if (!ResResourcesListView.RecId)
              {
                  select ValidTimeState(now) hcmEmployment
                      join hcmWorker
                      where hcmWorker.RecId == hcmEmployment.Worker
                          && hcmWorker.PersonnelNumber == stagingDataHour.DAXologyResourceId
                      join companyInfo where companyInfo.RecId == hcmEmployment.LegalEntity;

                  if (hcmWorker.RecId)
                  {
                      resourceId      = hcmWorker.PersonnelNumber;
                      resourceCompany = companyInfo.DataArea;
                  }
              }
              select firstonly ResResourceIdentifier
                  where ResResourceIdentifier.RecId == ResResourcesListView.RecId;

              journalTableData.initFromJournalName(journalTableData.journalStatic().findJournalName(ProjJournalTable::find(journalNum).JournalNameId));
              journalTrans.clear();
              journalTransData.initFromJournalTable();

              projInvoiceTable    = projTable.projInvoice();
              journalTrans.setTransDate();
              journalTrans.TransDate          = stagingDataHour.TransDate;
              journalTrans.ProjTransDate      = stagingDataHour.TransDate;
              journalTrans.ProjId             = projTable.ProjId;
              journalTrans.Qty                = stagingDataHour.Quantity;
              journalTrans.DAXInvoiceId      = stagingDataHour.InvoiceNumber;
              journalTrans.DAXInvoiceDate    = stagingDataHour.InvoiceDate;
              journalTrans.DAXTransactionId  = stagingDataHour.TransactionId;
              journalTrans.CategoryId         = ProjParameters::find().EmplCategory;
              // journalTrans.Resource           = ResResourceIdentifier.RefRecId;
              journalTrans.Worker             = ResResourcesListView.Worker;
              journalTrans.LinePropertyId     = 'Chargeable';
              //journalTrans.DAXResourceCompany = resourceCompany;
              journalTrans.DAXWrkCtrId       = stagingDataHour.DAXologyResourceId;
              journalTrans.DAXResourceName   = stagingDataHour.DAXologyResourceName;
              journalTrans.DAXologyRoleName  = stagingDataHour.DAXologyRoleName;
              journalTrans.Txt                = stagingDataHour.Description;
              journalTrans.CurrencyId         = projInvoiceTable.CurrencyId;
              journalTrans.DefaultDimension   = projTable.DefaultDimension;
              journalTrans.TaxGroupId         = ProjParameters::taxGroupInvoice(projTable.ProjId);
              journalTrans.SalesPrice         = stagingDataHour.SalesPrice;
              InventTableModule   inventTableModule;

              select inventTableModule
                  where inventTableModule.ItemId == InventTable::find(DAX_ProjectHourJournalCreateService::getDimensionValueFromDefaultDimension(projTable.DefaultDimension)).ItemId
                      && inventTableModule.ModuleType == ModuleInventPurchSales::Sales;

              journalTrans.TaxItemGroupId     = inventTableModule.TaxItemGroupId;
              numberSeq = NumberSeq::newGetVoucherFromId(journalTable.VoucherNumberSequenceTable, false);

              journalTrans.Voucher            = numberSeq.voucher();
              journalTransData.create();
              if (TaxParameters::checkTaxParameters_IN())
              {
                  ProjJournalTransTaxExtensionIN     projJournalTransHourTaxExtensionIN = null;
                  projJournalTransHourTaxExtensionIN  = ProjJournalTransTaxExtensionIN::findByProjJournalTrans(journalTrans.RecId);
                  if (!projJournalTransHourTaxExtensionIN.RecId)
                  {
                      projJournalTransHourTaxExtensionIN.initValue();
                      projJournalTransHourTaxExtensionIN.ProjJournalTrans = journalTrans.RecId;
                      projJournalTransHourTaxExtensionIN.AssessableValueTransactionCurrency = journalTrans.Qty * journalTrans.SalesPrice;
                      projJournalTransHourTaxExtensionIN.insert();
                  }
              }
          }

      }
      try
      {
          changecompany(projTable.DataAreaId)
          {
              if (journalNum)
              {
                  jourPost = ProjJournalCheckPost::newJournalCheckPost(true,true,JournalCheckPostType::Post,tableNum(ProjJournalTable), journalNum);
                  jourPost.run();
                  ret = true;
                  ProjJournalTrans                projJournalTrans;
                  ProjJournalTable                projJournalTabeUpd;

                  projJournalTabeUpd = ProjJournalTable::find(journalNum,true);

                  select count(RecId), sum(Qty) from projJournalTrans
                              where projJournalTrans.JournalId == journalNum;

                  projJournalTabeUpd.NumOfLines = int642int(projJournalTrans.RecId);
                  projJournalTabeUpd.ProjQty = projJournalTrans.Qty;
                  projJournalTabeUpd.update();

                  DaxHourStaging  stagingGlobalUpd;
                  update_recordset stagingGlobalUpd setting DAXProcessedUnprocessed = DAXProcessedUnprocessed::Processed
                          where stagingGlobalUpd.InvoiceNumber == stagingData.InvoiceNumber
                              && stagingGlobalUpd.OrderItemId  == stagingData.OrderItemId
                              && stagingGlobalUpd.TransactionType == DAXTransactionType::Hour;

              }
          }
      }
      catch
      {
          //Posting exception
      }
      finally
      {
          // Can go with an final update here
      }
      return ret;
  }
Thanks !!

Create and Post Pending vendor invoice using X++

 

The class below will be used to create and post pending vendor invoices along with the project information. In my case, I retrieved the data from a staging table and created the pending vendor invoices.

public class CreatePendingVendorInvoice
{
    VendInvoiceInfoTable    vendInvoiceInfoTable;
    ProjParameters          projParameters;
    str                     ItemCompany;
    str                     ItemProjId;
    RecId                   ItemProjDimension;

    /// <summary>
    /// This method will be used to create the Pending vendor invoice header
    /// </summary>
    /// <param name = "_stagingTrans"></param>
    public void createPendingVendorInvoiceHeader(DaxStagingTrans    _stagingTrans)
    {
        projParameters = ProjParameters::find();

        NumberSeq   numberSeq = NumberSeq::newGetNum(ProjParameters::invoiceId());

        vendInvoiceInfoTable.clear();
        vendInvoiceInfoTable.initValue();

        vendInvoiceInfoTable.DocumentOrigin          = DocumentOrigin::Manual;
        vendInvoiceInfoTable.InvoiceAccount          = this.getVendorAccount();
        vendInvoiceInfoTable.defaultRow(null, null, true);

        vendInvoiceInfoTable.Num                     = numberSeq.num();
        vendInvoiceInfoTable.VendInvoiceSaveStatus   = VendInvoiceSaveStatus::Pending;
        vendInvoiceInfoTable.DocumentDate            = _stagingTrans.JournalDate;
        vendInvoiceInfoTable.ReceivedDate            = _stagingTrans.JournalDate;
        vendInvoiceInfoTable.TransDate               = _stagingTrans.JournalDate;
        vendInvoiceInfoTable.LastMatchVariance       = LastMatchVarianceOptions::OK;
        vendInvoiceInfoTable.RequestStatus           = VendInvoiceRequestStatus::Approved;
        vendInvoiceInfoTable.insert();

        this.createPendingVendorInvoiceLine(_stagingTrans);
    }

    /// <summary>
    /// This method used to create the Pending Vendor incvoice lines
    /// </summary>
    /// <param name = "_stagingTrans">DaxStagingTrans</param>
    public void createPendingVendorInvoiceLine(DaxStagingTrans    _stagingTrans)
    {
        VendInvoiceInfoLine     vendInvoiceInfoLine;

        vendInvoiceInfoLine.clear();
        vendInvoiceInfoLine.initValue();
        vendInvoiceInfoLine.DeliveryName        = vendInvoiceInfoTable.DeliveryName;
        vendInvoiceInfoLine.TableRefId          = vendInvoiceInfoTable.TableRefId;
        vendInvoiceInfoLine.currencyCode        = vendInvoiceInfoTable.CurrencyCode;
        vendInvoiceInfoLine.LineNum             = 1;
        vendInvoiceInfoLine.InvoiceAccount      = vendInvoiceInfoTable.InvoiceAccount;
        vendInvoiceInfoLine.OrderAccount        = vendInvoiceInfoTable.OrderAccount;
        vendInvoiceInfoLine.ProcurementCategory = projParameters.ANTHProcurCategory;
        vendInvoiceInfoLine.modifiedField(fieldNum(VendInvoiceInfoLine, ProcurementCategory));
        vendInvoiceInfoLine.ReceiveNow          = 1;
        vendInvoiceInfoLine.PurchUnit           = projParameters.ANTHPurchUnit;
        vendInvoiceInfoLine.DocumentOrigin      = DocumentOrigin::Manual;

        container           conAttribute            = ANTHConcurCreateGeneralJournalService::getFDFromParameters();
        container           convalue                = this.getProjectDimensions(conAttribute, _stagingTrans);

        vendInvoiceInfoLine.DefaultDimension    = ANTHConcurCreateGeneralJournalService::createDefaultDimension(conAttribute, convalue);
        vendInvoiceInfoLine.insert();

        if (vendInvoiceInfoLine)
        {
            VendInvoiceInfoLine_Project   vendInvoiceInfoLine_Project;

            vendInvoiceInfoLine_Project.VendInvoiceInfoLineRefRecId = vendInvoiceInfoLine.RecId;
            vendInvoiceInfoLine_Project.ProjDataAreaId              = ItemCompany;
            vendInvoiceInfoLine_Project.ProjId                      = ItemProjId;
            vendInvoiceInfoLine_Project.ProjCategoryId              = _stagingTrans.Expensecategory;
            vendInvoiceInfoLine_Project.ProjLinePropertyId          = CreatePendingVendorInvoice::findLineProperty();
            vendInvoiceInfoLine_Project.TransDate                   = _stagingTrans.JournalDate;
            vendInvoiceInfoLine_Project.ProjSalesUnitId             = UnitOfMeasure::findBySymbol(projParameters.ANTHPurchUnit).RecId;
            vendInvoiceInfoLine_Project.ProjSalesCurrencyId         = _stagingTrans.Currency;
            vendInvoiceInfoLine_Project.TransferCurrency            = _stagingTrans.Currency;
            vendInvoiceInfoLine_Project.TransferPrice               = _stagingTrans.Costamount;
            vendInvoiceInfoLine_Project.ANTHConcurTransactionID     = _stagingTrans.ConcurTransactionID;
            vendInvoiceInfoLine_Project.ProjTaxGroupId              = '';
            vendInvoiceInfoLine_Project.ProjTaxItemGroupId          = '';
            vendInvoiceInfoLine_Project.insert();
        }
    }

    /// <summary>
    /// This method used to Post the Vendor invoice
    /// </summary>
    public void postInvoice()
    {
        PurchFormLetter         purchFormLetter;

        purchFormLetter = PurchFormLetter_Invoice::newFromSavedInvoice(vendInvoiceInfoTable);
        purchFormLetter.purchParmUpdate(null);
        purchFormLetter.parmId('');
        purchFormLetter.initNewPurchParmUpdate();
        purchFormLetter.proforma(false);
        purchFormLetter.reArrangeNow(false);

        purchFormLetter.update(vendInvoiceInfoTable,
                                vendInvoiceInfoTable.Num,
                                purchFormLetter.transDate(),
                                PurchUpdate::All,
                                AccountOrder::None,
                                purchFormLetter.proforma(),
                                purchFormLetter.printFormLetter(),
                                false,
                                purchFormLetter.creditRemaining(),
                                conNull(),
                                true);

    }

    /// <summary>
    /// This method used to retrieve the Default Dimensions
    /// </summary>
    /// <param name = "_attribute">Container</param>
    /// <param name = "_stagingTrans">DaxStagingTrans</param>
    /// <returns>Container</returns>
    private container getProjectDimensions(container    _attribute, DaxStagingTrans _stagingTrans)
    {
        // DeptCC-ProfitCenter-PRODGRP-UnifiedProductID-Customer-Vendor
        return [_stagingTrans.Costcenter, projParameters.DAXProfitCenter,
                CreatePendingVendorInvoice::getDimensionValue(orderItemProjDimension, conPeek(_attribute, 3)),
                CreatePendingVendorInvoice::getDimensionValue(orderItemProjDimension, conPeek(_attribute, 4)),
                CreatePendingVendorInvoice::getDimensionValue(orderItemProjDimension, conPeek(_attribute, 5))];
    }

    /// <summary>
    /// Gets the display value from the Dimension Recid
    /// </summary>
    /// <param name = "_dimension">RecId</param>
    /// <param name = "_dimensionName">Str</param>
    /// <returns>DimensionValue</returns>
    public static DimensionValue getDimensionValue(RecId  _dimension, str _dimensionName)
    {
        DimensionAttributeValueSetStorage   dimensionAttributeValueSetStorage ;
        DimensionAttribute                  dimensionAttribute;
        DimensionValue                      dimensionValue;

        dimensionAttributeValueSetStorage = dimensionAttributeValueSetStorage::find(_dimension);

        dimensionAttribute  = dimensionAttribute::findbyname(_dimensionName);
        dimensionValue      = dimensionAttributeValueSetStorage.getDisplayValueByDimensionAttribute(dimensionAttribute.recId);

        return dimensionValue;
    }

    public void OrderDetails(container  _OrderCon)
    {
        [orderItemCompany, orderItemProjId, orderItemProjDimension] = _OrderCon;
    }

    public str getVendorAccount()
    {
        DirPartyView    partyView;
        DirPartyRecId   partyId = CompanyInfo::findDataArea(orderItemCompany).RecId;

        select firstonly AccountNum from partyView
            where partyView.Party == partyId && partyView.RoleType == DirPartyRoleType::Vendor && partyView.DataArea == curExt();

        return partyView.AccountNum;
    }

    public static CreatePendingVendorInvoice construct(container  _OrderCon)
    {
        CreatePendingVendorInvoice    concurCreatePendingVendorInvoice = new CreatePendingVendorInvoice();

        concurCreatePendingVendorInvoice.OrderDetails(_OrderCon);

        return concurCreatePendingVendorInvoice;
    }

    public static str findLineProperty()
    {
        ProjLineProperty    projLineProperty;

        select firstonly LinePropertyId from projLineProperty
            where projLineProperty.ToBeInvoiced == false;

        return projLineProperty.LinePropertyId;
    }

    public static ResourceView findResource(str _resourceId)
    {
        ResourceView resource;

        select firstonly resource
            where resource.ResourceId == _resourceId;

        return resource;
    }

}

Thanks !!

Create General Journals in D365 fo using X++


Below is the code to create general journals in D365 using X++. In this implementation, we utilize the LedgerJournalEngine, which can handle both single- and multi-voucher journal creation scenarios. This approach increases the likelihood that the resulting lines will match those created manually.

public class CreateGeneralJournalService
{
    ledgerJournalTable      ledgerJournalTable;
    ProjParameters          projParameters;
    LedgerJournalEngine     ledgerJournalEngine;

    /// <summary>
    /// Method Used to create the Journal Header
    /// </summary>
    /// <returns>ledgerJournalTable</returns>
    public ledgerJournalTable createGeneralJournalHeader()
    {
        projParameters = ProjParameters::find();

        ledgerJournalTable.clear();
        ledgerJournalTable.initValue();
        ledgerJournalTable.initFromLedgerJournalName(projParameters.DaxJournalNameId);
        ledgerJournalTable.JournalNum = JournalTableData::newTable(ledgerJournalTable).nextJournalId();
        ledgerJournalTable.insert();

        ledgerJournalEngine = LedgerJournalEngine::construct(ledgerJournalTable.JournalType);
        ledgerJournalEngine.newJournalActive(ledgerJournalTable);

        return ledgerJournalTable;
    }

    /// <summary>
    ///Method Used to create the journal Lines
    /// </summary>
    /// <param name = "_StagingTrans"></param>
    public void createGeneralJournalLine(DaxStagingTrans    _StagingTrans)
    {
        ledgerJournalTrans  ledgerJournalTrans;

        // DeptCC-ProfitCenter-PRODGRP-UnifiedProductID-Customer-Vendor
        container           conAttribute            = CreateGeneralJournalService::getFDFromParameters();
        container           convalue                = [_StagingTrans.Costcenter, projParameters.DaxProfitCenter];
        RecId               defaultDimensionRecId   = CreateGeneralJournalService::createDefaultDimension(conAttribute, convalue);

        ledgerJournalTrans.clear();
        ledgerJournalTrans.initValue();
        ledgerJournalEngine.initValue(ledgerJournalTrans);

        ledgerJournalTrans.JournalNum             = ledgerJournalTable.JournalNum;
        ledgerJournalTrans.TransDate              = _StagingTrans.JournalDate;
        ledgerJournalTrans.AccountType            = LedgerJournalACType::Ledger;
        ledgerJournalTrans.modifiedField(fieldNum(LedgerJournalTrans, AccountType));
        ledgerJournalTrans.LedgerDimension        = LedgerDimensionFacade::serviceCreateLedgerDimension(this.getDefaultAccount(_StagingTrans.Expensecategory), defaultDimensionRecId);;
        ledgerJournalTrans.modifiedField(fieldNum(LedgerJournalTrans, LedgerDimension));
        ledgerJournalEngine.accountModified(LedgerJournalTrans);

        ledgerJournalTrans.OffsetAccountType        = LedgerJournalACType::Ledger;
        ledgerJournalTrans.modifiedField(fieldNum(LedgerJournalTrans, OffsetAccountType));
        ledgerJournalTrans.OffsetLedgerDimension    = LedgerDimensionFacade::serviceCreateLedgerDimension(projParameters.DaxReimbursableEmpExpensePayAccount, defaultDimensionRecId);
        ledgerJournalTrans.modifiedField(fieldNum(LedgerJournalTrans, OffsetLedgerDimension));
        ledgerJournalEngine.offsetAccountModified(ledgerJournalTrans);

        ledgerJournalTrans.CurrencyCode             = _StagingTrans.Currency;
        ledgerJournalEngine.currencyModified(LedgerJournalTrans);
        ledgerJournalTrans.AmountCurDebit           = _StagingTrans.Costamount;
        ledgerJournalTrans.DaxTransID               = _StagingTrans.TransID;
        ledgerJournalTrans.DaxOrderItemID           = _StagingTrans.OrderItemId;
        ledgerJournalTrans.DaxExpenseCategory       = _StagingTrans.Expensecategory;
        ledgerJournalTrans.Approved = NoYes::Yes;
        ledgerJournalTrans.SkipBlockedForManualEntryCheck = true;

        ledgerJournalTrans.defaultRow();
        ledgerJournalTrans.TaxItemGroup = nullValueFromType(Types::String);
        ledgerJournalTrans.TaxGroup     = nullValueFromType(Types::String);

        if (ledgerJournalTrans.validateWrite())
        {
            ledgerJournalTrans.insert();

            ledgerJournalEngine.write(ledgerJournalTrans);
        }
    }

    /// <summary>
    /// A construct method to Create an object
    /// </summary>
    /// <returns></returns>
    public static CreateGeneralJournalService construct()
    {
        return new CreateGeneralJournalService();
    }

    /// <summary>
    /// Gets the default dimension from Proj posting setup
    /// </summary>
    /// <param name = "_categoryRelation">Str</param>
    /// <returns>LedgerDimensionDefaultAccount</returns>
    public LedgerDimensionDefaultAccount getDefaultAccount(str _categoryRelation)
    {
        ProjPosting   projPosting;

        select firstonly projPosting
            where projPosting.ProjAccountType       == ProjAccountType::CostAccount
               && projPosting.ProjCategoryRelation  == _categoryRelation;

        return projPosting.LedgerDimension;
    }

    /// <summary>
    /// It will returns the format of finanicaial dimensions
    /// </summary>
    /// <returns>container</returns>
    public static container getFDFromParameters()
    {
        DimensionHierarchy              dimensionHierarchy;
        DimensionHierarchyIntegration   dimensionHierarchyIntegration;

        select firstonly DisplayString from dimensionHierarchyIntegration
            exists join dimensionHierarchy
                where dimensionHierarchy.RecId == dimensionHierarchyIntegration.DimensionHierarchy
                    && dimensionHierarchy.StructureType == DimensionHierarchyType::DataEntityDefaultDimensionFormat
                    && dimensionHierarchyIntegration.IsDefault == true;

        return str2con(dimensionHierarchyIntegration.DisplayString, DimensionParameters::getDimensionSegmentDelimiter());
    }

    /// <summary>
    /// Method Used to create the default Dimension
    /// </summary>
    /// <param name = "conAttribute"></param>
    /// <param name = "convalue"></param>
    /// <returns></returns>
    public static  DimensionDefault createDefaultDimension(container conAttribute , container convalue)
    {
        DimensionAttributeValueSetStorage   valueSetStorage = new DimensionAttributeValueSetStorage();
        DimensionAttribute                  dimensionAttribute;
        DimensionAttributeValue             dimensionAttributeValue;
        str                                 dimValue;

        for (int i = 1; i <= conLen(conAttribute); i++)
        {
            dimensionAttribute = dimensionAttribute::findByName(conPeek(conAttribute,i));
            if (dimensionAttribute.RecId == 0)
            {
                continue;
            }

            dimValue = conPeek(convalue,i);
            if (dimValue != "")
            {
                dimensionAttributeValue = dimensionAttributeValue::findByDimensionAttributeAndValue(dimensionAttribute, dimValue, false, true);
                valueSetStorage.addItem(dimensionAttributeValue);
            }
        }

        return valueSetStorage.save();
    }

}


Additionally, to post the above created Journals we can use LedgerJournalcheckpost class as below

LedgerJournalCheckPost ledgerjournalcheckpost = LedgerJournalCheckPost::newLedgerJournalTable(ledgerJournalTable,NoYes::Yes);
    if (ledgerjournalcheckpost.validate())
    {
        ledgerjournalcheckpost.run();
    }

Thanks !!

Create Expense Journals in D365 FO Using X++


Here is the sample code to create project expense journals in D365 FO using X++. In my case, I will retrieve data from a staging table sourced from a third-party system. The job outlined below will use values from the staging table to create the expense journals.

Public  final class CreateExpenseJournalService
{
    DaxExpenseStaging         StagingTrans;
    str                            empId;
    JournalId                      journalId;

    /// <summary>
    /// This method will create the ledger dimension for expense journals
    /// </summary>
    /// <param name = "_conData">DmensionData</param>
    /// <param name = "_mainAccount">MainAccountNum</param>
    /// <returns>DimensionDynamicAccount</returns>
    public DimensionDynamicAccount  generateLedgerDimension(container    _conData, MainAccountNum _mainAccount)
    {
        int                                 hierarchyCount;
        int                                 hierarchyIdx;
        RecId                               dimAttId_MainAccount;
        LedgerRecId                         ledgerRecId;
        MainAccount                         mainAccount;
        RefRecId                            recordvalue;
        DimensionAttribute                  dimensionAttribute;
        DimensionAttributeValue             dimensionAttributeValue;
        DimensionSetSegmentName             DimensionSet;
        DimensionStorage                    dimStorage;
        DimensionAttributeValueContract     ValueContract;
        DimensionAttributeValueCombination  dimensionAttributeValueCombination;
        LedgerAccountContract               LedgerAccountContract = new LedgerAccountContract();

        List   valueContracts = new List(Types::Class);

        try
        {
            mainAccount         = MainAccount::findByMainAccountId(_mainAccount);
            recordvalue         = DimensionHierarchy::getAccountStructure(mainAccount.RecId,Ledger::current());
            hierarchyCount      = DimensionHierarchy::getLevelCount(recordvalue);
            DimensionSet        = DimensionHierarchyLevel::getDimensionHierarchyLevelNames(recordvalue);
            for(hierarchyIdx = 1;hierarchyIdx<=hierarchyCount;hierarchyIdx++)
            {
                if(hierarchyIdx == 1)
                    continue;
                dimensionAttribute = DimensionAttribute::findByLocalizedName(DimensionSet[hierarchyIdx],false,"en-us");
                if(dimensionAttribute)
                {
                    dimensionAttributeValue = DimensionAttributeValue::findByDimensionAttributeAndValue(dimensionAttribute,conPeek(_conData,hierarchyIdx) ,false ,false);
                    if(dimensionAttributeValue)
                    {
                        ValueContract = new DimensionAttributeValueContract();
                        ValueContract.parmName(dimensionAttribute.Name) ;
                        ValueContract.parmValue(dimensionAttributeValue.CachedDisplayValue);
                        valueContracts.addEnd(ValueContract);
                    }
                }
            }
            LedgerAccountContract.parmMainAccount(_mainAccount);
            LedgerAccountContract.parmValues(valueContracts);
            dimStorage                         = DimensionServiceProvider::buildDimensionStorageForLedgerAccount(LedgerAccountContract);
            dimensionAttributeValueCombination = DimensionAttributeValueCombination::find(dimStorage.save());
            ledgerRecId                        = dimensionAttributeValueCombination.RecId;
        }
        catch
        {
            throw Exception::error;
        }
        return ledgerRecId;
    }

    /// <summary>
    /// A construct method to create object of this class
    /// </summary>
    /// <param name = "_stagingTrans">DaxExpenseStaging</param>
    /// <returns>CreateExpenseJournalService</returns>
    public static CreateExpenseJournalService construct(DaxExpenseStaging _stagingTrans)
    {
        CreateExpenseJournalService createExpenseExpense =  new CreateExpenseJournalService();
        createExpenseExpense.parmstagingTrans(_stagingTrans);
        return createExpenseExpense;
    }

    /// <summary>
    /// A parm method to return the current staging
    /// </summary>
    /// <param name = "_stagingTrans">DaxExpenseStaging</param>
    /// <returns>DaxExpenseStaging</returns>
    public DaxExpenseStaging parmstagingTrans(DaxExpenseStaging _stagingTrans)
    {
        StagingTrans = _stagingTrans;

        return StagingTrans;

    }

    /// <summary>
    /// Method to create the Journal Header
    /// </summary>
    /// <returns></returns>
    public LedgerJournalTable createJournalHeader()
    {
        LedgerJournalTable                                         ledgerJournalTable,jourTable1;
        ProjParameters                                             projParmeters = ProjParameters::find();

        ledgerJournalTable.initValue();
        ledgerJournalTable.JournalName                               = LedgerJournalName::find(projParmeters.DAXProjExpenseJournal).JournalName;
        ledgerJournalTable.initFromLedgerJournalName();
        ledgerJournalTable.Name                                      = ledgerJournalTable.ledgerJournalName().Name;
        ledgerJournalTable.JournalType                               = ledgerJournalType::Cost;
        ledgerJournalTable.LedgerJournalInclTax                      = NoYes::No;
        ledgerJournalTable.insert();

        return ledgerJournalTable;

    }

    /// <summary>
    /// Method used to create the Journal Lines
    /// </summary>
    /// <param name = "_ledgerJournalTable">LedgerJournalTable</param>
    public void createJournalTrans(LedgerJournalTable       _ledgerJournalTable)
    {
        LedgerJournalTrans                                         ledgerJournalTrans;
        LedgerJournalTrans_Project                                 ledgerJournalTrans_Project;
        ProjJournalCheckPost                                       jourPost;
        ledgerJournalCheckPost                                     ledgerJournalCheckPost;
        ProjTable                                                  projTable;
        NumberSequenceTable                                        numberSequenceTable;
        Voucher                                                    voucher;
        ResourceView                                               resourceView;
        MainAccount                                                mainaccount;
        ProjParameters                                             projParmeters = ProjParameters::find();



        if (_ledgerJournalTable)
        {
            select firstonly projTable
                    where projTable.DaxTransID == StagingTrans.TransID;

            ledgerJournalTrans.clear();
            ledgerJournalTrans.JournalNum            = _ledgerJournalTable.JournalNum;
            ledgerJournalTrans.DefaultDimension      = projTable.DefaultDimension;
            ledgerJournalTrans.AccountType           = LedgerJournalACType::Project;
            numberSequenceTable                      = NumberSequenceTable::find(LedgerJournalName::find(LedgerJournalName::find(projParmeters.DAXProjExpenseJournal).JournalName).NumberSequenceTable);
            voucher                                  = NumberSeq::newGetVoucherFromCode(numberSequenceTable.NumberSequence).voucher();
            ledgerJournalTrans.Voucher               = voucher;
            ledgerJournalTrans.CurrencyCode          = StagingTrans.Currency;
            ledgerJournalTrans.Qty                   = 1;
            ledgerJournalTrans.TransDate             = StagingTrans.JournalDate;
            ledgerJournalTrans.LedgerDimension       = LedgerDynamicAccountHelper::getDynamicAccountFromAccountNumber(projTable.ProjId,LedgerJournalACType::Project);
            ledgerJournalTrans.modifiedField(fieldNum(ledgerJournalTrans ,LedgerDimension ));

            ledgerJournalTrans.OffsetAccountType    = LedgerJournalACType::Ledger;
            ledgerJournalTrans.ExchRate = 100;
            Amount amount = StagingTrans.Costamount;
            if(amount> 0)
            {
                ledgerJournalTrans.AmountCurDebit = amount;
            }
            else
            {
                ledgerJournalTrans.AmountCurCredit = abs(amount);
            }
            ledgerJournalTrans.Company                  = strUpr(curExt());
            ledgerJournalTrans.OffsetCompany            = strUpr(curExt());
            ledgerJournalTrans.DAXstagingTransactionID  = StagingTrans.TransID;

           // mainaccount = MainAccount::find(projParmeters.DAXReimbursableEmpExpensePayAccount);
            str  mainaccountId  = LedgerDimensionFacade::getMainAccountFromLedgerDimension(projParmeters.DAXReimbursableEmpExpensePayAccount).MainAccountId;
            //MainAccount~CostCenter~ProfitCenter~Product_Family~UniqueProductID~Customer~Vendor
            container convalues = [mainaccountId, StagingTrans.Costcenter, projParmeters.DAXProfitCenter,'','','',''];
            refrecid ledgerdim  =  this.generateLedgerDimension(convalues, mainaccountId);

            ledgerJournalTrans.OffsetLedgerDimension    = LedgerDimensionFacade::serviceCreateLedgerDimension(ledgerdim,projTable.DefaultDimension);
            ledgerJournalTrans.modifiedField(fieldNum(ledgerJournalTrans ,OffsetLedgerDimension ));
            ledgerJournalTrans.insert();

            if (ledgerJournalTrans.RecId)
            {
                ledgerJournalTrans_Project.clear();
                ledgerJournalTrans_Project.RefRecId                         = ledgerJournalTrans.RecId;
                ledgerJournalTrans_Project.initValue();
                ledgerJournalTrans_Project.CategoryId                       = StagingTrans.Expensecategory;
                ledgerJournalTrans_Project.ProjId                           = projTable.ProjId;
                ledgerJournalTrans_Project.ProjTransDate                    = StagingTrans.JournalDate;
                ledgerJournalTrans_Project.modifiedField(fieldNum(ledgerJournalTrans_Project, CategoryId));
                ledgerJournalTrans_Project.Qty                              = 1;
                ledgerJournalTrans_Project.SalesCurrencyId                  = ledgerJournalTrans.CurrencyCode;
                ledgerJournalTrans_Project.CostPrice                        = amount;


                //select firstonly resourceView
                //    where resourceView.ResourceId == StagingTrans.EmployeeID;
                //ledgerJournalTrans_Project.Resource                         = resourceView.RecId; // If resource need to be filled uncomment the lines
                ledgerJournalTrans_Project.modifiedField(fieldNum(ledgerJournalTrans_Project, CostPrice));
                ledgerJournalTrans_Project.LinePropertyId = ProjLinePropertySetup::findLinePropertyId( projTable.ProjId,ledgerJournalTrans_Project.CategoryId );

                boolean priceFound;
                CostPrice costPriceMST = ProjCostSalesPrice::costPrice(ledgerJournalTrans,ledgerJournalTrans_Project, false);
                [ledgerJournalTrans_Project.SalesPrice, priceFound] = ProjCostSalesPrice::findCostSalesPrice(ledgerJournalTrans_Project.ProjId,ledgerJournalTrans_Project.Resource,ledgerJournalTrans_Project.CategoryId,costPriceMST,CompanyInfoHelper::standardCurrency(),ledgerJournalTrans_Project.SalesCurrencyId,ledgerJournalTrans_Project.ProjTransDate,ledgerJournalTrans_Project.ProjPriceGroupID,false);

                ledgerJournalTrans_Project.insert();
            }
        }

    }

}


Calculate Trail Balances In D365 FO X++

We often go through the requirements where we need to compute the Trail balances to get the vouher transactions amounts. Here , different ways to compute the trail balance in D365 fo X++

1. This is an easy way to compute trail balances without dimension sets. If required we can add the further ranges below in the dynamic query.

 protected Amount getProjBalanceForRevenue(ProjId _projId, MainAccountNum _mainaccount)
    {
        LedgerAllocationRuleSource  ruleSource;
        RecId                       keyValue;
        Map                         dimensionRange = new Map(Types::Int64, Types::String);
        Set                         dimensionRangeIncludeEmpty = new Set(Types::Int64);
        str                         emptyQueryStr = SysQuery::valueEmptyString();
        GeneralJournalAccountEntry  result;
        Amount                      DebitAmount,CreditAmount, TotalAllocatedAmount;


        Query query = new Query();
        QueryBuildDataSource qbds = query.addDataSource(tableNum(GeneralJournalAccountEntry));

        // Add fields from GeneralJournalAccountEntry to query
        qbds.addSelectionField(fieldNum(GeneralJournalAccountEntry, TransactionCurrencyAmount), SelectionField::Sum);
        qbds.addSelectionField(fieldNum(GeneralJournalAccountEntry, AccountingCurrencyAmount), SelectionField::Sum);
        qbds.addSelectionField(fieldNum(GeneralJournalAccountEntry, ReportingCurrencyAmount), SelectionField::Sum);

        // Configure the query to do a Group By currency & account/dimension combination
        qbds.orderMode(OrderMode::GroupBy);
        qbds.addSortField(fieldNum(GeneralJournalAccountEntry, TransactionCurrencyCode));
        qbds.addSortField(fieldNum(GeneralJournalAccountEntry, LedgerDimension));
        //  qbds.addRange(fieldnum(GeneralJournalAccountEntry,IsCredit )).value(SysQuery::value(Noyes::No));
        QueryBuildDataSource qbdsHeader = qbds.addDataSource(tableNum(GeneralJournalEntry));
        qbdsHeader.relations(true);
        qbdsHeader.joinMode(JoinMode::InnerJoin);
        qbdsHeader.fetchMode(QueryFetchMode::One2One);
        //    qbdsHeader.addLink(fieldNum(GeneralJournalAccountEntry, GeneralJournalEntry), fieldNum(GeneralJournalEntry, RecId));
        qbdsHeader.addRange(fieldNum(GeneralJournalEntry, PostingLayer)).value(queryValue(CurrentOperationsTax::Current));
        qbdsHeader.addRange(fieldNum(GeneralJournalEntry, Ledger)).value(SysQuery::value(Ledger::current()));
        QueryBuildDataSource qbdsFiscalPeriod = qbdsHeader.addDataSource(tableNum(FiscalCalendarPeriod));
        /// qbdsFiscalPeriod.addLink(fieldNum(GeneralJournalEntry,FiscalCalendarPeriod), fieldNum(fiscalCalendarPeriod, RecId));
        qbdsFiscalPeriod.relations(true);
        qbdsFiscalPeriod.joinMode(JoinMode::ExistsJoin);
        qbdsFiscalPeriod.addRange( fieldNum(fiscalCalendarPeriod, type)).value(SysQuery::value(FiscalPeriodType::Operating));
        QueryBuildRange qbr = qbdsHeader.addRange(fieldNum(GeneralJournalEntry, AccountingDate));
        qbr.value(SysQuery::range(fromDate, toDate));

        // Build the account and dimension range string from LedgerAllocationRuleSource for this rule
        this.createRuleSourceCriteria( dimensionRange, _Mainaccount, _projId);

        QueryBuildDataSource qbdsDavc = qbds.addDataSource(tableNum(DimensionAttributeValueCombination));
        qbdsDavc.joinMode(JoinMode::ExistsJoin);
        qbdsDavc.addLink(fieldNum(GeneralJournalAccountEntry, LedgerDimension), fieldNum(DimensionAttributeValueCombination, RecId));
        qbdsDavc.fetchMode(QueryFetchMode::One2One);

        LedgerAllocationProcessRequest::createDimensionRanges(qbdsDavc, dimensionRange.getEnumerator(), dimensionRangeIncludeEmpty);

        QueryRun queryRunloc = new QueryRun(query);

        while (queryRunloc.next())
        {
            result = queryRunloc.get(tableNum(GeneralJournalAccountEntry)) as GeneralJournalAccountEntry;
            if(result.IsCredit)
            {
                CreditAmount += result.AccountingCurrencyAmount;
            }
            else
            {
                DebitAmount += result.AccountingCurrencyAmount;
            }
        }
        TotalAllocatedAmount = DebitAmount + CreditAmount;
        
        return TotalAllocatedAmount;
    }

    public void createRuleSourceCriteria(Map _dimensionRange, MainAccountNum _mainaccount, ProjId _ProjId)
    {
        DimensionAttributeRecId mainAccountDimAttrId = DimensionAttribute::getWellKnownDimensionAttribute(DimensionAttributeType::MainAccount);
        _dimensionRange.insert(mainAccountDimAttrId, _mainaccount);
        _dimensionRange.insert(DimensionAttribute::findByName("Project").RecId, _ProjId);
    }

2. Computing trial balances and getting required closing balance based on operating type with dimension set.

 private void calcClosingBalanceAC()
    {
        LedgerTrialBalanceListPageBalanceParms  balanceParameters;
        DimensionHierarchy                      dimHier;
        TransDate                               fromDate,toDate;
        List                                    selected = new List(Types::Enum);
 
        selected.addEnd(CurrentOperationsTax::Current);

        dimHier = DimensionHierarchy::findByTypeAndName(DimensionHierarchyType::Focus, '@PRO_Synamedia:MaserviceId');
  
        balanceParameters = LedgerTrialBalanceListPageBalanceParms::construct();
  
        balanceParameters.parmDateCode('');
        balanceParameters.parmDimensionSetHierarchy(dimHier);
        balanceParameters.parmStartDate(dateNull());
        balanceParameters.parmEndDate(TrandateFilter.dateValue());
        balanceParameters.parmPostingLayers(selected);
        balanceParameters.parmIncludeClosingAdjustments(false);

        select firstOnly RecId from ledgerTrialBalanceTmp;

        LedgerTrialBalanceTmp::calculateBalances_V2(ledgerTrialBalanceTmp,
                                                    dimHier.Name,
                                                    balanceParameters.getStartDate(),
                                                    balanceParameters.getEndDate(),
                                                    balanceParameters.getIncludeOpeningPeriods(),
                                                    balanceParameters.getIncludeClosingAdjustments(),
                                                    balanceParameters.getIncludeClosingTransactions(),
                                                    balanceParameters.getPostingLayers(),
                                                    true,
                                                    true,
                                                    Ledger::current(),
                                                    balanceParameters.getDisplayMainAccountCategory());
    }
 private Real getClosingBalanceAc(BusinessUnit _BU)
    {
        real CLosingamount;
        select firstonly *
            from ledgerTrialBalanceTmp
                 where ledgerTrialBalanceTmp.DimensionValues[1] == unbilledRevnueAccount
                    && ledgerTrialBalanceTmp.DimensionValues[2] == _BU;
if (ledgerTrialBalanceTmp.RecId) { CLosingamount = this.getClosingBalance(); } return CLosingamount; }
 private real getClosingBalance()
    {
        DimensionAttributeValueCombination      dimensionAttributeCombo;
        GeneralJournalAccountEntry              generalJournalAccountEntry;
        DAXGeneralJourAccountEntryOperatingView GeneralJourAccountEntryOperatingView;
Query query = new Query(); QueryBuildDataSource GJAccountEntryDs, GJEntryDs, FCDS, DAVC_Ds; QueryBuildRange Operatingrange; real closingBalance; GJAccountEntryDs = query.addDataSource(tableNum(DAXGeneralJourAccountEntryOperatingView)); GJAccountEntryDs.addRange(fieldnum(DAXGeneralJourAccountEntryOperatingView, AccountingDate)).value(queryRange(datenull(), TrandateFilter.dateValue()));
DAVC_Ds = GJAccountEntryDs.addDataSource(tableNum(DimensionAttributeValueCombination)); DAVC_Ds.addLink(fieldNum(PROGeneralJourAccountEntryOperatingView , LedgerDimension) , fieldNum(DimensionAttributeValueCombination , RecId)); select firstonly dimensionAttributeCombo where dimensionAttributeCombo.Recid == ledgerTrialBalanceTmp.LedgerDimension; // DimensionHierarchy dimHierLoc = DimensionHierarchy::findByTypeAndName(DimensionHierarchyType::Focus, SubBillParameters::find().PRO_MonthEndAutomationJourDimSet); DimensionHierarchy dimHierLoc = DimensionHierarchy::findByTypeAndName(DimensionHierarchyType::Focus, '@PRO_Synamedia:MaserviceId'); this.updateQueryForLedgerDimension(query, dimensionAttributeCombo, dimHierLoc); QueryRun qr = new QueryRun(query); while (qr.next()) { GeneralJourAccountEntryOperatingView = qr.get(tableNum(DAXGeneralJourAccountEntryOperatingView));
closingBalance += GeneralJourAccountEntryOperatingView.AccountingCurrencyAmount; } return closingBalance; }

3. As performance concerned create a view for GeneralJournalAccountEntry Table for applying theoperating type ranges.



 private void updateQueryForLedgerDimension(Query _query, DimensionAttributeValueCombination _davcSearchCriteria, DimensionHierarchy _dimHier)
    {
        // If the account transaction details are related to the dimension set containing Main account only, the filter can
        // be applied directly to GeneralJournalAccountEntry for the account filtering.
        //boolean mainAccountTransactionDetails = this.updateQueryForMainAccountOnly(_query, _davcSearchCriteria, _dimHier);

        //if (mainAccountTransactionDetails == false)
        //{
        QueryBuildDataSource davcDS = this.getDimensionAttributeValueCombinationDataSource(_query);
        DimensionAttributeLevelValueView dalvView;

        while select DimensionAttribute, DisplayValue from dalvView
                where dalvView.ValueCombinationRecId == _davcSearchCriteria.RecId
        {
            DimensionRefFieldName dimensionValueColumnName = DimensionAttribute::find(dalvView.DimensionAttribute).DimensionValueColumnName;
            
            QueryBuildRange qbr = davcDS.addRange(fieldName2Id(tableNum(DimensionAttributeValueCombination), dimensionValueColumnName));
            qbr.value(queryValue(dalvView.DisplayValue));
        }

        // For any dimension specified in the hierarchy but not having a value in the ledger account, add an empty string criteria
        if (_dimHier)
        {
            DimensionHierarchyLevel dimHierLevels;

            while select DimensionAttribute from dimHierLevels
                    where dimHierLevels.DimensionHierarchy == _dimHier.RecId
                    notexists join dalvView
                        where dalvView.DimensionAttribute == dimHierLevels.DimensionAttribute
                            && dalvView.ValueCombinationRecId == _davcSearchCriteria.RecId
            {
                DimensionRefFieldName dimensionValueColumnName = DimensionAttribute::find(dimHierLevels.DimensionAttribute).DimensionValueColumnName;
                
                QueryBuildRange qbr = davcDS.addRange(fieldName2Id(tableNum(DimensionAttributeValueCombination), dimensionValueColumnName));
                qbr.value(SysQuery::valueEmptyString());
            }
        }
        //}
    }

 private QueryBuildDataSource getDimensionAttributeValueCombinationDataSource(Query _query)
    {
        QueryBuildDataSource davcDS = _query.dataSourceTable(tableNum(DimensionAttributeValueCombination));
        davcDS.joinMode(JoinMode::InnerJoin);

        return davcDS;
    }

Thanks !! 


















Generate DoCentric AX report and send to customer throw Email using email template

In this requirement, we need to generate a Docentric report for salesInvoice report and send the report as a email attachment using the SysEmailTemplates. Here is the X++ code to generate a Docentric report stream and send it as email attachment.  

internal final class DaxSendDocCentricInvoiceReport
{
    #PrintMgmtSetup
    public static void printSalesInvoiceToMemory(CustInvoiceJour  _custInvoiceJour)
    {
        SalesInvoiceController     salesInvoiceController;
        SalesInvoiceContract       salesInvoiceContract;
        Args                       args = new Args();
        CustInvoiceJour            custInvoiceJour;
        SalesInvoiceJournalPrint   salesInvoiceJournalPrint;
 
       // select custInvoiceJour where custInvoiceJour.SalesId != '';
 
        // Add record to be printed.
        // In order to have the context table we need to set args.record().
        args.record(_custInvoiceJour);
 
        salesInvoiceController = SalesInvoiceController::construct();
        salesInvoiceController.parmReportName(
        PrintMgmtDocType::construct(PrintMgmtDocumentType::SalesOrderInvoice).getDefaultReportFormat());
 
        salesInvoiceContract = salesInvoiceController.parmReportContract().parmRdpContract();
        salesInvoiceContract.parmCountryRegionISOCode(SysCountryRegionCode::countryInfo());
        salesInvoiceController.parmArgs(args);
        salesInvoiceController.parmExecutionMode(SysOperationExecutionMode::Synchronous);
 
        SRSPrintDestinationSettings pds = salesInvoiceController.parmReportContract().parmPrintSettings();
        pds.printMediumType(SRSPrintMediumType::Memory_DC);

        //PrintMgmtPrintContext printContext = new PrintMgmtPrintContext();

        //PrintMgmtPrintDestinationTokens destinationTokens = SrsPrintDestinationTokens::constructByTokenType(#SRSPrintDestinationTokenPrintMgmt);
        //destinationTokens.parmPrintContext(printContext);
        //destinationTokens.replace(pds);

 
        // Use the default Docentric design.
        pds.parmMemoryPrintDestSettings_DC().parmOutputFileFormat(DocOutputFileFormat::PDF);
        pds.parmMemoryPrintDestSettings_DC().parmUseSsrsBuiltInDesign(false);
        pds.parmSrsPrintReportSettings_DC().setProperty_PrintSrsOriginalDesign(false);


        /* Uncomment this code if you want to use the original SSRS design instead */
        //pds.parmMemoryPrintDestSettings_DC().parmOutputFileFormatSrs(SRSReportFileFormat::PDF);
        //pds.parmMemoryPrintDestSettings_DC().parmUseSsrsBuiltInDesign(true);
        //pds.parmSrsPrintReportSettings_DC().setProperty_PrintSrsOriginalDesign(true);
    
        // Initalize SalesInvoiceJournalPrint class instance because there is no other way
        // NOT to use Print Management.
        salesInvoiceJournalPrint = SalesInvoiceJournalPrint::construct();
        salesInvoiceJournalPrint.parmPrintFormletter(NoYes::Yes);
        salesInvoiceJournalPrint.parmUsePrintManagement(false);
        salesInvoiceJournalPrint.parmUseUserDefinedDestinations(true);
        salesInvoiceJournalPrint.parmPrinterSettingsFormLetter(salesInvoiceController.parmReportContract().parmPrintSettings().pack());
 
        args.caller(salesInvoiceJournalPrint);
        args.parmEnumType(enumNum(PrintCopyOriginal));
        args.parmEnum(PrintCopyOriginal::OriginalPrint);
 
        /* Collect the result of the execution */
        // Register event handler to the RenderingComplete event.
        salesInvoiceController.renderingCompleted +=
        eventhandler(DaxSendDocCentricInvoiceReport::printSalesInvoiceToMemory_renderingComplete);
 
        // Start the report execution.
        salesInvoiceController.parmShowDialog(false);
        salesInvoiceController.startOperation();
    }

    public static void printSalesInvoiceToMemory_renderingComplete(SrsReportRunController _sender, SrsRenderingCompletedEventArgs _eventArgs)
    {
        // Get the report execution info.
        DocReportExecutionInfo reportExecutionInfo = _eventArgs.parmReportExecutionInfo().parmReportExecutionInfo_DC();
        DocPrintReportToMemoryExecutionInfo printDestMemoryExecutionInfo = reportExecutionInfo.parmPrintToMemoryExecutionInfo();

        // Use the report content, e.g. upload it to Azure Blob storage.
        using (System.IO.MemoryStream reportMemoryStream = printDestMemoryExecutionInfo.getReportContentAsMemoryStream())
        {
           // File::SendFileToUser(reportMemoryStream, "TestInvoice.PDF");
            PROSendDocCentricInvoiceReport::sendInvoiceByEmail(_eventArgs.parmArgs().record(), "thirupathi.gandi@protivitiGlobal.in", reportMemoryStream );
           // DocAzureBlobHelper::uploadBlob('docentric-report-output', 'TestInvoice.pdf', reportMemoryStream);
        }

        // Do something else with the report content, e.g. send it to a web service...
    }

    public static void sendInvoiceByEmail(CustInvoiceJour _custInvoiceJour, Email _ToEmail, System.IO.MemoryStream _reportStream )
    {
        SysEmailParameters              sysEmailParameters = SysEmailParameters::find();
        #define.SMTP('SMTP')
        Map sysMailers = SysMailerFactory::getMailers();
        SysEmailSystemTable SysEmailSystemTable = SysEmailSystemTable::find("SalesInv");

        SysEmailMessageSystemTable SysEmailMessageSystemTable = SysEmailMessageSystemTable::find("SalesInv",SysEmailSystemTable.DefaultLanguage);

        str messageBody = SysEmailMessageSystemTable.Mail;
        str subject     = SysEmailMessageSystemTable.Subject + '' + _custInvoiceJour.InvoiceID;

        if (SysEmailMessageSystemTable)
        {
            SysMailerMessageBuilder messageBuilder = new SysMailerMessageBuilder();
            try
            {

                messageBuilder.setBody(messageBody);
                messageBuilder.setSubject(subject);
                messageBuilder.addTo(_ToEmail);
                messageBuilder.addCc("thiruvaroon555@gmail.com");
                messageBuilder.addAttachment(_reportStream, strFmt("Sales Invoice %1.PDF", _custInvoiceJour.InvoiceId));
                messageBuilder.setFrom(sysEmailParameters.SMTPUserName);

                SysMailerFactory::sendNonInteractive(messageBuilder.getMessage());
            }
            catch
            {
                exceptionTextFallThrough();
            }
        }
        else
        {
            throw error("Please verify the email template for SalesInvoice");
        }

    }
Thanks!!

Send Email from D365 FO X++

There are multiple ways in Dynamics to send emails to the users.  for  sending  an email from dynamics we need to configure a few things in the system adminstartion.

Configuration : 

Go to System Administrator> Setup> Email> Email parameters










Fill the details in the Configuration Tab as below











Provide the user name and password in the SMTP settings. Fill in the server Name and Port (you can find these details from your EMail > EMail Settings > SMTP settings)












Now Send the test Email to confirm the settings. Make sure "send as" is set to your admin user which you have setup in the step below.

setup the To Mail in below settings and click on "Send test Email"







Sometimes you may get  the issue as below.







To resolve the issue, ensure that the Admin Email ID you set up in the User Options > Accounts > Sender Email ID matches the Send As email ID.


Send email from D365 using Email templates and email history tables :

When we are sending mails using Email templates the following terms will be used : 

  • Email template - Templates stored underOrganization administration>Setup>Organization email templates
  • The underlying tables are SysEmailTable and SysEmailMessageTable.
  • Placeholder - a string enclosed within percent (%) symbols. E.g. %placeholder%.
  • Email processing table - two tables SysOutgoingEmailTable and SysOutgoingEmailData that store emails to be processed by a batch job. Emails can be found under System administration>Periodic tasks>Email processing>Email sending status
Below is the sample code for sending emails using templates. This code allows us to monitor the email sending history and resend in case of failures.
    internal final class DAXSendMailToResponsiblePerson extends RunBaseBatch
{
    /// <summary>
    /// Class entry point. The system will call this method when a designated menu
    /// is selected or when execution starts and this class is set as the startup class.
    /// </summary>
    /// <param name = "_args">The specified arguments.</param>
    public static void main(Args _args)
    {
        DAXSendMailToResponsiblePerson SendMailToResponsiblePerson = DAXSendMailToResponsiblePerson::construct();
        if (SendMailToResponsiblePerson.prompt())
        {
            SendMailToResponsiblePerson.run();
        }
    }

    public void run()
    {
        LedgerPeriodCloseTemplateTask                   ledgerPeriodCloseTemplateTask;
        LedgerPeriodCloseProjectTaskTemplateRelation    ledgerPeriodCloseProjectTaskTemplateRelation;
        LedgerPeriodCloseProjectTask                    ledgerPeriodCloseProjectTask;
        utcdatetime                                     duedate;
        LedgerPeriodCloseWorker                         periodworker;
        DirPersonUser                                   dirpersonuser;


        while select LedgerPeriodCloseProjectTask
            where  LedgerPeriodCloseProjectTask.DAXMailRemainder == NoYes::Yes
            && ledgerPeriodCloseProjectTask.DAXMailSentStatus == NoYes::No
        {
            duedate = DateTimeUtil::addMinutes(LedgerPeriodCloseProjectTask.DueDateTime, -15);

            if(duedate <= DateTimeUtil::getSystemDateTime())
            {
                try
                {
                 ttsbegin;
                    SysEmailSystemTable        sysEmailTable        = SysEmailSystemTable::find(LedgerPeriodCloseProjectTask.DAXHMailTemplate);
                    SysEmailMessageSystemTable sysEmailMessageTable = SysEmailMessageSystemTable::find(sysEmailTable.EmailId, sysEmailTable.DefaultLanguage);

                str messageBody = sysEmailMessageTable.Mail;
                str subject = sysEmailMessageTable.Subject;

                Map placeholderMap = new Map(Types::String, Types::String);

                HcmWorker  hcmWorker  = HcmWorker::find(LedgerPeriodCloseProjectTask.ResponsibleWorker);
                if (hcmWorker)
                {
                    dirpersonuser =  HcmWorker::findPersonUser(hcmWorker.RecId);

                }

                placeholderMap.insert("Employee",hcmWorker.name());
                placeholderMap.insert("Task", LedgerPeriodCloseProjectTask.Name);
               
                messageBody = SysEmailMessage::stringExpand(messageBody, placeholderMap);
                subject     = SysEmailMessage::stringExpand(subject, placeholderMap);
                SysOutgoingEmailTable      outgoingEmailTable;
                SysOutgoingEmailData       outgoingEmailData;
                outgoingEmailTable.EmailItemId                  = EventInbox::nextEventId();
                outgoingEmailTable.TemplateId                   = sysEmailTable.EmailId;
                outgoingEmailTable.Sender                       = sysEmailTable.SenderAddr;
                outgoingEmailTable.SenderName                   = sysEmailTable.SenderName;
                outgoingEmailTable.Recipient                    = SysUserInfo::getUserEmail(dirpersonuser.User);
                outgoingEmailTable.Subject                      = subject;
                outgoingEmailTable.Message                      = messageBody;
                outgoingEmailTable.Priority                     = sysEmailTable.Priority;
                outgoingEmailTable.WithRetries                  = true;
                outgoingEmailTable.RetryNum                     = 0;
                outgoingEmailTable.UserId                       = curuserid();
                outgoingEmailTable.Status                       = SysEmailStatus::Unsent;
                outgoingEmailTable.SessionLoginDateTime         = DateTimeUtil::addMinutes(DateTimeUtil::getSystemDateTime(), 2);
                outgoingEmailTable.LatestStatusChangeDateTime   = DateTimeUtil::getSystemDateTime();
                outgoingEmailTable.insert();
                if (outgoingEmailTable)
                {
                    SysEmailDistributor::construct().run();
                }
                    ledgerPeriodCloseProjectTask.reread();
                    ledgerPeriodCloseProjectTask.selectForUpdate(true);
                    ledgerPeriodCloseProjectTask.DAXMailSentStatus =  NoYes::Yes;
                    ledgerPeriodCloseProjectTask.doUpdate();
                    ttscommit;

            }
                catch (Exception::Deadlock)
                {
                    continue;
                }
                catch (Exception::Error)
                {
                    continue;
                }
                catch (Exception::Warning)
                {
                    CLRInterop::getLastException();
                    continue;
                }
                catch (Exception::CLRError)
                {
                    CLRInterop::getLastException();
                    continue;
                }
            }
        }

    }

    public server static DAXSendMailToResponsiblePerson construct()
    {
        return new DAXSendMailToResponsiblePerson();
    }

    public client server static ClassDescription description()
    {
        return "Send Mail remainders";
    }

    protected boolean canGoBatchJournal()
    {
        return true;
    }

    public boolean canGoBatch()
    {
        return true;
    }

    public boolean runsImpersonated()
    {
        return true;
    }

    protected boolean canRunInNewSession()
    {
        return true;
    }

}

Send Email with an attachment using SysMailer classes

Let's send an email with a report attachment to the corresponding email address. In my case, I'm sending the email to the registered vendor's email address when a vendor registers with the vendor portal.

Below is the Method responsible for sending mail with the  report stream.
    public void sendEmail(System.IO.MemoryStream   _mstream, Filename    _fileName, str _requestID,Email _mailFrom,Email _mailTo)
    {
        Map                                         templateTokens;
        str                                         emailSubject,emailBody;
        Filename                                    fileName;
        SysEmailTable                               SysEmailTable;
       
        var messageBuilder      = new SysMailerMessageBuilder();
        templateTokens          = new Map(Types::String, Types::String);

        
        emailSubject = "Vendor Request Report";
        templateTokens.insert("@SYS74341", emailSubject);
        templateTokens.insert("@SYS4009003", _fileName);
        emailBody =  strFmt("A vendor request has been registerd with the vendor request id %1.Please find the attached document for more information.",_requestID);
        emailBody = SysEmailMessage::stringExpand(emailBody, SysEmailTable::htmlEncodeParameters(templateTokens));
        emailBody = strReplace(emailBody, '\n', '<br>');
        emailBody +='<br><br>Thanks,<br>' +'ERPTeam';
        messageBuilder.addTo(_mailTo)
                                    .setSubject(emailSubject)
                                    .setBody(emailBody)
                                    .addCC("");

        messageBuilder.setFrom(_mailFrom, "ERPTeam") ;
        messageBuilder.addAttachment(_mstream, _fileName);

        SysMailerFactory::sendNonInteractive(messageBuilder.getMessage());

        info(strFmt("Email sent successfully for vendor request %1",_requestID));
    }
Additionally, below is the report bytes generate method used to generate and send the stream to above method.
    public static void generateReportAsBytes(VendVendorRequestNewVendor   _vendVendorRequestNewVendor,VendProspectiveVendorRegistrationRequest _vendorRegistrationRequest)
    {
        System.IO.MemoryStream                      mstream;
        Filename                                    fileName = strFmt("VendorRequest - %1.pdf", _vendVendorRequestNewVendor.RequestId);
        Args                                        args = new Args();
        PROSendEmailForVendRequest                  sendEmailForVendRequest = new PROSendEmailForVendRequest();

        VendProspectiveVendorRegistrationRequest    vendProspectiveVendorRegistrationRequest;
        VendProspectiveVendorRegistration           vendProspectiveVendorRegistration;
        SysEmailParameters                          sysEmailParameters = SysEmailParameters::find();

        try
        {
            args.record(_vendVendorRequestNewVendor);
            // get the report Contract tp intilize the request ID
            PRO_vendRequestContractClass vendRequestContractClass = new PRO_vendRequestContractClass();
            vendRequestContractClass.parmVendRequestNumber(_vendVendorRequestNewVendor.RequestId);

            // Runs the report 
            SrsReportRunController  srsReportRunController = new SrsReportRunController();
            srsReportRunController.parmReportName(ssrsReportStr(PRO_VendRequest, Report));
            srsReportRunController.parmExecutionMode(SysOperationExecutionMode::Asynchronous);
            srsReportRunController.parmShowDialog(false);
            srsReportRunController.parmReportContract().parmRdpContract(vendRequestContractClass);
            srsReportRunController.parmReportContract().parmReportExecutionInfo(new SRSReportExecutionInfo());
            srsReportRunController.parmReportContract().parmReportServerConfig(SRSConfiguration::getDefaultServerConfiguration());

            //Set the Printing settings for the report generation
            SRSPrintDestinationSettings printerSettings = srsReportRunController.parmReportContract().parmPrintSettings();
            printerSettings.printMediumType(SRSPrintMediumType::File);
            printerSettings.fileFormat(SRSReportFileFormat::PDF);
            printerSettings.parmFileName(fileName);
            printerSettings.overwriteFile(true);

            // Run the report service class to get the report stream 
            SRSReportRunService srsReportRunService = new SrsReportRunService();
            srsReportRunService.getReportDataContract(srsReportRunController.parmReportContract().parmReportName());
            srsReportRunService.preRunReport(srsReportRunController.parmReportContract());
            Map reportParametersMap = srsReportRunService.createParamMapFromContract(srsReportRunController.parmReportContract());
            Microsoft.Dynamics.AX.Framework.Reporting.Shared.ReportingService.ParameterValue[]  parameterValueArray = SrsReportRunUtil::getParameterValueArray(reportParametersMap);

            SRSProxy srsProxy = SRSProxy::constructWithConfiguration(srsReportRunController.parmReportContract().parmReportServerConfig());

            System.Byte[] reportBytes = srsproxy.renderReportToByteArray(srsReportRunController.parmreportcontract().parmreportpath(),
                                                parameterValueArray,
                                                printerSettings.fileFormat(),
                                                printerSettings.deviceinfo());
            mstream         = new System.IO.MemoryStream(reportBytes);

            // send the stream to send Mail method
            sendEmailForVendRequest.sendEmail(mstream, fileName,_vendVendorRequestNewVendor.RequestId, sysEmailParameters.SMTPUserName,_vendorRegistrationRequest.Email);
        }
        catch
        {
            throw error(sendEmailForVendRequest.getErrorStr());
        }
    }
Trigger the above methods from Main method. 
    public static void main(Args _args)
    {
        DaxvendRequestContractClass                contractclass =  new DaxvendRequestContractClass();
        VendProspectiveVendorRegistrationRequest    vendProspectiveVendorRegistrationRequest;
        VendProspectiveVendorRegistration           vendProspectiveVendorRegistration;
        VendVendorRequestNewVendor                  vendVendorRequestNewVendor;
        SysEmailParameters                          sysEmailParameters;
        PurchParameters                             purchParameters = PurchParameters::find();

        // Get the data fro Vendor request table for the current vendor
        VendVendorRequest       vendVendorRequest = _args.record();

        if (purchParameters.DaxSendEmailForVendorRequest)
        {
            select vendVendorRequestNewVendor
                where vendVendorRequestNewVendor.RecId == vendVendorRequest.RecId;

            select vendProspectiveVendorRegistration
                where vendProspectiveVendorRegistration.RecId == vendVendorRequestNewVendor.ProspectiveVendorRegistration;

            select vendProspectiveVendorRegistrationRequest
                where vendProspectiveVendorRegistrationRequest.RecId == vendProspectiveVendorRegistration.RegistrationRequest;

            DaxSendEmailForVendRequest::generateReportAsBytes(vendVendorRequestNewVendor,vendProspectiveVendorRegistrationRequest);
        }
        else
        {
            checkFailed(strFmt("field %1 in table %2 must be enabled. Please validate the setup in procurment and sourcing parameters", fieldPName(PurchParameters, DaxSendEmailForVendorRequest), tablePName(PurchParameters)));
        }
    }


We can also Send Email using standard email alerts, Business events and Power automate flow, we can see that in my upcoming posts.

Thanks !!