Thursday, October 2, 2025

Run ER Report through code and send as an Email attachment

str subject = strFmt("Subject: %1 [Ref#]", AccountNum);
str body = "<body><p>Dear Sir/Madam,</p><p>Please find attached.</p><p>Yours sincerely,<br>XXXX</p></body>";

if (email)
{
	var fileDestination = ERObjectsFactory::createFileDestinationAttachmentWithOtherDocuType(CustTable);

	Query query = new Query();
	query.addDataSource(tableNum(CustTable))
	.addRange(fieldNum(CustTable, AccountNum))
	.value(CustTable.AccountNum);
	
	str attachmentName = strFmt("OP-%1-%2", CustTable.AccountNum , DateTimeUtil::getSystemDateTime());

	str fileName = strFmt("%1.docx",attachmentName);

	ERObjectsFactory::createFormatMappingRunByFormatMappingId((select ERFormatMappingTable where ERFormatMappingTable.Name == CASParameters::find().ERCorporatePreSuspensionFormatName).RecId, fileName)
	.withFileDestination(fileDestination)
	.withParameter(new ERModelDefinitionDatabaseContext()
	.addQuery(tableNum(CustTable), query))
	.run();

	EmailService_XX::sendEmailAttachment(subject, body, email, this.getAttachmentData(member,fileName), attachmentName);
}
else
{
	throw error(strFmt("No primary email found for : %1", AccountNum));
}


 public static void sendEmailAttachment(
        str                     _subject,
        str                     _body,
        SysEmailAddress         _emailAddr,
        DocuRef                 _docuRef,
        str                     _fileName)
{
	SysMailerMessageBuilder mailer = new SysMailerMessageBuilder();

	mailer.setSubject(_subject);
	mailer.setFrom(SysEmailParameters::find().SMTPUserName);
	mailer.setBody(_body);
	mailer.addTo(_emailAddr);

	var conversionResult = ERPdfConversionService::convertFileToPdf(DocumentManagement::getAttachmentStream(_docuRef), ".docx");

	mailer.addAttachment(conversionResult.ResultStream,strFmt("%1.pdf",_fileName));

	try
	{
		SysMailerFactory::sendNonInteractive(mailer.getMessage());
	}
	catch(Exception::CLRError)
	{
		System.Exception ex = CLRInterop::getLastException();
		error(ex.Message);
		ttsbegin;
		_docuRef.selectForUpdate(true);
		_docuRef.delete();
		ttscommit;
	}
	finally
	{
		ttsbegin;
		_docuRef.selectForUpdate(true);
		_docuRef.delete();
		ttscommit;
	}
	
}

//////////Another class

using Microsoft.Dynamics365.LocalizationFramework;
using TL = Microsoft.Dynamics365.LocalizationFramework.Telemetry;
using BCS = Microsoft.Dynamics365.LocalizationFramework.ExternalApi.BCS;
using DC = Microsoft.Dynamics365.LocalizationFramework.Services.DocumentConversion;
using EA = Microsoft.Dynamics365.LocalizationFramework.ExternalApi;

/// <summary>
/// Class to provide access to PDF conversion process for Office documents.
/// </summary>
static class ERPdfConversionService
{
    private static EA.MicrosoftInternalUseOnly.IOfficeConversionClient conversionClient;
    /// <summary>
    /// Converts given Microsoft Word or Excel file stream to PDF.
    /// </summary>
    /// <param name = "_stream">A given file stream.</param>
    /// <param name = "_fileExtension">A given file extension.</param>
    /// <param name = "_requestId">A request id to track request; optional.</param>
    /// <param name = "_conversionSettings">Conversion settings; optional.</param>
    /// <param name = "_localeIdentifier">The locale identifier.</param>
    /// <returns>The conversion result.</returns>
    [Hookable(false)]
    static BCS.ConvertToPdfResponse convertFileToPdf(
        System.IO.Stream _stream, 
        str _fileExtension, 
        guid _requestId = newGuid(), 
     //   ERFormatFileDestinationPdfConversionSettings _conversionSettings = null,
        int _localeIdentifier = 0
    )
    {
        BCS.ConvertToPdfRequest request = new BCS.ConvertToPdfRequest();
        request.FileStream = _stream;
        request.ClientCorrelationId = _requestId;
        request.ClientName = identifierStr(DynamicsAxElectronicReporting);
        request.FileExtension = _fileExtension;
        request.PageOptions =  null;
        request.LocaleIdentifier = _localeIdentifier;
        var conversionClient1 = CASERPdfConversionService::getClient();
        return conversionClient1.ConvertToPdf(request, TL.ElectronicReportingMappingTelemetryLogger::Log);
    }

    /// <summary>
    /// Gets the client.
    /// </summary>
    /// <returns>Office conversion client.</returns>
    internal static EA.MicrosoftInternalUseOnly.IOfficeConversionClient getClient()
    {
        if (conversionClient == null)
        {
            conversionClient = EA.BCS.BcsApiClient::Instance;
        }
        return conversionClient;
    }

    /// <summary>
    /// Sets the client.
    /// </summary>
    /// <param name = "_conversionClient">A conversion client.</param>
    /// <remarks>For testing purpose.</remarks>
    internal static void setClient(EA.MicrosoftInternalUseOnly.IOfficeConversionClient _conversionClient)
    {
        conversionClient = _conversionClient;
    }
}


///////

Currency Exchange Helper

CurrencyExchangeHelper currencyExchangeHelper;
CurrencyCode toCurrency = _claimLine.CurrencyClaimed;
CurrencyCode FromCurrency = modifierAward.FixedAmountCurrencyCode;
currencyExchangeHelper = CurrencyExchangeHelper::newExchangeDate(Ledger::current(), _claimLine.TreatmentDate);
fixedAmt =  currencyExchangeHelper.calculateCurrencyToCurrency(toCurrency, fromCurrency,modifierAward.FixedAmount,true);

Sort a Map Descending order in X++

 public static Map sortbasedonRVUlevel(Map _unsortedMap, SortOrder _sortOrder = SortOrder::Descending)
 {
     // Initialize map with key=int, value=real
     Map sortedMap = new Map(Types::Int64, Types::Real);
     MapEnumerator e;
     container pairs = conNull();
     int64 key;
     int counter, i, j;
     real valueA, valueB;
     container temp;

     // Insert test data
     //m.insert(1, 10);
     //m.insert(2, 50);
     //m.insert(3, 20);
     //m.insert(4, 80);
     //m.insert(5, 40); 
     //m.insert(6, 40);

     // Gather all entries into the main container
     e = _unsortedMap.getEnumerator();
     while (e.moveNext())
     {
         key = e.current();
         pairs += [[key, _unsortedMap.lookup(key)]];
     }

     // Sort container pairs by value descending (bubble sort for demonstration)
     counter = conLen(pairs);
     for (i = 1; i < counter; i++)
     {
         for (j = i + 1; j <= counter; j++)
         {
             valueA = conPeek(conPeek(pairs, i), 2);
             valueB = conPeek(conPeek(pairs, j), 2);
             if(_sortOrder == SortOrder::Descending)
             {
                 if (valueA < valueB) // For descending order
                 {
                     temp = conPeek(pairs, i);
                     pairs = conPoke(pairs, i, conPeek(pairs, j));
                     pairs = conPoke(pairs, j, temp);
                 }
             }
             else
             {
                 if (valueA > valueB) // For ascending order
                 {
                     temp = conPeek(pairs, i);
                     pairs = conPoke(pairs, i, conPeek(pairs, j));
                     pairs = conPoke(pairs, j, temp);
                 }
             }
         }
     }

     // Output sorted pairs
     for (i = 1; i <= counter; i++)
     {
         key = conPeek(conPeek(pairs, i), 1);
         valueA = conPeek(conPeek(pairs, i), 2);
         sortedMap.add(key, valueA);
         info(strFmt("Key: %1, Value: %2", key, valueA));
     }

     return sortedMap;
 }

Generate custom URL D365 FO

public static str generateFTILink(RecId _invoiceIdentifier, DataAreaId _dataAreaId)
{
    //gets the generator instance
    var generator     = new Microsoft.Dynamics.AX.Framework.Utilities.UrlHelper.UrlGenerator();
    var currentHost   = new System.Uri(UrlUtility::getUrl());
    generator.HostUrl = currentHost.GetLeftPart(System.UriPartial::Authority);
    generator.Company = _dataAreaId;
    generator.MenuItemName = menuItemDisplayStr(CustFreeInvoiceListPage);
    generator.Partition = getCurrentPartition();
    generator.PageType = FormViewOption::Details;

    var requestQueryParameterCollection = generator.RequestQueryParameterCollection;
    requestQueryParameterCollection.AddRequestQueryParameter(
    formDataSourceStr(CustFreeInvoice, CustInvoiceTable),
    'RecId', int642Str(_invoiceIdentifier));

    System.Uri fullURI = generator.GenerateFullUrl();

    // to get the encoded URI, use the following code
    return fullURI.AbsoluteUri;
}

Thursday, January 30, 2025

Reserve and unreserve Item using X++

public void reserveItem(ItemId _itemId,InventTransId  _inventTransId, Purchline _purchline)
{
    InventTrans             inventTrans;
    InventTransOrigin       inventTransOrigin;
    InventMovement          inventMovement;
    InventUpd_Reservation   inventUpd_Reservation ;

    InventDim inventdim;

    // Reserve an item
    select inventTrans
        where  inventTrans.ItemId                   == _itemId
        &&  inventTrans.StatusReceipt               == StatusReceipt::None
        &&  inventTrans.StatusIssue                 == StatusIssue::OnOrder
        exists join inventTransOrigin
        where   inventTransOrigin.RecId            == inventTrans.InventTransOrigin
        && inventTransOrigin.InventTransId == _inventTransId
        && inventTrans.MarkingRefInventTransOrigin == InventTransOrigin::findByInventTransId(_purchline.InventTransId).RecId;

    inventdim = inventTrans.inventDim();

    inventdim.inventBatchId = strFmt("%1-%2",_purchline.PurchId,_purchline.InventTransId );

    inventdim = inventdim::findDim(inventdim);

    if(inventTrans.RecId)
    {
        Inventmovement = inventTrans.inventmovement(true);
        inventUpd_Reservation = InventUpd_Reservation::newInventDim(inventmovement,inventdim ? inventdim : inventTrans.inventDim(),inventTrans.Qty, false);
        inventUpd_Reservation.updatenow();
    }
}

public void removeReserveItem(ItemId _itemId,InventTransId  _inventTransId)
{
    InventTrans             inventTrans;
    InventTransOrigin       inventTransOrigin;
    InventMovement          inventMovement;
    InventUpd_Reservation   inventUpd_Reservation ;

    // Remove reservations and markings on a reserved transfer order
    while select inventTrans
        where  inventTrans.ItemId                      == _itemId
        &&  inventTrans.StatusReceipt               == StatusReceipt::None
        && (inventTrans.StatusIssue                 == StatusIssue::ReservPhysical
        ||  inventTrans.StatusIssue                 == StatusIssue::ReservOrdered)
        exists join inventTransOrigin
        where   inventTransOrigin.RecId            == inventTrans.InventTransOrigin
        && inventTransOrigin.InventTransId == _inventTransId
    {
        if (inventTrans.StatusIssue == StatusIssue::ReservPhysical || inventTrans.StatusIssue == StatusIssue::ReservOrdered)
        {
            Inventmovement = inventTrans.inventmovement(true);
            inventUpd_Reservation = InventUpd_Reservation::newInventDim(inventmovement,inventTrans.inventDim(), -1 * inventTrans.Qty, false);
            inventUpd_Reservation.updatenow();
        }
    }
}

Trigger Sysoperation through button click

class PurchTableForm_EventHandler
{
    
    /// <summary>
    /// run business logic for ConnectTransferOrder button
    /// </summary>
    /// <param name="sender">sender</param>
    /// <param name="e">e</param>
    [FormControlEventHandler(formControlStr(PurchTable, ConnectTransferOrder), FormControlEventType::Clicked),SuppressBPWarning('BPParameterNotUsed', 'Parameter required')]
    public static void ConnectTransferOrder_OnClicked(FormControl sender, FormControlEventArgs e)
    {
        FormDataSource formds = sender.formRun().dataSource(formDataSourceStr(PurchTable, PurchTable));
        PurchTable purchTable = PurchTable::findRecId(formds.cursor().RecId);

        CreateUpdateTransferOrderController controller;
        SysOperationStartResult sysOperationStartResult;
    
        Args args = new Args();

        args.caller(sender.formRun());
        args.record(purchTable);

        controller = CreateUpdateTransferOrderController::newFromArgs(args);
        controller.parmExecutionMode(SysOperationExecutionMode::Synchronous);

        sysOperationStartResult = controller.startOperation();

        formds.refresh();
        formds.reread();
    }

}

Parse JSON response using X++ to vairables

str output =//JSON String;
output = strRem(output, "[");
output = strRem(output, "]");

Map             jsonData;
jsonData = RetailCommonWebAPI::getMapFromJsonString(output);

container       Error,Log,LinkIds,currentReponse;
ListEnumerator  listEnumerator,listenum;
MapEnumerator   mapEnumerator;
mapEnumerator = jsonData.getEnumerator();
int                     AccNum;
int                     Name;
int                     Phone;
int                     address;
str                     errorTxt;
str                     logTxt;
str                     hasErrorStr;
while (mapEnumerator.moveNext())
{
    switch (mapEnumerator.currentKey())
    {
        case "Error":
            hasErrorStr = mapEnumerator.currentValue();
            break;
        case "Log":
            logTxt =  mapEnumerator.currentValue();
            break;
        case "CustomerDetails"://json array 
            currentReponse = mapEnumerator.currentValue();
            if(currentReponse)
            {
                for (int i=1; i <= conlen(currentReponse); i++)
                {
                    switch(conpeek(currentReponse, i))
                    {
                        case "AccNum":
                            AccNum = conPeek(currentReponse,i+1);
                            break;
                        case "Name":
                            Name = conPeek(currentReponse,i+1);
                            break;
                        case "Phone":
                            Phone = conPeek(currentReponse,i+1);
                            break;
                        case "address":
                            address = conPeek(currentReponse,i+1);
                            break;
                    }
                }
            }
            break;
    }
}

Sysoperation multithread using subtasks

class CustInvoiceProcessBaseService extends SysOperationServiceBase
{
    public void process(CustInvoiceProcessContract _contract)
    {
        Query           query;
        QueryRun        queryRun;
        CustInvoiceJour custInvoiceJour;
        int             defaultBundleSize = _contract.parmbundleSize();
        int             defaultRecordLimit = _contract.parmRecordLimit();

        query = _contract.getQuery();

        queryRun = new QueryRun(query);

        int                     counter = 0, lineCount = 0;
        BatchHeader             batchHeader =  this.getCurrentBatchHeader();
        List                    customerInvoiceList = new List(Types::Int64);

        while (queryRun.next())
        {
            custInvoiceJour = queryRun.get(tableNum(CustInvoiceJour));

            if (batchHeader)
            {
                counter++;

                customerInvoiceList.addEnd(custInvoiceJour.RecId);
               
                if (counter == DefaultBundleSize)
                {
                    CustInvoiceProcessSubTaskController accountResolver = CustInvoiceProcessSubTaskController::newTaskController(customerInvoiceList, strFmt("@DistITServices:BundleSize", lineCount == 0 ? 1 : lineCount, lineCount + counter));

                    batchHeader.addRuntimeTask(accountResolver, BatchHeader::getCurrentBatchTask().RecId);

                    customerInvoiceList = new List(Types::Int64);

                    lineCount = lineCount + counter;

                    counter = 0;
                }

                if (lineCount >= DefaultRecordLimit)
                {
                    break;
                }
            }
            else
            {
                lineCount++;
                CustInvoiceProcessSubTaskService::run(custInvoiceJour);
            }
        }

        if (batchHeader)
        {
            //To add last lines
            if (counter != 0 && counter <= DefaultBundleSize)
            {
                CustInvoiceProcessSubTaskController accountResolver = CustInvoiceProcessSubTaskController::newTaskController(customerInvoiceList, strFmt("@DistITServices:BundleSize",lineCount == 0 ? 1 : lineCount, lineCount + counter));

                batchHeader.addRuntimeTask(accountResolver, BatchHeader::getCurrentBatchTask().RecId);

                customerInvoiceList = new List(Types::Int64);

                lineCount = lineCount + counter;

                counter = 0;
            }

            if (batchHeader)
            {
                batchHeader.save();

                if (lineCount)
                {
                    info(strFmt("%1 : %2", "@DMP1153", lineCount));
                }
            }
        }
        else if (lineCount)
        {
            info(strFmt("%1 : %2", "@DMF:NumberOfRecordsProcessed", lineCount));
        }
    }

}
///////////////////////////

[DataContractAttribute]
class CustInvoiceProcessSubTaskContract  extends SysOperationDataContractBase
{
    List custInvoiceJourList = new List(Types::Int64);
 
    [
        DataMemberAttribute,
        SysOperationControlVisibilityAttribute(false),
        AifCollectionTypeAttribute('return', Types::Int64)
    ]
    public List parmCustInvoiceJourList( List _custInvoiceJourList = custInvoiceJourList )
    {
        custInvoiceJourList  =   _custInvoiceJourList;

        return custInvoiceJourList;
    }

}
//////////////////////////

class CustInvoiceProcessSubTaskController extends SysOperationServiceController
{
    public ClassDescription defaultCaption()
    {
        return "@DistITServices:CustInvoiceProcess";
    }

    private void new()
    {
        super();

        this.parmClassName(classStr(CustInvoiceProcessSubTaskService_DIS));
        this.parmMethodName(methodStr(CustInvoiceProcessSubTaskService_DIS, process));
        this.parmExecutionMode(SysOperationExecutionMode::Synchronous);
    }

    public static SysOperationController newTaskController(List _list, str _taskDesc)
    {
        CustInvoiceProcessSubTaskControllercontroller  = new CustInvoiceProcessSubTaskController_DIS();
        CustInvoiceProcessSubTaskContract  contract    = controller.getDataContractObject() as CustInvoiceProcessSubTaskContract_DIS;

        controller.parmDialogCaption(strFmt("@DistITServices:BatchTaskDescription", _taskDesc));

        contract.parmCustInvoiceJourList(_list);

        return controller;
    }

    [Hookable(false)]
    public boolean canGoBatch()
    {
        return true;
    }

    protected boolean canRunInNewSession()
    {
        return true;
    }

    [Hookable(false)]
    public boolean parmShowDialog(boolean _showDialog = showDialog)
    {
        return false;
    }

    [Hookable(false)]
    public boolean parmShowProgressForm(boolean _showProgressForm = showProgressForm)
    {
        return false;
    }

}
//////////////////////////////////////////

class CustInvoiceProcessSubTaskService extends SysOperationServiceBase
{
    public void process(CustInvoiceProcessSubTaskContract _contract)
    {
        ListIterator custInvoiceJourListIterator = new ListIterator(_contract.parmCustInvoiceJourList());
        container conCompleted, conEDIFailed, conEmailFailed, conNotSent;
        while (custInvoiceJourListIterator.more())
        {
           
            custInvoiceJourListIterator.next();
        }
    }
    public static void run(CustInvoiceJour _custInvoiceJour)
    {
        #OCCRetryCount
        try
        {
            
        }
        catch (Exception::Error)
        {
            throw Error("@SYS138340");
        }
        catch (Exception::Deadlock)
        {
            if (xSession::currentRetryCount() >= #RetryNum)
            {
                throw Exception::Deadlock;
            }
            else
            {
                retry;
            }
        }
        catch(Exception::UpdateConflict)
        {
            if (appl.ttsLevel() == 0)
            {
                if (xSession::currentRetryCount() >= #RetryNum)
                {
                    throw Exception::UpdateConflictNotRecovered;
                }
                else
                {
                    retry;
                }
            }
            else
            {
                throw Exception::UpdateConflict;
            }
        }
    }
}

Table browser URL in D365FO

Critical Thinking icon icon by Icons8