25 Automating Business Processes#

Automate business processes with APEX Workflows to coordinate work and show progress clearly.

A business process is a sequence of steps your organization follows to get something done. It can involve the work of multiple colleagues, each responsible for an activity like completing a job or approving a request. The tasks may also include interacting with external applications, collecting data, researching historical information, notifying stakeholders, or waiting for an event to occur or a period of time to elapse.

When the process is manual, it can be hard for everyone involved to understand questions like:
  • What processes are in progress?
  • Where are we in the process on this one?
  • Who are we waiting on for that one?

APEX Workflows let you automate business processes, improving efficiency for the people involved and enhancing visibility into the process for better decision-making.

25.1 Automating a Process with Workflow#

Model business processes visually and show progress in your app as they run.

You can automate a business process of any complexity with an APEX Workflow. It runs in the background, can be short-lived or long-running, and can perform any kind of actions required, including useful highlights like:
  • working with relational data, spatial data, graph data, JSON, XML, and more,
  • adding a task to one or more user's task lists and waiting for its completion,
  • predicting outcomes based on historical data using machine learning,
  • leveraging AI models for semantic similarity searching using vector search,
  • integrating with Generative AI services and other REST APIs,
  • notifying users via email or push notification,
  • generating pixel-perfect PDF reports, or
  • waiting for events to occur like data changes or a certain amount of time to elapse.

In App Builder, you define the sequence of activities in a visual diagram that anyone can understand at a glance. A workflow specifies the blueprint for a business process, and the APEX Engine manages each concrete example of every workflow. Using the Create Page Wizard, you can generate a workflow console page to show users "in flight" processes. The companion workflow detail page shows the status of a workflow instance complete with an annotated diagram so everyone involved stays aligned.

25.2 Exploring Patient Onboarding Example#

Explore how APEX Workflow automates patient onboarding at the fictitous Woods Clinic.

Consider a concrete use case to explore how an APEX workflow solves real business challenges. Imagine you are the lead developer at Woods Clinic, a fictional ambulatory medical clinic. You've been tasked with improving the efficiency of new patient onboarding. You start by understanding the current situation, then use APEX Workflow to deliver business value by automating a number of previously-manual steps.

25.3 Automating Business Entity Lifecycle Logic#

Use a trigger and automation to wake workflow lifecycle logic on business entity status changes.

Your workflows can wait for data change events to implement custom business logic visually as a business entity row progresses through its natural "lifecycle".

25.4 Maintaining Workflow Processes#

Maintain workflows and task definitions through import, debugging, deployment, and cleanup.

As you create and enhance your workflows and task definitions to automate business processes, it's important you can:
  • upgrade an existing app correctly to retain workflows and tasks,
  • recognize when you are running in development mode,
  • debug problems that arise during development,
  • navigate the workflow and task data dictionary views,
  • anticipate periodic purging of completed workflow and task instances,
  • activate an In Development version for use in production,
  • verify a workflow version is no longer used before deleting it, and
  • remove development instances created during testing.

25.1.1 Using the Diagram and Activities Palette#

Use the Workflow Designer to add activities, connect them, and edit their properties.

The Diagram Builder surface occupies the bulk of the Workflow Designer layout. The Activities palette below contains actions you can drag onto the diagram as workflow steps. Connection arrows between activities define the sequence the APEX engine follows. The Workflows tab on the left presents diagram elements organized in a tree. The Property Editor on the right lets you inspect and change settings for activities and connections.

A workflow begins with the Start activity shown below as a circle with the "play" triangle icon. If the workflow contains Switch activities to make decisions, there are multiple "paths" from the start activity to a Workflow End activity that appears as a circle with the "stop" square icon. While there is always only one Start activity, to simplify diagram layout you can include as many End activities as needed.

Figure 25-1 Activities Palette Shows Available Types You Can Drag onto Diagram

25.1.2 Surveying the Native Activities#

Understand the extensible set of native workflow activities.

Your workflows can use the following native activities:
  • Invoke API to call PL/SQL or REST APIs
  • Switch to decide between alternative paths
  • Human Task - Create to add an approval or action task for a user to complete before proceeding
  • Send E-Mail to notify users for any reason, including relevant data
  • Wait to pause until an event occurs or a period of time elapses
  • Execute Code to run inline PL/SQL
  • Send Push Notification to alert a user on smartphone, tablet, or desktop with a short message
  • Invoke Workflow to call another workflow like a subroutine and wait for it to return
  • Parallel Flow to run multiple sequences of activities in parallel and wait for all to complete
  • Generate Text with AI to produce textual output based on a system prompt and custom data, and
  • Server Side Geocoding to turn an address into coordinates to show on a map.

When none of the built-in activities meets your needs, you can install a community-developed workflow activity plug-in or create one yourself. Just create a process type plug-in with its Workflow Activity checkbox checked in the Supported For section.

Tip:

Server Side Geocoding is currently only available in Oracle APEX on Autonomous AI Database. In contrast, the Geocode Address dynamic action works in all APEX installations. It cooperates with a Geocoded Address page item type to geocode an address in a page.

25.1.3 Connecting and Routing Activities#

Learn the basic diagramming moves for creating, connecting, and routing workflow activities.

Creating organized, readable diagrams is straightforward after learning and practicing the basics of creating, connecting and routing activities.

25.1.4 Resolving Errors Before Saving a Workflow#

Resolve any Workflow Designer errors before saving your changes.

When working in Workflow Designer, if your diagram contains any errors a toolbar indicator shows the number of problems in a red circle. Click this icon to open the Messages dialog to review the details. Click a message to navigate to the item that needs fixing. When the diagram has no errors, click (Save) to make your changes permanent.

Figure 25-21 Clicking Error Indicator to Review Pending Issues

25.1.5 Associating a Table with a Workflow#

Associate a workflow with a table or query so activities can reference related row data.

A business process often involves a business entity in your application data model. A workflow definition models that process, and a workflow instance represents one example of it for a particular row in that business entity's table. Many workflow activities reference or modify data in this related business entity row. APEX simplifies this common usage pattern by letting you:
  • configure the Additional Data table and primary key column in a workflow definition, and
  • supply a Details Primary Key value of a corresponding row when creating a workflow instance.

Then, wherever your app starts a new workflow instance, remember to supply the primary key value of the business entity row to which the new flow relates.

This tiny investment of time pays big productivity dividends. Without writing any code or queries, you can reference the business entity row's details anywhere in the flow using any column name. Just use the syntax:
  • &NAME. in text substitutions,
  • :NAME in code and queries, and
  • NAME in activity properties requiring an item name.

For example if your Additional Data table's primary key column is ID, then reference :ID or &ID. or configure ID as the item name providing the value of an activity property.

Tip:

For even more powerful automatic data access in your workflow, change the Additional Data > Type from Table to SQL Query. Your query typically includes a primary table and its primary key column, and can optionally join in any other related data. It must return exactly one row. For workflows accepting a Details Primary Key, your query's WHERE clause can reference its value using the :APEX$WORKFLOW_DETAIL_PK bind variable. Then throughout the workflow you can use &NAME. or :NAME notation to reference the value of any column in your Additional Data query's SELECT list.

25.1.6 Retrieving Data for an Activity#

Use activity-level Additional Data to query one row of values the current activity needs.

In addition to the workflow-level Additional Data table or SQL query, every activity also supports an optional Additional Data query. Wherever it adds value, use it to declaratively retrieve a single row of relevant information the current activity requires to get its job done. Using bind variables, the query can reference column values from the workflow-level Additional Data, parameters, and version variables. It can join in data from any tables in the data model and engage functions in its SELECT list. This makes it convenient to perform on the fly computations whose results are useful to the current activity.

Once you've configured the optional Additional Data query on an activity, then in that specific activity you can reference the value of any of its SELECT list columns, using:
  • &NAME. in text substitutions,
  • :NAME in code and queries, and
  • NAME in activity properties requiring an item name.

Tip:

Activity-level Additional Data column values are only available to the activity whose Additional Data query retrieved them. Subsequent activities in the flow cannot reference these values. Retrieve the column in your workflow-level Additional Details SQL query to let all activities reference it. Or, if you need to store a point-in-time value that later activities can reference, use an Execute Code activity to assign it to a workflow variable.

25.1.7 Defining Parameters and Variables#

Use parameters and variables for values a workflow cannot query or must capture at a point in time.

Workflow parameters and variables are a convenient way to persistently store values unique to a workflow instance. Parameters get their value at instance start time and are read-only during the flow. In contrast, your workflow activities can both read and change the value of variables. So one activity can store results in a variable that a later activity can reference or modify. The APEX engine manages their storage automatically.

You define parameters at the workflow definition level, while you specify variables on the workflow version level. While formally called version variables, often workflow variable or just variable are clear alternatives. While local PL/SQL variables are transient values available only within the scope you declare them, workflow variables are available from any activity for the life of the workflow.

Most workflows configure an Additional Details table or query at the workflow level. When combined with the Details Primary Key value passed in when starting the workflow instance, this makes the values of Additional Data columns available anywhere in the flow using :NAME, &NAME. or NAME syntax. Activities can also use the value of the additional data table's primary key column to query other necessary data the process requires by defining an activity-level Additional Details query.

However, if a workflow needs an input value that you cannot query from a table, you can define any number of parameters and variables. When considering whether to add a workflow parameter or variable, ask yourself:
  • Can I query the value from a table in my data model?
    • Yes: retrieve it in the workflow Additional Data query if many activities need it, or in an activity Additional Data query otherwise.
    • No: it's a good candidate to accept as a parameter or to compute as a variable
  • Do I need to store a point in time value used for a calculation in this flow since it might change?
    • Yes: it's a good candidate for a workflow variable
    • No: retrieve it in the workflow Additional Data query if many activities need it, or in an activity Additional Data query otherwise.

Tip:

Both parameter and variables values appear for any instance in the workflow detail page you can generate from the Create Page Wizard. When necessary to fix a problem with a particular flow, that workflow instance's administrator can also modify variable values from this page.

25.1.8 Understanding Background Execution#

Workflows run in a background session without page session state or a logged-in user.

The APEX engine manages the progress of workflow instances asynchronously in the background. It processes each instance's next activity in a distinct engine session with no logged-in user. A workflow engine session is also different from the user session created for each end user of your application's pages.

By design, two consequences are:
  • a workflow instance has no access to session state values from application pages, and
  • evaluating APP_USER returns nobody in a workflow.
Therefore, any value your workflow depends on needs to be:
  • A built-in APEX workflow substitution (e.g. APEX$WORKFLOW_INITIATOR),
  • An Additional Data column,
  • Saved in a table, so an activity can query it,
  • Passed as a parameter value to the workflow instance at start time,
  • Stored in a workflow variable by an earlier activity, or
  • Saved to an updatable task parameter accessible via APEX_HUMAN_TASK API.

25.1.9 Getting Approval or User Input in a Workflow#

Wait for user approval or input by creating a task or sending a notification.

Your workflow runs the steps in a business process. Sometimes, it needs input from a user. They might need to confirm a manual task, approve or reject something, or enter data so the workflow can proceed. The user needs a page where they can see what’s needed and enter their response. But workflows run in the background where there’s no "current user" to show a page to. Instead, use one of these familiar patterns:
  • Create a work item in a to-do list the user checks periodically.
  • Send the user a push notification or email, and wait for them to act on it.

Either way, the user clicks a link, opens a page, and provides their input. The workflow waits in the background. It moves on only after the user responds, whether that’s in a few minutes or a few days.

25.1.10 Identifying Workflow Participants#

Assign workflow owners and administrators using case-sensitive usernames.

A workflow participant is either a workflow owner or workflow administrator. A workflow owner is a user with overall responsibility for the business process, while a workflow administrator helps address a problem relating to the flow. For example, if user DAVID is the owner of the Patient Onboarding workflow, he's the one who can see it in his workflow console when the context is set to My Workflows. He can also retry or terminate the flow. If user CARLA is the workflow's business administrator, she'll see it in her Admin Workflows console list. She can suspend it, change its variable values if needed to fix a problem, and resume it again.

Caution:

Participant usernames are case-sensitive. User DAVID can login with the username david. However, if you assign user david to a workflow, David does not see it in his My Workflows console list because david does not match what the APEX engine returns for the currently logged-in username (APP_USER). If :APP_USER returns DAVID, then use DAVID as the participant username.

25.1.11 Listing Workflows and Viewing Details#

Create Workflow Console pages to list the workflows a user owns, administers, or initiated.

In the Create Page Wizard, create a Workflow Console page to list workflow instances. You provide the name for the page that lists workflow instances, and the workflow details page. The Report Context you choose determines the filtered list of workflow instances the page displays at runtime.

Table 25-5 Report Context Determines Which Workflow Instances Display

Scroll horizontally to view the full table
Report Context Instances Where Current User Is API Constant
My Workflows Workflow Owner MY_WORKFLOWS
Admin Workflows Workflow Administrator ADMIN_WORKFLOWS
Initiated by Me Initiator INITIATED_BY_ME

You can use the pages as they come, or customize them in Page Designer as needed. The page below is a slightly modified version of an Admin Workflows console page. Notice the workflow title you configure is used as the Title of the Content Row region the generated console page contains.

Figure 25-72 Title Appears in Console Page Listing Workflow Instances

Tip:

By default, the console's list page shows workflow instances for all applications in the workspace, but you can focus the page to display only workflows for the current application by uncommenting the p_application_id parameter line in the Content Row region's query shown below. By default, the wizard generates a SELECT statement with a subset of available columns. Change the query to select * to see the complete list of available workflow columns you can reference.

select *
  from table ( apex_workflow.get_workflows (
                   p_context => 'ADMIN_WORKFLOWS'
                   --, p_application_id => :APP_ID
                   ) )

Clicking on the title of a particular workflow instance like New Patient Danny Deng, the generated workflow detail page appears. It shows the list of completed activities for the selected instance. Expand the Variables, Parameters, and History sections to see additional information.

Figure 25-73 Workflow Detail Page Displays Status of Selected Instance

25.2.1 Understanding the Current Process#

Understand the manual patient inquiry process before workflow automation.

A new patient inquires about a particular medical procedure they need. Then, Woods Clinic staff members review the provided information to decide if the clinic can accommodate the patient's request.

25.2.2 Identifying Automation Opportunities#

Identify the patient onboarding steps workflow automation can handle.

You identify several opportunities to automate the current manual patient onboarding process. You aim to:
  • Automatically reject patients who live outside the clinic's service range
  • Proactively reject historically risky combinations of insurance provider and medical procedure
  • Skip human approval step when the insurer has historically reimbursed the procedure
  • Notify rejected patients by email without staff involvement
  • Provision approved patient's portal access account and generate a temporary password
  • Mark process complete after patient provides their birth date and proof of insurance

25.2.3 Starting a Patient Onboarding Workflow#

Create a workflow definition and start building its first version in Workflow Designer.

In the existing application, you create a new Workflow shared component named Patient Onboarding. The workflow Diagram Builder feels familiar, since it works like Page Designer. As shown below, your workflow appears in the tree in the Workflows tab. Since a business process can change over time, a workflow definition can have multiple versions. Out of habit, you name the first version 1.0. A version starts in the In Development state until you activate it later for use in production. Notice the 1.0 [Dev] label in the tree below confirms this state. The Property Editor also reflects it.

To get you started, App Builder creates a single Execute Code activity sequenced between a Start and an End. You can change its type and properties as needed, and add other activities and connections to the diagram to build up your flow. As flows get more involved or you identify a reusable "subflow", one workflow can invoke another like a subroutine, passing parameters in and out as required.

Figure 25-76 Workflow Version Starts in Development Mode Until You Activate It

25.2.4 Associating the Patients Table#

Associate the workflow with the patients table so activities can reference patient row data.

The patient onboarding process relates to patients in the FRC_PATIENTS table. Each patient's onboarding journey is a concrete example of the process "blueprint" for one particular patient. Since the Patient Onboarding workflow represents this business process, most of its steps reference or modify the FRC_PATIENTS row whose onboarding progress each instance represents.

As shown below, Additional Data is a workflow version property. Just set the Patient Onboarding workflow's table to FRC_PATIENTS and identify ID as its Primary Key Column. Then, wherever your app starts a new Patient Onboarding workflow instance, remember to supply the ID value of the patient to whom the new flow relates. This lets you reference patient details as &NAME. or :NAME anywhere in the flow using any FRC_PATIENTS column name. For example, you can reference the patient's FIRST_NAME and LAST_NAME anywhere you need them in the flow.

Figure 25-77 Getting Started on a Patient Onboarding Workflow

25.2.5 Configuring a Meaningful Title#

Set a meaningful workflow title using values from the related patient row.

One convenient place to reference patient information is in the workflow's title. It appears anywhere users can list "in-flight" workflow instances, so it's important to make it meaningful. As shown below, after configuring your workflow's Additional Data table (or query) you can use &NAME. notation to include the patient's first name and last name using a Title of:
New Patient &FIRST_NAME. &LAST_NAME.
Figure 25-78 Referencing Patient Columns by Name in the Workflow Title

25.2.6 Building Patient Onboarding Visually#

Explore and understand key aspects of building the Patient Onboarding workflow visually.

You build business processes visually by adding activities to your workflow diagram, sequencing them with connection arrows, and using the Property Editor to configure their settings. The Woods Clinic Patient Onboarding flow uses of many APEX workflow's native features.

25.3.1 Learning the Medical Procedure Lifecycle#

Understand the patient procedure lifecycle tracked by the STATUS column.

In your Woods Clinic application, once a patient's initial procedure request is approved, you track the medical procedure for the patient as a new row in the Patient Procedures table. You can see below how it fits into the application data model. The figure shows an entity relationship diagram of the business entities involved in the Woods Clinic application. This scenario focuses on the Patient Procedure. It tracks a particular Patient's visit at which a selected clinic Doctor performs a certain Medical Procedure they need. Each patient has an Insurance Provider and can make Patient Referrals to help bring in new patients. Multiple doctors might perform a given procedure, so Doctor Procedures tracks that skills matrix. Finally, a patient procedure might have one or more Invoices to bill the insurance provider and the patient who pays the uncovered amount.

Figure 25-110 Patient Procedures Table in the Woods Clinic Data Model

Your FRC_PATIENT_PROCEDURES table has a STATUS column whose value reflects the current status of the "Patient Procedure" business entity.

After speaking with Woods Clinic staff, you draw yourself the diagram below of a medical procedure's natural flow from creation to closure. You think of it as the "lifecycle" of this business entity. You learn the following:
  • The procedure starts out "Awaiting Schedule".
  • When staff set a date, time, and doctor for the procedure, it becomes Scheduled.
  • Once performed, the procedure becomes Completed
  • After receiving the insurance reimbursement, the balance is Invoiced to the patient.
  • As soon as the patient pays, it's finally marked Paid.
Figure 25-111 Patient's Medical Procedure Statuses Define its Lifecycle

25.3.2 Seeing the Medical Procedure Lifecycle#

Visualize patient procedures by status to see how work flows through the clinic.

The medical procedure's lifecycle is not only for internal tracking purposes. As shown below, your Procedure Radar page uses the status value to help all staff understand how medical procedures are "flowing" through the clinic. The Kanban board shows:
  • Awaiting Schedule
    • Bob Bunn (Kidney stone treatment)
    • Enya Ensor (Hernia operation)
  • Scheduled
    • Dora Dodd (Cataract removal with Dr. Marshall Garay)
  • Completed
    • Freda Foote (Lasik laser vision correction with Dr. Russel Whyte)
    • Gregor Green (Cardio stress test with Dr. Javier Alfred)
  • Invoiced
    • Harry Hu (Mole removal with Dr. Joni Rosales)
Figure 25-112 Kanban Board Shows Users How Procedures Progress Through Their Lifecycle

25.3.3 Modeling the Medical Procedure Lifecycle#

Model status-driven business logic with a workflow that waits for procedure status or schedule changes.

The Woods Clinic business process for patient procedures closely aligns with these statuses. More specifically, it involves the steps back office staff take when a procedure changes statuses. From talking to Woods Clinic staff, you learn when a patient's medical procedure is:
  • Scheduled – staff send an email to the patient
  • Completed – staff prepare a draft invoice to review with the insurance provider
  • Invoiced – staff email the invoice to the client for payment
  • Paid – staff consider the procedure's lifecycle complete.

You can use a workflow with a Wait activity as shown below to automate this medical procedure lifecycle. Notice the Wait activity has the Static ID unique identifier of status_or_date_changed and that it has left the Timeout Type unset. This makes the workflow wait indefinitely until your application logic signals it to continue.

At that time, it can populate a workflow variable V_WHAT_CHANGED with the value:
  • STATUS – if the medical procedure status has changed, or
  • SCHEDULE – if its schedule has changed.

The Switch activities can use the value of the V_WHAT_CHANGED variable and Additional Data columns like STATUS, DOCTOR, and PROCEDURE_DATE of the associated FRC_MEDICAL_PROCEDURES row to perform appropriate business logic. Most of the paths in the workflow connect back to the Wait activity to await further data changes. However, when STATUS reflects the final "Paid" status, the workflow ends.

Figure 25-113 Procedure Lifecycle Workflow Coordinates Application Logic
Using this approach, you can automate the previously manual steps in the medical procedure lifecycle so that now, when a medical procedure's status becomes:
  • Scheduled – your workflow emails the patient
  • Completed – your workflow prepares the draft invoice as a pixel-perfect PDF document
  • Invoiced – your workflow emails the invoice as an attachment to the client for payment
  • Paid – your workflow marks the procedure as complete.

25.3.4 Starting the Lifecycle Workflow#

Start a procedure lifecycle workflow when onboarding creates the patient procedure row.

The final step of the patient_onboarding workflow uses an Invoke API activity to call the COMPLETED_REGISTRATION procedure below in the FRC_ONBOARDING package. It inserts a row in FRC_PATIENT_PROCEDURES to track the patient's medical procedure to perform at the clinic. Just after that, it calls the START_WORKFLOW_FOR helper procedure to create the procedure_lifecycle workflow instance related to the newly-assigned patient procedure ID.

-- in frc_onboarding package
procedure completed_registration(p_patient_id number)
is
    l_patient t_patient := patient(p_patient_id);
    l_new_procedure_id number;
begin
    -- insert initial procedure
    for j in (select l_patient.id as patient_id,
                     code,
                     'AwaitingSchedule' as status,
                     price,
                     duration_hours
                from frc_medical_procedures
               where code = l_patient.initial_procedure)
    loop
        insert into frc_patient_procedures(
            patient_id,
            medical_procedure,
            status,
            amount_due,
            duration_hours)
        values (
            j.patient_id,
            j.code,
            j.status,
            j.price,
            j.duration_hours)
        return id into l_new_procedure_id;
        start_workflow_for(l_new_procedure_id);
    end loop;
end;

The START_WORKFLOW_FOR helper method starts a workflow with Static ID procedure_lifecycle, passing in the medical procedure ID for the Details Primary Key value. It calls the START_WORKFLOW function in the APEX_WORKFLOW package.

-- internal in package frc_procedure_lifecycle
------------------------------------------------------------------
-- Start a procedure_lifecycle workflow instance associated with
-- the supplied patient procedure id
------------------------------------------------------------------
procedure start_workflow_for(
    p_procedure_id in number)
is
    l_workflow_id number;
begin
   l_workflow_id := apex_workflow.start_workflow(
                        p_application_id => frc_app.clinic_app_id,
                        p_static_id      => 'procedure_lifecycle',
                        p_detail_pk      => p_procedure_id,
                        p_initiator      => 'ADMIN');
end start_workflow_for;

Tip:

When you don't plan to use a function's result, to avoid a PL/SQL compilation error, assign it to a local variable that you then ignore. Failing to do this in the above example would yield the error:
PLS-00221: 'START_WORKFLOW' is not a procedure or is undefined

25.3.5 Following a Medical Procedure Lifecycle#

Follow a patient procedure through its full lifecycle in the Woods Clinic app.

Woods Clinic staff member David approved patient Danny Deng's medical procedure. Danny got his confirmation email with login credentials from your patient_onboarding workflow. He logged in and completed his registration. This concluded the patient onboarding workflow, whose last step is to create the patient procedure row and its corresponding procedure_lifecycle workflow. The steps that follow illustrate how David takes Danny's medical procedure through its lifecycle at the clinic.

25.3.6 Notifying Workflow of Data Changes#

Notify a waiting workflow about data changes with events an automation processes periodically.

After their initial approval, your Procedure Lifecycle workflow automates previously manual steps performed as medical procedures progress through the clinic. It accomplishes this by reacting to changes in the procedure status or schedule.

To notify the workflow of data changes, you use a combination of a database trigger to log patient procedure lifecycle events, and an automation to periodically process the logged events. As the automation processes any events, it notifies the workflow of data changes by signaling the Wait activity to continue.

25.4.1 Importing vs. Deleting an Application#

Import upgrades over the existing app to preserve production workflow and task instances.

Once you deploy your application using workflows and tasks into production, end users begin exercising its functionality to get their jobs done. The workflow and task instances your application creates are business critical, so they are as important as any other rows of data in your application data model. As you enhance your app and fix issues users report, you periodically import a new version of your application into production. When you do this, it is critical that you perform the upgrade without deleting the existing application first. There is clear motivation for this guidance. When you import a new application version over an existing one, any existing workflow and task instances remain as they are. This is not the case when you delete an application.

Caution:

When you delete an app, all its workflow and task instances are deleted, too.

25.4.2 Recognizing Development Mode#

Recognize Development Mode by the Developer Toolbar or APP_BUILDER_SESSION value.

During development, when you run your app from App Builder, it's in "Development Mode". The Developer Toolbar appears when in this mode. Using its settings menu, as shown below, you can change its default display position, auto-hide behavior, and button display style.

Figure 25-153 Development Toolbar Appears When Running in Development Mode

Tip:

To test at runtime if your app is running in Development Mode, you can use the condition V('APP_BUILDER_SESSION') IS NOT NULL, which returns true in this mode.

You can disable the Developer Toolbar in Development Mode session if necessary. As shown below, change the Availability > Status setting in Shared Components > Application Definition to Available instead of the default Available with Developer Toolbar.

Figure 25-154 Your Availability Setting Can Disable the Developer Toolbar

Tip:

Check your app's Availability > Status setting if you don't see the Developer Toolbar when running from App Builder, but do want it to display. Make sure it's set to Available with Developer Toolbar.

25.4.3 Debugging Business Process Logic#

Use APEX debug tracing to investigate workflow and task errors.

When a business process encounters an error, use APEX debug tracing to learn more information about what went wrong.

25.4.4 Accessing Workflow and Task Data#

Understand APEX dictionary views and packages for workflows, tasks, and their definitions.

APEX dictionary views shown below contain complete information about your workflow definitions and instances. The APEX_WORKFLOW package is your API to work programmatically with workflows.

Table 25-7 APEX Dictionary Views for Workflows

Scroll horizontally to view the full table
Workflow View Parent View
Definitions APEX_APPL_WORKFLOWS APEX_APPLICATIONS
Parameter Definitions APEX_APPL_WORKFLOW_PARAMS APEX_APPL_WORKFLOWS
Process Parameter Values APEX_APPL_WORKFLOW_COMP_PARAMS APEX_APPLICATION_PAGE_PROC
Versions APEX_APPL_WORKFLOW_VERSIONS APEX_APPL_WORKFLOWS
Variable Definitions APEX_APPL_WORKFLOW_VARIABLES APEX_APPL_WORKFLOW_VERSIONS
Participant Definitions APEX_APPL_WORKFLOW_PARTICIPANTS APEX_APPL_WORKFLOW_VERSIONS
Activity Definitions APEX_APPL_WORKFLOW_ACTIVITIES APEX_APPL_WORKFLOW_VERSIONS
Activity Variable Definitions APEX_APPL_WORKFLOW_ACT_VARS APEX_APPL_WORKFLOW_ACTIVITIES
Branches APEX_APPL_WORKFLOW_BRANCHES APEX_APPL_WORKFLOW_ACTIVITIES
Transitions APEX_APPL_WORKFLOW_TRANSITIONS APEX_APPL_WORKFLOW_ACTIVITIES
Instances APEX_WORKFLOWS APEX_APPL_WORKFLOW_VERSIONS
Activities APEX_WORKFLOW_ACTIVITIES APEX_WORKFLOWS
Activity Variables APEX_WORKFLOW_ACTIVITY_VARS APEX_WORKFLOW_ACTIVITIES
Variables APEX_WORKFLOW_VARIABLES APEX_WORKFLOWS
Parameters APEX_WORKFLOW_PARMETERS APEX_WORKFLOWS
Participants APEX_WORKFLOW_PARTICIPANTS APEX_WORKFLOWS
Audit APEX_WORKFLOW_AUDIT APEX_WORKFLOWS

The diagram shows the relationship between dictionary views related to workflow definition metadata and workflow instance data.

Figure 25-159 APEX Dictionary Views Related to Workflow Instances

The additional views shown below contain all relevant information about your task definitions and instances. The APEX_HUMAN_TASK package is your API to work programmatically with tasks.

Table 25-8 APEX Dictionary Views for Workflows

Scroll horizontally to view the full table
Task View Parent View
Definitions APEX_APPL_TASKDEFS APEX_APPLICATIONS
Parameter Definitions APEX_APPL_TASKDEF_PARAMS APEX_APPL_TASKDEFS
Participant Definitions APEX_APPL_TASKDEF_PARTICIPANTS APEX_APPL_TASKDEFS
Action Definitions APEX_APPL_TASKDEF_ACTIONS APEX_APPL_TASKDEFS
Instances APEX_TASKS APEX_APPL_TASKDEFS
Parameters APEX_TASK_PARMETERS APEX_TASKS
Participants APEX_TASK_PARTICIPANTS APEX_TASKS
History APEX_TASK_HISTORY APEX_TASKS
Comments APEX_TASK_COMMENTS APEX_TASKS

The diagram shows the relationship between dictionary views related to task definition metadata and task instance data.

Figure 25-160 APEX Dictionary Views Related to Task Instances

25.4.5 Understanding Task and Workflow Purging#

Plan for workflow and task purging by storing any permanent history in application tables.

The APEX platform manages the workflow and task instances for all workspaces. To avoid unbounded storage requirements, a daily job enforces the retention period your instance administrator's configures for completed tasks and workflows.

The default retention period defaults to 7 days for tasks and 30 days for workflows. The administrator can extend these up to 30 days for tasks and up to 100 days for workflows. Any completed instances beyond the retention period get archived, then purged.

Tip:

Any errored or canceled tasks are archived and removed daily. They don't have a retention period.

Given this platform policy, evaluate if any aspect of your workflow and task instance information needs to be recorded permanently in a table in your application data model. If so, some approaches to consider include:
  • A task action on the Complete event can store key info in application tables
  • A workflow activity can store key info in application tables at any time
  • An automation can transfer key info to application tables by querying appropriate dictionary views.

25.4.6 Activating an In Development Workflow#

Activate a workflow version before running it outside Development Mode.

When you run you application in Development Mode, starting a workflow like Procedure Lifecycle shown below uses the In Development version if it exists. Otherwise, it uses the Active version. However an attempt to start a workflow with only an In Development version when outside of a Development Mode session produces the runtime error message:
  • Workflow your_static_id has no Active version

Caution:

To use a workflow outside of a Development Mode session, you must activate a workflow version first.

To activate a version, select it in the Workflows tree. First, ensure it has a Workflow Owner participant configured. It's recommended to configure a Workflow Administrator, too. Then, use the Activate option on the context menu. Alternatively, as shown below, click the (Activate) button next to the State setting in the Property Editor. A confirmation dialog appears informing you any currently active version will be deactivated. This means the state of the existing Active workflow changes to Inactive.

Workflow versions in the Inactive state display with a strikethrough font in the Workflows tree like 1.0.8 and 1.0.9 below. Make any activations and deactivations permanent by clicking (Save) in the Diagram Builder.

Figure 25-161 Activating an In Development Workflow

25.4.7 Deploying Workflows to Production#

Promote active workflow versions from development so production stays in sync.

Once your workflow has an Active version – for example, 1.0.0 – you can promote your application to the production environment. Any new workflow instances created in production, use the Active version at their time of creation. Every workflow instance tracks the version used to create it, and continues using that version for its entire lifecycle.

Business processes change over time. So naturally, at some point new requirements compel you to duplicate Active version 1.0.0 to create the In Development version 1.0.1. Always do this in your Development environment. You make any necessary changes to the process, testing the In Development version in Development Mode by running your app from App Builder. When it's ready, you activate version 1.0.1 in development.

When you next promote your application to production, new workflow instances will now use the Active version 1.0.1, while previously created flows continue using the Inactive version 1.0.0 they were created with. As time goes on, your application accumulates new workflow versions by repeating this process over and over. You only modify In Development workflow versions in your Development environment, promote the application to Production, and never modify workflow versions in Production. This ensures your production environment is always in sync with development. The diagram illustrates how workflow versions accumulate in production over an application's lifecycle. The starred workflow version is the single active one. The versions with a moon icon are inactive, but might still be used by existing workflow instances.

Figure 25-162 Workflow Versions Accumulate Over Time

Caution:

To keep development and production environments in sync, never duplicate Active versions in production. Since your APEX application export contains all component definitions, the application you import into production technically contains the In Development versions, too. However, since these are only usable in Development Mode, the diagram omits them in production for clarity.

25.4.8 Deleting a Workflow with No Instances#

Check whether any workflow instances still use a version before deleting it.

Workflow versions let your business process evolve over time. Every workflow instance created outside of Development Mode uses the Active version at the time of creation. The WORKFLOW_VERSION column in the APEX_WORKFLOWS dictionary view reflects the version each instance uses. A given workflow instance continues using the workflow version it was created with throughout its lifecycle.

If you activate a new version, this only affects new workflow instances. An Inactive version does not mean an Unused version. It only means no new workflow instances will use that inactive version.

In addition to deactivating previous workflow versions, the Workflow Builder also lets you delete workflow versions. However, you must ensure no workflow instances are using that version any more before you delete it. Before deleting a workflow, run the following query in the SQL Workshop > SQL Commands page to verify that the version you are considering deleting is no longer in use.

-- list procedure_lifecycle workflow versions in use
select distinct workflow_version
 from apex_workflows
where application_id = 103
  and workflow_def_static_id = 'procedure_lifecycle'
order by workflow_version

Caution:

Avoid accidentally deleting a workflow version that is still in use by running the above query first!

25.4.9 Removing Development Instances#

Remove Development Mode workflow instances and their related tasks after testing.

In Development Mode, starting a workflow uses the In Development version if available. Otherwise, the Active version is used. During development, you typically create many workflow instances for testing purposes. When you want to clean up these testing artifacts, call the REMOVE_DEVELOPMENT_INSTANCES procedure in the APEX_WORKFLOW package. It immediately purges any workflow instances created in Development Mode without archiving them. Since task instances created by workflows are dependent artifacts, this procedure also immediately purges any tasks related to these deleted workflow instances without archiving them.

Task instances created outside of Development Mode cannot be deleted. The workflow administrator can cancel them, and the daily job that purges canceled workflows and tasks removes them within 24 hours.

Tip:

Since task and workflow instances are removed when an application is deleted, you can use a Working Copy of an application to perform tests that might result in creating new workflow and task instances. When you delete the working copy, any workflow and task instances it created are deleted, too.

25.1.3.1 Adding an Activity from the Palette#

Add workflow activities by dragging them from the palette onto the canvas.

To add a new activity to the workflow canvas, drag it from the Activities palette and drop it onto the canvas. As shown below, guidelines appear to help you align it to existing activities.

Figure 25-2 Dragging an Activity from the Palette and Dropping onto the Canvas

25.1.3.2 Naming a New Activity#

Name each new activity clearly in the Property Editor.

By default, a new activity is named New, so use the Property Editor as shown below to give it a more meaningful name. For this exercise, you just name it B.

Figure 25-3 Naming a New Activity in the Property Editor

25.1.3.3 Changing a Connection's Target Activity#

Reattach a connection by dragging its arrowhead or starting dot to another activity.

To reattach an existing connection line, as shown below, drag its arrowhead onto the new activity it should connect to. An orange outline helps you see when you are within the visual boundary of a new target activity. Release the mouse button to complete the operation. Now activity A continues to B.

The figure below shows a diagram with a start activity connected to an activity named A. A then connects to the End activity. A B activity is in the upper right corner, initially with no inbound or outbound connections. A Which Way? switch activity lies in the center with one connection to C and another connection to E.

You can change a connection's origin as well by dragging its starting dot onto a new source activity. In the figure, the mouse drags the arrowhead previously connecting A to the End activity to drop it onto B. The result is that A then connects to B instead.

Figure 25-4 Reattach a Connection by Dragging its Arrowhead or Starting Dot

25.1.3.4 Fine-Tuning Line Slope with Anchor Dots#

Adjust a connection line’s slope by moving its source or target anchor dots.

When you select a connection line, anchor dots appear inside the boundaries of the source and target activities as shown below. The anchor dots determine the slope of the connection line between source and target. To help your eye see this concept, the figure below includes additional dotted line segments. Notice in the other connection lines that only the segment from the edge of the source activity to the edge of the target one is actually visible.

Figure 25-5 Connection Anchor Dots Determine Line Slope

You can move the anchor dot in the source or target activity anywhere within the boundary of the respective shape. For example, as shown below, by moving the anchor dot within the B activity's shape, you can change the slope of the connection line to be horizontal. Also notice the grab handle appears on the now-horizontal line.

Figure 25-6 Reposition Anchor Dots in Source or Target Activities to Change Slope

25.1.3.5 Moving Connections with Grab Handles#

Move horizontal or vertical connection segments with their grab handles.

Grab handles appear on any segment of a connection line that is horizontal or vertical, and, as shown below, you can use the drag handle to move the line segment up/down. The same goes for using the grab handle to move a vertical segment left and right more easily.

Figure 25-7 Drag Handles on Horizontal and Vertical Segments of Selected Connection

25.1.3.6 Adding a Connection Between Activities#

Connect activities by dragging a plus sign from the source activity to the target activity.

To connect two activities, hover over the source activity until the four plus signs appear. Then, as shown below, drag one of these plus signs and drop it into target activity rectangle. The orange highlight confirms the target activity. Your pointer position within the target activity shape when you release the mouse button is meaningful. It determines the initial placement of the target's anchor dot. You can then adjust the position of a selected connection's anchor dots anytime.

Figure 25-8 Adding a New Connection Between Activities

25.1.3.7 Bending a Connection with Elbow Dots#

Bend a connection by adding, moving, or removing elbow dots.

To bend a line, start by selecting it. Then, as shown below, single-click on the line where you want to place the elbow dot.

Figure 25-9 Select a Line Then Single Click to Add an Elbow Dot

After you've added an elbow dot, drag it into a new position as shown below.

Figure 25-10 Repositioning an Elbow Dot with Drag and Drop

As shown below, you can repeat the process of adding an elbow dot and dragging it to a new position to achieve whatever clean line routing feels right. If necessary, you can reposition the source or target anchor dots to fine-tune the slope of the initial or terminal connection line segments. Notice when your connection contains horizontal or vertical segments, grab handles appear that you can use to adjust each one separately in a single movement. To remove an elbow dot, select the connection line and then double-click on the dot.

Figure 25-11 Add Additional Elbow Dots and Reposition Them to Create Any Routing

25.1.3.8 Adding Next Activity from Existing One#

Add and connect the next activity directly from an existing activity.

Click on one of the plus icons on an existing activity to create an activity that happens next in the flow. As shown below, a context menu presents available activities to choose from.

Figure 25-12 Adding a Next Activity From a Current One

Your chosen activity type appears on the diagram already connected with a connection from the source you clicked on to initiate the process. By default, it is named New, so use the Property Editor to change its name to something more meaningful. Here, you name it D. Since the source activity is a Switch, the new connection line is red to let you know some configuration adjustment is needed.

Figure 25-13 Next Activity and Connection Appear on the Diagram

25.1.3.9 Configuring a Switch Connection#

Configure a Switch connection with a condition and name for its decision outcome.

In addition to its descriptive name, a connection originating from a Switch activity also identifies which decision outcome it represents. A red connection line indicates this detail is missing. Select the connection line originating from the Switch and, as shown below, configure its condition and give it a meaningful name. The Which Way? switch activity in this example decides based on a workflow variable. This particular D connection line represents the case when the variable Is Equal To the value D. Here the connection name and the target activity are both named D for simplicity. Rest assured your workflows can use any connection and activity names that best communicate the business process flow to developers and end users.

Figure 25-14 Configuring Properties on Connection Originating from a Switch

25.1.3.10 Inserting Activity Between Existing Ones#

Insert an activity into an existing connection.

To add a new activity between two existing ones, select the connection line between them and click the plus in the middle of the line. As shown below, a context menu appears letting you choose the activity type to create.

Figure 25-15 Using Content Menu to Add a New Activity Between Existing Ones

Alternatively, as shown below, you can drag an activity from the palette and drop it in the middle of the connection line between the existing activities.

Figure 25-16 Dragging and Dropping a New Activity Between Existing Ones

25.1.3.11 Moving a Group of Activities#

Move selected activities as a group while their connections follow.

If you've already positioned a set of activities and connections but need to make space, you can select and move a group. Use [Control]-Click on Windows or [Command]-click on the Mac to select multiple activities as shown below. Then you can drag the group as a unit by clicking on a shape inside the selected area. This action maintains relative positions and alignment of the group elements. You only need to select activities, the connection lines move accordingly.

Figure 25-17 Selecting and Moving a Group of Activities

An even faster way to select multiple activities is to hold down the [Shift] key and drag out a rectangle as shown below. This gesture selects any activities the rectangular selection touches. Once selected, as before, you can move them as a group by clicking on a shape inside the selected area.

Figure 25-18 Selecting a Set of Activities with a Shift+Drag Rectangle

25.1.3.12 Bringing Connections in Front of Activities#

Ensure arrowheads stay visible over activities in the runtime workflow progress diagram.

When viewing the runtime workflow diagram, you may notice some connection arrowheads appear to be "buried under" the activity shapes. While this effect does not confuse end users, it does look less than perfect. During the natural course of developing your workflow diagram, the "stacking order" of the shapes can result in activities stacking above connections. As shown below, this causes the tip of the connection arrow head to be obscured by the activity shape in a way that is even more noticeable in the read-only Workflow Diagram region.

Figure 25-19 Connection Arrow Tips Obscured by Activity Shapes

The solution is simple. As shown below, just use the "Bring to Front" tool in the toolbar to bring connections on top of activity shapes. Alternatively, use the "Send to Back" tool next to it to send the activity shapes behind the connections.

Figure 25-20 Use "Bring to Front" to Stack Connection Arrows On Top of Activity Shapes

25.1.7.1 Creating a Workflow Parameter#

Create workflow parameters with meaningful static IDs for start-time values.

Create a parameter using the Create Parameter option on the workflow's context menu in the Workflows tree. If a parameter already exists, the same option appears on the Parameters header in the tree. For each parameter you create, use the Property Editor to choose a data type and assign it a meaningful Static ID. While not required, using a parameter static ID that starts with a prefix like P_ can help you distinguish parameter names from Additional Data column names and from workflow variable names. You use the parameter's static ID to reference its value as a bind variable :P_YOUR_STATIC_ID in code or queries, &P_YOUR_STATIC_ID. in substitutions, and P_YOUR_STATIC_ID in activity properties requiring an item name.

Tip:

The parameter Label you define is the friendly description that appears in the workflow detail page when a user inspects a workflow instance.

25.1.7.2 Creating a Workflow Variable#

Create workflow variables with meaningful static IDs for values activities can change.

Create a variable using the Create Variable option on the workflow version's context menu in the Workflows tree. The same option exists on the Variables heading in the tree. For each variable you create, use the Property Editor to choose a data type and assign it a meaningful Static ID. While not required, using a variable static ID that starts with a prefix like V_ can help you distinguish variable names from Additional Data column names and from workflow parameter names. You use the variable's static ID to reference its value as a bind variable :V_YOUR_STATIC_ID in code or queries, &V_YOUR_STATIC_ID. in substitutions, and V_YOUR_STATIC_ID in activity properties requiring an item name. For example, when configuring a variable name to receive an "out" value from activities like Invoke API, Invoke Workflow, Human Task - Create, and others, use the V_YOUR_STATIC_ID variable name.

Tip:

The variable Label you define is the friendly description that appears in the workflow detail page when a user inspects a workflow instance.

25.1.9.1 Assigning Work to a User's Task Inbox#

Assign work to users so APEX manages the task lifecycle and continues the workflow when completed.

Use a Task Definition shared component to track a work item to assign to one or more people. The task Subject line you configure appears in the task list "inbox" of all assignees. They are all potential owners of the task. The first available user claims the task and becomes its actual owner. Once claimed, it no longer appears in other potential owners' task lists.

By configuring a Task Details Page, users can click a task's subject in this "inbox" to review its details. You can generate it in the task definition editor and adjust it in Page Designer as needed. Your enhancements to the page can include saving additional data to updatable task parameters or to application tables.

Some tasks require only that the owner confirm they've completed the action it represents. Others seek an outcome indicating the owner's approval or rejection. A task can be open-ended, or made time-sensitive by configuring a due date and expiration behavior. Your workflow uses Human Task - Create activities to coordinate which work items need to be done in what order. The APEX engine automatically waits for a potential owner to complete a task before proceeding to the next activity in the workflow. The figure illustrates the workflow as a postal employee delivering tasks to three different users task inboxes. One reviews tasks from a smartphone. Another sees tasks on a tablet. A third person works on tasks from a laptop.

Figure 25-22 Workflow Delivers Tasks to Potential Owners' Task List "Inbox"

Using this approach, you get many built-in features that enterprise applications often require. APEX manages the "lifecycle" of a task from creation to completion. Task definitions support more than just a priority and deadlines. Your dynamic task assignment can account for end-user vacations. Users can request and receive more info about a task, delegate it to a colleague, or participate in a threaded discussion about it with teammates when collaboration is required. You can relate each task instance to a particular row in a database table, and your task definition can react to changes in the state by notifying users or executing custom business logic. In fact, by defining an action on the task Create event, you can notify a user a new work item has been added to their to-do list, encouraging them to check their task "inbox".

25.1.9.2 Waiting for a Signal to Continue#

Send users a page link, wait for their response, and continue the workflow from application logic.

Your workflow can notify a user with a Send E-Mail or Send Push Notification activity. Either can include a link in the message. By clicking or tapping the link, the recipient navigates to the page to provide their input. After notifying the user, the next step in the flow can be a Wait activity with no Timeout Type set. This causes the workflow instance to wait indefinitely until some code calls CONTINUE_ACTIVITY in the APEX_WORKFLOW package to tell the workflow to move on. When invoking this method to continue a workflow activity that's currently waiting, you need to pass in:
  • The unique ID of the workflow instance
  • The unique Static ID of the activity to continue (typically a Wait activity)

When continuing a waiting activity, you can also optionally pass a list of name/value pairs. If any of the names in this list match the names of workflow variables, their values get assigned automatically to the corresponding value you return.

25.1.10.1 Configuring Static Workflow Participants#

To create a new participant, choose Create Participant from the context menu of the Participants node in the Workflows tree.

Then set the participant Type in the Property Editor. As shown below, in a simple application, either or both of the Workflow Owner or Workflow Administrator participants can be a static value like DAVID or CARLA. But normally you use one of the Value > Type settings like SQL Query, Function Body, or Expression to dynamically determine one or both of these participant lists.

Figure 25-71 Configuring Workflow Participants

Caution:

Participant usernames are case-sensitive. If :APP_USER returns DAVID, then use DAVID.

25.1.10.2 Determining Participants Dynamically#

To determine workflow participants dynamically, use a participant Value > Type of SQL Query, Function Body, or Expression.

When using the SQL Query option, your single column SELECT list returns one case-sensitive username per row. For the Function Body or Expression options, return a comma-separated list of case-sensitive usernames.

Your query, function, or expression can use bind variable syntax to reference workflow parameter values, workflow version Additional Data column names, application items, or other APEX substitution strings.

Tip:

Workflows execute in the background where there is no "logged-in user". So the APP_USER substitution returns nobody in a workflow context. Use APEX$WORKFLOW_INITIATOR to reference the username that initiated the workflow. If it is not the user you need, define a workflow parameter to accept a username for other purposes.

For easiest maintainability, your expression can invoke a PL/SQL package function as well. The function can be specific to the workflow at hand, or by passing the value of the pre-defined bind variable APEX$WORKFLOW_ID your generic function can lookup the related workflow definition using the APEX_WORKFLOWS dictionary view's WORKFLOW_DEF_STATIC_ID column.

your_app_pkg.owners_for_workflow(:APEX$WORKFLOW_ID)

Caution:

Participant usernames are case-sensitive. If :APP_USER returns DAVID, then use DAVID.

25.1.10.3 Passing in Workflow Participants#

A common requirement is to let the page or workflow activity that creates a workflow pass in the list of task assignees as an input parameter.

This is easy to accomplish by defining a parameter – for example named P_ASSIGNEE_LIST – and referencing it in an expression consisting of just the bind variable:
:P_ASSIGNEE_LIST
Of course, you could also use it in a SQL Query, but in that case you need to return one row per case-sensitive username supplied. For example, if the input value is a colon-separated list of usernames, you could use the SQL Query option with the query:
select column_value
from apex_string.split(:P_ASSIGNEE_LIST,':')

Caution:

Participant usernames are case-sensitive. If :APP_USER returns DAVID, then use DAVID.

25.2.1.1 Inquiring About a Procedure#

Submit a procedure inquiry so the clinic can decide whether it can help.

A new patient completes the Procedure Inquiry form shown below on the clinic's website to check if the clinic can accommodate a particular medical procedure. They provide contact information, home address, and insurance provider. They also indicate the procedure they seek. In this example, Danny Deng needs a Colonoscopy and hopes Woods Clinic can help.

Figure 25-74 Woods Clinic New Patient Procedure Inquiry Form

As shown below, after the patient completes this initial registration, they await the clinic's response.

Figure 25-75 After Registering a Procedure Inquiry, a New Patient Awaits a Response

25.2.1.2 Onboarding the Patient Manually#

Review the manual onboarding steps staff follow before approving or rejecting a patient.

The clinic's back-office staff currently follows a manual business process to onboard a new potential patient. The steps involve:
  • Verifying the patient lives within 400km of the clinic
  • Evaluating the insurance reimbursement risk for the procedure they seek by researching past cases
  • Deciding whether to approve the new patient's request
  • If approved, then provisioning an account for them to access the clinic's portal application
  • Periodically checking to see if newly-approved patients logged in to complete their registration

If any of the steps results in a decision to reject the patient's request, a staff member writes an email to the patient apologizing and explaining they need to seek their care at another facility.

25.2.6.1 Applying Activities to Patient Onboarding#

Review the native workflow activities used to automate patient onboarding.

You can create the Patient Onboarding workflow shown below to automate the Woods Clinic's manual business process. The diagram uses the following native activities:
  • 9 Invoke APIs run procedures to evaluate proximity and insurance risk, predict approval, and more
  • 4 Switchs route a patient outside service range, with risky insurance, auto-approved, or rejected
  • 1 Human Task - Create assigns the patient for manual approval if not auto-approved
  • 1 Send E-Mail notifies the patient of a rejection, and
  • 1 Wait pauses until approved patient uses portal to supply proof of insurance and birth date.
  • 2 Workflow Ends terminate the rejected and approved paths separately.
Figure 25-79 Woods Clinic Patient Onboarding Workflow in App Builder

25.2.6.2 Evaluating Spatial Distance with an API#

Use built-in geospatial functions to check whether a patient lives within service range.

As shown below, a Geocoded Address page item in the registration page identifies the longitude and latitude of a new patient's home address. The page saves these coordinates along with the patient's other data in the FRC_PATIENTS table.

Figure 25-80 Geocoding New Patient Address in Registration Page

The Evaluate Proximity activity shown below is an Invoke API that calls the EVALUATE_PROXIMITY PL/SQL package API to determine if the patient's home address is within the clinic's 400km service radius.

Figure 25-81 Invoking a PL/SQL Package API to Evaluate Service Range

The function accepts a patient ID and returns an outcome string of "Within Range" or "Out of Range" that the following In Service Range? switch activity uses to decide which way to flow. As shown below, you configure the invoked function's parameters using the Property Editor after selecting each parameter name under the activity’s Parameters heading in the Workflows tree. Here, the P_PATIENT_ID parameter's value comes from the workflow Additional Data column ID containing the patient identifier associated to the current workflow instance. The Function Result is mapped to the V_RESULT workflow variable whose Result label shows under the Variables heading in the tree.

Figure 25-82 Configuring Patient ID as Value of P_PATIENT_ID PL/SQL Parameter

The code for the invoked function appears below. It returns a VARCHAR2 result of "Within Range" if the distance from the clinic to the patient's home address is less than 400km. Otherwise, it returns "Out of Range". The function is essentially a single SELECT…INTO statement using the SDO_DISTANCE function in the SDO_GEOM package to compute the distance in kilometers between the clinic and the patient's home address.

-- Result = 'Within Range' or 'Out of Range'
function evaluate_proximity(p_patient_id number)
return varchar2
is
    l_result_val varchar2(30);
    /*
     | Assume the clinic is located at:
     | 1244 Cotter Way, Hayward, CA 94541
     */
    c_clinic_lat  constant number := 37.68255;
    c_clinic_long constant number := -122.09026;
begin
    begin
        select case
                when sdo_geom.sdo_distance(
                        sdo_geometry(2001,4326,
                            sdo_point_type(pat.address_longitude,
                                           pat.address_latitude,
                                           null),null,null),
                        sdo_geometry(2001,4326,
                            sdo_point_type(c_clinic_long,
                                           c_clinic_lat,
                                           null),null,null),
                        tol=>1,
                        unit=>'unit=KM'
                    ) > 400 /* Kilometers */
                then 'Out of Range'
                else 'Within Range'
               end
        into l_result_val
        from frc_patients pat
        where pat.id = p_patient_id
          and address_latitude  is not null
          and address_longitude is not null;
    exception
        when no_data_found then
            l_result_val := 'Within Range';
    end;
    return l_result_val;
end; 

25.2.6.3 Switching Path Using a Condition#

Use a Switch activity to decide which path the workflow takes.

The Evaluate Proximity activity configures the V_RESULT workflow variable to store its Function Return value. The subsequent In Service Range? is a Switch activity that as shown below is set to check this V_RESULT variable to decide which path to follow. The Switch activity identifies the source of the conditional decision. Then, each connection originating from that Switch activity configures a condition based on this source. When evaluating the next activity, the APEX engine follows the connection path whose condition evaluates to true.

Figure 25-83 Configuring a Switch Activity to Decide Based on a Workflow Variable

As shown below, the connection named Within Range represents the condition that the Switch source V_RESULT equals the literal value "Within Range". The name of the connection need not coincide with the condition value, but sometimes that makes the diagram the most easy to understand. For example, you could change the connection name to Inside Service Area, as long as the condition remains the same, without affecting the workflow's flow.

Figure 25-84 Assigning Outcome Condition to Connection Originating from a Switch

The following table lists all the available Switch types you can use.

Table 25-6 Switch Decision Types

Scroll horizontally to view the full table
Type Routes Based On Outbound Connections
Check Workflow Variable Distinct values of a variable One connection per unique value. Otherwise evaluated last.
Case Distinct values of an expression Any number of connections, each configured with an Execute Sequence. First in sequence to evaluate to true is used. Sequence Otherwise last if used.
True False Check True or false result of boolean expression One True, one False
If Elsif Else Evaluating connection conditions in execution sequence order Any number of connections, each with a Server-side Condition configured and an Execute Sequence. First in sequence to evaluate to true is used.

25.2.6.4 Predicting Results with Machine Learning#

Use historical data to predict likely outcomes and automate confident decisions.

Historical data can be useful to predict the future. After years of considering patient requests manually for onboarding, Woods Clinic FRC_PATIENTS table holds the institutional knowledge of what patient requests were approved and which were rejected. It potentially holds clues to further automate the patient onboarding business process. You can train a machine learning model then use it to predict patient approvals. If historically, you approved or rejected a situation with high confidence, you can automatically handle similar new cases, leaving only unsure situations to be approved manually.

25.2.6.5 Visualizing Workflow Progress#

Show a workflow instance’s progress in a Workflow Diagram region.

In the Woods Clinic app, you can use a Workflow Diagram region in a page to visualize the progress of a particular instance. For example, imagine your end users asked to quickly understand where each recent inquiry was in the onboarding process. As shown below, you can add an link icon to your Recent Patient Inquiries page to pass the workflow instance ID to a modal page containing a Workflow Diagram region.

Figure 25-89 Linking to a Workflow Diagram Page to Visualize Progress
Given the workflow instance ID value, the Workflow Diagram region displays its progress as shown below. A colored outline highlights the activities and connections Danny Deng's onboarding journey has taken so far. At a glance, it's easy to see that:
  • his home address was within the clinic's service range
  • his insurance company had a reasonably low risk of refusing to pay, and
  • his procedure request didn't qualify to be auto-approved.

So it's clear that Danny Deng's procedure request currently awaits manual approval by back-office staff

Figure 25-90 Visualizing Onboarding Progress of New Patient Danny Deng

25.2.6.6 Awaiting Completion of a Human Task#

Create an approval task and wait for its outcome before the workflow continues.

Your Woods Clinic app uses the approval task definition shown below to track the work item for manual approval of a new patient. Its Actions Source table is FRC_PATIENTS so its Subject can reference patient details using substitutions referencing columns like &FIRST_NAME. and &LAST_NAME. in that table.

Figure 25-91 Task Definition Subject Uses Column Values from the Actions Source Table

As shown below, the Patient Onboarding workflow uses a Human Task - Create activity configured to use this task definition. It passes in the patient ID as the Details Primary Key value when creating the task using the workflow's Additional Data table ID column. In its Result section, the approval Outcome and actual Owner are returned into the workflow variables named TASK_OUTCOME and APPROVER. Since capturing these two values from an approval task is very common, the Workflow Builder creates these two variables automatically when you first add a Human Task - Create activity to the diagram. You can rename them, or configure different variables to receive these values as well.

Figure 25-92 Using Human Task Create Activity to Wait for Approval

Your Patient Onboarding workflow guides Danny Deng's request to manual approval, so, as shown below, back office user David sees Danny's pending approval in his Procedure Approvals task "inbox". You can create this page by picking a Universal Task List in the Create Page Wizard, and using the My Tasks report context. Notice David has used several Smart Filters to narrow the list by Priority, Application, and State. He also typed in a text search of "New Patient" to further filter the list. He's currently sorting the list by Create Date.

Figure 25-93 Unified Task List Showing Patient Onboarding Approval Tasks for DAVID

25.2.6.7 Emailing Users a Rejection#

Notify rejected patients with an email template populated from workflow data.

From the Recent Patient Inquiries page, as shown below, David can see that patient Anya Ableton's request was automatically rejected. The figure shows a Woods Clinic backoffice application modal dialog with a Workflow Diagram region. The dialog title shows "Patient Onboarding Status - Anya Ableton". The workflow diagram is an end-user-friendly view of the same workflow Patient Onboarding diagram from App Builder Workflow Designer. The color and bolder style of certain activity shape outlines and connection arrows help the user understand the progress. In this example, David sees the Risk Level? decision followed the Risky path to the Auto-Reject Patient activity and on to the Notify Rejection activity.

Figure 25-94 Anya Ableton's Procedure Request Was Rejected for Being Too Risky

In the Patient Onboarding workflow, your Notify Rejection activity of type Send E-Mail uses the Patient Rejected email template above, and passes &FIRST_NAME. and &DESCRIPTION. as the values of the template's two placeholders. Notice the From field references the &SENDER_EMAIL. column from the activity's Additional Data query. The value of &DESCRIPTION. used for the placeholder also comes from that query.

Figure 25-95 Configuring Send Email Activity and Email Template Placeholders

25.2.6.8 Provisioning a New User Account#

Provision a portal user account when the workflow approves a patient request.

Consulting the Recent Patient Inquiries page again, as shown below, David can see that patient Cristina Cordero's request was automatically approved, her portal account got provisioned, and the workflow is waiting for her to complete her registration.

Figure 25-100 Cristina Cordero's Request Was Auto-Approved and Is Now Waiting

25.2.6.9 Waiting Until Patient Completes Profile#

Wait for the approved patient to complete portal registration before the workflow continues.

As shown below, patient Cristina Cordero's request is waiting for her to provide her date of birth and proof of insurance on the portal. The diagram show the activity path the workflow took to get to this moment:
  • Evaluate Proximity and In Service Range? determined she was within range
  • Evaluate Insurance Risk and Risk Level? determined her insurance was low risk
  • Predict Approval and Auto-Approve? approved her based on historical data
  • Provision Account set up her account and mailed her initial credentials for registration.

Now it's clear in the diagram that the Patient Onboarding workflow for Cristina Cordero is waiting for her to complete her registration, before proceeding on to the last leg of the "journey": marking her registration as completed.

Figure 25-103 Cristina Cordero's Request Was Auto-Approved and Is Now Waiting

25.3.5.1 Launching Woods Clinic Application#

Launch the Woods Clinic application like a native app with persistent authentication.

As shown below, earlier in the week when David logged into the Woods Clinic application, he checked the Remember me checkbox on the login page. He avoids having to type in his password every time he launches the app for the next 30 days.

Tip:

You enable this feature in APEX instance administration in the Manage Instance > Security settings. Set Allow Persistent Auth to Yes and adjust the Persistent Authentication Lifetime Days setting from the default 30 days if needed.

Figure 25-114 On David's Last Login, He Ticked the Remember Me Checkbox

As shown below, today David starts his day by launching the Woods Clinic app from the dock on his iMac. You enable the Progressive Web App (PWA) installable switch in your application definition, so with one click David added the app to his desktop to launch like other native Mac apps. He can do the same on his iPad and iPhone to launch the app with a tap on his home screen. When combined with the persistent authentication feature, this lets end-users immediately get to work just like they do with other apps they use everyday like Outlook, Slack, and more.

Figure 25-115 David Launches Woods Clinic APEX App from His Dock

Your Woods Clinic app opens in its own window like a native desktop application. As shown below, a PWA application has no browser address bar. Using a List region associated with your Navigation Menu shared component, the home page shows a tile for each screen David has access to as clinic staff member.

Tip:

Using built-in access control list security enforcement, the same home page shows a totally different set of tiles when a patient like Danny Deng logs in.

Figure 25-116 Home Page for Woods Clinic App Launched as a Native App

From any screen in the app, David can click the "hamburger" icon with three-horizontal lines in the upper left corner. This pops open the collapsible side menu. As shown below, he uses it to navigate to the Scheduling page to see if any medical procedures await him there.

Figure 25-117 Navigating to Scheduling Page from a Different Screen

25.3.5.2 Scheduling a Medical Procedure#

Schedule a medical procedure and let the workflow notify the patient.

David visits the Scheduling screen and sees that Danny Deng's medical procedure needs a date and doctor assigned. As shown below, he clicks on Danny's card to open his procedure details and schedules the exam for July 28th at 11:30am with Dr. Sandy Osborn.

Figure 25-118 David Schedules Danny Deng's Procedure

When David confirms the scheduling details, as shown below, Danny's procedure appears on the calendar and no longer shows in the pending list.

Figure 25-119 Scheduled Medical Procedures Appear on the Calendar

Checking the Procedure Radar page, as shown below, David now sees Danny Deng's procedure in the Scheduled column on the Kanban board.

Figure 25-120 Office Staff See Danny Deng's Procedure Scheduled on Kanban Board

In the background, your Procedure Lifecycle workflow reacts to the change in status to "Scheduled". It automates notifying Danny Deng of the details. Later in the morning, when David visits the Pending Procedures page, as shown below, he clicks on the workflow icon next to Danny's procedure in the list.

Figure 25-121 Checking a Workflow from Pending Procedures List

The Procedure Lifecycle Status page for Danny Deng appears. It gives David a progress report at a glance. As shown below, he observes colored highlights on the workflow diagram that confirm it sent the patient email and push notifications.

Tip:

APEX only sends the push notification if Danny Deng opted-in to receiving these mobile notifications after installing the Woods Clinic app on his smartphone, tablet, or desktop computer. If he hasn't explicitly allowed them, APEX silently skips your workflow's attempt to deliver the message.

Figure 25-122 Confirming Danny Deng Was Notified of Appointment

25.3.5.3 Receiving Medical Procedure Schedule#

Receive the scheduled procedure details by email.

Danny receives the email shown below and he makes a note of the date and time. As the message advises, he'll plan to show up 15 minutes early to register on the day.

Figure 25-123 Danny Deng Gets Email with His Appointment Information

25.3.5.4 Discharging Patient After Procedure#

Complete a patient procedure and generate draft discharge instructions with AI.

On the day of the appointment, when the procedure is over, Danny stops at the front desk before leaving. As shown below, David uses the three-vertical-dots dropdown menu on Danny's procedure record to open the Discharge page.

Figure 25-124 Discharging the Patient and Completing the Procedure

To send Danny on his way with some advice, on the Procedure Info tab, David ticks some common recommendations like sleeping at least 8 hours and to stay hydrated. He recommends Paracetamol if Danny experiences any discomfort.

Figure 25-125 Entering Patient Discharge Instructions

On the Discharge Letter tab, David clicks the (Generate Initial Letter) button to get a head start on writing Danny's discharge letter. David adjusts the initial letter as needed, prints out a copy for Danny, and clicks (Apply Changes) to complete the discharge step.

Tip:

Your page's (Generate Initial Letter) button uses a dynamic action event handler on the button click to run a native Generate Text with AI dynamic action step. Its related AI Configuration contains the system prompt and RAG source to use a Generative AI Service to prepare a draft of the discharge letter with Danny's procedure information and David's discharge recommendations included. This productivity idea has proven to be a big time saver and is a real hit with staff.

Figure 25-126 Preparing Discharge Letter Using Generative AI

25.3.5.5 Preparing Pixel-Perfect PDF Invoice#

Generate a pixel-perfect PDF invoice in the background with a report query and layout.

By using a report query and report layout, your Procedure Lifecycle workflow engages a remote printing service to generate a pixel-perfect invoice document on-demand, in the background.

25.3.5.6 Reviewing Invoice with Insurance Provider#

Review the prepared invoice and enter the insurance coverage amount.

From the Review Invoices page, David sees the "Prepared" status badge on the invoice so he knows it's ready to review. He uses the three-vertical-dots dropdown menu to review the invoice details.

Figure 25-133 Opening the Prepared Invoice Details

On the Document tab of the Review Invoice dialog that appears, David can inspect the PDF document the patient will eventually get. But for the moment, notice that it starts out with zero (0) dollars in the Covered Amount field.

Figure 25-134 Reviewing Pixel-Perfect PDF Invoice

David calls Danny Deng's insurance provider and learns that they will reimburse 5000 dollars of the medical procedure, so on the Overview tab David enters that amount in the Insurance Covers field. Your page uses a dynamic action event handler on the value change of this field to recalculate the Patient Pays field as shown below.

Figure 25-135 Entering Insurance Reimbursement Amount

25.3.5.7 Regenerating Invoice in Background#

Regenerate an invoice in the background while preventing duplicate runs for the same procedure.

After David enters the definitive amount Danny Deng's insurance provider will reimburse, he can regenerate the invoice to reflect the updated value. As shown below, he does this using the Regenerate option on the invoice three-vertical-dots dropdown menu.

Figure 25-136 Regenerating Final Invoice After Entering Insurance Payment

Since the invoice generation happens in the background, David is free to continue using the Woods Clinic app in the meantime. For example, as shown below, he visits the Overview page for a glance at the dashboard of four charts showing various clinic trends.

Figure 25-137 Reviewing Dashboard While Invoice Regenerates in Background

Your Review Invoices page makes quick work of offloading the invoice generation to the background using an Execution Chain page process type. It groups child processes, and they automatically run asynchronously when you enable its Run in Background switch. You enable its Serialize switch as well, and configure a Context Value Item. This combination ensures the APEX engine only lets one invoice generation per medical procedure ID happen simultaneously. The Generate Invoice child process invokes the same GENERATE_INVOICE procedure the workflow uses to prepare the original invoice draft.

Figure 25-138 Execution Chain Simplifies Regenerating Invoice in the Background

25.3.5.8 Invoicing Patient for the Balance#

Mark the invoice complete so the workflow emails it to the patient and advances the procedure lifecycle.

When David sees the regenerated invoice badge shows Prepared again, he reviews it to make sure the Covered Amount and patient's Balance Due look right. He confirms the insurance company's 5000 payment shows as expected, and the patient's remaining balance to pay is correct.

Figure 25-139 Reviewing Regenerated Invoice Before Sending to Patient

David marks the invoice as ready to send to the patient by choosing Invoice Complete from the three-vertical-dots dropdown menu as shown below.

Figure 25-140 David Marks Danny Deng's Invoice as Ready to Send to Patient

When David reviews Danny Deng's procedure progress later, he notices, as shown below, that your workflow reacted to the completion of the invoice by emailing it to the patient.

Figure 25-141 Danny Deng's Procedure Lifecycle Workflow Shows Invoice Emailed

He also sees Danny Deng's procedure shows in the Invoiced column in the Kanban board as shown below.

Figure 25-142 Progress of Danny Deng's Procedure on Kanban Board

25.3.5.9 Acknowledging Receipt of Payment#

Record the patient’s payment so the procedure reaches its final status.

Patient Danny Deng gets the email, and reviews the invoice as shown below. He then calls Woods Clinic to pay the balance with his credit card.

Figure 25-143 Danny Deng Receives Invoice for His Medical Procedure

David accepts Danny's payment on the phone, and, as shown below, he marks the invoice as settled using the Invoice Paid option on the three-vertical-dots dropdown menu.

Figure 25-144 David Marks Danny Deng's Medical Procedure Invoice as Paid

This changes the status of Danny Deng's medical procedure row to "Paid" and, as shown below, David sees Danny's has moved off the Kanban board.

Figure 25-145 Kanban No Longer Shows Danny Deng's Procedure After Invoice is Paid

25.3.5.10 Confirming the Lifecycle is Complete#

Confirm the paid procedure completed its workflow and full business lifecycle.

Reviewing patient procedures since mid-month on the All Procedures page, as shown below, David confirms that Danny Deng's medical procedure shows as Paid. He clicks the workflow icon next to the procedure to review the Procedure Lifecycle workflow progress one last time.

Figure 25-146 David Confirms Danny Deng's Medical Procedure is Paid

David sees that all lifecycle steps are complete for Danny Deng's medical procedure, so he moves on to help other patients. The Woods Clinic staff gives you positive feedback on how much more effective the clinic is running now that you've automated so many of the manual steps in their patient onboarding and medical procedure business processes.

Figure 25-147 David Confirms Danny Deng's Procedure Lifecycle is Done

25.3.6.1 Understanding Lifecycle Event Strategy#

Log procedure lifecycle events in a trigger and process them on a schedule with an automation.

To notify the workflow of data changes, you adopt a strategy shown below that decouples the lifecycle event for a status change from the work it entails. This separation keeps the user experience fast by offloading work to the background. Using an AFTER UPDATE FOR EACH ROW trigger on the FRC_PATIENT_PROCEDURES table, you call the ADD_EVENT procedure in the FRC_PROCEDURE_LIFECYCLE application package to register the event. That procedure inserts a row in the FRC_PATIENT_PROCEDURE_EVENTS table. The row contains the primary key of the patient procedure, and an event type indicating whether the procedure is new, or whether its schedule or status changed.

On a recurring schedule, an Automation shared component "wakes up" and queries the list of events to process in the current run using the EVENTS_LIST function in the same package. For each lifecycle event to process, the automation's action calls the PROCESS_EVENT procedure. The package calls CONTINUE_ACTIVITY in the APEX_WORKFLOW package, passing the event type value back to the workflow.

Figure 25-148 Strategy for Using Workflow to Orchestrate Patient Procedure Lifecycle

25.3.6.2 Simplifying Event Management#

Define a package API that lets the trigger add events and the automation process them.

The FRC_PROCEDURE_LIFECYCLE package offers a simple API for managing lifecycle events. Its package specification shown below has procedures to add and process an event, and a function that returns the list of events.

package frc_procedure_lifecycle is
    type t_lifecycle_event is record (id frc_patient_procedure_events.id%type);
    type t_lifecycle_events is table of t_lifecycle_event;
    ------------------------------------------------------------------
    -- Add procedure lifecycle event to table processed by automation
    ------------------------------------------------------------------
    procedure add_event(
        p_procedure_id in number,
        p_type         in varchar2);
    ------------------------------------------------------------------
    -- Process procedure lifecycle event (called by automation)
    ------------------------------------------------------------------
    procedure process_event(
        p_event_id in number);
    ------------------------------------------------------------------
    -- Return list of event ids to process (used in automation query)
    ------------------------------------------------------------------
    function event_list
    return t_lifecycle_events
    pipelined;
end frc_procedure_lifecycle;

The ADD_EVENT procedure simply inserts a row into the FRC_PATIENT_PROCEDURE_EVENTS table. The table includes a primary key ID that is defaulted when null from an identity sequence, and a CREATED timestamp column that defaults to SYSTIMESTAMP. So here you only need to insert the patient procedure ID and the event type passed in (e.g. NEW, STATUS, or SCHEDULE).

-- external in package frc_procedure_lifecycle
procedure add_event(
    p_procedure_id in number,
    p_type         in varchar2)
is
begin
    insert into frc_patient_procedure_events(procedure_id, event_type)
    values (p_procedure_id, p_type);
end add_event;

The PROCESS_EVENT accepts the unique ID of a lifecycle event and signals a status or schedule change to a medical procedure's workflow. It calls the NOTIFY_DATA_CHANGED helper procedure to do so. If the process is successful, it deletes the event row it just processed from the FRC_PATIENT_PROCEDURE_LIFECYCLE table.

-- external in package frc_procedure_lifecycle
procedure process_event(
    p_event_id in number)
is
    l_success boolean := false;
begin
    for j in (select id, procedure_id, event_type
                from frc_patient_procedure_events
               where id = p_event_id )
    loop
        if j.event_type in ('STATUS','SCHEDULE') then
            l_success := notify_data_changed(j.procedure_id,j.event_type);
            if l_success then
                delete from frc_patient_procedure_events
                where id = p_event_id;
            end if;
        end if;
    end loop;
end process_event;

The EVENT_LIST function returns a table of T_LIFECYCLE_EVENT records. Each record contains a single ID field. Notice the PIPELINED modifier and corresponding PIPE ROW statement inside the loop. This combination defines a function whose result you can use in a SELECT statement as a row source by "wrapping" the function call with the TABLE() operator.

function event_list
return t_lifecycle_events
pipelined
is
begin
    for j in (select id
                from (select id,
                             row_number() over
                             (partition by procedure_id
                              order by created) as rn
                      from frc_patient_procedure_events
                  )
                 where rn = 1)
    loop
        pipe row (t_lifecycle_event(j.id));
    end loop;
exception
    when no_data_needed then
        null;
end event_list; 

Tip:

EVENT_LIST's cursor for loop uses the ROW_NUMBER() analytic function – aliased to RN – along with the WHERE RN = 1 clause to ensure only a single event per distinct PROCEDURE_ID value is returned. Using the ORDER BY CREATED in the windowing clause guarantees that the single event returned for each procedure ID is the one that occurred earliest in time. This simplifies the event processing so that only one event per patient procedure ID row is processed in a given automation job run.

25.3.6.3 Adding Events Using a Trigger#

Use a trigger to log status or schedule changes as lifecycle events.

When an FRC_PATIENT_PROCEDURES row is updated, it compares the :NEW.STATUS and :OLD.STATUS to decide if the status has changed. If so, it uses "STATUS" for the event type. If instead the procedure's date, doctor, or duration in hours changes, then the event type is "SCHEDULE". If either case occurs, then it calls ADD_EVENT in the FRC_PROCEDURE_LIFECYCLE package to do that.

trigger frc_patient_procedures_au
after update on frc_patient_procedures
for each row
declare
    l_event_type varchar2(80);
begin
    if :new.status != :old.status then
        l_event_type := 'STATUS';
    elsif    :new.procedure_date != :old.procedure_date
          or :new.doctor         != :old.doctor
          or :new.duration_hours != :old.duration_hours then
        l_event_type := 'SCHEDULE';
    end if;
    if l_event_type is not null then
        frc_procedure_lifecycle.add_event(:new.id, l_event_type);
    end if;
end;

25.3.6.4 Processing Events with an Automation#

Use an automation to process queued lifecycle events on a schedule.

The Handle Patient Lifecycle Event automation shown below is a shared component that defines a recurring background job. It runs periodically based on the Schedule Expression you create with the Schedule Builder dialog, or type in directly. This particular job runs in the background every two minutes.

The Query choice for Actions Initiated On means the automation's actions execute once for each row in the query you define on the Source tab.

Figure 25-149 Lifecycle Events Handler Automation – Settings
On the Source tab you specify the simple query that retrieves the patient procedure lifecycle events to process during the current automation job run. The query uses the TABLE() operator to select the event ID values to process from the EVENT_LIST function in the FRC_PROCEDURE_LIFECYCLE package. The simple query is:
select id
  from table(frc_procedure_lifecycle.event_list)
Figure 25-150 Lifecycle Events Handler Automation – Source
An automation can have multiple actions, each of which can contain a server-side condition to control whether it executes or not for any given row. However, in this simple use case, you only need a single Process Lifecycle Event action shown below. Like any action, it can reference the source query column values for the current row as bind variables. This action uses the Execute Code type to run the single line below, passing the value of the current event ID:
frc_procedure_lifecycle.process_event(:ID);
Figure 25-151 Lifecycle Events Handler Automation – Actions

On the Action Execution tab, as shown below, you configure error handling and commit behavior for the job. By committing after each event row is processed, you ensure that a later event's error does not rollback the successful processing of earlier events.

Figure 25-152 Lifecycle Events Handler Automation – Action Execution

25.3.6.5 Continuing a Waiting Lifecycle Workflow#

Continue the waiting workflow activity and pass the event type into a workflow variable.

When the PROCESS_EVENT procedure in the FRC_PROCEDURE_LIFECYCLE package executes, it calls NOTIFY_DATA_CHANGED shown below. This calls a local helper function WORKFLOW_ID_FOR_WAITING_ACTIVITY to look up the procedure_lifecycle workflow instance ID for the current medical procedure row.

In the process, it ensures the workflow is active and has an activity with Static ID status_or_date_changed that is waiting to continue. If such a workflow instance is found, it passes its ID to CONTINUE_ACTIVITY in the APEX_WORKFLOW package to signal the waiting activity to continue.

It passes a map of name/value pairs back to the activity including the key V_WHAT_CHANGED whose value is the event type passed in. Any workflow variable whose name matches a map key value is automatically assigned the corresponding value in the process. Consequently, the V_WHAT_CHANGED workflow variable then contains the event type and the workflow continues to the next activity.

-- internal in package frc_procedure_lifecycle
------------------------------------------------------------------
-- Notify an instance of the procedure_lifecycle workflow associated
-- with the supplied patient procedure id that
------------------------------------------------------------------
function notify_data_changed(
    p_procedure_id in number,
    p_event_type   in varchar2)
    return            boolean
is
    l_workflow_id number := workflow_id_for_waiting_activity(
                                'procedure_lifecycle',
                                'status_or_date_changed',
                                p_procedure_id);
begin
    if l_workflow_id is not null then
        apex_workflow.continue_activity(
            p_instance_id     => l_workflow_id,
            p_static_id       => 'status_or_date_changed',
            p_activity_params => apex_global_application.vc_map(
                                    'V_WHAT_CHANGED' => p_event_type));
        return true;
    end if;
    return false;
end notify_data_changed;

The WORKFLOW_ID_FOR_WAITING_ACTIVITY helper function ensures the procedure lifecycle workflow for the Details Primary Key value passed in is active and that the desired activity is waiting. If both of these conditions hold, then it returns the ID of that workflow instance. Otherwise, it returns null.

-- internal in package frc_procedure_lifecycle
------------------------------------------------------------------
-- Return the active workflow id of static def id type passed in
-- associated with provided PK and having a waiting activity with
-- supplied static id that can be continued. Otherwise, null
------------------------------------------------------------------
function workflow_id_for_waiting_activity(
    p_workflow_static_id         in varchar2,
    p_waiting_activity_static_id in varchar2,
    p_details_pk                 in varchar2)
    return                          number
is
    l_ret number;
begin
    -- Only return the workflow id if the workflow is in ACTIVE
    -- state and the indicated continue activity is in WAITING state
    for wf in ( select workflow_id
                  from apex_workflow_activities ac
                  join apex_workflows wf using (workflow_id)
                 where wf.detail_pk              = p_details_pk
                   and wf.workflow_def_static_id = p_workflow_static_id
                   and ac.static_id              = p_waiting_activity_static_id
                   and ac.state                  = 'WAITING'
                   and wf.state_code             = 'ACTIVE')
    loop
        l_ret := wf.workflow_id;
    end loop;
    if l_ret is null then
        apex_debug.warn('workflow_id_for_waiting_activity: No active workflow (%s)'
                        |' with activity (%s) waiting for pk (%s)',
            p_workflow_static_id,
            p_waiting_activity_static_id,
            p_workflow_static_id);
    end if;
    return l_ret;
end workflow_id_for_waiting_activity;

25.4.3.1 Querying Recent Debug Messages#

Query recent debug messages to find workflow errors and related trace details.

The APEX engine logs unexpected errors. Query them using the APEX_DEBUG_MESSAGES view. Run the query below in the SQL Workshop > SQL Commands window to see the most recent debug messages in your workspace. These may include messages related to business process logic, or messages from page processing.

select apex_util.get_since(message_timestamp) as when, message
  from apex_debug_messages
 order by message_timestamp desc

A more detailed query against this view to see only workflow related messages is the following. Notice it also includes the WORKFLOW_INSTANCE_ID that encountered the problem:

select message_timestamp,
       elapsed_time,
       execution_time,
       message,
       page_id,
       workflow_instance_id,
       session_id,
       message_level,
       trim(replace(call_stack,'%')) as call_stack
 from apex_debug_messages
where application_id = :APP_ID
and apex_user = 'nobody'
and call_stack like '%\%WORKFLOW.%' escape '\'
and message_level < 9
order by message_timestamp desc

25.4.3.2 Enabling Debug Tracing Proactively#

Enable debug tracing from the Developer Toolbar or directly on a workflow version.

Using the Developer Toolbar's Debug > Current Debug Level menu, as shown below, you can also enable debug tracing proactively. Start with the default Info level of detail. Other levels are increasingly verbose, but typically not required to triage most problems. This refreshes the current page and keeps your session in debug tracing mode until you turn if off by using the same menu.

Figure 25-155 Enabling Info-Level Debug Tracing with the Developer Toolbar

Since your workflows run asynchronously in the background, the error may occur without any page interaction. In this case, as shown below, you can enable the debug tracing level on a workflow version in the Advanced section of the Property Editor.

Figure 25-156 Enabling Debug Tracing for Background Workflow Execution

25.4.3.3 Viewing Debug Trace Information#

View workflow debug traces by clearing page filters in the Debug Message Data window.

Use the Developer Toolbar's Debug > View Debug menu option to open the Debug Message Data window shown below. By default, the Interactive Report showing debug trace requests is filtered by the current application id and the current page. Since workflow activities execute in the background, they are not associated to a page. They also have no logged-in username. To see the workflow debug messages, as shown below, either:

  • Disable the Page filter by unchecking its checkbox, or
  • Clear the Page filter by clicking its "x" icon.

Then workflow messages for user nobody with no associated page appear.

Figure 25-157 Disabling Page Filter to View Recent Workflow Debug Requests
Clicking on a view identifier that occurred around the time the error happened, you can view all debug messages related to that APEX engine unit of work. As shown below, while investigating different approaches for notifying your workflow of data changes, one earlier approach attempted to start a workflow from an AFTER INSERT trigger. The call to APEX_WORKFLOW.START_WORKFLOW encountered an ORA-04091 exception while running its Additional Details query. As shown below, the debug log includes:
  • The query executing when the error occurred
  • The ORA-04091 exception with call stack
  • A related mention of:
    start_workflow: ORA-20999: Additional data row … not found

The debug trace log usually includes enough information to provide clues on where the issue is happening.

Tip:

The ORA-04091 error is known as a "Mutating Table" exception. It occurs when a database trigger initiates logic that queries the table that is in the process of being changed.

Figure 25-158 Examining Debug Exception Call Stack in Debug Viewer

25.4.3.4 Adding Custom Debug Messages#

Add custom debug messages with APEX_DEBUG.INFO when needed.

When natively available debug information does not suffice, you can add your own by calling the INFO procedure in the APEX_DEBUG package. Use it in an Execute Code type:
  • page process in a page,
  • action in a task definition or automation, or
  • activity in a workflow.

To log a message, use INFO as shown below. Consider starting your debug message with a prefix like ### that's easy to search for in the debug trace log. By surrounding the %s token with parentheses, a null value is easier to see.

apex_debug.info('### somevar=(%s), someexpr=(%s), anotherexpr=(%s)',
                :SOMEVAR,
                some_expression,
                another_expression);
The message can contain up to ten %s replacement tokens that are substituted sequentially by the corresponding additional values you pass as additional parameters. Alternatively the message can contain %0, %1, …, %9 to reference the additional parameter values. For example, the following produces the same message as above:
apex_debug.info('### somevar=(%2), someexpr=(%0), anotherexpr=(%1)',
                some_expression,
                another_expression,
                :SOMEVAR);

The message, including substituted values, can be up to 32727 characters. However, messages exceeding 4000 characters appear "chunked" in the debug viewer. By default, the parameter values are truncated at 1000 characters each. Pass the optional p_max_length parameter to specify longer values.

Tip:

If your message substitution token appear to be ignored, check the message string more closely. It's common to accidentally type % alone instead of the required %s or %0, %1, etc.

25.1.9.1.1 Deciding if Task Needs an Outcome#

Choose an action task to track completion or an approval task to capture an outcome.

When you create a Task Definition shared component, as shown below, set the Type to Action Task to track only that an assignee has completed the task. Choose Approval Task instead to capture the actual owner's APPROVED or REJECTED outcome. If you realize later that you picked the wrong task type, then you'll need to try again by creating a new task definition. You can only set this property at create time.

Figure 25-23 Choosing an Action Task or Approval Task at Create Time

The task type determines the buttons end users will see in the Task List and Task Details pages when your app assigns them tasks to work on. As shown below, by default approval tasks display quick action buttons to approve or reject the task, while action tasks do not.

Figure 25-24 Approval Tasks Show Quick Action Buttons in the Task List

Similarly, while the default task detail page for an approval task shows (Approve) and (Reject) buttons, as shown below, the generated page for an action task has just a (Complete) button instead.

Figure 25-25 Action Tasks Have a (Complete) Button in Task Details Page

25.1.9.1.2 Associating a Table with a Task#

Associate a task with a table or query so task logic can reference related row data.

An end-user task often relates to a business entity in your application data model. A task definition models that work item, and a task instance represents one example of it for a particular row in that business entity's table. Task assignment and your actions that react to task status changes tend to reference or modify data in this related business entity row.

APEX simplifies this common usage pattern by letting you:
  • configure the Actions Source table and primary key column in a task definition, and
  • supply a Details Primary Key value of a corresponding row when creating a task instance.

Then, wherever your app creates a new task instance, remember to supply the primary key value of the business entity row to which the new task relates.

This brief investment of time delivers a strong productivity payoff. Without writing any code or queries, you can reference the business entity row's details anywhere in the task definition using any column name. Just use this syntax:
  • &NAME. in text substitutions, and
  • :NAME in code and queries.

For example if your Actions Source table's primary key column is ID, then you can reference :ID in action PL/SQL code or participant assignment expressions or queries. In email notifications use the substitution &ID. instead.

Tip:

For even more powerful automatic data access in your task definition, change the Actions Source type from Table to SQL Query. Your query typically includes a primary table and its primary key column, and can optionally join in any other related data. It must return exactly one row. If your task accepts a Details Primary Key, your WHERE clause can reference its value using the :APEX$TASK_PK bind variable. Then throughout the workflow you can use &NAME. or :NAME notation to reference the value of any column in your Actions Source query's SELECT list.

25.1.9.1.3 Accepting Input Values in Parameters#

Use task parameters for values the task cannot get from its Actions Source row.

Use the pre-defined APEX$TASK_PK parameter to reference the value of the Details Primary Key passed in when a task instance is created. For many use cases, it is the only parameter value you need. Assuming you have also configured the task definition's Actions Source table or query, this combination can often provide all necessary context information. However, if there are additional values unique to the pending work item that are not (yet) available as Actions Source column values, you can define explicit task parameters to accept them when the task is created.

Tip:

You can optionally make task parameters updatable. These are useful to model additional data that the task assignee must provide as part of reviewing or approving the task.

When defining a parameter, you provide a user-friendly label that can be in mixed case and include spaces. You also provide an alphanumeric static ID. You can reference any parameter's value throughout the task definition using the &STATIC_ID. or :STATIC_ID notation depending on the context. The APEX engine manages task parameter storage and makes them queryable in a generic way through the APEX_TASK_PARAMETERS dictionary view.

A simple example of a task parameter for a Salary Change approval task is the New Proposed Salary, with a Static ID of P_PROPOSED_SALARY. Until the change of salary is approved, this value only exists as part of the task instance for approvers to consider. If one of them approves the task, then a task action you define on the Completed event with Approved outcome can update the SAL column in the EMP table for the employee to the new, approved value. Assuming you configured EMP as the Actions Source table for this task, and passed in the employee's EMPNO value as the Details Primary Key when creating the task, your action code can perform this update using a simple statement like:
update emp
   set sal = :P_PROPOSED_SAL
  where empno = :EMPNO;

Tip:

Imagine you find your task definition is not logically related to any particular table in your application data model, and that you are compelled to create many parameters to capture input data for the task. This can be a sign your application data model needs a new table to track the task in question. Consider defining a new table to represent the particular request, with columns for all the data you found yourself defining task parameters for. Should that make sense for your use case, then associate this new table with your task definition as its Actions Source. After doing that, the built-in APEX$TASK_PK parameter may be the only parameter you need. Your page or workflow inserts a row into this new table, then passes its primary key to the task flow as its Details Primary Key value. A follow-on benefit is that it is simpler to query all of the data related to the task from other parts of your application.

25.1.9.1.4 Determining Task Participants#

Assign potential owners and business administrators using case-sensitive usernames.

A task participant is either a potential owner or business administrator. A potential owner is a user you expect to perform the work the task represents, while a business administrator can help address a problem relating to the task.

For example, if user DAVID is the potential owner of the New Patient approval task, he sees it in his My Tasks list. He's also the one who can click (Approve) or (Reject). If user CARLA is the task's business administrator, she sees it in her Admin Tasks list. She can reassign the task to another colleague if a response is due today but DAVID is unexpectedly absent. CARLA the business administrator can also adjust a task's due date.

Caution:

Participant usernames are case-sensitive. User DAVID can login with the username david. However, if you assign user david to a task, David does not see it in his task list because david does not match the DAVID value the APEX engine returns for the currently logged-in username (APP_USER). If :APP_USER returns DAVID, then use DAVID as the participant username.

25.1.9.1.5 Understanding Task States#

Understand task states and the actions that move a task through its lifecycle.

Every task instance has a state. It reflects the status of the task as it progresses through its lifecycle from creation to closure. Each state has a corresponding state code. The state is a mixed-case string like Information Requested, while the state code is uppercase and contains no spaces like INFO_REQUESTED. With the exception of this example, all other state codes are the uppercase version of the corresponding state name.

When you create a task, as shown in the diagram below, it starts in the Unassigned state when it has multiple potential owners, or the Assigned state when there is just one. The arrows in the diagram indicate the action that changes the state of a task instance. Once a task is Assigned, if it's an action task, the actual owner can complete it. For an approval task, they can approve or reject it.

Before they take action on the task, the actual owner can request additional information from the initiator of the task, which makes it appear in the initiator's task list until they supply the requested information. Users can comment on a task at any time, which doesn't change its state. That's why the Comment action is not reflected in the diagram.

A task's business administrator can cancel it at any time, add a new participant, and delegate it to a different actual owner. They can also renew a task that has expired or encountered an error in order to retry the lifecycle.

The flow diagram depicts the states of the task lifecycle and the actions that change state. It starts with Unassigned, progresses to Assigned, and continues to Completed. Optionally, the task can be Cancelled, Errored, Expired, or have additional Information Requested.

Figure 25-27 Task Instance Lifecycle: States and Actions that Change State

25.1.9.1.6 Creating Task "Inbox" Page for Users#

Create a Unified Task List page that shows users the tasks they need to act on.

Choose Unified Task List in the Create Page Wizard to add a task "inbox" page for end users.

25.1.9.1.7 Collecting Data Using a Human Task#

Create a human task from a workflow to collect user input before the flow continues.

Use a Human Task - Create activity in a workflow to create an instance of a work item to be completed. A task definition formalizes the work item by configuring potential owners, parameters, and actions that can react to task lifecycle events. The workflow waits until the task completes its lifecycle before continuing, and can return the task id, the username of the actual owner, and the outcome back into workflow variables.

25.1.9.1.8 Using and Tuning the Task Details Page#

Explore how users work with a Task Details page and how developers can tune it.

User LUCY starts a Collect Two Numbers workflow, passing in a Purpose parameter value of "Testing Purposes" and a Requested User value of LEO. Both users participate in the task lifecycle using a combination of:
  • "Requested Tasks" Unified Task List page with Report Context "Initiated by Me"
  • "My Tasks" Unified Task List page with Report Context "My Tasks"
  • "Task Details" page associated with the Provide Two Random Numbers task definition.
  • "Initiated Workflows" Workflow Console page with Report Context "Initiated by Me"

25.1.9.1.9 Letting an Initiator Approve a Task#

Enable initiators to approve their own tasks when the business process requires it.

By default, a user who initiates an approval task isn't allowed to approve their own request. Even if their username is among the participants as a potential owner, the APEX engine filters out the initiator's username to enforce this common business practice. However, if you need to allow a requester to approve their own task to meet a business requirement, then enable the approval task definition's Initiator Can Complete switch to allow it.

25.1.9.1.10 Handling Time-Sensitive Tasks#

Configure due dates and expiration behavior for time-sensitive tasks.

By default, a task is open-ended. Potential owners get to it when they can. When a task is time-sensitive, you an assign it a due date. By choosing an appropriate Due On Type, you set the due date using:
  • A time interval relative to the creation date – e.g. 2 days from now
  • An exact date and time computed using an expression, function body, or SQL Query, or
  • The first occurrence of a DBMS_SCHEDULER expression – e.g. "weekly on Tuesdays at 9am"

When tasks have a due date, they display additional information in the Unified Task List "inbox" page and users can sort by the due date to work on the most time-critical tasks first. At any time, a task's business administrator can change the due date if necessary.

Once a task goes beyond its due date, end users can see that it's overdue in their task list, but by default nothing else automatic happens. To automatically handle an overdue task, configure the Expiration Behavior to one of the following optional strategies:
  • Expire – Close the task with the status of Expired, or
  • Renew – Close the task as Expired, then create a new "renewal" task if it has not yet exceeded your maximum renewal count.

When using one of these expiration behaviors, you can notify assignees about the impending deadline using a task action on the Before Expire event. You can declaratively send a reminder email or push notification, or execute custom code. When it finally expires, you can react to that with an action on the Expired event.

If you opt for the creation of a renewal task, know that it is a new task instance. It is linked to the previous now-expired task. When viewing a renewal task in a task details page, the end-user can optionally view the history of the complete "task renewal chain" if needed. Your dynamic participant assignment logic can reference the value of :APEX$TASK_RENEWAL_COUNT and :APEX$MAX_RENEWAL_COUNT to decide whether to assign the renewal task in a different way.

25.1.9.1.11 Reacting to Task Change Events#

Use task actions to react to lifecycle events while a workflow waits.

Each Human Task - Create activity in a workflow creates a task instance, then waits for that task instance to complete its lifecycle before continuing to the next activity in the flow. Define task actions to react to changes in the task instance's state during a task's lifecycle.

25.1.9.2.1 Enabling Deep Linking to a Specific Page#

Enable Deep Linking so notification links can open a specific application page.

Normally, a link to your application navigates the user to the application home page after login. If you want the user to navigate to a specific page by clicking on a link, as shown below, you need to enable Deep Linking under Shared Components > Security Attributes for your application.

Figure 25-61 Enabling Deep Linking so User's Click Can Directly Link to Page

25.1.9.2.2 Generating Page URL for Notification#

Generate an absolute page URL for email or push notification links.

After enabling deep linking, use a function like the one below to generate a link to the page for the user to provide additional details. The GET_URL function in the APEX_PAGE package returns a relative URL by default. This means the https://example.com part of the URL is omitted. The browser interprets such a link relative to the page in which you embed it. Relative URLs are fine in an application page, but when generating a page link for an email or push notification, use an absolute URL instead. The user can click on this complete URL from any context.

Notice the ABSOLUTE_PAGE_URL function uses v('APEX$WORKFLOW_ID'). In a workflow activity context, this variable returns the workflow instance ID. The function assumes the target page number n has two hidden page items Pn_WORKFLOW_ID and Pn_ACTIVITY_STATIC_ID. Finally, note that it passes true to p_absolute_url and 0 to p_session.

function absolute_page_url(
    p_page                         in number,
    p_activity_static_id           in varchar2)
    return                            varchar2
is
    c_workflow_page_item constant varchar2(255)
        := 'P'||p_page||'_WORKFLOW_ID';
    c_activity_static_id_item constant varchar2(255)
        := 'P'||p_page||'_ACTIVITY_STATIC_ID';
begin
    return apex_page.get_url(
            p_page         => p_page,
            p_items        => c_workflow_page_item
                              ||','
                              ||c_activity_static_id_item,
            p_values       => v('APEX$WORKFLOW_ID')
                              ||','
                              ||p_activity_static_id,
            p_absolute_url => true,
            p_session      => 0,
            p_debug        => 'NO');
end absolute_page_url;

25.1.9.2.3 Emailing User a Page Link#

Email a user an absolute page link generated from workflow activity data.

To notify a user via email with a link to a data entry page, use a Send E-Mail activity. Since a workflow runs asynchronously in the background, the APP_USER system variable returns nobody. Therefore, this example workflow accepts a P_REQUESTED_USERNAME parameter, along with a P_SUBJECT parameter. The Additional Data query below on the Send E-Mail activity calls an ABSOLUTE_PAGE_URL function to generate the link to page 5, also passing the unique static ID of the wait activity user_provide_two_values. The query aliases the result to the LINK_TO_PAGE column name. A second SELECT list expression uses a subquery to lookup the email address of the requested username from the APEX_WORKSPACE_APEX_USERS view, using the value of the P_REQUESTED_USERNAME parameter as a bind variable. It aliases that result to REQUESTED_USER_EMAIL.

select
    workflow_user_input.absolute_page_url(
        5,
        'user_provide_two_values'
    ) as link_to_page,
   (select email
      from apex_workspace_apex_users
     where user_name = :P_REQUESTED_USERNAME) as requested_user_email
  from dual

As shown below, the Send E-Mail activity's Subject uses the &P_SUBJECT. substitution, the To field references the &REQUESTED_USER_EMAIL. from the Additional Data query result, and the Body HTML references the &LINK_TO_PAGE!ATTR. in an href attribute of an HTML anchor (<a>) element. The !ATTR modifier ensures the URL value is correctly formatted for inclusion in an HTML attribute.

Figure 25-62 Send E-Mail Activity Includes Page Link from Additional Data Query

Also notice the From field defaults to the &APP_EMAIL. substitution. This is a system variable whose value you configure in the Shared Components > Application Definition page in the Properties section as shown below.

Figure 25-63 Configuring the Value of APP_EMAIL Substitution

25.1.9.2.4 Delivering Push Notification with Page Link#

Send a push notification with an absolute page link generated from workflow activity data.

To prompt a user via push with a link to a data entry page, use a Send Push Notification activity. Since a workflow runs asynchronously in the background, the APP_USER system variable returns nobody. Therefore, this example workflow accepts a P_REQUESTED_USERNAME parameter, along with a P_SUBJECT parameter. The Additional Data query below on the Send Push Notification activity calls an ABSOLUTE_PAGE_URL function to generate the link to page 5, also passing the unique static ID of the wait activity user_provide_two_values. The query aliases the result to the LINK_TO_PAGE column name.

select
    workflow_user_input.absolute_page_url(
        5,
        'user_provide_two_values'
    ) as link_to_page
  from dual

As shown below, the Send Push Notification activity's Title uses the &P_SUBJECT. substitution, the To field references the &P_REQUESTED_USERNAME. parameter, and the Link Target references #LINK_TO_PAGE# from the Additional Data query result.

Figure 25-64 Send Push Notification Uses Link from Additional Data Query

25.1.9.2.5 Waiting Until Application Logic Continues#

Pause a workflow with a Wait activity until application logic continues it.

To pause a workflow until your app tells it to continue, use a Wait activity with no Timeout Type set. The Static ID you set on the activity is the value you pass to CONTINUE_ACTIVITY in the APEX_WORKFLOW package. Notice below that the Wait activity's Static ID is user_provide_two_values and that it leaves Timeout Type with no selection.

Figure 25-65 Wait Activity user_provide_two_values Has No Timeout Type

Tip:

A native Wait activity with no Timeout Type is the most common subject of a CONTINUE_ACTIVITY call. However, a custom workflow activity you create can also wait for completion in the same way. In your process type plug-in with its Workflow Activity checkbox checked in the Supported For section, check the Wait for Completion checkbox in the Standard Attributes section. It can also provide a Completion Function the APEX engine executes when the custom activity continues.

25.1.9.2.6 Navigating to Page Using Email Link#

Open a notification link to enter requested data on a deep-linked page.

When a user receives a notification containing an absolute URL page link, as shown below, they can click the link to navigate to the page.

Figure 25-66 Receiving the Notification Email and Clicking the Link

After logging in, since you enabled Deep Linking, they navigate to the page where they can provide the additional data and click (Submit Info) button to confirm their input. Notice the page uses a Static Content region with the Hero template to show an application icon and title. The title includes the workflow instance title and the label of the Wait workflow activity.

Figure 25-67 Page Collecting Two Values from the Notified User

25.1.9.2.7 Retrieving Title and Activity Labels#

Retrieve workflow and activity labels to show context on the data entry page.

As shown below, the page to collect user input retrieves workflow title and activity label dynamically. It uses an Invoke API page process in the Pre‑Rendering section to call a GET_LABELS_FOR_PAGE procedure. This function accepts the workflow instance ID, the activity's static ID from hidden page items, and returns the workflow title and activity label into two other hidden page items. Then the Static Content region's title references the values of these hidden page items using:
&P5_WORKFLOW_TITLE. - &P5_ACTIVITY_LABEL.
Figure 25-68 Page Collecting Values from the User

The GET_LABELS_FOR_PAGE procedure looks like this. It retrieves the title and activity label by joining the APEX_WORKFLOWS and APEX_WORKFLOW_ACTIVITIES views.

procedure get_labels_for_page(
    p_application_id     in  number default null,
    p_workflow_id        in  number,
    p_activity_static_id in  varchar2,
    p_workflow_title     out varchar2,
    p_activity_label     out varchar2)
is
    l_application_id apex_applications.application_id%type;
begin
    l_application_id := coalesce(p_application_id,v('APP_ID'));
    select a.label as activity_label,
           w.title as workflow_title
     into p_activity_label,
          p_workflow_title
     from apex_workflow_activities a
     join apex_workflows w
       on w.workflow_id = a.workflow_id
    where a.application_id = l_application_id
      and w.workflow_id    =  p_workflow_id
      and a.type_code      = 'NATIVE_WORKFLOW_WAIT'
      and a.state          = 'WAITING'
      and a.static_id      = p_activity_static_id;
exception
    when no_data_found then
        null;
end get_labels_for_page;

25.1.9.2.8 Signaling to Continue and Returning Data#

Continue a waiting workflow activity and return submitted values to workflow variables.

The page below contains hidden page items P5_WORKFLOW_ID and P5_ACTIVITY_STATIC_ID. When the user clicks the (Submit Information) button, a Provide Info page process in the Processing section executes a small block of PL/SQL code.

It first populates a parameter map containing the user's submitted values. Then it calls CONTINUE_ACTIVITY, passing the workflow ID, activity static ID, and the parameter map. The map is a set of key/value pairs. Notice that the keys are the Static ID values of three workflow variables. Any workflow variable whose Static ID matches a key in the parameter map is assigned its corresponding value when the activity continues.

declare
    l_user_input apex_application_global.vc_map;
begin
    l_user_input('V_PROVIDING_USERNAME') := :APP_USER;
    l_user_input('V_VALUE1')             := :P5_VALUE1;
    l_user_input('V_VALUE2')             := :P5_VALUE2;
    apex_workflow.continue_activity(
        p_instance_id     => :P5_WORKFLOW_ID,
        p_static_id       => :P5_ACTIVITY_STATIC_ID,
        p_activity_params => l_user_input);
end;
Figure 25-69 Page Process Calls CONTINUE_ACTIVITY, Passing Data in Parameter Map

Tip:

Passing workflow variable values back through the CONTINUE_ACTIVITY procedure is only one way to capture end user input in a workflow. Depending on your use case, your page could instead populate the data into columns of an appropriate application table row directly. Using the workflow ID, you can lookup the primary key value of the associated business entity row by retrieving the DETAIL_PK value from the APEX_WORKFLOWS table where the WORKFLOW_ID equals the value you know.

25.1.9.2.9 Inspecting Resulting Workflow Instance#

Inspect workflow variables and diagram progress after a waiting activity continues.

If a page calls CONTINUE_ACTIVITY in the APEX_WORKFLOW package to signal a Wait activity to proceed, any workflow variable whose Static ID matches a key in the parameter map is assigned its corresponding value. The default workflow detail page you generate when creating a Workflow Console in the Create Page Wizard displays the workflow variables and parameter values. It also displays a Workflow Diagram showing the workflow path completed so far. As shown below, while the requested username parameter was LEO, he must have forwarded the email to his colleague LUCY to click on the link and provide the data. Notice in the Variables region, the values she provided are reflected, along with her username. The page displays the user-friendly labels and values of the V_PROVIDING_USERNAME, V_VALUE1, and V_VALUE2 variables. The Completed badge in the Initiated Workflows Content Row region confirms this workflow instance is done executing. The visual diagram's highlighted activities also confirm this.

Figure 25-70 Inspecting the Details of the Completed Workflow

25.1.9.2.10 Evaluating Benefits of Using Tasks Instead#

Consider using tasks when user input needs tracking, reminders, and a task inbox.

Prompting an end user with a link to a data entry page using email or push notification is straightforward. However, if the user dismisses the notification or the message gets lost among other emails, your business process may stall. A workflow could remain stuck waiting for input that never arrives.

To avoid the pitfalls of this simple approach, a more complete solution would:
  • Track which data entry requests the application sent,
  • Provide users with a list of outstanding requests requiring their input, and
  • Remind them if they haven't responded after a reasonable amount of time.
A workflow using a Task Definition to formally track a user work item gets these features natively:
  • Updatable task parameters can model user input required,
  • Task instances track pending work items
  • Task actions can notify users when they've been assigned something new
  • A Unified Task List page is a task "inbox" where users see and complete assigned tasks
  • Due dates and Before Expire actions can remind users about time-sensitive items.

Before building a custom solution to add these features, consider whether using a Human Task - Create activity combined with a well-designed task definition can save you significant time and effort.

25.2.6.4.1 Finding Best Algorithm for Your Data#

Use AutoML to find the machine learning model that best predicts your target outcome.

Your application has access to over 30 different machine learning algorithms. You can use them to "mine" your historical data for interesting correlations. By training a machine learning model using the most appropriate algorithm, your applications can predict future outcomes using that model.

As shown below, Oracle Cloud's AutoML tool lets you run an experiment on your historical data to help you determine the machine learning algorithm that best predicts the outcome you seek. Setting up an experiment is simple. You tell the tool which table to analyze, what column represents the outcome you want to predict, and identify the table's primary key column. For the Woods Clinic app, you select the FRC_PATIENTS table, with its ID primary key column, and specify the patient's onboarding STATUS as the value you aim to predict.

Figure 25-85 Configuring an Oracle Machine Learning AutoML Experiment

AutoML runs an experiment trying all of its available machine learning algorithms on the Woods Clinic patient onboarding data. This experiment takes about 13 minutes to run. As shown below, it keeps a "Leader Board" of the algorithms that deliver the best prediction accuracy. Clicking on the name of any candidate model in the experiment, you can see the column values that had the strongest impact on predicting the historical outcome. It identifies the INSURANCE_PROVIDER and the INITIAL_PROCEDURE the patient requested during registration as the strongest predictors.

Figure 25-86 Inspecting the Algorithm "Leader Board" Results of the Experiment

The experiment finds the Neural Network model with the system-generated name of NN_CE379F672F gives the best results, so you can use that model in your Patient Onboarding business process to attempt to automatically predict the patient onboarding approval.

Figure 25-87 Winning Neural Network Model for Woods Clinic Patient Onboarding

25.2.6.4.2 Predicting Patient Onboarding Outcome#

Invoke a machine learning model to predict patient onboarding outcomes and route the workflow.

The Patient Onboarding workflow uses the machine learning model in two different Invoke API activities. First, the Evaluate Insurance Risk calls the EVALUATE_RISK function to detect if historically a patient with a similar request to the current one was automatically rejected. Office staff tells you that this automatic rejection was due to the patient's insurance not reimbursing the procedure.

Figure 25-88 Two Combinations of Invoke API and Switch Using Machine Learning

The code for EVALUATE_RISK appears below. Predicting the outcome of the onboarding patient approval using the Neural Network machine learning model that won the AutoML experiment is a single SQL statement using the PREDICTION function. Its USING clause feeds the model the values of the current patient's insurance provider and initial procedure. Recall that these were the two columns that most strongly predicted the outcome. If the model predicts the STATUS to be "AutoRejected", then the function returns the outcome "Risky", otherwise it returns "Low Risk". The Evaluate Insurance Risk activity returns the Function Result into the V_RESULT workflow variable, and the subsequent Risk Level? Switch activity uses that variable containing the outcome value to decide the path to follow.

-- Result = 'Low Risk' or 'Risky'
function evaluate_risk(p_patient_id number)
return varchar2 is
    l_result varchar2(30);
begin
    /*
     | Machine Learning Model NN_CE379F672F was the winning model
     | automatically identified by using AutoML.
     */
    select
         prediction (NN_CE379F672F
         using
            pat.insurance_provider  as insurance_provider,
            pat.initial_procedure   as initial_procedure
         )
      into l_result
      from frc_patients pat
     where pat.id = p_patient_id;
    if l_result = 'AutoRejected' then
        return 'Risky';
    else
        return 'Low Risk';
    end if;
end;  

The Predict Approval activity uses Invoke API again to call a similar PREDICT_APPROVAL function. It accepts the previous prediction as a parameter. If that prediction was "AutoApproved" then it returns the outcome string "Auto-Approved", otherwise it returns the string "Manual. The Predict Approval activity returns the Function Result into the V_RESULT workflow variable, and the subsequent Auto‑Approve? Switch activity uses that variable containing the outcome value to decide the path to follow.

25.2.6.7.1 Emailing or Push Notifying Users#

Notify workflow users with email or push notification activities.

It's equally easy to send users a push notification or an email. The figure shows the Workflow Designer palette offers both Send E-Mail and Send Push Notfication activities. The Woods Clinic app opted for the email option. Notice the Notify Rejection Send E-Mail activity is selected in the diagram, and the Email Template configured is Patient Rejected.

Figure 25-96 Native Activities for Alerting Users with Email or Push Notifications

25.2.6.7.2 Using Email Template with Placeholders#

Use an email template with placeholders for workflow notification content.

To send the rejection email, you first setup an appropriate Patient Rejected shared component email template shown below. It includes two placeholders in the email Body #FIRST_NAME# and #PROCEDURE_DESCRIPTION#.

Figure 25-97 Rejection Email Template with Two Placeholders

25.2.6.7.3 Querying Activity Data On Demand#

Query activity-level data to supply email fields and template placeholders.

As shown below, the Notify Rejection activity uses the following Additional Data query. It retrieves the sender's email, and the description of the requested medical procedure by joining in another table. The From field references the SENDER_EMAIL column value, while a placeholder value uses the DESCRIPTION column.

select prc.description,
       frc_app.get_sender_email as sender_email
from frc_patients pat
left join frc_medical_procedures prc
       on prc.code = pat.initial_procedure
where pat.id = :ID

The figure shows the App Builder Workflow Designer with the Notify Rejection activity selected in the diagram. The Property Editor shows the Additional Data SQL Query above, that includes a SENDER_EMAIL column in its select list. The Email Header section references its value using &SENDER_EMAIL. in the value of its From property.

Figure 25-98 Referencing Additional Data Columns in an Activity

25.2.6.7.4 Receiving the Rejection Email#

Receive an automatic rejection email when the workflow denies a patient request.

With no back office staff involvement, your Patient Onboarding workflow now automatically notifies Anya Ableton that she'll need to seek her procedure at another facility.

When Ms. Ableton's procedure request is rejected, the Patient Onboarding workflow automatically notifies her as shown below, with no back office staff involvement. The figure shows Anya's email client with the subject line "Unable to Accommodate Your Request" and a message explaining she'll need to seek the care she needs at another facility.

Figure 25-99 Anya Ableton Rejection Email from Woods Clinic Patient Onboarding Workflow

25.2.6.8.1 Invoking the Provision Account API#

Invoke a provisioning API to create the approved patient’s account and assign its role.

As shown below, your Patient Onboarding workflow's Provision Account activity is an Invoke API that calls the PROVISION_ACCOUNT procedure.

Figure 25-101 Provision Account Invoke API Activity Creates User Account

The PROVISION_ACCOUNT routine is below. It looks up the patient information using the patient ID passed in, then calls a CREATE_USER procedure in the FRC_APEX_USER package to create the APEX user account. Finally, it calls ADD_USER_ROLE in the APEX_ACL package to add the new account's username to the Access Control List (ACL) role with Static ID PATIENTS. The Woods Clinic application serves both back office staff and patients. It uses Authorization Schemes based on ACL role membership to automatically show each user appropriate pages and functionality based on their role.

procedure provision_account(
    p_patient_id number)
is
    l_patient t_patient    := patient(p_patient_id);
    l_clinic_app_id number := frc_app.clinic_app_id();
begin
    frc_apex_user.create_user(
        p_username   => l_patient.username,
        p_email      => l_patient.email,
        p_patient_id => p_patient_id
    );
    apex_acl.add_user_role(
        p_application_id => l_clinic_app_id,
        p_user_name      => l_patient.username,
        p_role_static_id => 'PATIENTS'
    );
end;

25.2.6.8.2 Inserting Account Creation Request#

Insert an account creation request for a background job to process outside the workflow session.

To create an APEX user, you call the CREATE_USER procedure in the APEX_UTIL package. However, since this must be run from context outside of an APEX application session, the CREATE_USER function in the Woods Clinic package uses a two-step approach. Here, it simply inserts a row in a table to track the request to create a new user account. Then, a DBMS_SCHEDULER background job periodically processes the table to actually create the accounts.

-- package frc_apex_user
procedure create_user(
    p_username   in varchar2,
    p_email      in varchar2,
    p_patient_id in number)
is
    l_group_id number := apex_util.get_group_id(p_group_name=>'Patients');
begin
    insert into frc_apex_users_queue(
       username,
       group_id,
       email,
       patient_id)
    values (
       upper(p_username),
       l_group_id,
       p_email,
       p_patient_id);
end;

25.2.6.8.3 Scheduling Create New Account Job#

Schedule the account creation job from SQL Developer Web so it runs as the workspace schema.

Scheduling a background job is simple, but if you run the one-line PL/SQL block below inside in the SQL Commands or SQL Scripts pages of SQL Workshop, DBMS Scheduler infers a JOB_CREATOR of APEX_PUBLIC_USER. That generic schema does not have privileges to call APEX_UTIL.CREATE_USER.

Instead, use SQL Workshop > SQL Developer Web if your APEX instance administrator has enabled this integration. This menu option opens SQL Developer Web in a new browser tab, automatically logged in as the workspace schema user WOODS (the Woods Clinic schema). Then, from the SQL Worksheet in SQL Developer Web, the DBMS Scheduler correctly infers the WOODS schema as the Job Creator. Notice that the JOB_ACTION parameter passes the PL/SQL block to handle the new users queue, and the REPEAT_INTERVAL indicates to run every minute.

--
-- NOTA BENE: Run this outside of App Builder so that the
-- ~~~~~~~~~  JOB_CREATOR ends up correctly reflecting WOODS
--            instead of the APEX_PUBLIC_USER proxy user.
--
BEGIN
    DBMS_SCHEDULER.CREATE_JOB (
      job_name            => 'APEX_CREATE_USER_JOB',
      job_type            => 'PLSQL_BLOCK',
      job_action          => 'begin frc_apex_user.handle_new_users_queue; end;',
      number_of_arguments => 0,
      repeat_interval     => 'FREQ=MINUTELY',
      end_date            => NULL,
      enabled             => TRUE,
      auto_drop           => TRUE,
      comments            => ''
      );
END;

25.2.6.8.4 Handling New Account Requests#

Process queued account requests, create users, and email portal credentials from PL/SQL.

A DBMS_SCHEDULER job calls the procedure below every minute to check the new account creation request table. It loops over any rows in that table, generates a temporary password, creates the new APEX end-user account as part of the Patients group that was recorded in the request table. Then it notifies the new user of their patient portal credentials by calling a SEND_APPROVAL_EMAIL in the FRC_ONBOARDING package. This email could have been sent using another Send E-Mail workflow activity, but doing it from PL/SQL is an opportunity to see how to send mail programmatically.

Tip:

The procedure uses EXECUTE IMMEDIATE in this example to avoid a circular dependency between the FRC_APEX_USER package containing HANDLER_NEW_USERS_QUEUE and the FRC_ONBOARDING package containing the rest of the patient onboarding application logic.

-- package frc_apex_user
procedure handle_new_users_queue is
    l_temp_password varchar2(255);
begin
    -- set workspace context
    apex_util.set_workspace('WOODS');
    for x in (select * from frc_apex_users_queue)
    loop
        -- Generate temporary password
        l_temp_password := generate_password();
        -- create the apex user in the supplied group if present
        apex_util.create_user
        (
            p_user_name     => x.username,
            p_web_password  => l_temp_password,
            p_email_address => x.email,
            p_group_ids     => x.group_id
        );
        execute immediate q'~
          begin
             frc_onboarding.send_approval_email(
                :PATIENT_ID,
                :TEMP_PASSWORD);
          end;
        ~' using x.patient_id,
                 l_temp_password;
        -- remove the user from the queue
        delete from frc_apex_users_queue where id = x.id;
    end loop;
end; 

25.2.6.8.5 Notifying User of Temporary Password#

Email the approved patient their portal username and temporary password from PL/SQL.

The final step of user account provisioning is notifying the user of their new username and temporary password. While you could send the email from the workflow, here you explore the programmatic way to send an email using SEND_MAIL in the APEX_EMAIL package. It's using an email template for the new account notification that uses a number of placeholder values. Notice that the P_PLACEHOLDERS argument expects to receive the placeholder name/value pairs as a JSON document. The local APPROVAL_PLACEHOLDERS helper function builds up that JSON document using PL/SQL's native JSON_OBJECT_T type and returns it as a CLOB in JSON format.

-- in package frc_onboarding
procedure send_approval_email(
    p_patient_id         in number,
    p_temporary_password in varchar2)
is
    l_patient   t_patient := patient(p_patient_id);
    -----------------------------------------------
    function approval_placeholders(
        p_patient            in t_patient,
        p_temporary_password in varchar2)
        return                  clob
    is
        l_json json_object_t := json_object_t;
        l_clinic_app_url varchar2(255);
    begin
        l_clinic_app_url := frc_app.url_for_page(
                                frc_app.clinic_app_id,'home');
        l_json.put('FIRST_NAME',
                    p_patient.first_name);
        l_json.put('PROCEDURE_DESCRIPTION',
                    p_patient.procedure_description);
        l_json.put('PORTAL_URL',
                    l_clinic_app_url);
        l_json.put('USERNAME',
                    lower(p_patient.username));
        l_json.put('TEMPORARY_PASSWORD',
                    p_temporary_password);
        return l_json.stringify();
    end;
begin
    apex_mail.send(
        p_to                 => l_patient.email,
        p_from               => frc_app.get_sender_email,
        p_template_static_id => frc_app.c_approved_email_template,
        p_placeholders       => approval_placeholders(
                                    l_patient,
                                    p_temporary_password),
        p_application_id     => frc_app.onboarding_app_id());
    -- Force mail to be sent immediately
    apex_mail.push_queue;
end;  

25.2.6.8.6 Receiving New Account Email#

As shown below, your Patient Onboarding workflow's Provision Account activity results in automatically sending Cristina Cordero the credentials for her new account.

Figure 25-102 Cristina Cordero Receives Her New Account Credentials

25.2.6.9.1 Waiting Indefinitely for a Custom Signal#

Use a Wait activity without a timeout to pause until custom application logic continues it.

Your Patient Onboarding workflow uses a Wait activity to pause until the new patient completes their profile by providing a date of birth and proof of insurance scan. If a Unified Task List "inbox" is not the kind of user experience you want for your end users, you can use the combination of a Wait activity and a later call to CONTINUE_ACTIVITY in the APEX_WORKFLOW package to craft a custom user experience. Notice that by not choosing a Timeout Type, you opt to wait indefinitely until your own application logic in another part of the application signals the waiting activity should continue.

Figure 25-104 Configuring an Activity to Wait Indefinitely

25.2.6.9.2 Completing the New Patient Profile#

Complete portal registration by entering birth date and insurance details.

Cristina clicks the link in her notification email and logs into the portal for the first time. After changing her initial password, she sees tiles for functionality available to her. To complete her profile, she clicks on Complete Registration.

Figure 25-105 Patient Portal for Cristina Cordero on First Login

As shown below, Cristina enters her date of birth on the first step of the Complete Registration wizard, then clicks (Next >).

Figure 25-106 Cristina Cordero Completes Registration - Date of Birth

On step 2 of the wizard, she uploads her proof of insurance and enters her insurance policy number, then clicks (Finish).

Figure 25-107 Cristina Cordero Completes Registration - Proof of Insurance

25.2.6.9.3 Signaling the Workflow to Continue#

Save the patient’s registration data and signal the waiting workflow to continue.

The last page of the Complete Registration wizard uses an Invoke API page process to call the COMPLETE_REGISTRATION procedure below. Each data item the wizard collected gets passed in by declaratively configuring the parameter value expressions. The value of an Image Upload page item provides the unique file name in the APEX_APPLICATION_TEMP_FILES table where the APEX engine stores the uploaded file for application-specific processing.

Notice that after retrieving the uploaded proof of insurance scan image blob from the temporary file table, it updates Cristina Cordero's FRC_PATIENTS row with date of birth and proof of insurance information. For fraud detection purposes, the wizard also uses a Get Current Position dynamic action step in a Page Load event handler to record the latitude and longitude of the user's position when uploading the proof of insurance.

After updating the patient row, the procedure calls CONTINUE_ACTIVITY in the APEX_WORKFLOW package to signal that Cristina Cordero's Patient Onboarding workflow should now proceed.

procedure complete_registration(
    p_patient_id              in number,
    p_year_of_birth           in number,
    p_month_of_birth          in number,
    p_day_of_birth            in number,
    p_insurance_policy_number in varchar2,
    p_scan_uploadname         in varchar2,
    p_scan_latitude           in number,
    p_scan_longitude          in number)
is
    l_scan_blob   blob;
    l_params apex_application_global.vc_map;
begin
    -- get uploaded insurance proof scan blob from temp storage
    for scan in (select blob_content
                   from apex_application_temp_files
                  where name = p_scan_upload_name)
    loop
        l_scan_blob := scan.blob_content;
    end loop;
    -- update patient to include insurance proof scan blob
    update frc_patients
    set insurance_proof_scan = l_scan_blob,
        date_of_birth = to_date(
                        to_char(p_year_of_birth)||
                        to_char(p_month_of_birth,'00')||
                        to_char(p_day_of_birth,'00'),
                        'YYYYMMDD'),
        insurance_policy_number   = p_insurance_policy_number,
        insurance_proof_latitude  = p_scan_latitude,
        insurance_proof_longitude = p_scan_longitude
    where id = p_patient_id;
    -- Continue onboarding workflow that's waiting for new patient
    -- registration to be complete. Use workflow id from the most
    -- recent onboarding workflow associated with this patient id
    for wf in (select workflow_id
                 from apex_workflows
                where workflow_def_static_id = 'patient_onboarding'
                  and detail_pk = to_char(p_patient_id)
                order by start_time desc
                fetch first row only)
    loop
        -- Continue the complete_registration "Wait" activity
        apex_workflow.continue_activity(
            p_instance_id     => wf.workflow_id,
            p_static_id       => 'complete_registration',
            p_activity_params => l_params);
    end loop;
end;

25.2.6.9.4 Understanding Onboarding is Complete#

Confirm that completing registration updates the portal and finishes the onboarding workflow.

After completing her profile, as shown below, Cristina Cordero sees the Complete Registration tile is replaced by a Your Profile tile. This confirms she provided all necessary information the clinic requires.

Figure 25-108 After Completing Registration, New Patients Portal Dashboard Options Change

In the back office, David confirms Cristina Cordero completed her Patient Onboarding workflow by glancing at the visual workflow diagram. As shown in the figure, now the End activity is highlighted on the path that ended by running the Completed Registration activity.

Figure 25-109 Verifying Cristina Cordero's Patient Onboarding is Completed

25.3.5.5.1 Printing Documents from a Workflow#

Print documents from a workflow by combining a Report Query and Report Layout.

As David sees from the lifecycle diagram below, in the background, your Procedure Lifecycle workflow reacts when Danny Deng's medical procedure status changes to "Completed". It prepares a draft invoice for Woods Clinic staff to review with Danny's insurance provider.

The workflow's Prepare Invoice activity invokes the PREPARE_PROCEDURE_INVOICE procedure in the FRC_CLINIC package. First, it inserts a row in the FRC_INVOICES table with a reference to the medical procedure ID. Then, it generates the pixel-perfect invoice by combining two shared components: a Report Query and a Report Layout.

Figure 25-127 Invoice Prepared After Procedure Completed

25.3.5.5.2 Retrieving Invoice Data#

Define a Report Query that supplies invoice data as JSON for the report layout.

Your Invoice Report Query shared component shown below defines the data the invoice needs. You provide one or more Source Queries whose results the APEX engine includes in a single JSON document sent to the remote printing service. For the invoice, a single query suffices.

Using the Set Bind Values dialog, you can set example values for any bind variables your source queries reference. Then, clicking the (Download) button produces a JSON file containing example data. You use it to learn the names to reference to inject data values in appropriate places in your report layout. You use the Static ID invoice_query to identify the Report Query definition at document generation time.

Figure 25-128 Report Query Provides JSON Data for Pixel-Perfect PDF Report Layout

The Invoice source query appears below. It uses a PROCEDURE_ID_TO_INVOICE application item as a bind variable for the medical procedure whose ID needs an invoice printed.

  select  invoice_number,
          invoice_date,
          procedure_id,
          patient_id,
          patient_name,
          address,
          doctor_name,
          medical_procedure,
          procedure_date,
          status,
          amount_due,
          insurance_provider,
          covered_amount,
          balance_due
 from frc_procedures_invoices_v
 where procedure_id = :PROCEDURE_ID_TO_INVOICE

Notice this query in the Report Source edit page below, as well as the Data Loop Name. This name identifies the array containing this source query's result rows in the JSON data document.

Figure 25-129 Report Source Configures SQL Query and Data Loop Name

After downloading an example JSON data document for medical procedure 2301, you can open it in an editor like Visual Studio Code shown below. Notice the "invoice" Data Loop Name and its corresponding array of a single row of JSON medical procedure query results. The property names in this document are vital when creating your report layout.

Tip:

If you use VS Code, you will find the SQL Developer extension productive. As shown below, it lets you easily experiment with queries from the same code editor environment.

The figure shows Visual Studio Code with the SQL Developer Extension installed. On the left, the downloaded JSON appears containing the data that will drive the report layout. On the left, a SQL Developer worksheet tab where you can run SQL queries and observe the results.

Figure 25-130 Studying Report Query JSON and Testing Query in VS Code

25.3.5.5.3 Formatting the Invoice in a Layout#

Create a Word report layout that includes invoice data using special tags.

Your Invoice Report Layout shared component shown below defines a Static ID you use when generating the invoice. It includes the Microsoft Word document template that details how the invoice looks and where it includes data.

Figure 25-131 Report Layout Provides Word Document Template for Invoice

Your Microsoft Word document for the invoice uses the tag syntax the Oracle Document Generator cloud service defines to loop over JSON data arrays and include values of data object properties. Notice the {#invoice} tag opens the data loop and the matching {/invoice} closes the loop. The invoice name in these tags is the Data Loop Name you defined in your Report Query, which used it as the JSON array name for the query result rows. Inside this loop, tags like {patient_name} and {balance_due} among others reference the relative names of JSON properties in the row of invoice data.

Figure 25-132 Microsoft Word Document Defines Pixel-Perfect PDF Layout

25.3.5.5.4 Generating the Data-Driven Document#

Generate a PDF invoice and store it for review, download, and email delivery.

The workflow's Prepare Invoice activity ends up calling the GENERATE_INVOICE procedure below in the FRC_CLINIC package. This procedure looks up the procedure ID from the invoice ID, sets that value into the application item PROCEDURE_ID_TO_INVOICE used as a bind variable in the report query, and calls GENERATE_DOCUMENT in the APEX_PRINT package to create the pixel-perfect PDF invoice as a BLOB. Notice it passes the Static ID values of the Report Query and Report Layout, as well as the current application ID using V('APP_ID').

Finally, it updates the FRC_INVOICES row to assign the generated PDF to its DOCUMENT column. This lets staff review it interactively in the Woods Clinic app and download it. It also helps your workflow attach it to the email it eventually sends to the patient.

Tip:

The multiple calls to the UPDATE_INVOICE_STATUS procedure provide feedback that appears in the Review Invoices page in the app. The status info helps users see the report generation is in progress in the background.

-- in frc_clinic package
procedure generate_invoice(
    p_invoice_id in number)
is
    l_pdf blob;
    l_procedure_id number;
begin
    -- Lookup patient procedure id for invoice for report query bind var
    select patient_procedure_id
      into l_procedure_id
      from frc_invoices
     where id = p_invoice_id;
    -- Set application item used as a bind variable in report query
    apex_session_state.set_value('PROCEDURE_ID_TO_INVOICE',l_procedure_id);
    update_invoice_status(p_invoice_id,'Processing');
    commit;
    -- Generate the pixel-perfect PDF invoice
    l_pdf := apex_print.generate_document(
                p_application_id          => v('APP_ID'),
                p_report_query_static_id  => 'invoice_query',
                p_report_layout_static_id => 'invoice_layout');
    update frc_invoices
    set status = 'Prepared',
        document = l_pdf
    where id = p_invoice_id;
exception
    when others then
        update_invoice_status(p_invoice_id,'Error');
        commit;
        raise;
end;

25.1.9.1.4.1 Setting Static Task Participants#

Set fixed task participants when a simple process always assigns work to the same users.

As shown below, in a simple application, either or both of the Potential Owner or Business Administrator participants can be a static value like DAVID or CARLA. But normally you use one of the Value Type settings like SQL Query, Function Body, or Expression to dynamically determine one or both of these participant lists.

Figure 25-26 Configuring Task Participants

Caution:

Participant usernames are case-sensitive. If :APP_USER returns DAVID, then use DAVID.

25.1.9.1.4.2 Assigning Participants Dynamically#

Assign task participants dynamically with queries, expressions, or functions that return case-sensitive usernames.

To assign task participants dynamically, use a participant Value Type of SQL Query, Function Body, or Expression. When using the SQL Query option, your single column SELECT list returns one case-sensitive username per row. For the Function Body or Expression options, return a comma-separated list of case-sensitive usernames.

Your query, function, or expression can use bind variable syntax to reference task parameter values, action source column names, application items, or other APEX substitution strings.

Tip:

Workflows execute in the background where there is no "logged-in username". So the APP_USER substitution returns nobody in a workflow context. Use APEX$WORKFLOW_INITIATOR to reference the username that initiated the workflow, or APEX$TASK_INITIATOR for the username that initiated the task. If neither of these is the user you need, define a workflow parameter or task parameter to accept a username for other purposes.

For easiest maintainability, your expression can invoke a PL/SQL package function as well. The function can be specific to the task at hand or be generic in nature and accept values like the task ID and task details primary key as PL/SQL IN parameters:
your_app_pkg.owners_for_task(:APEX$TASK_ID, :APEX$TASK_PK)

By passing the value of pre-defined bind variables like APEX$TASK_ID and APEX$TASK_PK, your generic function can lookup related information from APEX dictionary views like APEX_TASKS and from your application table using the primary key value. If needed, you can join in task definition information from the APEX_APPL_TASKDEFS view, using the value of the TASK_DEF_STATIC_ID column in APEX_TASKS.

Caution:

Participant usernames are case-sensitive. If :APP_USER returns DAVID, then use DAVID.

25.1.9.1.4.3 Passing Participants in a Parameter#

Pass task assignees in a parameter and use it to assign potential owners.

A common requirement is to let the page or workflow that creates the new task instance pass in the list of task assignees. This is easy to accomplish by defining a parameter – for example named P_ASSIGNEE_LIST – and referencing it in an expression consisting of just the bind variable:
:P_ASSIGNEE_LIST
You could also use it in a SQL Query, but in that case you need to return one row per case-sensitive username supplied. For example, if the input value is a colon-separated list of usernames you could use the SQL Query option with the query:
select column_value
from apex_string.split(:P_ASSIGNEE_LIST,':')

Caution:

Participant usernames are case-sensitive. If :APP_USER returns DAVID, then use DAVID.

25.1.9.1.4.4 Accounting for Vacations or Absences#

Use a vacation rule procedure to add alternate task participants for absent users.

Sometimes, stakeholders involved in task assignments are out of the office. Your task definition can use a vacation rule procedure to run custom business logic after the APEX engine determines participants using your original Participants configuration. The rule affects both initial task participant assignment, as well as task delegation.

You can also specify a vacation rule procedure at application level. If you do that, a task uses it unless it specifies a more specific rule of its own. You can optionally create the procedure in a PL/SQL package. It needs the following signature using exactly the indicated parameter names, modes, and types, in the given order:
procedure your_vacation_rule (
  p_param    in apex_human_task.t_vacation_rule_input,
  p_result  out apex_human_task.t_vacation_rule_result )

To process the list of existing participants, access the original_participants list from the p_param input record. If any alternative assignees are needed, populate the participant_changes list in the output record. For each participant change you populate the original username, the username who replaces them, and a reason explaining why that is recorded in the task history. The participant changes you return automatically become additional participants on the task.

Tip:

The participant changed reason is logged in the PARTICIPANT_CHANGED_REASON column of the APEX_TASK_HISTORY view and is available among the result columns returned by the GET_TASK_HISTORY function in the APEX_HUMAN_TASK package. This information is not included by default in a generated Task Details page, but is straightforward to add when required.

When configuring an out-of-office assignment handler, specify either:
  • your_vacation_proc, or
  • yourpackage.your_vacation_proc

as the value of the Vacation Procedure property. The following example vacation rule procedure adds SUSAN as an additional approver for DAVID due to some personal time off. In your application, you can read absence assignments from a table, but this code illustrates the fundamentals.

procedure handle_absences(
    p_param   in apex_human_task.t_vacation_rule_input,
    p_result out apex_human_task.t_vacation_rule_result )
is
    l_participant apex_human_task.t_task_participant;
    l_change      apex_human_task.t_task_participant_change;
    --------------------------------------------------------
    function potential_owner(
        p_username in varchar2)
        return apex_human_task.t_task_participant
    is
    begin
        return apex_human_task.t_task_participant(
                apex_human_task.c_task_potential_owner,
                apex_human_task.c_task_identity_type_user,
                p_username);
    end potential_owner;
    --------------------------------------------------------
    function absent_user_cover(
        p_original_username in varchar2,
        p_covering_username in varchar2,
        p_reason            in varchar2)
        return apex_human_task.t_task_participant_change
    is
    begin
        return apex_human_task.t_task_participant_change(
            potential_owner(p_original_username),
            potential_owner(p_covering_username),
            p_reason);
    end absent_user_cover;
begin
    -- Loop over original participants
    for j in 1..p_param.original_participants.count loop
        l_participant := p_param.original_participants(j);
        -- If the participant is DAVID as a potential owner
        if     l_participant.type  = apex_human_task.c_task_potential_owner
           and l_participant.value = 'DAVID'
        then
            -- Add participant change: SUSAN covers for DAVID's personal time off
            p_result.participant_changes(p_result.participant_changes.count + 1) :=
                absent_user_cover('DAVID','SUSAN','Personal Time Off');
            p_result.has_participant_changes := true;
        end if;
    end loop;
end handle_absences;

Caution:

Participant usernames are case-sensitive. If :APP_USER returns DAVID, then use DAVID.

25.1.9.1.6.1 Tailoring Task List to Purpose#

Choose a report context to control which task instances a Unified Task List page shows.

When creating a Unified Task List page, the Report Context you choose determines the filtered list of task instances the page displays at runtime.

Table 25-1 Report Context Determines Which Task Instances Display

Scroll horizontally to view the full table
Report Context Instances Where Current User Is API Constant
My Tasks
  • Potential Owner (when task is not assigned)
  • Actual Owner (when task is assigned)
MY_TASKS
Admin Tasks Business Administrator ADMIN_TASKS
Initiated by Me Initiator INITIATED_BY_ME
The wizard creates a new Unified Task List page that contains:
  • A Content Row region to display the task list,
  • A Smart Filters region to narrow the task list in various useful ways, and
  • An Order By Item to let users sort the list by creation date or due date.

You can use the page as it comes, or customize it in Page Designer as needed. Create as many task inbox pages as your app requires, choosing an appropriate Report Context in each one as needed.

25.1.9.1.6.2 Showing Only Current Application Tasks#

Limit a Unified Task List page to tasks from the current application.

By default, the task list displays tasks from all applications in the workspace. If instead you want it to only display tasks related to the current application, one small change is required. As shown below, the Content Row region's query retrieves the task list from the GET_TASKS function in the APEX_HUMAN_TASK package. This function returns a table of task information records. By using the table() operator, this result works just like a regular table in a SELECT statement.

Notice that the p_application_id parameter is commented out using two consecutive dashes (--), the PL/SQL line comment.

select task_id,
       task_type,
         ⋮
       badge_state
  from table ( apex_human_task.get_tasks (
                   p_context            => 'MY_TASKS',
                   p_show_expired_tasks => :P33_SHOW_EXPIRED
                   --, p_application_id => :APP_ID
                   ) )

To only display tasks related to the current application, as shown below, simply remove the two dashes to uncomment the p_application_id parameter line. This passes the current application id as the value of this parameter, and limits tasks returned to those from the current application only.

select task_id,
       task_type,
         ⋮
       badge_state
  from table ( apex_human_task.get_tasks (
                   p_context            => 'MY_TASKS',
                   p_show_expired_tasks => :P33_SHOW_EXPIRED
                   , p_application_id => :APP_ID
                   ) )

25.1.9.1.6.3 Including Extra Columns in Task List#

Expose additional GET_TASKS columns so the task list can show more task details.

In a new Unified Task List page, the Content Row region's SELECT list includes only some of available task information columns the GET_TASKS function returns. To see all of the available columns, change the query to use * for the column list as shown below:
select *
  from table ( apex_human_task.get_tasks (
                   p_context            => 'MY_TASKS',
                   p_show_expired_tasks => :P33_SHOW_EXPIRED,
                   p_application_id     => :APP_ID
                   ) )

This causes Page Designer to show all available column names under the region's Columns heading in the Rendering tab. The following table highlights a few important GET_TASKS result columns.

Table 25-2 Important GET_TASKS Result Columns

Scroll horizontally to view the full table
Column Description
TASK_ID Unique ID of this task instance
SUBJECT Subject of this task instance
STATE_CODE Current state of this task instance (e.g. INFO_REQUESTED)
STATE Current state of this task instance (e.g. Information Requested)
INITIATOR Username who created the task instance
ACTUAL_OWNER Username of assigned potential owner
DETAIL_PK Details Primary Key for this instance's related business entity row
TASK_DEF_STATIC_ID Unique task definition identifier for this task instance
DETAILS_LINK_TARGET URL of the task details page for this instance

25.1.9.1.6.4 Limiting Which Types of Tasks Appear#

Add a WHERE clause to show only selected task definition types.

In a new Unified Task List page, the Content Row region's query contains no WHERE clause. The user can use the Smart Filters region at runtime to narrow the list, but sometimes you may want to limit which tasks show up in the list at all. The table() operator wrapping the GET_TASKS function call lets its results participate in the query like a regular table. So, just add a WHERE clause to the query to filter the list. For example, as shown below, to show only tasks based on two specific task definition types you can add a WHERE clause that filters on the TASK_DEF_STATIC_ID column.

select task_id,
       task_type,
         ⋮
       badge_state
  from table ( apex_human_task.get_tasks (
                   p_context            => 'MY_TASKS',
                   p_show_expired_tasks => :P33_SHOW_EXPIRED,
                   p_application_id => :APP_ID
                   ) )
 where task_def_static_id in ('NEW_PATIENT','PURCHASE_OFFICE_SUPPLIES')

25.1.9.1.6.5 Joining Extra Tables into Task List#

Join application tables into the task list to display related business data.

In a new Unified Task List page, the Content Row region's select list includes only columns related to the task itself. Sometimes, it's useful to join in data from other application tables using the Details Primary Key value in the join clause.

For example, the following query adds a left join to the FRC_PATIENTS table based on the value of the DETAIL_PK column for task instances of the NEW_PATIENT type. This lets the task list page display the related patient name.

Notice the table alias T is added for the table(apex_human_tasks.get_tasks()) row source, and that table alias is added to the columns in the SELECT list coming from that source. In addition, the table alias P for the FRC_PATIENTS table lets the PATIENT_NAME column concatenate the first name and last name values from the related patient table.

select t.task_id,
       t.task_type,
         ⋮
       t.badge_state,
       p.first_name||' '||p.last_name as patient_name
  from table ( apex_human_task.get_tasks (
                   p_context            => 'MY_TASKS',
                   p_show_expired_tasks => :P33_SHOW_EXPIRED,
                   p_application_id     => :APP_ID
                   ) ) t
  left join frc_patients p
    on p.id = case task_def_static_id
                when 'NEW_PATIENT'
                then to_number(t.detail_pk)
              end
 where task_def_static_id in ('NEW_PATIENT','PURCHASE_OFFICE_SUPPLIES')

25.1.9.1.7.1 Defining Updatable Task Parameters#

Define updatable task parameters to collect values from the task owner.

If a task requires the actual owner to supply additional data values, model them as updatable task parameters. Notice below that the Provide Two Random Numbers task definition accepts two read-only ones to specify the purpose of the request and the requested username. It also defines two updatable parameters P_VALUE1 and P_VALUE2. They let the actual owner provide two values while completing the task. The workflow can retrieve these task parameter values using an API in the APEX_HUMAN_TASK package.

Figure 25-28 Updatable Action Task Definition Parameters Model User-Supplied Data

25.1.9.1.7.2 Controlling Parameter Visibility#

Control which task parameters appear on task details pages.

Use a parameter's Visible setting to control whether it appears by default in the task details page. This visibility hint lets you avoid displaying the parameter even in a custom task details page by including IS_VISIBLE='Y' filter in your query against the APEX_TASK_PARAMETERS view.

Figure 25-29 Disable Visible Flag to Hide a Parameter from the Task Details Page

25.1.9.1.7.3 Parameterizing the Task Assignee#

Use a task parameter to supply the potential owner at runtime.

The Provide Two Random Numbers task definition uses an Expression to compute its Potential Owner. As shown below, the simple expression just returns the value of the P_REQUESTED_USERNAME parameter by referencing it as a bind variable. This lets the workflow pass in the desired assignee for the task in the Human Task - Create activity.

Figure 25-30 Configuring Task Potential Owners Using a Parameter Value

25.1.9.1.7.4 Creating the Human Task Instance#

Create a human task instance and store its task ID and actual owner in workflow variables.

The Get Two Numbers activity of type Human Task - Create shown below configures its Definition property to use the Provide Two Random Numbers task definition. Notice how it uses the special system substitution value APEX$WORKFLOW_INITIATOR to pass along the username who started the workflow as the initiator of the task instance it's creating. The Task ID Item and Owner property values name two workflow variables V_TWO_NUMBERS_TASK_ID and V_PROVIDING_USER that will store the task id and actual owner of the task instance upon completion.

Figure 25-31 Configuring a Human Task - Create Activity to Use a Task Definition

25.1.9.1.7.5 Passing Workflow Parameter to a Task#

Pass a workflow parameter value into a task definition parameter.

A workflow configures task definition parameters using corresponding items in the Workflows tree indented under the Human Task - Create activity's Parameters heading. As shown below, the Get Two Numbers activity configures the task definition parameter P_REQUESTED_USERNAME to the value of the workflow's parameter of the same name using the &P_REQUESTED_USERNAME. substitution in the Static Value field in the Property Editor.

Figure 25-32 Passing the Value of a Workflow Parameter to a Task Definition Parameter

25.1.9.1.7.6 Assigning Task Parameters to Variables#

Store completed task parameter values in workflow variables for later activities.

The workflow pauses after creating the human task instance. When the task's lifecycle completes, it proceeds to the next activity. If later workflow activities need to reference the values of the updatable task parameters acquired during the human task lifecycle, they can use the GET_TASK_PARAMETER_VALUE function in the APEX_HUMAN_TASK package.

Its first parameter expects the unique id of a task, motivating why it's useful for a Human Task - Create activity to return its task ID into a workflow variable. Recall that in this workflow, the return variable for the task ID is V_TWO_NUMBERS_TASK_ID.

If you find it more convenient to store a task parameter value in a workflow variable, then as shown below, a subsequent Execute Code activity can call this same API to assign the updatable task parameter values into workflow variables using code like:
:V_VALUE1 := apex_human_task.get_task_parameter_value(
               :V_TWO_NUMBERS_TASK_ID,'P_VALUE1');
:V_VALUE2 := apex_human_task.get_task_parameter_value(
               :V_TWO_NUMBERS_TASK_ID,'P_VALUE2');
Figure 25-33 Retrieving Task Parameter Values into Workflow Variables

25.1.9.1.8.1 Viewing Work Items You Requested#

View requested work items with a Unified Task List page filtered to tasks the user initiated.

User LUCY visits a Requested Tasks page to see a list of requests she's made. In this simple example, there is only the Provide Two Random Numbers task definition in the list, but in a real application she would see work items of any type that she initiated. The page shown below is an example of a Unified Task List page you can add to an application with the Create Page Wizard. Since its goal is to show users the tasks they initiated, it uses the Report Context of "Initiated by Me". If a task instance has an associated Task Details page, then the task Subject line is a link Lucy can click on to review the details of the task.

Figure 25-34 Lucy Initiates a Workflow and Sees It In Her "Requested Tasks" Page

25.1.9.1.8.2 Adding a Task Details Page#

Add or assign a Task Details page so users can open a task from its subject link.

Like all task definitions, the action task Provide Two Random Numbers in this example starts off with no Task Details Page. The blank Task Details Page Number field below confirms this. Before clicking the (Create Task Details Page) button, you can either:
  • Leave Task Details Page Number blank,
  • Enter the number of an existing page, or
  • Type the number of your desired new page.

If you leave the field blank, APEX asks your confirmation to proceed using the next available page number.

Figure 25-35 Creating a Task Details Page for a Task
In this case, the new Task Details Page gets assigned page number 6. After you associate a Task Details Page to a task definition, it displays thereafter as a Task Details Page URL. It uses the classic format of an APEX page URL:
f?p=App:Page:Session:Request:Debug:ClearCache:itemNames:itemValues
The Task Details Page URL shown below for page 6 looks like this:
f?p=&APP_ID.:6:&SESSION.::&DEBUG.:RP,6:P6_TASK_ID:&TASK_ID.

It leaves the Request segment empty and uses substitution values for &APP_ID., &SESSION., &DEBUG., and &TASK_ID. in the respective segments of the p query string parameter value. In short, the URL navigates to page 6 in the current application, using the current session, and the current debug mode, passing the value of the current task instance's unique ID as the value of the P6_TASK_ID parameter in that page.

Figure 25-36 Task Details Page URL Appears After You Create or Assign a Page

Tip:

In addition to the substitutions mentioned above, your Task Details Page URL can also reference the substitution string &DETAIL_PK. to pass the value of the Details Primary Key of the business entity row associated with the task instance. This can be useful if your task details page needs to display and possibly modify data in that associated business entity row using a Form region you add to the generated page as a fine-tuning step.

25.1.9.1.8.3 Viewing Tasks Requiring Your Attention#

View assigned work items from a My Tasks page and open a task's details page.

User LEO visits his My Tasks page to view any work items requiring his attention. As shown below, he sees LUCY initiated a task that needs his input. To review the details of a task, Leo can click on any task Subject link to open its Task Details Page.

Figure 25-37 Leo Sees Task Assigned to Him in His "My Tasks" Page

25.1.9.1.8.4 Requesting More Info for a Task#

Request more information from the task initiator when the assigned work is unclear.

Clicking on a task Subject in the My Tasks page opens its Task Details Page. As shown below, by default, it contains sections that display the values of task parameters Purpose, Value1, and Value2, along with a Comments and History section, too. Leo notices Edit links next to the Value1 and Value2 parameters, but remains a bit confused. He clicks the three-dot menu and requests more information about the task from Lucy, the requester.

Figure 25-38 Leo Asks for More Information to Clarify the Task

A dialog box appears and, as shown below, Leo types in his question, "Do I just edit the values in the Details section?" and clicks (Request Information).

Figure 25-39 Leo Enters His More Information Question

25.1.9.1.8.5 Responding to Requests for Information#

Respond to a request for information so the task can return to its owner.

While her My Tasks list was previously empty, she now sees the task she requested show up in the list since it requires her attention. The Information Requested badge gives her a clue that Leo must have a question.

Figure 25-40 Lucy Sees the Task Shows Up in Her "My Tasks" Page

Clicking on the Subject, the Task Details page opens, and as shown below, Lucy clarifies, "Yes, please. That will be perfect!" Then she clicks (Submit Information) to send the task back to Leo along with her clarification.

Figure 25-41 Lucy Clarifies and Submits the Additional Information

25.1.9.1.8.6 Editing Updatable Parameters#

Edit updatable task parameters from the Task Details page.

The work item shows up again in Leo's My Tasks page and he clicks to open the Task Details page. He sees the conversation with Lucy in the Comments section and proceeds. As shown below, he enters the two values Lucy needs by clicking the Edit link next to the Value1 and Value2 parameters.

Figure 25-42 Leo Enters Parameter Values

25.1.9.1.8.7 Completing an Action Task#

Complete an action task when its work is done.

After entering values for Value1 and Value2 in the Task Details page, Leo is done. He clicks the (Complete) button to mark the work item as completed.

Figure 25-43 Leo Completes the Task

25.1.9.1.8.8 Inspecting Overall Workflow Status#

Inspect workflow details to review status, history, parameters, and variables.

From her Initiated Workflows page, Lucy can see all the workflows she created. It's a Workflow Console page with Report Context of "Initiated by Me". Clicking on the Title link, the Workflow Details page opens. As shown below, the workflow details page the wizard generates displays the variable and parameter values as well as visual diagram of the workflow's current status. Scrolling up and down, Lucy can also see the activity steps list and history that are not visible below. She sees the workflow has completed, and notices the Value1 and Value2 of the updatable task parameters were assigned to the workflow values of the same names.

Figure 25-44 Lucy Reviews Completed Workflow in Her "Initiated Workflows" Page

25.1.9.1.8.9 Fine-Tuning a Task Details Page#

Fine-tune a generated Task Details page for the needs of each task definition.

Your generated Task Details Page is a great starting point. Consider fine-tuning it to meet your particular task definition's needs. It offers useful built-in functionality, but know that it illustrates one particular way to use the native Human Task page processes and APEX_HUMAN_TASK programmatic APIs. Understand what you get by default, then decide on a task by task basis what to remove, add, or change.

While in theory you could use a single, generic task definition page with every task definition, the user experience would suffer. In practice, if a work item is unique enough to model as a task definition, it benefits from a task-specific Task Details Page. This way, you can show the assignee additional context tailored to that task definition, along with additional fields and functionality as required.

25.1.9.1.11.1 Choosing Event for an Action#

Choose the task event that should trigger an email, push notification, or code action.

When creating an action, choose an event from the table below that determines when your action triggers. Then, decide whether you want to send an email, send a push notification, or execute code. If the action should trigger only in certain conditions, configure an appropriate server-side condition.

When using the Execute Code option, you can write in-line PL/SQL as part of the action definition. However, for easiest maintenance it's best to organize your code into a PL/SQL package procedure or function, then invoke that packaged program unit from the action's PL/SQL block.

Tip:

Call ADD_TO_HISTORY in the APEX_HUMAN_TASK package from your Execute Code action to add a custom message to the task history.

Table 25-3 Pre-Defined Task Events

Scroll horizontally to view the full table
Event Happens When
Create Initiator creates task
Claim Potential owner claims task
Delegate Business administrator delegates task
Update Comment A user adds a new comment to task
Update Priority Business administrator changes task priority
Update Parameter Actual owner sets the value of updatable task parameter
Release Actual owner releases task so another potential owner can claim it
Cancel Business administrator cancels task
Request Information Actual owner request more information, typically from task initiator
Submit Information User submits requested additional information
Before Expire Less than Before Expire Interval left before task expires
Expire Task expired
Complete Actual owner completes task

25.1.9.1.11.2 Accessing Task Information#

Reference task source columns and built-in substitutions from task actions.

When configuring an action, you can reference any of the Actions Source columns by name using either :NAME in PL/SQL code or &NAME. in email or push notification substitutions. You can also reference any of the built-in substitution values below:

Table 25-4 Pre-Defined Task Substitutions

Scroll horizontally to view the full table
Name Purpose
APEX$TASK_ID Unique ID of task instance
APEX$TASK_PK Details Primary Key value of related business entity row
APEX$TASK_STATE Mixed-case
APEX$TASK_OUTCOME In Complete action, outcome of APPROVED, REJECTED
APEX$TASK_CREATED_ON Date task instance was created
APEX$TASK_SUBJECT Task subject
APEX$TASK_DUE_ON Date task is due (or null if no due date)
APEX$TASK_INITIATOR Case-sensitive username of task initiator
APEX$TASK_OWNER Case-sensitive username of task's actual owner
APEX$TASK_RENEWAL_COUNT Number of times task has been renewed
APEX$TASK_MAX_RENEWAL_COUNT Maximum task renewal count
APEX$TASK_PREVIOUS_ID Unique ID of previous task in renewal "chain"
APEX$TASK_TEXT In Update Comment action, text of comment added.

In addition, an Update Parameter action can use the HAS_TASK_PARAM_CHANGED, GET_TASK_PARAMETER_VALUE, and GET_TASK_PARAMETER_OLD_VALUE functions in the APEX_HUMAN_TASK package to get more information on which parameters have changed, what any parameter's current value is, and what a parameter's previous value was.

25.1.9.1.11.3 Notifying Assignees on Create#

Email potential owners when a new task requires their attention.

Some users check their task "inbox" frequently, while others may appreciate getting notified about a new task that requires their attention. You can configure a task action to email the potential owners of a newly created task.

25.1.9.1.11.4 Updating Columns on Completion#

A common requirement is to react to the outcome of an approval task by updating one or more columns in associated business entity row. You do this with an action on the Complete event.

The columns you update depend on whether the business entity related to the task definition represents:
  • A primary business entity, like an employee, or
  • A request for approval, related data, and an approval status.

25.1.9.1.8.8.1 Reviewing a Completed Task#

Review completed task details from the list of work items you requested.

In her Requested Tasks page, Lucy notices the Completed badge in the list, and she clicks on the Subject link to open the Task Details page to review Leo's work.

Figure 25-45 Lucy Sees Request Completed in Her "Requested Tasks" Page

Lucy reviews the details of the work item and sees the two random numbers Leo entered. Stakeholders can continue adding comments to the task if needed, but otherwise its details are read-only once completed.

Figure 25-46 Lucy Reviews Task Details to See Numbers Leo Supplied

25.1.9.1.8.9.1 Adding Page Items to Edit Parameters#

Add page items to give users a better experience when entering task parameter values.

The Details region in the generated Task Details page offers a basic parameter editing experience. However, your end users might appreciate entering data using page items. As shown below, you can right-click on a region like Details, Developer Information, Comments, and History and choose Comment Out to temporarily ignore those regions when the page is run. As shown below, commented out elements display with a line through their label in the tree. After adding page items P6_VALUE1 and P6_VALUE2 you can set their Type to Number Field, enable Value Required, and set their Session State > Storage to Per Request (Memory Only).

Figure 25-47 Commenting Out Regions and Adding Page Items to Enter Additional Data

Trying out the modified page, you can see that your end user immediately gets a better data entry experience. Any attempts to submit the page with non-numeric values or without filling in a required value gives the normal APEX field validation errors shown below. The figure shows the customized Task Details page for the Provide Two Random Numbers task definition. If Leo submits a Value1 by typing in words, and leaves Value 2 blank, he sees the error notification on clicking the (Complete) button. It displays "2 errors have occurred" and shows the two validation errors "Value1 must be a valid number", and "Value2 must have some value".

Figure 25-48 Automatic Field Types and Required Fields Validation

25.1.9.1.8.9.2 Making Items Conditionally Read Only#

Make task page items editable only for the assigned owner while the task is active.

To make the page items editable only for the assignee, and only while the status of the task is Assigned, you can use the Read Only settings on P6_VALUE1 and P6_VALUE2 as shown below. The fields are be read-only if no row is returned by the query below. It returns a row when the task exists, the current user is the actual owner, and the status is ASSIGNED. Otherwise, no row is returned, and the fields render read-only.

select task_id
from apex_tasks
where task_id      = :P6_TASK_ID
  and actual_owner = :APP_USER
  and state_code   = 'ASSIGNED'
Figure 25-49 Making Items Read Only Unless User is Assigned Owner

25.1.9.1.8.9.3 Loading Parameter Values Into Items#

Load updatable task parameter values into page items before rendering.

Using an appropriate computation in the Pre-Rendering section of the page, as shown below, you can load the value of P6_VALUE1 from the corresponding updatable task parameter using its Static ID P_VALUE1.

The PL/SQL expression follows, referencing the value of the task id in a hidden page item:
apex_human_task.get_task_parameter_value(:P6_TASK_ID,'P_VALUE1')

You can add a similar computation to load the value of parameter P_VALUE2 into P6_VALUE2. The figure shows the P6_VALUE1 computation selected in the component tree and highlights its PL/SQL expression described above that retrieves the value of an updateable task parameter from the current task instance.

Figure 25-50 Loading Task Parameter Values Into Page Items Before Page Render

25.1.9.1.8.9.4 Validating Parameter Page Items#

Validate task parameter page items before saving their values back to the task.

You can use other page features like validations to perform further data checks before letting the parameter values be saved back to the task. For example, imagine a rule that the two numbers provided must be distinct. You can enforce this by adding a Values Need to Be Different validation as shown below. It configures an appropriate error message and uses the following PL/SQL expression using the NV function to reference the value of a page item as a number.

nv('P6_VALUE1') != nv('P6_VALUE2')

To ensure the validation is performed only when the user clicks the SAVE or COMPLETE buttons to submit the page, you can add a Server-side Condition that the request is contained in the /SAVE/COMPLETE/ value. You can use any value separator other than slash as well.

Figure 25-51 Configuring a Custom Validator for Page Items in Task Details Page

As shown below, now any user who tries to submit the same value for both page items sees an error "The two values need to be different" and must try again.

Figure 25-52 Validation Error if User Enters Same Number Twice

25.1.9.1.8.9.5 Saving Page Items to Task Parameters#

Save validated page item values back to updatable task parameters.

If the user's page item values pass validation, you need to save them back to the updatable task parameters. As shown below, you can do this with an Execute Code page process configured to fire only when the user clicks the SAVE or COMPLETE buttons to submit the page. The code to save the parameters looks like this:
apex_human_task.set_task_parameter_values(
  :P6_TASK_ID,
  apex_human_task.t_task_parameters(
    1 => apex_human_task.t_task_parameter('P_VALUE1', :P6_VALUE1),
    2 => apex_human_task.t_task_parameter('P_VALUE2', :P6_VALUE2)));
Figure 25-53 Saving Task Parameter Values

25.1.9.1.8.9.6 Working with a Business Entity Row#

Show or update the related business row from a task-specific details page.

If your task is tied to a business entity row and you use a Details Primary Key, you may need to view data from that row in your Task Details page to provide additional context. You also might want the actual owner to store additional data right away into columns in that row. Add a Form region to your Task Details page to display and edit the appropriate columns from this related data row. If you do, adopt one of the following approaches.

Suppose that your form region's primary key page item is named P6_ID. For the form to load its existing column data, populate the value of P6_ID with the Details Primary Key in the Pre-Rendering section of your page before the Form - Initialization page process executes. Do that in one of two ways:
  • Use the hidden P6_TASK_ID to query the DETAIL_PK from the APEX_TASKS view, or
  • Edit the task definition's Task Details Page URL to pass P6_ID as &DETAIL_PK.
To adopt the first approach, add a Pre-Rendering computation for P6_ID with the query:
select detail_pk
  from apex_tasks
 where task_id = :P6_TASK_ID

With the second approach, the P6_TASK_ID value is already set by the Task Details Page URL.

In either case, the Form - Initialization page process in the Pre-Rendering section of your page loads all of the Form region's columns using that primary key value. Enable the Query Only switch on any columns that should be read-only in the page. If you let the assignee edit any of the columns to acquire additional data, then ensure you have a Form - Automatic DML page process related to the form region in your page's Processing tab. It should have a server-side condition to trigger on for SAVE and COMPLETE buttons. It automatically saves the changes back to the business entity row when the user submits the page. For an approval task, the buttons are named APPROVE and REJECT instead of COMPLETE.

Tip:

If updating the business entity row immediately is not appropriate, then use updatable task parameters instead to capture the pending additional data. Then define a task action on the Complete event to update business entity row columns with task parameter values at completion time.

25.1.9.1.11.3.1 Determining Assignees Generically#

Use a package function to determine potential owners for a task at runtime.

The Purchase Office Supplies task definition in this example configures a static username DAVID as the business administrator, and uses an Expression to call an OWNERS_FOR_TASK function in the package YOUR_APP_PKG. This dynamically determines the list of potential owners. It returns a comma-separated list of case-sensitive usernames. The function accepts the task ID and primary key of the business entity related to the task instance as parameters. The Purchase Office Supplies task definition does not use an associated business entity row, nor does it accept a Details Primary Key at creation time. However, this function signature illustrates a best practice for providing key information that generic task owner assignment logic typically needs.

The figure shows the Purchase Office Supplies task definition in the Task Definition edit page in App Builder and highlights the PL/SQL expression in use for its Potential Owner participants:
your_app_pkg.owners_for_task(:APEX$TASK_ID,:APEX$DETAILS_PK)
Figure 25-54 Participants for Purchase Office Supplies Task Definition
The your_app_pkg specification looks like this:
package your_app_pkg as
    -------------------------------------------------
    -- Returns comma-separated list of case-sensitive
    -- usernames who are potential owners for task id
    -- passed in
    -------------------------------------------------
    function owners_for_task(
        p_task_id   in number,
        p_detail_pk in varchar2)
        return         varchar2;
    -------------------------------------------------
    -- Returns comma-separated list of email
    -- addresses of potential owners for task id
    -- passed in
    -------------------------------------------------
    function owner_emails_for_task(
        p_task_id   in number,
        p_detail_pk in varchar2)
        return         varchar2;
end your_app_pkg;

25.1.9.1.11.3.2 Computing Subject and Action Values#

Compute task subject text and action values in the Actions Source query.

The Purchase Office Supplies task definition in this example accepts two parameters. As shown below, one required parameter, P_ITEMS_TO_BUY, lets the requester list the items to purchase. A second optional parameter, P_STORE, lets them indicate a preferred store to purchase the items from. If the initiator provides both items and a preferred store, then the Subject should be:
Please Buy Coffee and Bagels at Zabar's
In contrast, if the user only supplies a value for the items to buy, the Subject should be:
Please Buy 2GB Credit Card Sized Hard Drive

The task definition Subject field does not allow HTML Directives for conditional substitution, but it is still straightforward to get a computed task subject. The figure shows the Parameters tab of the Task Definition edit page in App Builder showing one required parameter P_ITEMS_TO_BUY and one optional parameter P_STORE.

Figure 25-55 Parameters for Purchase Office Supplies Task Definition

Your Actions Source Query can retrieve one row containing any number of columns to provide information to reference in the task definition's Subject or actions. Notice below that the query computes a dynamic subject line using a CASE expression to include the optional store information in the title.

It also invokes the OWNER_EMAILS_FOR_TASK function to retrieve a comma-separated list of emails of the potential owners. You can then reference the COMPUTED_SUBJECT and POTENTIAL_OWNER_EMAILS column aliases wherever needed in the task definition. The query looks like this:
select 'Please Buy '
       ||:P_ITEMS_TO_BUY
       ||case
            when :P_STORE is not null
            then ' at '||:P_STORE
         end as computed_subject,
       your_app_pkg.owner_emails_for_task(:APEX$TASK_ID, :APEX$DETAIL_PK)
             as potential_owners_emails
from dual

As shown below, the task definition's Subject references the dynamically computed subject using the &COMPUTED_SUBJECT. notation. The figure shows the Settings tab of the Task Definition edit page in App Builder and highlights how the Subject can use &COMPUTED_SUBJECT. to reference a column from the Actions SQL Query.

Figure 25-56 Actions Source Query Retrieves Computed Title and Potential Owner Emails

25.1.9.1.11.3.3 Formatting Email with Templates#

Format task notification emails with templates and placeholder values.

When sending email, you can reference an Email Template. This shared component defines the email subject line along with a message header, body, and footer. These sections can all use #NAME# substitutions as placeholders for data you pass in anywhere you use the template to send a mail. Notice how the Notify New Task email template below references a placeholder name #NEW_TASK_SUBJECT# in the Email Subject field.

Figure 25-57 Email Template Used to Send New Task Notification

In the HTML Format section of the email template edit page, you can see this simple example only defines a static HTML body, but this section as well as Header and Footer can also mention additional placeholders if needed.

Figure 25-58 Email Body HTML in the Template for New Task Notification

25.1.9.1.11.3.4 Sending Email on Task Creation#

Send an email on task creation using Actions Source values and template placeholders.

In the Actions section, the Purchase Office Supplies action task definition configures a single action on the Create task lifecycle event. The action type is Send E-Mail.

Figure 25-59 Create Action on Purchase Office Supplies Task Definition

Notice below how the Create action's To field references the Actions Source Query column POTENTIAL_OWNERS_EMAILS in the Send Email Settings section. It also uses the Notify New Task email template. Clicking on the (Set Placeholder Values) button opens the Placeholders Grid dialog shown below. It lets you configure values for all placeholders in the email template. Here, the NEW_TASK_SUBJECT placeholder uses the task's subject with the &APEX$TASK_SUBJECT. syntax.

Figure 25-60 Send E-Mail Action on Task Create Event

25.1.9.1.11.3.5 Studying Generic Assignee Code#

Study package functions that return task owners and their email addresses.

The your_app_pkg contains two generic functions for returning the list of potential owners for a task, as well as a list of their emails. The package body code looks like the following. Notice the OWNER_EMAILS_FOR_TASK queries the APEX_TASK_PARTICIPANTS view using the supplied task id to find the potential owner participants, and joins that view with the APEX_WORKSPACE_APEX_USERS view to get the email addresses of the potential owners. It uses the LISTAGG function with a comma as the delimiter to return a single string of distinct email addresses.

package body your_app_pkg as
    -------------------------------------------------
    function owners_for_task(
        p_task_id   in number,
        p_detail_pk in varchar2)
        return         varchar2
    is
       l_ret varchar2(4000);
    begin
        -- Your custom logic here to return comma-separated
        -- list of case-sensitive user names.
        return l_ret;
    end owners_for_task;
    -------------------------------------------------
    function owner_emails_for_task(
        p_task_id                in number,
        p_detail_pk              in varchar2)
        return         varchar2
    is
        l_ret varchar2(4000);
    begin
         select listagg(distinct u.email,',')
           into l_ret
           from apex_task_participants p
           join apex_workspace_apex_users u
             on u.user_name = p.participant
          where p.task_id = p_task_id
            and p.participant_type = 'POTENTIAL_OWNER';
         return l_ret;
    end owner_emails_for_task;
end your_app_pkg;

25.1.9.1.11.4.1 Updating Entity Columns on Complete#

Update the related business entity row when an approval task completes.

Assume a Salary Change task definition uses the primary business entity table EMP as its Actions Source table. The task definition defines parameters like:
  • P_PROPOSED_SALARY – to store the suggested new employee monthly pay, and
  • P_MOTIVATION – to let the requester summarize the reason for the change.

The task definition defines a subject like the following, referencing the value of the Action Source table ENAME column and the parameter name.

Review &ENAME. Salary Change to &P_PROPOSED_SALARY.
When a Salary Change task completes successfully, it has an outcome of either APPROVED or REJECTED. Your Execute Code action on the Complete event needs to update the SAL column to the P_PROPOSED_SALARY value for EMP table row related to the task instance. Your code looks like:
-- If approved, update sal using Action Table EMPNO column value
if :APEX$TASK_OUTCOME = 'APPROVED' then
   update emp
      set   sal = :P_PROPOSED_SALARY
    where empno = :EMPNO;
end if;

25.1.9.1.11.4.2 Updating Request Columns on Complete#

Update request and business entity rows when an approval task completes.

Consider a Salary Change task definition that uses the following EMP_SAL_CHANGE_REQUEST table as its Actions Source table. A row in this table represents the request to change a particular employee's salary.

create table emp_sal_change_request (
    id            number
                  generated by default on null as identity
                  constraint emp_sal_change_request_id_pk primary key,
    empno         number
                  constraint emp_sal_change_request_empno_fk
                  references emp,
    new_salary    number,
    motivation    varchar2(255 char),
    status        varchar2(8 char)
);

For this task definition, the built-in Details Primary Key parameter suffices, since you can already reference all the columns in the EMP_SAL_CHANGE_REQUEST row it identifies. There is no need to define additional parameters.

Notice the EMP_SAL_CHANGE_REQUEST table includes an EMPNO foreign key column. So, to show the ENAME value in the task subject, you need to change from using the Actions Source table to using a query instead. This lets you join in the EMP table to include the related ENAME and existing SAL value using a query like this:
select r.id as request_id, r.empno, r.new_salary, e.ename, e.sal
  from emp_sal_change_request r
  join emp e
    on e.empno = r.empno
 where r.id = :APEX$TASK_PK

Notice above that if your task definition subject or actions don't need to reference the current value of particular columns like MOTIVATION and STATUS you can leave those out of the SELECT list.

Your task definition can now use a subject like the following, referencing the value of the Action Source query's ENAME, SAL, and NEW_SALARY columns.

Review &ENAME. Salary Change from &SAL. to &NEW_SALARY.
As before, when a Salary Change task completes successfully, it has an outcome of either APPROVED or REJECTED. Your action on the Complete event of type Execute Code needs to:
  • Record the outcome of the salary change request in the STATUS column, then
  • Update the SAL column to the NEW_SALARY value for EMP table row identified by the EMPNO value in the employee salary change request row related to the task instance.
Your code looks like:
-- Record the outcome in the salary change request row
update emp_sal_change_request
   set status = :APEX$TASK_OUTCOME
 where id = :REQUEST_ID;
-- If approved, update sal using Action Table query EMPNO column value
if :APEX$TASK_OUTCOME = 'APPROVED' then
   update emp
      set   sal = :NEW_SALARY
    where empno = :EMPNO;
end if;