Documente Academic
Documente Profesional
Documente Cultură
relationship
Summary
Extend the standard BOL model BT with a custom relationship to accomodate your z-table.
Intro
There's been a few discussions in the forums about finding a way to add a custom table to the standard
BOL models. It's a bit tricky, but possible. In this WIKI, I will show you how to enhance the BOl model BT with
a new 1..n relationship. The advantages are obvious. Any change in your custom data will register in the BOl
layer, you will be able to capture the BOl Core Reset and Order Init(revert) events, use the BOL model node
wizards etc., Enhancing a few standard classes are in order. So, knowing how to enhance the methods using
the implicit enhancement points is necessary.
In this example, we will create the relationship under the administration header(BTAdminH). Actually, two
relationships. One will be a read only relationship(similiar to BTItems) that is linked and the other will be the
1..n relation that corresponds to your database.
My custom database ZORD has the following fields.
CLIENT
type MANDT
"Client
GUID
type CRMT_OBJECT_GUID "guid of table entry(key)
REF_GUID
type CRMT_OBJECT_GUID "guid of order object
EXTERN_CASE_NO type CRMT_OBJECT_ID "end-user usable
NOTE
type CHAR120
"end-user usable
A structure 'ZORD_STRUCT' that corresponds to the table structure and a table type 'ZORD_STRUCT_TAB' is
available.
1. Initialization -> When the order is being initialized say, during lock/revert scenarios, we want our object to
be initialized too.
2. Save -> When the order is being saved, save our data too.
3. Delete -> When the order is deleted, we need to delete corresponding data from our custom table
For this, you must identify the correct places to insert our event callback function modules. If not already
done, set your user parameterCRM_EVENT_TRACE to "X". Next, do something say, create transactions,
modify it, save it and delete it. After each action, look at the events raised in
transaction CRMD_EVENT_TRACE. Study this data carefully and decide on the points in which to insert your
event callback function modules. For creating the FMs(the signature part), look at the standard FMs (they
end with *_EC) defined for the same event in the trace data. Create empty FMs for each of the three events
mentioned and study the trace again. We will be calling our custom API class' methods inside these FMs. We
will cover this later. For now, create entries in the transaction event handler table and ensure that the FMs are
getting called in the correct places for various scenarios.
For example, if I'm extending the Opportunity model, I will create these entries - In SPRO, go to CRM>Transactions->Basic Settings->Edit event handler table
1. Initialization
Choosing transaction category is opportunity will ensure that this FM will be called only for Opportunities
Transaction Category
BUS2000111 (Opportunity)
Execution Time
88 (Initialize Document)
Priority
99
Object
ZORD (Z Order Extension)
Event
INIT
Attribute
<*>
Function
ZORD_INIT_EC
Perform Function for Document Header X
Perform Function for Document Item blank
Do Not Process Function if Event Error Occurs blank
Call Callback
Call Just Once Per Transaction
2. Save
Unlike above, we cannot choose transaction category as Opportunity here! This is because an opportunity
save event will be raised only when you make changes in the opportunity relevant fields(eg OPPORT_H),
even though you are editing an opportunity. For the below FM to trigger, we will have to "remind" the system
to do it when changes are made to our custom objects. So, you will not see this FM in the event trace right
now till the coding part is complete. We will cover this further down(Look further down at the last part of the
coding part for the method implementation for METHOD ZORD_RUN_BTIL>if_crm_runtime_btil~maintain_attributes).
Transaction Category
BUS20001 (CRM Bus Transactions)
Execution Time
80 (Save Document)
Priority
99
Object
ZORD (Z Order Extension)
Event
SAVE
Attribute
<*>
Function
ZORD_SAVE_EC
Perform Function for Document Header X
Perform Function for Document Item blank
Do Not Process Function if Event Error Occurs blank
Call Callback
Call Just Once Per Transaction
3. Delete
For deletion also, only generic order delete event can be chosen. Inside this Fm, you should make sure that
the scenario is right. For example, make sure that the order being deleted is an opportunity before making
API calls! We will cover this below.
Transaction Category
BUS20001 (CRM Bus Transactions)
Execution Time
1 (Immediately)
Priority
90
Object
ORDER (General order processing)
Event
AFTER_DELETE
Attribute
<*>
Function
ZORD_DEL_EC
Perform Function for Document Header X
Perform Function for Document Item blank
Do Not Process Function if Event Error Occurs blank
Call Callback
Call Just Once Per Transaction
Maintain Z-Relations
Note:
If you are not willing to implement the classes, you may skip maintaining the handler classes. You can even directly specify a single 1..n relationship
directly under BTAdminH. The relationships will be available, but not usable in the real sense. Then, you can follow this wiki to make do with value
nodes.
http://wiki.sdn.sap.com/wiki/display/CRM/How+to+display+a+z-table+in+an+assignment+block
Moving forward, here's a screenshot of the model with the new relationships expanded.
Method Signatures:
*"* public components of class ZL_ZORD_BTIL_API
*"* do not include other source files here!!!
public section.
class-methods ZORD_CREATE
importing
!IS_KEY type CRMT_OBJECT_GUID
!IS_ATTRIBUTES type ZORD_STRUCT .
class-methods ZORD_MODIFY
importing
!IS_KEY type CRMT_OBJECT_GUID
!IV_HEADER_GUID type CRMT_OBJECT_GUID
!IS_ATTRIBUTES type ZORD_STRUCT
!IT_CHANGED_FIELDS type CRMT_ATTR_NAME_TAB
exporting
!ET_RETURN type BAPIRET2_T
!ET_CHANGED_OBJECTS type CRMT_GENIL_OBJ_INSTANCE_TAB .
class-methods ZORD_DELETE
importing
!IS_KEY type CRMT_OBJECT_GUID
!IV_HEADER_GUID type CRMT_OBJECT_GUID
exporting
!ET_RETURN type BAPIRET2_T
!ET_CHANGED_OBJECTS type CRMT_GENIL_OBJ_INSTANCE_TAB .
class-methods ZORD_SAVE
importing
!IV_HEADER_GUID type CRMT_OBJECT_GUID .
class-methods ZORD_READ
importing
!IV_HEADER_GUID type CRMT_OBJECT_GUID
exporting
!ET_ZORD type ZORD_STRUCT_TAB .
class-methods ZORD_READ_SINGLE
importing
!IV_GUID type CRMT_OBJECT_GUID
exporting
!ET_ZORD type ZORD_STRUCT_TAB .
class-methods ZORD_INIT
importing
!IV_HEADER_GUID type CRMT_OBJECT_GUID optional .
class-methods ZORD_DELETE_WITH_ORDER
importing
!IV_HEADER_GUID type CRMT_OBJECT_GUID optional .
Method Implementations
METHOD zord_create.
DATA: ls_zord TYPE zord_struct.
ls_zord = is_attributes.
ls_zord-guid = is_key.
INSERT ls_zord INTO TABLE gt_zord.
ENDMETHOD.
METHOD zord_modify.
FIELD-SYMBOLS: <fs_zord> LIKE LINE OF gt_zord,
<old> TYPE simple,
<new> TYPE simple,
<name> TYPE name_komp.
READ TABLE gt_zord WITH KEY guid = is_key ASSIGNING <fs_zord>.
IF sy-subrc EQ 0.
LOOP AT it_changed_fields ASSIGNING <name>.
ASSIGN COMPONENT <name> OF STRUCTURE <fs_zord> TO <old>.
CHECK sy-subrc = 0.
ASSIGN COMPONENT <name> OF STRUCTURE is_attributes TO <new>.
CHECK sy-subrc = 0.
<old> = <new>.
ENDLOOP.
ENDIF.
ENDMETHOD.
METHOD zord_delete.
DATA: ls_zord LIKE LINE OF gt_zord.
READ TABLE gt_zord WITH KEY guid = is_key INTO ls_zord.
IF sy-subrc EQ 0.
INSERT ls_zord INTO TABLE gt_zord_del.
DELETE gt_zord WHERE guid = is_key.
ENDIF.
ENDMETHOD.
METHOD zord_save.
DATA: lt_zord TYPE zord_struct_tab,
ls_zord LIKE LINE OF lt_zord.
LOOP AT gt_zord INTO ls_zord WHERE ref_guid = iv_header_guid.
INSERT ls_zord INTO TABLE lt_zord.
ENDLOOP.
IF lt_zord[] IS NOT INITIAL.
MODIFY zord FROM TABLE lt_zord.
ENDIF.
REFRESH lt_zord.
LOOP AT gt_zord_del INTO ls_zord WHERE ref_guid = iv_header_guid.
INSERT ls_zord INTO TABLE lt_zord.
ENDLOOP.
IF lt_zord[] IS NOT INITIAL.
DELETE zord FROM TABLE lt_zord.
ENDIF.
ENDMETHOD.
METHOD zord_read.
DATA: lt_zord TYPE zord_struct_tab,
ls_zord LIKE LINE OF lt_zord.
*Check buffer first
*If entries are found in buffer, no need to check DB as whole buffer is used.
*no special provisions for deleted items
READ TABLE gt_zord WITH KEY ref_guid = iv_header_guid TRANSPORTING NO FIELDS.
IF sy-subrc EQ 0.
et_zord[] = gt_zord[].
DELETE et_zord WHERE ref_guid NE iv_header_guid.
ELSE.
READ TABLE gt_zord_del WITH KEY ref_guid = iv_header_guid TRANSPORTING NO FIELDS.
IF sy-subrc EQ 0.
*deleted entries exist in buffer!! => Table has already been buffered => No DB Read
EXIT.
ELSE.
REFRESH et_zord.
SELECT * FROM zord INTO TABLE et_zord WHERE ref_guid = iv_header_guid.
INSERT LINES OF et_zord INTO TABLE gt_zord.
ENDIF.
ENDIF.
ENDMETHOD.
METHOD zord_read_single.
METHOD if_crm_runtime_btil~read_attributes.
DATA: ls_guid TYPE crmst_guid_btil.
*This is a static read-only relation.
*Inherit GUID from order
TRY.
ir_cont_obj->set_key( iv_ref_guid ).
CATCH cx_crm_cic_duplicate_entry.
ENDTRY.
IF ir_cont_obj->check_attr_requested(
ls_guid-crm_guid = iv_ref_guid.
me->set_attributes( ir_cont_obj
=
is_attributes =
iv_ref_kind
=
iv_log_key
=
ENDIF.
) EQ true.
ir_cont_obj
ls_guid
iv_ref_kind
iv_ref_guid ).
=
=
=
=
ir_cont_obj
ir_api_data
iv_ref_guid
iv_ref_kind ).
*
ENDMETHOD.
METHOD IF_CRM_RUNTIME_BTIL~MAINTAIN_ATTRIBUTES.
DATA: lv_delta TYPE crmt_delta.
***
*This is a read-only relation, nothing much to do here except inherit GUID from Order object
lv_delta = ir_cont_obj->get_delta_flag( ).
IF lv_delta EQ ir_cont_obj->delta_created.
TRY.
ir_cont_obj->set_key( iv_ref_guid ).
CATCH cx_crm_cic_duplicate_entry.
ENDTRY.
ENDIF.
*Take care of underlying relations
me->modify_children( ir_cont_obj
ir_data
ir_input_fields
iv_ref_kind
iv_ref_guid
=
=
=
=
=
ir_cont_obj
ir_api_data
ir_input_fields
iv_ref_kind
iv_ref_guid ).
ENDMETHOD.
METHOD MODIFY_CHILDREN .
*Do not redefine this method! Super class will take care of this
ENDMETHOD.
METHOD fieldcheck.
*redefined to prevent system looking for unimplemented FM
*empty, create your own implementation
endmethod.
METHOD if_crm_runtime_btil~read_attributes.
DATA: lr_root_container TYPE REF TO if_genil_container_object,
lv_guid TYPE crmt_object_guid,
lv_first TYPE crmt_boolean VALUE true,
lv_relation TYPE crmt_relation_name.
DATA: lr_zord_i TYPE REF TO zord_struct_tab,
lt_zord_i TYPE zord_struct_tab,
lr_attr_props TYPE REF TO if_genil_obj_attr_properties,
lr_cont_obj TYPE REF TO if_genil_container_object.
FIELD-SYMBOLS: <ls_zord_i> TYPE zord_struct,
<ls_api_data>
TYPE crmt_intlay_get,
<lt_api_data>
TYPE crmt_intlay_get_tab.
***
CALL METHOD zl_zord_btil_api=>zord_read
EXPORTING
iv_header_guid = iv_ref_guid "Order GUID
IMPORTING
et_zord
= lt_zord_i.
CALL METHOD ir_cont_obj->get_parent_relation
IMPORTING
ev_relation_name = lv_relation.
LOOP AT lt_zord_i ASSIGNING <ls_zord_i>.
TRY.
IF lv_first EQ true.
lv_first = false.
lr_cont_obj = ir_cont_obj.
lr_cont_obj->set_key( <ls_zord_i>-guid ).
ELSE.
lr_cont_obj = ir_cont_obj->copy_self_with_structure(
is_object_key
= <ls_zord_i>-guid
iv_relation_name = lv_relation ).
CHECK lr_cont_obj IS BOUND.
ENDIF.
CATCH cx_crm_cic_duplicate_entry.
TRY.
lr_cont_obj = me->turn_path( ir_cont_obj
= ir_cont_obj
iv_relation_name = lv_relation
is_key
= <ls_zord_i>-guid ).
CATCH cx_crm_genil_general_error.
CONTINUE.
ENDTRY.
CATCH cx_crm_genil_model_error.
CONTINUE.
ENDTRY.
IF lr_cont_obj->check_attr_requested( ) EQ true.
me->set_attributes( ir_cont_obj
= lr_cont_obj
is_attributes = <ls_zord_i>
iv_ref_kind
= iv_ref_kind
iv_log_key
= <ls_zord_i>-guid ).
lr_attr_props = lr_cont_obj->get_attr_props_obj( ).
lr_attr_props->set_all_properties( if_genil_obj_attr_properties=>changeable ).
lr_attr_props->set_property_by_idx( iv_index = 1
iv_value = if_genil_obj_attr_properties=>read_only ). "Client
lr_attr_props->set_property_by_idx( iv_index = 2
iv_value = if_genil_obj_attr_properties=>technical ). "GUID
lr_attr_props->set_property_by_idx( iv_index = 3
iv_value = if_genil_obj_attr_properties=>technical ). "REF_GUID
ENDIF.
IF lr_cont_obj->check_rels_requested( ) EQ true.
me->get_foreign_id( ir_cont_obj = lr_cont_obj
is_data
= <ls_zord_i> ).
ENDIF.
ENDLOOP.
ENDMETHOD.
*
METHOD if_crm_runtime_btil~maintain_attributes.
INCLUDE crm_mode_con.
DATA: lv_delta
TYPE crmt_delta.
DATA: ls_changed_object TYPE crmt_genil_obj_instance.
DATA: lt_names TYPE crmt_attr_name_tab.
DATA: lr_attr_props TYPE REF TO if_genil_obj_attr_properties.
FIELD-SYMBOLS: <ls_api_data> TYPE crmst_order_maintain,
<lv_name>
TYPE name_komp.
***
DATA: lv_props_obj
TYPE REF TO if_genil_obj_attr_properties,
lt_changed_attr TYPE crmt_attr_name_tab,
ls_attributes
TYPE zord_struct,
lv_guid
TYPE crmt_object_guid,
*
lt_return
TYPE bapiret2_t,
*
lv_msg_cont
TYPE REF TO cl_crm_genil_bapi_mess_cont,
ls_obj_inst
TYPE crmt_genil_obj_instance,
lv_header
TYPE REF TO if_genil_container_object,
lv_header_guid TYPE crmt_genil_object_guid,
ls_header_attr
TYPE crmst_guid_btil.
CHECK ir_cont_obj->get_name( ) = 'ZORD'.
ls_obj_inst-object_name = 'ZORD'.
"#EC NOTEXT
* retrieve the GUID of the header
lv_header = ir_cont_obj->get_parent( ).
CALL METHOD lv_header->get_key
IMPORTING
es_key = lv_header_guid.
CALL METHOD lv_header->get_attributes
IMPORTING
es_attributes = ls_header_attr.
* retrieve the delta flag
lv_delta = ir_cont_obj->get_delta_flag( ).
* branch according to the delta flag. Dependent objects may be created, modified, or deleted.
CASE lv_delta.
WHEN if_genil_cont_simple_object=>delta_changed.
*
retrieve the attribute property object to get the modify details
lv_props_obj = ir_cont_obj->get_attr_props_obj( ).
*
which attributes were modified?
CALL METHOD lv_props_obj->get_name_tab_4_property
EXPORTING
iv_property = if_genil_obj_attr_properties=>modified
IMPORTING
et_names
= lt_changed_attr.
CALL METHOD ir_cont_obj->get_key
IMPORTING
es_key = lv_guid.
CALL METHOD ir_cont_obj->get_attributes
IMPORTING
es_attributes = ls_attributes.
*
*
*
*
*
**
*
*
*
*
*
*
*
*
1. Initialization
FUNCTION zord_init_ec.
*"---------------------------------------------------------------------*"*"Local Interface:
*" IMPORTING
*"
REFERENCE(IV_HEADER_GUID) TYPE CRMT_OBJECT_GUID OPTIONAL
*"---------------------------------------------------------------------IF iv_header_guid IS NOT INITIAL.
CALL METHOD ZL_ZORD_BTIL_API=>ZORD_INIT
EXPORTING
iv_header_guid = iv_header_guid.
ENDIF.
ENDFUNCTION.
2. Save
FUNCTION zord_save_ec.
*"---------------------------------------------------------------------*"*"Local Interface:
*" IMPORTING
*"
REFERENCE(IV_OBJECT_NAME) TYPE CRMT_OBJECT_NAME
*"
REFERENCE(IV_EVENT_EXETIME) TYPE CRMT_EVENT_EXETIME
*"
REFERENCE(IV_EVENT) TYPE CRMT_EVENT
*"
REFERENCE(IT_HEADER_GUID) TYPE CRMT_OBJECT_GUID_TAB
*"---------------------------------------------------------------------Data: lv_header_guid type crmt_object_guid.
loop at it_header_guid into lv_header_guid.
CALL METHOD ZL_ZORD_BTIL_API=>ZORD_SAVE
EXPORTING
iv_header_guid = lv_header_guid.
endloop.
ENDFUNCTION.
3. Delete
FUNCTION zord_del_ec.
*"---------------------------------------------------------------------*"*"Local Interface:
*" IMPORTING
*"
REFERENCE(IV_OBJECT_NAME) TYPE CRMT_OBJECT_NAME
*"
REFERENCE(IV_EVENT_EXETIME) TYPE CRMT_EVENT_EXETIME
*"
REFERENCE(IV_EVENT) TYPE CRMT_EVENT
*"
REFERENCE(IV_HEADER_GUID) TYPE CRMT_OBJECT_GUID OPTIONAL
*"---------------------------------------------------------------------*Use FM "CRM_ORDERADM_H_READ_OB" with include_deleted_header option to ensure that object is relevant to us
* - before calling API method
IF iv_header_guid IS NOT INITIAL.
*Delete from object buffer
CALL METHOD ZL_ZORD_BTIL_API=>ZORD_INIT
EXPORTING
iv_header_guid = iv_header_guid.
*Delete from database
CALL METHOD ZL_ZORD_BTIL_API=>ZORD_DELETE_WITH_ORDER
EXPORTING
iv_header_guid = iv_header_guid.
ENDIF.
ENDFUNCTION.
Results:
Here are a few screens showing the results