Quantcast
Channel: SCN : Blog List - ABAP Development
Viewing all 943 articles
Browse latest View live

My CDS view self study tutorial - Part 4 how does annotation @OData.publish work

$
0
0

Part1 - how to test odata service generated by CDS view

Part2 - what objects are automatically generate after you activate one CDS view

Part3 - how is view source in Eclipse converted to ABAP view in the backend

Part4 - this blog

 

In part1 of this tutorial, the old way to create OData service on top of CDS view is introduced.

In SAP Help Generate Service Artifacts From a CDS View a new annotation is described:


@OData.publish: true

 

Just add this annotation to your CDS view and the odata service is automatically created, no need for you to go to code SEGW any more.

clipboard1.png

Once activated, the odata service with naming convention "<your CDS view name>_CDS" is available for registration in tcode /IWFND/MAINT_SERVICE:

clipboard2.png

Metadata retrieval test ok:

clipboard3.png

So the question here is: How does this annotation work? How can we research the service generation process through debugging?

 

Here below is how I figure it out.

 

First check what objects have been generated: three additional artifacts are highlighted below.

 

  • IWMO: SAP Gateway Business Suite Enablement - Model
  • IWSV: SAP Gateway Business Suite Enablement - Service
  • CLAS: provider class ZCL_ZJERRYTEST20160311 is generated

clipboard4.png

If I remove the annotation, or change the annotation to @OData.publish: false, and the two are gone:

clipboard5.png

So it means the annotation @OData.publish: true will trigger table entry insertion for type IWMO, IWSV and CLAS during view activation. Then again I switch on ST05 trace and easily find the ABAP code where the table insertion is done.

clipboard6.png

Set breakpoint on the code and observe the callstack in the runtime.

clipboard7.png

The highlighted callstack frames are essential for odata service generation.

clipboard8.png

Let's have a deep look at stack frame 21:

CL_WB_DDLS_SECOBJ_HNDLR_SINGLE->IF_DDIC_WB_DDLS_SECOBJ_HANDLER~ON_ACTIVATION

 

It will first identify which objects must be created based on delta state.

clipboard9.png

For example, if I add @OData.publish: true to an existing CDS view and activate it, the corresponding entry will have flag N ( New ) while other existing annotation has type "U" ( Unchanged ).

clipboard10.png

Inside this method, if annotation ODATA.PUBLISH is found,

clipboard11.png

and the annotation value is true, then it is considered that the odata service must be created.

clipboard14.pngclipboard12.png

clipboard13.png

The odata service creation is implemented in CL_SADL_GTK_ODATA_SERVICE_GEN~CREATE_VIA_EXPOSURE below.

clipboard14.png

Complete callstack:

clipboard15.png


My CDS view self study tutorial - Part 5 how to create CDS view which supports navigation in OData service

$
0
0


 

So far we have a working CDS view ready for us to create a UI5 application on top of it via Smart Template in WebIDE within just a couple of minutes. Once done, the UI5 application will display the data from our CDS view like below. For step by step how to achieve this, please refer to this blog: Step by Step to create CDS view through SmartTemplate + WebIDE .


clipboard1.png

How is navigation implemented among CDS views

 

In this part, let's create CDS view which supports node navigation in OData service. The previous CDS view we created has a flat structure which only have a root node. Now let's create a series of CDS views:

 

1. A CDS view which contains two fields: spfli.connid and spfli.carrid. This view acts as the root node of the corresponding OData service model from semantic point of view. This view can support navigation from itself to the defined children node.

 

2. A CDS view which acts as the navigation target from previously defined "root" view. Besides the two fields from sflight.connid and sflight.carrid which correspond to the root view, it has additional new field sflight.fldate.

 

OData navigation means suppose currently I am in the context of spfli.connid = 0001 and spfli.carrid ( data record with yellow ), and through navigation I can get all its dependent data in red color. We will see how this navigation would be performed later.

 

clipboard1.png

3. A CDS view which exposes the two fields connid and carrid from root view and the associated data  from child view.

This view is called "consumption" view and used to published as OData service.

 

Source code of view #1:

 

@AbapCatalog.sqlViewName: 'zspfliroot'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'root view'
define view Zspfli_Root as  select from spfli
association [0..*] to Zsflight_Child as _Item on $projection.carrid = _Item.carrid                                       and $projection.connid = _Item.connid
{  key spfli.connid,  key spfli.carrid,  @ObjectModel.association.type: #TO_COMPOSITION_CHILD  _Item
}

Source code of view #2:

 

@AbapCatalog.sqlViewName: 'zsflightchild'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'child_view'
define view Zsflight_Child as select from sflight
association [1..1] to zspfli_root as _root
on $projection.connid = _root.connid
and $projection.carrid = _root.carrid
{   key sflight.carrid,   key sflight.connid,   key sflight.fldate,   @ObjectModel.association.type: [#TO_COMPOSITION_ROOT, #TO_COMPOSITION_PARENT]   _root
}

Source code of view #3:

 

@AbapCatalog.sqlViewName: 'zflight_c'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'flight consumption view'
@OData.publish: true
@ObjectModel: {   type: #CONSUMPTION,   compositionRoot,   createEnabled,   deleteEnabled,   updateEnabled
}
define view Zflight_Com as select from Zspfli_Root {  key Zspfli_Root.carrid,  key Zspfli_Root.connid,  @ObjectModel.association.type: [#TO_COMPOSITION_CHILD]  Zspfli_Root._Item
}

Activate all of these three CDS views. Since the third consumption view has annotation @OData.publish: true, once activated there will be an OData service automatically generated:

clipboard3.png

How to test navigation

 

First check the response from OData metadata request via url /sap/opu/odata/sap/ZFLIGHT_COM_CDS/$metadata in gateway client.

You should find two AssociationSets generated based on corresponding annotation in CDS views.

clipboard4.png

The entityset Zflight_Com has type Zflight_ComType, which has the navigation Property "to_Item". Now we can test the navigation.

clipboard5.png

First we get the root node's content via url: /sap/opu/odata/sap/ZFLIGHT_COM_CDS/Zflight_Com(connid='0400',carrid='LH') .

clipboard6.png

And in the response, we are told that the correct url for navigation from current node to its child node is just to append the navigation property defined in metadata, toItem, to the end of url, that is, /sap/opu/odata/sap/ZFLIGHT_COM_CDS/Zflight_Com(connid='0400',carrid='LH')/to_Item .

clipboard7.png

How the navigation is implemented in ABAP side

 

Set the breakpoint in the method below and re-trigger the navigation operation.

Check the generated SQL statement in variable statement in line 27.

clipboard8.png

SELECT "Zsflight_Child"."CARRID" AS "CARRID", "Zsflight_Child"."CONNID" AS "CONNID", "Zsflight_Child"."FLDATE" AS "FLDATE" FROM "ZSFLIGHTCHILD" AS "Zsflight_Child"
WHERE "Zsflight_Child"."CARRID" = ? AND "Zsflight_Child"."CONNID" = ? AND "Zsflight_Child"."MANDT" = '001' WITH PARAMETERS( 'LOCALE' = 'CASE_INSENSITIVE' )

 

The value for two placeholders ( ? ) are stored in me->parameters->param_tab:

clipboard9.png


And check response in et_flag_data:

clipboard10.png

clipboard11.png

Open Source ABAP tools - abaplint and abapCov

$
0
0

abaplint

Continuous Integration is a hot topic, but it has always been difficult to do anything for ABAP open source projects, as it would require a dedicated SAP system to run the tests on.

 

So I've started writing abaplint which is a linter written in TypeScript for checking ABAP code. abaplint compiles to javascript, which can be executed on various platforms. It will only do the "Code Inspector" part of checking the code, unit tests will not be run and is not in scope for this project.

 

abaplint will work for objects that have been serialized using abapGit, or files containing the raw abap code.

 

Currently different 17 rules have been implemented, see https://github.com/larshp/abaplint/wiki. The checks can be enabled or disabled plus configured via a abaplint.json file. The project is open source and is work in progress, so expect some problems, but pull requests and suggestions are also welcome.

 

abaplint can be setup to run on Travis CI(see example at https://travis-ci.org/larshp/abapGit), each time a push is done to the ABAP git repository, Travis will download the latest abaplint via npm and run the linter on the committed code.

 

It also works in the Atom editor,

atom.png

 

And in Eclipse(update site to be created),

eclipse.png

 

Plus on the web

web.png

 

abapCov

abapCov takes a step towards visualizing the coverage of the unit tests run on the ABAP server.

 

Run abapCov on the ABAP server, it will run the unit tests with coverage enabled, and the  coverage result is then uploaded to https://codecov.io where anyone can check it out.

 

See example at https://codecov.io/github/larshp/abapOpenChecks

 

 

 

https://github.com/larshp/abaplint or via npm

https://github.com/larshp/abapCov

How to do convenient multicase unit tests with zmockup_loader

$
0
0

Some time ago I published a post about a mockup loading tool we use for unit testing in our projects http://scn.sap.com/community/abap/blog/2015/11/12/unit-testing-mockup-loader-for-abap. Since then me and my team were using it intensively in our developments and also introduced a couple of new features. This post describes our apprach to unit test preparation.

 

Just briefly for those who didn't read the previous post: the idea of the mockup loader is that you prepare your test data in excel, copy it to a bunch of text files (manually or with excel2txt script that is also availble in repo), zip them, upload as binary object via SMW0, and use mockup loader to extract and deploy this data dynamically for your unit test. For more details see the previous post (link above).

 

Use case

Let's suppose we have a method which is designed to identify quality of some product. We input a table of quality inspection parameters and expect some quality class assessment, in the example "A" or "B". Certain parameters are compulsory and must not be skipped during quality inspection: if they are missing the method raises an exception.

 

The goal: write a unit test code once and then easily update it with new test cases prepared in Excel.

 

Data preparation

Let's say we prepared a unit test table with 3 cases (docno = 10, 20 and 30). Note that QUANTITY parameter is missing in the document 30 - exception expected.

 

DOCNOPARAMVALUE
10WEIGHT12,89
10QUANTITY99
10MOISTURE12,6
20WEIGHT14,89
20QUANTITY103
20MOISTURE11,9
30WEIGHT14,89
30QUANTITY
30MOISTURE12,10

 

Another table is the index of test cases. Each refers to a DOCNO, describes expected result and describes the case in a human-readable form (as a kind of documentation and for failure messages). TYPE field describes if the case is positive or negative.

 

TESTIDTYPEDOCNOEXPECTMSG
1+10ACase with quality A
2+20BCase with quality B
3-30Case with a missing Param

 

 

Unit test code

 

1) Define the test case index structure (in the test class)

 

class lcl_test definition for testing inheriting from cl_aunit_assert

  duration short risk level harmless.

  public section.

    types:

        begin of ty_test_case,

          testid    type i,

          type      type char2,

          docno    type char10,

          expect    type char1,

          msg      type string,

        end of ty_test_case.

 

 

    data at_cases type table of ty_test_case.        " << index table

    data o_ml    type ref to zcl_mockup_loader.    " << mockup loader instance

    data o        type ref to zcl_class_under_test.  " << class under test

    ...

 

2) In setup() method acquire a mockup loader instance and load index table

 

  method setup.

    data lo_ex type ref to zcx_mockup_loader_error.

 

    create object o importing ...    " << Object under test initialization

 

    try.

      o_ml = zcl_mockup_loader=>get_instance( ).

 

      o_ml->load_data(

        exporting i_obj      = 'TEST1/index'    " << file path inside zipfile

        importing e_container = me->at_cases ).

 

    catch cx_static_check into lo_ex.

      fail( lo_ex->get_text( ) ).

    endtry.

  endmethod.

 

3) And here is main test cycle. BTW one of new features of ML introduced recently is data filtering - see the call of o_ml->load_data() - it passes the field name and value. If specified, the mockup loader filters the output to meed this condition. It is possible to define more complex filtering also (more details @github homepage).

 

  method test_assess_quality.

    data l_case  type ty_test_case.

    data lo_ex  type cx_root.

    data lt_insp type zcl_class_under_test=>ty_inspections.

    data l_act  type zcl_class_under_test=>ty_quality.   

 

    loop at at_cases into l_case.                      " << Loop through cases

      clear lo_ex.

 

      try.

        o_ml->load_data(

          exporting i_obj      = 'TEST1/inspections'     

                    i_where    = 'DOCNO = ' && l_case-testid

          importing e_container = lt_insp ). " ^^^ Filter only relevant lines

 

        l_act = o->assess_quality( lt_insp ).                " << Test call

 

      catch zcx_mockup_loader_error into lo_ex.

        fail( lo_ex->get_text( ) ). " Mockup load error handling, just in case

      catch zcx_root into lo_ex.

        " do nothing - this is the expected exception for negative tests

        " better use specific exeption classes of course

      endtry.

 

      if l_case-type = '+'. " Positive test

        " msg indicates the failed test id and it's description

        assert_initial( act = lo_ex

                        msg = |[{ l_case-testid }] { l_case-msg }| ).

        assert_equals( act = l_act

                       exp = l_case-expect

                       msg = |[{ l_case-testid }] { l_case-msg }| ).

      else. " '-'          " Negative test

        assert_not_initial( act = lo_ex

                            msg = |[{ l_case-testid }] { l_case-msg }| ).

        " Potentially more specific error check code should follow

      endif.

 

    endloop.

  endmethod.   

 

4) That's it !

 

Now we've got an easy extendable test infrustructure. New test cases for this test can be added without extra coding. Honestly speaking, this works better for integrated tests than for checking small methods. However, it is very useful when applicable - some methods in out developments have unit tests with 20+ cases and they are still updated from time to time with new ones (usually after yet another bug discovery ).

 

Hope you find it useful !

 

The ABAP mockup loader project lives @github, there you can find full reference of the methods and also some use cases in wiki.

 

All the best, Alexander Tsybulsky.

Example on how to modify BEGDA and/or ENDDA of any PA infotype using the decoupled framework

$
0
0

Introduction

 

I was surprised not to find any examples on how to modify the start and/or end dates of PA infotypes using the decoupled framework. Typically the start and end dates are part of the key identifying the record. Unless you know what you are doing, you will most likely get a CX_HRPA_INVALID_PARAMETER exception with the parameter PRIMARY_RECORD-PSKEY. Important here is to have the existing record, create a new one (with new values) and refer the old record when calling the MODIFY method.

 

Acknowledgements

 

All the code was copied and adapted from the standard function module HR_CONTROL_INFTY_OPERATION, the decoupled framework parts of it.

 

The Code

 

First the definition for method CHANGE_BEGDA_ENDDA

 

  class-methods CHANGE_BEGDA_ENDDA

    importing

      value(IV_TCLAS)type PSPAR-TCLAS default'A'

      value(IV_INFTY)type PRELP-INFTY

      value(IV_SUBTY)type P0001-SUBTY

      value(IV_OBJPS)type P0001-OBJPS

      value(IV_SPRPS)type P0001-SPRPS

      value(IV_SEQNR)type P0001-SEQNR default'000'

      value(IV_PERNR)type P0001-PERNR

      value(IV_BEGDA)type P0001-BEGDA

      value(IV_ENDDA)type P0001-ENDDA

      value(IV_BEGDA_NEW)type P0001-BEGDA

      value(IV_ENDDA_NEW)type P0001-ENDDA

      value(IV_TEST_MODE)type BOOLE_D default' '

    exporting

      value(ET_MESSAGES)type HRPAD_MESSAGE_TAB

      value(EV_OK)type BOOLE_D .

 

 

Then the implementation of method CHANGE_BEGDA_ENDDA

 

method change_begda_endda.

  data lr_message_list          typerefto cl_hrpa_message_list.

  data lr_masterdata_bl         typerefto if_hrpa_masterdata_bl.

  data container                typerefto if_hrpa_infty_container.

  data old_container            typerefto cl_hrpa_infotype_container.

  data new_container            typerefto if_hrpa_infty_container.

  data new_infotype_container   typerefto cl_hrpa_infotype_container.

  data infotype_ref             typereftodata.

  data lv_is_ok                 type boole_d.

  data lv_dummy                 type string.

  data ls_msg                   type symsg.

  data container_tab            type hrpad_infty_container_tab.

  data container_if             type hrpad_infty_container_ref.

  data t777d                    type t777d.

  data lv_has_error             type boole_d.

  data lv_count                 type i.

 

  field-symbols<pshdr>         type pshdr.

  field-symbols<pnnnn>         typeany.

  field-symbols<pskey>         type pskey.

  field-symbols<record>        typeany.

  field-symbols<pxxxx>         typeany.

 

  createobject lr_message_list.

 

  callmethod cl_hrpa_masterdata_factory=>get_business_logic

    importing

      business_logic = lr_masterdata_bl.

 

  check lr_masterdata_bl isbound.

 

  callmethod lr_masterdata_bl->read

    exporting

      tclas          = iv_tclas

      pernr          = iv_pernr

      infty          = iv_infty

      subty          = iv_subty

      objps          = iv_objps

      sprps          = iv_sprps

      begda          = iv_begda

      endda          = iv_endda

      seqnr          = iv_seqnr

      mode           = if_hrpa_masterdata_bl=>exact_matching_record

      no_auth_check  = abap_true

      message_handler = lr_message_list

    importing

      container_tab  = container_tab

      is_ok          = lv_is_ok.

 

  if lv_is_ok eq abap_true.

    describetable container_tab lines lv_count.

 

    if lv_count gt1.

      lv_is_ok = abap_false.

 

      message e016(pg)with'Multiple records found, limit to exactly one'into lv_dummy.

      move-corresponding sy to ls_msg.

 

      callmethod lr_message_list->if_hrpa_message_handler~add_message

        exporting

          message = ls_msg

          cause  = lr_message_list->if_hrpa_message_handler~infotype_generic.

    elseif lv_count eq0.

      lv_is_ok = abap_false.

 

      message e009(pg)with iv_infty into lv_dummy.

      move-corresponding sy to ls_msg.

 

      callmethod lr_message_list->if_hrpa_message_handler~add_message

        exporting

          message = ls_msg

          cause  = lr_message_list->if_hrpa_message_handler~infotype_generic.

    else.

      readtable container_tab into container index1.

    endif.

  endif.

 

  if lv_is_ok = abap_false.

    callmethod lr_message_list->get_abend_list

      importing

        messages = et_messages.

 

    if et_messages isinitial.

      callmethod lr_message_list->get_error_list

        importing

          messages = et_messages.

    endif.

 

    return.

  endif.

 

  old_container ?= container.

 

  try.

      callmethod old_container->if_hrpa_infty_container_data~primary_record_ref

        importing

          pnnnn_ref = infotype_ref.

    catch cx_hrpa_violated_assertion .

  endtry.

 

  assign infotype_ref->* to<pxxxx>.

 

  t777d = cl_hr_t777d=>read( infty = iv_infty ).

 

  createdata infotype_ref type(t777d-ppnnn).

 

  assign infotype_ref->* to<pnnnn> casting like<pxxxx>.

  <pnnnn> = <pxxxx>.

  assign<pnnnn>to<pshdr> casting.

  <pshdr>-infty = iv_infty.

 

  assign infotype_ref->* to<record> casting like<pxxxx>.

  assign component 'PSKEY'ofstructure<record>to<pskey>.

 

  callmethod lr_masterdata_bl->get_infty_container

    exporting

      tclas          = 'A'

      pskey          = <pskey>

      no_auth_check  = abap_true

      message_handler = lr_message_list

    importing

      container      = container_if.

 

  new_infotype_container ?= container_if.

 

  <pskey>-endda = iv_endda_new.

  <pskey>-begda = iv_begda_new.

 

  new_infotype_container ?= new_infotype_container->modify_key(<pskey>).

  new_infotype_container ?= new_infotype_container->modify_primary_record(<record>).

 

  new_container ?= new_infotype_container.

 

  if iv_test_mode eq abap_false.

    callmethod lr_masterdata_bl->modify

      exporting

        old_container  = old_container

        message_handler = lr_message_list

      importing

        is_ok          = lv_is_ok

      changing

        container      = new_container.

 

    if lr_message_list->has_error() = abap_true or

       lr_message_list->has_abend() = abap_true.

      lv_has_error = abap_true.

    else.

      lv_has_error = abap_false.

    endif.

 

    if lv_has_error = abap_true.

      callmethod lr_message_list->get_abend_list

        importing

          messages = et_messages.

 

      if et_messages[] isinitial.

        callmethod lr_message_list->get_error_list

          importing

            messages = et_messages.

      endif.

 

      callmethod lr_masterdata_bl->if_hrpa_buffer_control~initialize.

    else.

      callmethod lr_masterdata_bl->flush

        exporting

          no_commit = ' '.

    endif.

  endif.

 

endmethod.

 

To use it, you would simply call the method with all required parameters

 

  data lt_messages type hrpad_message_tab.

  data lv_ok type boole_d.

 

  callmethod change_begda_endda

    exporting

      iv_tclas    = 'A'

      iv_infty    = '0002'

      iv_subty    = ''

      iv_objps    = ''

      iv_sprps    = ''

      iv_seqnr    = '000'

      iv_pernr    = '12345678'

      iv_begda    = '20001201'

      iv_endda    = '99991231'

      iv_begda_new = '20101201'

      iv_endda_new = '99991231'

      iv_test_mode = ' '

    importing

      et_messages = lt_messages

      ev_ok       = lv_ok.

 

 

This would change the start date of infotype 0002 for employee 12345678 from 12/01/2000 to 12/01/2010.

 

Limitations

 

I'm sure there are many situations where the code will break. For example, I didn't bother with secondary records and the code handles only one record at a time. In addition the code should check that the decoupled framework can actually be used for the infotype in question. Nevertheless, I thought I'd share this as it may help folks out there.

A bug in bapi BAPI_OUTB_DELIVERY_CREATE_SLS (and its solution)?

$
0
0

Hi community,

 

Another blazing fast and short blog post of mine. It seems I have come across a bug in standard bapi BAPI_OUTB_DELIVERY_CREATE_SLS.

 

I was trying to use it to deliver completely a sales order, but I ran into an interesting issue.

 

This BAPI calls function module RV_DELIVERY_CREATE, but, alas, it calls it without a "selektionsdatum", which seems to be what the "due_date" in the BAPI is for.

 

I'm not sure about this, but the fact is, if I populate this field (selektionsdatum) with the value from due_date, it works (for me... in the system I work with...).

 

So what I did was implement an implicit enhancement at the beginning of the BAPI, and I store the value of due_date in the attribute of a global class.

 

Then I created another implicit enhancement implementation at the beginning of RV_DELIVERY_CREATE and get the due_date into selektionsdatum.

 

There ya go.

 

If this is helpful, great. If it's not, I'm sorry.

 

If I'm terribly mistaken about this, let me know.

 

Cheers,

Bruno

My CDS view self study tutorial - Part 6 consume table function in CDS view

$
0
0


Let's try to resolve one real issue now. What we want to achieve is: in CRM we need a CDS view which returns the service order guid together with its Sold-to Party information, "Title" ( Mr. ) and "Name" ( blGMOUTH ).

clipboard1.png

The title and Name information are stored on table BUT000, while Service order transactional information is maintained in table CRMD_PARTNER, which has a field PARTNER_NO ( CHAR32 ) linking to table BUT000's PARTNER_GUID ( RAW16 ).

clipboard2.png

clipboard3.png

It is not allowed to do join on these two fields since their data type are not equal. This question is asked via this SCN thread: ABAP CDS View: join tables on columns of different type .


As suggested in the Correction Answer, this issue could be resolved by using CDS Table Function. Here below are the detail steps.

clipboard5.png

1. Create a new table function

clipboard6.png

@ClientDependent: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
define table function ztf_BP_DETAIL  with parameters @Environment.systemField: #CLIENT                  clnt:abap.clnt  returns { client:s_mandt;            partner_guid:BU_PARTNER_GUID;            partset_guid:CRMT_OBJECT_GUID;            partner_no: CRMT_PARTNER_NO;            bp_guid: BU_PARTNER_GUID;            title:AD_TITLE;            name: BU_NAME1TX;          }  implemented by method    zcl_amdp_bp_detail=>crmd_partner_but000;

With keyword "with parameters", the client parameters is defined which works as the importing parameters for the ABAP class method zcl_amdp_bp_detail=>crmd_partner_but000. The keywords "returns" defines available fields which could be consumed by other CDS entities.

 

For further information about AMDP ( ABAP Managed Database Procedure ), please refer to this document Implement and consume your first ABAP Managed Database Procedure on HANAor this blog An example of AMDP( ABAP Managed Database Procedure ) in 740 .

 

2. Create a new AMDP implementation

Create a new ABAP class zcl_amdp_bp_detail by copying the following source code:

 

CLASS zcl_amdp_bp_detail DEFINITION  PUBLIC  FINAL  CREATE PUBLIC .  PUBLIC SECTION.  INTERFACES if_amdp_marker_hdb.  CLASS-METHODS crmd_partner_but000 FOR TABLE FUNCTION ztf_bp_Detail.  PROTECTED SECTION.  PRIVATE SECTION.
ENDCLASS.
CLASS zcl_amdp_bp_detail IMPLEMENTATION.
METHOD crmd_partner_but000        BY DATABASE FUNCTION FOR HDB        LANGUAGE SQLSCRIPT        OPTIONS READ-ONLY        USING crmd_partner but000.    RETURN SELECT sc.client as client,                  sc.partner_guid as partner_guid,                  sc.guid as partset_guid,                  sc.partner_no as partner_no,                  sp.partner_guid as bp_guid,                  sp.title as title,                  sp.name1_text as name                  FROM crmd_partner AS sc                    INNER JOIN but000 AS sp ON sc.client = sp.client AND                                              sc.partner_no = sp.partner_guid                    WHERE sc.client = :clnt AND                          sc.partner_fct = '00000001'                    ORDER BY sc.client;  ENDMETHOD.
ENDCLASS.

Here in line 30 the two columns of CRMD_PARTNER and BUT000 are joined. The importing parameter is used in SQLScript source code by adding a ":" before the variable name. The hard code "00000001" means the constant value for partner function "Sold-to Party".

clipboard8.png

3. Consume the created table function in CDS view

 

@AbapCatalog.sqlViewName: 'zcpartner'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'partner detail'
define view Z_c_partner as select from crmd_orderadm_h
inner join crmd_link as _link on crmd_orderadm_h.guid = _link.guid_hi and _link.objtype_hi = '05'  and _link.objtype_set = '07'
inner join ztf_bp_detail( clnt: '001') as _bp on _link.guid_set = _bp.partset_guid
{  key crmd_orderadm_h.guid,  --_link.objtype_hi as header_type,  --_link.objtype_set as item_type,  _bp.bp_guid,  _bp.partner_no,  _bp.name,  case _bp.title    when '0001' then 'Ms.'    when '0002' then 'Mr.'    when '0003' then 'Company'    when '0004' then 'Mr and Mrs'    else 'Unknown'  end as title
}

Please note that the created table function in step1 could be directly consumed just as a normal CDS view, see example in line 8.

Since the table function declares client as parameter, so when consumed, I put the current client id 001 into it. The fields defined as the returning parameters in table function could be used in consuming view.

clipboard9.png

4. Test the whole solution


Click F8 on the view z_c_partner, check whether data previews as expected.

clipboard10.png

or you can also check against single data record, by clicking "SQL Console", maintain the SQL and click Run:

clipboard11.png

The same test could also easily be done on ABAP side:

clipboard12.png

clipboard13.png

ZINCLUDE_ASSEMBLER - develop conveniently, publish as a single file

$
0
0

Hi Community,

 

This post is dedicated to open source lovers.

 

In a couple of open source projects I've seen the following dilema:

 

  • Convenience for users: the code must be easy installable so there is a tendency to keep the whole code (program) as one piece.
  • contra Convenience for the developer: when the code grows it becomes to be difficult to support it as one piece.

 

Of course there are tools like SAPLINK and ZABAPGIT available, which solves this issue (not only this one of course ). However, they must be installed on a target system which puts additional restriction.

 

Recently, I've published at Github a tool that addresses this issue from, let's say, publisher perspective. The tool is called ZINCLUDE_ASSEMBLER and what it does is simply "includes" program's includes which belong to the same dev package into the one piece of code.

 

illustration_small.png

 

So it can be used to publish easy-to-install single-file code while still enjoying nice code structure in dev environment. The result can be saved to a file or to another program (kind of ZXXX_BUILD).

 

The project homepage at Github: https://github.com/sbcgua/abap_include_assembler

 

I hope it will come to use for someone

 

All the best, Alexander Tsybulsky


An ABAP tool to get ABAP source codes line number

$
0
0

You can use this tool ( an ABAP report ) to get the line number of your ABAP source code.

 

How to use this tool

 

Just specify the criteria based on which the source code will be scanned and line number will be calculated.

clipboard12.png


Execute and it will show you detail statistics about line number and the total count. Double click on the ALV list item and you can navigate to method source code.

clipboard13.png

How to get the source code of this tool

 

This tool consists of the following ABAP objects ( as also listed in above picture):

 

  • report ZTOOL_CODE_LINE_COUNT
  • include ZTOOL_DEV_OBJ_SELSCR1
  • class ZCL_TOOL_RS_SERVICE
  • class ZCL_TOOL_SRC_CODE_ANALYZE
  • class ZCL_TOOL_SRC_CODE_LOCATION
  • class ZCL_TOOL_SRC_CODE__ANALYSIS
  • interface ZIF_TOOL_SRC_CODE__ANALYSIS
  • table ZTOOL_METRICS_LO

 

You can get all their source code from this github repository. Feel free to change the source code to fulfill your own requirement.

 

Text Symbols:

clipboard1.png

Selection Texts:

clipboard2.png

clipboard3.png

Table structure

clipboard4.png

Getting Started with ABAP Programming Model for Fiori Apps

$
0
0

This blog provides a collection of information (presentations, blogs, videos, ...) about the ABAP programming model for Fiori Apps which is delivered in the ABAP stack starting from SAP NetWeaver 7.5 SP01.

 

Latest News : 

Currently no news available.

Scenario 1: Develop a Simple List Reporting App

In this introducing scenario, you have the opportunity - starting from an already existing data model - to develop a simple list - reporting scenario based on the standardized ABAP programming model for SAP Fiori. You will be guided step-by-step through the new development infrastructure, which includes technologies such as Core Data Services (CDS), SADL, and SAP Gateway.

Access Tutorial

Watch the video(coming soon)

 

 

Scenario 2: Developing a List Reporting App with Search and Analytical Capabilities

Starting from the elementary list reporting (see scenario 1), you may want to add some further list reporting functions. For example, if your table or list contains many rows, it becomes difficult for end users to find the information they need. To facilitate finding the desired information, you can provide selection fields (filters) to specify the range of information that the end user is looking for. In addition, you may want to specify the positioning of columns or prioritize, or even hide, specific fields in your table or list.

Access Tutorial

Watch the video(coming soon)

ABAP 740 – Is CONSTANT not a Static Attribute anymore?

$
0
0

Originally published at - ABAP 740 – Is CONSTANT not a Static Attribute anymore?


CLASS_CONSTRUCTOR would be called automatically whenever the class would be accessed – either by creation of an instance or accessing any component. But seems like it is changed with ABAP 740.


Preface

In article CLASS_CONSTRUCTOR and CONSTRUCTOR: Who comes before whom?, we discussed how both constructors are called at runtime. Before continue reading further, I suggest you to visit the memory lane and refresh your ideas on when CLASS_CONSTRUCTOR is called.

 

Thanks Ramakrishna Koliparthi for pointing out this odd behavior. I would not have tried this, based on assumption that it would have worked across all versions.

 

Example 1 in ABAP 740

The same example 1 from article CLASS_CONSTRUCTOR and CONSTRUCTOR: Who comes before whom? generates different output. Comparing both outputs next to each other:

 

ABAP_740_Class_constructor_Ex_1_compare.png

 

What ?


In the earlier version, when constant was accessed the first time, CLASS_CONSTRUCTOR from all the classes involved in the class hierarchy were accessed. But, now in ABAP 740, it doesn’t execute the CLASS_CONSTRUCTOR at all. It doesn’t execute the even the static constructor of that particular class, e.g. LCL_D.


From the keyword help: Constructor


The static constructor is called automatically exactly once per class and internal session before the class is first accessed. An access to the class is the creation of an instance of the class or the addressing of a static component using the class component selector.

But it didn’t trigger as expected!

 

Adding a “Real” Static Attribute

My thought was that there is something wrong with how CLASS_CONSTRUCTOR is being called. I didn’t trust the highlighted statement from the help. So, to see the behavior and to test what help says is true or not, I added one Static Attribute in the class LCL_D and access that.

 

 

 

* New Definition for LCL_D
* ==== LCL_D ===== *
*
CLASS lcl_dDEFINITION INHERITING FROM lcl_c.
 
PUBLIC SECTION.
   
CONSTANTS: c_dTYPE char1 VALUE 'D'.
   
CLASS-DATA: v_dTYPE char1.
   
CLASS-METHODS: class_constructor.
   
METHODS constructor.
ENDCLASS.                    "lcl_d DEFINITION
LOAD-OF-PROGRAM.
 
WRITE: / '... LOAD-OF-PROGRAM ...'.
*
START-OF-SELECTION.
 
SKIP 2.
 
WRITE: / '... START-OF-SELECTION ...'.
 
WRITE: / 'LCL_D=>V_D  ..... ', lcl_d=>v_d.

 

  ABAP_740_Class_constructor_Ex_4.png

 

Now, the output is same as how it was before ABAP 740.

 

Why?

Based on this, and it was working as expected in earlier versions other than ABAP 740, expected behavior was to trigger all the static constructor within the hierarchy, but it did not. So, the question on the subject line – Is Constant NOT a Static Attribute Anymore? Or SAP has changed the way constants are loaded in PXA in ABAP 740. May be all the constants are loaded already and thus no need to load them again, so no CLASS_CONSTRUCTOR call.

 

From help on CONSTANTS

Constants are stored in the PXA and are available to all programs.

What do you think? Let me know by your comment.

 

Also, I'm hopping that Horst Keller or someone from his league might be able to shed some lights on this.

Smartform Version Comparison

$
0
0

There has long been a requirement, where we need to compare 2 Smartform versions.

In SAP there is no feature for this, there are no past versions maintained and also certainly no remote comparison.

 

This makes comparison and version management of smartforms a tedious task.

 

I was faced with such a task too. There were multiple complex smartforms and due to multiple activities and phases of my project working simultaneously, it was very difficult to trace what went wrong and definitely made it a lot of effort to maintain and revert changes in case of any issues.

 

Thus began my journey down the lane on how to overcome this problem.

 

My initial research "Google" shed no fruitful results. All I got was it is not possible in SAP to compare the smartforms and we will need to download the XML files and save locally in order to do version management.

And in case we need to compare, we will need to upload and create a temporary version and then compare each window and element.

 

Well, to me it did not make a lot of sense. Since it would be way easier to create new in a lot of cases and yes, I believe that is what must have been done in most cases.

 

So I started with my own utility program for Smartform Version Comparison.

 

I noticed that always on Download and XML file is generated and this remains more or less of a similar structure.

There are tags which we can traverse and identify to compare the smartforms.

And also the entire data in the smartform is present in the XML.

 

So, why not automate this and voila now I have a working program to compare smartforms.

I have uploaded the source code of the same on Github at https://github.com/sajid-a/SmartformVersionCompare

 

This is currently a working version with a known bug that it can only compare smartforms upto a certain size. The reason is that currently we are not able to process the complete string provided by the XML in the file. Other than that this can be used to compare smartforms.

 

The process for comparison is as follows:

 

1. Download and Maintain the XMLs of the smartform before making any change. This will act as your repository of version management. Alternatively an SVN can be used for this purpose

 

files.PNG

 

2. Execute the program and provide the two XML files that you want to compare.

upload.PNG

3. On execution, the changes in the two versions will be displayed.

 

3.PNG

4.PNG

The program is currently in the Alpha Phase and hence any suggestions or improvements are welcome.

Also all are welcome to collaborate on the source code and help in the development of a comprehensive tool.

 

The features to be added in the current program are as follows:

1. Only Code Comparison

2. Remote System Comparison

3. Automatic Version Management and Comparison

4. Auto generation of XML by just entering the Smartform Name

5. Bug fixes

 

Hope this post proves useful to all who are in need for a way to compare smartforms.

Working with SABRIX when Japanese Entity is Involved

$
0
0

In this Technical Solution I am sharing my experience with SABRIX and doing business with japan Vendors or Customers.

 

Many questions might arise if you do not know about SABRIX. If SABRIX is known to you, then next question will be what could be the issue in doing business with Japan Vendors or Customers.

 

SABRIX is tax calculation third party software. Input is Tax Jurisdiction Code and Ship from and Ship to and other attributes.

 

Issue:

 

  1. Japan has a very specific requirement for Collective Invoicing. For that reason a special Business Functionality was activated. This functionality does not require TXJCD and SAP standard ignores this. For that reason, it was decided that Japan will be going with the Non-Sabrix option similar to Brazil.

 

   2. We have scenarios where other countries do business with Japan(NON-JAPAN to JAPAN). These countries use SABRIX for          tax calculation. As TXJCD is a required field for SABRIX, it gives and error when calculating Taxes.


   3. Affected areas are Billing and Purchasing.


Possible Business Scenario’s

Sales

  1. Non Japan entity shipping to customer in Japan (Non Japan to Japan).
  2. Non Japan entity shipping to customer in Japan from plant in Japan (JAPAN to  JAPAN, SO is owned NON JAPAN).
  3. Non Japan entity shipping to Non JAPAN customer from Japan plant (JAPAN to Non SP )

  Purchase 

  1. Purchase order with non Japanese Vendor and delivery address in Japan      (Non JAPAN to JAPAN)
  2. Purchase order with Japanese Vendor and Delivery address in Japan.(JAPAN to    JAPAN. PO is owned NON JAPAN)
  3. Purchase order with Japanese Vendor and delivery address not in Japan.(JAPAN to  Non JAPAN)

   

Solution in different area’s:


Order

User Exit: USEREXIT_PRICING_PREPARE_TKOMK (Program: MV45AFZZ)


Logic:

  1. Check for TKOMK-BUKRS. If not Japan Company Code, then
  2. Check customer from XVBAK-KUNNR, if customer belongs to Japan.
  3. If both conditions suffice, Update TKOMK-TXJCD ; update ship to and ship from.



  Invoice 


Header Level:

User Exit: USEREXIT_PRICING_PREPARE_TKOMK (Program: RV60AFZZ)

Logic:

  1. Check for TKOMK-BUKRS. If not Japan Company Code,
  2. Check customer from TKOMK-KUNWE, if customer belongs to Japan.
  3. If both conditions suffice, Update TKOMK-TXJCD; update ship to and ship from.



  Item Level

User Exit: USEREXIT_PRICING_PREPARE_TKOMK (Program: RV60AFZZ)

Logic:

  1. Check for  VBRK-BUKRS. If not Japan Company Code, then
  2. Check customer from VBRK-KUNAG, if customer belongs to Japan.
  3. If both conditions suffice, Update XVBRP-TXJCD ; update ship to and ship from.



Purchase

BADI: ME_TAX_FROM_ADDRESS

Logic:

  1. Check if TXJCD is populated or not. If not then below is the logic
  2. Populate country key as the TXJCD from either Vendor, Customer, Address number and Plant.
  3. If Vendor or customer is present then populate their country key.
  4. If Address number is present then populate the country key from the address.
  5. If Plant is present then populate the Plants country.
  6. Population of Country gets prioritized in the following order

  Vendor à Customer àManual Address Number in PO à Plant 

  1. As data is filled in the PO, during creation of an invoice, the same data moves in it as well, populating the TXJCD at the line item level as expected.




Populating  Ship from

  1. Ship from needs to be populated when a Non Japan entity is delivering from Japan plant (Sales Order / Purchase Order).

  Program: ZXFYTU03 

Logic:

  1. Check if Ship From TXJCD is initial.
  2. If yes, check the plant’s country key and populate it.


I hope this will give the basic Idea about the programs where we have to readily look into when using SABRIX.


Regards,

Sagar Puppala.

Sending files from non-SAP to SAP system via Javascript and Webservice (Quick and Dirty)

$
0
0

A year ago, I encountered a challenge working with a third party document repository system that does not have a real RESTful Webservice.

 

The challenge was the third party system uploads PDF files that needs to be attached in SAP via GOS. There is no established interface between an old system to the SAP system but luckily, the old system can still do java script.

 

To make it worse, the SAP system did not have NW Gateway and the customer will not pay for NW Gateway for this requirement. So, implementing official RESTful SDK is not possible

 

So, the solution is to cheat the Webservice to be able to send a file. Another problem is that it cannot send a complex file.

 

So the solution I implemented was:

 

1. I have created a unofficial webservice in the SAP system via SICF and custom handlers. If you are not familiar with this -> view Pseudo-RESTful API in SAP without SAP NW Gateway . The tutorial will show you how to create an unofficial RESTful service.

 

 

2. On the Third Party system side, I created a custom Webservice consumer via Javascript.

 

var postUrl = "http://servername:8000/zrest_2/zfilepost?xblnr="   + entry.xblnr + "&lifnr=" + entry.lifnr + "&sp_id=" + entry.id;     $.ajax({  url: postUrl,  type: "POST",  data: filestring,  success: function(data){  $("#wsSuccess").append(data.Result);     }
});

I am using the standard Jquery API

 

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>

What it does is call the webservice created in SAP.

 

The first two steps are not really too trivial. There are a lot of discussions about this before. The challenge now is how to send a file across that framework and the solution is base64.

 

What is Base64? Base64 is an encoding scheme that converts binary format to radix 64. MIME specification lists Base64 as one of the binary to text scheme. Base64 has its advantages and disadvantages that I will not discuss here.

 

So, moving on with the steps.

 

3. The next step is to convert binary to base64 that will in turn be send via JS in step 2. In Javascript, you need to convert the file to Base64

 

if(this.status == 200){                    var blob = new Blob([this.response],{type:"application/pdf"});                    console.log(blob);                                        reader.readAsDataURL(blob);                    reader.onloadend = function(){                        var result = reader.result;                        console.log(result);                        var bin = convertDataURIToBinary(result);                                             }                                    }
function convertDataURIToBinary(dataURI) {                var BASE64_MARKER = ';base64,';                var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;                var base64 = dataURI.substring(base64Index);                var raw = window.atob(base64);                var rawLength = raw.length;                var array = new Uint8Array(new ArrayBuffer(rawLength));                for(i = 0; i < rawLength; i++) {                  array[i] = raw.charCodeAt(i);                }                return array;              }

The convertDataURIToBinary converts the file to base64 file text file.

 

4. Lastly, we use ABAP FM SSFC_BASE64_DECODE to convert base64 back to binary file using the lv_string as the file converted in the Javascript

 

  lv_string = iv_base64.  SPLIT iv_base64     AT ','     INTO lv_header          lv_string.
*  rv_output = cl_http_utility=>if_http_utility~decode_base64( encoded = lv_string ).
*  CALL FUNCTION 'SSFC_BASE64_DECODE'    EXPORTING      b64data = lv_string    IMPORTING      bindata = rv_output    EXCEPTIONS      OTHERS  = 8.

 

5. Lastly, we attach the file to GOS via SO_OBJECT_INSERT. This has been well documented and you can search a lot of documents to support this.

 

I hope this helps!

The case of source compare utility

$
0
0

Hi,

 

Recently I was involve in complex development environment that was not working so well.....

 

The overhead of this adventure was the need to verify that the code was transported successfully form the development environment to multiple target systems .

 

Usually for small scale changes the split screen editor is sufficient but when we are talking about multiple programs, functions, class, and interfaces the job become tedious and error prone.

 

Fortunately I found some documents about functions RS_CMP_COMPUTE_DELTA and RPY_PROGRAM_READ and using those I wrote program Z_R_EITAN_TEST_15_02 .

 

The program was written using 740 and use a little bit of "String Templates" (those funny looking {})

 

The program is quite new (there might be some bugs there...) but gain some experience in the field....

 

Lets check a a program:

 

 

The result:

 

List of include:

 

 

If there is a discrepancy we get:

 

 

It was a nice surprise to find out that class work as well !!!!

 

The hot spot takes us to SE24

 

 

Any comment is welcome.

 

Enjoy.


How to perform an ATC check automatically during transport TASK release

$
0
0

The ABAP Test Cockpit (ATC) can easily be configured to check every transport request that is released.

 

But in case you are using transport of copies to import into the quality/test system (for example if you use SAP ChaRM for transport management), it is not possible in the standard to perform the ATC checks automatically when transporting from development to quality/test system.

 

On the other hand, in various places, for example the SAP Code Inspector book by Randolf Eilenberger and others, it is described how to trigger a Code Inspector test for every transport task release.

 

I have not yet found a description how to call the ATC automatically, when a transport task is released.

 

If this is what you want in your development system, you only have to implement one method in an implementation of BAdI CTS_REQUEST_CHECK:

 

METHOD if_ex_cts_request_check~check_before_release.

 

  DATA is_ok TYPE abap_bool VALUE abap_true.

 

  SELECT SINGLE trfunction, strkorr FROM e070 WHERE trkorr = @request

                              INTO (@DATA(trfunction), @DATA(strkorr)).

  ASSERT sy-subrc = 0.

  " only process release of transport tasks,

  " not transport requests.

  " And only customer code (not repairs, not customizing).

  CHECK strkorr IS NOT INITIAL AND trfunction = 'S'.

 

  TRY.

      DATA(or_factory) = NEW cl_satc_api_factory( ).

      DATA(it_objects) = cl_satc_object_set_factory=>create_for_transport( request

                                                             )->if_satc_object_set~get_object_keys( ).

      DATA(or_objects) = cl_satc_object_set_factory=>create_for_object_keys( it_objects ).

 

      DATA(or_variant) = NEW cl_satc_ci_check_variant( ).

      or_variant->set_name( 'Z_STANDARD' ).  " replace with your check variant

      DATA(or_run_config) = or_factory->create_run_config_with_chk_var( EXPORTING i_object_set = or_objects

                                                                                                                  i_check_variant = or_variant

                                                                                                                  i_description = |Transport release { request } | ).

      DATA(or_run_controller) = or_factory->create_run_controller( or_run_config ).

      or_run_controller->run( IMPORTING e_result_access = DATA(or_result_access) ).

      or_result_access->get_findings( IMPORTING e_findings = DATA(it_f) ).

      LOOP AT it_f ASSIGNING FIELD-SYMBOL(<wa_f>) WHERE ( kind = 'E' OR kind = 'W' ) " errors/warnings

                                                    AND exceptn<> 'P'. " pseudo comments and pragmas

        is_ok = abap_false.

        EXIT.

      ENDLOOP.

    CATCH cx_satc_failurecx_satc_not_found INTO DATA(cx).

      DATA(exc_text) = cx->get_text( ).

      MESSAGE exc_text TYPE 'E'.

      is_ok = abap_false.

    CATCH cx_satc_empty_object_set cx_satc_invalid_argument INTO cx" ok, if transport is empty or contains only non-checkable objects

  ENDTRY.

 

  IF is_ok = abap_true.

    MESSAGE s007(zs_dev_tools_local).  " success message - create your own message

  ELSE.

    MESSAGE s008(zs_dev_tools_local).  " failure message - create your own message

    " we only get the execution ID with this "dirty" cast:

    DATA(or_result_access_int) = CAST cl_satc_result_access( or_result_access ).

    CALL FUNCTION 'SATC_AC_DISPL_RESULT_BY_EXEC'

      EXPORTING i_execution_id = or_result_access_int->if_satc_result_access~result_id

      EXCEPTIONS

        xpt_no_results     = 1

        xpt_not_authorized = 2

        xpt_display_used   = 3

        OTHERS             = 4.

    ASSERT sy-subrc = 0.

    RAISE cancel.

  ENDIF.

ENDMETHOD.

 

(This coding uses some of the new 7.40 SP08 language features, so you may have to adapt it.)

 

That’s it, basically.

 

I noted however, that include programs are not tested when transported on their own, because the ATC methods that I call only test frame programs (type 1 reports, classes, function groups). I did not find an easy way to overcome this using standard ATC framework functions, so I programmed it in a local class with the following methods:

 

  CLASS-METHODS:

      enrich_main_programs

        CHANGING c_it_keys                  TYPE if_satc_object_set=>ty_object_keys,

      is_include_program

        IMPORTING i_object                    TYPE if_satc_object_set=>ty_object_key

        RETURNING VALUE(r_is_include_program) TYPE abap_bool,

      get_main_programs_for_include

        IMPORTING i_object    TYPE if_satc_object_set=>ty_object_key

        RETURNING VALUE(r_it) TYPE if_satc_object_set=>ty_object_keys,

      is_mainprog_included

        IMPORTING

                  i_it_mainprogs    TYPE if_satc_object_set=>ty_object_keys

                  i_it_current_keys TYPE if_satc_object_set=>ty_object_keys

        RETURNING VALUE(r) TYPE abap_bool,

 

  METHOD enrich_main_programs.

    LOOP AT c_it_keys ASSIGNING FIELD-SYMBOL(<wa>).

      IF is_include_program( <wa> ).

        DATA(it_main_programs) = get_main_programs_for_include( <wa> ).

        IF it_main_programs IS NOT INITIAL

           AND NOT is_mainprog_included( EXPORTING i_it_mainprogs = it_main_programs

                                                   i_it_current_keys  = c_it_keys ).

          INSERT it_main_programs[ 1 ] INTO TABLE c_it_keys.

        ENDIF.

      ENDIF.

    ENDLOOP.

  ENDMETHOD.

 

  METHOD is_include_program.

    r_is_include_program = abap_false.

    CHECK i_object-obj_type = 'PROG'.

    SELECT SINGLE subc FROM reposrc WHERE progname = @i_object-obj_name AND subc = 'I'

                                           INTO @DATA(subc) ##warn_ok##needed.

    IF sy-subrc = 0.

      r_is_include_program = abap_true.

    ENDIF.

  ENDMETHOD.

 

  METHOD get_main_programs_for_include.

    DATA it_mainprogs LIKE STANDARD TABLE OF i_object-obj_name.

    DATA wa LIKE LINE OF r_it.

    CLEAR r_it. " if there are no main programs, return initial

    CALL FUNCTION 'RS_GET_MAINPROGRAMS'

      EXPORTING

        name         = i_object-obj_name

      TABLES

        mainprograms = it_mainprogs

      EXCEPTIONS

        cancelled    = 1

        OTHERS       = 2.

    ASSERT sy-subrc = 0.

    wa-obj_type = 'PROG'.

    LOOP AT it_mainprogs ASSIGNING FIELD-SYMBOL(<wa>).

      wa-obj_name = <wa>.

      INSERT wa INTO TABLE r_it.

    ENDLOOP.

  ENDMETHOD.

 

  METHOD is_mainprog_included.

    r = abap_false.

    LOOP AT i_it_mainprogs ASSIGNING FIELD-SYMBOL(<wa>).

      READ TABLE i_it_current_keys WITH KEY obj_type = <wa>-obj_typeobj_name = <wa>-obj_name

                                  TRANSPORTING NO FIELDS.

      IF sy-subrc = 0.

        r = abap_true.

        RETURN.

      ENDIF.

    ENDLOOP.

  ENDMETHOD.

 

Now, enrich_main_programs() needs to be called to change it_objects after the statement that is creating it_objects.

 

Edit 25.04.2016: exception handling for cx_satc_invalid_argument added.

Easily count custom code (Z Code) lines in your system

$
0
0

One of my colleague asked this question and I found that it is very easy to get the custom lines count in your server. Here is how.

 

1. Goto Tcode /SDF/CD_CCA =>Select "SAP Clone Finder"

 

2. In the field "Customer Namespace" provide "/0CUST/".

 

3. Keep all other fields empty.

 

4. Select "Similarity Check Off"

 

1.PNG

 

5. Run. (F8)

 

6. Select the "Object Size" column and hit "Total" button.

2.PNG

 

You have your Count of Custom Code in your system.

My CDS view self study tutorial - Part 7 unveil the secret of @ObjectModel.readOnly

$
0
0

 

Almost one month has passed since my last tutorial and now I am able to build a useful UI5 application with CDS view + Smart template

 

See my study result recently:


Now let me continue the self study tutorial.


In my application with edit function enabled, I have set posting date as read only by using the annotation below according to sap help.

clipboard1.png

It works as expected in the runtime. However, why it works? Being a developer, I hate the fact that these whole stuff work as a black box to me. I want to know what happens under the hood. Here below I will share with you how I dig it out via debugging in both frontend and backend.

clipboard2.png

1. Debugging in UI5 application

 

In Chrome development tool, I know that the editable state is controlled by property "editable" of the given UI model.

clipboard3.png


So set a breakpoint on SmartField.setEditable function as below, then click edit button, we can observe the parameter bValue is passed as false into this function. Now next question, how and where is this bValue calculated to false?

clipboard4.png

From the callstack I can find another important function: canUpdateProperty. Inside this function, from line 241 we can get this conclusion: any property with attribute "sap:updatable" equals to false, will be rendered as read only in UI.

clipboard5.png

Apparently this attribute is set in backend, as we can confirm by checking in metadata:

clipboard6.png

Now question to ABAP backend: I never specify any annotation like sap:updatable = false in my CDS view, where does it come from?

clipboard5.png

 

2. Debugging in ABAP backend

 

In this method, framework is scanning against annotation with name "OBJECTMODEL.READONLY", as defined in constant in line 836.

If found, ls_sfc-read_only is set as true.

clipboard7.png

This assignment will lead to the check succeeds in line 848 ( since by default a field is editable ), as a result an entry is inserted into internal table mt_element_static_field_ctrl.

clipboard10.png

This is the detail view of the entry for posting date in the table.

clipboard11.png

Later, the internal table is evaluated and if there is entry with read_only = abap_true existing ( see line 71 ), lv_creatable and lv_updatable is set as false.

clipboard12.png

Finally in line 82 and 85, lv_creatable and lv_updatable are used to set property accordingly. All secrets are published now!

clipboard13.png

My CDS view self study tutorial - Part 8 my summary of different approaches for annotation declaration and generation

$
0
0

 

During my recent CDS view explore I have experienced several ways of CDS view annotation declaration or generation. I list them in this blog for study purpose.

 

Approach 1: developer does not declaration explicitly, but backend generates automatically

 

Take this field for example:

clipboard1.png

I do not declare any display format on this field. It has DATS data type in the backend.

clipboard2.png

When you publish this CDS view as OData service and check its metadata response, you can find the annotation "sap:display-format=Date" is automatically added. Who adds it?

clipboard3.png

Answer: it is added here. Backend framework will automatically add corresponding annotation according to the data type of exposed field.

clipboard4.png

Approach 2: developer adds one style of annotation, and backend framework detects such annotation, and do some conversion

 

One example of this kind of approach can already be found from tutorial 7 - unveil the secret of @ObjectModel.readOnly


In that tutorial I explained how the annotation defined by developer ( @ObjectModel.readOnly: true ) is converted by backend framework into sap:updatable = false.

 

@ObjectModel.readOnly: true

 

Z_i_Order_View.posting_date

 

We can also look into another example. We have use the following annotation "@Consumption.valueHelp: '_statusfixedvalue' " to assign a value help to field txt04:

 

@Consumption.valueHelp: '_statusfixedvalue'

Z_i_Order_View.txt04,

 

When framework has scanned the annotation in line 47, it will set flag lv_consumption_vh as true.

clipboard6.png


Base on the fact whether the assigned value help is a consumption or foreign key value help, different element name are put to internal table lt_param_display and finally all the left annotation necessary for value help is generated by framework:

clipboard7.png

And this picture below explains where does each field in metadata response come from. The logic in method _add_value_help is very useful as it teaches us how to create necessary annotation via ABAP code, which we will use later.

clipboard8.png

Approach 3: developer adds necessary annotation via ABAP code

If you would like to build a drop down list based on a CDS view field like the status field below,

clipboard9.png

it is not just enough to use the annotation "Consumption.valueHelp" in CDS view source code - you would just get a drop down list with technical code of status value displayed. Instead, you must tell Smart template framework to display status description text in drop down list.

clipboard10.png

In order to archieve it, you must add the following ABAP code in DEFINE method of the MPC_EXT class of your OData service, to tell the framework that the text property for "status_code" is "status_text" by annotation. The usage of such coding to create annotation by ABAP code is learned from method _add_value_help mentioned in approach 2.


lo_txt_property = model->get_entity_type( 'Z_C_Status_FixedvalueType' )->get_property( 'status_code' ).

lo_txt_property->set_value_list( /iwbep/if_mgw_odata_property=>gcs_value_list_type_property-fixed_values ).

lo_text_anno = lo_txt_property->/iwbep/if_mgw_odata_annotatabl~create_annotation( 'sap' ).

lo_text_anno->add( iv_key = 'text' iv_value = 'status_text').

 

For details please refer to this blog How to build a drop down list using Smart template + CDS view

 

Approach 4: developer adds UI related annotation in CDS view which are consumed by UI5 SmartTemplate

 

All UI related annotation which could be inserted into CDS view source code could be found from SAP help.

 

Those UI annotations will not be converted by ABAP backend:

 

@UI.identification: [ { position: 10 } ]

Z_i_Order_View.posting_date

 

All UI related annotations could be retrieved via the url below:

clipboard1.png

And in the runtime, SmartTemplate also uses this very url to retrieve UI annotations so that it can know what styles of UI control the annotated CDS view field must be rendered as.

clipboard2.png

 

Conclusion

For approach 1 & 2, they sometimes look mysterious as everything happens under the hood. Fortunately we are in ABAP world and we can debug everything to clarify what confuses us by ourselves

 

Select-options to RFC_READ_TABLE options

$
0
0

Hello.

 

This is not going to be rocket-science, but after searching SCN for some time not finding the answer/code I needed, but realizing that many people have the same/similar requirement to mine, I decided to share this code via this blog.

 

Summary:

* you have a report with select-options

* you need to collect data from remote systems and despite all the warnings that the RFC_READ_TABLE function module should not be used (although it is used in zillions of SAP standard programs as well as customer programs so it is zero chance SAP would ever change or remove this function module, I would say), you decided to go for RFC_READ_TABLE.

* you need to transfer the complex selection provided by the user to the remote system because downloading all the data to the local system and deleting the data based on the selection locally is just so inefficient and slow (try transferring huge tables via RFC once and you will understand why this blog...)

* ...which means you need to transfer the select-options to the remote system, because the selection is much smaller and faster to transfer than all the data in the table you need to download

 

Here is the code you need...

* first comes the part of the WHERE clause that you can easily specify manually/hardcode in the source code, because it never changes

* then comes the conversion of one select-option (so for more select-options you need to do this several times)

* ...and finally comes the code that puts the two together

 

cheers Otto

 

 DATA lt_options TYPE tt_option.   DATA lt_selopt TYPE TABLE OF ddshselopt.   DATA ls_selopt LIKE LINE OF lt_selopt.   DATA ls_option LIKE LINE OF et_options.   DATA lv_where TYPE string.   FIELD-SYMBOLS <so_fld> LIKE LINE OF so_fld.
 CLEAR ls_option.   CONCATENATE 'FROM_DAT <= ''' sy-datum ''' AND TO_DAT >= ''' sy-datum '''' INTO ls_option.   APPEND ls_option TO et_options.
CLEAR: lt_selopt[].   LOOP AT so_fld ASSIGNING <so_fld>.     CLEAR: ls_selopt.     MOVE-CORRESPONDING <so_fld> TO ls_selopt.     ls_selopt-shlpfield = 'FIELD'.     APPEND ls_selopt TO lt_selopt.   ENDLOOP.
CLEAR lv_where.   CALL FUNCTION 'F4_CONV_SELOPT_TO_WHERECLAUSE'     IMPORTING       where_clause = lv_where     TABLES       selopt_tab   = lt_selopt.   CLEAR: lt_options[].   PERFORM convert_where_to_rfc_options USING lv_where CHANGING lt_options.   IF lt_options IS NOT INITIAL.     APPEND 'AND' TO et_options.     APPEND LINES OF lt_options TO et_options.     CLEAR lt_options[].   ENDIF.
FORM convert_where_to_rfc_options USING iv_where TYPE rsstring CHANGING et_options TYPE tt_option.   DATA lv_where LIKE iv_where.   DATA lv_bit TYPE so_text.   DATA lv_len TYPE int4.   IF iv_where IS INITIAL.     RETURN.   ENDIF.   CLEAR lv_where.   lv_where = iv_where.   DO.     IF lv_where IS INITIAL.       EXIT.     ENDIF.     CLEAR lv_len.     lv_len = strlen( lv_where ).     IF lv_len <= 72.       APPEND lv_where TO et_options.       EXIT.     ELSE.       lv_bit = lv_where.       lv_len = 71.       DO 71 TIMES.         IF lv_bit+lv_len(1) = ' '.           EXIT.         ENDIF.         lv_len = lv_len - 1.       ENDDO.
*** Index of the last space is in LV_LEN now.       APPEND lv_bit(lv_len) TO et_options.       lv_where = lv_where+lv_len.     ENDIF.   ENDDO.
ENDFORM.
Viewing all 943 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>