做SAP这行,表单打印是永远绕不开的环节。项目上刚来的顾问,多半第一反应是Smart Forms;要是碰到做了十年SAP的老开发,他可能会反过来问你一句:这场景用Adobe Form还是SAP Script?这个问题的背后不是技术洁癖,而是实实在在的维护成本和功能边界。我这些年经手过的打印需求,从FICO的财务凭证、MM的采购订单、SD的发票到WM的拣配单,能稳定活过上线后三个月的方案,几乎都是Adobe Form。
Adobe Form,本质上是SAP和Adobe LiveCycle Designer深度集成的表单解决方案。它在SAP里的标准事务代码是SFP,开发对象分接口(Interface)和表单(Form)两层,运行时由Adobe Document Services(ADS)把设计好的XDP模板转成PDF。相比老一代的SAP Script和Smart Forms,Adobe Form最讨喜的地方是:版面控制能力极强,可以做到像素级排版;表单本身又是一个独立的PDF文件,能用Acrobat打开继续改,业务部门很多打印需求,顾问自己调整模板就能交付,不一定每次都要提开发票改代码。
写这篇东西,主要是想把我这些年从环境搭建、模板设计到输出控制集成、中文乱码排查的过程捋一遍。尤其是新手容易卡住的几个点:数据接口怎么建、绑定关系怎么理解、模板为什么激活时报错、打印出来中文为什么变方块。这些坑我基本都踩过,写下来也算是个实战备忘,给正在做或准备做Adobe Form的人省点时间。
1. 为什么还要谈Adobe Form:一套老方案的新处境
1.1 SAP表单方案的三代演进
SAP的表单开发,大致经历过三个阶段。最早是SAP Script,事务代码SE71,定义窗口、段落格式、字符格式,靠页窗口和逻辑页来控制内容位置。说实话,SAP Script做顺了也能出不错的表单,但缺点是排版太死板,想画个复杂表格、想动态隐藏段落,就得在ABAP代码里拼凑逻辑,维护性很差。
第二代是Smart Forms,事务代码SMARTFORMS,用图形化界面画表单、定义表格式区域,ABAP代码量少了很多,而且支持树形节点动态输出。Smart Forms在ECC时代几乎是标配,性能也快,很多老项目里的送货单、入库单都是它。但Smart Forms的问题在于控件能力受限,比如二维码、签名位、嵌入图片做复杂票面,都很难做得精细。
第三代就是这个Adobe Form。严格说它在Basis层面靠Adobe LiveCycle Designer做前端设计,靠SAP服务器上的ADS组件做后台PDF渲染。它的模板文件格式是XDP,本质上是XML和PDF特性的结合体。你可以把一个字段拖到任意坐标位置,设置字体、边框、背景色,做单选按钮和下拉框,甚至嵌入JavaScript来动态控制字段显示。对业务部门来说,Adobe Acrobat可以直接打开XDP查看和微调,这也是它能在很多行业项目里站稳脚跟的关键原因。
1.2 Adobe Form到底解决什么问题
我理解Adobe Form主要解决三类痛点。
第一类是样式复杂度。像发票、质检报告、装箱单这类单据,通常有公司Logo、多级表头、合并单元格、条款声明、条形码。用SAP Script做这些,代码量可怕;用Smart Forms做,维护也麻烦;但用Adobe Form配合LiveCycle Designer的表格控件,拖拖拽拽就能实现。
第二类是响应式动态隐藏。比如一张采购订单,有的供应商需要显示"付款条款",有的不需要;有的行项目要显示"批次号",有的不显示。在Adobe Form里,可以通过Subform的重复、隐藏、内容溢出控制来做,在模板层就能解决,不需要业务程序里塞一堆判断逻辑。
第三类是输出格式的灵活性。Adobe Form可以直接输出PDF,也可以配合打印服务器发送到物理打印机;可以生成PDF/A归档格式,也可以嵌入条形码、二维码做扫码追溯。现在很多企业的无纸化项目,都要求单据既要能打印,又要能归档电子文件,Adobe Form天然满足这个需求。
1.3 什么时候该选Adobe Form,什么时候不要碰
也不是所有打印需求都非要上Adobe Form。我个人的判断标准有三个:如果表单版面简单、字段固定、长期不变,用Smart Forms更快更稳;如果业务部门三天两头调整样式、希望自己能改模板,那直接选Adobe Form;如果系统是老旧ECC且没有配置ADS,预算又紧张,那还是先用Smart Forms过渡,不要为了技术而技术。
另外要提醒的是,Adobe Form的运行时依赖ADS。如果SAP服务器上没有安装或激活对应组件,模板设计得再好也生成不了PDF。这一点在项目实施初期就要评估好,别等开发完了才发现生产机不能渲染。
2. 开发前的环境认知:不是装上SFP就万事大吉
2.1 两个核心对象:接口与表单
在SFP里,必须要先理解"接口"和"表单"这两个概念。
接口(Interface),逻辑上定义了这张表单的输入和输出数据结构。你可以理解为给模板传递数据的"管道"。接口的导入参数,可以是单个的结构、内表,也可以是多个参数组合;导出参数通常放PDF二进制内容或打印参数。实际开发中,我习惯把一个打印场景的所有数据集中到一个主结构里,比如ZFI_INVOICE_PRINT,里面嵌套抬头、行项目、公司信息、付款条件等,避免表单绑定多个参数时搞混。
表单(Form),就是实际的模板容器,它引用某个接口,并在LiveCycle Designer中呈现出来。表单和接口是分离的,这样设计上很灵活:同一个数据结构可以出多种版式,比如同一份发票数据,可以做一个A4打印版,再做一个A5邮件附件版,只要绑定同一个接口就行。
做一个项目时,最常见的命名规范是Z + 模块 + 场景 + FORM/IF,比如ZFI_INV_FORM、ZFI_INV_IF。接口和表单的名称建议分开管理,不要混在一起,方便后续传输和权限控制。
2.2 Adobe LiveCycle Designer与SAP的连接
Adobe Form的模板设计需要安装Adobe LiveCycle Designer(也叫Designer)。这个软件不是装完就能直接连SAP,还要在SAP里维持一个稳定连接,具体说就是SAP GUI、Designer、SAP应用服务器三者要网络通、版本兼容。
在SFP的初始界面,你创建完表单后,有个"打开"按钮,点它会唤起本地Designer。如果一直打不开或者报错,先检查三件事:一是SAP GUI是否以管理员身份运行,二是Designer版本和SAP NetWeaver版本是否兼容,三是本机是否有足够的临时目录权限。还有一点是RZ10参数配置,常见的一个参数是icm/HTTP相关,具体要看你的服务器是否启用了ICM的HTTP服务给ADS用。
ADS这一层很多项目会忽略。SAP服务器上需要有Adobe Document Services的相关实例配置,事务代码SFP里能看到服务健康状态。如果状态异常,PDF渲染会报类似"Form rendering failed"的错误。排查思路通常是看SMICM(ICM监控)、SM50的进程状态,以及SLG1应用日志里的ADS相关条目。
2.3 输出设备与PDF生成适配器
表单设计完成后,真正出PDF或打印,还涉及输出设备和输出控制。
对直接调用场景,比如ABAP程序用函数模块渲染PDF,得到的是一段二进制内容,后续由程序决定是存到数据库、发邮件,还是提交给打印机。对标准业务场景,比如SD发票、采购订单,更常走NACE(输出控制)。NACE里配置输出类型、输出程序、表单名称、打印机设备,这样单据过账后,系统按条件自动触发打印请求。
打印机设备用事务代码SPAD维护,设置设备类型、格式、纸盒等。Adobe Form还有一个特殊点:它可以在模板里直接设置打印属性,比如双面打印、纸型、缩放比例。这些属性如果和SPAD里的设备设置冲突,以模板设置和输出请求的实际配置为准。项目上最容易踩的坑就是:模板里设了A5纸,SPAD设备默认A4,结果打印出来被强行缩放,版面全乱了。遇到这种问题,先统一纸张规格,再看设备类型是否需要走PDF打印中间件。
3. 从零开始做一个可用的打印表单
3.1 第一步:用SFP创建数据接口
打开事务代码SFP,界面很简单,左边是接口和表单两个树节点。右键"接口",新建一个接口,比如叫ZFI_PRINT_IF。
进入接口编辑器后,主要维护两个东西:导入参数和导出参数。导入参数就是表单要用的业务数据,我习惯建一个主结构,比如ZFI_PRINT_DATA,里面包含抬头字段、日期、单号、金额、行项目内表。导出参数一般留一个输出PDF用的二进制参数,类型XSTRING,方便后续程序拿内容。
有一点要特别注意:接口激活时,SAP会基于字段名生成对应的上下文(Context)树。如果接口结构里字段名带下划线或特殊字符,在Designer里绑定时可能显示异常,容易误操作。所以接口里的字段命名,最好用纯字母和数字,语义可读即可。
接口激活后,可以在"预览"里看到生成的XML结构,之后在Designer里绑定的数据源就是这棵树。
3.2 第二步:画表单布局并绑定数据
有了接口,就可以在SFP里新建表单,选择引用该接口。保存后点击"打开",本地Designer会弹出XDP模板。
Designer的使用逻辑和普通图形设计软件类似,左边是数据视图、中间是设计视图、右边是对象属性。初次进来最关键的是把数据视图中的节点,拖到设计视图中对应的位置,形成绑定关系。
我有几个设计经验可以分享:
第一,页面尺寸和页边距要提前在"页面设置"里定好,不要画完内容再改,否则所有控件位置都会移位。
第二,重复行项目的Table,要用Subform包裹,并把Subform的"内容类型"设置为"流式",重复的数据会自动换行生成多行,否则一个订单10个行项目会挤在一页或只显示一行。
第三,固定区域和动态区域最好分开。抬头、公司Logo、签名区用固定位置;明细、条款这类会变长的内容,放在动态Subform里,并设置内容溢出的行为是"翻页"还是"继续在本页扩展"。
关于绑定,常见有两种方式:直接拖拽,等于把某个字段和控件做了单向绑定;也可以在控件属性里选"数据绑定",手动选择数据节点。我更推荐后者,尤其是做复杂表格时,手动绑定更可控,不容易把树节点拖错。绑定完成后,在Designer里切到"预览PDF"模式,能看到模拟效果,这一步就能提前发现字段重叠、宽度溢出等问题。
检查无误后,保存并返回SAP,激活表单。激活通过后,在SFP里可以看到表单的状态为"有效"。
3.3 第三步:在ABAP程序里调用表单
使用场景不同,调用方式不一样。如果是自己写ABAP程序做打印,核心是调用SAP标准函数生成PDF。
比较标准的调用过程是:
DATA: ls_docparams TYPE sfpdocparams, ls_data TYPE zfi_print_data, lv_pdf TYPE xstring. * 打开打印工作 CALL FUNCTION 'FP_JOB_OPEN' EXPORTING i_device = 'PRINTER' EXCEPTIONS OTHERS = 1. * 获取表单对应的函数模块名 CALL FUNCTION 'FP_FUNCTION_MODULE_NAME' EXPORTING i_name = 'ZFI_PRINT_FORM' IMPORTING e_funcname = lv_funcname. * 动态调用表单函数模块 CALL FUNCTION lv_funcname EXPORTING /1bcdwb/docparams = ls_docparams is_data = ls_data IMPORTING /1bcdwb/formoutput = lv_pdf. * 关闭打印工作 CALL FUNCTION 'FP_JOB_CLOSE' EXCEPTIONS OTHERS = 1.注意,FP_FUNCTION_MODULE_NAME返回的不是固定函数名,而是SAP根据表单动态生成的一个函数模块,名称通常是一串系统字符。所以不管你的表单叫什么,代码里都用这个动态调用的方式去获取和执行。
如果只是想在程序里生成PDF文件,不需要真正打印,可以通过/1bcdwb/formoutput拿到PDF二进制,然后存到ALV的OLE相关应用或发送邮件。这种方式很常见,用于替代传统的打印,直接把PDF作为附件发出去。
3.4 第四步:挂到输出控制上跑通打印
如果要在标准业务中自动触发打印,比如FI过账后自动打印凭证,通常配置NACE输出控制。
以FICO为例,事务代码NACE,选择应用范围"FI",进入输出类型列表。新增一个输出类型,指定输出程序(一般是标准驱动或自定义驱动)、表单名称、触发事件、打印设备。保存后还要维护访问顺序和条件记录,让系统知道什么条件下用这个输出类型。
配置完成后,做一张凭证测试,到事务代码SP01里查看输出请求。如果请求状态是"挂起"或"错误",双击进去能看到错误日志。常见的问题是输出程序里没有正确传递表单字段,或者表单名称写错,重新检查NACE配置即可。
这里的经验是:先把表单用ABAP程序直接调用跑通,再挂到NACE。如果一上来就配NACE,出了问题很难分清是配置问题、驱动问题还是表单本身的问题。分步排查,是最省时间的方式。
4. 踩坑实录:中文乱码、分页错乱、性能卡顿
4.1 中文显示成方块的真正原因
Adobe Form在中文环境下最常见的问题,就是模板预览时一切正常,到了正式输出PDF,中文全变成方块或者直接消失。
这个问题的根源,绝大多数是Designer模板里的字体设置。模板在渲染成PDF时,需要找到对应的字体资源。如果某台开发机上Designer的默认字体是英文系统字体,比如Arial,那中文字符就没有对应字形,只能显示成方块。
解决办法有两个层面。第一,在Designer里把所有要显示中文的文本控件、TableCell,统一设置字体为支持中文的字体,比如SimSun(宋体)或Microsoft YaHei(微软雅黑)。第二,如果模板本身是从英文环境继承来的,批量处理所有控件,避免漏改。
另一个容易被忽略的是,嵌入字体的设置。在Designer的表单属性里,会有字体嵌入选项。如果是生产环境需要稳定显示,建议嵌入中文字体子集。但要注意,嵌入字体会让PDF文件体积变大,打印数百页批量单据时,会影响性能。所以嵌入与否,要在"显示准确性"和"文件大小"之间权衡。通常对于内部打印,不嵌入问题不大;对于外部发送给客户的PDF,建议嵌入。
4.2 动态分页与表格换页的处理
做单据打印,最郁闷的就是明明设计了明细表格,行项目一多,第二页的表头、边框、合计位置全乱了。
Adobe Form的分页和Smart Forms不太一样,它没有直接的"分页"节点,而是靠Subform的属性来控制。你要做的是:把每页重复出现的表头区域,放在主页面(Master Page)上,或者设置Subform的"重复"属性,让表格头自动跟着内容走;把明细行内容放在一个可重复的Subform内,内容溢出到页面底部时,自动跳到下一页。
实际操作中,我会建一个"页头"区域,固定显示单号、日期、公司名;再建一个"明细流"区域,里面套一个Table,绑定行项目;最后建一个"页脚"区域放合计、签名条。三个区域分属不同的Subform,各自设置绑定和溢出方式。这样做出来的表单,不管行项目多少,都不会出现表头只出现在第一页、后面几页光秃秃的情况。
还有一个小技巧:明细Table里如果有多列,要注意列宽总和是否正好等于页面可用宽度。差一点点在预览时看不出来,但正式输出时会出现折行或者列宽被自动拉伸。可以在Designer里打开"标尺",把列宽调整为整数,减少这种偏差。
4.3 批量打印慢的排查思路
批量打印慢,通常是ADS性能问题,而不是模板本身的问题。
如果打印机一次要出几百份PDF,后端每个请求都要调ADS渲染。排查思路按这个顺序:
第一,看服务器的ICM线程数和进程数是否足够。ADS渲染是CPU密集操作,如果配置过小,一到月底打印高峰就排队。
第二,看打印请求是否走了后台作业。标准输出控制是可以在NACE配置成后台生成的,如果不配置,前台就会同步等待PDF生成,体验很差。
第三,看是否有多余的字体嵌入。刚才说的字体嵌入,在批量打印时是性能杀手。如果只需要打印,可以关闭嵌入,PDF作为中间打印格式会小很多。
第四,优化模板复杂度。尽可能减少不必要的Subform嵌套层数,少用大图片和复杂阴影。设计模板时,图片尽量用压缩后的PNG或JPG,不要放设计原图。
最后一点实践心得:如果批量量确实很大,建议把打印请求拆分到多个后台作业中,同时处理。NACE支持按公司代码、按时间段拆包,配合后台作业调度,能有效分散压力。
4.4 传输与权限的几个坑
表单相关的传输和权限,也容易出问题。
第一,接口和表单是两个独立的开发对象,都需要加入传输请求。经常有人只传输了表单,忘了接口,结果在测试机里激活表单时提示找不到接口。注意检查两个对象的传输记录。
第二,LiveCycle Designer的本地缓存。开发机如果改过模板,客户端有缓存,重新下载模板时可能拿到旧版本。在Designer里可以通过"文件-恢复"或者清理缓存目录来强制刷新。项目规范上,我推荐把XDP文件也纳入版本管理,每次修改后导出一份备份,避免多人开发时互相覆盖。
第三,权限方面。普通顾问通常只需要开发权限,但生产环境回传表单要经过审批。有人会在传输请求里漏掉表单对应的函数模块生成逻辑,导致生产机激活后找不到动态函数名。这种问题不多,但遇过一次就很头疼。经验是:表单传输完成后,在生产环境用事务代码SFP激活一次,再跑一遍调用程序,确保一切正常。
5. 把这张表单做扎实:进阶设计建议
5.1 字段不是越多越好:接口设计原则
接口结构的设计,直接影响后续维护的轻松程度。我见过很多接口越改越乱,最后连负责开发的同事都分不清哪个字段是哪个版本的。
接口字段建议遵循三个原则:一、只放表单真的要显示的字段;二、要显示但源系统没有的字段,尽量在程序里预先算好,不要在模板里做复杂计算逻辑;三、预留一个扩展字段区,比如XSTRING类型或CHAR200的备注字段,方便紧急加内容时不用改接口结构。
一个反直觉的经验是:接口结构越精简,绑定越不容易出错。不要试图把一个数据字典大结构整个做成接口,里面几十个没用到的字段,只会让Designer里的数据树变得臃肿,找绑定目标都费劲。每次看到"开发人员图省事直接把EKKO、EKPO全塞进接口"的项目,后续大概率要返工。
5.2 模板复用与样式统一
在一个大型项目里,不同单据之间常有共同的表头样式、公司信息、签名栏逻辑。建议做一个公共模板库。
Adobe Form的LiveCycle Designer支持对象模板和片段(Fragment)。把常用的Logo区域、表格样式、页脚声明保存为片段,新建表单时直接拖拽复用。这样有两个好处:一是新表单开发速度快;二是公司统一改Logo或改页脚时,只改片段,引用它的表单都跟着变。
但片段的使用要规范。如果引用片段的表单被改了局部样式,后续再更新片段时,冲突处理很费劲。所以我建议:公共高频元素用片段,特殊版式尽量独立,别过度复用。
5.3 后续还能往哪些方向扩展
Adobe Form作为一个成熟的表单技术,除了传统打印,还有很多扩展玩法。
一是PDF归档。很多财务项目要符合会计档案电子化要求,Adobe Form可以直接输出PDF/A格式,配合SAP的归档组件存到系统。
二是条形码和二维码。仓库场景,拣配单、装箱单上会印二维码,里面包含单据号、物料号,现场扫码就能确认;这种实现比传统条形码灵活得多。
三是邮件自动发送。把表单生成的PDF作为附件,通过邮件服务器自动发给客户或供应商,现在很多企业对账平台就是这么玩的。程序里拿到PDF内容后,拼接邮件即可,不需要额外打开一个打印对话框。
四是结合S/4HANA的新表单技术。SAP S/4HANA Cloud环境里,Adobe Form的完整能力正在被云端的Print Form方案逐步吸收,但内部部署项目里Adobe Form仍然是一个稳妥的选择。如果你在S/4HANA on-premise升级项目里做过表单,会发现在打印能力上,Adobe Form的很多经验可以直接平移,所以学习它并不是在学一门将被淘汰的技术。
最后再分享一个我自己的习惯:每次交付一张表单,我都会把字段清单、绑定关系、字体设置要求、测试样本这四样东西整理成一份简单的说明文档,放到项目共享目录。因为表单这个东西,半年后再回头看,连自己都可能遗忘当时绑的是哪个节点、为什么给这个Subform设置了溢出方式。留一份文档,既是对自己负责,也是对后来维护的人负责。