Appearance
Project 4 — Service-Ready Purchase Request Application
When to start: after Lesson 18 of ABAP SQL, CDS & RAP and Project 3 — Purchase Request Workflow Manager.
Approximate duration: one working day
Project overview
Extend your Purchase Request Business Object into a service-ready RAP application.
Use the concepts learned in Lessons 15–18 in an independent coding challenge. You receive the contract and acceptance tests. You design, implement, activate, debug, and prove the solution.
Build on your working Project 3 objects. Add ownership and authorization, persistent draft editing, a Review Note child entity, and a service layer for Fiori Elements consumption.
Use your own student/group suffix in place of ###.
What you are building
An internal purchasing team needs to create requests, record review notes, and complete the approval workflow through a Fiori Elements application.
Each Purchase Request owns its Review Notes:
text
Purchase Request
└── Review NotesKeep the workflow from Project 3:
text
N = New
S = Submitted
A = Approved
X = Rejected
N ──submit──→ S
├──approve──→ A
└──reject───→ XThe application must support:
- owner-based read and modification authorization;
- draft editing for the root and its Review Notes;
- an OData V4 UI service with Currency value help and Fiori Elements metadata;
- the existing workflow actions, field control, and backend validations;
- independent EML verification of permissions and parent/child operations.
Target architecture:
text
Fiori Elements
↓
OData V4 UI Service
↓
Service Definition
↓
Projection Layer
ZC_PURCHASE_REQ_###
│ composition
↓
ZC_PREQ_NOTE_###
↓
Base RAP BO
ZR_PURCHASE_REQ_###
│ composition
↓
ZR_PREQ_NOTE_###
↓
Managed Behavior + Authorization + Draft
↓
Active / Draft PersistenceThe purpose of the checkpoint is to prove that the behavior contract remains in the BO. EML and OData consumers must receive the same business rules, authorization, and feature control.
Before you start — Create a package
Create a new package for the new Project 4 artifacts.
In ADT, select your course superpackage.
For local BTP ABAP Trial development this is typically:
text
ZLOCALunless your course uses another superpackage.
Then:
text
Project Explorer
→ right-click superpackage
→ New
→ ABAP PackageSuggested package:
text
ZPKG_CP4_###Description:
text
Checkpoint 4 - Service Ready Purchase RequestUse this new package for the new Project 4 objects.
Existing Project 3 objects remain in their original package and are modified in place.
1. Challenge scope
Start from your working Project 3 objects:
text
ZDM_PRSTAT_###
ZDE_PRSTAT_###
ZPREQ_###
ZR_PURCHASE_REQ_###
base Behavior Definition
ZBP_R_PREQ_###
ZCL_PREQ_FLOW_###Add the service-ready architecture:
text
ResponsibleUser ownership
CDS DCL
RAP global + instance authorization
Draft
root draft persistence
Review Note child
child draft persistence
projection views
projection DCL
projection behavior
Currency value help
metadata extensions
Service Definition
OData V4 UI Service Binding
Fiori Elements Preview
EML verification2. Required new objects
Create new artifacts equivalent to:
text
ZDPREQ_###
ZPREQNOTE_###
ZR_PREQ_NOTE_###
ZDPRNOTE_###
Access Control for ZR_PURCHASE_REQ_###
Access Control for ZR_PREQ_NOTE_###
ZC_PURCHASE_REQ_###
ZC_PREQ_NOTE_###
Access Control for ZC_PURCHASE_REQ_###
Access Control for ZC_PREQ_NOTE_###
Projection Behavior Definition
Metadata Extension for ZC_PURCHASE_REQ_###
Metadata Extension for ZC_PREQ_NOTE_###
ZUI_PURCHASE_REQ_###
ZUI_PREQ_###_O4
ZCL_PREQ_VERIFY_###3. Secure the existing root
Add business ownership
Extend:
text
ZPREQ_###with:
abap
responsible_user : abp_creation_user;Keep the field nullable because Project 3 rows already exist.
Expose it in:
text
ZR_PURCHASE_REQ_###as:
text
ResponsibleUserChange the root CDS annotation to:
abap
@AccessControl.authorizationCheck: #CHECKRoot read authorization
Create an Access Control for:
text
ZR_PURCHASE_REQ_###Use:
abap
@EndUserText.label: 'Purchase Request Access'
@MappingRole: true
define role ZR_Purchase_Req_###
{
grant select on ZR_Purchase_Req_###
where ResponsibleUser = aspect user;
}The rule is:
text
current user
=
ResponsibleUserOld Project 3 rows with initial ResponsibleUser should no longer appear through the protected root CDS.
They still physically exist in:
text
ZPREQ_###System-derived owner
Make:
text
ResponsibleUserread-only to external consumers.
Add:
abap
determination setResponsibleUser on modify { create; }Required behavior:
text
new Purchase Request
→ ResponsibleUser = current technical userUse:
abap
cl_abap_context_info=>get_user_technical_name( )inside provider logic.
Use local-mode EML.
Enable RAP authorization
Replace:
abap
authorization master ( none )with:
abap
authorization master ( global, instance )For this BTP Trial checkpoint, implement the global authorization contract so requested:
text
create
update
deleteare allowed.
This is a training authorization stub. Productive business-role / authorization-object design is outside the Trial setup used here.
Implement instance authorization:
text
ResponsibleUser = current user
→ update allowed
→ delete allowed
otherwise
→ update unauthorized
→ delete unauthorizedThe existing:
text
submit
approve
rejectactions must delegate authorization to:
text
updateUse:
abap
authorization: updateon the business actions.
Draft-aware authorization
Draft introduces authorization requests that were not present in Project 3.
Your authorization implementation must correctly handle the draft lifecycle.
At minimum, include the update-style authorization result for:
text
Edit
Activateaccording to the current owner policy.
Do not add authorization control to the draft determine action:
text
PrepareFor newly created instances with no persistent before-image, create authorization applies.
4. Enable draft for the root
Add draft to the base BDEF
Add:
abap
with draft;Declare:
abap
draft table zdpreq_###Use ADT Quick Fix:
text
Ctrl+1to generate:
text
ZDPREQ_###Do not manually design the draft administration fields.
Add total ETag
The root already has:
text
LocalLastChangedAt
LastChangedAtKeep:
abap
etag master LocalLastChangedAtand add:
abap
total etag LastChangedAtto the lock master.
The two timestamps have different concurrency roles.
Add draft actions
Declare:
abap
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;Add:
abap
draft determine action Prepare
{
validation validateRequest;
validation validateNeededBy;
validation validateCost;
}Reuse the Project 3 validations.
Do not create draft-specific duplicate validation methods.
5. Add the Review Note child
Create child persistence
Create:
text
ZPREQNOTE_###One row represents one Review Note belonging to one Purchase Request.
Use fields:
| Field | Suggested type |
|---|---|
client | client type |
note_uuid | SYSUUID_X16 |
request_uuid | SYSUUID_X16 |
note_no | NUMC(4) |
note_type | CHAR(20) |
note_text | CHAR(255) |
local_last_changed_at | ABP_LOCINST_LASTCHANGE_TSTMPL |
Technical key:
text
client + note_uuidCreate the child CDS entity
Create:
text
ZR_PREQ_NOTE_###Use:
text
define view entitynot:
text
define root view entityRequired relationship:
abap
association to parent ZR_Purchase_Req_### as _PurchaseRequest
on $projection.RequestUuid = _PurchaseRequest.UuidExpose:
text
_PurchaseRequest
NoteUuid
RequestUuid
NoteNo
NoteType
NoteText
LocalLastChangedAtAnnotate:
text
LocalLastChangedAtwith the local-instance-last-changed semantic annotation.
Add composition to the root
Extend:
text
ZR_PURCHASE_REQ_###with:
abap
composition [0..*] of ZR_Preq_Note_### as _NotesExpose:
text
_Notesin the root select list.
The BO hierarchy is now:
text
PurchaseRequest
└── NotesProtect child reads
Create Access Control for:
text
ZR_PREQ_NOTE_###The child read policy must follow parent ownership.
Use the parent association to enforce:
text
_PurchaseRequest.ResponsibleUser
=
current userThe child will later be exposed as its own OData entity set, so it needs explicit read protection.
6. Extend managed behavior for the child
Enable create through the composition
In the root behavior add:
abap
association _Notes
{
create ( authorization: update );
with draft;
}The child is created:
text
through the parent associationnot with a normal child create;.
Add child behavior
Add a second behavior entity for:
text
ZR_PREQ_NOTE_###Requirements:
text
persistent table ZPREQNOTE_###
draft table ZDPRNOTE_###
authorization dependent by _PurchaseRequest
lock dependent by _PurchaseRequest
etag master LocalLastChangedAt
update
delete
NoteUuid
→ managed numbering + read-only
RequestUuid
→ read-only
LocalLastChangedAt
→ read-only
NoteNo
→ mandatory:create + readonly:update
_PurchaseRequest
→ with draft
complete mappingGenerate:
text
ZDPRNOTE_###with ADT Quick Fix.
Base BDEF target
Your resulting behavior must contain the equivalent of:
abap
managed implementation in class zbp_r_preq_### unique;
strict ( 2 );
with draft;
define behavior for ZR_Purchase_Req_### alias PurchaseRequest
persistent table zpreq_###
draft table zdpreq_###
lock master
total etag LastChangedAt
authorization master ( global, instance )
etag master LocalLastChangedAt
{
create;
update;
delete;
...
determination setInitialStatus on modify { create; }
determination setResponsibleUser on modify { create; }
validation validateRequest on save
{ create; field RequesterName, ItemDescription; }
validation validateNeededBy on save
{ create; field NeededBy; }
validation validateCost on save
{ create; field Quantity, UnitPrice, CurrencyCode; }
action ( features : instance, authorization: update )
submit result [1] $self;
action ( features : instance, authorization: update )
approve result [1] $self;
action ( features : instance, authorization: update )
reject result [1] $self;
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
draft determine action Prepare
{
validation validateRequest;
validation validateNeededBy;
validation validateCost;
}
association _Notes
{
create ( authorization: update );
with draft;
}
...
}
define behavior for ZR_Preq_Note_### alias Note
persistent table zpreqnote_###
draft table zdprnote_###
authorization dependent by _PurchaseRequest
lock dependent by _PurchaseRequest
etag master LocalLastChangedAt
{
update;
delete;
field ( numbering : managed, readonly ) NoteUuid;
field ( readonly )
RequestUuid,
LocalLastChangedAt;
field ( mandatory:create, readonly:update )
NoteNo;
association _PurchaseRequest { with draft; }
mapping for zpreqnote_###
{
NoteUuid = note_uuid;
RequestUuid = request_uuid;
NoteNo = note_no;
NoteType = note_type;
NoteText = note_text;
LocalLastChangedAt = local_last_changed_at;
}
}Keep the complete Project 3 root mapping and field-control declarations.
7. Build the projection layer
Create root projection
Create:
text
ZC_PURCHASE_REQ_###Use:
abap
define root view entity ZC_Purchase_Req_###
provider contract transactional_query
as projection on ZR_Purchase_Req_###Requirements:
text
@AccessControl.authorizationCheck: #CHECK
@Metadata.allowExtensions: true
@Search.searchable: true
all important root fields projected
_Notes projectedMake:
text
RequestNo
RequesterNamedefault search elements.
Add Currency value help
Add to projection field:
text
CurrencyCodea current:
text
@Consumption.valueHelpDefinitionusing:
text
I_CurrencyStdVHand element:
text
CurrencyUse:
text
useForValidation: trueCreate child projection
Create:
text
ZC_PREQ_NOTE_###as a projection on:
text
ZR_PREQ_NOTE_###Project all child fields.
Redirect the parent association:
abap
_PurchaseRequest
: redirected to parent ZC_Purchase_Req_###In the root projection redirect:
abap
_Notes
: redirected to composition child ZC_Preq_Note_###Protect projection reads
Create Access Control for:
text
ZC_PURCHASE_REQ_###using a condition equivalent to:
abap
where inheriting conditions from entity ZR_Purchase_Req_###;Create Access Control for:
text
ZC_PREQ_NOTE_###using:
abap
where inheriting conditions from entity ZR_Preq_Note_###;Both projection DCL objects must use:
text
@MappingRole: trueDo not assume CDS access control automatically propagates through projection layers.
8. Expose projection behavior
Create the projection BDEF
Create a projection Behavior Definition for:
text
ZC_PURCHASE_REQ_###Use:
abap
projection;
strict ( 2 );
use draft;Root behavior must expose:
text
use etag
create
update
delete
Edit
Activate
Discard
Resume
Prepare
submit
approve
reject
_Notes create with draftChild behavior must expose:
text
use etag
update
delete
_PurchaseRequest with draftEquivalent structure:
abap
projection;
strict ( 2 );
use draft;
define behavior for ZC_Purchase_Req_### alias PurchaseRequest
use etag
{
use create;
use update;
use delete;
use action Edit;
use action Activate;
use action Discard;
use action Resume;
use action Prepare;
use action submit;
use action approve;
use action reject;
use association _Notes { create; with draft; }
}
define behavior for ZC_Preq_Note_### alias Note
use etag
{
use update;
use delete;
use association _PurchaseRequest { with draft; }
}9. Add UI metadata
Root metadata extension
Create a Metadata Extension for:
text
ZC_PURCHASE_REQ_###Use:
text
@Metadata.layer: #CORERequired UI design:
text
header title
→ RequestNo
list columns
→ RequestNo
→ RequesterName
→ ItemDescription
→ Quantity
→ UnitPrice
→ CurrencyCode
→ NeededBy
→ Status
→ ResponsibleUser
selection fields
→ Status
→ NeededBy
→ CurrencyCode
identification fields
→ all major business fieldsAdd an object-page facet for:
text
Review Notesusing:
text
#LINEITEM_REFERENCE
targetElement = _NotesExpose workflow actions in the UI
Add Fiori action-button annotations using:
text
#FOR_ACTIONfor:
text
submit
approve
rejectUse either or both:
text
@UI.lineItem
@UI.identificationdepending on where you want the buttons.
The dataAction names must match the projected RAP action names.
The buttons must still respect the existing instance feature-control matrix at runtime.
Child metadata extension
Create Metadata Extension for:
text
ZC_PREQ_NOTE_###Add child line-item fields:
text
NoteNo
NoteType
NoteTextThe Review Notes facet must render a child collection rather than a flattened root field.
10. Expose the business service
Create Service Definition
Create:
text
ZUI_PURCHASE_REQ_###Expose:
abap
ZC_Purchase_Req_### as PurchaseRequests;
ZC_Preq_Note_### as ReviewNotes;
I_CurrencyStdVH as Currencies;The service definition answers:
text
WHAT is exposedCreate OData V4 UI binding
Create:
text
ZUI_PREQ_###_O4Binding:
text
OData V4 - UIUse:
text
ZUI_PURCHASE_REQ_###as the Service Definition.
Activate and publish the local service.
Launch the Fiori Elements Preview.
The Service Binding answers:
text
HOW the service is exposed11. Verify the BO through EML
Create ZCL_PREQ_VERIFY_###
Create an external EML verification class.
Implement:
text
IF_OO_ADT_CLASSRUNUse normal EML only.
The class must demonstrate:
text
GET PERMISSIONS
create-by-association
read-by-association
MAPPED
LINK
COMMIT ENTITIESAuthorization and feature verification
For an owned active Purchase Request, request:
text
instance authorization
instance featuresAt minimum inspect:
text
update authorization
delete authorization
submit feature
approve feature
reject feature
UnitPrice feature
CurrencyCode featureFor the owned instance:
text
update authorization = allowed
delete authorization = allowedThe verification class must show that:
text
authorization
and
feature controlare separate permission dimensions.
Create a Review Note with EML
For an owned active Purchase Request, create one child through:
text
CREATE BY \_NotesUse:
text
%target
child %cidDo not supply:
text
NoteUuid
RequestUuidInspect:
text
MAPPED
FAILED
REPORTEDCommit.
Read Review Notes by association
Use:
text
READ ENTITIES
ENTITY PurchaseRequest
BY \_NotesReturn:
text
RESULT
LINKThe runtime evidence must prove the parent-to-child relationship.
12. Required test cases
Complete the tests in order. For each test, record the setup, expected result, and actual result. Capture relevant Fiori Elements screens, persistence previews, or EML output so the result can be reviewed.
Use 99004001 and 99004002 only for this project. Before repeating the acceptance run, check for existing active rows and drafts with those Request Numbers. Resume the existing test state or remove only your test instances through the BO and discard their drafts through the UI. Do not reset active or draft persistence with direct SQL.
Test 1 — Read security
Prove DCL before creating new data
Project 3 left active rows:
text
99000001
99000002They were created before ResponsibleUser existed.
After root DCL activation, those old rows should normally be hidden from:
text
ZR_PURCHASE_REQ_###
ZC_PURCHASE_REQ_###
Fiori Elements servicewhile still visible physically in:
text
ZPREQ_###Use:
text
F8
Alt+F8to prove the difference.
If you already removed those old rows, use another pre-ownership row if one exists; do not create an unauthorized row with direct SQL just for the test.
Test 2 — Draft create and activation
Create request 99004001
In the Fiori Elements preview choose:
text
CreateEnter valid data:
text
RequestNo = 99004001
RequesterName = Casey Walker
RequesterEmail = casey@example.com
ItemDescription = Mobile test devices
Quantity = 3
UnitPrice = 850.00
CurrencyCode = EUR
NeededBy = current date + 21
Notes = Approval branchDo not enter:
text
Uuid
Status
ResponsibleUser
admin fieldsExpected provider defaults:
text
Status = N
ResponsibleUser = current userProve new-draft persistence
Before Save:
text
ZPREQ_###
→ no active 99004001 row
ZDPREQ_###
→ draft 99004001 existsThis must be visible through Table Data Preview.
Add Review Notes before Save
While the new Purchase Request is still a draft, add at least two Review Notes:
text
0001 / TECHNICAL / Device specifications reviewed
0002 / BUDGET / Budget owner confirmation requiredBefore Save:
text
ZPREQNOTE_###
→ no active notes
ZDPRNOTE_###
→ child drafts existSave the composition tree
Choose:
text
SaveExpected:
text
Prepare runs root validations
Activate succeeds
root becomes active
both Review Notes become active
root draft removed
child drafts removedOne Save activates the composition tree.
Test 3 — Validation
Prove invalid draft cannot activate
Edit:
text
99004001Set:
text
NeededBy = yesterdayChoose:
text
SaveRequired:
text
validation message shown
active row remains unchanged
invalid draft remains available for correctionCorrect the date and Save successfully.
Test 4 — Value help and metadata
Prove the service metadata
In Fiori Elements verify:
text
RequestNo shown as object title
expected list columns visible
Status / NeededBy / Currency filters available
CurrencyCode has value help
Review Notes facet visible
NoteNo / NoteType / NoteText shown in child tableChoose a currency through the value help at least once.
Test 5 — Approval workflow through OData
Submit 99004001
While Status is:
text
Nverify:
text
submit available
approve unavailable
reject unavailable
purchase fields editableExecute:
text
submitExpected:
text
N → SVerify submitted feature control
After Submit:
text
submit unavailable
approve available
reject available
ItemDescription
Quantity
UnitPrice
CurrencyCode
NeededBy
→ read-onlyAttempt to edit one controlled purchase field through the UI.
The service must respect the backend feature-control result.
Approve 99004001
Execute:
text
approveExpected:
text
S → AFinal state:
text
all workflow actions unavailable
purchase fields read-only
Review Notes still visibleTest 6 — Rejection workflow
Create request 99004002
Create a second valid Purchase Request in the UI.
Use:
text
RequestNo = 99004002Save it.
Drive:
text
N
→ submit
→ S
→ reject
→ XAdd at least one Review Note before or during the draft editing lifecycle.
Test 7 — Discard a child draft
Prove child draft discard
Edit:
text
99004002Add a new Review Note but do not Save.
Verify:
text
active child table
→ note not present
child draft table
→ note presentChoose:
text
Cancel / DiscardRequired:
text
draft note removed
active child data unchangedTest 8 — Root total ETag
Prove child change updates the root BO
Capture root:
text
LastChangedAtfor an active Purchase Request.
Edit and save only:
text
Review Note dataRead root:
text
LastChangedAtagain.
Required:
text
root total ETag changedThis proves the total ETag represents the complete compositional BO.
13. Final runtime state
Required active root states:
text
99004001 → A
99004002 → XRequired child state:
text
99004001
→ at least two active Review Notes
99004002
→ at least one active Review NoteRequired security/service state:
text
root DCL active
child DCL active
projection DCL active
owner-based modification authorization active
Draft active
OData V4 UI service published
Currency value help working
composition navigation working
feature control working
backend validations working14. Technical constraints
Your solution must keep business rules in the RAP provider.
Required:
text
no direct SQL business writes from consumers
no IN LOCAL MODE in external consumers
no direct consumer Status update
no consumer-supplied UUIDs
no user-entered ResponsibleUser
no duplicated validation logic in UI code
no duplicated ownership logic in child handler
no separate child lock master
no normal child create operationUse:
text
DCL
RAP authorization
determinations
validations
actions
feature control
draft
composition behaviorfor the responsibilities they were designed to handle.
15. Deliverables
Submit the modified and new objects, using your own suffix:
text
modified ZPREQ_###
modified ZR_PURCHASE_REQ_###
modified base BDEF
modified ZBP_R_PREQ_###
ZDPREQ_###
ZPREQNOTE_###
ZR_PREQ_NOTE_###
ZDPRNOTE_###
root + child Access Controls
ZC_PURCHASE_REQ_###
ZC_PREQ_NOTE_###
projection Access Controls
projection BDEF
root Metadata Extension
child Metadata Extension
ZUI_PURCHASE_REQ_###
ZUI_PREQ_###_O4
ZCL_PREQ_VERIFY_###Include:
- the actual names of the modified Project 3 objects and all new Project 4 objects;
- an architecture diagram and explanation covering the root, child, projections, service, authorization, and active/draft persistence;
- the setup, expected result, and actual result for all eight required tests;
- EML output showing
GET PERMISSIONS, childMAPPED, and read-by-associationRESULTandLINK, together with anyFAILEDandREPORTEDresponses; - evidence of the final approved and rejected requests and their active Review Notes;
- an explanation of how DCL, RAP authorization, feature control, and validations each contribute to the application.
If no pre-ownership row exists for the read-security test, record that limitation. Do not report an unexecuted check as passed.
16. Submission checklist
Ownership and authorization
Draft and composition behavior
Projection and service layer
Runtime evidence
17. Certification focus
This checkpoint is intentionally close to practical system work.
You must navigate and connect multiple repository objects correctly:
text
table
root CDS
child CDS
DCL
base BDEF
Behavior Pool
draft tables
projection CDS
projection BDEF
metadata extensions
Service Definition
Service Binding
EML verification classThe important skill is not memorizing one code block.
It is being able to diagnose which layer owns a problem:
text
wrong rows visible
→ DCL
wrong user can modify
→ RAP authorization
invalid data saves
→ validation / Prepare
Edit/Save flow missing
→ Draft
child cannot be created
→ composition behavior
child navigation broken
→ CDS/projection redirection
action missing in UI
→ projection behavior / UI action metadata
value help missing
→ consumption annotation / service exposure
service endpoint missing
→ Service BindingThis checkpoint emphasizes building, activating, testing, and repairing the RAP stack. It is not intended to simulate the entire certification; later lessons add events, extensibility, and quality tooling.
18. Next steps
The project is complete when you can demonstrate the persisted approval and rejection paths, the root and child draft lifecycle, owner-based access, and the required EML results.
Continue with Lesson 19 — RAP Business Events →
Revisit the Lesson 18 companion for composition guidance or the EML Cheat Sheet for permissions, associations, and transactions.