Slack Approval Notification

Send a Slack direct message when an invoice enters an approval workflow, giving reviewers a one-click link to the document in Rossum.

Introduction

This extension allows you to send a notification to a Slack DM when a invoice entered the workflow for approval. The notification in Slack contains a button to go directly to the approval task in Rossum.

Prerequisites

  1. Access to the Slack Workspace

  2. Create an Slack App on https://api.slack.com/apps/ (ask your workspace administrator for access).

  3. Get the Bot User OAuth Token for API access to your workspace

Extension settings

  1. Create an extension

  2. Triggered event: Document status: changed

  3. Queues: select your queues

  4. Additional metadata: none

  5. Runtime: python3.12

  6. Select
    token owner

Configuration JSON

Configuration contains the email address of the slack user to sent the DM to. This is for demo purposes only, so you receive the notifications.

settings:

{
 "slack_user_email": "example@rossum.ai"
}

secrets:

{
 "slack_token": "copy_slack_token_here"
}

Python code

Copy following code in

import requests
import json
from typing import List, Optional


def rossum_hook_request_handler(payload: dict) -> dict:
 
 messages = []
 operations = []

 #general settings 
 slackuser = payload["settings"]["slack_user_email"]
 slacktoken = payload["secrets"]["slack_token"]
 base_url = payload["base_url"]
 rossum_token = payload["rossum_authorization_token"]
 
 #annotation data 
 status = payload["annotation"]["status"]
 annotation_id = payload["annotation"]["id"]
 annotation_content_url = payload["annotation"]["content"]

 # get annoation data
 annotation_content = get_annotation_content(annotation_content_url, rossum_token)
 
 # check if status is to_review, only then sent slack notification
 if status == "in_workflow":
 user = find_slackuser_by_email(slackuser, slacktoken)
 send_slack_message(user, slacktoken, annotation_id, base_url, annotation_content["content"])
 
 
 messages = [create_message("info", "slack notification sent")]
 return {"messages": messages, "operations": operations}


def find_slackuser_by_email(slackuser, slacktoken):
 
 url = "https://slack.com/api/users.lookupByEmail?email=" + slackuser
 header = {"Authorization": "Bearer " + slacktoken}
 response = requests.get(url, headers=header)

 return response.json()["user"]
 
 

def send_slack_message(user, slacktoken, annotation_id, base_url, annotation_content):
 
 userid = user["id"]
 realname = user["real_name"]
 
 # Extract invoice information
 invoicenumber = find_by_schema_id(annotation_content, "document_id")["content"]["value"]
 vendorname = find_by_schema_id(annotation_content, "sender_name")["content"]["value"]
 duedate = find_by_schema_id(annotation_content, "date_due")["content"]["value"]
 amount_total = find_by_schema_id(annotation_content, "amount_total")["content"]["value"]
 currency = find_by_schema_id(annotation_content, "currency")["content"]["value"].upper()

 # Get document URL for direct access
 document_url = base_url + "/requests/" + str(annotation_id)
 
 # Prepare Slack message
 blocks = [
 {
 "type": "header",
 "text": {
 "type": "plain_text",
 "text": " Invoice Approval Required"
 }
 },
 {
 "type": "section",
 "fields": [
 {
 "type": "mrkdwn",
 "text": f"*Assigned to:*\n{realname}"
 },
 {
 "type": "mrkdwn",
 "text": "*Status:*\nPending Approval"
 }
 ]
 },
 {
 "type": "divider"
 },
 {
 "type": "section",
 "text": {
 "type": "mrkdwn",
 "text": "*Invoice Details:*"
 }
 },
 {
 "type": "section",
 "fields": [
 {
 "type": "mrkdwn",
 "text": f"*Vendor:*\n{vendorname}"
 },
 {
 "type": "mrkdwn",
 "text": f"*Invoice Number:*\n{invoicenumber}"
 },
 {
 "type": "mrkdwn",
 "text": f"*Amount:*\n{amount_total} {currency}"
 },
 {
 "type": "mrkdwn",
 "text": f"*Due Date:*\n{duedate}"
 }
 ]
 },
 {
 "type": "actions",
 "elements": [
 {
 "type": "button",
 "text": {
 "type": "plain_text",
 "text": "Review Invoice"
 },
 "style": "primary",
 "url": document_url
 }
 ]
 }
 ]
 

 url = "https://slack.com/api/chat.postMessage"
 header = {"Authorization": "Bearer " + slacktoken}
 formdata = {"channel": userid, "text":"An invoice requires your approval", "blocks": json.dumps(blocks)}
 
 response = requests.post(url, headers=header, data=formdata)
 return


 
def create_message(message_type, message_content, datapoint_id=None):
 return {
 "content": message_content,
 "type": message_type,
 "id": datapoint_id,
 }


def get_annotation_content(annotationurl: str, rossum_token: str) -> dict:
 url = annotationurl
 header = {"Authorization": "Bearer " + rossum_token}
 response = requests.get(url, headers=header)

 return response.json()
 
 

def find_by_schema_id(content, schema_id: str) -> Optional[dict]:
 """
 Return the first datapoint matching a schema id or None if no such element exists.
 :param content: annotation content tree (see https://api.elis.rossum.ai/docs/#annotation-data)
 :param schema_id: field's ID as defined in the extraction schema(see https://api.elis.rossum.ai/docs/#document-schema)
 :return: the datapoint matching the schema ID
 """
 for node in content:
 if node["schema_id"] == schema_id:
 return node
 elif "children" in node:
 # if the element with specified schema_id was found
 if found := find_by_schema_id(node["children"], schema_id):
 return found
 return None