The generated Custom Business Object (CBO) User Interface only allows deletion of single entries. This blog post explains how you can delete all CBO entries at once or how to delete multiple entries based on deletion rules.
User Interface Overview
You need to create an own CBO that will serve as "Deletion CBO" for all of your CBOs. An end user will select in which CBO they want to delete entries. And in a second step they can either delete all data or define several deletion rules and execute those.
Deletion CBO Setup
Execute the following steps to build the "Deletion CBO":
- Create a Custom Code List YY1_SIGN with the code values I for Include and E for Exclude. Create a second Custom Code List YY1_OPERATOR with the following entries:
Code |
Description |
---|
EQ |
Equal |
GE |
Greater Than or Equal |
GT |
Greater Than |
LE |
Lower Than or Equal |
LT |
Lower Than |
NE |
Not Equal |
- Create a new CBO YY1_MASS_DELETE. The name of the CBO is important if you want to copy the code of this blog post. The root node consists of the following fields:
Label |
Identifier |
Type |
Key |
Read Only |
---|
CBO Name |
CBOName |
Text (Length 30) |
X |
|
Deletion Last Executed On |
LastExecutedOn |
Date |
|
X |
All Data Deleted |
DeletedAll |
Checkbox |
|
X |
- Create a subnode DeletionRule with the following fields:
Label |
Identifier |
Type |
---|
Field Name |
FieldName |
Text (Length 30) |
Sign |
Sign |
Code List YY1_SIGN |
Option |
Operator |
Code List YY1_OPERATOR |
Value |
Value |
Text (Length 100) |
- Mark the CBO for UI Generation, publish your CBO and maintain the Catalog Extension. As next step, in order to allow mass deletion and deletion based on deletion rules, you will now maintain two actions in the tab 'Logic'. The action will automatically appear as buttons on your generated UI.
- Create the action "DeleteAllData" with Label "Delete All CBO Data". Also create the action "DeleteRuleBased" with Label "Execute Deletion Rules". Publish your CBO. After publishing, you are able to define the logic for your actions.
- Open the logic for the action "DeleteAllData". In the section 'Select entries' you have to add each CBO which you want to enable for deletion. In case of this blog post the CBOs YY1_USER and YY1_BUILDING are enabled. Publish your action logic after implementation.
* Action DeleteAllData for Node ID MASS_DELETE
*
* Importing Parameter : association (Navigation to Parent/Child/
* Associated Node Instances)
* write (API for creating and updating
* Custom Business Object Node Instances)
* Changing Parameter : MASS_DELETE (Current Node Data)
* Exporting Parameter : message (Message with Severity S(uccess),
* W(arning), E(rror))
DATA: lv_timezone TYPE timezone.
CONSTANTS: lc_package_size TYPE i VALUE 5000.
*----------------------------------------------------------------------*
* Select entries
*----------------------------------------------------------------------*
CASE mass_delete-cboname.
WHEN 'YY1_USER'.
SELECT sap_uuid FROM yy1_user INTO TABLE @DATA(lt_keys).
WHEN 'YY1_BUILDING'.
SELECT sap_uuid FROM yy1_building INTO TABLE @lt_keys.
WHEN OTHERS.
message = VALUE #(
severity = co_severity-error
text = |CBO { mass_delete-cboname } | &
|not enabled for deletion| ).
RETURN.
ENDCASE.
DATA(lv_lines) = LINES( lt_keys ).
DATA(lv_all_lines) = lv_lines.
*----------------------------------------------------------------------*
* Delete entries in 5000 chunks -> Prevent System Overloading
*----------------------------------------------------------------------*
DATA(lv_packaging) = abap_false.
IF lv_lines > lc_package_size.
lv_packaging = abap_true.
ENDIF.
DATA(lv_end_delete) = abap_false.
WHILE lv_end_delete = abap_false.
IF lv_lines <= lc_package_size.
lv_end_delete = abap_true.
ENDIF.
DATA(lt_current_keys) = lt_keys.
IF lv_lines > lc_package_size.
DATA(lv_delete_index) = lc_package_size + 1.
DELETE lt_current_keys FROM lv_delete_index.
DELETE lt_keys FROM 1 TO lc_package_size.
lv_lines = lines( lt_keys ).
ENDIF.
TRY.
write->delete_root(
EXPORTING
business_object_id = CONV #( mass_delete-cboname )
keys = lt_current_keys
).
CATCH cx_root.
message = VALUE #( severity = co_severity-error
text = 'Deletion of Entries failed' ).
RETURN.
ENDTRY.
ENDWHILE.
*----------------------------------------------------------------------*
* Update Last Executed at Column and Delete All indicator
*----------------------------------------------------------------------*
GET TIME STAMP FIELD DATA(lv_current_time).
CONVERT TIME STAMP lv_current_time TIME ZONE lv_timezone
INTO DATE DATA(lv_current_date).
mass_delete-lastexecutedon = lv_current_date.
mass_delete-deletedall = abap_true.
*----------------------------------------------------------------------*
* Success Message
*----------------------------------------------------------------------*
message = VALUE #( severity = co_severity-success
text = 'All data deleted successfully' ).
- Open the logic for the action "DeleteRuleBased". You have to implement the following sections:
- Data declarations per field name: Define a range table for each field, you want to enable for rule based deletion.
- Build Range Tables: Add each CBO with its field names which you want to enable for deletion. In case of this blog post the CBO YY1_USER is enabled. Deletion rules for CBO YY1_BUILDING would fail as they are not implemented.
- Select Entries: Add the select statement for each enabled CBO with the corresponding range tables.
Publish your action logic after implementation.
* Action DeleteRuleBased for Node ID MASS_DELETE
*
* Importing Parameter : association (Navigation to Parent/Child/
* Associated Node Instances)
* write (API for creating and updating
* Custom Business Object Node Instances)
* Changing Parameter : MASS_DELETE (Current Node Data)
* Exporting Parameter : message (Message with Severity S(uccess),
* W(arning), E(rror))
DATA: lv_timezone TYPE timezone.
*-----------------------------------------------------------------------*
* Data declarations per field name
*-----------------------------------------------------------------------*
DATA: lt_range_city TYPE RANGE OF yy1_s_yy1_user_d-city,
lt_range_country TYPE RANGE OF yy1_s_yy1_user_d-country.
CONSTANTS: lc_package_size TYPE i VALUE 5000.
*-----------------------------------------------------------------------*
* Allowed Values Check -> Prevent Dump
*-----------------------------------------------------------------------*
DATA(lt_allowed_values_sign) = VALUE string_table( ( `I` ) ( `E` ) ).
DATA(lt_allowed_values_option) = VALUE string_table( ( `EQ` ) ( `NE` )
( `CP` ) ( `LE` )
( `GE` ) ( `NP` )
( `GT` ) ( `LT` )
).
*-----------------------------------------------------------------------*
* Check Deletion Rules
*-----------------------------------------------------------------------*
DATA(lt_rules) = association->to_deletionrule( ).
IF lines( lt_rules ) = 0.
message = VALUE #( severity = co_severity-error
text = 'No deletion rules defined' ).
RETURN.
ENDIF.
LOOP AT lt_rules INTO DATA(ls_rule).
IF NOT line_exists( lt_allowed_values_sign[
table_line = ls_rule-deletionrule-sign ] ).
message = VALUE #( severity = co_severity-error
text = |Sign { ls_rule-deletionrule-sign } | &
|is not allowed| ).
RETURN.
ENDIF.
IF NOT line_exists( lt_allowed_values_option[
table_line = ls_rule-deletionrule-operator ] ).
message = VALUE #( severity = co_severity-error
text = |Option | &
|{ ls_rule-deletionrule-operator } | &
|is not allowed| ).
RETURN.
ENDIF.
*-----------------------------------------------------------------------*
* Build Range Tables
*-----------------------------------------------------------------------*
DATA(lv_no_fieldname) = abap_false.
CASE mass_delete-cboname.
WHEN 'YY1_USER'.
CASE ls_rule-deletionrule-fieldname.
WHEN 'City'.
APPEND VALUE #( sign = ls_rule-deletionrule-sign
option = ls_rule-deletionrule-operator
low = ls_rule-deletionrule-value )
TO lt_range_city.
WHEN 'Country'.
APPEND VALUE #( sign = ls_rule-deletionrule-sign
option = ls_rule-deletionrule-operator
low = ls_rule-deletionrule-value )
TO lt_range_country.
WHEN OTHERS.
lv_no_fieldname = abap_true.
ENDCASE.
WHEN OTHERS.
message = VALUE #( severity = co_severity-error
text = |Deletion for CBO | &
|{ mass_delete-cboname } | &
|is not implemented| ).
RETURN.
ENDCASE.
IF lv_no_fieldname = abap_true.
message = VALUE #( severity = co_severity-error
text = |Fieldname | &
|{ ls_rule-deletionrule-fieldname } | &
|does not exist in CBO | &
|{ mass_delete-cboname } | ).
RETURN.
ENDIF.
ENDLOOP.
*-----------------------------------------------------------------------*
* Select entries
*-----------------------------------------------------------------------*
CASE mass_delete-cboname.
WHEN 'YY1_USER'.
SELECT sap_uuid FROM yy1_user INTO TABLE @DATA(lt_keys)
WHERE city IN @lt_range_city
AND country IN @lt_range_country.
SELECT count( sap_uuid ) FROM yy1_user INTO @DATA(lv_db_lines).
WHEN OTHERS.
message = VALUE #( severity = co_severity-error
text = |Deletion for CBO | &
|{ mass_delete-cboname } | &
|is not implemented| ).
RETURN.
ENDCASE.
DATA(lv_lines) = LINES( lt_keys ).
DATA(lv_all_lines) = lv_lines.
IF lv_lines = 0.
message = VALUE #( severity = co_severity-success
text = 'No entries match the deletion rules' ).
RETURN.
ENDIF.
*-----------------------------------------------------------------------*
* Delete entries in 5000 chunks -> Prevent System Overloading
*-----------------------------------------------------------------------*
IF lv_db_lines = lv_all_lines.
CLEAR lt_keys.
write->get_context_node( )->execute_action(
EXPORTING
action_id = 'DeleteAllData'
IMPORTING
data = mass_delete
" messages = " Action messages are returned anyhow to the UI
).
mass_delete-deletedall = abap_false.
RETURN.
ENDIF.
DATA(lv_packaging) = abap_false.
IF lv_lines > lc_package_size.
lv_packaging = abap_true.
ENDIF.
DATA(lv_end_delete) = abap_false.
WHILE lv_end_delete = abap_false.
IF lv_lines <= lc_package_size.
lv_end_delete = abap_true.
ENDIF.
DATA(lt_current_keys) = lt_keys.
IF lv_lines > lc_package_size.
DATA(lv_delete_index) = lc_package_size + 1.
DELETE lt_current_keys FROM lv_delete_index.
DELETE lt_keys FROM 1 TO lc_package_size.
lv_lines = lines( lt_keys ).
ENDIF.
TRY.
write->delete_root(
EXPORTING
business_object_id = CONV #( mass_delete-cboname )
keys = lt_current_keys
).
CATCH cx_root.
message = VALUE #( severity = co_severity-error
text = 'Deletion of Entries failed' ).
RETURN.
ENDTRY.
ENDWHILE.
*-----------------------------------------------------------------------*
* Update Last Executed at Column
*-----------------------------------------------------------------------*
GET TIME STAMP FIELD DATA(lv_current_time).
CONVERT TIME STAMP lv_current_time TIME ZONE lv_timezone
INTO DATE DATA(lv_current_date).
mass_delete-lastexecutedon = lv_current_date.
*-----------------------------------------------------------------------*
* Success Message
*-----------------------------------------------------------------------*
message = VALUE #(
severity = co_severity-success
text = COND #( WHEN lv_all_lines = 1 THEN
|1 entry deleted successfully|
ELSE
|{ lv_all_lines } entries deleted successfully| )
).
Deletion through OData Service
For both actions a function import was created in the generated OData Service - YY1_MASS_DELETEDeletealldata and YY1_MASS_DELETEDeleterulebased. You can call these function imports to delete your CBO data from the outside of the Marketing system, e.g. through SAP Cloud Platform Integration (
Example for scheduled Function Import call). You will need the SAP_UUID of your root node entry. To get this, use the following call with your CBO Name:
GET https://my<...>-api.s4hana.ondemand.com/sap/opu/odata/sap/YY1_MASS_DELETE_CDS/YY1_MASS_DELETE?$selec... eq 'YY1_USER'
Afterwards you can e.g. delete all data using this call with the returned SAP_UUID:
POST https://my<...>-api.s4hana.ondemand.com/sap/opu/odata/sap/YY1_MASS_DELETE_CDS/YY1_MASS_DELETEDeletealldata?SAP_UUID=guid'00163e69-ebdd-1ee9-9785-c254cae1b958'