Attaching Concurrent Program Output as Workflow Email Notification Attachment

This article explains how to attach a concurrent program output as a Workflow email notification.

Prerequisites

  1. DBA Directory should be created with Concurrent Program output path
  2. A new document category should be created under “Application Developer –> Attachments –> Document Categories”

Steps to Implement

  1. Call API “FND_DOCUMENTS_PKG.INSERT_ROW” to detail the information of document to be created
  2. Insert a BLOB type of record into “FND_LOBS” table
  3. Use “DBMS_LOB” API to read the output from Concurrent Program output path and point it to the BLOB type of record created in step 2


Below is the sample PL/SQL procedure which takes Concurrent Request ID as input parameter and returns the media_id of the BLOB document created

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
PROCEDURE load_inv_pdf_p (
   p_conc_req_id         IN       apps.fnd_concurrent_requests.request_id%TYPE,
   p_project_number      IN       apps.pa_projects_all.segment1%TYPE,
   p_draft_invoice_num   IN       apps.pa_draft_invoices_all.draft_invoice_num%TYPE,
   p_file_id             OUT      apps.fnd_lobs.file_id%TYPE
)
-- +===================================================================+
-- | Name             : load_inv_pdf_p                                 |
-- | Description      : Loads a file to FND_LOBS as a BLOB and returns |
-- |                    BLOB file_id                                   |
-- | Parameters       :                                                |
-- |                                                                   |
-- |   p_conc_req_id      --> Concurrent Request ID                    |
-- |   p_project_number   --> Project Number                           |
-- |   p_draft_invoice_num--> Draft Invoice Number                     |
-- |   p_file_id          --> File ID from fnd_lobs                    |
-- |                                                                   |
-- +===================================================================+
IS
   lr_row_id              ROWID;
   ln_document_id         NUMBER;
   ln_media_id            NUMBER;
   lb_blob_data           BLOB;
   ln_category_id         NUMBER;
   lb_blob                BLOB;
   lb_bfile               BFILE;
   lc_actual_file_name    VARCHAR2 (200);
   lc_display_file_name   VARCHAR2 (200);
   lc_description         VARCHAR2 (400);
   lb_go                  BOOLEAN := TRUE;
   ln_count               NUMBER;
   lc_cp_short_name       apps.fnd_concurrent_programs.concurrent_program_name%TYPE;
--
BEGIN
--
--Finding CP Short name based on request ID
--
   SELECT program_short_name
     INTO lc_cp_short_name
     FROM apps.fnd_conc_req_summary_v
    WHERE 1 = 1 AND request_id = p_conc_req_id;
 
   lc_actual_file_name := lc_cp_short_name || '_' || p_conc_req_id || '_1.PDF';
   lc_display_file_name :=
         'Project#'
      || p_project_number
      || '_DraftInvoice#'
      || p_draft_invoice_num
      || '.pdf';
   lc_description :=
         'Draft Invoice#'
      || p_draft_invoice_num
      || ' for Project#'
      || p_project_number;
--
   DBMS_OUTPUT.put_line ( 'lc_actual_file_name: ' || lc_actual_file_name);
   DBMS_OUTPUT.put_line ( 'lc_display_file_name: ' || lc_display_file_name);
 
--
   BEGIN
      SELECT category_id
        INTO ln_category_id
        FROM apps.fnd_document_categories_tl
       WHERE user_name = 'XX Project Draft Invoice PDF'
         AND LANGUAGE = USERENV ('lang');
   EXCEPTION
      WHEN OTHERS
      THEN
--
         lb_go := FALSE;
         p_file_id := -1;
         DBMS_OUTPUT.put_line (
                        'Error While Deriving Document Category ' || SQLERRM
                       );
--
   END;
 
--
--Create a FND DOCUMENT
--
   BEGIN
      DBMS_OUTPUT.put_line ( 'FND_DOCUMENTS_PKG.INSERT_ROW Call');
      apps.fnd_documents_pkg.insert_row
                        (x_rowid                       => lr_row_id,
                         x_document_id                 => ln_document_id,
                         x_creation_date               => SYSDATE,
                         x_created_by                  => fnd_global.user_id,
                         x_last_update_date            => SYSDATE,
                         x_last_updated_by             => fnd_global.user_id,
                         x_last_update_login           => fnd_global.user_id,
                         x_datatype_id                 => 6,
                         --Indicates BLOB type of data
                         x_category_id                 => ln_category_id,
                         x_security_type               => 1,
                         x_publish_flag                => 'Y',
                         x_usage_type                  => 'S',
                         x_start_date_active           => SYSDATE,
                         x_request_id                  => fnd_global.conc_request_id,
                         x_program_application_id      => fnd_global.prog_appl_id,
                         x_program_update_date         => SYSDATE,
                         x_language                    => fnd_global.current_language,
                         x_description                 => lc_description,
                         x_file_name                   => lc_display_file_name,
                         x_media_id                    => ln_media_id
                        );
      DBMS_OUTPUT.put_line ( 'file_id: ' || ln_media_id);
      --Setting out parameter p_file_id
      p_file_id := ln_media_id;
--
   EXCEPTION
      WHEN OTHERS
      THEN
         lb_go := FALSE;
         p_file_id := -1;
         DBMS_OUTPUT.put_line (
                           'Error during FND_DOCUMENTS_PKG.INSERT_ROW Call '
                        || SQLERRM
                       );
   END;
 
--
--Creating Empty BLOB with reference to the above ceated document
--
   BEGIN
      lb_blob_data := EMPTY_BLOB ();
      DBMS_OUTPUT.put_line ( 'Inserting to FND LOBS');
 
      INSERT INTO apps.fnd_lobs
                  (file_id, file_name,
                   file_content_type, upload_date, expiration_date,
                   program_name, program_tag, file_data,
                   LANGUAGE, oracle_charset, file_format
                  )
           VALUES (ln_media_id, lc_display_file_name,
                   'application/octet-stream', SYSDATE, NULL,
                   'FNDATTCH', NULL, lb_blob_data,
                   fnd_global.current_language, NULL, 'binary'
                  )
        RETURNING file_data
             INTO lb_blob;
   EXCEPTION
      WHEN OTHERS
      THEN
         lb_go := FALSE;
         p_file_id := -1;
         DBMS_OUTPUT.put_line (
                        'Error during Insert into FND_LOBS ' || SQLERRM
                       );
   END;
 
--
   BEGIN
--
--Loading physical file to LOBS table as a BLOB
--
      DBMS_OUTPUT.put_line ( 'Loading File to Lobs');
--
--Getting a pointer for Physical file
--
      lb_bfile := BFILENAME ('XX_CP_OUTPUT_DIR', lc_actual_file_name);
      --
      --Open the file with the pointer returned above
      --
      DBMS_LOB.fileopen (lb_bfile);
      --
      --load the file from disk to the table directly using lb_blob created in previous insert statement
      --
      DBMS_LOB.loadfromfile (lb_blob, lb_bfile,
                             DBMS_LOB.getlength (lb_bfile));
      --
      --close the file after storing it in the table
      --
      DBMS_LOB.fileclose (lb_bfile);
   --
   EXCEPTION
      WHEN OTHERS
      THEN
         DBMS_OUTPUT.put_line (
                           'There is no such File or Directory..File Name : '
                        || lc_actual_file_name
                        || ' Error : '
                        || SQLERRM
                       );
--
         ROLLBACK;
   END;
 
--
   COMMIT;
--
END load_inv_pdf_p;

Attaching the BLOB document to workflow Notification

Below is the package to set BLOB file file_id to workflow notification

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
SET serveroutput ON;
 
CREATE OR REPLACE PACKAGE "BOLINF"."XX_TEST1_PKG" AUTHID CURRENT_USER
IS
   PROCEDURE set_att_invoice_media_id (
      p_item_key   apps.wf_notifications.item_key%TYPE
   );
 
   --
   PROCEDURE notif_attach_procedure (
      document_id     IN       VARCHAR2,
      display_type    IN       VARCHAR2,
      document        IN OUT   BLOB,
      document_type   IN OUT   VARCHAR2
   );
END xx_test1_pkg;
/
 
--
--
 
CREATE OR REPLACE PACKAGE BODY "BOLINF"."XX_TEST1_PKG"
AS
-- +===================================================================+
-- | Name             : notif_attach_procedure                         |
-- | Description      : Attach draft invoice to workflow notification  |
-- | Parameters       :                                                |
-- |                                                                   |
-- |   document_id   --> Unique String to identify document            |
-- |   display_type  --> type used for notification presentation       |
-- |   document      --> The outbound LOB locator pointing to where    |
-- |                       the document text is stored                 |
-- |   document_type --> The outbound text buffer where the document   |
-- |                       content type is returned                    |
-- |                                                                   |
-- | RETURNS          : No Return                                      |
-- +===================================================================+
   PROCEDURE notif_attach_procedure (
      document_id     IN       VARCHAR2,
      display_type    IN       VARCHAR2,
      document        IN OUT   BLOB,
      document_type   IN OUT   VARCHAR2
   )
   IS
      lob_id         NUMBER;
      bdoc           BLOB;
      content_type   VARCHAR2 (100);
      filename       VARCHAR2 (300);
   BEGIN
      --
      DBMS_OUTPUT.put_line ('XX In notif_attach_procedure');
      --
      --–set_debug_context('notif_attach_procedure');
      lob_id := TO_NUMBER (document_id);
	  --
	  --Creating a temporary blob type of document
	  --
      DBMS_LOB.createtemporary (bdoc, FALSE, DBMS_LOB.CALL);
      --
      DBMS_OUTPUT.put_line ('XX lob_id: ' || lob_id);
 
      --
      -- Obtain the BLOB version of the document
	  --
      SELECT file_name, file_content_type, file_data
        INTO filename, content_type, bdoc
        FROM apps.fnd_lobs
       WHERE file_id = lob_id;
 
      --
      document_type := content_type || ';name=' || filename;
      --
	  --Copying the blob document 'document' to the temporary blob document 'bdoc'
	  --
      DBMS_LOB.COPY (document, bdoc, DBMS_LOB.getlength (bdoc));
      --
      DBMS_OUTPUT.put_line ('XX End of notif_attach_procedure');
   --
   EXCEPTION
      WHEN OTHERS
      THEN
         --
         DBMS_OUTPUT.put_line (   'XX Exception in notif_attach_procedure :'
                               || SQLERRM
                              );
   --
   END notif_attach_procedure;
 
--
--
   PROCEDURE set_att_invoice_media_id (
      p_item_key   apps.wf_notifications.item_key%TYPE
   )
   IS
-- +===================================================================+
-- | Name             : set_att_invoice_media_id                       |
-- | Description      : To trigger workflow                            |
-- | Parameters       :                                                |
-- |                                                                   |
-- |   p_item_key   --> Unique String to identify workflow instance    |
-- |    															   |
-- +===================================================================+   
      l_process    VARCHAR2 (20)                         := 'XX_TEST_PROCESS';
      l_itemtype   VARCHAR2 (8)                          := 'XXTEST1';
      l_itemkey    apps.wf_notifications.item_key%TYPE   := p_item_key;
      l_user_key   apps.wf_notifications.user_key%TYPE   := 'XX-TEST-11-PDF';
      l_doc_id     VARCHAR2 (20)                         := '417633'; --media_id generated from procedure load_inv_pdf_p
   --
   BEGIN
      --
      DBMS_OUTPUT.put_line ('XX Begin set_att_invoice_media_id');
      --
      --
      --Creating Workflow Process
      --
      apps.wf_engine.createprocess (itemtype        => l_itemtype,
                                    itemkey         => l_itemkey,
                                    process         => l_process,
                                    user_key        => l_user_key,
                                    owner_role      => 'SYSADMIN'
                                   );
      --
      DBMS_OUTPUT.put_line ('XX Setting Attribute');
      --
      apps.wf_engine.setitemattrtext
              (itemtype      => l_itemtype,
               itemkey       => l_itemkey,
               aname         => 'XX_BLOB_DOC_ID',
               avalue        => TO_CHAR
                                   (l_doc_id)
                                          --doc_id must be in character format
              );
      --
      --Start Process
      --
      apps.wf_engine.startprocess (itemtype      => l_itemtype,
                                   itemkey       => l_itemkey
                                  );
      --
      DBMS_OUTPUT.put_line ('XX End set_att_invoice_media_id');
   --
   EXCEPTION
      WHEN OTHERS
      THEN
         --
         DBMS_OUTPUT.put_line (   'XX Exception in set_att_invoice_media_id :'
                               || SQLERRM
                              );
   --
   END set_att_invoice_media_id;
END xx_test1_pkg;
/

Create a workflow with two types of attributes, one is of type ‘Text’ (XX_BLOB_DOC_ID) and other is of type ‘Document’ (XX_DRAFT_INV_FILE_ATTACH)

Workflow Structure:
Wokflow Structure

Workflow Process
Wokflow Process

Document type attribute
Document_Type_Attribute

Value: plsqlblob:XX_TEST1_PKG.notif_attach_procedure/&XX_BLOB_DOC_ID

Assigning Document type attribute to a message
Document_Type_Attribute_Under_Message

file_id of blob document should be set to attribute XX_BLOB_DOC_ID and it should be passed as parameter to Document type attribute XX_DRAFT_INV_FILE_ATTACH which in turn calls procedure “XX_TEST1_PKG.notif_attach_procedure” which copies the original blob document in fnd_lobs to workflow notification.

Below is the block of code to trigger the workflow

SET SERVEROUTPUT ON;
DECLARE
   l_item_key   NUMBER := 101;
BEGIN
   xx_test1_pkg.set_att_invoice_media_id (l_item_key);
END;
/

You can download a copy of workflow here.