ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SAP Script调用PERFORM:从传参调试到逻辑处理的完整指南

SAP Script调用PERFORM:从传参调试到逻辑处理的完整指南 1. 为什么文本里拼不了逻辑三个场景逼你上PERFORM1.1 场景一要输出的值不在打印程序的全局变量里接手SAP Script表单维护的人第一课基本都是文本元素里能显示的符号就是那个字段不是你想显示什么就显示什么它必须来自当前打印驱动程序的变量池。举个例子采购订单打印模板里要显示“该供应商最后登录系统日期”这种听起来有点诡异的字段。文本里直接写USR02-LDATE是不可能的因为SAP Script不会因为你写了个表字段符号就自动去数据库查。绝大多数标准打印程序里根本没有这个变量符号解析后就是空值。有人会去SE71里翻字段目录、看程序数据结构翻半天也找不到。这时候唯一合理的路就是用preform到ABAP侧取数、加工完再传回文本区。类似的情况还有要显示会计凭证的过账期间、要取出物料主数据里的某一类自定义字段、要根据某个后台配置表的标志决定输出文本。这些全是“标准模板没给你备好变量”的典型场景。1.2 场景二一张单子要判断多次条件结构比想象中弱SAP Script文本元素里有/: IF、/: ELSE、/: ENDIF这一套控制命令但它的比较能力非常有限本质上是基于字符内容的条件分支不支持查表、不支持循环更不要指望在文本里写正则表达式。我第一次用SAP Script时干过一件蠢事想在文本里判断交货单的重量单位是KG还是LB然后显示不同的提示语。我在文本里写了整整三行/: IF嵌套测试时发现单位符号带前导空格比较永远不成立。折腾了半小时最后老老实实把这个判断搬进ABAP程序里的FORM处理完返回一个显示内容。同样一个逻辑在ABAP里写不超过五行还不用考虑文本解析器那一堆隐式转换问题。业务需求稍微复杂一点——比如“根据客户主数据的行业分类决定页脚要不要显示特定声明语句”——这种情况下你把判断全部摊在SAP Script文本里维护成本极高。后任顾问看到一段十几行的控制命令嵌套基本只能删了重写。preform至少还能让逻辑待在正常ABAP代码里有缩进、有注释、有断点。1.3 场景三计算和格式化文本控件不是计算器金额千分位、日期转成“YYYY年MM月DD日”的中文习惯、把多行明细拼接成一个摘要字符串。这些过程式逻辑在SAP Script里几乎无解你没法在文本里定义临时变量、做循环累加更没法调用转换退出。尤其那种“把若干行项目汇总成一个提示串放在页脚”的需求。SAP Script的Main窗口虽然能做LOOP配合表结构循环显示多行但那是给主体明细用的页脚这种汇总区域用起来很别扭。而preform里可以声明任意内表先取数、再排序、再拼接、最后返回一个字符串。说白了文本负责“摆”逻辑负责“算”分工明确。1.4 为什么不直接CALL FUNCTIONPERFORM的优势边界有人会问SAP Script控制命令里也有/: CALL FUNCTION能直接调函数模块为什么还要绕到ABAP程序的FORM我的习惯是这样的个别的、一次性的格式化需求用CALL FUNCTION确实更快比如直接调CONVERSION_EXIT_ALPHA_OUTPUT输出物料号。但一旦涉及多步逻辑、需要查多张表、有分支判断、要传递内表上下文时函数模块的参数接口比较死板调试时经常要跳进函数内部不直观。而PREFORM是你自己写的普通ABAP子程序参数可以直接用CHANGING传回来内部可以用任意ABAP语法还能顺手打一个断点看调用栈调试体验完全是正常ABAP开发的体验。还有一个实际原因很多SAP Script标准模板本来就带着一堆PERFORM ... IN PROGRAM调用比如销售订单打印里能看到PERFORM KOPIERTEXT这类写法。顺着这个模式扩展比在文本里硬插函数模块更符合系统既有惯例后续顾问接手也更容易理解。2. 最小调用模板与传参机制从一行PERFORM到程序断点2.1 在SE71文本元素里写下一组控制命令先给一个最小可用的调用模板在SAP Script的文本元素中这样写/: PERFORM GET_USER_LOGIN_INFO IN ZSD_SCRIPT_BLOG /: USING SY-UNAME /: CHANGING ZUSER_LOGIN_INFO拆开说几个要点行首的/:是SAP Script控制命令标志PERFORM、USING、CHANGING这些行都属于同一组调用中间不要插普通文本行。PERFORM后面跟的是ABAP侧子程序名IN后面跟ABAP程序名。程序名必须大写而且必须是当前系统里真实存在的程序否则打印时会在假脱机日志里报类似“PERFORM NOT FOUND”的错误。USING是传值进去CHANGING是传回来。SAP Script文本里的符号用包裹这些符号通常是打印程序里的全局变量或者当前窗口能解析到的输入字段。这里有个容易忽略的点CHANGING后面的符号如果不存在于打印程序的变量池SAP Script不会在保存时明确报错运行时它会当成空值处理。你看到的结果就是“函数好像执行了但返回字段是空的”白白排查半天。2.2 ABAP侧FORM定义入参为什么推荐TYPE CLIKEABAP侧对应子程序的骨架长这样FORM get_user_login_info USING p_bname TYPE clike CHANGING p_result TYPE string. 这里写取数和加工逻辑 ENDFORM.我见过不少人在这个位置直接写USING p_bname LIKE sy-uname大多数情况下也能跑通但有一个隐患SAP Script文本里解析出来的值本质是字符内容如果你在ABAP侧声明的是D类型或N类型字段系统会做一次隐式转换。转换能过还好一旦传入空串、一串空格、或者带前后缀的显示格式化文本ABAP运行时很可能直接抛异常而你还很难从假脱机日志里看出端倪。所以我的习惯很固定入参一律定义成TYPE clike也就是接受所有字符类数据。先在子程序内部做判空、做归一化再赋值给其他类型变量。CHANGING返回参数尽量用TYPE string别用定长字符也别用D/T这种强类型。返回之前你可以在ABAP里任意格式化返回后SAP Script只管显示不管类型。2.3 完整实例读取用户最后登录日期输出到表单这个例子可以覆盖查询、判空、格式化、传回四个环节核心代码不多但每个环节都有坑。FORM get_user_login_info USING p_bname TYPE clike CHANGING p_result TYPE string. DATA: ls_usr02 TYPE usr02, lv_date TYPE d. CLEAR: ls_usr02, p_result. IF p_bname IS INITIAL. RETURN. ENDIF. SELECT SINGLE ldate FROM usr02 INTO ls_usr02-ldate WHERE bname p_bname. IF sy-subrc 0. p_result 用户主数据不存在. RETURN. ENDIF. IF ls_usr02-ldate IS INITIAL. p_result 从未登录. RETURN. ENDIF. lv_date ls_usr02-ldate. WRITE lv_date TO p_result USING EDIT MASK __.__.____. ENDFORM.几个细节值得说明。usr02表存的是用户主数据登录相关字段ldate是最后一次登录日期字段本身是D类型。为什么我不直接查完就往p_result里塞因为D类型在后台赋值给字符串时输出默认是YYYYMMDD这种紧凑格式业务上看不懂。所以我先赋给本地lv_date再用WRITE ... TO ... USING EDIT MASK按业务习惯格式化输出。p_bname IS INITIAL的判断一定要放在最前面。如果SAP Script文本里SY-UNAME不知为何没有值你后面用字符串去查询是没有意义的而且还会让整个FORM空转一次。返回一个空字符串至少不会让界面显示出一堆奇怪的东西。如果用户查不到我给的返回是“用户主数据不存在”而不是清空。开发测试阶段这种显性文本能帮你一眼看出问题。上线前如果不想让用户看到这句可以再改成更平和的提示。2.4 传参机制的本质符号解析后都是“文本”这个点想单独拿出来说因为它解释了最诡异的那些现场。SAP Script文本里的VAR在打印解析阶段会被替换成VAR变量在那一刻的内容。如果VAR本身是日期字段替换后的结果会遵循该字段的输出格式如果VAR是金额替换后可能带千分位分隔符甚至带正负号。也就是说你的preform入参虽然是“值传递”但你拿到的值并不一定是你程序里习惯的那个内部格式。比如日期打印程序里sy-datum是20250115这样的8位内部格式但用户参数里把日期显示格式改成了MM/DD/YYYY文本里某个符号被格式化输出后传到preform里可能是01/15/2025。如果你在ABAP侧直接就lv_date p_date轻则转换出非法日期重则直接异常。所以preform的第一职责永远是“清洗入参”。我在团队里定的规矩是所有从SAP Script文本进来的入参先当成不可信字符串处理做归一化、做类型判断、做范围和空值检查然后才进入业务逻辑。这条规矩救过我很多次尤其是碰上用户改了个人显示参数导致打印格式全乱的案例。3. 数值判断、日期换算、内表排序preform里三个高频逻辑3.1 数值校验CO比正则更顺手但别忽略空串从SAP Script文本传进来的数字字段往往不干净可能带逗号千分位分隔符、带正负号、甚至带一串前导空格。你写一个通用校验FORM是很有必要的。FORM is_numeric USING p_value TYPE clike CHANGING p_flag TYPE c. p_flag X. IF p_value IS INITIAL. p_flag . RETURN. ENDIF. SHIFT p_value LEFT DELETING LEADING space. IF p_value CO 0123456789. RETURN. ENDIF. p_flag . ENDFORM.这里用了ABAP的COContains Only比较运算符判断字符串是否只包含数字字符。为什么不用cl_abap_matcher正则也能用但CO一行写完老系统兼容性好性能还高。唯一要注意的是空字符串对CO来说也成立所以必须先判IS INITIAL。如果业务上允许负数和小数再额外允许-和.出现一次否则直接返回失败。判断小数的时候还有个常见坑欧洲用户习惯用逗号当小数点判断之前最好确认SAP Script字段输出是否受语言环境影响。3.2 日期处理先归一化成8位D类型再谈格式化日期是preform里翻车率最高的类型。我推荐的做法是写一个标准的“日期归一化”FORM专门把各种格式的文本整理成D类型。思路是先移除所有常见分隔符比如点、横线、斜杠得到8位字符再根据用户习惯判断是YYYYMMDD还是DDMMYYYY最后转成D类型。FORM normalize_date USING p_date_in TYPE clike CHANGING p_date_out TYPE d p_ok TYPE c. DATA: lv_raw TYPE string. p_ok . lv_raw p_date_in. CONDENSE lv_raw NO-GAPS. REPLACE ALL OCCURRENCES OF . IN lv_raw WITH . REPLACE ALL OCCURRENCES OF - IN lv_raw WITH . REPLACE ALL OCCURRENCES OF / IN lv_raw WITH . IF strlen( lv_raw ) 8. RETURN. ENDIF. 如果前两位超过12基本是YYYYMMDD反之后两位是年份 IF lv_raw(2) 12. p_date_out lv_raw. ELSE. CONCATENATE lv_raw4(4) lv_raw2(2) lv_raw(2) INTO p_date_out. ENDIF. IF p_date_out IS NOT INITIAL. p_ok X. ENDIF. ENDFORM.这段代码是实践逻辑不是标准库用它之前务必按项目实际情况调整。但思路是通用的不信任原始格式、先统一分隔符、再判断年月日顺序。更稳妥的方案永远是“源头规避”——如果SAP Script文本里能直接引用一个D类型全局变量那就别绕道取格式化后的文本。preform的入参填写阶段仔细观察符号的类型定义能少掉后面一大半日期转换的麻烦。3.3 内表排序与去重在PREFORM里拼出多行结果SAP Script的文本区域不太擅长展示preform内部循环产生的多行内容但有一个常见需求是“把若干行项目的关键信息汇总成一个字段显示在页脚”。这时候在preform里先取数、再排序、再拼接是很顺手的一套组合。我先给一个根据交货单号生成物料清单摘要的示例FORM build_item_list USING p_vbeln TYPE vbeln_vl CHANGING p_list TYPE string. DATA: lt_vbap TYPE TABLE OF vbap, ls_vbap LIKE LINE OF lt_vbap, lv_tmp TYPE string. CLEAR: p_list. SELECT posnr matnr FROM vbap INTO (ls_vbap-posnr, ls_vbap-matnr) WHERE vbeln p_vbeln. APPEND ls_vbap TO lt_vbap. ENDSELECT. IF lt_vbap IS INITIAL. RETURN. ENDIF. SORT lt_vbap BY matnr posnr. DELETE ADJACENT DUPLICATES FROM lt_vbap COMPARING matnr. LOOP AT lt_vbap INTO ls_vbap. IF p_list IS INITIAL. p_list ls_vbap-matnr. ELSE. CONCATENATE p_list ls_vbap-matnr INTO p_list SEPARATED BY ,. ENDIF. ENDLOOP. ENDFORM.这里演示了两个高频动作。SORT lt_vbap BY matnr posnr是为了让拼出来的物料号有确定顺序。别小看这一步数据库返回的顺序不保证稳定同一个单据多打几次顺序可能都不一样。业务上如果要求“按物料号升序输出”就必须在ABAP侧做显式排序。DELETE ADJACENT DUPLICATES只删除相邻重复行所以必须先SORT再删否则重复项如果没有挨在一起删不掉。这个机制跟其他语言里的去重函数不太一样新手经常踩坑。另外注意我用了COMPARING matnr意思是只要物料号相同就视为重复即使POSNR不同也删除。如果业务上要求同一行项目内部的不同子项都要保留这个参数就要去掉。3.4 关于字符串长度与换行符的一个提醒把内表拼成一个长字符串返回给SAP Script文本时要留意输出设备的行宽限制。老的打印格式一行能显示多少字符取决于窗口位置和打印机设备类型。如果摘要内容太长可以考虑在form内部加工成带换行符的字符串比如用cl_abap_char_utilitiescr_lf拼接再传给文本。但不同输出设备对换行符的解析不完全一致这跟SAP Script的窗口高度设置也有关系。我的建议是先跑一次预览看看换行效果再决定是拆成多个返回变量还是用换行符。4. 调试三板斧断点、现场留痕、条件定位4.1 断点命中前台打印才能进调试器preform是ABAP子程序所以最直观的调试方法就是打断点。操作上在目标FORM的第一行执行代码处加一个断点FORM get_user_login_info USING p_bname TYPE clike CHANGING p_result TYPE string. IF sy-uname ZSD_ADMIN. 先只放行调试用户 BREAK-POINT. ENDIF. 其余逻辑 ENDFORM.然后从SE71进入表单点测试打印或者保存后执行预览保持前台运行。只要执行路径走到这个FORMABAP调试器就会弹出来。有一个现实限制后台批量打印不会弹调试器。SM37里的后台作业、通过输出类型自动触发的后台打印都进不了交互式调试。碰到这种情况你有两个选择临时把打印方式改到前台或者用下一节讲的日志留痕。调试点命中后我建议先看调用栈。因为SAP Script是在运行时动态触发ABAP子程序的调用栈里会出现一段类似RSTXPRPS或者系统打印驱动的帧会让人以为跳到了系统程序里。别慌一层层翻你自己的程序名就在那里点进去就能看到传给这个FORM的参数值。4.2 现场留痕后台批量场景用OPEN DATASET写日志如果问题只发生在后台大批量打印里前台又复现不了那就要给preform加一段临时日志代码。最朴素的办法是写到应用服务器文件系统。DATA: lv_path TYPE string, lv_msg TYPE string. CONCATENATE /tmp/script_debug_ sy-uname .log INTO lv_path. OPEN DATASET lv_path FOR APPEND IN TEXT MODE ENCODING DEFAULT. CONCATENATE BNAME p_bname RESULT p_result INTO lv_msg. TRANSFER lv_msg TO lv_path. CLOSE DATASET lv_path.这段代码会以追加模式写文件所以不会覆盖上一次的内容。调试完记得删掉别留到生产环境。如果你想直接在用户电脑桌面上生成日志文件理论上可以用cl_gui_frontend_servicesget_desktop_directory拿到桌面路径再配合前端文件操作。但要注意后台作业没有GUI会话拿不到前端桌面路径。所以这个方法只适用于前端触发打印、但临时又不想打断点的情况。我在实际项目里更习惯直接写应用服务器的/tmp目录或者干脆写进一张自定义日志表查询和清理都方便。4.3 条件定位为“问题单据”单独开门有时候一个preform被成千上万张单据调用只有某一个客户、某一类物料才出问题。你不可能每张单都打断点人会被逼疯的。我一般直接在FORM开头放一个条件断点。IF p_vbeln 0000001234 OR p_kunnr 100020. BREAK-POINT. ENDIF.这个写法比在ABAP调试器里设置条件断点直观得多。SAP Script调用链很长调试器里的条件断点在复杂调用栈中偶尔不生效而写在代码里的IF ... BREAK-POINT是百分之百会执行到的。还有个小技巧如果断点进来后你想确认SAP Script文本传给这个FORM的原始值到底是什么直接在调试器里看入参变量就够了。调试器里显示的p_z输入值就是经过SAP Script解析后的最终文本内容所有截断、格式转换问题在这一眼就能看出来。4.4 调试前白检查程序激活、命名、变量定义、多窗口有四次我排查了很久最后发现根本不是逻辑问题而是很基础的东西没弄对。列一个检查清单帮你省时间ABAP程序是否激活改了FORM保存了但没激活SAP Script打印时仍然调用的是旧版本甚至可能报“找不到FORM”。程序名和FORM名大小写对不对SAP Script调用命令里的程序名通常全大写靠这一点能规避掉很多大小写差异问题。CHANGING后面的符号在打印程序里有没有定义没定义则运行时默认空值而且不上报错误。同一个窗口或者多个窗口是否多次调用了同一个FORM并写同一个返回符号如果是后一次调用会覆盖前一次结果。这类问题有一个共同特征你单独看文本、看FORM代码都觉得没问题组合起来就是出不来。所以我把它们定义成“调试前白检查”任何preform表现异常先过这一遍再深入业务逻辑。5. 锁、去重、并发preform边界问题别踩5.1 锁与解锁不要顺手DEQUEUE_ALL有时候你会在preform里看到这种写法加了一把锁逻辑处理完程序末尾直接调用DEQUEUE_ALL释放所有锁。短期测试没问题但风险很大。DEQUEUE_ALL释放的是当前工作进程中的所有锁对象而不是只有你刚加的那一把。在批量打印或者同一个后台作业连续处理多张单据的场景下其他功能模块可能也持有锁你一个DEQUEUE_ALL就把别人正在保护的资源也解了。这种问题极难排查因为它不是每次必现要看作业的调度顺序。正确做法很简单加锁用的是哪个ENQUEUE函数解锁就用对应的DEQUEUE函数。比如对某个锁对象加的锁就调它配套的DEQUEUE_E...系列功能模块。如果你是在打印程序外层加锁那解锁也应该放在外层别混进preform里。坦白讲我通常不建议在SAP Script打印链路里加数据库锁。打印本质上是把当前数据快照输出出来锁数据的收益很有限反而带来并发隐患。真有并发修改风险更可靠的是在业务发布单据的状态字段上做限制那才是应用层的正解。5.2 多次PERFORM的变量覆盖与重复取数一个表单的抬头窗口、行项目窗口、页脚窗口逻辑上可能各自触发同一个PREFORM往同一个全局符号里写结果。这个设计很容易踩覆盖的坑明明这个窗口需要显示A值那个窗口需要显示B值但因为共用Z_COMMON_OUT后执行的调用把前一个结果覆盖了。我的建议是返回符号按业务含义分开命名比如Z_HEADER_OUT、Z_FOOTER_OUT不要每个窗口都用一个通用OUT变量。如果确实需要在一个变量上做累积就在FORM内部读出旧值再拼接。还有一个相关问题是重复取数。抬头窗口查一遍客户主数据行项目窗口又查一遍同一个客户主数据偶尔还有第三处查询。打印一张单子数据库压力不大但批量打印几百张时重复查询就很明显。性能优化上可以在preform里用EXPORT/IMPORT配合内存ID做会话内缓存。不过要注意内存ID的作用范围是整个ABAP内存会话并行打印作业共享同一内存时可能会串。所以缓存ID最好带上单据号例如ZSD_CUST_ 客户号降低串数据风险。5.3 入参不可信KEY要转换异常要显性单据号、物料号、客户号这一类KEY从SAP Script文本传进来时可能会丢失前导零。比如物料号内部存储是000000000000010000文本显示时经过转换退出可能变成10000传到preform里如果你直接拿这个值去查表查不到。处理KEY的标准动作是调用转换退出CALL FUNCTION CONVERSION_EXIT_ALPHA_INPUT EXPORTING input p_matnr IMPORTING output lv_matnr_internal.然后在正式查询前再判断一下转换结果是否有效。如果遇到查不到数据的异常场景我倾向于在返回字段里放显性内容比如“未找到对应主数据”而不是静默清空。开发阶段这个提示非常有用能让你第一时间知道是KEY没传对还是数据本身缺失。等上线前再根据业务需要改成正式文案。6. 迁移到新表单之前的工程化建议6.1 逻辑收敛到公共FORM集避免撒进打印程序接手过几个项目之后我把SAP Script的preform逻辑统一收进了一个公共子程序池比如ZSD_SCRIPT_COMMON里面放日期格式化、数值校验、取数工具所有表单文本的/: PERFORM都指向这个公共程序。这样做的好处很多。第一重复代码少了抬头和页脚不再各写一份相同的格式化逻辑。第二测试方便你可以在SE38里写个测试报表直接调这些FORM不用每次打印表单才能验证。第三后续迁移的时候公共FORM集是你最重要的资产。注意一个细节子程序池不需要像报表程序那样能直接运行也能被SAP Script调用。只要程序存在、激活并且FORM名在程序里存在就可以。6.2 迁移Smart Forms/Adobe时哪些代码能直接带走如果你在评估把SAP Script迁移到Smart Forms或者Adobe Formspreform的很多逻辑是可以平移的。严格依赖符号取数的部分迁移成本最高因为新平台的参数接口完全不同。但纯计算、纯校验型的子程序几乎可以原样复制到新的函数模块或者类方法里。比如数值判断、日期格式化、内表排序拼接这些跟表单引擎无关直接就带过去了。所以我在写preform时一直强调“入参清洗、逻辑独立、返回字符串”。这种风格天然有利于后续迁移。反过来如果你把业务逻辑直接堆在打印程序的全局变量操作里迁一次等于重构一次。6.3 哪些情况我还会坚持留在SAP Script虽然新项目普遍优先考虑Adobe Forms甚至S/4HANA输出控制但实际工作中仍有不少场景留在SAP Script更合理。客户或集团有统一的SAP Script标准模板改起来文件最小、验收最快目标环境没有部署Adobe相关服务端组件或者只是在一个几十年的老模板上加一个字段、改一段提示语。这种情况下我一般不会劝人迁移Scope太大、风险太高收益又不确定。合理的做法是先把要改的逻辑在preform里做干净控制好影响面。这套东西确实老但在存量系统里它一天不退役就一天需要有人能把它玩明白。preform是最关键的那块拼图。我在实际项目里最快定位表单问题的方式永远是先跑一遍预览进断点看SAP Script传给form的入参值如果复现不了再上日志留痕。这两步做完九成问题都能定性。别一上来就怀疑是SAP Script解析器坏了绝大多数时候是我们自己没把传参约定理清楚。写FORM时多花十分钟处理字符清洗和类型校验后面调试能省出一个下午。
返回列表