" TARGET OBJECT : class ZZBP_R_CHANGEAPPROVAL000 - LOCAL TYPES include (Local Types tab in ADT) " abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Source%20Code%20Library/Classes/ZZBP_R_CHANGEAPPROVAL000/zzbp_r_changeapproval000.clas.locals_imp.abap " ACTION : replace entire content, then activate the class (BDEF ZZR_CHANGEAPPROVAL000 must be active first). " " Sources (all verified 2026-09-15): " handler class inherits from CL_ABAP_BEHAVIOR_HANDLER (released) [sap-released-objects public_cloud A] " FOR INSTANCE FEATURES signature + result via %tky / %features-%action- / if_abap_behv=>fc-o-enabled|disabled " [abap-docs /abap-docs-cloud/ABAPHANDLER_METH_FEATURES, ABAPDERIVED_TYPES_FEATURES, ABENBDL_ACTIONS_FC] " FOR MODIFY ... FOR ACTION with RESULT; %param carries the action parameter (keys) and the result ($self) " [abap-docs /abap-docs-cloud/ABENBDL_ACTION2_ABEXA, ABAPDERIVED_TYPES_PARAM] " READ ENTITIES ... IN LOCAL MODE ... FIELDS ( ) WITH CORRESPONDING #( keys ) RESULT ... FAILED failed " [abap-docs /abap-docs-cloud/ABENBDL_ACTIONS_FC example] " MODIFY ENTITIES ... IN LOCAL MODE (skips feature control and authorization) with UPDATE FIELDS " [abap-docs /abap-docs-cloud/ABAPMODIFY_ENTITY_ENTITIES, ABAPMODIFY_ENTITY_ENTITIES_OP, ABENBDL_AUTHORIZATION] " %msg = new_message_with_text( ... ) into reported [abap-docs /abap-docs-cloud/ABENBDL_ACTIONS_FC example] " FOR DETERMINE ON MODIFY signature [abap-docs /abap-docs-cloud/ABAPHANDLER_METH_DET] " utclong_current( ) [abap-docs /abap-docs-cloud/ABENUTCLONG_CURRENT] " cl_abap_context_info=>get_user_technical_name( ) (released class) [sap-docs sap-help loio 7be24435b3e24540a9edad9a2535175a; " sap-released-objects CL_ABAP_CONTEXT_INFO public_cloud A] " Design notes: " - Status transitions are enforced twice: get_instance_features disables the action (UI greys it out / EML fails " with an entry in failed [ABENBDL_ACTIONS_FC]) AND each action re-checks the current status so an EML caller " IN LOCAL MODE cannot skip the state machine. " - The six actions repeat the same read/check/update/read pattern on purpose: each action has its own derived " key type, so a generic helper would need additional derived-type plumbing that is not worth it for six methods. " - RequestedBy is the technical user of the creator (for the A2X flow: the worker's communication user). " If the Arena requester should be shown instead, make RequestedBy mandatory:create in the BDEF and drop it " from the determination. CLASS lhc_changeapproval DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. CONSTANTS: BEGIN OF status, pending TYPE zzchngpproval000-status VALUE 'PENDING', approved TYPE zzchngpproval000-status VALUE 'APPROVED', executing TYPE zzchngpproval000-status VALUE 'EXECUTING', synced TYPE zzchngpproval000-status VALUE 'SYNCED', rejected TYPE zzchngpproval000-status VALUE 'REJECTED', failed TYPE zzchngpproval000-status VALUE 'FAILED', END OF status. METHODS get_instance_features FOR INSTANCE FEATURES IMPORTING keys REQUEST requested_features FOR ChangeApproval RESULT result. METHODS approve FOR MODIFY IMPORTING keys FOR ACTION ChangeApproval~approve RESULT result. METHODS reject FOR MODIFY IMPORTING keys FOR ACTION ChangeApproval~reject RESULT result. METHODS retrySync FOR MODIFY IMPORTING keys FOR ACTION ChangeApproval~retrySync RESULT result. METHODS markExecuting FOR MODIFY IMPORTING keys FOR ACTION ChangeApproval~markExecuting RESULT result. METHODS markSynced FOR MODIFY IMPORTING keys FOR ACTION ChangeApproval~markSynced RESULT result. METHODS markFailed FOR MODIFY IMPORTING keys FOR ACTION ChangeApproval~markFailed RESULT result. METHODS setInitialStatus FOR DETERMINE ON MODIFY IMPORTING keys FOR ChangeApproval~setInitialStatus. ENDCLASS. CLASS lhc_changeapproval IMPLEMENTATION. METHOD get_instance_features. READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals) FAILED failed. result = VALUE #( FOR approval IN approvals ( %tky = approval-%tky %features-%action-approve = COND #( WHEN approval-Status = status-pending THEN if_abap_behv=>fc-o-enabled ELSE if_abap_behv=>fc-o-disabled ) %features-%action-reject = COND #( WHEN approval-Status = status-pending THEN if_abap_behv=>fc-o-enabled ELSE if_abap_behv=>fc-o-disabled ) %features-%action-retrySync = COND #( WHEN approval-Status = status-failed THEN if_abap_behv=>fc-o-enabled ELSE if_abap_behv=>fc-o-disabled ) %features-%action-markExecuting = COND #( WHEN approval-Status = status-approved THEN if_abap_behv=>fc-o-enabled ELSE if_abap_behv=>fc-o-disabled ) %features-%action-markSynced = COND #( WHEN approval-Status = status-executing THEN if_abap_behv=>fc-o-enabled ELSE if_abap_behv=>fc-o-disabled ) %features-%action-markFailed = COND #( WHEN approval-Status = status-executing THEN if_abap_behv=>fc-o-enabled ELSE if_abap_behv=>fc-o-disabled ) ) ). ENDMETHOD. METHOD approve. " PENDING -> APPROVED; records the decision, never writes SapChangeNumber (PRD RAP-1) READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals) FAILED failed. LOOP AT keys INTO DATA(key). ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(). IF sy-subrc <> 0. CONTINUE. ENDIF. IF -Status <> status-pending. APPEND VALUE #( %tky = -%tky ) TO failed-changeapproval. APPEND VALUE #( %tky = -%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Only PENDING records can be approved (status is { -Status })| ) ) TO reported-changeapproval. CONTINUE. ENDIF. MODIFY ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval UPDATE FIELDS ( Status DecidedBy DecidedOn ) WITH VALUE #( ( %tky = -%tky Status = status-approved DecidedBy = cl_abap_context_info=>get_user_technical_name( ) DecidedOn = utclong_current( ) ) ) FAILED DATA(update_failed) REPORTED DATA(update_reported). ENDLOOP. READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(updated). result = VALUE #( FOR row IN updated ( %tky = row-%tky %param = row ) ). ENDMETHOD. METHOD reject. " PENDING -> REJECTED with a mandatory reason (PRD RAP-2); REJECTED is terminal READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals) FAILED failed. LOOP AT keys INTO DATA(key). ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(). IF sy-subrc <> 0. CONTINUE. ENDIF. IF -Status <> status-pending. APPEND VALUE #( %tky = -%tky ) TO failed-changeapproval. APPEND VALUE #( %tky = -%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Only PENDING records can be rejected (status is { -Status })| ) ) TO reported-changeapproval. CONTINUE. ENDIF. IF key-%param-RejectReason IS INITIAL. APPEND VALUE #( %tky = -%tky ) TO failed-changeapproval. APPEND VALUE #( %tky = -%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'A reject reason is required' ) ) TO reported-changeapproval. CONTINUE. ENDIF. MODIFY ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval UPDATE FIELDS ( Status RejectReason DecidedBy DecidedOn ) WITH VALUE #( ( %tky = -%tky Status = status-rejected RejectReason = key-%param-RejectReason DecidedBy = cl_abap_context_info=>get_user_technical_name( ) DecidedOn = utclong_current( ) ) ) FAILED DATA(update_failed) REPORTED DATA(update_reported). ENDLOOP. READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(updated). result = VALUE #( FOR row IN updated ( %tky = row-%tky %param = row ) ). ENDMETHOD. METHOD retrySync. " FAILED -> EXECUTING; clears the previous error so the worker re-executes the staged payload (ARN-002 amendment) READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals) FAILED failed. LOOP AT keys INTO DATA(key). ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(). IF sy-subrc <> 0. CONTINUE. ENDIF. IF -Status <> status-failed. APPEND VALUE #( %tky = -%tky ) TO failed-changeapproval. APPEND VALUE #( %tky = -%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Only FAILED records can be retried (status is { -Status })| ) ) TO reported-changeapproval. CONTINUE. ENDIF. MODIFY ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval UPDATE FIELDS ( Status ErrorMessage ) WITH VALUE #( ( %tky = -%tky Status = status-executing ErrorMessage = '' ) ) FAILED DATA(update_failed) REPORTED DATA(update_reported). ENDLOOP. READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(updated). result = VALUE #( FOR row IN updated ( %tky = row-%tky %param = row ) ). ENDMETHOD. METHOD markExecuting. " APPROVED -> EXECUTING (worker claims the record; the UI never offers Approve on an executing record, PRD RAP-6) READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals) FAILED failed. LOOP AT keys INTO DATA(key). ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(). IF sy-subrc <> 0. CONTINUE. ENDIF. IF -Status <> status-approved. APPEND VALUE #( %tky = -%tky ) TO failed-changeapproval. APPEND VALUE #( %tky = -%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Only APPROVED records can start executing (status is { -Status })| ) ) TO reported-changeapproval. CONTINUE. ENDIF. MODIFY ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval UPDATE FIELDS ( Status ) WITH VALUE #( ( %tky = -%tky Status = status-executing ) ) FAILED DATA(update_failed) REPORTED DATA(update_reported). ENDLOOP. READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(updated). result = VALUE #( FOR row IN updated ( %tky = row-%tky %param = row ) ). ENDMETHOD. METHOD markSynced. " EXECUTING -> SYNCED (terminal success); SAP is the ledger, the change number is the receipt (ARN-006) READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals) FAILED failed. LOOP AT keys INTO DATA(key). ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(). IF sy-subrc <> 0. CONTINUE. ENDIF. IF -Status <> status-executing. APPEND VALUE #( %tky = -%tky ) TO failed-changeapproval. APPEND VALUE #( %tky = -%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Only EXECUTING records can be marked synced (status is { -Status })| ) ) TO reported-changeapproval. CONTINUE. ENDIF. IF key-%param-SapChangeNumber IS INITIAL. APPEND VALUE #( %tky = -%tky ) TO failed-changeapproval. APPEND VALUE #( %tky = -%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'The SAP change number is required to mark a record synced' ) ) TO reported-changeapproval. CONTINUE. ENDIF. MODIFY ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval UPDATE FIELDS ( Status SapChangeNumber ErrorMessage ) WITH VALUE #( ( %tky = -%tky Status = status-synced SapChangeNumber = key-%param-SapChangeNumber ErrorMessage = '' ) ) FAILED DATA(update_failed) REPORTED DATA(update_reported). ENDLOOP. READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(updated). result = VALUE #( FOR row IN updated ( %tky = row-%tky %param = row ) ). ENDMETHOD. METHOD markFailed. " EXECUTING -> FAILED with the error text; distinct from REJECTED (predecessor defect, ARN-002) READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals) FAILED failed. LOOP AT keys INTO DATA(key). ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(). IF sy-subrc <> 0. CONTINUE. ENDIF. IF -Status <> status-executing. APPEND VALUE #( %tky = -%tky ) TO failed-changeapproval. APPEND VALUE #( %tky = -%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Only EXECUTING records can be marked failed (status is { -Status })| ) ) TO reported-changeapproval. CONTINUE. ENDIF. IF key-%param-ErrorMessage IS INITIAL. APPEND VALUE #( %tky = -%tky ) TO failed-changeapproval. APPEND VALUE #( %tky = -%tky %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'An error message is required to mark a record failed' ) ) TO reported-changeapproval. CONTINUE. ENDIF. MODIFY ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval UPDATE FIELDS ( Status ErrorMessage ) WITH VALUE #( ( %tky = -%tky Status = status-failed ErrorMessage = key-%param-ErrorMessage ) ) FAILED DATA(update_failed) REPORTED DATA(update_reported). ENDLOOP. READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(updated). result = VALUE #( FOR row IN updated ( %tky = row-%tky %param = row ) ). ENDMETHOD. METHOD setInitialStatus. " Every created record starts PENDING with the requester audit fields (ARN-002) READ ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals). DELETE approvals WHERE Status IS NOT INITIAL. IF approvals IS INITIAL. RETURN. ENDIF. MODIFY ENTITIES OF zzr_changeapproval000 IN LOCAL MODE ENTITY ChangeApproval UPDATE FIELDS ( Status RequestedBy RequestedOn ) WITH VALUE #( FOR approval IN approvals ( %tky = approval-%tky Status = status-pending RequestedBy = cl_abap_context_info=>get_user_technical_name( ) RequestedOn = utclong_current( ) ) ) REPORTED DATA(update_reported). reported = CORRESPONDING #( DEEP update_reported ). ENDMETHOD. ENDCLASS.