Design and implement your transaction flow

Prerequisites

To run this tutorial, you must have a running instance with (i) an assigned application profile and (ii) the P6 Demo App installed.
If you are not familiar with the P6 Demo App, please read this section.

Design the transaction flow

There are a few questions to be answered in order to design a transaction flow, such as:

  • Where will the transaction originate from (it could be retrieved from the blockchain, or received from an internal system like an ERP, or from a trading partner via a protocol like AS2 or SFTP…)
  • What will be the source format of the transaction / business document and what shall be the target format
  • What transaction attributes / details should be captured from the business document (for example: total amount, number of line items,…)
  • Which statuses will the transaction take and what will dictate a status change
  • Will specific statuses trigger a workflow and if so, what actions would be presented to the assignee(s)

Let us take the Demo App as an example:

Step & Description Source & Target system(s) Business document P6 transaction(s) Workflow
Step 1 - A Request for Quotation (RFQ) is created every 10 minutes and stored in a smart contract P6 -> Smart contract UBL RFQ created and stored in smart contract No transaction created on P6 -
Step 2 - The UBL RFQ is retrieved from the smart contract and an RFQ Transaction is created on P6 Smart contract -> P6 UBL RFQ stored as is in the instance file system RFQ transaction created, with technical status set to “Received” -
Step 3 - The received RFQ triggers a P6 workflow, which offers two options to the supplier user: either (a) provide a Quote or (b) decline the RFQ P6 -> P6 - - Workflow task created
Step 4a - The Supplier user decides to provide a Quote in reply to the received RFQ P6 -> Smart contract UBL Quote created and stored both on the instance file system and the smart contract RFQ tech status set to “Handled”, functional one to “Quote provided”
Quote transaction created, with tech status set to “Sent”
-
Step 4b - The Supplier user decides to decline the RFQ and can enter a reason P6 -> Smart contract No business doc created but the RFQ status is set to “Declined” in the smart contract RFQ tech status set to “Handled”, functional one to “Declined” -
Step 5 - The Buyer receives the Quote via the blockchain and generates a Purchase Order matching the Quote Smart contract -> P6 UBL Order created and stored on the file system - -
Step 6 - The received PO triggers a P6 workflow, which offers two options to the supplier user: either (a) accept the Order or (b) reject it P6 -> P6 - PO transaction created and tech status set to “Received” Workflow task created and email notification sent to the assignee(s)
Step 7a - The Supplier user decides to accept the PO and can enter a Sales Order Number P6 -> Smart contract - PO transaction tech status set to “Handled” and functional status set to “Accepted” -
Step 7b - The Supplier user decides to reject the PO and can enter a rejection reason P6 -> P6 - PO transaction tech status set to “Handled” and functional status set to “Rejected” -

In this tutorial, we will extend the scenario of the Demo App and trigger a workflow that will offer the Supplier user the ability to notify the buyer of the shipment date for a specific purchase order.
If the user opts for sending a confirmation, an email with PO and shipment details will be posted and the transaction information will be updated accordingly. Else, no email will be sent but the transaction information will be nonetheless updated to reflect the user’s choice and the shipment date.

So, here are the few steps we will want to add:

Step & Description Source & Target system(s) Business document P6 transaction(s) Workflow
Step 8 - Each “Accepted” PO triggers a workflow, which offers two options to the supplier user: either (a) Confirm shipment to the buyer - via email - or (b) Skip shipment confirmation P6 -> P6 - - Workflow task created
Step 9a - The Supplier user decides to confirm shipment (and enters ship date and email details) P6 -> Email - PO transaction functional status set to “Shipment confirmation sent”, email sent to buyer (with copy to the user who handled the workflow task) -
Step 9b - The Supplier user decides to skip shipment confirmation (but enters the ship date) P6 -> P6 - PO transaction functional status set to “Shipment confirmation not sent” -

Implement the transaction flow

The implementation of the designed flow will usually involve Scripts, Routes, Workflow Steps - and potentially other types of service items.
If you need a refresh on how the P6 Demo App is implemented, you may read again this section, which details the various service items leveraged at each step of the transaction flow.

Now, let us implement steps 8 and 9a/9b…

Add a Route Deployment Script

We shall start by creating a route - an endpoint that the Demo App will call.

So click on the ‘Routes’ menu entry, duplicate the route deployment script called ‘RoutingRulesForPurchaseOrders’ and keep the same name (so just hit the green ‘Duplicate’ icon and validate with the ‘Enter’ key).

Note

Service items are identified by their application key and name, so two service items can have the same name as long as they don’t have the same app key

Open the route deployment script you created via duplication and make the required changes to the script so it looks as follows:

p6.camel.getCtx().addRoutes(new RouteBuilder() {

    void configure() {

        from('direct:p6router.ExtendPurchaseOrder')
            .choice()
                .when(xpath("/TransactionInfo/FunctionalStatusCode='Accepted'"))
                    .setHeader("platform6.request.action").constant("invoke")
                    .setHeader("status").constant("Shipment to be confirmed")
                    .setHeader("step").constant("HandleShipmentConfirmation")
                    .setHeader("appkey").constant("")
                    .setHeader("flowname").constant("UUID")
                    .setHeader("script").constant("CustomWFStepBuilder")
                    .to("p6route://platform6.workflowsteps")
            .end()
            .routeId('Extend p6_demo Routing rules for Purchase Orders')
            .description("Extend p6_demo Routing rules for Purchase Orders")
    }
})
This script will create a route (with routeId ‘Extend p6_demo Routing rules for Purchase Orders’).
When called and if the transaction FunctionalStatusCode is ‘Accepted’, this route will trigger the ‘HandleShipmentConfirmation’ workflow step.

Alternatively, instead of duplicating the ‘RoutingRulesForPurchaseOrders’ script from p6_demo, you can can also create it from scratch. To do so, hit the ‘+ Add’ button and fill in the form as follows:

Route deployment script for approved POs

Add a Workflow Step

We will now create the workflow step that will offer two option to the workflow assignees, either to confirm or not confirm shipment. So, click on the ‘Workflow Steps’ menu entry, then hit the ‘+Add button’, enter ‘HandleShipmentConfirmation’ as the name and the following XML description:

<WorkflowStep enabled="true">

    <Description>
        <EN>Shipment - Confirm or not confirm</EN>
        <FR>Expédition - Confirmer ou pas</FR>
    </Description>

    <AllowTransactionEdit>ASSIGNEE</AllowTransactionEdit>
    <TransactionDataModel>p6_demo.TransactionInfo</TransactionDataModel>
    <ViewNames>
        <Item>p6_demo.Transactions</Item>
        <WorkItem>p6_demo.Workflow Tasks</WorkItem>
    </ViewNames>

    <AllowRecall>false</AllowRecall>
    <AllowApproverDelegation>false</AllowApproverDelegation>

<!-- This section determines if workflow assignees will receive notification emails and, if so, which email template to use -->    
    <SendEmails>true</SendEmails>
    <EmailTemplate modelScript="p6_demo.WFHandlePO-BuildEmail">file://${B2BOX_DATA}/resources/templates/p6demo_POReview.ftl</EmailTemplate>

<!-- This is the Time To Live for the workflow task, expressed in number of minutes. A value of 0 means never expire. -->        
    <Ttl id="expire">120</Ttl>

    <WorkItem script="p6_demo.WFWorkflowTaskEnhancer"/>

<!-- This is where the workflow assignees are defined, in particular to where they are in the org structure and which permission they shall have -->     
    <Assignee name="DemoApp" path="/${INSTANCE_ID}" type="UNIT" scope="*=*">
        <Label>
            <EN>Supplier</EN>
            <FR>Fournisseur</FR>
        </Label>
    </Assignee>

    <StatusLabels>
        <Label name="Shipment to be confirmed" >
            <EN>Shipment to be confirmed</EN>
            <FR>Expédition à confirmer</FR>
        </Label>
    </StatusLabels>

<!-- This is the section where the various actions offered are defined. -->     
    <Actions>
        <Action id="confirm" status="Shipment confirmed" type="ACTION" stop="true" script="WFHandleShipmentConf-HandleActionConfirmShipment">

            <Style>icon:fa-check,btn:btn-success</Style>

            <Label>
                <EN>Confirm shipment</EN>
                <FR>Confirmer expédition</FR>
            </Label>

           <Parameter>
                <Name>recipient</Name>
                <Label>
                    <EN>To</EN>
                    <FR>To</FR>
                </Label>
                <Mandatory>true</Mandatory>
                <InputType>TEXT</InputType>
                <DefaultValue>purchasing-dept@buyercorp.com</DefaultValue>
            </Parameter>

           <Parameter>
                <Name>carboncopy</Name>
                <Label>
                    <EN>Cc</EN>
                    <FR>Cc</FR>
                </Label>
                <Mandatory>true</Mandatory>
                <InputType>TEXT</InputType>
                <DefaultValue></DefaultValue>
            </Parameter>

            <Parameter>
                <Name>shipmentdate</Name>
                <Label>
                    <EN>Shipment date</EN>
                    <FR>Date d'expédition</FR>
                </Label>
                <Mandatory>true</Mandatory>
                <InputType>TEXT</InputType>
                <DefaultValue></DefaultValue>
            </Parameter>

            <Parameter>
                <Name>files</Name>
                <Label>
                    <EN>Attachments</EN>
                    <FR>Pièces jointes</FR>
                </Label>
                <Mandatory>false</Mandatory>
                <InputType>FILES</InputType>
            </Parameter>
        </Action>

        <Action id="notconfirm" status="Shipment confirmation not sent" type="ACTION" stop="true" script="WFHandleShipmentConf-HandleActionSkipConfirmation">

            <Style>icon:fa-times,btn:btn-warning,color:warning</Style> 

            <Label>
                <EN>Skip shipment confirmation</EN>
                <FR>Ne pas confirmer l'expédition</FR>
            </Label>

            <Parameter>
                <Name>shipmentdate</Name>
                <Label>
                    <EN>Shipment date</EN>
                    <FR>Date d'expédition</FR>
                </Label>
                <Mandatory>true</Mandatory>
                <InputType>TEXT</InputType>
                <DefaultValue></DefaultValue>
            </Parameter>

        </Action>

        <Action id="expire" status="EXPIRED" type="EXPIRE" display="false">
            <Expiry error="false"/>
        </Action>

    </Actions>

</WorkflowStep>

Save this workflow step, then stop and restart the service. The Workflow Steps service compiles all step definitions and caches them for runtime efficiency. To force your new workflow step to be compiled, a service restart is needed (using the stop and start buttons, top right of the page).

Add Scripts

The ‘HandleShipmentConfirmation’ workflow step we just created references two scripts, each linked to one of the actions offered to the workflow assignee.

Create a script named ‘WFHandleShipmentConf-HandleActionConfirmShipment’, that will be called after the workflow assignee has chosen the ‘Confirm shipment’ action, completed and submitted the workflow task.
Here are the groovy script details::

import groovy.json.*
import java.text.SimpleDateFormat
import org.apache.commons.io.FileUtils


def dataType = p6.pipeline.get 'platform6.request.dataType'
def itemIds = p6.pipeline.get 'platform6.request.ids'

def recipient = p6.pipeline.get 'recipient'
def shipmentdate = p6.pipeline.get 'shipmentdate'
def cc = p6.pipeline.get 'carboncopy'

def itemPk = p6.transaction.buildPK(dataType, itemIds)

def transactionInfoContent = p6.transaction.exists(itemPk)

XmlSlurper slurper = new XmlSlurper()
def transactionInfo = slurper.parseText(transactionInfoContent)

def currentDate = new Date()
SimpleDateFormat transaction_sdf = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss")

def formatedDate = transaction_sdf.format(currentDate)

transactionInfo.FunctionalStatusCode = 'Shipment confirmation sent'
transactionInfo.FunctionalStatusMessage = 'Shipment confirmation emailed to '+recipient+' with shipment date: '+shipmentdate
transactionInfo.FunctionalStatusDate = formatedDate

def transactionId = transactionInfo.Id.text()

def ipk = p6.transaction.buildPK('TransactionInfo', transactionId)

p6.transaction.save(groovy.xml.XmlUtil.serialize(transactionInfo), 'p6_demo.TransactionInfo', ipk)


def params = [ 'subject': 'Shipment confirmation', 'cc' : cc ]

def attachments = null

def poFile = p6.uri.fileFromUrl(transactionInfo.TargetDocumentURI.text())

def purchaseOrder = FileUtils.readFileToString(poFile, 'UTF-8')

def html_body = p6.xslt.process("PO XML to ShipmentConfirmation HTML", p6.resource.get('PO_to_ShipmentConfirmation'), purchaseOrder)

html_body = html_body.replace('REPLACESHIPMENTDATE', shipmentdate)

p6.email.sendHtmlEmail( 'demo@platform6.io', recipient, html_body, params, attachments  )

Create a script named ‘WFHandleShipmentConf-HandleActionSkipConfirmation’ that will be called after the workflow assignee has chosen the ‘Skip shipment confirmation’ action, completed and submitted the workflow task.
Here are the groovy script details:

import groovy.json.*
import java.text.SimpleDateFormat

def dataType = p6.pipeline.get 'platform6.request.dataType'
def itemIds = p6.pipeline.get 'platform6.request.ids'

def shipmentdate = p6.pipeline.get 'shipmentdate'


def itemPk = p6.transaction.buildPK(dataType, itemIds)

def transactionInfoContent = p6.transaction.exists(itemPk)

XmlSlurper slurper = new XmlSlurper()
def transactionInfo = slurper.parseText(transactionInfoContent)

def currentDate = new Date()
SimpleDateFormat transaction_sdf = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss")

def formatedDate = transaction_sdf.format(currentDate)

transactionInfo.FunctionalStatusCode = 'Shipment confirmation not sent'
transactionInfo.FunctionalStatusMessage = 'Shipment date: '+shipmentdate
transactionInfo.FunctionalStatusDate = formatedDate

def transactionId = transactionInfo.Id.text()

def ipk = p6.transaction.buildPK('TransactionInfo', transactionId)
p6.transaction.save(groovy.xml.XmlUtil.serialize(transactionInfo), 'p6_demo.TransactionInfo', ipk)

We will now add a script that will adapt the workflow step definition “on the fly”. Create a script named ‘CustomWFStepBuilder’, as follows:

import java.text.SimpleDateFormat

def workflowStep = p6.pipeline.getXml 'templateStepXml'


def dataType = p6.pipeline.get 'platform6.request.dataType'
def itemIds = p6.pipeline.get 'platform6.request.ids'

def itemPk = p6.transaction.buildPK(dataType, itemIds)

def transactionInfoContent = p6.transaction.exists(itemPk)

XmlSlurper slurper = new XmlSlurper()
def transactionInfo = slurper.parseText(transactionInfoContent)




//  SET Default shipmentdate parameters to current date
def currentDate = new Date()
SimpleDateFormat transaction_sdf = new SimpleDateFormat("yyyy-MM-dd")
def formatedDate = transaction_sdf.format(currentDate)

def myAction = workflowStep.Actions.Action.find{it.@id == 'confirm'}
def myParameter = myAction.Parameter.find{it.Name == 'shipmentdate'}
myParameter.DefaultValue = formatedDate

def mySecondAction = workflowStep.Actions.Action.find{it.@id == 'notconfirm'}
def myOtherParameter = mySecondAction.Parameter.find{it.Name == 'shipmentdate'}
myOtherParameter.DefaultValue = formatedDate


def stepXml = groovy.xml.XmlUtil.serialize( workflowStep )

p6.pipeline.put 'stepXml', stepXml

Deploy the Route

Go back to the ‘Routes’ page, open the ‘RoutingRulesForPurchaseOrders’ deployment script created earlier, and hit the ‘Start job’ button - in order to actually deploy the route.
Expand the ‘ACTIVE ROUTES’ top panel (which is collapsed by default), and you will see a new ‘Extend p6_demo Routing rules for Purchase Orders’ route, which just started.

Update p6_demo script to call your endpoint

Go to the ‘Scripts’ page, open the ‘p6_demo.WFHandlePO-HandleActionAcceptOrder’ script and edit it so it calls the ‘direct:p6router.ExtendPurchaseOrder’ endpoint we created initially in the ‘RoutingRulesForPurchaseOrders’ route.
To do so, you just need to change the last line of the script:

//p6.transaction.saveAndRoute(groovy.xml.XmlUtil.serialize(transactionInfo), 'p6_demo.TransactionInfo', ipk, 'direct:p6router.p6_demo_Dispatcher')
p6.transaction.saveAndRoute(groovy.xml.XmlUtil.serialize(transactionInfo), 'p6_demo.TransactionInfo', ipk, 'direct:p6router.ExtendPurchaseOrder')

Once done, restart the Scripts service.

That’s it. You’ve now added steps to the Demo scenario.