Sunday, December 28, 2008

ABAP Proper Case Regex

I need to pull full user names from ADRP for an audit report, but the case is all over the place. This piece of code converts a character string to proper case (first letter uppercase). I wanted to handle Mc and Mac too, but ABAP implements the Posix regex syntax, which has no support for back referencing. If anyone knows a regular expression to catch the first character after Mac then please post a response.

report zzzzzzzz.

data:
li_ofs type i,
lv_name(30).

lv_name = 'PETER CHAPMAN'.

write: / lv_name.

translate lv_name to lower case.
while sy-subrc = 0.
translate lv_name+li_ofs(1) to upper case.
find regex '\b[a-z]' in lv_name match offset li_ofs.
endwhile.

write: / lv_name.

Tuesday, December 16, 2008

Efficiency versus Effort - The Eff Debate

A lot of inefficient code is developed from ignorance and laziness. This is so good for hardware vendors that they have established a school of thought that says it's easier to upgrade than to make the code faster. If users really cared about performance, we wouldn't have the explosion of internet applications, or the idea that Office running on the internet ala Googledocs is an improvement.

In spite of all this evidence, I still have a deep abhorrance of needlessly wasted processor cycles. In the world of custom SAP development, most sites can easily double the performance of their custom code by simply having an expert tune it. Even (especially?) SAP developed code can appear to prefer obfuscation over efficiency. It is a tough place for people who see coding as a minimilst artform, constantly striving for irreducible perfection.

There are times, though, when elegance trumps efficiency. In the code below, I have preferred unnecessary calls to LAST_DAY_IN_PERIOD_GET to preserve the integrity of the case statement as the sole logic to determine intervals. Do you agree? It would also be possible to put the periods into a table (array for any non-SAP people reading this, shocked that COBOL is making a comeback - we call it ABAP). But for such a small number of intervals it is more readable like this.


*&---------------------------------------------------------------------*
*& Form SET_AGE
*&---------------------------------------------------------------------*
form set_age
using p_end type d
changing p_agerq type ysd_agerq.

constants:
c_periv type tkel-periv value 'G1'.

statics:
ld_age0003 type d,
ld_age0406 type d,
ld_age0709 type d,
ld_age1012 type d.

data:
lv_enddate type d,
lv_horizon type i,
lv_period type jahrper.

*-- set up static dates
if ld_age0003 is initial.

lv_period = p_per.

do 12 times.

lv_horizon = sy-index.

call function 'RKE_GET_NEXT_PERIOD'
exporting
perio = lv_period
periv = c_periv
importing
nextperio = lv_period
exceptions
i_error = 1
i_perflag_invalid = 2
i_periv_notfound = 3
others = 4.
if sy-subrc <> 0.
message id sy-msgid type sy-msgty number sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
endif.

call function 'LAST_DAY_IN_PERIOD_GET'
exporting
i_gjahr = lv_period+0(4)
i_periv = c_periv
i_poper = lv_period+4(3)
importing
e_date = lv_enddate
exceptions
input_false = 1
t009_notfound = 2
t009b_notfound = 3
others = 4.

if sy-subrc <> 0.
message id sy-msgid type sy-msgty number sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
endif.

case lv_horizon.
when 3.
ld_age0003 = lv_enddate.
when 6.
ld_age0406 = lv_enddate.
when 9.
ld_age0709 = lv_enddate.
when 12.
ld_age1012 = lv_enddate.
endcase.
enddo.
endif.

*-- Allocate this date to a period range
if p_end le ld_age0003.
p_agerq = '00-03'.
elseif p_end le ld_age0406.
p_agerq = '04-06'.
elseif p_end le ld_age0709.
p_agerq = '07-09'.
elseif p_end le ld_age1012.
p_agerq = '10-12'.
else.
p_agerq = '12+'.
endif.

endform. " SET_AGE

Monday, December 1, 2008

ALV grid control in 3 lines

Here is some code inspired by Dany Charbonneau (link). It creates an ALV grid control with just 3 lines of ABAP. You should not be using REUSE_ALV_GRID_DISPLAY or REUSE_ALV_GRID_DISPLAY_LVC function modules any more - these are superseded by programming the objects directly. As you move over to ABAP Web Dynpro you will need those object skills anyway...

I like the elegance of well designed and documented classes. But I hate abstraction for its own sake - simple structured programs and reports are quicker to develop and easier to maintain. If you are not expecting to reuse the class then there is a strong argument for not writing it in the first place.

Fortunately CL_GUI_ALV_GRID is one of the well designed and documented ones. Here is the program.



report zzzzzzz1.

data:
lo_alv type ref to cl_gui_alv_grid,
lt_tab type table of t001.

selection-screen begin of screen 1100.
selection-screen end of screen 1100.

select * from t001 into table lt_tab.

*-- Line 1 - instantiate the alv object in the required screen
create object lo_alv
exporting
i_parent = cl_gui_container=>screen0.

*-- Line 2 - specify the data structure and content
call method lo_alv->set_table_for_first_display
exporting
i_structure_name = 'T001'
changing
it_outtab = lt_tab.

*-- Line 3 - display the screen
call selection-screen 1100.

Thursday, November 27, 2008

CL_GUI_CHART_ENGINE

SAP have released their CL_GUI_CHART_ENGINE tool in ECC 6.0. I had such a great reaction from my users just now that I thought I had better share it with you. It was fiddly to get right, but once working the results are fast and high impact.

The requirement is for a high speed data entry screen for the sales call team - no web dynpro please - everything keyboard based and fast. So it is a traditional screen dynpro with the chart for sizzle.


Implementing the chart control is simple - create a cutom control and instantiate the chart control in it during initialisation.

create object lo_container exporting container_name = 'CC_CHART'.
create object gsdynp-chart exporting parent = lo_container.

Now load a bunch of XML to format the chart. Download SAP's Chart Designer tool to generate the XML you require (here). Save it in the MIME repository and extract it using cl_wb_mime_repository=>load_mime and load it into the chart using the chart engine's set_customizing method.

Here is the code I used to extract the XML chart formatting from the MIME repository.


form get_chart_xml changing p_xml type xstring.

data:
lv_xstr type xstring,
lt_xmlr type sdokcntbins,
ls_xmlr type sdokcntbin,
lv_io type skwf_io,
lo_conv type ref to cl_abap_conv_obj.

*-- Load the xml template
* The chart XML is created using SAP's Chart Designer Tool
* The XML definition is stored in the MIME repository
* as YSD_CREDIT_CHART. For performance, the logical address
* is coded here directly.

lv_io-objtype = 'L'.
lv_io-class = 'M_APP_L'.
lv_io-objid = '492EEFF3E0C7014DE10080000A00D597'.
call method cl_wb_mime_repository=>load_mime
exporting
io = lv_io
importing
bin_data = lt_xmlr
changing
language = sy-langu
exceptions
no_io = 1
illegal_io_type = 2
not_found = 3
error_occured = 4
others = 5.
check sy-subrc = 0.

*-- Convert raw to xstring
create object lo_conv.
loop at lt_xmlr into ls_xmlr.
call method lo_conv->convert
exporting
inbuff = ls_xmlr
outbufflg = 25000
importing
outbuff = lv_xstr.
concatenate p_xml lv_xstr into p_xml in byte mode.
endloop.
free lo_conv.

endform. " GET_CHART_XML




And here is the code I used to create the chart:





*&---------------------------------------------------------------------*
*& Form SET_CHART
*&---------------------------------------------------------------------*
form set_chart using p_visible.

statics:
lo_container type ref to cl_gui_custom_container.

data:
lv_xml type xstring,
lv_txt(6),
xml type string.

*-- Initialise chart controls
if lo_container is not bound.
create object lo_container
exporting
container_name = 'CC_CHART'.
endif.

if gsdynp-chart is not bound.
create object gsdynp-chart
exporting
parent = lo_container.
endif.

*-- Set visibility
call method lo_container->set_visible
exporting
visible = p_visible.

if p_visible is initial.
exit.
endif.

*-- Format the chart
perform get_chart_xml changing lv_xml.
gsdynp-chart->set_customizing( xdata = lv_xml ).

*-- Create chart data
if gsdynp-klprz < lv_txt =" '0'."> 100.
lv_txt = '100'.
else.
write gsdynp-klprz to lv_txt.
endif.
xml = ''.
concatenate xml '' into xml.
concatenate xml '' into xml.
concatenate xml lv_txt into xml.
concatenate xml '
' into xml.
concatenate xml '
' into xml.
gsdynp-chart->set_data( data = xml ).

endform. " SET_CHART

Monday, February 4, 2008

Counterintuitive Coding

BAPI_ALM_ORDEROPER_GET_LIST is provided by SAP to find a list of operations for a service order. It is the equivalent of transaction IW33 where you can see the list of the order operations.

The question is, how do you specify the service order that the operations are attached to?

There is a helpful IT_RANGES structure which you can populate with field / value pairs to simulate the entry of selection options. So far so good. The problem comes in determining the field names to use.

Unable to find any documentation on the field names, and having failed to get the thing to work with SAP data dictionary field names, or with the non-German BAPI structure field names, I ended up in the debugger tracking down the ME206 error message - selection parameter not defined.

Guess what intuitive field names have been chosen for the ranges table?

OPTIONS_FOR_WORK_CNTR

OPTIONS_FOR_PLANT

OPTIONS_FOR_ORDERID

OPTIONS_FOR_DOC_TYPE

OPTIONS_FOR_ACTIVITY

OPTIONS_FOR_REFDATE

OPTIONS_FOR_DESCRIPTION

OPTIONS_FOR_FUNCLOC

OPTIONS_FOR_EQUIPMENT

OPTIONS_FOR_MATERIAL

OPTIONS_FOR_SERIALNO

OPTIONS_FOR_SORTFIELD


So if you are using this function, and you think you should put ORDERID in the fieldname, try OPTIONS_FOR_ORDERID instead....