Wednesday, April 22, 2009

Check delivery status

To check the delivery status of an order the following code can be used:

*Get all the schedule details by doing
SELECT * INTO TABLE xvbap FROM vbap
  WHERE vbeln = '7500000028'
  AND posnr = '000010'.
SELECT * INTO TABLE xvbup FROM vbup
  WHERE vbeln = '7500000028'
  AND posnr = '000010'.
SELECT * INTO TABLE xvbfa FROM vbfa
  WHERE vbelv = '7500000028'
  AND posnv = '000010'
  AND vbtyp_n = 'J'.
SELECT * INTO TABLE da_vbep FROM vbep
  WHERE vbeln = '7500000028'
  AND posnr = '000010'.
CALL FUNCTION 'RV_SCHEDULE_CHECK_DELIVERIES'
  EXPORTING
    fbeleg = '7500000028'
    fposnr = '000010'
* FVERRECHNUNG = ' '
* FS073_ALT = ' '
* IF_NO_SORT = ' '
  TABLES
    fvbfa = xvbfa
    fvbup = xvbup
    fxvbep = da_vbep
* FVBLB =
    fvbap = xvbap
  EXCEPTIONS
    fehler_bei_lesen_fvbup = 1
    fehler_bei_lesen_fxvbep = 2
  OTHERS = 3.

Tuesday, April 14, 2009

Read the domain value ranges

Sometimes we need to display the description based on the key value of a field. And the description is not in a text table but defined as value range in the domain definition. For example the domain XUDATFM lists all the types of dates possible

image

Using the class CL_ABAP_ELEMDESCR, one can retrieve these values in an internal table of type DDFIXVALUES. Here is an example:

DATA o_rtti_attr TYPE REF TO cl_abap_elemdescr.
DATA t_fixed_values TYPE ddfixvalues.
TRY.
    rtti_attr ?= cl_abap_typedescr=>describe_by_name( 'XUDATFM' ).
  CATCH cx_root.                                            "#EC *
ENDTRY.
*       get the fixed values
CALL METHOD rtti_attr->get_ddic_fixed_values
  RECEIVING
    p_fixed_values = fixed_values
  EXCEPTIONS
    no_ddic_type   = 1
    OTHERS         = 2.

Thursday, April 09, 2009

Dynamic where conditions in select

I had a problem in work where I would be selecting from tables that are decided on run time and also the where conditions on these tables are to be decided on run time. I found a lot of examples where one could create a dynamic internal table using the RTTS for INTO clause of SELECT. This was only half the solution to my problem. i still have to dynamically create as many where conditions as possible. The syntax of a dynamic select is:

SELECT (columns) INTO CORRESPONDING FIELDS OF TABLE (Internal Table Name)
  FROM (Table Name)
  WHERE (Where String containing conditions)
We have four components in the above SQL query that can be given at run time:
  1. Columns. We don’t worry about this as the clause ‘INTO CORRESPONDING FIELDS’ will only pick the fields that are common in the internal table and the database table.
  2. Internal Table: This is achieved by following the example here.
  3. Table name: The calling service knows what table to select from.
  4. Where string containing conditions: This is the problem that is to be solved!
In SELECT conditions there is a concept of ranges. You can create an internal table of type range that will contain the value to be searched and the operators for searching. I will use this for the solution here.

What I intend to do is let the calling program send me any number of ranges and its respective column name. So there will be an internal table (RANGES_TABLE) of the following structure:
BEGIN OF ranges_tables,
  tablename TYPE tabname16,
  fieldname TYPE fieldname,
  range TYPE REF TO data,
END OF ranges_table.
Taking the example of table T001:

image

Let’s populate RANGES_TABLE with two criteria, ORT01 and LAND1. We want to get table entries where ORT01 = ‘Walldorf’ and LAND1 = ‘DE’.
* 1
* Create range for ORT01
s_ort01-sign = 'I'.
s_ort01-option = 'EQ'.
s_ort01-low = 'Walldorf'.
APPEND s_ort01 TO r_ort01.
* Add entry in the Ranges table
s_ranges_table-tablename = 'T001'.
s_ranges_table-fieldname = 'ORT01'.
CREATE DATA s_ranges_table-range LIKE r_ort01.
ASSIGN s_ranges_table-range->* TO <range>.
<range> = r_ort01.
APPEND s_ranges_table TO t_range_table.
* 2
* Create range for LAND1
s_land1-sign = 'I'.
s_land1-option = 'EQ'.
s_land1-low = 'DE'.
APPEND s_land1 TO r_land1.
* Add entry in the Ranges table
s_ranges_table-tablename = 'T001'.
s_ranges_table-fieldname = 'LAND1'.
CREATE DATA s_ranges_table-range LIKE r_land1.
ASSIGN s_ranges_table-range->* TO <range>.
<range> = r_land1.
APPEND s_ranges_table TO t_range_table.
RANGES_TABLE now has a list of columns that we need to select from with the conditions respectively. But the user can send one column or two columns or (n) columns. This is will be solved by dynamically creating a structure that will have fields referring to different ranges. The structure will look like this:
BEGIN OF select_struc
  field1 TYPE range1
  .
  .
  fieldn TYPE rangen
END OF select_struc
Having creating a structure we create dynamic where conditions. So now the select query will be as follows:
SELECT * INTO TABLE <l_rows>
  FROM (s_ranges_table-tablename)
  WHERE field1 IN select_struc-field1
  .
  .
  AND   fieldn IN select_struc-fieldn
Here is the full list of the method that does all this:

image

METHOD select_from_ranges.
  DATA:
  l_index TYPE i,
  l_t_dyn_field TYPE abap_component_tab,
  l_s_dyn_field LIKE LINE OF l_t_dyn_field,
  l_str_range_name TYPE string,
  l_str_sel_option TYPE string,
  l_o_structdescr TYPE REF TO cl_abap_structdescr,
  l_d_range_str TYPE REF TO data,
  l_s_table_range LIKE LINE OF i_t_range.
  FIELD-SYMBOLS:
    <l_rows> TYPE ANY TABLE,
    <l_dyn_range_str> TYPE ANY,
    <l_range_tbl> TYPE table,
    <l_range_tbl2> TYPE table.
* For all the where conditions sent
* create fields in the select strucuture
  LOOP AT i_t_range INTO l_s_table_range.
    _index = sy-tabix.
    ASSIGN l_s_table_range-range->* TO <l_range_tbl>.
* Create dynamic field
    CLEAR:
      l_s_dyn_field.
l_s_dyn_field-name = l_s_table_range-fieldname.
    l_s_dyn_field-type ?= cl_abap_datadescr=>describe_by_data( <l_range_tbl> ).
    APPEND l_s_dyn_field TO l_t_dyn_field.
    CONCATENATE '<l_dyn_range_str>-' l_s_table_range-fieldname INTO l_str_range_name.
* More than one where requires in AND
    IF l_index > 1.
      CONCATENATE l_str_sel_option 'AND'
       INTO l_str_sel_option SEPARATED BY space.
    ENDIF.
    CONCATENATE l_str_sel_option l_s_table_range-fieldname 'in' l_str_range_name
      INTO l_str_sel_option SEPARATED BY space.
  ENDLOOP.
* Create dynamic structure with fields pointing to different range
  l_o_structdescr = cl_abap_structdescr=>create( p_components = l_t_dyn_field ).
  CREATE DATA l_d_range_str TYPE HANDLE l_o_structdescr.
  ASSIGN l_d_range_str->* TO <l_dyn_range_str>.
* For all the fields in the structure point them to the range tables
  WHILE sy-subrc IS INITIAL.
    ASSIGN COMPONENT sy-index OF STRUCTURE <l_dyn_range_str> TO <l_range_tbl2>.
    IF sy-subrc IS NOT INITIAL.
      EXIT.
    ENDIF.
    READ TABLE i_t_range INTO l_s_table_range INDEX sy-index.
    ASSIGN l_s_table_range-range->* TO <l_range_tbl>.
    <l_range_tbl2> = <l_range_tbl>.
  ENDWHILE.
  ASSIGN c_rows->* TO <l_rows>.
* Select from the table
  SELECT * INTO TABLE <l_rows>
    FROM (l_s_table_range-tablename)
    WHERE (l_str_sel_option).
ENDMETHOD.

Friday, April 03, 2009

Date formatter class

One useful class SAP has provided recently is the date formatter class to manage different type of dates that one encounters in the wild. So converting from external to internal formats and vice-versa is better now.

The class in question is CL_ABAP_DATFM. The following date formats are supported at the moment:

1    DD.MM.YYYY
2    MM/DD/YYYY
3    MM-DD-YYYY
4    YYYY.MM.DD
5    YYYY/MM/DD
6    YYYY-MM-DD
7    GYY.MM.DD
8    GYY/MM/DD
9    GYY-MM-DD
A    YYYY/MM/DD
B    YYYY/MM/DD
C    YYYY/MM/DD

One example in which I am using it is converting from say format of DD.MM.YYYY to SAP’s internal format of YYYYMMDD.

TRY.
   CALL METHOD cl_abap_datfm=>conv_date_ext_to_int
     EXPORTING
       im_datext   = '01.01.2009'
       im_datfmdes = '1' " DD.MM.YYYY
     IMPORTING
       ex_datint   = sapdate.
     CATCH cx_abap_datfm_no_date cx_abap_datfm_invalid_date
           cx_abap_datfm_format_unknown cx_abap_datfm_ambiguous .
* Invalide date format
ENDTRY.