Multi-attachment upload from ZIP archive

Batch job to attach multiple files to D365 F&SCM records (Fixed Assets in the example) from ZIP archive.
Working with ZIP archives in D365 F&SCM.

ZIP Archive should contain a main.csv file with 3 columns (Company, RecordKeyValue, FileName). Example:
"DAT","FA000014VK","file2.txt"
"DAT","FA000015VK","file3.txt"
"DAT","FA000016VK","file4.txt"

Batch class:


using System.IO.Compression;
/// <summary>
/// Multi-attachment upload from ZIP archive
/// </summary>
class VKAttachmentUpload extends RunBaseBatch implements BatchRetryable
{
    DialogRunbase   dialog;
 
    Filename                filename;
    protected str           availableTypes    = '.zip';
    protected const str     OkButtonName      = 'OkButton';
    protected const str     FileUploadName    = 'FileUpload';
    protected const str     FileCSVMain       = 'main.csv';
    protected Map           entryMapping      = new Map(Types::String, Types::Class);
    ZipArchive              zipArchive;

    str                 textFile;
 
    Set                 set = new Set(Types::Record);
    SetEnumerator       setEnumerator;
 
    int                    fileLineNumber;
    container           currentLine;
    container            storageResult;
 
    #define.CurrentVersion(1)
    #localmacro.CurrentList
        filename,
        storageResult
    #endmacro
 
     public boolean canGoBatch()
    {
        return true;
    }

    protected boolean canGoBatchJournal()
    {
        return true;
    }

    public Object dialog()
    {
        DialogGroup      dialogGroup;
        FormBuildControl formBuildControl;
        FileUploadBuild  dialogFileUpload;
 
        dialog = new DialogRunbase(VKAttachmentUpload::description(), this);
        dialogGroup = dialog.addGroup(VKAttachmentUpload::description());
        formBuildControl = dialog.formBuildDesign().control(dialogGroup.name());
 
        dialogFileUpload = formBuildControl.addControlEx(classstr(FileUpload), FileUploadName);
        dialogFileUpload.style(FileUploadStyle::Standard);
        dialogFileUpload.baseFileUploadStrategyClassName(classstr(FileUploadTemporaryStorageStrategy));
        dialogFileUpload.fileTypesAccepted(availableTypes);
        dialogFileUpload.fileNameLabel("@SYS308842");
 
        return dialog;
    }

    public void dialogPostRun(DialogRunbase _dialog)
    {
        FileUpload fileUpload = this.getFormControl(_dialog, FileUploadName);
        fileUpload.notifyUploadCompleted += eventhandler(this.uploadCompleted);
        this.setDialogOkButtonEnabled(_dialog, false);
    }

    /// <summary>
    /// To handle file upload event and enable OK dialog button
    /// </summary>
    protected void uploadCompleted()
    {
        FileUpload fileUpload = this.getFormControl(dialog, FileUploadName);
        FileUploadTemporaryStorageResult uploadResult = fileUpload.getFileUploadResult();
        storageResult = uploadResult.pack();
         
        if (uploadResult.getUploadStatus())
        {
            fileUpload.notifyUploadCompleted -= eventhandler(this.UploadCompleted);
            filename = fileUpload.fileName();
            this.setDialogOkButtonEnabled(dialog, true);
        }
        else
        {
            warning(uploadResult.getLogMessage());
        }
    }

    protected void setDialogOkButtonEnabled(DialogRunbase _dialog, boolean _isEnabled)
    {
        FormControl okButtonControl = this.getFormControl(_dialog, OkButtonName);
 
        if (okButtonControl)
        {
            okButtonControl.enabled(_isEnabled);
        }
    }

    protected FormControl getFormControl(DialogRunbase _dialog, str _controlName)
    {
        return _dialog.formRun().control(_dialog.formRun().controlId( _controlName));
    }

    public void run()
    {
        #File
        System.IO.MemoryStream              memoryStream;
        FileUpload                          fileUploadControl;
        FileUploadTemporaryStorageResult    fileUploadResult = new FileUploadTemporaryStorageResult();
 
        if(this.isInBatch())
        {
            fileUploadResult.unpack(storageResult);
        }
        else
        {
            fileUploadControl = this.getFormControl(dialog, FileUploadName);
            fileUploadResult = fileUploadControl.getFileUploadResult();
        }
          
        if (fileUploadResult != null && fileUploadResult.getUploadStatus())
        {
            textFile = fileUploadResult.getDownloadUrl();
        }

        memoryStream = File::UseFileFromURL(textFile);

        zipArchive = new ZipArchive(memoryStream);
 
        this.readCSVFile();
        this.processAttachments();
    }

    /// <summary>
    /// Fetches attachment files from ZIP archive and attaches them to relevant records.
    /// </summary>
    protected void processAttachments()
    {
        ZipArchiveEntry         entry;
        System.IO.MemoryStream  stream;
        VKAttachmentContract    attachmentContract;

        var enumerator = zipArchive.Entries.GetEnumerator();

        while (enumerator.MoveNext())
        {
            entry = enumerator.Current;
            if (entry.Name != FileCSVMain)
            {
                if (entryMapping.exists(entry.Name))
                {
                    attachmentContract = entryMapping.lookup(entry.Name);
                    Common record = this.findEntityRecord(attachmentContract);

                    if (record)
                    {
                        stream = new System.IO.MemoryStream();
                        var unzippedStream = entry.Open();
                        unzippedStream.CopyTo(stream);

                        // required as otherwise file with zero content will be attached
                        if (stream.CanSeek)
                        {
                            stream.Position = 0;
                        }

                        //System.IO.StreamReader sreader    = new System.IO.StreamReader(stream, System.Text.Encoding::UTF8, true);
                        //info(strFmt('File content: %1', sreader.ReadToEnd()));

                        DocumentManagement::attachFileForRecord(record, 'File', stream, entry.Name, entry.Name);
                    }
                    else
                    {
                        warning(strFmt("Was not able to find entry with key %1, skipping.", attachmentContract.parmKey()));
                    }
                }
                else
                {
                    warning(strFmt("File name %1 not found, skipping.", entry.Name));
                }
            }
        }
    }

    /// <summary>
    /// Finds entity record by the key to attach the file.
    /// </summary>
    /// <param name = "_attachmentContract">VKAttachmentContract</param>
    /// <returns>Entity record</returns>
    protected Common findEntityRecord(VKAttachmentContract _attachmentContract)
    {
        AssetTable assetTable;
        changecompany(_attachmentContract.parmCompany())
        {
            assetTable  = null;
            assetTable  = AssetTable::find(_attachmentContract.parmKey());
        }
        return assetTable;
    }

    /// <summary>
    /// Reads CSV file from ZIP archive
    /// </summary>
    protected void readCSVFile()
    {
        #File
        #OCCRetryCount
        #define.delimiterField(',')

        ZipArchiveEntry         entry;
        System.IO.MemoryStream  csvStream    = new System.IO.MemoryStream();

        var enumerator = zipArchive.Entries.GetEnumerator();

        while (enumerator.MoveNext())
        {
            entry = enumerator.Current;
            if (entry.Name == FileCSVMain)
            {
                break;
            }
        }
        if (!entry || entry.Name != FileCSVMain)
        {
            throw error(strFmt("Was not able to find %1 file.", FileCSVMain));
        }

        var unzippedStream = entry.Open();
        unzippedStream.CopyTo(csvStream);

        CommaTextStreamIo commaTextStreamIo = CommaTextStreamIo::constructForRead(csvStream);
        commaTextStreamIo.inFieldDelimiter(#delimiterField);
        commaTextStreamIo.inRecordDelimiter(#delimiterCRLF);

        if (commaTextStreamIo.status() != IO_Status::Ok)
        {
            throw error(strfmt("Not possible to open the file. Error: %1", enum2str(commaTextStreamIo.status())));
        }
 
        str attachFileName;
        str recordKey;
        str company;

        VKAttachmentContract   attachmentContract = new VKAttachmentContract();

        while (!commaTextStreamIo.status())
        {
            currentLine = commaTextStreamIo.read();
            fileLineNumber++;
            if (conlen(currentLine) < 3)
            {
                warning(strFmt("Line %1, incorrect column count. Skipping.", fileLineNumber));
                continue;
            }
 
            company         = conPeek(currentLine, 1);
            recordKey       = conPeek(currentLine, 2);
            attachFileName  = conPeek(currentLine, 3);
            
            attachmentContract = new VKAttachmentContract();

            attachmentContract.parmFileName(attachFileName);
            attachmentContract.parmKey(recordKey);
            attachmentContract.parmCompany(company);

            if (!entryMapping.exists(attachFileName))
            {
                entryMapping.insert(attachFileName, attachmentContract);
            } 
        }
    }

    public boolean runsImpersonated()
    {
        return true;
    }

    public container pack()
    {
        return [#CurrentVersion, #CurrentList];
    }

    public boolean unpack(container _packedClass)
    {
        Integer version = conPeek(_packedClass, 1);
 
        switch (version)
        {
            case #CurrentVersion:
                [version, filename, storageResult] = _packedClass;
                break;
            default:
                return false;
        }
        return true;
    }

    public boolean validate(Object _calledFrom = null)
    {
        boolean ret = true;
 
        if (!filename)
        {
            ret = checkFailed("@SYS18624");
        }
 
        return ret;
    }

    public static VKAttachmentUpload construct()
    {
        return new VKAttachmentUpload();
    }

    /// <summary>
    /// Description of the job
    /// </summary>
    /// <returns>Description of the job</returns>
    public static ClassDescription description()
    {
        return "Multi-attachment upload.";
    }

    public static void main(Args args)
    {
        VKAttachmentUpload  attachmentUpload;
 
        attachmentUpload = VKAttachmentUpload::construct();
 
        if (attachmentUpload.prompt())
        {
            attachmentUpload.runOperation();
        }
    }

    protected boolean canRunInNewSession()
    {
        return false;
    }

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

Attachment contract:


/// <summary>
/// Contract with information about attachment.
/// </summary>
class VKAttachmentContract
{
    Filename    fileName;
    DataAreaId  company;
    str         key;

    public Filename parmFileName(Filename _fileName = fileName)
    {
        fileName = _fileName;

        return fileName;
    }

    public DataAreaId parmCompany(DataAreaId _company = company)
    {
        company = _company;

        return company;
    }

    public str parmKey(str _key = key)
    {
        key = _key;

        return key;
    }
}




 

Search

About

DaxOnline.org is free platform that allows you to quickly store and reuse snippets, notes, articles related to Dynamics AX.

Authors are allowed to set their own AdSense units and "buy me a coffee" link.
Join us.

Blog Tags