" TARGET OBJECT : class ZBP_R_CHANGEAPPROVAL - LOCAL TYPES include (Local Types tab in ADT) - README step U7, UI leg " abap: URI : abap:/repotree-v1/0M7DEJI/System%20Library/ZLOCAL/ZARENA_PLM/Source%20Code%20Library/Classes/ZBP_R_CHANGEAPPROVAL/zbp_r_changeapproval.clas.locals_imp.abap " ACTION : replace entire content, save, then activate the class (README U7; BDEF ZR_CHANGEAPPROVAL must be " active first). Activation goes through the class URI (.clas.abap), not this include. " " Sources (verified 2026-09-15 unless dated otherwise): " handler class inherits from CL_ABAP_BEHAVIOR_HANDLER (released) [sap-released-objects public_cloud A, verified 2026-09-16] " 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 " [abap-docs /abap-docs-cloud/ABENBDL_ACTIONS_FC example] " MODIFY ENTITIES ... IN LOCAL MODE (skips feature control and authorization) with UPDATE FIELDS ( ) WITH " [abap-docs /abap-docs-cloud/ABAPMODIFY_ENTITY_ENTITIES, ABAPMODIFY_ENTITY_ENTITIES_OP, ABENBDL_AUTHORIZATION; " sap-docs sap-help loio e75275489fe648f6b00977be76f0bc06 (deductDiscount: collect valid keys, update, read, result), verified 2026-09-16] " 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, verified 2026-09-16] " if_abap_behv / if_abap_behv_message are released [sap-released-objects INTF public_cloud A, verified 2026-09-16] " RAP action implementation contract (verified 2026-09-16): " - an unsuccessful action returns one row per failed input identifier in failed "with the correct fail cause and " flags the %action component on the correct action"; for every failed identifier with a cause other than " not_found there is one error message in reported [sap-docs sap-help loio de6569d4b92e40a0911c926170140beb] " - for fail causes other than not_found/locked the failed operation in %op must be provided; reported always fills " %op; result contains only input identifiers not in failed, without duplicates " [sap-docs sap-help loio 1040b81372d44ed38b07a409fa0e1769] " - %op groups the operation flags incl. %action [abap-docs /abap-docs-cloud/ABAPDERIVED_TYPES_OP]; %fail-cause values " are defined in IF_ABAP_BEHV [abap-docs /abap-docs-cloud/ABAPDERIVED_TYPES_FAIL]; %op-%action- = mk-on in " reported [sap-docs loio e75275489fe648f6b00977be76f0bc06] " - allowed fail causes: actions any cause except if_abap_behv=>cause-locked; validations on save only " if_abap_behv=>cause-unspecific; with a non-initial %state_area only %element may be flagged in %op " [sap-docs sap-help abaprap_impl_rules "RAP Implementation Rules"] " - strict(2) turns RAP BO contract violations into runtime errors [abap-docs /abap-docs-cloud/ABENBDL_STRICT_2] " - merging a MODIFY response: CORRESPONDING #( DEEP APPENDING BASE ( reported ) ... ). DEEP alone replaces a nested " table such as reported-changeapproval and would drop the messages appended earlier in the handler; DEEP APPENDING " BASE "ensures that the nested target tables are not deleted" and "appends the lines of the nested source tables" " [abap-docs /abap-docs-cloud/ABENCORRESPONDING_CONSTR_ARG_TYPE, ABENNEWS-783-ASSIGNMENTS, verified 2026-09-16] " Validation (verified 2026-09-16): " - METHODS meth FOR VALIDATE ON SAVE IMPORTING keys FOR bdef~val; failed/reported implicit " [abap-docs /abap-docs-cloud/ABAPHANDLER_METH_VALIDATE]; no EML MODIFY in validations; a failing validation " rejects the whole transactional buffer [abap-docs /abap-docs-cloud/ABENBDL_VALIDATIONS] " - state message pattern: invalidate %tky + %state_area first, then fail + message with %state_area and %element " [abap-docs /abap-docs-cloud/ABAPDERIVED_TYPES_STATE_AREA, ABENBDL_VALIDATIONS example] " - a SELECT on an access-controlled CDS view without OPTIONS PRIVILEGED ACCESS inside a RAP handler is a RAP BO " contract violation [sap-docs sap-help abaprap_impl_rules "Non-privileged access"]; PRIVILEGED ACCESS disables CDS " access control for the SELECT, OPTIONS goes at the end of the statement " [abap-docs /abap-docs-cloud/ABAPSELECT_PRIVILEGED_ACCESS, ABAPSELECT_OPTIONS] " Design notes: " - State machine (PRD ARN-002; retrySync decision 2026-09-16): create -> PENDING; approve PENDING -> APPROVED; " reject PENDING -> REJECTED (terminal); markExecuting APPROVED -> EXECUTING; markSynced EXECUTING -> SYNCED " (terminal); markFailed EXECUTING -> FAILED; retrySync FAILED -> APPROVED. Only markExecuting writes EXECUTING, so a " retried record is re-claimed by the worker through markExecuting and cannot be executed twice in parallel. " - 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 flags its own " %op-%action- component, so a generic helper would need additional derived-type plumbing. " - A failed UPDATE with cause locked is reported as unspecific, because locked is not an allowed cause for actions. " - 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. " - checkDuplicate: the SELECT only sees persisted rows (a validation runs in the save sequence before the new " instances are written), so duplicates inside the same request are checked separately on the buffer data. " UNVERIFIED 2026-09-16: the list of SELECT additions that cannot be combined with PRIVILEGED ACCESS was not " retrievable; this uses SELECT SINGLE without FOR ALL ENTRIES. The syntax check at U7 activation is the proof. " abaplint (abap-docs abap_lint, 2026-09-16) does not parse `OPTIONS PRIVILEGED ACCESS` but parses the FROM-clause " form. If the tenant syntax check refuses the OPTIONS form, use the equally documented " `SELECT SINGLE FROM zr_changeapproval WITH PRIVILEGED ACCESS FIELDS ID WHERE ... INTO @DATA(existing_id).` " (affects only that entity; not combinable with OPTIONS PRIVILEGED ACCESS) " [source: abap-docs /abap-docs-cloud/ABAPSELECT_DATA_SOURCE, verified 2026-09-16]. CLASS lhc_changeapproval DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. CONSTANTS: BEGIN OF status, pending TYPE zchangeapproval-status VALUE 'PENDING', approved TYPE zchangeapproval-status VALUE 'APPROVED', executing TYPE zchangeapproval-status VALUE 'EXECUTING', synced TYPE zchangeapproval-status VALUE 'SYNCED', rejected TYPE zchangeapproval-status VALUE 'REJECTED', failed TYPE zchangeapproval-status VALUE 'FAILED', END OF status. CONSTANTS state_area_duplicate TYPE string VALUE `CHECK_DUPLICATE`. 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. METHODS checkDuplicate FOR VALIDATE ON SAVE IMPORTING keys FOR ChangeApproval~checkDuplicate. ENDCLASS. CLASS lhc_changeapproval IMPLEMENTATION. METHOD get_instance_features. READ ENTITIES OF zr_changeapproval 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) DATA approvals_for_update TYPE TABLE FOR UPDATE zr_changeapproval. READ ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals). LOOP AT keys INTO DATA(key). ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(). IF sy-subrc <> 0. APPEND VALUE #( %tky = key-%tky %fail-cause = if_abap_behv=>cause-not_found %op-%action-approve = if_abap_behv=>mk-on ) TO failed-changeapproval. CONTINUE. ENDIF. IF -Status <> status-pending. APPEND VALUE #( %tky = key-%tky %fail-cause = if_abap_behv=>cause-unspecific %op-%action-approve = if_abap_behv=>mk-on ) TO failed-changeapproval. APPEND VALUE #( %tky = key-%tky %op-%action-approve = if_abap_behv=>mk-on %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. APPEND VALUE #( %tky = key-%tky Status = status-approved DecidedBy = cl_abap_context_info=>get_user_technical_name( ) DecidedOn = utclong_current( ) ) TO approvals_for_update. ENDLOOP. IF approvals_for_update IS NOT INITIAL. MODIFY ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval UPDATE FIELDS ( Status DecidedBy DecidedOn ) WITH approvals_for_update FAILED DATA(update_failed) REPORTED DATA(update_reported). " Propagate the internal UPDATE response into the action response LOOP AT update_reported-changeapproval ASSIGNING FIELD-SYMBOL(). -%op-%action-approve = if_abap_behv=>mk-on. ENDLOOP. reported = CORRESPONDING #( DEEP APPENDING BASE ( reported ) update_reported ). LOOP AT update_failed-changeapproval INTO DATA(update_failure). APPEND VALUE #( %tky = update_failure-%tky %fail-cause = COND #( WHEN update_failure-%fail-cause = if_abap_behv=>cause-locked THEN if_abap_behv=>cause-unspecific ELSE update_failure-%fail-cause ) %op-%action-approve = if_abap_behv=>mk-on ) TO failed-changeapproval. IF update_failure-%fail-cause <> if_abap_behv=>cause-not_found AND NOT line_exists( update_reported-changeapproval[ ID = update_failure-ID ] ). APPEND VALUE #( %tky = update_failure-%tky %op-%action-approve = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'The approval could not be recorded' ) ) TO reported-changeapproval. ENDIF. ENDLOOP. ENDIF. READ ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(updated). LOOP AT updated INTO DATA(row). IF NOT line_exists( failed-changeapproval[ ID = row-ID ] ). APPEND VALUE #( %tky = row-%tky %param = row ) TO result. ENDIF. ENDLOOP. ENDMETHOD. METHOD reject. " PENDING -> REJECTED with a mandatory reason (PRD RAP-2); REJECTED is terminal DATA approvals_for_update TYPE TABLE FOR UPDATE zr_changeapproval. READ ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals). LOOP AT keys INTO DATA(key). ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(). IF sy-subrc <> 0. APPEND VALUE #( %tky = key-%tky %fail-cause = if_abap_behv=>cause-not_found %op-%action-reject = if_abap_behv=>mk-on ) TO failed-changeapproval. CONTINUE. ENDIF. IF -Status <> status-pending. APPEND VALUE #( %tky = key-%tky %fail-cause = if_abap_behv=>cause-unspecific %op-%action-reject = if_abap_behv=>mk-on ) TO failed-changeapproval. APPEND VALUE #( %tky = key-%tky %op-%action-reject = if_abap_behv=>mk-on %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 = key-%tky %fail-cause = if_abap_behv=>cause-unspecific %op-%action-reject = if_abap_behv=>mk-on ) TO failed-changeapproval. APPEND VALUE #( %tky = key-%tky %op-%action-reject = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'A reject reason is required' ) ) TO reported-changeapproval. CONTINUE. ENDIF. APPEND VALUE #( %tky = key-%tky Status = status-rejected RejectReason = key-%param-RejectReason DecidedBy = cl_abap_context_info=>get_user_technical_name( ) DecidedOn = utclong_current( ) ) TO approvals_for_update. ENDLOOP. IF approvals_for_update IS NOT INITIAL. MODIFY ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval UPDATE FIELDS ( Status RejectReason DecidedBy DecidedOn ) WITH approvals_for_update FAILED DATA(update_failed) REPORTED DATA(update_reported). LOOP AT update_reported-changeapproval ASSIGNING FIELD-SYMBOL(). -%op-%action-reject = if_abap_behv=>mk-on. ENDLOOP. reported = CORRESPONDING #( DEEP APPENDING BASE ( reported ) update_reported ). LOOP AT update_failed-changeapproval INTO DATA(update_failure). APPEND VALUE #( %tky = update_failure-%tky %fail-cause = COND #( WHEN update_failure-%fail-cause = if_abap_behv=>cause-locked THEN if_abap_behv=>cause-unspecific ELSE update_failure-%fail-cause ) %op-%action-reject = if_abap_behv=>mk-on ) TO failed-changeapproval. IF update_failure-%fail-cause <> if_abap_behv=>cause-not_found AND NOT line_exists( update_reported-changeapproval[ ID = update_failure-ID ] ). APPEND VALUE #( %tky = update_failure-%tky %op-%action-reject = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'The rejection could not be recorded' ) ) TO reported-changeapproval. ENDIF. ENDLOOP. ENDIF. READ ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(updated). LOOP AT updated INTO DATA(row). IF NOT line_exists( failed-changeapproval[ ID = row-ID ] ). APPEND VALUE #( %tky = row-%tky %param = row ) TO result. ENDIF. ENDLOOP. ENDMETHOD. METHOD retrySync. " FAILED -> APPROVED (decision 2026-09-16): clears the previous error and keeps DecidedBy/DecidedOn of the original " approval. The worker re-claims the record through markExecuting (APPROVED -> EXECUTING) and re-executes the staged " payload (ARN-002 amendment, ARN-010). retrySync never writes EXECUTING itself. DATA approvals_for_update TYPE TABLE FOR UPDATE zr_changeapproval. READ ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals). LOOP AT keys INTO DATA(key). ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(). IF sy-subrc <> 0. APPEND VALUE #( %tky = key-%tky %fail-cause = if_abap_behv=>cause-not_found %op-%action-retrySync = if_abap_behv=>mk-on ) TO failed-changeapproval. CONTINUE. ENDIF. IF -Status <> status-failed. APPEND VALUE #( %tky = key-%tky %fail-cause = if_abap_behv=>cause-unspecific %op-%action-retrySync = if_abap_behv=>mk-on ) TO failed-changeapproval. APPEND VALUE #( %tky = key-%tky %op-%action-retrySync = if_abap_behv=>mk-on %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. APPEND VALUE #( %tky = key-%tky Status = status-approved ErrorMessage = '' ) TO approvals_for_update. ENDLOOP. IF approvals_for_update IS NOT INITIAL. MODIFY ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval UPDATE FIELDS ( Status ErrorMessage ) WITH approvals_for_update FAILED DATA(update_failed) REPORTED DATA(update_reported). LOOP AT update_reported-changeapproval ASSIGNING FIELD-SYMBOL(). -%op-%action-retrySync = if_abap_behv=>mk-on. ENDLOOP. reported = CORRESPONDING #( DEEP APPENDING BASE ( reported ) update_reported ). LOOP AT update_failed-changeapproval INTO DATA(update_failure). APPEND VALUE #( %tky = update_failure-%tky %fail-cause = COND #( WHEN update_failure-%fail-cause = if_abap_behv=>cause-locked THEN if_abap_behv=>cause-unspecific ELSE update_failure-%fail-cause ) %op-%action-retrySync = if_abap_behv=>mk-on ) TO failed-changeapproval. IF update_failure-%fail-cause <> if_abap_behv=>cause-not_found AND NOT line_exists( update_reported-changeapproval[ ID = update_failure-ID ] ). APPEND VALUE #( %tky = update_failure-%tky %op-%action-retrySync = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'The retry could not be recorded' ) ) TO reported-changeapproval. ENDIF. ENDLOOP. ENDIF. READ ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(updated). LOOP AT updated INTO DATA(row). IF NOT line_exists( failed-changeapproval[ ID = row-ID ] ). APPEND VALUE #( %tky = row-%tky %param = row ) TO result. ENDIF. ENDLOOP. ENDMETHOD. METHOD markExecuting. " APPROVED -> EXECUTING (worker claims the record; the UI never offers Approve on an executing record, PRD RAP-6). " The only place that writes EXECUTING. DATA approvals_for_update TYPE TABLE FOR UPDATE zr_changeapproval. READ ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals). LOOP AT keys INTO DATA(key). ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(). IF sy-subrc <> 0. APPEND VALUE #( %tky = key-%tky %fail-cause = if_abap_behv=>cause-not_found %op-%action-markExecuting = if_abap_behv=>mk-on ) TO failed-changeapproval. CONTINUE. ENDIF. IF -Status <> status-approved. APPEND VALUE #( %tky = key-%tky %fail-cause = if_abap_behv=>cause-unspecific %op-%action-markExecuting = if_abap_behv=>mk-on ) TO failed-changeapproval. APPEND VALUE #( %tky = key-%tky %op-%action-markExecuting = if_abap_behv=>mk-on %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. APPEND VALUE #( %tky = key-%tky Status = status-executing ) TO approvals_for_update. ENDLOOP. IF approvals_for_update IS NOT INITIAL. MODIFY ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval UPDATE FIELDS ( Status ) WITH approvals_for_update FAILED DATA(update_failed) REPORTED DATA(update_reported). LOOP AT update_reported-changeapproval ASSIGNING FIELD-SYMBOL(). -%op-%action-markExecuting = if_abap_behv=>mk-on. ENDLOOP. reported = CORRESPONDING #( DEEP APPENDING BASE ( reported ) update_reported ). LOOP AT update_failed-changeapproval INTO DATA(update_failure). APPEND VALUE #( %tky = update_failure-%tky %fail-cause = COND #( WHEN update_failure-%fail-cause = if_abap_behv=>cause-locked THEN if_abap_behv=>cause-unspecific ELSE update_failure-%fail-cause ) %op-%action-markExecuting = if_abap_behv=>mk-on ) TO failed-changeapproval. IF update_failure-%fail-cause <> if_abap_behv=>cause-not_found AND NOT line_exists( update_reported-changeapproval[ ID = update_failure-ID ] ). APPEND VALUE #( %tky = update_failure-%tky %op-%action-markExecuting = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'The execution start could not be recorded' ) ) TO reported-changeapproval. ENDIF. ENDLOOP. ENDIF. READ ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(updated). LOOP AT updated INTO DATA(row). IF NOT line_exists( failed-changeapproval[ ID = row-ID ] ). APPEND VALUE #( %tky = row-%tky %param = row ) TO result. ENDIF. ENDLOOP. ENDMETHOD. METHOD markSynced. " EXECUTING -> SYNCED (terminal success); SAP is the ledger, the change number is the receipt (ARN-006) DATA approvals_for_update TYPE TABLE FOR UPDATE zr_changeapproval. READ ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals). LOOP AT keys INTO DATA(key). ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(). IF sy-subrc <> 0. APPEND VALUE #( %tky = key-%tky %fail-cause = if_abap_behv=>cause-not_found %op-%action-markSynced = if_abap_behv=>mk-on ) TO failed-changeapproval. CONTINUE. ENDIF. IF -Status <> status-executing. APPEND VALUE #( %tky = key-%tky %fail-cause = if_abap_behv=>cause-unspecific %op-%action-markSynced = if_abap_behv=>mk-on ) TO failed-changeapproval. APPEND VALUE #( %tky = key-%tky %op-%action-markSynced = if_abap_behv=>mk-on %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 = key-%tky %fail-cause = if_abap_behv=>cause-unspecific %op-%action-markSynced = if_abap_behv=>mk-on ) TO failed-changeapproval. APPEND VALUE #( %tky = key-%tky %op-%action-markSynced = if_abap_behv=>mk-on %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. APPEND VALUE #( %tky = key-%tky Status = status-synced SapChangeNumber = key-%param-SapChangeNumber ErrorMessage = '' ) TO approvals_for_update. ENDLOOP. IF approvals_for_update IS NOT INITIAL. MODIFY ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval UPDATE FIELDS ( Status SapChangeNumber ErrorMessage ) WITH approvals_for_update FAILED DATA(update_failed) REPORTED DATA(update_reported). LOOP AT update_reported-changeapproval ASSIGNING FIELD-SYMBOL(). -%op-%action-markSynced = if_abap_behv=>mk-on. ENDLOOP. reported = CORRESPONDING #( DEEP APPENDING BASE ( reported ) update_reported ). LOOP AT update_failed-changeapproval INTO DATA(update_failure). APPEND VALUE #( %tky = update_failure-%tky %fail-cause = COND #( WHEN update_failure-%fail-cause = if_abap_behv=>cause-locked THEN if_abap_behv=>cause-unspecific ELSE update_failure-%fail-cause ) %op-%action-markSynced = if_abap_behv=>mk-on ) TO failed-changeapproval. IF update_failure-%fail-cause <> if_abap_behv=>cause-not_found AND NOT line_exists( update_reported-changeapproval[ ID = update_failure-ID ] ). APPEND VALUE #( %tky = update_failure-%tky %op-%action-markSynced = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'The sync result could not be recorded' ) ) TO reported-changeapproval. ENDIF. ENDLOOP. ENDIF. READ ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(updated). LOOP AT updated INTO DATA(row). IF NOT line_exists( failed-changeapproval[ ID = row-ID ] ). APPEND VALUE #( %tky = row-%tky %param = row ) TO result. ENDIF. ENDLOOP. ENDMETHOD. METHOD markFailed. " EXECUTING -> FAILED with the error text; distinct from REJECTED (predecessor defect, ARN-002) DATA approvals_for_update TYPE TABLE FOR UPDATE zr_changeapproval. READ ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval FIELDS ( Status ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals). LOOP AT keys INTO DATA(key). ASSIGN approvals[ ID = key-ID ] TO FIELD-SYMBOL(). IF sy-subrc <> 0. APPEND VALUE #( %tky = key-%tky %fail-cause = if_abap_behv=>cause-not_found %op-%action-markFailed = if_abap_behv=>mk-on ) TO failed-changeapproval. CONTINUE. ENDIF. IF -Status <> status-executing. APPEND VALUE #( %tky = key-%tky %fail-cause = if_abap_behv=>cause-unspecific %op-%action-markFailed = if_abap_behv=>mk-on ) TO failed-changeapproval. APPEND VALUE #( %tky = key-%tky %op-%action-markFailed = if_abap_behv=>mk-on %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 = key-%tky %fail-cause = if_abap_behv=>cause-unspecific %op-%action-markFailed = if_abap_behv=>mk-on ) TO failed-changeapproval. APPEND VALUE #( %tky = key-%tky %op-%action-markFailed = if_abap_behv=>mk-on %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. APPEND VALUE #( %tky = key-%tky Status = status-failed ErrorMessage = key-%param-ErrorMessage ) TO approvals_for_update. ENDLOOP. IF approvals_for_update IS NOT INITIAL. MODIFY ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval UPDATE FIELDS ( Status ErrorMessage ) WITH approvals_for_update FAILED DATA(update_failed) REPORTED DATA(update_reported). LOOP AT update_reported-changeapproval ASSIGNING FIELD-SYMBOL(). -%op-%action-markFailed = if_abap_behv=>mk-on. ENDLOOP. reported = CORRESPONDING #( DEEP APPENDING BASE ( reported ) update_reported ). LOOP AT update_failed-changeapproval INTO DATA(update_failure). APPEND VALUE #( %tky = update_failure-%tky %fail-cause = COND #( WHEN update_failure-%fail-cause = if_abap_behv=>cause-locked THEN if_abap_behv=>cause-unspecific ELSE update_failure-%fail-cause ) %op-%action-markFailed = if_abap_behv=>mk-on ) TO failed-changeapproval. IF update_failure-%fail-cause <> if_abap_behv=>cause-not_found AND NOT line_exists( update_reported-changeapproval[ ID = update_failure-ID ] ). APPEND VALUE #( %tky = update_failure-%tky %op-%action-markFailed = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'The failure could not be recorded' ) ) TO reported-changeapproval. ENDIF. ENDLOOP. ENDIF. READ ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval ALL FIELDS WITH CORRESPONDING #( keys ) RESULT DATA(updated). LOOP AT updated INTO DATA(row). IF NOT line_exists( failed-changeapproval[ ID = row-ID ] ). APPEND VALUE #( %tky = row-%tky %param = row ) TO result. ENDIF. ENDLOOP. ENDMETHOD. METHOD setInitialStatus. " Every created record starts PENDING with the requester audit fields (ARN-002) READ ENTITIES OF zr_changeapproval 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 zr_changeapproval 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. METHOD checkDuplicate. " Semantic key ItemNumber + ChangeType + ArenaEcoId (PRD RAP-8) must be unique among records that are not REJECTED. " Checks (1) duplicates among the instances of this request and (2) persisted records, read through the CDS view " with privileged access (released access only; the view is access-controlled, see header sources). READ ENTITIES OF zr_changeapproval IN LOCAL MODE ENTITY ChangeApproval FIELDS ( ItemNumber ChangeType ArenaEcoId ) WITH CORRESPONDING #( keys ) RESULT DATA(approvals). LOOP AT approvals INTO DATA(approval). " Invalidate a state message left by an earlier run of this validation for the instance APPEND VALUE #( %tky = approval-%tky %state_area = state_area_duplicate ) TO reported-changeapproval. DATA(same_key_in_request) = REDUCE i( INIT count = 0 FOR other IN approvals WHERE ( ItemNumber = approval-ItemNumber AND ChangeType = approval-ChangeType AND ArenaEcoId = approval-ArenaEcoId ) NEXT count = count + 1 ). DATA(is_duplicate) = xsdbool( same_key_in_request > 1 ). IF is_duplicate = abap_false. SELECT SINGLE FROM zr_changeapproval FIELDS ID WHERE ItemNumber = @approval-ItemNumber AND ChangeType = @approval-ChangeType AND ArenaEcoId = @approval-ArenaEcoId AND Status <> @status-rejected AND ID <> @approval-ID INTO @DATA(existing_id) OPTIONS PRIVILEGED ACCESS. is_duplicate = xsdbool( sy-subrc = 0 ). ENDIF. IF is_duplicate = abap_true. APPEND VALUE #( %tky = approval-%tky %fail-cause = if_abap_behv=>cause-unspecific ) TO failed-changeapproval. APPEND VALUE #( %tky = approval-%tky %state_area = state_area_duplicate %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |A change approval for item { approval-ItemNumber }, | && |change type { approval-ChangeType } and ECO { approval-ArenaEcoId } | && |already exists and is not rejected| ) %element-ItemNumber = if_abap_behv=>mk-on %element-ChangeType = if_abap_behv=>mk-on %element-ArenaEcoId = if_abap_behv=>mk-on ) TO reported-changeapproval. ENDIF. ENDLOOP. ENDMETHOD. ENDCLASS.