the use of AI Builder's Form Processing capability to import vendor invoices into D365FO and I was very much intrigued.
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.