Sync current sources: clean RAP handover names, app rebind, tooling

Working-tree state as of 2026-09-29, uncommitted since the initial
"upload current sources" commit:

- docs/handover/rap/: renamed the 11 generator-suffixed sources
  (zz*000) to the clean object names built on the tenant, plus the
  A2X leg and parameter-entity edits.
- app/: manifest, annotations, i18n, mock data and local/mock ui5
  configs updated to the live service shape.
- PRD.md, CHANGELOG.md, KNOWLEDGE.md: build record through
  2026-09-19, including the rejected reentrance-ticket path.
- tools/adt-reentrance-login/: retained login helper (new).
- docs/v2/zui_arena_change_appr_o4.metadata.xml: captured live
  $metadata (new).
- Dropped a stray 0.3GB-class Eclipse installer that the initial
  commit had tracked under a URL-mangled filename, and the
  machine-specific 0M7DEJI.code-workspace.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-29 10:22:52 -07:00
co-authored by Claude Opus 5
parent f8b45ea4a5
commit 468729eb4e
42 changed files with 4597 additions and 1680 deletions
@@ -0,0 +1,765 @@
" 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-<name> / 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 <table>
" [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-<name> = 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-<name> 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(<approval>).
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 <approval>-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 { <approval>-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(<update_message>).
<update_message>-%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(<approval>).
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 <approval>-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 { <approval>-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(<update_message>).
<update_message>-%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(<approval>).
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 <approval>-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 { <approval>-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(<update_message>).
<update_message>-%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(<approval>).
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 <approval>-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 { <approval>-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(<update_message>).
<update_message>-%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(<approval>).
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 <approval>-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 { <approval>-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(<update_message>).
<update_message>-%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(<approval>).
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 <approval>-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 { <approval>-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(<update_message>).
<update_message>-%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.