Business Software: 303|905-4110
Bookkeeping Services: 303|905-6645
sales@cloudstreetportal.com

This feature was implemented in BP Forms release 4.1.2 (10/17/2025)

When the BP Forms Phantom Thread processes a document, after the output is produced (to a printer or to PDF for example), before the .odt file is archived or removed, there is now an option to execute a program to perform special handling.  This program is created by you, the customer.  

NOTE: THIS FEATURE IS EXECUTED BY THE PHANTOM, NOT BY YOUR PRINTING PROGRAM AND ITS API.

This program can do virtually anything with the output including:

  • Renaming documents
  • Moving them to specialized storage or directory structures
  • Emailing them
  • Sending them to a portal
  • Etc.

The program can be a UNIX command or a BASIC program.  It just needs to be visible to the user and account that is running the phantom thread.  

You name the postProcess program in the parameters of your phantom thread.  To view and edit phantom thread parameters:

  1. From the bpi.form.phantom.menu (see admin interfaces)
  2. Navigate up/down until the cursor is on the desired thread.
  3. Press space bar to select it. (you'll see a > charater to the left to indicate that it is selectted)
  4. Press (V) to view
  5. Press (E) to edit

You'll see something like this (uses your default text editor, your view may vary):

File BPI.FORM.PHANTOM.CONTROL , Record 'bpiform1.1' Insert 17:03:28
Command->
0001 key=bpiform1.1
0002 order=1
0003 status=active
0004 description=bpiform1.1 form print
0005 account=MYACCOUNT
0006
0007 queuePath=/dbms/BPIFORMS/bpi_forms/queue/bpiform1
0008 archivePath=
0009 ooServerAddress=10.25.37.46
0010 user=bpiform1.1
0011 remoteDefaultPrinter=Q0
0012 remoteQueuePath=/mnt/REMOTEMOUNTNAME/bpi_forms/queue/bpiform1
0013 killSignal=CONTROL/KILL-FORMPHANTOM-%ident%
0014 phantomWait=1
0015 postProcess=BPI.MOVE.DOC %formId% -v%verbose%
0016 localTestPageDirective=cp /dbms/BPIFORMS/bpi_forms/templates/bluePrairieFormsTestPage.odt /dbms/BPIFORMS/bpi_forms/queue/bpifo
0017
0018 switches=-v5

 

The other param names are documented in the admin interfaces topic, we'll focus on postProcess here.

In the example above, we've declared that this thread will call a BASIC program called BPI.MOVE.DOC.  We've passed two parameters to this program.  The first is a token called %formId% and the second is the switch -v followed by the token %verbose%.

The tokens will be expanded into values based on the document being processed where:

  • The token %formId% will be converted to the document (form) that is being processed by the phantom.
  • The token %verbose% will be converted to the verbosity specified in the switches parameter (see attribute 18 in the example above).  So in this example, the token would be converted to a 5.

BPI.MOVE.DOC

Here is an example program followed by an explanation

 * 10/16/2025 by Bruce Decker, Blue Prairie, Inc.
 * This is open source with no restrictions
 * No warranty provided for this code.
 * Call as:
 * BPI.MOVE.DOC <fileName> -v<verbosity>
 * Where:
 * BPI.MOVE.DOC is the name of this program
 * <fileName> is the filename (form id), assumed to be in pdfPath (see init)
 * -v is the switch specifying verbosity
 * <verbosity> is an integer from 0 to whatever indicating level
 *----------------------------------------------------------------
 GOSUB Init
 GOSUB ParseSentence
 GOSUB MoveDoc
 GOTO Exit
 * ---------------------------------------------------------------
 * Subroutines
 *
 MoveDoc:
    IF verbose GE 3 THEN
       CRT pgmId:\::MoveDoc()\
    END
    OPEN pdfDir TO f.pdfDir THEN
       baseFileName = FIELD(fileName, \.\, 1)
       printerName = FIELD(baseFileName, "___", 1)
       docName = FIELD(baseFileName, "___", 2)
       docNameArray = CHANGE(docName, "_", @AM)
       docNameArray = CHANGE(docNameArray,\-\,@AM)
       docNameArray = CHANGE(docNameArray,\.\,@AM)
       monthYear = docNameArray<4>
       year = monthYear[3,2]+2000 \R%4\; *call me in year 2100 for assistance changing :-)
       month = monthYear[1,2] \R%2\
       pdfDocName = docNameArray<1>:\_\:docNameArray<2>:\-\:docNameArray<3>:\-\monthYear:\.pdf\
       subDir = moveToDir:delim:year:\-\:month
       GOSUB MakeDirIfRequired
       IF subDirOpened THEN
          directive = CHAR(255):\kmv \:pdfDir:delim:baseFileName:\.pdf\:SPACE(1):subDir:delim:docName:\.pdf\
          IF verbose GE 1 THEN
             CRT \directive=\:directive
             EXECUTE directive
          END ELSE
             EXECUTE directive CAPTURING screen RETURNING errors
          END
       END
    END ELSE
       CRT \BPI.MOVE.DOC: error opening \:pdfDir
    END
 RETURN
 *
 MakeDirIfRequired:
    IF verbose GE 3 THEN
       CRT pgmId:\::MakeDirIfRequired\
    END
    subDirOpened = @FALSE
    OPEN subDir TO f.subDir THEN
       subDirOpened = @TRUE
    END ELSE
       directive = CHAR(255):\kmkdir \:subDir
       IF verbose GE 3 THEN
          EXECUTE directive
       END ELSE
          EXECUTE directive CAPTURING screen RETURNING errors
       END
       OPEN subDir TO f.subDir THEN
          subDirOpened = @TRUE
       END
    END
 *
 RETURN

 ParseSentence:
    fileName = \\
    verbose = 0
    sentence = CHANGE(@SENTENCE,SPACE(1),@AM)
    max = DCOUNT(sentence, @AM)
    FOR i = 2 TO max
       word = sentence<i>
       uc.word = OCONV(word, \MCU\)
       BEGIN CASE
       CASE uc.word[1,2] EQ \-V\
          IF NUM(word[3,-1]) THEN
             verbose = word[3,99]
           END
       CASE fileName EQ \\
          fileName = sentence<i>
       END CASE
    NEXT i
 RETURN
 *
 Init:
    pgmId = \BPI.MOVE.DOC\
    pdfDir = "/dbms/BPIFORMS/bpi_forms/PDF"
    moveToDir = "/dbms/BPIFORMS/bpi_forms/PDF"
    delim = "/"
 RETURN
 *
 Exit:
    CLOSE f.pdfDir
    CLOSE f.subDir

In the example above, the document filename contained the year and month appended to the end of the filename like this:

PDF___INVOICE_1-123456-0125.odt

Where:

PDF___ is the printer name of PDF to direct BP forms to convert this .odt to .pdf

INVOICE is just a string indicating the document type (the print program decided this)

1-123456 is the invoice number from the ERP system used as part of the filename

0125 indicates Jan (01) of 2025 (25).

.odt is the suffix used for the queued document, a PDF will be produced replacing .odt with .pdf in the file name.

The program does the following:

  • Strips the PDF___ from the output filename
  • Converts the notation 0125 to 2025-01
  • Moves the original PDF stored at /dbms/BPIFORMS/bpi_forms/PDF/PDF___INVOICE_1-123456-0125.pdf to /dbms/BPIFORMS/PDF/2025-01/INVOICE_1-123456-0125.pdf

Note that while the BP Forms phantom can only tell the BPI.MOVE.DOC program the original .odt filename, we know that libreoffice will simple change the .odt to .pdf when it lands the final .pdf document into the PDF directory.  The rest of the file name is the same so we can use the .odt filename and just code knowing that the PDF is the same format except for the .pdf extension.

Of course, this program could do other things such as:

  • READING the invoice record from the ERP database to collect additional info, such as email address for sending invoices
  • Then, emailing the PDF to that email address
  • Sending the PDF to a portal (like cloudstreet portal for example)
  • Obfuscating the filename to improve security
  • Chaning permissions or ownerships
  • etc.

 Logging

Any output produced by your program will be captured into the BP Forms log file.  This is why it is useful to pass the %verbose% token to the program and write it to be aware of verbosity level defined for the thread (see switches).

In the example above, verbosity had been set to level 5 in the thread, so the BPI.MOVE.DOC program also had verbosity set to 5.  This resulted in the following output into the log:

00162 bpi.form.phantom::PostProcess()
00163 directive=BPI.MOVE.DOC PDF___INVOICE_1-507714-0416.odt -v5
00164 BPI.MOVE.DOC::MoveDoc()
00165 BPI.MOVE.DOC::MakeDirIfRequired
00166 directive=_kmv /dbms/BPIFORMS/bpi_forms/PDF/PDF___INVOICE_1-507714-0416.pdf /dbms/BPIFORMS/bpi_forms/PDF/2016-04/INVOICE_1-507714-0416.pdf

As you can see, the phantom saw that there was a post processing directive set for this phantom thread and it executed the program defined in the postProcess param of the thread's definition record.  The yellow highlights were output by the BPI.MOVE.DOC program and became part of the log for that thread in the appropriate place within the log record.