Post Processing of a Document
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.
BPI.FORM.PHANTOM.CONTROL record format
The records in the BPI.FORM.PHANTOM.CONTROL file/directory can be arranged as the user sees fit using a tag=value param file format.
|
Tag Names |
Description |
|
key |
Key of phantom. Usually a word to describe the thread (e.g., LOC1) to define the phantom for Location 1 |
|
order |
This is a right justified numeric value that is used by the bpi.form.phantom.menu process to sort the phantom threads into right-justified ascending order when displayed in the menu. |
|
status |
Current status of the phantom (e.g. active) |
|
description |
A text description of the phantom |
|
account |
MultiValue account in which this phantom will run |
|
|
|
|
queuePath |
Path to the directory where queued documents will be placed by the app (directory to poll) and where this phantom thread should look for them |
|
archivePath |
Path where documents will be moved after processing. If not specified, document will be deleted and not moved to archive. |
|
ooServerAddress |
hostname or ip address of the OpenOffice server to be used for this thread. |
|
user |
unix username to be used when phantom communicates with OpenOffice server to issue ssh directives. |
|
remoteDefaultPrinter |
printer name on the OpenOffice server to which print jobs from this thread will be sent. |
|
remoteQueuePath |
the path on the remote OpenOffice server where it will find the document to be printed (the mount point and path on the OpenOffice server). Usually, this is a directory on the Linux OpenOffice Printer Server (LOOPS) under the /mnt directory. To this directory is mounted a CIFS/SAMBA share exposed on the MultiValue host. |
|
killSignal |
On release > 3.2 (Mar 2020) Now, you can specify a full path to a record such as
On all releases Item ID of a record in the CONTROL file. The phantom will check for the existence of this record during its polling cycle. Phantom will terminate if this record ID is found in CONTROL. (aka the Kill record) |
|
phantomWait |
If specified is the number of second the phantom will sleep when there are no records found in the queue before it will check the queue again. Note, as long as records are present, the phantom will not sleep again until zero records are found in the queue directory. Default is 5 seconds. 1/25/2018: on jBASE releases of BP Forms, you may optionally specify a unix/linux command to run instead of a numeric value for sleep. For example, "sleep 2" and it will invoke the unix sleep command rather than using the jBASE JBC SLEEP function. Using the *nix sleep command is preferred if your company routinely changes the system clock as it works more efficiently in these situations and prevents the phantom from sleeping for longer periods than specified. |
|
postProcess |
Implemented at release 4.2.1 (10/16/2025). A command that will be issued after the document has rendered, and before it is moved or deleted. You may use this to implement your own post processing hooks. You make use the tokens %formId% and %verbose% within the command, and these will be expanded properly for the rendered document. |
|
localTestPageDirective |
A unix command to issued on the MultiValue host to place a test document into the queue directory. Generally, you’ll copy a .odt file from the template directory to the queue directory and this will cause that document to print. |
|
remote TestPageDirective |
Not yet implemented |
|
switches |
-v{n} sets level of verbosity for log files where n is an optional integer indicating level of verbosity. -f causes the BPI.PHANTOM.CONTROL definition record to be re-read each time the phantom reaches the top of its loop. This allows the phantom to recognize changes made to the BPI.PHANTOM.CONTROL record while the phantom is running. If this switch is not set, then the phantom must be stopped and restarted to cause the phantom to re-read its BPI.PHANTOM.CONTROL record. |
Example BPI.FORM.PHANTOM.CONTROL record:
key=bpiform1order=1status=activedescription=bpiform1 form printaccount=MYACCOUNTqueuePath=/uddata/accts/BPIFORMS/bpi_forms/queue/bpiform1archivePath=/uddata/accts/BPIFORMS/bpi_forms/archive/bpiform1/%filename%ooServerAddress=192.168.001.001user=bpiform1remoteDefaultPrinter=Q0remoteQueuePath=/mnt/prodserver.bpi_forms/queue/bpiform1killSignal=/db/accts/BPIFORMS/BPI.CONTROL/KILL-FORMPHANTOM-%ident%phantomWait=1localTestPageDirective=!cp /uddata/accts/BPIFORMS/bpi_forms/templates/bluePrairieFormsTestPage.odt /uddata/accts/BPIFORMS/bpi_forms/queue/bpiform1/remotePrinter___bluePrairieFormsTestPage.odtswitches=-v1 -f
Note: Unless the -f switch is set, the phantom thread must be restarted (bounced) in order for changed parameters to take effect.