惯例闲话:7月,闲人家乡的水蜜桃成熟了,闲人很喜欢吃桃子,可惜经常出门在外,经常错过了水果最好的季节,这次委托家人邮寄了几箱,果然还是家乡的桃子好吃。回顾这几年,错过了不仅仅是水果,孩子的教育、成长,都是有很大的缺位。
做顾问到了这个年纪,也确实是要考虑家庭和事业均衡的时候了。PLM项目还有2-3个月,是差不多该做出决定了。
闲话到此为止,这次聊聊标准报表的陷阱!
项目生产工单成本和入库统计
项目制生产常见需求,统计某个项目在某个区间内投入和产出。
索引条件通常有
1、项目
2、WBS
3、科目
4、日期区间
财务报表带上时间区间的,往往就意味着 附带效率问题。而在项目型业务中,这种效率问题更为突出,通常会按整个项目全生命周期来查询累计,这种必然会引起报表效率问题。
解决效率问题,无外乎以下几种方法:
1、存表法
1)明确数据的汇总级别,存在项目制业务的,通常按项目、WBS作为汇总级别,常规业务则颗粒度细一点,按工单。可作为项目报表的下级钻取;
2)期末数据保存自定义表中,作为下一期期初数据,累加;
3)下一期数据仅查询期间内,通常为一个自然月为期间。
2、BW,本质也是存表;
3、调用标准程序法,这是近些年,闲人在项目上遇到的某个财务顾问的创新,直接调用KOB1N,CJI3N等。
前台选择条件如下
代码样例如下:
"获取ALV内容并且不显示ALV
CL_SALV_BS_RUNTIME_INFO=>SET( EXPORTING DISPLAY = ABAP_FALSE
METADATA = ABAP_FALSE
DATA = ABAP_TRUE ).
IF RT_AUFNR IS NOT INITIAL.
SET PARAMETER ID ‘CAC’ FIELD ‘CSCE’.
SUBMIT RKAEP000 WITH P_KOKRS = ‘CSCE’
WITH AUFNR IN RT_AUFNR "订单号
WITH R_BUDAT IN RT_BUDAT "过账日期
WITH P_HANA = ‘X’ "加速选择
WITH P_TREE = ‘’ "层次显示
WITH P_IMOUT = ‘’ "从完成清单开始
WITH P_GROBJ = ‘’ "组对象
WITH P_TCODE = ‘KOB1’
EXPORTING LIST TO MEMORY AND RETURN.
TRY.
"从内存中获取ALV内表数据
CL_SALV_BS_RUNTIME_INFO=>GET_DATA_REF( IMPORTING R_DATA = LR_KOB1N_DATA ).
ASSIGN LR_KOB1N_DATA->TO <LT_KOB1N_DATA>.
IF <LT_KOB1N_DATA> IS ASSIGNED.
"处理数据
LOOP AT <LT_KOB1N_DATA> ASSIGNING FIELD-SYMBOL(<LFS_KOB1N_DATA>).
CLEAR:LS_KOB1N_DATA.
MOVE-CORRESPONDING <LFS_KOB1N_DATA> TO LS_KOB1N_DATA.
COLLECT LS_KOB1N_DATA INTO LT_KOB1N_DATA.
ENDLOOP.
"以订单为维度汇总
"以成本要素KSTAR 为条件获取:累计入库金额、当期入库金额、累计生产成本
WITH +KOB1N_SUM AS (
SELECT
DATA~AUFNR, "订单
DATA~RWAER, "报表货币
CASE WHEN DATA~KSTAR = '5011999910'
AND DATA~GJAHR = @P_GJAHR
AND SUBSTRING( DATA~PERIO,2,2 ) = @P_MONAT
THEN SUM( DATA~WRGBTR )
END AS ZXXX2, "累计入库金额
CASE WHEN DATA~KSTAR = '5011999910'
AND DATA~GJAHR = @P_GJAHR
AND SUBSTRING( DATA~PERIO,2,2 ) = @P_MONAT
THEN SUM( DATA~WRGBTR )
END AS ZXXX3, "当期入库金额
CASE WHEN DATA~KSTAR <> '5011999910'
AND SUBSTRING( DATA~KSTAR,1,3 ) <> '998'
AND DATA~GJAHR = @P_GJAHR
AND SUBSTRING( DATA~PERIO,2,2 ) = @P_MONAT
THEN SUM( DATA~WRGBTR )
END AS ZXXX1 "累计生产成本
FROM @LT_KOB1N_DATA AS DATA
GROUP BY DATA~BUKRS,DATA~AUFNR,DATA~RWAER,DATA~KSTAR,DATA~GJAHR,DATA~PERIO )
SELECT
DATA~AUFNR, "订单
DATA~RWAER, "报表货币
SUM( DATA~ZXXX1 ) AS ZXXX1, "累计生产成本
SUM( DATA~ZXXX2 ) AS ZXXX2, "累计入库金额
SUM( DATA~ZXXX3 ) AS ZXXX3 "当期入库金额
FROM +KOB1N_SUM AS DATA
GROUP BY DATA~AUFNR,DATA~RWAER
INTO TABLE @DATA(LT_KOB1N_SUM).
SORT LT_KOB1N_SUM BY AUFNR.
ENDIF.
CATCH CX_SALV_BS_SC_RUNTIME_INFO.
MESSAGE `无法获取事务码:KOB1N 的数据` TYPE 'E'.
ENDTRY.
ENDIF.
本文也是着重解构第三种方案的陷阱。
KOB1N的效率陷阱
从一般的思维角度,标准程序确实比自开发程序效率要高很多,这也是闲人的惯性思维,在多数情况,这个也是成立的。
已本案例为例,
在集成测试环节,前台测试了KOB1N和写逻辑从AUFM表中获取项目工单的投入和产出财务凭证数据。注意,闲人这里用前台测试,这个关键字眼,也恰恰这一点,被闲人和项目组同事忽略了,导致买下了一个效率的巨坑。
刚开始测试结果喜人:KOB1N的效率在15秒之内就可以读出数,而语句取AUFM时间大概在2分钟左右。项目组也毫不犹豫选择了这个“创新的方法”。
上线后1年多,财务反馈说报表出来数据异常
部分项目有入库数量没有入库金额。经过DEBUG发现
原来在最大获取数据条目数据限制了5000,这也是为啥之前很长时间没有发现效率问题所在的直接原因,系统每次最大获取5000条数据,自然不会有“问题”。
对标准代码做隐式增强,默认最大数
当然结果可想而知,大概率dump
然后按常规手法,用存表法来做,结果还是dump——很显然,每个月的生产量不一样,高峰期月数据量大,但个月也显然会内存溢出。所以,到这里,基本上就宣判了这个创新方法的“死刑”。
但是闲人不甘心,想不明白,为啥前台可以把当月的数据读出来,后台引用却不行。
直到前台操作,放出全量字段,就出现了一样的效果
再回到程序调用,果然不出所料,默认全量字段244个字段,内存够才怪!!!根本原因应该就在这个地方——内存接收数据必须全字段,不能指定若干字段。
回归原始方法
最后结论还是用原始的办法,从AUFM表中获取数据+存表
效果立竿见影
基本上10秒之内出结果
ZTFICO_PRDORDER表为历史数据表,作为本期计算的期初数据。
SELECT DISTINCT
ZTFICO_PRDORDER~PROJN,
ZTFICO_PRDORDER~GJAHR,
ZTFICO_PRDORDER~PERIO,
ZTFICO_PRDORDER~PSMNG,
ZTFICO_PRDORDER~ZXXX1,
ZTFICO_PRDORDER~LJRKSJ,
ZTFICO_PRDORDER~ZXXX2,
ZTFICO_PRDORDER~DQRKSJ,
ZTFICO_PRDORDER~ZXXX3,
ZTFICO_PRDORDER~MEINS,
ZTFICO_PRDORDER~RWAER,
ZTFICO_PRDORDER~ZZT,
ZTFICO_PRDORDER~ZCPJSFS,
ZTFICO_PRDORDER~PRCTR
FROM ZTFICO_PRDORDER INNER JOIN @GT_ALV AS A1 ON A1~PROJN = ZTFICO_PRDORDER~PROJN
WHERE ZTFICO_PRDORDER~GJAHR <= @LV_LASTDAY+0(4)
AND ZTFICO_PRDORDER~PERIO <= @LV_LASTDAY+4(2)
INTO CORRESPONDING FIELDS OF TABLE @LT_ZTFICO_PRDORDER.
SORT LT_ZTFICO_PRDORDER BY PROJN GJAHR PERIO DESCENDING.
DELETE ADJACENT DUPLICATES FROM LT_ZTFICO_PRDORDER COMPARING PROJN.
LOOP AT GT_ALV INTO GS_ALV.
READ TABLE LT_ZTFICO_PRDORDER INTO LS_ZTFICO_PRDORDER WITH KEY PROJN = GS_ALV-PROJN.
IF SY-SUBRC = 0.
GS_ALV-ZXXX1 = GS_ALV-ZXXX1 + LS_ZTFICO_PRDORDER-ZXXX1.
GS_ALV-LJRKSJ = GS_ALV-LJRKSJ + LS_ZTFICO_PRDORDER-LJRKSJ.
GS_ALV-ZXXX2 = GS_ALV-ZXXX2 + LS_ZTFICO_PRDORDER-ZXXX2.
ENDIF.
MODIFY GT_ALV FROM GS_ALV.
CLEAR:LS_ZTFICO_PRDORDER.
ENDLOOP.
小结
闲人在上几篇文章有提到过关于项目财务报表设计的一些技巧,其实这些技巧并不新鲜,恰恰很多项目遇到的问题老方法,有点成熟有效,缺点需要花费额外的精力写逻辑和设计历史数据表。
这期文章主要想表达,作为一个顾问,对于开发功能的验证,必须建立在实际业务数据的考验,集成测试明显是不够的,特别是财务报表。