Wednesday, May 14, 2025

Upload file CSV/TXT using SysOperation in D365 X++

Upload CSV/TXT using SysOperation X++


Contract:
/// <summary>
/// Contract class upload through excel for PO inbound journals
/// </summary>
[DataContractAttribute,
    SysOperationContractProcessingAttribute(classStr(SAN_POImportUIBuilder))]
public class SAN_POImportContract extends SysOperationDataContractBase 
                    Implements SysOperationInitializable, SysOperationValidatable
{
    FileName                    fileName;
    private System.IO.Stream    inputDataStream;
    NoYes                       hasColumnNames;
    Str1260                     layout;

    /// <summary>
    /// Validates the parameters.
    /// </summary>
    /// <returns>
    /// true if successful; otherwise, false.
    /// </returns>
    public boolean validate()
    {
        return true;
    }

    public void initialize()
    {
        hasColumnNames = NoYes::Yes;
    }

    /// <summary>
    /// Gets or sets the value of the data contract parameter fileName.
    /// </summary>
    /// <param name="_fileName">
    /// The new value of the data contract parameter fileName.
    /// </param>
    /// <returns>
    /// Returns the current value of data contract parameter fileName.
    /// </returns>
    public Filename parmFileName(FileName _fileName = fileName)
    {
        fileName = _fileName;
        return fileName;
    }

    /// <summary
    /// Gets or sets the value of the data contract parameter hasColumnNames.
    /// </summary>
    /// <param name = "_hasColumnNames">
    /// The new value of the data contract parameter hasColumnNames.
    /// </param>
    /// <returns>
    /// Returns the current value of data contract parameter hasColumnNames.
    /// </returns>
    [DataMemberAttribute,
        SysOperationLabelAttribute("File has header column") ]
    public NoYes parmHasColumnNames(NoYes _hasColumnNames = hasColumnNames)
    {
        hasColumnNames = _hasColumnNames;
        return hasColumnNames;
    }

    /// <summary>
    /// Set or get Input file data stream
    /// </summary>
    /// <param name = "_value">current value of stream</param>
    /// <returns>file memory stream to process</returns>
    [Hookable(false)]
    public System.IO.Stream parmInputDataStream(System.IO.Stream _value = inputDataStream)
    {
        inputDataStream = _value;
        return inputDataStream;
    }

    /// <summary>
    /// Gets or sets the value of the data contract parameter fileLayout.
    /// </summary>
    /// <param name="_layout">
    /// The new value of the data contract parameter fileLayout.
    /// </param>
    /// <returns>
    /// Returns the current value of data contract parameter fileLayout.
    /// </returns>
    [DataMemberAttribute,
        SysOperationLabelAttribute("File layout") ]
    public Str1260 parmLayout(Str1260 _layout = layout)
    {
        layout =  '';
        layout =  'TXT/CSV File delimited as |' + '\n';

        layout += strfmt("%1 | %2 : %3\n", '1', 'A', 'Column1');
        layout += strfmt("%1 | %2 : %3\n", '2', 'B', 'Column2');
        layout += strfmt("%1 | %2 : %3\n", '3', 'C', 'Column3');
        layout += strfmt("%1 | %2 : %3\n", '4', 'D', 'Column4');
        layout += strfmt("%1 | %2 : %3\n", '5', 'E', 'Column5');


        return layout;
    }

}

UI Builder:
/// <summary>
/// Class is to upload through excel for PO inbound substitution journals to handle run time contract operations
/// </summary>
class SAN_POImportUIBuilder extends SysOperationAutomaticUIBuilder
{

    #define.commandButton('CommandButton')
    #define.fileUpload('FileUpload')
    #define.fileTypesAccepted('.txt,.csv')
    
    SAN_POImportContract   contract;

    /// <summary>
    /// Adds a button to the dialog to import a file.
    /// </summary>
    [Hookable(false)]
    public void build()
    {
        DialogGroup             dialogGroup;
        DialogTabPage           dialogTabPage;
        Dialog                  dlg;
        FormBuildControl        formBuildControl;
        FileUploadBuild         dialogFileUpload;
        DialogField             dialogLayout;
        FormBuildTabPageControl parametersTabPage;
        FormBuildTabPageControl layoutTabPage;

        dlg = this.dialog();

        contract = this.dataContractObject() as SAN_POImportContract;

        dialogTabPage = dlg.addTabPage("@SYS7764");
        dialogTabPage.columns(2);
        parametersTabPage = dlg.formBuildDesign().control(dialogTabPage.name());
        parametersTabPage.fastTabExpanded(FastTabExpanded::Yes);
        dialogGroup = dlg.addGroup('Upload file');
        formBuildControl = dlg.formBuildDesign().control(dialogGroup.name());
        dialogFileUpload = formBuildControl.addControlEx(classstr(FileUpload), #fileUpload);
        dialogFileUpload.style(FileUploadStyle::MinimalWithFilename);
        dialogFileUpload.baseFileUploadStrategyClassName(classstr(FileUploadTemporaryStorageStrategy));
        dialogFileUpload.fileTypesAccepted(#fileTypesAccepted);
        dialogFileUpload.fileNameLabel("@SYS308842");

        dlg.addGroup("@SYS339526");
        this.addDialogField(methodStr(SAN_POImportContract, parmHasColumnNames), contract);

        dialogTabPage = dlg.addTabPage("@Polaris:FileLayout");
        layoutTabPage = dlg.formBuildDesign().control(dialogTabPage.name());
        layoutTabPage.fastTabExpanded(FastTabExpanded::Yes);
        dialogLayout = this.addDialogField(methodStr(SAN_POImportContract, parmLayout), contract);
        dialogLayout.showLabel(false);
        dialogLayout.enabled(false);
        dialogLayout.widthMode(1);
        dialogLayout.displayHeight(15);
    }

    /// <summary>
    /// Handles dialog closing events.
    /// </summary>
    /// <param name = "sender">
    /// xFormRun class.
    /// </param>
    /// <param name = "e">
    /// FormEventArgs class.
    /// </param>
    [SuppressBPWarningAttribute('BPParametersNotUsed', 'This event parameter is not used')]
    private void dialogClosing(xFormRun sender, FormEventArgs e)
    {
        FormEventArgs formEventArgs;

        formEventArgs = e;

        this.dialogEventsUnsubscribe(sender as FormRun);
    }

    /// <summary>
    /// Handles dialog events.
    /// </summary>
    /// <param name = "_formRun">
    /// FormRun class type.
    /// </param>
    private void dialogEventsSubscribe(FormRun _formRun)
    {
        FileUpload fileUpload = _formRun.control(_formRun.controlId(#fileUpload));
        fileUpload.notifyUploadCompleted += eventhandler(this.uploadCompleted);
        fileUpload.notifyUploadAttemptStarted += eventhandler(this.uploadStarted);
        _formRun.onClosing += eventhandler(this.dialogClosing);
    }

    /// <summary>
    /// Handles dialog event.
    /// </summary>
    /// <param name = "_formRun">
    /// FormRun class type.
    /// </param>
    private void dialogEventsUnsubscribe(FormRun _formRun)
    {
        FileUpload fileUpload = _formRun.control(_formRun.controlId(#fileUpload));
        fileUpload.notifyUploadCompleted -= eventhandler(this.uploadCompleted);
        fileUpload.notifyUploadAttemptStarted -= eventhandler(this.uploadStarted);
        _formRun.onClosing -= eventhandler(this.dialogClosing);
    }

    /// <summary>
    /// Handles dialog event post run.
    /// </summary>
    [Hookable(false)]
    public void postRun()
    {
        super();
        FormRun formRun = this.dialog().dialogForm().formRun();
        this.dialogEventsSubscribe(formRun);
        this.setDialogOkButtonEnabled(formRun, false);
    }

    /// <summary>
    /// Enables or disables the dialog Ok button.
    /// </summary>
    /// <param name = "_formRun">
    /// The <c>FormRun</c> object.
    /// </param>
    /// <param name = "_isEnabled">
    /// Indicates to enable or disable the Ok button.
    /// </param>
    protected void setDialogOkButtonEnabled(FormRun _formRun, boolean _isEnabled)
    {
        FormControl okButtonControl = _formRun.control(_formRun.controlId(#commandButton));
        if (okButtonControl)
        {
            okButtonControl.enabled(_isEnabled);
        }
    }

    /// <summary>
    /// After the file has been uploaded, the Ok button is enabled.
    /// </summary>
    protected void uploadCompleted()
    {
        contract = this.dataContractObject() as SAN_POImportContract;
        var formRun = this.dialog().dialogForm().formRun();
        FileUpload fileUpload = formRun.control(formRun.controlId(#fileUpload));

        contract.parmFileName(fileUpload.fileName());
        using (System.IO.Stream stream = fileUpload.getUploadedFile(true))
        {
            if (stream)
            {
                System.IO.MemoryStream copiedStream = new System.IO.MemoryStream();
                stream.CopyTo(copiedStream);
                
                contract.parmInputDataStream(copiedStream);
            }
        }

        this.setDialogOkButtonEnabled(formRun, contract.parmInputDataStream() != null);
    }

    /// <summary>
    /// During file upload, the Ok button is disabled.
    /// </summary>
    private void uploadStarted()
    {
        var formRun = this.dialog().dialogForm().formRun();
        this.setDialogOkButtonEnabled(formRun, false);
    }

}


Controller:
/// <summary>
/// Class is to handle controller logic to upload  PO journals
/// </summary>
class SAN_POImportController extends SysOperationServiceController
{
    public ClassDescription caption()
    {
        return "Upload  Purchase Journals";
    }

    /// <summary>
    /// Provides entry point for the instance of <c>SAN_POImportController</c>.
    /// </summary>
    /// <param name = "_args">
    /// The arguments passed to the class <c>SAN_POImportController</c>.
    /// </param>
    public static void main(Args _args)
    {
        SAN_POImportController controller;

        controller = SAN_POImportController::construct();
        controller.showBatchTab(false);
        controller.parmArgs(_args);
        controller.startOperation();
    }

    /// <summary>
    /// Initializes new instance of <c>SAN_POImportController</c>.
    /// </summary>
    /// <returns>
    /// Return object of <c>SAN_POImportController</c>.
    /// </returns>
    public static SAN_POImportController construct()
    {
        SAN_POImportController   controller;
        
        controller = new SAN_POImportController(classStr(SAN_POImportService),
                                                        methodStr(SAN_POImportService, Run),
                                                        SysOperationExecutionMode::Synchronous);

        return controller;
    }

}

Service:
/// <summary>
/// Class is to upload through excel for PO upload journals to handle service operations
/// </summary>
class SAN_POImportService extends SysOperationServiceBase
{
    System.IO.Stream                        stream;
    int                                     totalLinesProcessed = 0;
    SAN_POImportContract        contract;
    str                             filename, journalProcessId;

    /// <summary>
    /// process file to upload WG purchase line item substitution journals
    /// </summary>
    /// <param name = "_contract">
    /// Contract class.
    /// </param>
    public void run(SAN_POImportContract _contract)
    {
        contract            = _contract;
        stream              = _contract.parmInputDataStream();
        filename            = _contract.parmFileName();
        this.processFile(_contract);
    }

    /// <summary>
    /// Processing excel file input
    /// </summary>
    /// <param name = "_contract">
    /// Contract class.
    /// </param>
    protected void processFile(SAN_POImportContract _contract)
    {
        #File
        TextStreamIo inFile = TextStreamIo::constructForRead(stream);
        inFile.inFieldDelimiter('|'); //TODO delimiter used
        inFile.inRecordDelimiter('\r\n'); 
        container recordContainer;
        str item;
        boolean     isFirstLine = true;
        recordContainer = inFile.read();
        if(_contract.parmHasColumnNames())
        {
            recordContainer = inFile.read();
        }
        
        ttsbegin;
        while (inFile.status() == IO_Status::OK)
        {
            if(isFirstLine)
            {
journalProcessId = '';
                //Header record creation logic
                isFirstLine= false;
            }

           //line record creation logic
            //Read the next line
            recordContainer = inFile.read();
        }
        ttscommit;

        info(strFmt("Journal - %2 Total record lines created %1",totalLinesProcessed, journalProcessId));
    }

}

Tuesday, April 15, 2025

OData Batch request API - D365FO

 --batch_MultipleInvoices

Content-Type: multipart/mixed;boundary=changeset_123


--changeset_123

Content-Type: application/http

Content-Transfer-Encoding: binary


POST https://<d365fourl>/data/PortalSalesDocuments/Microsoft.Dynamics.DataEntities.getSalesInvoice HTTP/1.1

Content-Type: application/json

Content-ID: 1


{

    "invoiceId": "SIAA00026",

    "dataAreaId": "usmf"

}

--changeset_123

Content-Type: application/http

Content-Transfer-Encoding: binary


POST https://<d365fourl>/data/PortalSalesDocuments/Microsoft.Dynamics.DataEntities.getSalesInvoice HTTP/1.1

Content-Type: application/json

Content-ID: 2


{

    "invoiceId": "SIAA00047",

    "dataAreaId": "usmf"

}

--changeset_123--

--batch_MultipleInvoices--

Friday, March 21, 2025

Disabling the flight in D365FO (CHD - Tier 1)

 INSERT INTO dbo.SYSFLIGHTING(FLIGHTNAME, ENABLED) 

VALUES ('<FlightObjectName>_KillSwitch', 1)

 or 

INSERT INTO dbo.SYSFLIGHTING(FLIGHTNAME, ENABLED) 

VALUES ('<FlightObjectName>', 0)

Monday, March 17, 2025

Get unicode character in excel

 Excel formula


=AND(ISTEXT(C2),SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(LOWER(C2),"a",),"b",),"c",),"d",),"e",),"f",),"g",),"h",),"i",),"j",),"k",),"l",),"m",),"n",),"o",),"p",),"q",),"r",),"s",),"t",),"u",),"v",),"w",),"x",),"y",),"z",),"-",),",",),"",),"0",),"1",),"2",),"3",),"4",) ,"5",),"6",),"7",),"8",),"9",),"&",),")",),"(",),".",),"/",),"–",),"#",),"*",),"'",),"+",),":",),";",),"$",),"%",)= "")

Wednesday, February 19, 2025

Validate product from Retail Assortment in D365FO X++

Public boolean SAN_validateProductAssortment(Str    _itemId)

{

    RetailAssortmentTable           retailAssortmentTable;

    RetailChannelAssortedProductView  retailChannelAssortedProductView;

    InventTable                       inventTable;

    while select  retailAssortmentTable

    {

            select firstonly retailChannelAssortedProductView

                 exists join inventTable

                where inventTable.ItemId == _itemId

                  && retailChannelAssortedProductView.AssortmentRecId == retailAssortmentTable.RecId

                  && retailChannelAssortedProductView.ProductID == inventTable.Product

                  && retailChannelAssortedProductView.InventLocationDataAreaId == inventTable.DataAreaId;

            if(retailChannelAssortedProductView)

            {

                return true;

            }

        }


    }

    throw Error("Invalid item to sell based on assortments");

Monday, January 6, 2025

Search hierarchy for a match (TableALLGroup) X++

  Table1 ppt;


 select firstonly ppt

 order ItemCode, ItemRelation, AccountCode, AccountRelation where

     (ppt.ItemCode == TableGroupAll::Table && ppt.ItemRelation == _itemId &&

         ppt.AccountCode == TableGroupAll::Table && ppt.AccountRelation == _accountNum) ||

     (ppt.ItemCode == TableGroupAll::Table && ppt.ItemRelation == _itemId &&

         ppt.AccountCode == TableGroupAll::GroupId && ppt.AccountRelation == _accountGroup) ||

     (ppt.ItemCode == TableGroupAll::Table && ppt.ItemRelation == _itemId &&

         ppt.AccountCode == TableGroupAll::All && ppt.AccountRelation == '') ||


     (ppt.ItemCode == TableGroupAll::GroupId && ppt.ItemRelation == _ItemGroup &&

         ppt.AccountCode == TableGroupAll::Table && ppt.AccountRelation == _accountNum) ||

     (ppt.ItemCode == TableGroupAll::GroupId && ppt.ItemRelation == _ItemGroup &&

         ppt.AccountCode == TableGroupAll::GroupId && ppt.AccountRelation == _accountGroup) ||

     (ppt.ItemCode == TableGroupAll::GroupId && ppt.ItemRelation == _ItemGroup &&

         ppt.AccountCode == TableGroupAll::All && ppt.AccountRelation == '') ||


     (ppt.ItemCode == TableGroupAll::All && ppt.ItemRelation == '' &&

         ppt.AccountCode == TableGroupAll::Table && ppt.AccountRelation == _accountNum) ||

     (ppt.ItemCode == TableGroupAll::All && ppt.ItemRelation == '' &&

         ppt.AccountCode == TableGroupAll::GroupId && ppt.AccountRelation == _accountGroup) ||

     (ppt.ItemCode == TableGroupAll::All && ppt.ItemRelation == '' &&

         ppt.AccountCode == TableGroupAll::All && ppt.AccountRelation == '');


 // Return table buffer if found, otherwise return 0 as the default

 if (ppt.RecId)

 {

     // return result

 }

Thursday, January 2, 2025

Create call center channel users X++ D365FO

 RetailChannelTable   channelTable;

MCRChannelUser      channelUser;


while select channelTable

    where channelTable.ChannelType ==  RetailChannelType::MCRCallCenter

{

    channelUser.clear();

    changecompany(channelTable.DefaultCustDataAreaId)

    {

        channelUser = MCRChannelUser::find();


        if(!channelUser)

        {

            channelUser.clear();

            channelUser.initValue();

            channelUser.Channel = channelTable.RecId;

            channelUser.User = curUserId();

            channelUser.insert();

        }

    }           

}

Upload file CSV/TXT using SysOperation in D365 X++

Upload CSV/TXT using SysOperation X++ Contract: /// <summary> /// Contract class upload through excel for PO inbound journals /// <...