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:
- From the bpi.form.phantom.menu (see admin interfaces)
- Navigate up/down until the cursor is on the desired thread.
- Press space bar to select it. (you'll see a > charater to the left to indicate that it is selectted)
- Press (V) to view
- 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() |
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.