Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
vonglan
Active Participant


As an SAP customer, we have lots of small development topics (change requests) every year. Most of the change requests are tested and then transported into the production system within a short time frame. However, there are always some change requests which are abandoned for various reasons.  Periodically, my colleagues in the Basis (administration) Team create a list of these “long running changes”, and ask the developers to either release them or rolling them back (revert the objects to the original state, as in the productive system. This is unpleasant work for both the Basis colleagues and the developers.

To avoid this, we implemented a little report that automatically reverts the development and customizing objects in selected open transports to the state that they have in the production system.

It does this by creating a “mirror transport” in the production system.

Two pieces of coding are needed:

  • An RFC function module in the production system, which is called with the object list, and adds them to the open transport of copies

  • A small report for the development system, which performs some checks, creates backup versions of the objects, and then calls the function module


I attach the coding in “new” 7.40 SP08 syntax as text files. Of course, it can be rewritten to work on older ABAP versions as well. The function module logic is implemented in a local class, so the text file consists of 3 parts. In addition to the code, one message has to be created for the function module – see comment in code. There are 480 lines of code in total.

Besides executing the report, 4 manual steps are necessary. The sequence is:

  1. Manually: Create an empty "transport of copies" in the production system (P system), to use as a mirror transport. Target = development system + client (if you do not enter a target system, no transport file is created!)

  2. Using the new report (executed in the development system), fill the "mirror transport" in the P system automatically, based on a list of open transports (from the selection screen). The report also deletes empty transport tasks and requests, releases all transport tasks that are not yet released, checks  if any of the objects in the transports have inactive versions (because these would not be overwritten in step 4), and creates versions of all objects

  3. Manually: Release the mirror transport in the P system. Ignore the warning message "Not all objects could be identified due to missing object catalog entry".

  4. Manually: Import the mirror transport into the Development system. You have to choose the "Overwrite originals" option (and if there are any modifications involved, "overwrite objects in repairs").

  5. Manually: Release and transport the original transport requests


Known limitation: The report does not work for customizing if source and target client differ.

Disclaimer: I have tested the process and code in our own systems, but of course I cannot guarantee that it is bug-free, works as specified, etc.

Also, I do not have experience how this might work in complex landscapes with several development systems. (My first assumption is, that it does not introduce any additional risks or complexity.)

 

Update history:

29.06.2016: language edit: decommission --> roll back

Warning concerning "overwrite objects in repairs", later removed because of updated code

30.06.2016: sentence about complex landscapes in disclaimer

04.07.2016: Code updated with check for inactive objects

07.07.2016: Code updated according to Daniel Klein's feedback. Limitation regarding customizing / different clients mentioned.

21.07.2016: Code updated (V4): automatically delete empty tasks and requests

16.11.2016: Code added to blog directly, because attachments are not possible with new SCN. Small bugfix (search for "SCN Version 4" in the code). Added "known problem" to code as comment.
*----------------------------------------------------------------------*
* Author: Edo von Glan
* Version 5 (16.11.2016)
*----------------------------------------------------------------------*
* Known problem:
* - if the transport of copies contains table (TABL) and related table entries (TABU),
* AND the table does not exist in the productive system,
* then we get strange errors when releasing (unknown syntax, table does not exist, ...)
* when we try to release the transport of copies in the P system.
* Solution: manually delete the table contents entry.
* (In the program, we could avoid this situation by
* checking in the RFC in the P system for all TABU records, whether
* the table exists)
*----------------------------------------------------------------------*
REPORT zs_decom_obsol_trans.

TABLES: e070.

SELECT-OPTIONS s_trans FOR e070-trkorr. " Trans.Requests 2 B rolled back
PARAMETERS: p_p_tr TYPE trkorr OBLIGATORY " Transprt of copies on P System
, p_p_sys TYPE rfcdest OBLIGATORY DEFAULT 'MT3_050' " Productive system (RFC-Dest.)
.

CLASS lcl DEFINITION FINAL.
PUBLIC SECTION.
CLASS-METHODS main.
PRIVATE SECTION.
CLASS-METHODS:
process_trkorr
IMPORTING i_trkorr TYPE trkorr,
prepare_trkorr_ok
IMPORTING i_trkorr TYPE trkorr
RETURNING VALUE(r_is_ok) TYPE abap_bool,
prevent_inactive_objects_ok
IMPORTING i_trkorr TYPE trkorr
RETURNING VALUE(r_is_ok) TYPE abap_bool,
release_ok
IMPORTING i_trkorr TYPE trkorr
RETURNING VALUE(r_is_ok) TYPE abap_bool,
store_pre_decom_version
IMPORTING i_it_e071 TYPE e071_t,
delete_empty_task_request_ok
IMPORTING i_trkorr TYPE trkorr
RETURNING VALUE(r_is_ok) TYPE abap_bool.
CONSTANTS: con_allowed_trfunctions_req TYPE string VALUE 'KW' " workbench&customizing request
, con_allowed_trfunctions_task TYPE string VALUE 'QSXR' " workbench&customizing task, unclassified (empty), repair
.
ENDCLASS.

START-OF-SELECTION.
lcl=>main( ).

CLASS lcl IMPLEMENTATION.

METHOD main.
IF s_trans IS INITIAL.
WRITE / |Selection criteria for transports missing|.
EXIT.
ENDIF.

SELECT trkorr FROM e070
WHERE trkorr IN @s_trans
INTO TABLE @DATA(it_trkorr).
IF sy-subrc <> 0.
WRITE / |No transports found with selection criteria|.
ENDIF.

LOOP AT it_trkorr ASSIGNING FIELD-SYMBOL(<wa_trkorr>).
IF prepare_trkorr_ok( <wa_trkorr>-trkorr ).
process_trkorr( <wa_trkorr>-trkorr ).
ENDIF.
ENDLOOP.

ENDMETHOD.

METHOD process_trkorr.
DATA: return_msg TYPE bapiret2
, msg TYPE string
, it_e071 TYPE e071_t
.

SELECT * FROM e071
WHERE trkorr = @i_trkorr
" SCN Version 4 of program (ZS_DECOM_OBSOL_TRANS) had another line here, which is a tiny bug
INTO TABLE @it_e071.

IF sy-subrc <> 0.
" new 18.07.2016: automatically delete empty transport requests
IF delete_empty_task_request_ok( i_trkorr = i_trkorr ).
WRITE |Empty transport request - deleted|.
ELSE.
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4 INTO msg.
WRITE |FAILED deleting empty transport { i_trkorr }: { msg }, subrc { sy-subrc }|.
RETURN.
ENDIF.
RETURN.
ENDIF.

DELETE it_e071 WHERE pgmid = 'CORR'. " ignore log entries CORR RELE and CORR MERG

SORT it_e071 BY pgmid object obj_name objfunc lockflag gennum lang activity.
DELETE ADJACENT DUPLICATES FROM it_e071 COMPARING pgmid object obj_name objfunc lockflag gennum lang activity.

SELECT * FROM e071k
WHERE trkorr = @i_trkorr
INTO TABLE @DATA(it_e071k). "#ec ci_subrc

CALL FUNCTION 'ZS_DEV_APPEND_TO_TR' DESTINATION p_p_sys
EXPORTING
i_it_e071 = it_e071
i_it_e071k = it_e071k
i_trkorr = p_p_tr
IMPORTING
e_wa_return_msg = return_msg
EXCEPTIONS
communication_failure = 1
system_failure = 2.
IF sy-subrc <> 0.
WRITE |Problem with RFC call to system. Sy-subrc = { sy-subrc }|.
EXIT.
ENDIF.

IF return_msg IS NOT INITIAL.
MESSAGE ID return_msg-id TYPE return_msg-type NUMBER return_msg-number
WITH return_msg-message_v1 return_msg-message_v2 return_msg-message_v3 return_msg-message_v4 INTO msg.
WRITE |FAILED: { msg }|.
ELSE.
store_pre_decom_version( it_e071 ).
WRITE |finished|.
ENDIF.

ENDMETHOD.

METHOD prepare_trkorr_ok.
DATA msg TYPE string.
r_is_ok = abap_false.
WRITE / |{ i_trkorr } |.
SELECT SINGLE trfunction, trstatus FROM e070
WHERE trkorr = @i_trkorr
INTO ( @DATA(trfunction), @DATA(trstatus) ).
ASSERT sy-subrc = 0.
IF trfunction NA con_allowed_trfunctions_req.
WRITE |Wrong type: E070-TRFUNCTION = { trfunction }. Allowed: { con_allowed_trfunctions_req }|.
RETURN.
ENDIF.
IF trstatus <> 'D'. " changeable
WRITE |Wrong status: E070-TRSTATUS = { trstatus }. Needs to be D (changeable)|.
RETURN.
ENDIF.
SELECT trkorr, trfunction, trstatus FROM e070
WHERE strkorr = @i_trkorr
ORDER BY trkorr
into table @DATA(it_dependent). "#ec ci_subrc
LOOP AT it_dependent ASSIGNING FIELD-SYMBOL(<dep>).
IF <dep>-trfunction NA con_allowed_trfunctions_task.
WRITE |Wrong type: E070-TRFUNCTION = { <dep>-trfunction } of dependent transport { <dep>-trkorr }. Allowed: { con_allowed_trfunctions_task }|.
RETURN.
ENDIF.

" new 18.07.2016: automatically delete unclassified, empty transport tasks
IF <dep>-trfunction = 'X'. " unclassified
IF NOT delete_empty_task_request_ok( i_trkorr = <dep>-trkorr ).
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4 INTO msg.
WRITE |FAILED deleting empty dependent transport { <dep>-trkorr }: { msg }, subrc { sy-subrc }|.
ENDIF.
CONTINUE.
ENDIF.

CASE <dep>-trstatus.
WHEN 'L' OR 'O'. " changeable locked, or release in progress
WRITE |Wrong status: E070-TRSTATUS = { <dep>-trstatus } of dependent transport { <dep>-trkorr }. Allowed: D, R or N (changeable or released)|.
RETURN.
WHEN 'R' OR 'N'.
CONTINUE.
WHEN 'D'. " changeable
IF release_ok( <dep>-trkorr ).
CONTINUE.
ELSE.
RETURN. " error message has already been written
ENDIF.
WHEN OTHERS.
ASSERT 1 = 0.
ENDCASE.
ENDLOOP.

r_is_ok = prevent_inactive_objects_ok( i_trkorr ).
ENDMETHOD.

METHOD prevent_inactive_objects_ok.
" Warn if there are any inactive objects.
" Return true if everything is active.
DATA: it_log TYPE STANDARD TABLE OF sprot_u
, it_e071 TYPE trwbo_t_e071
, msg_str TYPE string
.
SELECT SINGLE * FROM e070
WHERE trkorr = @i_trkorr
INTO @DATA(e070).
ASSERT sy-subrc = 0.

SELECT * FROM e071
WHERE trkorr = @i_trkorr
INTO CORRESPONDING FIELDS of TABLE @it_e071.
IF sy-subrc = 0.
CALL FUNCTION 'TRINT_CHECK_INACTIVE_OBJECTS'
EXPORTING
is_e070 = e070
it_e071 = it_e071
TABLES
et_log = it_log.
ENDIF.

r_is_ok = abap_true.
LOOP AT it_log ASSIGNING FIELD-SYMBOL(<wa_log>).
IF sy-tabix = 1.
WRITE |Contains inactive objects: |.
ELSE.
WRITE |, |.
ENDIF.
IF <wa_log>-severity = 'E' AND <wa_log>-ag = 'EU' AND <wa_log>-msgnr = 829.
WRITE |{ <wa_log>-var1 } { <wa_log>-var2 }|.
ELSE.
MESSAGE ID <wa_log>-ag TYPE <wa_log>-severity NUMBER <wa_log>-msgnr WITH <wa_log>-var1 <wa_log>-var2 INTO msg_str.
WRITE |"{ msg_str }"|.
ENDIF.
r_is_ok = abap_false.
ENDLOOP.
ENDMETHOD.

METHOD release_ok.
DATA msg TYPE string.
CALL FUNCTION 'TR_RELEASE_REQUEST'
EXPORTING
iv_trkorr = i_trkorr
iv_dialog = abap_false
iv_success_message = abap_false
iv_display_export_log = abap_false
EXCEPTIONS
cts_initialization_failure = 1
enqueue_failed = 2
no_authorization = 3
invalid_request = 4
request_already_released = 5
repeat_too_early = 6
error_in_export_methods = 7
object_check_error = 8
docu_missing = 9
db_access_error = 10
action_aborted_by_user = 11
export_failed = 12
OTHERS = 13.
IF sy-subrc <> 0.
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4 INTO msg.
WRITE |FAILED releasing dependent transport { i_trkorr }: { msg }, subrc { sy-subrc }|.
r_is_ok = abap_false.
ELSE.
r_is_ok = abap_true.
ENDIF.
ENDMETHOD.


METHOD store_pre_decom_version.
" this function module stores a version of all versionable
" objects, as if the current version had just been released
" with a transport request
CALL FUNCTION 'SVRS_STORE_VERSION'
EXPORTING
date = sy-datum
time = sy-uzeit
user = sy-uname
korr = 'PRE_DECOMM'
mode = ''
TABLES
e071_tab = i_it_e071.
ENDMETHOD.

METHOD delete_empty_task_request_ok.
SELECT @abap_true FROM e071
WHERE trkorr = @i_trkorr
INTO @DATA(has_content)
UP TO 1 ROWS.
ENDSELECT.
ASSERT has_content = abap_false.
CALL FUNCTION 'TR_CHANGE_USERNAME'
EXPORTING
wi_dialog = abap_false
wi_trkorr = i_trkorr
wi_user = sy-uname
EXCEPTIONS
already_released = 1
e070_update_error = 2
file_access_error = 3
not_exist_e070 = 4
user_does_not_exist = 5
tr_enqueue_failed = 6
no_authorization = 7
wrong_client = 8
unallowed_user = 9
OTHERS = 10.
IF sy-subrc = 0.
CALL FUNCTION 'TR_DELETE_COMM'
EXPORTING
wi_dialog = abap_false
wi_trkorr = i_trkorr
EXCEPTIONS
file_access_error = 1
order_already_released = 2
order_contains_c_member = 3
order_contains_locked_entries = 4
order_is_refered = 5
repair_order = 6
user_not_owner = 7
delete_was_cancelled = 8
ordernumber_empty = 9
tr_enqueue_failed = 10
objects_free_but_still_locks = 11
order_lock_failed = 12
no_authorization = 13
wrong_client = 14
project_still_referenced = 15
successors_already_released = 16
OTHERS = 17.
ENDIF.
r_is_ok = xsdbool( sy-subrc = 0 ).
ENDMETHOD.

ENDCLASS.

 
* PART 1: Top-Include of Function Pool (Include LZS_DEV_TOOLS_RFCTOP)
FUNCTION-POOL zs_dev_tools_rfc MESSAGE-ID zs_dev_tools.

CLASS lcl DEFINITION FINAL.
PUBLIC SECTION.
CLASS-METHODS:
append_to_tr
IMPORTING i_trkorr TYPE trkorr
i_it_e071 TYPE e071_t
i_it_e071k TYPE e071k_t
EXPORTING e_wa_return_msg TYPE bapiret2.

PRIVATE SECTION.
CLASS-METHODS:
check_transport
IMPORTING i_trkorr TYPE trkorr
EXPORTING e_is_ok TYPE abap_bool
CHANGING c_wa_return_msg TYPE bapiret2,
get_next_as4pos
IMPORTING i_trkorr TYPE trkorr
RETURNING VALUE(r) TYPE e071-as4pos,
determine_as4pos
CHANGING
c_it_e071k TYPE e071k_t.
ENDCLASS.





* PART 2: Function Module (Include LZS_DEV_TOOLS_RFCU01)
* Note that this function module has to be created as an RFC-enabled function
FUNCTION ZS_DEV_APPEND_TO_TR
IMPORTING
VALUE(I_IT_E071) TYPE E071_T
VALUE(I_IT_E071K) TYPE E071K_T
VALUE(I_TRKORR) TYPE TRKORR
EXPORTING
VALUE(E_WA_RETURN_MSG) TYPE BAPIRET2.



lcl=>append_to_tr( EXPORTING i_it_e071 = i_it_e071
i_it_e071k = i_it_e071k
i_trkorr = i_trkorr
IMPORTING e_wa_return_msg = e_wa_return_msg ).

ENDFUNCTION.





* PART 3: Local Class implementation (Include LZS_DEV_TOOLS_RFCP01)
CLASS lcl IMPLEMENTATION.

METHOD append_to_tr.
DATA: it_e071 TYPE e071_t
, it_e071k TYPE e071k_t
, wa_e071 TYPE e071
.
check_transport( EXPORTING i_trkorr = i_trkorr
IMPORTING e_is_ok = DATA(is_ok)
CHANGING c_wa_return_msg = e_wa_return_msg ).
CHECK is_ok = abap_true.

LOOP AT i_it_e071 ASSIGNING FIELD-SYMBOL(<wa_e071>).
MOVE-CORRESPONDING <wa_e071> TO wa_e071.
wa_e071-trkorr = i_trkorr.
wa_e071-as4pos = get_next_as4pos( i_trkorr ).
wa_e071-objfunc = COND #( WHEN wa_e071-objfunc = 'K' THEN 'K' " entry has key records in E071K
ELSE '' ). " other values are only relevant in source system
CLEAR wa_e071-lockflag. " only relevant in source system
APPEND wa_e071 TO it_e071.
ENDLOOP.
INSERT e071 FROM TABLE it_e071.
ASSERT sy-subrc = 0.

it_e071k = VALUE #( FOR wa_e071k IN i_it_e071k ( VALUE #( BASE wa_e071k
trkorr = i_trkorr ) ) ).
determine_as4pos( CHANGING c_it_e071k = it_e071k ).
INSERT e071k FROM TABLE it_e071k.
ASSERT sy-subrc = 0. " there are potential conflicts, if the same e071k entries are added via this method again. The method should not be used like this

COMMIT WORK.

ENDMETHOD.


METHOD check_transport.
e_is_ok = abap_false.
CLEAR c_wa_return_msg.
SELECT SINGLE @abap_true FROM e070 INTO @e_is_ok
WHERE trkorr = @i_trkorr
AND trstatus = 'D'. "#ec ci_subrc D = changeable
IF e_is_ok = abap_false.
MESSAGE e001 WITH i_trkorr INTO c_wa_return_msg-message. " transport not found or not suitable
c_wa_return_msg-type = sy-msgty.
c_wa_return_msg-id = sy-msgid.
c_wa_return_msg-number = sy-msgno.
c_wa_return_msg-message_v1 = sy-msgv1.
c_wa_return_msg-message_v2 = sy-msgv2.
c_wa_return_msg-message_v3 = sy-msgv3.
c_wa_return_msg-message_v4 = sy-msgv4.
ENDIF.
ENDMETHOD.


METHOD get_next_as4pos.
STATICS: last_trkorr TYPE trkorr
, last_as4pos TYPE e071-as4pos
.
IF last_trkorr <> i_trkorr.
SELECT MAX( as4pos ) FROM e071
WHERE trkorr = @i_trkorr
INTO @last_as4pos.
ASSERT sy-subrc = 0. " always 0 for aggregate functions
last_trkorr = i_trkorr.
ENDIF.
r = last_as4pos + 1.
last_as4pos = r.
ENDMETHOD.


METHOD determine_as4pos.
LOOP AT c_it_e071k INTO DATA(wa_temp) GROUP BY ( trkorr = wa_temp-trkorr
pgmid = wa_temp-pgmid
object = wa_temp-object
objname = wa_temp-objname )
ASSIGNING FIELD-SYMBOL(<group>).
SELECT MAX( as4pos ) FROM e071k
WHERE trkorr = @<group>-trkorr
AND pgmid = @<group>-pgmid
AND object = @<group>-object
AND objname = @<group>-objname
INTO @DATA(last_as4pos).
ASSERT sy-subrc = 0. " always 0 for aggregate functions
LOOP AT GROUP <group> ASSIGNING FIELD-SYMBOL(<wa_e071k>).
<wa_e071k>-as4pos = last_as4pos + 1.
last_as4pos = <wa_e071k>-as4pos.
ENDLOOP.
ENDLOOP.
ENDMETHOD.

ENDCLASS.



20 Comments