Hi SCN,
I've been doing some form developments over the past months and I noticed that the standard SAP print / driver programs of the different forms had some differences in them.
I tought it could come in handy to create an overview of what you can include and to explain some parts of the form driver program which will simplify creating your custom Adobe Form driver program.
Note: Before starting to create your custom driver program, inspect the already existing SAP standard driver programs. You might be able to copy and expand or even use already existing driver programs to process your form. Make sure that you use the correct driver program for your Adobe Form since the driver programs for Adobe Forms and Smart Forms have small differences.
The following topics will be covered in this blog:
If you have some remarks or suggestions, feel free to comment so I can improve my blog posts in the future.
Form Processing Flow
First of all it's important to know the complete flow of the form processing to fully understand the role of the driver program:
- You navigate to the transaction of the document you want to print (ex. Billing Document - VF02)
- In customizing some requirements (see 2. Form Customizing) are bound towards an output type, if all requirements are met, the output type gets added automatically to the document.
If you expect that the output type should be added but it's not, you can always enter the document in change mode (in display mode this can't be accessed) and navigate towards the output assignment. You can then select "Determination Analysis" from the "Goto" menu and analyze why your output type is not automatically added to the output types of the document.
The output type can always be added manually to your document. - Issue the the document towards the output type which has been set up in Customizing (see Form Customizing).
- The driver program, which is assigned in Customizing towards the output type, will be triggered and all the logic in the program will be executed.
This logic involves getting the data to correctly process the form (see Importing Parameters / Structures), getting the data of the document (see Gather Document Data), cancel form processing in case of issues (see Issue Handling), update the processing log (see Processing Log), pass the data towards the form and build the layout (see Calling the Form), handle post processing (see Post Processing) and Archive the form when this option has been selected in the Output (see Archiving). - Send the document towards the printer, mail / fax recipient, ...
As you can see the driver program plays a big role in the processing of the document so a good driver program will do a lot of work for you.
Form Customizing
The Customizing part of the form is actually out of the scope of this blog but some general knowledge on how the Cusomizing works will help you while creating the driver program. I will therefore give a short explanation what we can set up in Customizing.
Output is generated when several conditions are fulfilled. The basis of the process is the output determination process; based on the document type (ex. billing document) the output determination procedure is selected.
The output determination procedure contains the output types that can be generated as well as requirements that need to be fulfilled for the output to be created. You can maintain procedures in transaction VOFM under Requirements > Output Control. Standard procedures can be used or you can create your own procedure which sends SY-SUBRC = 4 back, if the requirement is not met. If a specific output can be generated according to the procedure, SAP will check if output should be generated for this specific situation.
To determine this, the system will check the user defined settings. These are defined in the condition tables, which are linked through the access sequence. An access sequence is a search strategy that the system uses to find valid data for a particular condition type. It determines the sequence in which the system searches for data. The access sequence consists of one or more accesses. The sequence of the accesses establishes which condition records have priority over others. The accesses tell the system where to look first, second, and so on, until it finds a valid condition record.
Steps that will be done to do the Output configuration:
- Creation of Adobe Form + Print program.
- Set-up of Output Type: a copy of the standard Output Type can be used as basis for the new Output Type.
- Set-up Access Sequence: the access sequence will show the search strategy that the system uses to find valid data for a particular condition type.
- Set-up Condition Table.
- Assign the Output Type to a Partner Function: in this step you assign the allowed output types to partner functions and specify the allowed type of output processing for the combination of output types and partner functions.
- Add output to the Output Determination procedure: in this step the output is added to an output determination procedure. To find the correct procedure some checks will be made.
- After the Customizing is done, conditions on the user side will be set up (VV71).
To see how Output Types and their processing routines are set up, you can go to the NACE transaction, highlight the line of the application area of your document and press Output Types. Select the Output Type you wish to investigate and you can see the "Mail title and texts" (sending the form via mail), "Processing routines" (driver program, form routine in driver program that is called, Adobe Form object and type of form) and "Partner functions" (default partner for every medium).
Importing Parameters / Structures
A lot of information about Form Processing has been set up via Customizing as mentioned in the previous chapter.
To be able to influence the Form Processing the data is passed towards our driver program as importing parameters, structures and variables.
Structure NAST: the message details of the Output Type.
Structure TNAPR: the processing routines of the Output Type.
In the Processing Rountines of the Customizing we defined a FORM Rountine which is the starting point in the driver program.
This FORM Rountine is called for starting the form processing and the parameters "return_code" and "us_screen" are passed from a previous SAP program (that initiates the form processing) towards the routine of our driver program:
FORM entry USING return_code us_screen.
When we are facing errors during form processing in the driver program, we can change this "return_code" variable (type sy-subrc) to 1 so that after leaving the driver program, the SAP system will be informed that the form processing resulted in an error. Return 0 when no error was found.
The "us_screen" variable (single character flag) will contain the value 'X' (= abap_true = not initial) in case the print is a print preview. In this case no spool job needs to be created and the form does not need to be printed physically.
In "Output & Document Parameters" we dive deeper into influencing the form processing based on the importing structures.
Clear Global Data
Maybe it's obvious but it's definately crucial to clear all your global driver program TOP include data at the start of your driver program.
You can create a form rountine that clears your TOP include data to prevent that old data is used which happened to me in the underneath scenario.
Ex.: Your print program is called several times in the same loop and the previous data is buffered into your global data tables.
Gather Document Data
A routine is created to gather all the data which needs to appear on the form.
We try to work with form routines as much as possible since they are easy maintainable, thus making the program well-structured.
The field NAST-OBJKY is the starting point for gathering the data since it holds the document number.
The driver program is the best point to gather your data which needs to appear on the form since the chances are high it can be reused for other forms in the same application area.
Ex: Invoice and Credit Note Header and Item data is stored in tables VBRK and VBRP, so 1 print program can be used for the processing of both forms.
The data can also be gathered in the Interface of the form but the downside of this approach is that the code can't be reused. You need to take a copy of the interface if you want 2 forms with the same coding, which means you have to maintain 2 forms when parts of the coding changes.
You could resolve this issue by creating an include file that you include in both Form Interfaces, but be aware that this can cause synchronization errors between the generated function module of the form and the include file when one of both changes.
Output & Document Paramaters
A lot of information, that has to influence form processing, is already passed towards the driver program as structures and variables and the remainder data will be gathered based on the already existing data.
The best practice is again to create a new form routine in which we will bind all the external data which we need towards the internal types and gather the additional data based on this.
Underneath you can find some code snippets in which we pass the data towards the internal types, I'll briefly explain each of the code snippets.
The field NAST-NACHA holds the transmission medium, based on this medium we will have to get different data.
In case of an External Send (can be hard copy, mail or fax), we need the address of the receiver. If this is not present in our global data, after the "Gather Document Data" is done, we need to gather this with help of the Function Module "ADDR_GET_NEXT_COMM_TYPE". This FM will get the default transmission medium of our customer and in case this is "e-mail", it will gather the customers e-mail address.
In case we are sending our form via mail or fax we will also have to put the value "cs_outputparams-getpdf" to TRUE.
The structure "cs_outputparams" is needed to open the Spool job for Adobe Forms processing.
CASE nast-nacha.
WHEN gc_nacha-external_send. "= 5
IF NOT nast-tcode IS INITIAL.
* If sending to customer not listed under Partners
IF gs_vbdkr-adrnr IS INITIAL AND
nast-parnr IS NOT INITIAL.
SELECT SINGLE adrnr FROM kna1
INTO gs_vbdkr-adrnr
WHERE kunnr = nast-parnr.
ENDIF.
CALL FUNCTION 'ADDR_GET_NEXT_COMM_TYPE'
EXPORTING
strategy = nast-tcode
address_type = gs_vbdkr-address_type
address_number = gs_vbdkr-adrnr
person_number = gs_vbdkr-adrnp
IMPORTING
comm_type = lv_comm_type
comm_values = ls_comm_values
EXCEPTIONS
address_not_exist = 1
person_not_exist = 2
no_comm_type_found = 3
internal_error = 4
parameter_error = 5
OTHERS = 6.
IF sy-subrc <> 0.
* Error Handling
RETURN.
ENDIF.
CASE lv_comm_type.
WHEN 'INT'. "e-mail
cs_outputparams-getpdf = abap_true.
cv_device = gc_device-email. "= 'E'
gv_email_addr = ls_comm_values-adsmtp-smtp_addr.
WHEN 'FAX'.
cs_outputparams-getpdf = abap_true.
cv_device = gc_device-fax. "= 'F'
nast-telfx = ls_comm_values-adfax-fax_number.
nast-tland = ls_comm_values-adfax-country.
WHEN 'LET'. "Printer
cv_device = gc_device-printer. "= 'P'
ENDCASE.
ELSE.
cv_device = gc_device-printer. "= 'P'
ENDIF.
WHEN gc_nacha-printer.
cv_device = gc_device-printer. "= 'P'
WHEN gc_nacha-fax.
cs_outputparams-getpdf = abap_true.
cv_device = gc_device-fax. "= 'F'
ENDCASE.
Sometimes we need to check if it's the first time we are printing the document, for instance for displaying the text "Original" or "Copy" in the footer of our document. This is necessary because it's advised to allow only 1 original document print-out.
If the code snippet underneath returns something, it means we already printed the Output Type and we can pass a repeat flag towards our form.
DATA:
lv_nast TYPE nast.
* Get message
SELECT SINGLE * INTO lv_nast FROM nast
WHERE kappl = nast-kappl "#EC *
AND objky = nast-objky
AND kschl = nast-kschl
AND spras = nast-spras
AND parnr = nast-parnr
AND parvw = nast-parvw
AND nacha BETWEEN '1' AND '4'
AND vstat = '1'.
IF sy-subrc IS INITIAL.
repeat = abap_true.
ENDIF.
In case of a print preview we don't need to get the PDF object and we check the preview flag in the output parameters.
IF xscreen = abap_true.
cs_outputparams-getpdf = space.
cs_outputparams-preview = abap_true.
ENDIF.
For the language of the form we use the defaulted partner (set up in customizing) language which is stored in NAST-SPRAS.
If this language is not present we can still assign a second fallback language (in this case the Sales Organization language from the billing document header view) and a third language which should be "E" (= English).
* Set Language
cs_docparams-langu = nast-spras.
cs_docparams-replangu1 = gs_vbdkr-spras_vko.
cs_docparams-replangu2 = gc_english. "='E'
cs_docparams-country = gs_vbdkr-land1.
If we want to use the archiving function of form processing (see title Archiving for more information) we have to include the "toa_dara" structure into the cs_docparams.
* Archiving
APPEND toa_dara TO cs_docparams-daratab.
The following variables are bound from the imported structures towards internal types which we are using to call our form:
cs_outputparams-nodialog = abap_true.
"=> Hide dialog with Output Type options (they can be modified in the document)
cs_outputparams-dest = nast-ldest.
"=> Destination of the print (ex. printer name)
cs_outputparams-copies = nast-anzal.
"=> Number of prints: if this value is "0" you should default it to "1"
cs_outputparams-dataset = nast-dsnam.
"=> Spool Name
cs_outputparams-suffix1 = nast-dsuf1.
"=> Spool Suffix 1
cs_outputparams-suffix2 = nast-dsuf2.
"=> Spool Suffix 2
cs_outputparams-cover = nast-tdocover.
"=> Flag for printing SAP cover page
cs_outputparams-covtitle = nast-tdcovtitle.
"=> Spool Description
cs_outputparams-authority = nast-tdautority.
"=> Print Authorization object, will be checked in OPEN_JOB when calling the form
cs_outputparams-receiver = nast-tdreceiver.
"=> Spool recipient name
cs_outputparams-division = nast-tddivision.
"=> Spool department name
cs_outputparams-arcmode = nast-tdarmod.
"=> Archiving mode: can be print only, archive only or both
cs_outputparams-reqimm = nast-dimme.
"=> Print Immediately flag
cs_outputparams-reqdel = nast-delet.
"=> Release After Output flag
cs_outputparams-senddate = nast-vsdat.
"=> Request date for sending message
cs_outputparams-sendtime = nast-vsura.
"=> Request time for sending message (from)
There are a lot other parameters which can be set but I've only described the most important ones over here.
For a full overview you can inspect the structure "SFPOUTPUTPARAMS" to see a list of all the possible parameters which you can pass towards the JOB-OPEN Function Module which opens the spool job (See Calling the Form).
Calling the Form
After you have gathered all the data that needs to appear on your form and you have bound all data needed for form processing towards the internal types we can now call the Form.
Calling the Adobe form is different than calling a Smart Form object; To call an Adobe Form you need to open and close the spool job yourself.
First we check if the amount of print-outs is not equal to zero, if this is the case we default it to 1.
IF ls_outputparams-copies EQ 0.
lv_anzal = 1.
ELSE.
lv_anzal = ls_outputparams-copies.
ENDIF.
Then we open the spool job and pass the structure ls_outputparams (which we filled in "Output & Document Paramaters") as importing parameter.
* Open the spool job
CALL FUNCTION 'FP_JOB_OPEN'
CHANGING
ie_outputparams = ls_outputparams
EXCEPTIONS
cancel = 1
usage_error = 2
system_error = 3
internal_error = 4
OTHERS = 5.
IF sy-subrc <> 0.
cv_retcode = sy-subrc.
* Error Handling
RETURN.
ENDIF.
To call the Adobe Form, we don't call the Adobe Form object since we can't execute this object type, therefore we call the unique generated Function Module name of the Adobe Form object. Since the generated FM name can differ from SAP system to SAP system we use the FM "FP_FUNCTION_MODULE_NAME" to get the unique generated FM name on the current system.
* Get the name of the generated function module
TRY.
CALL FUNCTION 'FP_FUNCTION_MODULE_NAME'
EXPORTING
i_name = lv_form
IMPORTING
e_funcname = lv_fm_name.
CATCH cx_fp_api_repository
cx_fp_api_usage
cx_fp_api_internal.
cv_retcode = 99.
* Error Handling
RETURN.
ENDTRY.
We use this generated FM name to call the Adobe Form the amount of times that was declared in the Output Type (passed towards variable "lv_anzal").
The first time of the loop, the "repeat" flag will be empty to notify the form that this print is the original document (first print). The other times we will pass a checked "repeat" flag to notify the form that this time it's a copy of the original.
If there was an error during form processing, we do not abort the processing at this time but we store the result of the processing into a variable (lv_failed), since we need to close the spool job first.
* Directly call the function module generated from according PDF form
DO lv_anzal TIMES.
IF sy-index NE 1 AND repeat NE abap_true.
repeat = abap_true.
ENDIF.
CALL FUNCTION lv_fm_name
EXPORTING
/1bcdwb/docparams = ls_docparams
is_vbrk = gs_vbrk
iv_repeat = repeat
IMPORTING
/1bcdwb/formoutput = ls_pdf_file
EXCEPTIONS
usage_error = 1
system_error = 2
internal_error = 3
OTHERS = 4.
ENDDO.
IF NOT sy-subrc IS INITIAL.
cv_retcode = sy-subrc.
* Error Handling
* Do not directly return but only after closing the spool job
lv_failed = abap_true.
ENDIF.
Now we can close the spool job and save the result (Output done flag, Spool ID's, number of pages, ...) into the structure "ls_joboutput".
After closing the spool job we check if there was an error during for processing. If this is the case we can leave the further processing.
* Close the spool job
CALL FUNCTION 'FP_JOB_CLOSE'
IMPORTING
e_result = ls_joboutput
EXCEPTIONS
usage_error = 1
system_error = 2
internal_error = 3
OTHERS = 4.
IF sy-subrc <> 0.
cv_retcode = sy-subrc.
* Error Handling
RETURN.
ENDIF.
IF NOT lv_failed IS INITIAL.
* Now, leave processing if printing did fail
RETURN.
ENDIF.
If everything went according to plan, your document should now be present in the spool (SP01) or printed immediately if that flag was checked, however this is not always the case.
To be able to fix these errors, it's very important to handle all the issues that might occur during the form processing and give the user some response.
Issue Handling
A variable (ex. lv_return_code) should be created from the type SY-SUBRC and at every point in the code where an issue might happend you should bind the SY-SUBRC value to that variable. At crucial points in the driver program you can then include the code "CHECK lv_return_code IS INITIAL." to stop the form processing.
At the very end of the form processing you should check for the value of this variable. If the value of this variable is not initial (= "0"), you should return a value different from "0" in the main starting form routine (FORM entry USING return_code us_screen.) as said in "Importing Parameters / Structures".
In case you return a value different from "0", the user will get notified that the form processing was not successful.
Simply notifying the user that the form processing was not successful is not enough, the user should be able to know where the issue was caused.
All the issues which are happening inside the driver program can be logged into the Processing log. Errors which happen outside the driver program should be viewed in the spool request processing list (transaction SP01).
Generating a message into the Processing Log can be achieved with the code underneath:
CLEAR gv_variable.
gv_variable = NAST-OBJKY.
MESSAGE e000 WITH gv_variable INTO gv_dummy.
PERFORM protocol_update.
The message is an error message which you can create in your message class and variables can be passed towards this message (ex. document number in this case). The gv_dummy variable is a single character variable which has the single use of not displaying the error message on the screen but just storing it in the buffer. Then we execute the protocol_update routine, in which the Function Module "NAST_PROTOCOL_UPDATE" is called to update the message.
This way we can create a processing log as displayed underneath:
Archiving
Archiving is a very usefull feature for form development, since it allows you to store a copy of the printed document onto a fileserver. If you add an output type to a document, you can select if the document only needs to be printed, only needs to be archived or both should be done.
As already mentioned, it should only be possible to print a single original version of your document. A way to omit this limitation is to reprint an archived version of the original document stored onto the fileserver.
The Function Module "ARCHIV_CREATE_OUTGOINGDOC_MULT" will archive the document onto the fileserver when the ARMCODE is different from 1 (= print only) and when the variable NOARCHIVE is not checked. You should pass the PDF file, which was returned from the generated Function Module call of your Adobe Form object, towards the archiving Function Module.
* --- Archiving
IF ls_outputparams-arcmode <> '1' AND
ls_outputparams-noarchive NE abap_true.
CALL FUNCTION 'ARCHIV_CREATE_OUTGOINGDOC_MULT'
EXPORTING
documentclass = 'PDF'
document = ls_pdf_file-pdf
TABLES
arc_i_tab = ls_docparams-daratab
EXCEPTIONS
error_archiv = 1
error_communicationtable = 2
error_connectiontable = 3
error_kernel = 4
error_parameter = 5
error_format = 6
OTHERS = 7.
IF sy-subrc <> 0.
MESSAGE ID sy-msgid TYPE 'E' NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4
RAISING system_error.
ENDIF.
ENDIF.
To display the archived version of your document, you should go to the transaction of your document (ex. VF02 for billing documents) and enter the document. In the menu you can navigate to System > Services for Object > press on the Attachment List icon.
In case the document has already been archived, a list will be displayed of all documents. You can export these documents towards your local file system or reprint the archived version.
Conclusion
The driver program for printing your Adobe Forms has a lot of capabilities and has a very important role in your form processing.
Not only does the driver program gather the data to display on your form, give the command to start building the form layout or influences the form processing, but it also gives the user a good view on the for processing status and issues which occured while processing the form.
I hope that this document might come in handy when you start developing Adobe Forms as it would have been for me.
Feel free to leave your comments, remarks or suggestions below so I can improve this document.
Also let me know if you want some more information about certain parts or if I forgot to add something to the guide.
Kind regards,
Niels De Greef