在SAP项目实施过程中,我们经常遇到这样的需求:将系统中的业务数据生成标准格式的文档,比如采购订单、发货单、财务报表等。传统做法是直接打印纸质文档,但在数字化办公场景下,PDF电子文档显然更符合现代企业的需求。
我参与过的一个制造业项目就遇到过典型场景:车间需要每天打印数百张生产工单,既浪费纸张又难以管理。后来我们改用ABAP程序自动生成PDF工单,直接推送到车间显示屏,每年节省的打印成本就超过10万元。
SmartForm作为SAP标准的表单工具,配合OTF(Output Text Format)数据流转换技术,能够完美解决这类需求。OTF是SAP特有的一种中间格式,可以理解为打印数据的"通用语言"。通过将内表数据先输出为OTF格式,再转换为PDF,既能保留SmartForm强大的排版能力,又能获得PDF的便携性。
在开始编码前,需要确认系统环境满足以下条件:
特别提醒:如果系统版本较老,可能需要申请安装SAP Note 1096932等补丁来增强PDF转换功能。我在一个ECC6.0系统上就遇到过CONVERT_OTF函数不稳定的情况,更新内核后问题解决。
假设我们要创建一个生产工单表单ZPP001,关键设计建议:
abap复制TABLES: T_ITEM TYPE ZPPS001.
实际项目中我遇到过一个坑:当内表包含超长文本时,SmartForm可能自动换页导致格式错乱。解决方法是在表格属性的"行"设置里勾选"保护行不拆分"。
原始代码中的内表填充部分可以优化为动态方式。比如使用字段符号动态映射:
abap复制FIELD-SYMBOLS: <fs_tab> TYPE zpps001.
APPEND INITIAL LINE TO lt_tab ASSIGNING <fs_tab>.
<fs_tab>-werks = '1001'.
<fs_tab>-maktx = '动态填充示例'.
获取SmartForm函数模块的关键代码:
abap复制CALL FUNCTION 'SSF_FUNCTION_MODULE_NAME'
EXPORTING
formname = 'ZPP001'
IMPORTING
fm_name = zsmart_name
EXCEPTIONS
no_form = 1.
IF sy-subrc <> 0.
MESSAGE '表单不存在' TYPE 'E'.
ENDIF.
控制参数w_ctrlop的配置很有讲究:
getotf = 'X':必须开启以获取OTF数据no_dialog = 'X':禁止弹出打印对话框device = 'PRINTER':某些版本需要明确指定输出参数w_compop的实用配置组合:
abap复制w_compop-tdimmed = 'X'. "立即打印
w_compop-tddest = 'LP01'. "打印机设备
w_compop-tdnoprev = 'X'. "禁用预览
w_compop-tdnewid = 'X'. "新建假脱机请求
方案一:直接生成二进制流(适合后台处理)
abap复制CALL FUNCTION 'CONVERT_OTF'
EXPORTING
format = 'PDF'
IMPORTING
bin_file = g_pdf_xstring
TABLES
otf = w_return-otfdata[].
方案二:生成可供下载的文档表(适合前台交互)
abap复制CALL FUNCTION 'CONVERT_OTF_2_PDF'
TABLES
otf = w_return-otfdata[]
lines = l_lines[].
在性能测试中发现,当处理超过100页的文档时,CONVERT_OTF的效率比CONVERT_OTF_2_PDF高出约30%。但后者支持更丰富的文档属性设置。
使用OPEN DATASET的增强版实现:
abap复制DATA(lv_filename) = |/usr/sap/tmp/{ sy-uname }_{ sy-datum }_{ sy-uzeit }.pdf|.
OPEN DATASET lv_filename FOR OUTPUT IN BINARY MODE.
IF sy-subrc = 0.
TRANSFER g_pdf_xstring TO lv_filename.
CLOSE DATASET lv_filename.
MESSAGE |文件已保存至{ lv_filename }| TYPE 'S'.
ENDIF.
改进的GUI下载代码包含错误处理:
abap复制CALL METHOD cl_gui_frontend_services=>file_save_dialog
EXPORTING
default_extension = 'PDF'
file_filter = 'PDF (*.pdf)|*.pdf|所有文件 (*.*)|*.*'
initial_directory = 'C:\Temp'
CHANGING
filename = lv_file
path = lv_path
fullpath = lv_fullpath.
IF lv_fullpath IS NOT INITIAL.
CALL FUNCTION 'GUI_DOWNLOAD'
EXPORTING
bin_filesize = lv_filesize
filename = lv_fullpath
filetype = 'BIN'
TABLES
data_tab = lt_lines
EXCEPTIONS
file_write_error = 1.
IF sy-subrc = 0.
MESSAGE '下载完成' TYPE 'S'.
ENDIF.
ENDIF.
通过SO_NEW_DOCUMENT_ATT_SEND_API1发送PDF附件:
abap复制DATA: lt_packing_list TYPE TABLE OF sopcklsti1,
lt_contents TYPE TABLE OF solisti1,
lt_receivers TYPE TABLE OF somlreci1.
" 准备PDF内容
APPEND INITIAL LINE TO lt_contents ASSIGNING FIELD-SYMBOL(<fs_content>).
<fs_content>-line = l_lines.
" 设置附件信息
APPEND INITIAL LINE TO lt_packing_list ASSIGNING FIELD-SYMBOL(<fs_pack>).
<fs_pack>-transf_bin = 'X'.
<fs_pack>-head_start = 1.
<fs_pack>-head_num = 0.
<fs_pack>-body_start = 1.
<fs_pack>-body_num = lines( lt_contents ).
<fs_pack>-doc_type = 'PDF'.
" 调用发送函数
CALL FUNCTION 'SO_NEW_DOCUMENT_ATT_SEND_API1'
EXPORTING
document_data = ls_doc_data
TABLES
packing_list = lt_packing_list
contents_bin = lt_contents
receivers = lt_receivers
EXCEPTIONS
too_many_receivers = 1.
当处理超过1000行数据时,建议:
abap复制CALL FUNCTION 'SAP_MEMORY_ESTIMATE'
EXPORTING
kind_of_memory = 'ABAP'
IMPORTING
size = lv_mem_size.
IF lv_mem_size > 1000000. "1MB
MESSAGE '数据量过大,请分批处理' TYPE 'W'.
ENDIF
我总结的典型错误及解决方案:
OTF数据为空
PDF转换失败
中文乱码
权限问题
建议添加详细的日志记录:
abap复制DATA: lt_log TYPE TABLE OF string.
APPEND |开始处理PDF生成,时间:{ sy-uzeit }| TO lt_log.
TRY.
" PDF生成代码
APPEND 'PDF生成成功' TO lt_log.
CATCH cx_root INTO DATA(lx_error).
APPEND |错误发生:{ lx_error->get_text( ) }| TO lt_log.
ENDTRY.
" 保存日志
OPEN DATASET '/tmp/pdf_gen_log.txt' FOR OUTPUT IN TEXT MODE.
LOOP AT lt_log INTO DATA(lv_log).
TRANSFER lv_log TO '/tmp/pdf_gen_log.txt'.
ENDLOOP.
CLOSE DATASET '/tmp/pdf_gen_log.txt'.
对于更复杂的表单需求,可以结合Adobe Forms:
abap复制CALL FUNCTION 'FP_JOB_OPEN'
CHANGING
ie_outputparams = ls_outputparams.
CALL FUNCTION 'FP_FUNCTION_MODULE_NAME'
EXPORTING
i_name = 'ZADOBE_FORM'
IMPORTING
e_funcname = lv_fm_name.
CALL FUNCTION lv_fm_name
EXPORTING
/1bcdwb/docparams = ls_docparams
IMPORTING
/1bcdwb/formoutput = ls_pdf.
在SmartForm中添加条码的步骤:
动态多语言实现方法:
abap复制DATA: lv_lang TYPE spras.
SELECT SINGLE sprsl INTO lv_lang
FROM usr02
WHERE bname = sy-uname.
CALL FUNCTION 'SSF_FUNCTION_MODULE_NAME'
EXPORTING
formname = 'ZPP001'
language = lv_lang
IMPORTING
fm_name = zsmart_name.
在实际项目中,这套方案成功应用于全球30多个工厂的工单系统,每天自动生成约5000份多语言PDF文档。关键是要在SmartForm中做好文本元素的翻译维护,使用SE63事务码管理多语言文本。