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));
    }

}

No comments:

Post a Comment

Set visibility of menu item based on Feature or parameter options in D365FO

 [SubscribesTo(classstr(SysMenuNavigationObjectFactory), staticdelegatestr(SysMenuNavigationObjectFactory, checkAddSubMenuDelegate))] public...