How to: Create data packages for the data import in Dynamics 365 Finance and Operations from Invoice PDFs

Recently I saw the video by Murray fife about the use of AI Builder's Form Processing capability to import vendor invoices into D365FO and I was very much intrigued. You can look at his Linkedin post here.

The entire post is about reading the Invoice pdf using AI builder in Power Automate, Creating pending vendor invoice by reading pdf, and attaching the pdf as an attachment in D365FO. One of the components of the entire solution was the creation of a data package from the incoming invoice pdf. 

In this blog, I will focus on this piece of the solution. Murray has already put the code on GitHub for this part but there was no information about how to use it. I had absolutely no experience in C# development and I had to struggle a lot to understand how to use the code he had shared. So I decided to blog about the entire process I did so anybody who doesn't know anything can still follow it and repeat it.

Also, This can be used for any other integration type where the data package needs to be created in an automated way from the raw files. 

Things we need are as follows:

1. Sample Invoice pdf

2. Sample Data Package from D365FO for import

3.Access to the Azure portal as we will be creating an Azure Function. 

Part 1:

We can work with any pdf file as a sample which we will attach to the Pending vendor invoice in D365FO.

Part 2:

To create the template data package for import, Create the new proceeding group in D365FO as follows:

Save it and Download the Package. It will look like below:

Extract the package, Open and update the CSV file like below:


Create a new folder named 'Resources' and add a subfolder named 'Vendor invoice document attachment'. Zip the package. It should look like below:

Confirm that there is no InvoiceTemplate folder again it and it should not look like below: 

Now our Template data package is ready. 

Part 3: 

Login to Azure portal and create an Azure Function using Microsoft Documentation

Once all the steps are followed and the function is deployed it should look like below:

Open Visual Studio on your machine and create a new Azure Function:















Once the Azure function is created copy the code as below: (This code is copied from Murray, but I have updated it to make it more robust and handle more scenarios) 

using System;

using System.IO;

using System.Threading.Tasks;

using Microsoft.AspNetCore.Mvc;

using Microsoft.Azure.WebJobs;

using Microsoft.Azure.WebJobs.Extensions.Http;

using Microsoft.AspNetCore.Http;

using Microsoft.Extensions.Logging;

using System.IO.Compression;

using System.Text;


namespace APInvoiceAutomation

{

    public static class ProcessPackage

    {

        [FunctionName("ProcessPackage")]

        public static async Task<IActionResult> Run(

            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,

            ILogger log,

            ExecutionContext context)

        {

            string invoiceNo = req.Query["invoice"];

            string headerRef = req.Query["headerref"];


            string source = context.FunctionDirectory + @"\template\InvoiceTemplate.zip";

            string target = context.FunctionDirectory + $@"\template\{Guid.NewGuid().ToString()}.zip";


            File.Copy(source, target);

            using (FileStream zipToOpen = new FileStream(target, FileMode.Open))

            {

                using (ZipArchive archive = new ZipArchive(zipToOpen, ZipArchiveMode.Update))

                {

                    //write attachment to the package

                    string filename = @"Resources\Vendor invoice document attachment\" + headerRef + ".pdf";

                    ZipArchiveEntry invoiceEntry = archive.CreateEntry(filename);

                    req.Body.CopyTo(invoiceEntry.Open());


                    //update the CSV content

                    ZipArchiveEntry csvFile = archive.GetEntry(@"Vendor invoice document attachment.csv");

                    StringBuilder csvContent;

                    using (StreamReader csvStream = new StreamReader(csvFile.Open()))

                    {

                        csvContent = new StringBuilder(csvStream.ReadToEnd());

                        csvContent.Replace("{DocId}", Guid.NewGuid().ToString());

                        csvContent.Replace("{invoice}", invoiceNo);

                        csvContent.Replace("{headerRef}", headerRef);

                    }

                    csvFile.Delete();


                    ZipArchiveEntry newCsvFile = archive.CreateEntry(@"Vendor invoice document attachment.csv");

                    MemoryStream msCsv = new MemoryStream(Encoding.ASCII.GetBytes(csvContent.ToString()));

                    msCsv.CopyTo(newCsvFile.Open());

                }

            }


            var output = File.ReadAllBytes(target);

            File.Delete(target);

            FileContentResult result = new FileContentResult(output, @"application/zip")

            {

                FileDownloadName = "package.zip"

            };

            return (ActionResult)result;

        }

    }

}

The next step is to publish this code to the Azure function which we created on the Azure portal.

Select Appropriate Subscription and Azure function which was created. Click OK.

Once Publish is completed, go back to Azure Function and open 'App Service Editor'

Under ProcessPackage Create a new folder 'template' and add the zip file created in step 2.

Now we are done.

How do we test it? Simply call this function with HTTP request. You can get the Base URL from 'Get function URL'

and add parameters as invoice=<invoiceNumber>&headerref=<header reference number>

add pdf content as the body to this request.

URL will look something like https://rdapinvoiceautomation.azurewebsites.net/api/ProcessPackage?invoice=1&headerref=2

When you paste it in the browser, packge.zip will be downloaded with the updated CSV and pdf added to the resources folder:

Now you can use this zip to import the Invoice into D365FO. We used this in Power automate to process things but the same can be used by any other tool to generate the data package.

In my example, I have taken of a Vendor invoice, it can be used for any other attachment in D365.

P.S. I have done this according to my research, if there is a better way to do it, Feel free to let me know 




Comments

  1. Hi Vinit!, this is a great post. I'm also struggling with this one. I have a couple of questions that I cannot figure out myself.
    I'm trying to recreate the folder structure of "template\..." but I get a "500 Error" or "access is denied" when creating a folder. I researched a bit and found that it could be the "run from package" parameter that is enabled. Is this the way that you did it?.

    The other question is, this code could be used to read from a blob storage folder instead of the azure function folder instead?. Could you give me some tips to achieve this?.

    Thanks in advance!

    ReplyDelete
    Replies
    1. Look at this repo https://github.com/ccampora/ZipFunction

      Delete
  2. This comment has been removed by a blog administrator.

    ReplyDelete
  3. us import data Wow, cool post. I'd like to write like this too - taking time and real hard work to make a great article... but I put things off too much and never seem to get started. Thanks though.

    ReplyDelete

Post a Comment

Popular posts from this blog

How to : get the Deep URL for a particular form with filter in Dynamics 365 Finance and Operations

How to: Solve issue when DVT script for service model MRApplicationService on machine XXX failed

How to: Display individual financial dimension value in grid control