RAP Draft für Workflow-Szenarien: Mehrstufige Genehmigungsprozesse

Kategorie
RAP
Veröffentlicht
Autor
Johannes

Approval-Workflows sind ein zentrales Element in Unternehmensanwendungen. Urlaubsanträge, Bestellfreigaben oder Reisekostenabrechnungen durchlaufen mehrere Genehmigungsstufen, bevor sie final genehmigt werden. Das RESTful ABAP Programming Model (RAP) bietet mit Draft Handling die ideale Grundlage für solche Workflows: Anträge können als Entwurf gespeichert, eingereicht und von verschiedenen Genehmigern bearbeitet werden.

In diesem Artikel implementieren wir einen vollständigen Urlaubsantrag-Workflow mit mehrstufiger Genehmigung. Sie lernen, wie Sie Status-basierte Actions definieren, Feature Control für kontextabhängige UI-Steuerung einsetzen und den gesamten Genehmigungsprozess in RAP abbilden.

Der Workflow im Überblick

Unser Urlaubsantrag durchläuft folgende Phasen:

┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ DRAFT │────▶│ SUBMITTED │────▶│ APPROVED │────▶│ ACTIVE │
│ (Entwurf) │ │ (Eingereicht)│ │(Genehmigt L1)│ │ (Final) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ DISCARDED │ │ REJECTED │ │ REJECTED │
│ (Verworfen) │ │(Abgelehnt L1)│ │(Abgelehnt L2)│
└─────────────┘ └─────────────┘ └─────────────┘
StatusBeschreibungNächste Aktionen
DRAFTMitarbeiter erstellt/bearbeitet AntragSubmit, Discard
SUBMITTEDEingereicht, wartet auf VorgesetztenApprove, Reject (Level 1)
APPROVEDGenehmigt durch Vorgesetzten, wartet auf HRApprove, Reject (Level 2)
ACTIVEFinal genehmigt, im System aktiv-
REJECTEDAbgelehnt, zurück an AntragstellerResubmit, Discard

Datenmodell

Datenbanktabelle für Urlaubsanträge

@EndUserText.label : 'Urlaubsantrag'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
define table zleave_request {
key client : abap.clnt not null;
key request_uuid : sysuuid_x16 not null;
request_id : abap.numc(10);
employee_id : abap.numc(8);
employee_name : abap.char(80);
leave_type : abap.char(2); // VA=Vacation, SP=Special, SI=Sick
start_date : abap.dats;
end_date : abap.dats;
days_requested : abap.int4;
reason : abap.string(500);
// Status-Management
workflow_status : abap.char(2); // DR/SU/A1/AC/RJ
current_approver : abap.char(12);
approval_level : abap.int1; // 1=Manager, 2=HR
// Approval-Historie
submitted_at : timestampl;
submitted_by : abap.char(12);
approved_l1_at : timestampl;
approved_l1_by : abap.char(12);
approved_l2_at : timestampl;
approved_l2_by : abap.char(12);
rejected_at : timestampl;
rejected_by : abap.char(12);
rejection_reason : abap.string(500);
// Administrative Felder
created_by : abap.char(12);
created_at : timestampl;
last_changed_by : abap.char(12);
last_changed_at : timestampl;
local_last_changed_at : timestampl;
}

Draft-Tabelle

@EndUserText.label : 'Urlaubsantrag Draft'
@AbapCatalog.tableCategory : #TRANSPARENT
define table zdraft_leave_req {
key client : abap.clnt not null;
key request_uuid : sysuuid_x16 not null;
request_id : abap.numc(10);
employee_id : abap.numc(8);
employee_name : abap.char(80);
leave_type : abap.char(2);
start_date : abap.dats;
end_date : abap.dats;
days_requested : abap.int4;
reason : abap.string(500);
workflow_status : abap.char(2);
current_approver : abap.char(12);
approval_level : abap.int1;
// Draft-Admin-Felder
"%admin" : include sych_bdl_draft_admin_inc;
}

CDS Interface View

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Urlaubsantrag - Interface View'
define root view entity ZI_LeaveRequest
as select from zleave_request
{
key request_uuid as RequestUUID,
request_id as RequestId,
employee_id as EmployeeId,
employee_name as EmployeeName,
leave_type as LeaveType,
start_date as StartDate,
end_date as EndDate,
days_requested as DaysRequested,
reason as Reason,
// Workflow-Status
workflow_status as WorkflowStatus,
current_approver as CurrentApprover,
approval_level as ApprovalLevel,
// Approval-Historie
submitted_at as SubmittedAt,
submitted_by as SubmittedBy,
approved_l1_at as ApprovedL1At,
approved_l1_by as ApprovedL1By,
approved_l2_at as ApprovedL2At,
approved_l2_by as ApprovedL2By,
rejected_at as RejectedAt,
rejected_by as RejectedBy,
rejection_reason as RejectionReason,
// Berechnetes Feld für Status-Text
case workflow_status
when 'DR' then 'Entwurf'
when 'SU' then 'Eingereicht'
when 'A1' then 'Genehmigt (Level 1)'
when 'AC' then 'Aktiv'
when 'RJ' then 'Abgelehnt'
else 'Unbekannt'
end as WorkflowStatusText,
// Criticality für Status-Anzeige
case workflow_status
when 'DR' then 0 // Neutral
when 'SU' then 2 // Warning (gelb)
when 'A1' then 2 // Warning (gelb)
when 'AC' then 3 // Success (grün)
when 'RJ' then 1 // Error (rot)
else 0
end as StatusCriticality,
// Administrative Felder
@Semantics.user.createdBy: true
created_by as CreatedBy,
@Semantics.systemDateTime.createdAt: true
created_at as CreatedAt,
@Semantics.user.lastChangedBy: true
last_changed_by as LastChangedBy,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at as LastChangedAt,
@Semantics.systemDateTime.localInstanceLastChangedAt: true
local_last_changed_at as LocalLastChangedAt
}

Behavior Definition mit Workflow-Actions

Die Behavior Definition ist das Herzstück des Workflows. Hier definieren wir alle Status-Transitionen als Actions mit Feature Control:

managed implementation in class zbp_i_leave_request unique;
strict ( 2 );
with draft;
define behavior for ZI_LeaveRequest alias LeaveRequest
persistent table zleave_request
draft table zdraft_leave_req
lock master total etag LastChangedAt
authorization master ( instance, global )
etag master LastChangedAt
{
// Standard-Operationen
create;
update;
delete;
// Feld-Eigenschaften
field ( readonly ) RequestId, CreatedBy, CreatedAt, LastChangedBy, LastChangedAt;
field ( readonly ) WorkflowStatus, CurrentApprover, ApprovalLevel;
field ( readonly ) SubmittedAt, SubmittedBy, ApprovedL1At, ApprovedL1By;
field ( readonly ) ApprovedL2At, ApprovedL2By, RejectedAt, RejectedBy;
field ( numbering : managed ) RequestId;
// ═══════════════════════════════════════════════════════════════════
// WORKFLOW ACTIONS
// ═══════════════════════════════════════════════════════════════════
// Antragsteller: Einreichen
action ( features : instance ) submit result [1] $self;
// Genehmiger Level 1 (Vorgesetzter): Genehmigen oder Ablehnen
action ( features : instance ) approveL1 result [1] $self;
action ( features : instance ) rejectL1
parameter ZA_RejectionInput
result [1] $self;
// Genehmiger Level 2 (HR): Final genehmigen oder ablehnen
action ( features : instance ) approveL2 result [1] $self;
action ( features : instance ) rejectL2
parameter ZA_RejectionInput
result [1] $self;
// Antragsteller: Abgelehnten Antrag erneut einreichen
action ( features : instance ) resubmit result [1] $self;
// Antragsteller: Zurück an Entwurf (vor Submit)
action ( features : instance ) withdraw result [1] $self;
// ═══════════════════════════════════════════════════════════════════
// VALIDATIONS
// ═══════════════════════════════════════════════════════════════════
validation validateDates on save { field StartDate, EndDate; }
validation validateDaysRequested on save { field DaysRequested; }
validation validateLeaveType on save { field LeaveType; }
// ═══════════════════════════════════════════════════════════════════
// DETERMINATIONS
// ═══════════════════════════════════════════════════════════════════
determination setInitialStatus on modify { create; }
determination calculateDays on modify { field StartDate, EndDate; }
determination setEmployeeData on modify { create; }
// ═══════════════════════════════════════════════════════════════════
// DRAFT ACTIONS
// ═══════════════════════════════════════════════════════════════════
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
draft determine action Prepare {
validation validateDates;
validation validateDaysRequested;
validation validateLeaveType;
}
// Mapping
mapping for zleave_request
{
RequestUUID = request_uuid;
RequestId = request_id;
EmployeeId = employee_id;
EmployeeName = employee_name;
LeaveType = leave_type;
StartDate = start_date;
EndDate = end_date;
DaysRequested = days_requested;
Reason = reason;
WorkflowStatus = workflow_status;
CurrentApprover = current_approver;
ApprovalLevel = approval_level;
SubmittedAt = submitted_at;
SubmittedBy = submitted_by;
ApprovedL1At = approved_l1_at;
ApprovedL1By = approved_l1_by;
ApprovedL2At = approved_l2_at;
ApprovedL2By = approved_l2_by;
RejectedAt = rejected_at;
RejectedBy = rejected_by;
RejectionReason = rejection_reason;
CreatedBy = created_by;
CreatedAt = created_at;
LastChangedBy = last_changed_by;
LastChangedAt = last_changed_at;
LocalLastChangedAt = local_last_changed_at;
}
}

Abstract Entity für Ablehnungsgrund

@EndUserText.label: 'Ablehnungsgrund'
define abstract entity ZA_RejectionInput
{
@UI.multiLineText: true
@EndUserText.label: 'Ablehnungsgrund'
RejectionReason : abap.string(500);
}

Behavior Implementation

Die Implementation enthält die Workflow-Logik mit Status-Transitionen, Feature Control und Validierungen:

CLASS lhc_leaverequest DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
CONSTANTS:
" Workflow-Status
BEGIN OF gc_status,
draft TYPE c LENGTH 2 VALUE 'DR',
submitted TYPE c LENGTH 2 VALUE 'SU',
approved1 TYPE c LENGTH 2 VALUE 'A1',
active TYPE c LENGTH 2 VALUE 'AC',
rejected TYPE c LENGTH 2 VALUE 'RJ',
END OF gc_status.
" Determinations
METHODS setInitialStatus FOR DETERMINE ON MODIFY
IMPORTING keys FOR LeaveRequest~setInitialStatus.
METHODS calculateDays FOR DETERMINE ON MODIFY
IMPORTING keys FOR LeaveRequest~calculateDays.
METHODS setEmployeeData FOR DETERMINE ON MODIFY
IMPORTING keys FOR LeaveRequest~setEmployeeData.
" Validations
METHODS validateDates FOR VALIDATE ON SAVE
IMPORTING keys FOR LeaveRequest~validateDates.
METHODS validateDaysRequested FOR VALIDATE ON SAVE
IMPORTING keys FOR LeaveRequest~validateDaysRequested.
METHODS validateLeaveType FOR VALIDATE ON SAVE
IMPORTING keys FOR LeaveRequest~validateLeaveType.
" Workflow Actions
METHODS submit FOR MODIFY
IMPORTING keys FOR ACTION LeaveRequest~submit RESULT result.
METHODS approveL1 FOR MODIFY
IMPORTING keys FOR ACTION LeaveRequest~approveL1 RESULT result.
METHODS rejectL1 FOR MODIFY
IMPORTING keys FOR ACTION LeaveRequest~rejectL1 RESULT result.
METHODS approveL2 FOR MODIFY
IMPORTING keys FOR ACTION LeaveRequest~approveL2 RESULT result.
METHODS rejectL2 FOR MODIFY
IMPORTING keys FOR ACTION LeaveRequest~rejectL2 RESULT result.
METHODS resubmit FOR MODIFY
IMPORTING keys FOR ACTION LeaveRequest~resubmit RESULT result.
METHODS withdraw FOR MODIFY
IMPORTING keys FOR ACTION LeaveRequest~withdraw RESULT result.
" Feature Control
METHODS get_instance_features FOR INSTANCE FEATURES
IMPORTING keys REQUEST requested_features FOR LeaveRequest RESULT result.
" Authorization
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR LeaveRequest RESULT result.
METHODS get_global_authorizations FOR GLOBAL AUTHORIZATION
IMPORTING REQUEST requested_authorizations FOR LeaveRequest RESULT result.
" Hilfsmethoden
METHODS get_current_user
RETURNING VALUE(result) TYPE syuname.
METHODS get_current_timestamp
RETURNING VALUE(result) TYPE timestampl.
METHODS get_manager_for_employee
IMPORTING employee_id TYPE numc8
RETURNING VALUE(result) TYPE syuname.
METHODS is_current_user_approver
IMPORTING request TYPE zi_leaverequest
RETURNING VALUE(result) TYPE abap_boolean.
ENDCLASS.
CLASS lhc_leaverequest IMPLEMENTATION.
METHOD setInitialStatus.
" Initiale Werte bei CREATE setzen
MODIFY ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
UPDATE FIELDS ( WorkflowStatus ApprovalLevel )
WITH VALUE #( FOR key IN keys (
%tky = key-%tky
WorkflowStatus = gc_status-draft
ApprovalLevel = 0
) ).
ENDMETHOD.
METHOD calculateDays.
" Anzahl Urlaubstage aus Start- und Enddatum berechnen
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
FIELDS ( StartDate EndDate )
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
LOOP AT requests ASSIGNING FIELD-SYMBOL(<request>).
IF <request>-StartDate IS NOT INITIAL
AND <request>-EndDate IS NOT INITIAL.
DATA(days) = <request>-EndDate - <request>-StartDate + 1.
IF days > 0.
MODIFY ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
UPDATE FIELDS ( DaysRequested )
WITH VALUE #( (
%tky = <request>-%tky
DaysRequested = days
) ).
ENDIF.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD setEmployeeData.
" Mitarbeiterdaten aus Benutzerstamm ermitteln
DATA(current_user) = get_current_user( ).
MODIFY ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
UPDATE FIELDS ( EmployeeId EmployeeName )
WITH VALUE #( FOR key IN keys (
%tky = key-%tky
EmployeeId = current_user
EmployeeName = current_user " Vereinfacht - normalerweise aus HR-Stamm
) ).
ENDMETHOD.
METHOD validateDates.
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
FIELDS ( StartDate EndDate )
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
DATA(today) = cl_abap_context_info=>get_system_date( ).
LOOP AT requests ASSIGNING FIELD-SYMBOL(<request>).
" Startdatum muss in der Zukunft liegen
IF <request>-StartDate < today.
APPEND VALUE #(
%tky = <request>-%tky
%element-StartDate = if_abap_behv=>mk-on
) TO failed-leaverequest.
APPEND VALUE #(
%tky = <request>-%tky
%state_area = 'VALIDATE_DATES'
%element-StartDate = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Das Startdatum muss in der Zukunft liegen'
)
) TO reported-leaverequest.
ENDIF.
" Enddatum muss nach Startdatum liegen
IF <request>-EndDate < <request>-StartDate.
APPEND VALUE #(
%tky = <request>-%tky
%element-EndDate = if_abap_behv=>mk-on
) TO failed-leaverequest.
APPEND VALUE #(
%tky = <request>-%tky
%state_area = 'VALIDATE_DATES'
%element-EndDate = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Das Enddatum muss nach dem Startdatum liegen'
)
) TO reported-leaverequest.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD validateDaysRequested.
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
FIELDS ( DaysRequested LeaveType )
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
LOOP AT requests ASSIGNING FIELD-SYMBOL(<request>).
" Maximale Urlaubstage prüfen
DATA(max_days) = SWITCH i(
<request>-LeaveType
WHEN 'VA' THEN 30 " Vacation
WHEN 'SP' THEN 5 " Special Leave
WHEN 'SI' THEN 180 " Sick Leave
ELSE 30
).
IF <request>-DaysRequested > max_days.
APPEND VALUE #(
%tky = <request>-%tky
%element-DaysRequested = if_abap_behv=>mk-on
) TO failed-leaverequest.
APPEND VALUE #(
%tky = <request>-%tky
%state_area = 'VALIDATE_DAYS'
%element-DaysRequested = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Maximal { max_days } Tage für diesen Urlaubstyp erlaubt|
)
) TO reported-leaverequest.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD validateLeaveType.
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
FIELDS ( LeaveType )
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
DATA(valid_types) = VALUE string_table( ( `VA` ) ( `SP` ) ( `SI` ) ).
LOOP AT requests ASSIGNING FIELD-SYMBOL(<request>).
IF NOT line_exists( valid_types[ table_line = <request>-LeaveType ] ).
APPEND VALUE #(
%tky = <request>-%tky
%element-LeaveType = if_abap_behv=>mk-on
) TO failed-leaverequest.
APPEND VALUE #(
%tky = <request>-%tky
%state_area = 'VALIDATE_TYPE'
%element-LeaveType = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Ungültiger Urlaubstyp (VA, SP, SI erlaubt)'
)
) TO reported-leaverequest.
ENDIF.
ENDLOOP.
ENDMETHOD.
"═══════════════════════════════════════════════════════════════════════
" WORKFLOW ACTIONS
"═══════════════════════════════════════════════════════════════════════
METHOD submit.
" Antrag einreichen: DRAFT → SUBMITTED
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
FIELDS ( WorkflowStatus EmployeeId )
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
DATA(current_user) = get_current_user( ).
DATA(timestamp) = get_current_timestamp( ).
LOOP AT requests ASSIGNING FIELD-SYMBOL(<request>).
" Nur Draft-Anträge können eingereicht werden
IF <request>-WorkflowStatus <> gc_status-draft.
APPEND VALUE #(
%tky = <request>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Nur Entwürfe können eingereicht werden'
)
) TO reported-leaverequest.
APPEND VALUE #( %tky = <request>-%tky ) TO failed-leaverequest.
CONTINUE.
ENDIF.
" Manager ermitteln
DATA(manager) = get_manager_for_employee( CONV #( <request>-EmployeeId ) ).
" Status-Transition durchführen
MODIFY ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
UPDATE FIELDS ( WorkflowStatus CurrentApprover ApprovalLevel SubmittedAt SubmittedBy )
WITH VALUE #( (
%tky = <request>-%tky
WorkflowStatus = gc_status-submitted
CurrentApprover = manager
ApprovalLevel = 1
SubmittedAt = timestamp
SubmittedBy = current_user
) ).
ENDLOOP.
" Aktualisierte Daten zurückgeben
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(updated_requests).
result = VALUE #( FOR req IN updated_requests (
%tky = req-%tky
%param = req
) ).
ENDMETHOD.
METHOD approveL1.
" Level-1-Genehmigung: SUBMITTED → APPROVED1
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
DATA(current_user) = get_current_user( ).
DATA(timestamp) = get_current_timestamp( ).
LOOP AT requests ASSIGNING FIELD-SYMBOL(<request>).
" Prüfung: korrekter Status und Genehmiger
IF <request>-WorkflowStatus <> gc_status-submitted.
APPEND VALUE #(
%tky = <request>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Antrag kann in diesem Status nicht genehmigt werden'
)
) TO reported-leaverequest.
APPEND VALUE #( %tky = <request>-%tky ) TO failed-leaverequest.
CONTINUE.
ENDIF.
IF NOT is_current_user_approver( <request> ).
APPEND VALUE #(
%tky = <request>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Sie sind nicht als Genehmiger berechtigt'
)
) TO reported-leaverequest.
APPEND VALUE #( %tky = <request>-%tky ) TO failed-leaverequest.
CONTINUE.
ENDIF.
" HR-Genehmiger ermitteln (Level 2)
DATA(hr_approver) = CONV syuname( 'HR_APPROVER' ). " Vereinfacht
" Status-Transition: SUBMITTED → APPROVED1
MODIFY ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
UPDATE FIELDS ( WorkflowStatus CurrentApprover ApprovalLevel ApprovedL1At ApprovedL1By )
WITH VALUE #( (
%tky = <request>-%tky
WorkflowStatus = gc_status-approved1
CurrentApprover = hr_approver
ApprovalLevel = 2
ApprovedL1At = timestamp
ApprovedL1By = current_user
) ).
ENDLOOP.
" Ergebnis zurückgeben
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(updated_requests).
result = VALUE #( FOR req IN updated_requests (
%tky = req-%tky
%param = req
) ).
ENDMETHOD.
METHOD rejectL1.
" Level-1-Ablehnung: SUBMITTED → REJECTED
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
DATA(current_user) = get_current_user( ).
DATA(timestamp) = get_current_timestamp( ).
LOOP AT requests ASSIGNING FIELD-SYMBOL(<request>).
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>)
WHERE %tky = <request>-%tky.
" Ablehnungsgrund aus Parameter
DATA(rejection_reason) = <key>-%param-RejectionReason.
IF <request>-WorkflowStatus <> gc_status-submitted.
APPEND VALUE #(
%tky = <request>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Antrag kann in diesem Status nicht abgelehnt werden'
)
) TO reported-leaverequest.
APPEND VALUE #( %tky = <request>-%tky ) TO failed-leaverequest.
CONTINUE.
ENDIF.
IF NOT is_current_user_approver( <request> ).
APPEND VALUE #(
%tky = <request>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Sie sind nicht als Genehmiger berechtigt'
)
) TO reported-leaverequest.
APPEND VALUE #( %tky = <request>-%tky ) TO failed-leaverequest.
CONTINUE.
ENDIF.
" Status-Transition: SUBMITTED → REJECTED
MODIFY ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
UPDATE FIELDS ( WorkflowStatus CurrentApprover RejectedAt RejectedBy RejectionReason )
WITH VALUE #( (
%tky = <request>-%tky
WorkflowStatus = gc_status-rejected
CurrentApprover = <request>-EmployeeId " Zurück an Antragsteller
RejectedAt = timestamp
RejectedBy = current_user
RejectionReason = rejection_reason
) ).
ENDLOOP.
ENDLOOP.
" Ergebnis zurückgeben
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(updated_requests).
result = VALUE #( FOR req IN updated_requests (
%tky = req-%tky
%param = req
) ).
ENDMETHOD.
METHOD approveL2.
" Level-2-Genehmigung (HR): APPROVED1 → ACTIVE
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
DATA(current_user) = get_current_user( ).
DATA(timestamp) = get_current_timestamp( ).
LOOP AT requests ASSIGNING FIELD-SYMBOL(<request>).
IF <request>-WorkflowStatus <> gc_status-approved1.
APPEND VALUE #(
%tky = <request>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Antrag muss zuerst vom Vorgesetzten genehmigt werden'
)
) TO reported-leaverequest.
APPEND VALUE #( %tky = <request>-%tky ) TO failed-leaverequest.
CONTINUE.
ENDIF.
IF NOT is_current_user_approver( <request> ).
APPEND VALUE #(
%tky = <request>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Sie sind nicht als HR-Genehmiger berechtigt'
)
) TO reported-leaverequest.
APPEND VALUE #( %tky = <request>-%tky ) TO failed-leaverequest.
CONTINUE.
ENDIF.
" Final Approval: APPROVED1 → ACTIVE
MODIFY ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
UPDATE FIELDS ( WorkflowStatus CurrentApprover ApprovedL2At ApprovedL2By )
WITH VALUE #( (
%tky = <request>-%tky
WorkflowStatus = gc_status-active
CurrentApprover = '' " Workflow abgeschlossen
ApprovedL2At = timestamp
ApprovedL2By = current_user
) ).
ENDLOOP.
" Ergebnis zurückgeben
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(updated_requests).
result = VALUE #( FOR req IN updated_requests (
%tky = req-%tky
%param = req
) ).
ENDMETHOD.
METHOD rejectL2.
" Level-2-Ablehnung (HR): APPROVED1 → REJECTED
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
DATA(current_user) = get_current_user( ).
DATA(timestamp) = get_current_timestamp( ).
LOOP AT requests ASSIGNING FIELD-SYMBOL(<request>).
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>)
WHERE %tky = <request>-%tky.
DATA(rejection_reason) = <key>-%param-RejectionReason.
IF <request>-WorkflowStatus <> gc_status-approved1.
APPEND VALUE #(
%tky = <request>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Antrag kann in diesem Status nicht abgelehnt werden'
)
) TO reported-leaverequest.
APPEND VALUE #( %tky = <request>-%tky ) TO failed-leaverequest.
CONTINUE.
ENDIF.
" APPROVED1 → REJECTED
MODIFY ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
UPDATE FIELDS ( WorkflowStatus CurrentApprover RejectedAt RejectedBy RejectionReason )
WITH VALUE #( (
%tky = <request>-%tky
WorkflowStatus = gc_status-rejected
CurrentApprover = <request>-EmployeeId
RejectedAt = timestamp
RejectedBy = current_user
RejectionReason = rejection_reason
) ).
ENDLOOP.
ENDLOOP.
" Ergebnis zurückgeben
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(updated_requests).
result = VALUE #( FOR req IN updated_requests (
%tky = req-%tky
%param = req
) ).
ENDMETHOD.
METHOD resubmit.
" Abgelehnten Antrag erneut einreichen: REJECTED → SUBMITTED
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
FIELDS ( WorkflowStatus EmployeeId )
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
DATA(current_user) = get_current_user( ).
DATA(timestamp) = get_current_timestamp( ).
LOOP AT requests ASSIGNING FIELD-SYMBOL(<request>).
IF <request>-WorkflowStatus <> gc_status-rejected.
APPEND VALUE #(
%tky = <request>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Nur abgelehnte Anträge können erneut eingereicht werden'
)
) TO reported-leaverequest.
APPEND VALUE #( %tky = <request>-%tky ) TO failed-leaverequest.
CONTINUE.
ENDIF.
DATA(manager) = get_manager_for_employee( CONV #( <request>-EmployeeId ) ).
" Reset und erneut einreichen
MODIFY ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
UPDATE FIELDS ( WorkflowStatus CurrentApprover ApprovalLevel
RejectedAt RejectedBy RejectionReason SubmittedAt SubmittedBy )
WITH VALUE #( (
%tky = <request>-%tky
WorkflowStatus = gc_status-submitted
CurrentApprover = manager
ApprovalLevel = 1
RejectedAt = VALUE #( ) " Reset
RejectedBy = ''
RejectionReason = ''
SubmittedAt = timestamp
SubmittedBy = current_user
) ).
ENDLOOP.
" Ergebnis zurückgeben
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(updated_requests).
result = VALUE #( FOR req IN updated_requests (
%tky = req-%tky
%param = req
) ).
ENDMETHOD.
METHOD withdraw.
" Eingereichten Antrag zurückziehen: SUBMITTED → DRAFT
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
FIELDS ( WorkflowStatus EmployeeId )
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
DATA(current_user) = get_current_user( ).
LOOP AT requests ASSIGNING FIELD-SYMBOL(<request>).
" Nur eingereichte Anträge können zurückgezogen werden
IF <request>-WorkflowStatus <> gc_status-submitted.
APPEND VALUE #(
%tky = <request>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Nur eingereichte Anträge können zurückgezogen werden'
)
) TO reported-leaverequest.
APPEND VALUE #( %tky = <request>-%tky ) TO failed-leaverequest.
CONTINUE.
ENDIF.
" Nur Antragsteller darf zurückziehen
IF CONV syuname( <request>-EmployeeId ) <> current_user.
APPEND VALUE #(
%tky = <request>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Nur der Antragsteller kann den Antrag zurückziehen'
)
) TO reported-leaverequest.
APPEND VALUE #( %tky = <request>-%tky ) TO failed-leaverequest.
CONTINUE.
ENDIF.
" Zurück auf Draft
MODIFY ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
UPDATE FIELDS ( WorkflowStatus CurrentApprover ApprovalLevel
SubmittedAt SubmittedBy )
WITH VALUE #( (
%tky = <request>-%tky
WorkflowStatus = gc_status-draft
CurrentApprover = ''
ApprovalLevel = 0
SubmittedAt = VALUE #( )
SubmittedBy = ''
) ).
ENDLOOP.
" Ergebnis zurückgeben
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(updated_requests).
result = VALUE #( FOR req IN updated_requests (
%tky = req-%tky
%param = req
) ).
ENDMETHOD.
"═══════════════════════════════════════════════════════════════════════
" FEATURE CONTROL
"═══════════════════════════════════════════════════════════════════════
METHOD get_instance_features.
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
FIELDS ( WorkflowStatus EmployeeId CurrentApprover )
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
DATA(current_user) = get_current_user( ).
LOOP AT requests ASSIGNING FIELD-SYMBOL(<request>).
DATA(is_owner) = xsdbool( CONV syuname( <request>-EmployeeId ) = current_user ).
DATA(is_approver) = is_current_user_approver( <request> ).
APPEND VALUE #(
%tky = <request>-%tky
" Update/Delete nur für Eigentümer im Draft-Status
%update = COND #(
WHEN is_owner = abap_true AND <request>-WorkflowStatus = gc_status-draft
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
%delete = COND #(
WHEN is_owner = abap_true AND <request>-WorkflowStatus = gc_status-draft
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
" Submit: nur für Eigentümer im Draft-Status
%action-submit = COND #(
WHEN is_owner = abap_true AND <request>-WorkflowStatus = gc_status-draft
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
" Withdraw: nur für Eigentümer im Submitted-Status
%action-withdraw = COND #(
WHEN is_owner = abap_true AND <request>-WorkflowStatus = gc_status-submitted
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
" Resubmit: nur für Eigentümer im Rejected-Status
%action-resubmit = COND #(
WHEN is_owner = abap_true AND <request>-WorkflowStatus = gc_status-rejected
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
" ApproveL1/RejectL1: nur für Genehmiger im Submitted-Status
%action-approveL1 = COND #(
WHEN is_approver = abap_true AND <request>-WorkflowStatus = gc_status-submitted
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
%action-rejectL1 = COND #(
WHEN is_approver = abap_true AND <request>-WorkflowStatus = gc_status-submitted
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
" ApproveL2/RejectL2: nur für Genehmiger im Approved1-Status
%action-approveL2 = COND #(
WHEN is_approver = abap_true AND <request>-WorkflowStatus = gc_status-approved1
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
%action-rejectL2 = COND #(
WHEN is_approver = abap_true AND <request>-WorkflowStatus = gc_status-approved1
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
) TO result.
ENDLOOP.
ENDMETHOD.
METHOD get_instance_authorizations.
" Authorization-Prüfung analog zu Feature Control
READ ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
FIELDS ( WorkflowStatus EmployeeId CurrentApprover )
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
DATA(current_user) = get_current_user( ).
LOOP AT requests ASSIGNING FIELD-SYMBOL(<request>).
DATA(is_owner) = xsdbool( CONV syuname( <request>-EmployeeId ) = current_user ).
DATA(is_approver) = is_current_user_approver( <request> ).
APPEND VALUE #(
%tky = <request>-%tky
%update = COND #(
WHEN is_owner = abap_true AND <request>-WorkflowStatus = gc_status-draft
THEN if_abap_behv=>auth-allowed
ELSE if_abap_behv=>auth-unauthorized )
%delete = COND #(
WHEN is_owner = abap_true AND <request>-WorkflowStatus = gc_status-draft
THEN if_abap_behv=>auth-allowed
ELSE if_abap_behv=>auth-unauthorized )
%action-submit = COND #(
WHEN is_owner = abap_true AND <request>-WorkflowStatus = gc_status-draft
THEN if_abap_behv=>auth-allowed
ELSE if_abap_behv=>auth-unauthorized )
%action-approveL1 = COND #(
WHEN is_approver = abap_true AND <request>-WorkflowStatus = gc_status-submitted
THEN if_abap_behv=>auth-allowed
ELSE if_abap_behv=>auth-unauthorized )
%action-rejectL1 = COND #(
WHEN is_approver = abap_true AND <request>-WorkflowStatus = gc_status-submitted
THEN if_abap_behv=>auth-allowed
ELSE if_abap_behv=>auth-unauthorized )
%action-approveL2 = COND #(
WHEN is_approver = abap_true AND <request>-WorkflowStatus = gc_status-approved1
THEN if_abap_behv=>auth-allowed
ELSE if_abap_behv=>auth-unauthorized )
%action-rejectL2 = COND #(
WHEN is_approver = abap_true AND <request>-WorkflowStatus = gc_status-approved1
THEN if_abap_behv=>auth-allowed
ELSE if_abap_behv=>auth-unauthorized )
%action-withdraw = COND #(
WHEN is_owner = abap_true AND <request>-WorkflowStatus = gc_status-submitted
THEN if_abap_behv=>auth-allowed
ELSE if_abap_behv=>auth-unauthorized )
%action-resubmit = COND #(
WHEN is_owner = abap_true AND <request>-WorkflowStatus = gc_status-rejected
THEN if_abap_behv=>auth-allowed
ELSE if_abap_behv=>auth-unauthorized )
) TO result.
ENDLOOP.
ENDMETHOD.
METHOD get_global_authorizations.
" CREATE-Berechtigung für alle Benutzer
result = VALUE #(
%create = if_abap_behv=>auth-allowed
).
ENDMETHOD.
"═══════════════════════════════════════════════════════════════════════
" HELPER METHODS
"═══════════════════════════════════════════════════════════════════════
METHOD get_current_user.
result = cl_abap_context_info=>get_user_technical_name( ).
ENDMETHOD.
METHOD get_current_timestamp.
GET TIME STAMP FIELD result.
ENDMETHOD.
METHOD get_manager_for_employee.
" Vereinfachte Implementierung - in der Realität aus HR-Stammdaten
" oder Organisationsstruktur ermitteln
result = 'MANAGER01'.
ENDMETHOD.
METHOD is_current_user_approver.
DATA(current_user) = get_current_user( ).
result = xsdbool( request-CurrentApprover = current_user ).
ENDMETHOD.
ENDCLASS.

Projection View mit UI-Annotations

Die Projection View exponiert den Workflow für Fiori Elements:

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Urlaubsantrag - Projection'
@Metadata.allowExtensions: true
define root view entity ZC_LeaveRequest
provider contract transactional_query
as projection on ZI_LeaveRequest
{
@UI.facet: [
{ id: 'General', type: #COLLECTION, label: 'Allgemein', position: 10 },
{ id: 'Dates', type: #FIELDGROUP_REFERENCE, targetQualifier: 'Dates',
parentId: 'General', label: 'Zeitraum', position: 10 },
{ id: 'Details', type: #FIELDGROUP_REFERENCE, targetQualifier: 'Details',
parentId: 'General', label: 'Details', position: 20 },
{ id: 'Workflow', type: #COLLECTION, label: 'Workflow', position: 20 },
{ id: 'Status', type: #FIELDGROUP_REFERENCE, targetQualifier: 'Status',
parentId: 'Workflow', label: 'Status', position: 10 },
{ id: 'History', type: #FIELDGROUP_REFERENCE, targetQualifier: 'History',
parentId: 'Workflow', label: 'Historie', position: 20 }
]
@UI.hidden: true
key RequestUUID,
@UI.lineItem: [{ position: 10, importance: #HIGH }]
@UI.selectionField: [{ position: 10 }]
RequestId,
@UI.lineItem: [{ position: 20 }]
@UI.fieldGroup: [{ qualifier: 'Details', position: 10 }]
EmployeeName,
@UI.lineItem: [{ position: 30 }]
@UI.fieldGroup: [{ qualifier: 'Details', position: 20 }]
@Consumption.valueHelpDefinition: [{ entity: { name: 'ZI_LeaveType', element: 'LeaveType' }}]
LeaveType,
@UI.lineItem: [{ position: 40 }]
@UI.fieldGroup: [{ qualifier: 'Dates', position: 10 }]
StartDate,
@UI.lineItem: [{ position: 50 }]
@UI.fieldGroup: [{ qualifier: 'Dates', position: 20 }]
EndDate,
@UI.lineItem: [{ position: 60 }]
@UI.fieldGroup: [{ qualifier: 'Dates', position: 30 }]
DaysRequested,
@UI.fieldGroup: [{ qualifier: 'Details', position: 30 }]
@UI.multiLineText: true
Reason,
// Workflow-Status mit Criticality
@UI.lineItem: [{ position: 70, criticality: 'StatusCriticality', importance: #HIGH }]
@UI.fieldGroup: [{ qualifier: 'Status', position: 10, criticality: 'StatusCriticality' }]
@UI.selectionField: [{ position: 20 }]
WorkflowStatusText,
@UI.hidden: true
WorkflowStatus,
@UI.hidden: true
StatusCriticality,
@UI.fieldGroup: [{ qualifier: 'Status', position: 20 }]
CurrentApprover,
// Workflow-Historie
@UI.fieldGroup: [{ qualifier: 'History', position: 10 }]
SubmittedAt,
@UI.fieldGroup: [{ qualifier: 'History', position: 20 }]
SubmittedBy,
@UI.fieldGroup: [{ qualifier: 'History', position: 30 }]
ApprovedL1At,
@UI.fieldGroup: [{ qualifier: 'History', position: 40 }]
ApprovedL1By,
@UI.fieldGroup: [{ qualifier: 'History', position: 50 }]
ApprovedL2At,
@UI.fieldGroup: [{ qualifier: 'History', position: 60 }]
ApprovedL2By,
@UI.fieldGroup: [{ qualifier: 'History', position: 70 }]
RejectedAt,
@UI.fieldGroup: [{ qualifier: 'History', position: 80 }]
RejectedBy,
@UI.fieldGroup: [{ qualifier: 'History', position: 90 }]
@UI.multiLineText: true
RejectionReason,
// Workflow-Actions in der Line Item
@UI.lineItem: [
{ type: #FOR_ACTION, dataAction: 'submit', label: 'Einreichen', position: 10 },
{ type: #FOR_ACTION, dataAction: 'approveL1', label: 'Genehmigen', position: 20 },
{ type: #FOR_ACTION, dataAction: 'rejectL1', label: 'Ablehnen', position: 30 },
{ type: #FOR_ACTION, dataAction: 'withdraw', label: 'Zurückziehen', position: 40 }
]
// Actions auf Object Page
@UI.identification: [
{ type: #FOR_ACTION, dataAction: 'submit', label: 'Einreichen', position: 10 },
{ type: #FOR_ACTION, dataAction: 'approveL1', label: 'Genehmigen (L1)', position: 20 },
{ type: #FOR_ACTION, dataAction: 'rejectL1', label: 'Ablehnen (L1)', position: 30 },
{ type: #FOR_ACTION, dataAction: 'approveL2', label: 'Final Genehmigen', position: 40 },
{ type: #FOR_ACTION, dataAction: 'rejectL2', label: 'Final Ablehnen', position: 50 },
{ type: #FOR_ACTION, dataAction: 'withdraw', label: 'Zurückziehen', position: 60 },
{ type: #FOR_ACTION, dataAction: 'resubmit', label: 'Erneut Einreichen', position: 70 }
]
EmployeeId,
// Administrative Felder
@UI.hidden: true
CreatedBy,
@UI.hidden: true
CreatedAt,
@UI.hidden: true
LastChangedBy,
@UI.hidden: true
LastChangedAt,
@UI.hidden: true
LocalLastChangedAt,
@UI.hidden: true
ApprovalLevel
}

Projection Behavior Definition

projection implementation in class zbp_c_leaverequest unique;
strict ( 2 );
use draft;
define behavior for ZC_LeaveRequest alias LeaveRequest
{
use create;
use update;
use delete;
// Workflow Actions
use action submit;
use action approveL1;
use action rejectL1;
use action approveL2;
use action rejectL2;
use action resubmit;
use action withdraw;
// Draft Actions
use action Edit;
use action Activate;
use action Discard;
use action Resume;
use action Prepare;
}

Best Practices für Workflow-Szenarien

DO: Status-Transitionen explizit prüfen

" GUT: Explizite Status-Prüfung vor jeder Transition
IF <request>-WorkflowStatus <> gc_status-submitted.
APPEND VALUE #(
%tky = <request>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Ungültiger Status für diese Aktion'
)
) TO reported-leaverequest.
RETURN.
ENDIF.

DO: Feature Control und Authorization synchron halten

" GUT: Gleiche Logik für beide
DATA(can_approve) = xsdbool(
is_approver = abap_true AND
<request>-WorkflowStatus = gc_status-submitted
).
" Feature Control
%action-approveL1 = COND #(
WHEN can_approve = abap_true
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
" Authorization
%action-approveL1 = COND #(
WHEN can_approve = abap_true
THEN if_abap_behv=>auth-allowed
ELSE if_abap_behv=>auth-unauthorized )

DO: Workflow-Historie vollständig dokumentieren

" GUT: Alle Workflow-Aktionen mit Zeitstempel und Benutzer protokollieren
MODIFY ENTITIES OF zi_leaverequest IN LOCAL MODE
ENTITY LeaveRequest
UPDATE FIELDS ( WorkflowStatus ApprovedL1At ApprovedL1By )
WITH VALUE #( (
%tky = <request>-%tky
WorkflowStatus = gc_status-approved1
ApprovedL1At = get_current_timestamp( )
ApprovedL1By = get_current_user( )
) ).

DON’T: Status-Logik duplizieren

" SCHLECHT: Gleiche Prüfung an mehreren Stellen
METHOD approveL1.
IF <request>-WorkflowStatus = 'SU'. " Hardcoded String
...
ENDIF.
ENDMETHOD.
METHOD get_instance_features.
IF <request>-WorkflowStatus = 'SU'. " Wieder hardcoded
...
ENDIF.
ENDMETHOD.
" GUT: Konstanten verwenden
CONSTANTS:
BEGIN OF gc_status,
submitted TYPE c LENGTH 2 VALUE 'SU',
END OF gc_status.

Erweiterungsmöglichkeiten

Der gezeigte Workflow kann erweitert werden:

ErweiterungBeschreibung
E-Mail-BenachrichtigungBei Statusänderung automatisch Beteiligte informieren
EskalationAutomatische Weiterleitung nach Zeitüberschreitung
VertretungsregelnGenehmigung durch Stellvertreter ermöglichen
Parallele GenehmigungMehrere Genehmiger gleichzeitig
Bedingte WorkflowsUnterschiedliche Genehmigungsstufen je nach Urlaubslänge

Zusammenfassung

In diesem Artikel haben wir einen vollständigen Approval-Workflow für Urlaubsanträge implementiert:

  • Draft Handling ermöglicht das Zwischenspeichern von Anträgen vor dem Einreichen
  • Status-basierte Actions steuern die Workflow-Transitionen (Submit, Approve, Reject)
  • Feature Control zeigt nur relevante Actions für den jeweiligen Status und Benutzer
  • Authorization stellt sicher, dass nur berechtigte Benutzer Aktionen ausführen
  • Workflow-Historie dokumentiert alle Genehmigungsschritte mit Zeitstempel

Das Muster lässt sich auf beliebige Genehmigungsszenarien übertragen: Bestellfreigaben, Reisekostenabrechnungen, Änderungsanträge und mehr.

Weiterführende Themen