Tech Blog

Using the Invoice Attachments API

Many institutions have integrated Alma with their campus ERP system. Using the file-based or API-based integration, it’s possible to send invoices which are ready for payment to the ERP system to be processed, and to receive payment confirmation back when the invoice has been paid or rejected.

In some cases, the ERP system requires that a digital copy of the invoice be filed before the invoice can be paid. While it’s possible to add attachments to invoices in Alma, the attachments have not been available via API. The June 2020 Alma release includes a new invoice attachment API which provides access to the attachments. This API enables end-to-end automation for financial integrations which require files to be transferred from Alma to the ERP system.

In this post, we will write a script which does the following:

  • Loop over the data from the Invoice Export action of the Financial Systems integration profile
  • For each invoice, call the invoice attachments API
  • For each attachment, save the file to the local filesystem

From this point, the attachments are available and can be imported into the ERP along with the invoices.

First, be sure you’ve configured a Financial Systems integration profile and the invoice export action. Once the integration profile is configured, the job will run as scheduled and export all invoices marked as “Ready to be Paid,” as described in the invoicing workflow. The exported file adheres to this schema, and includes the invoice ID and the number of attachments (from the July 2020 release).

Our script has a number of functions defined. In the main function, we loop through the specified directory and process each XML file in the directory. For each file, we parse the XML and loop through the invoices. For each invoice, we call the process_invoice function.

def main():
    directory = os.fsencode(sys.argv[1])
    for file in os.listdir(directory):
        filename = os.fsdecode(file)
        if filename.endswith(".xml"):
            xml = ET.parse(os.path.join(directory, file)).getroot()
            for invoice in xml.findall('./exl:invoice_list/exl:invoice', NS):
                process_invoice(invoice)

The process_invoice function extracts the invoice ID and the number of attachments and calls the get_attachments function to download the attachments. In get_attachments, we use the new invoice attachments API to retrieve the attachment list, and for each attachment we call the get attachment API with the expand=content parameter. The expand parameter tells Alma to return the contents of the file as a base64-encoded string. We then convert the string to binary and save the result as a file on our filesystem.

def process_invoice( invoice ):
    id = invoice.find('exl:unique_identifier', NS).text
    number_of_attachments = invoice.find('exl:number_of_attachments', NS)
    if (number_of_attachments is None or int(number_of_attachments.text) > 0):
        get_attachments(id)

def get_attachments(invoice_id):
    attachments = get(f"/almaws/v1/acq/invoices/{invoice_id}/attachments")
    os.makedirs(invoice_id, exist_ok = True) 
    for attachment in attachments.findall('attachment'):
        print(attachment.attrib['link'])
        file_name, content = get_attachment(attachment.attrib['link'])
        if not re.match(r"^ERP\..*\.xml$", file_name): # exclude export file
            file = open(os.path.join(invoice_id, file_name) , 'wb')
            file.write(base64.b64decode(content))
            file.close()

def get_attachment(link): 
    attachment = get(link + "?expand=content")
    return attachment.find('file_name').text, attachment.find('content').text

Now that we have the files saved locally, we can import them to our ERP system along with the invoices for processing. Using a combination of the financial systems integration and the invoice attachments API, we are able to completely automate the process of sending invoices for payment to our ERP system.

The full script is available in this gist.

Leave a Reply