SAP SFP生成PDF表单:建模到Driver Program调用全流程
2026/9/11 5:37:27 网站建设 项目流程

做 SAP 开发的,谁没被“出一张好看的单据”这个需求缠过几回?客户一句话,背后就是整套打印开发流程。在 SD、MM、FI 的标准单据输出里,绕不开 SAPscript、Smart Forms、SFP 这三条技术路线。SFP 也就是基于 Adobe PDF 的表单方案,近年来越来越多项目在 SAP GUI 里直接用 SFP 建模,再通过 Driver Program(驱动报表程序)调用生成 PDF 文件,做打印、邮件发送、合并归档。

这篇文章不打算做理论对比,直接讲落地。我最近正好在 SAP GUI 里用 SFP 完成了一套 PDF 表单,从建模到 Driver Program 调用全程走通,中间踩了不少基础但致命的坑。今天把整个流程拆出来:SFP 建模时该定义什么、Layout 怎么摆、Context 怎么绑定、调用程序的函数名怎么取、输出参数怎么设,以及最常见的缓存、字体、空白页问题怎么排查。无论你是刚开始接触 Adobe Form 的新人,还是被 SFP 搞得头疼的 ABAPer,这篇应该都能帮上忙。

1. 为什么要在 SAP GUI 里选择 SFP 做 Adobe PDF 表单

1.1 三种输出方案对比:SAPscript、Smart Forms 与 SFP

SAP 传统的单据输出有三条路,很多人容易混淆。SAPscript 是老一代方案,基于 Script Form 的排版语言,适合简单文本和地址打印,但要说做漂亮的表格、图片、条形码,开发起来非常痛苦。Smart Forms 是下一代,SAP GUI 里能做可视化布局,输出时也能通过函数模块直接获取 PDF,但本质还是 SAP 自身的排版引擎,很多细节需要动代码。SFP 全称 SAP Form Processing,是基于 Adobe LiveCycle Designer 的 PDF 表单方案,Layout 上用拖拽方式设计,输出结果是原生 PDF,数据接口、子表单、校验规则、交互域都支持。

对比项SAPscriptSmart FormsSFP(Adobe Form)
设计工具Script 编程窗口SAP GUI 图形设计器Adobe LiveCycle Designer 嵌入环境
输出介质打印列表Spool / PDF原生 PDF / 打印 / 邮件附件
数据接口定义不直观通过函数模块传参Form Interface + Context 绑定
交互式表单域不支持有限支持支持签名域、输入域
复杂度简单场景还行中等复杂度设计器上手成本略高,但上限高

如果你的需求只是打印一张发票、一份采购订单,Smart Forms 完全够用。但一旦客户要求“PDF 文件直接作为邮件附件发出”“模板上放二维码”“签字域要可填写”,Smart Forms 处理起来就会很别扭。SFP 的优势正好落在这几个点上:原生 PDF、视觉化布局、组件丰富。另一个很关键的区别是,SFP 的方案适配 Adobe 生态,交付给客户的 PDF 文件既专业又稳定,这在很多客户验收时是加分项。

1.2 两条建模路线:Form Interface 与 Form without Interface

进入事务码 SFP 后,系统会问你要创建什么类型:表单、样式、上下文或者 PDF 基础表单。创建 Adobe Form 时,通常会遇到一个选择——带 Form Interface 还是不带 Form Interface。

Form Interface 的含义,就是你在表单里定义一个数据接口,相当于把 ABAP 的结构声明映射到表单的 Context 上。后续 Driver Program 通过这个接口把业务数据塞进表单,布局里的字段再绑定到 Context 对应的路径上。这种做法非常直观,适合绝大多数业务单据。

Form without Interface 则是表单自己维护数据,不需要外部传参,一般用于数据完全来自表单内部定义的场景。在我的实际项目中,基本不会选用这条路线,因为它的维护成本高,外部调用时也不够灵活。尤其是当同一张表单要适配多个业务程序时,不带接口的表单很难复用。

如果业务上还需要 Web 端在线填单,也别硬套 SFP。SFP 的强项是后端渲染 PDF,真要做一个 HTML 表单或者录入页面,直接选对应的前端表单引擎方案更合适。SAP GUI 里的 SFP 解决的是“输出打印 PDF”这条链路,不是“在线编辑表单”那条链路。

2. SFP 建模全流程:从接口定义到 Layout 设计

2.1 第一步:规划数据结构与创建表单

真正动手前,先把数据结构想清楚。我通常先定义一个 ABAP 结构,对应业务单据的抬头与行项目。以一个发货单示例,结构大致长这样:

TYPES: BEGIN OF ts_item, posnr TYPE posnr, " 行项目号 matnr TYPE matnr, " 物料编号 werks TYPE werks_d, " 工厂 menge TYPE menge_d, " 数量 netwr TYPE netwr, " 净价值 END OF ts_item. TYPES: tt_item TYPE STANDARD TABLE OF ts_item. TYPES: BEGIN OF ts_data, vbeln TYPE vbeln_vf, " 发货单号 vkorg TYPE vkorg, " 销售组织 kunnr TYPE kunnr, " 客户编号 name1 TYPE name1, " 客户名称 total TYPE netwr, " 合计金额 items TYPE tt_item, " 行项目内表 END OF ts_data.

在 SE11 里把这个结构建成透明结构或者生成一个全局结构类型ZSSFP_DATA,然后打开事务码 SFP,创建表单。表单名字建议以 Z 开头,比如ZSFP_DELIVERY。创建的时候系统会提示选择一个“接口/表格”,这时候勾选“接口”,并且把刚才的数据结构关联进去。

这一步很容易忽略的地方在于,很多人建完表单就直接去画 Layout,结果发现左侧数据视图里根本没有字段可拖。原因就是 Form Interface 没有正确关联。这个接口一旦建立,后续 Context 的结构会跟着变,所以最好在画布局之前就确认清楚。

2.2 理解 Context:表单的数据源地图

在 SFP 设计器里,左侧常用的是“数据视图”和“设计视图”两种窗口。数据视图展示的就是 Context,也就是表单可以引用的全部数据。Context 的根节点通常叫DATAFORM,下面挂抬头字段、行项目内表。设计视图则是画布局的区域,相当于一张空白画布,你可以从数据视图里把字段拖到画布上,拖过去的同时系统会自动建立数据绑定。

我见过不少同事在这块卡壳。他们以为把界面画好、字段拖上去就够了,却忽略了一个核心逻辑:SFP 的每个文本域、表格单元格,必须通过绑定路径连接到 Context 上的具体字段,生成的 PDF 才能拿到值。如果只是画了一个输入框,没有绑定任何路径,那最终输出就是空白的占位符。

绑定路径长什么样?一般会显示成$data.items.posnr这样的形式。设计器里拖拽时,它会自动生成。手动写绑定路径也可以,但容易写错大小写或层级,建议优先用拖拽方式。

2.3 第二步:Layout 设计、页面设置与表格循环

Layout 设计是工作量最大的环节。大概能分成四项:页眉页脚、抬头区域、表格循环区域、合计区域。

页眉页脚通常放在“主表单”的顶层。想固定打印公司 Logo 和页脚信息,可以用“定位”方式放图片和文本,这样每页都会出现。抬头区域一般用“流动”方式,把客户名称、发货单号、日期这些字段按顺序排成一列或几列。这里要注意列宽对齐。我自己的习惯是把列宽设置成一样的数值,避免打印出来参差不齐。

行项目表则是核心。SFP 里做表格循环,需要插入一个“表格空间”,行属性里选择“重复子表单”,然后把重复的数据源指向items[]。编译时系统会按内表的行数自动循环填充。做这一步时,记得把行项目里的列宽固定下来,别让某列的文字过长把整个表格撑破。

页面大小和方向也要在这个阶段设置。找到主表单节点的“页面”属性,把页面格式固定为 A4、纵向,并把页边距设置成统一值。别小看这步,后面合并 PDF 时页面大小不一致,多半就是在这里埋的雷。还有一种情况,某些子表单会继承父级页面方向,导致个别页被切成横向,最好把子表单的页面方向也显式指定。

如果你接触过前端表单,会发现 SFP 布局里的对齐问题和 Web 端框宽度对齐的逻辑很像。前一阵子有同事做 Ruoyi 前端,下拉框、日期框和输入框宽度没统一,被测试提了 bug;SFP 里也是一样,网格线、辅助线打开,控件的宽度、间距尽量统一,出来的 PDF 才专业。

2.4 保存、预览与激活

设计完成后,点保存。SFP 的保存和普通 ABAP 对象不同,它内部还有版本管理。保存之后,建议先在 SFP 的预览功能里填充测试数据,确认渲染效果。预览能发现约一半的问题:字段没绑定、数据层级错位、表格列宽溢出,基本都能在预览阶段看出来。

确认无误后,执行“激活”操作。注意,激活之后系统中的表单函数模块才会更新。如果没有激活,后面 Driver Program 调用时可能拿到旧版本。这一步也是我干活时反复强调的“先激活,再调用”。

3. Driver Program 调用:三板斧完成 PDF 输出

3.1 Driver Program 到底是什么角色

Driver Program,直译过来就是驱动报表程序。它更像是一个“取数 + 调用 + 输出”的外壳。表单只负责展示数据和排版,不负责查询数据库,也不负责输出到打印机或邮件。所有业务逻辑,比如从哪个表取发货单、金额怎么算、传给表单的数据怎么组装,全部写在 Driver Program 里。

这种拆分非常实用。一套表单可以供多个程序调用,每个程序用不同的取数逻辑;同样,一个程序也可以同时输出多套表单。逻辑和布局解耦,后续需求变更时,不必因为一个字段的取值规则而对 Layout 大动干戈。

3.2 调用三重奏:取函数名、设输出参数、调用生成函数

很多 SFP 新手在这里会卡住。SAP 没有直接让你调用的“SFP 表单”,你需要通过FP_FUNCTION_MODULE_NAME获取一个实际的函数模块名。

第一步,获取函数名:

DATA: lv_fm_name TYPE rs38l_fnam. CALL FUNCTION 'FP_FUNCTION_MODULE_NAME' EXPORTING i_name = 'ZSFP_DELIVERY' IMPORTING e_funcname = lv_fm_name.

注意,i_name传入的就是表单名。如果表单激活正常,lv_fm_name会返回一个类似ZSFP_DELIVERY或者带了系统内部后缀的函数名。这个函数名你可以到 SE37 里查看,它的接口里会包含表单 Form Interface 的定义。

第二步,设置输出参数。输出参数结构是sfpoutputparams,重点字段是:

  • GETPDF:置为ABAP_TRUE,表示把 PDF 二进制数据返回给程序,适合下载、邮件附件场景。
  • NODIALOG:置为ABAP_TRUE,表示不弹出对话框,适合后台运行。
  • DEST:打印机名称,用于直接打印。
  • REQNEW:置为'X',表示生成新的输出请求。
  • CONNECTION:配置连接名称,通常默认即可。

第三步,调用生成函数:

DATA: ls_data TYPE zssfp_data, ls_docparams TYPE sfpoutputparams, ls_result TYPE sfpoutputparams. ls_docparams-getpdf = abap_true. ls_docparams-nodialog = abap_true. CALL FUNCTION lv_fm_name EXPORTING is_data = ls_data job_output_info = ls_docparams IMPORTING job_output_info = ls_result.

这段代码里有两个关键点。第一,is_data这个名字不是固定的,它对应 Form Interface 的根节点名。如果你的接口根节点叫DATA,生成函数里的导入参数通常是IS_DATA;如果根节点叫其他名字,参数名也会相应变化。最稳妥的方法是先到 SE37 看生成函数文档,确认导入参数名再写调用代码。

第二,调用前最好把ls_datals_result清空。这个习惯很像前端表单提交前的“清空表单内容”逻辑,后端也容易漏。SAP 内存里变量一旦被上次运行污染,这次生成的 PDF 就会出现莫名数据残留。

3.3 输出 PDF:下载、邮件、直接打印

拿到ls_result之后,PDF 二进制数据实际上就在某个字段里。不同系统版本存放位置略有差异,常见的是ls_result-pdf或者通过ls_result-docparams再取。我按照常见的ls_result-pdf来写示例。

下载到本地,用 ABAP 标准类转换并保存:

DATA: lt_solix TYPE solix_tab. CALL METHOD cl_bcs_convert=>xstring_to_solix EXPORTING iv_xstring = ls_result-pdf IMPORTING et_solix = lt_solix. CALL METHOD cl_gui_frontend_services=>gui_download EXPORTING bin_filesize = xstrlen( ls_result-pdf ) filename = 'C:\temp\delivery.pdf' filetype = 'BIN' CHANGING data_tab = lt_solix.

如果是发邮件,用 BCS 组件:

DATA: lo_send_request TYPE REF TO cl_bcs, lo_document TYPE REF TO cl_document_bcs. lo_send_request = cl_bcs=>create_persistent( ). lo_document = cl_document_bcs=>create_document( i_type = 'PDF' i_subject = '发货单' i_hex = ls_result-pdf ). lo_send_request->add_document( lo_document ).

如果是直接打印,不需要设置GETPDF,改用:

ls_docparams-dest = 'LP01'. ls_docparams-reqnew = 'X'.

调用生成函数后,系统会直接把打印请求发到对应输出设备,程序里不用再做额外处理。

3.4 高级补充:PDF_FORM_OPEN / WRITE / CLOSE

普通业务用生成函数就够了。还有一种相对底层的调用方式,是使用PDF_FORM_OPENPDF_FORM_WRITEPDF_FORM_CLOSE三个函数。这种方式适合你要把 XML 数据直接喂给表单引擎,或者需要一页一页写入、动态拼装复杂文档的场景。

大致流程是:

CALL FUNCTION 'PDF_FORM_OPEN' EXPORTING i_insert_docoutput = ls_docparams IMPORTING e_job_output_info = ls_job. CALL FUNCTION 'PDF_FORM_WRITE' EXPORTING i_job_output_info = ls_job i_xml = lv_xml. CALL FUNCTION 'PDF_FORM_CLOSE' EXPORTING i_job_output_info = ls_job IMPORTING e_job_output_info = ls_job.

这种方式更接近“表单引擎”的底层 API,灵活度更高,但排查问题也更难。如果是第一次做 SFP,我建议先把生成函数的常规调用跑通,再考虑这种高级玩法。

4. 常见问题与避坑指南

4.1 改完表单,调用程序还是输出旧版式

这是我被问过最多的问题。症状是:在 SFP 里改了布局,保存也激活了,但程序调用后生成的 PDF 没有变化。原因通常是旧函数模块缓存没刷新,或者表单的版本时间没对上。

我的排查路径是:先在 SFP 打开表单,确认“激活”或“生成”是否已经执行;再到 SE37 里看对应的函数模块最近修改时间。如果函数模块时间没更新,说明激活动作没有触发函数模块重建。此时可以重新执行一次“生成”,必要时在对象列表里删除旧的函数模块再重新生成。生产机上做这个操作要谨慎,最好走传输请求,别直接在开发机测试后手动改。

4.2 中文字体变成方框或者乱码

SAP 后端渲染 Adobe PDF 依赖 ADS(Adobe Document Services)组件,而 ADS 渲染时又依赖服务器上安装的字体。开发机经常表现正常,因为开发机的 ADS 环境装了可用字体;生产服务器如果缺少对应中文字体,PDF 里中文就会变成方框。

解决办法分两步。第一步,确认服务器字体目录下有可用的中文字体文件。第二步,在 SFP 布局里检查是否指定了特殊字体。如果项目里用的都是标准中文字体,但生产机还是乱码,很可能是字体文件没放对位置,需要 BASIS 同事协助把字体部署到 ADS 对应的字体目录。

经验之谈:别在表单里使用生僻的自定义字体,尤其不要用 Adobe 的某个特定版本字体。项目上线后,客户一旦换一台没有该字体的电脑预览,整个 PDF 版式就废了。

4.3 生成的 PDF 是空白页,但字段已经画上去了

这种问题十有八九出在 Context 绑定上。你在布局里放了文本,但它没有绑定到 Context 的字段上;或者绑定的路径写错了大小写;或者 Form Interface 的数据结构没有正确传进调用程序。

排查步骤很简单。先在 SFP 预览里输入测试数据看效果。如果预览正常,说明问题出在 Driver Program 传参上;如果预览就是空白的,说明 Layout 绑定出了问题。这里有个小技巧:在 Driver Program 里临时写一段代码,把ls_data的内容展开到日志里,确认字段值真的传到了调用函数。很多时候你以为填了数据,其实内表是空的,字段自然没有输出。

4.4 合并 PDF 后页面大小不一样

这是最近客户反馈很典型的一个坑。他们用 Adobe 或其他工具把多个单据合并成一个 PDF,结果发现不同单据页面大小参差不齐,有的 A4 纵向,有的 Letter 横向,还伴有多余白边。

这里有双重原因。一方面是 SFP 源表单没有统一定义页面大小。前面在 2.3 提到,每个表单都要把页面格式固定为 A4 或客户要求的统一格式,不要留着默认的 Letter。另一方面是合并工具本身,有些 PDF 合并工具提供了“统一页面大小”的选项,一旦勾选,它会强制把每一页改成相同尺寸,导致内容被拉伸或留白。

正确做法是:SFP 源头统一定义页面属性和方向,合并时选择“保留原始页面大小”。如果你已经被 Adobe 合并成了“大小不一样”的效果,可以在“组织页面”里手动裁剪页面框,但这是补救措施,不是长期方案。真正治本的办法还是锁定每个表单的页面规格。

4.5 大批量输出时内存溢出或者速度很慢

SFP 渲染 PDF 本身就是后端 CPU 密集操作。如果一个 Driver Program 里循环几百份单据,每份都生成 PDF 并保存在内表里,应用服务器内存很快就会被顶满。我的做法是:大批量输出走后台 Job,通过输出请求方式交给后台处理;如果必须在前台交互,也建议每处理完一份就释放对应变量,不要把所有 PDF 的 XSTRING 累积到一个内表里。

如果客户需求是“所有单据合并成一个 PDF 发邮件”,内存占用会更高。建议先用cl_bcs_convert=>xstring_to_solix把每个 PDF 转成二进制大对象流式写入,或者用分段合并的方式,不要一次性把 100 个 PDF 的原始内容都堆在同一个变量里。

5. 实战心得:这套流程还能怎么玩

5.1 通用输出工具化

把“取函数名 + 设输出 + 下载/打印”这套逻辑封装成一个公共方法,是我在项目里做的最有价值的一件事。业务程序只需要传入表单名和数据结构,剩下的调用细节全部由公共方法处理。后续增加下载路径规范、打印设备调整,只需要改一处,不用每个报表程序各自维护。

我在一个项目里用这套思路,把销售订单确认函、发货单、标签打印三套表单全部收敛到同一个输出模块里,新增业务单据输出时,工作量从半天缩短到半小时。

5.2 往交互式表单和自动发送方向扩展

SFP 表单不只是静态 PDF。如果业务需要,可以在表单里设计可填写域、数字签名域,生成后的 PDF 打开时就允许收件人填写并签名。这适合审批场景,比如客户确认函、质检报告,生成后发给对方,对方填写盖章再返回。配合邮件网关和后台 Job,甚至可以做到系统自动触发、自动发送,整个流程完全无人值守。

当然这套扩展也意味着项目复杂度会上升,尤其是签名域、校验规则的配置。我的建议是先把标准打印链路跑稳,再逐步往交互和自动化方向加功能,否则问题叠加在一起排查起来会很痛苦。

根据我的个人经验,SFP 最大的坑从不在 Layout 设计本身,而是整个链路依赖的环境:ADS 服务是否正常、字体是否部署、函数模块是否重新生成、Form Interface 参数是否匹配。你只要把这几个链路节点逐一确认,SFP 开发其实比 Smart Forms 还要顺手。希望这篇文章能帮你少走几步弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询