Appearance
Project 2 — Travel Budget Review Manager
When to start: after ABAP Foundations and Lesson 9 of ABAP SQL, CDS & RAP.
Approximate duration: one working day
Project overview
A finance team wants to review existing travel requests from the SAP Flight Reference Scenario.
The travel data already exists in SAP's /DMO/ model.
Your application will own a separate set of budget reviews.
Each review refers to one existing Travel and stores:
text
technical UUID
business Review Number
Travel ID
Budget Limit
Currency
Review Status
Reviewer Note
administrative fieldsThe final application must combine your custom review data with existing /DMO/ Travel, Customer, and Agency data.
It must also calculate whether the Travel is within its review budget and provide an aggregate summary.
It should use only concepts learned through Lesson 9.
No RAP objects are required yet.
1. Learning objectives
By the end of the project, you should have used:
text
ABAP SQL
Reader / Runner architecture
CDS View Entities
joins
associations
path expressions
calculated elements
CASE
filtering
semantics
aggregation
GROUP BY
HAVING
Domains
Data Elements
custom persistence
UUID technical keys
safe INSERT / UPDATE / DELETE
sy-dbcnt
implicit client handlingThe project should prove that you can connect the individual techniques from Lessons 1–9 into one coherent backend application.
2. Business scenario
A finance team reviews Travel requests before they are approved.
The Travel itself already exists in:
text
/DMO/TRAVELYour application stores the finance team's review separately.
One review answers questions such as:
text
Which Travel is being reviewed?
Who is the Customer?
Which Agency is responsible?
What is the Travel's total price?
What budget limit did finance set?
Is the Travel within budget?
What is the current review status?Example output:
text
Review Number: 91000001
Travel ID: 00000123
Customer: Jane Smith
Agency: Best Travel Agency
Begin Date: 2026-08-10
End Date: 2026-08-18
Travel Total Price: 1250.00 EUR
Budget Limit: 1500.00 EUR
Assessment: WITHIN_BUDGET
Review Status: R3. Required architecture
Normal read path:
text
Runner
↓
Reader
↓
Review CDS
↓
Custom Review Table + /DMO/TRAVEL
↓
Customer / Agency associationsAggregate read path:
text
Runner
↓
Reader
↓
Summary CDSControlled write path:
text
ZCL_TRVREV_SQL_###
↓
ABAP SQL
↓
ZTRVREV_###The Runner must not directly access database tables or CDS entities.
4. Objects to create
Replace ### with your own student/group suffix.
Create:
text
ZDM_TRVSTAT_###
ZDE_TRVSTAT_###
ZTRVREV_###
ZI_TRAVEL_REVIEW_###
ZI_TRAVEL_REVIEW_SUM_###
ZCL_TRVREV_SQL_###
ZCL_TRVREV_READER_###
ZCL_TRVREV_RUN_###5. Suggested development sequence
First 3 hours — Data model
text
00:00–00:30 Inspect /DMO/TRAVEL and choose test data
00:30–01:15 Create Domain, Data Element, and Database Table
01:15–02:15 Build the review CDS model
02:15–03:00 Build the aggregate CDS modelSecond 3 hours — Application and testing
text
03:00–04:00 Build the SQL lifecycle class
04:00–05:00 Build Reader and Runner
05:00–05:30 Run acceptance tests and fix defects
05:30–06:00 Record test/output evidence, clean up project rows, and complete review questionsIf you attempt optional stretch goals, complete and test them before recording your final evidence and cleaning up project rows.
6. Inspect the existing Travel data
Before creating your own rows, inspect:
text
/DMO/TRAVELUse Database Table Data Preview:
text
Alt+F8Choose three distinct existing Travel IDs.
For each Travel, record:
text
Travel ID
Customer ID
Agency ID
Total Price
Currency Code
Begin Date
End DateFor the easiest aggregate test, choose three Travels using the same currency.
Do not invent Travel IDs.
7. Review Status Domain and Data Element
Create:
text
ZDM_TRVSTAT_###Technical definition:
text
Type: CHAR
Length: 1Fixed values:
| Value | Meaning |
|---|---|
N | New |
R | Reviewed |
A | Approved |
X | Rejected |
Then create:
text
ZDE_TRVSTAT_###Requirements:
- use
ZDM_TRVSTAT_###; - maintain useful labels;
- activate successfully.
Suggested meaning:
text
Travel Review Status8. Design the custom persistence
Create:
text
ZTRVREV_###One row represents:
One budget review for one existing Travel.
The table must be:
text
client-dependent
application data
RAP-ready for later lessonsUse the same table annotation pattern introduced in Lesson 8.
9. Required persistence fields
Your table must contain fields equivalent to:
text
client
uuid
review_no
travel_id
budget_limit
currency_code
status
reviewer_note
local_created_by
local_created_at
local_last_changed_by
local_last_changed_at
last_changed_atThe technical key must be:
text
client + uuidUse:
text
SYSUUID_X16for the UUID.
The Review Number is a business-facing identifier.
It must not replace the UUID as the technical key.
10. Reuse appropriate /DMO/ types
Reuse suitable types from the Flight Reference Scenario.
At minimum, inspect and use appropriate reusable types for:
text
Travel ID
Currency Code
Total PriceFor budget_limit, use the same technical amount type as Travel Total Price where possible.
A suitable choice is:
text
/DMO/TOTAL_PRICEFor the review currency, use the same reusable currency type as Travel:
text
/DMO/CURRENCY_CODEThis keeps the two monetary values technically compatible for later comparison.
11. Amount and currency semantics
The custom table stores:
text
Budget Limit
Currency CodeThe Budget Limit must reference the Currency Code with the correct Database Table semantic annotation.
Conceptually:
text
1500.00
+
EUR
=
1500.00 EURThe Review Currency must match the Travel Currency for this project.
No currency conversion is required.
12. RAP-ready administrative fields
Include:
text
local_created_by
local_created_at
local_last_changed_by
local_last_changed_at
last_changed_atUse the standard RAP reuse Data Elements introduced in Lesson 8.
Direct SQL will not populate these fields automatically.
That is expected at this stage.
13. Persistence design questions
Before continuing, answer:
What does one row represent?
What is the technical primary key?
What is the business-facing identifier?
Why is Travel ID not the technical primary key?
Why must Budget Limit carry a Currency Code?
What currently prevents someone from inserting a Review for a Travel ID that does not exist?
14. Build the review CDS View Entity
Create:
text
ZI_TRAVEL_REVIEW_###Use:
abap
define view entityDo not use obsolete:
text
@AbapCatalog.sqlViewNameExpose:
text
Uuidas the CDS key.
Do not expose the client field as a normal business element.
15. Join Review to Travel
Use an explicit INNER JOIN between:
text
ZTRVREV_###and:
text
/DMO/TRAVELThe relationship is:
text
Review Travel ID
=
Travel Travel IDThe CDS should return only reviews that reference an existing Travel, so use an INNER JOIN rather than a LEFT OUTER JOIN.
Do not make the Reader or Runner perform a second Travel lookup.
16. Required review fields
Expose at least:
text
Uuid
ReviewNo
TravelId
BeginDate
EndDate
CustomerId
AgencyId
TravelTotalPrice
TravelCurrencyCode
BudgetLimit
CurrencyCode
Status
ReviewerNotePreserve amount/currency semantics for:
text
TravelTotalPrice
BudgetLimitFor this project:
text
CurrencyCode
=
the Review currency used for BudgetLimit and aggregationThe SQL lifecycle class must ensure it matches:
text
TravelCurrencyCodebefore inserting a review.
17. Add Customer and Agency associations
Use associations from the Travel data to:
text
/DMO/I_CUSTOMER
/DMO/I_AGENCYUse path expressions to expose useful descriptive information.
For example:
text
Customer name information
Agency nameInspect the target entities in ADT and choose appropriate descriptive elements.
Do not perform separate Customer or Agency SELECTs in the Runner.
Expose at least one association in the CDS element list.
Use Data Preview:
text
F8and verify that Follow Association works.
18. Create BudgetAssessment
Add a calculated CDS element:
text
BudgetAssessmentUse a CDS:
text
CASEexpression.
Required values:
text
WITHIN_BUDGET
AT_LIMIT
OVER_BUDGETRules:
text
TravelTotalPrice < BudgetLimit
→ WITHIN_BUDGET
TravelTotalPrice = BudgetLimit
→ AT_LIMIT
TravelTotalPrice > BudgetLimit
→ OVER_BUDGETDo not calculate the same result again in ABAP.
The comparison is meaningful only because the SQL lifecycle requires:
text
Review Currency
=
Travel CurrencyThis project performs no currency conversion.
The CDS model owns this calculation.
19. Build the summary CDS View Entity
Create:
text
ZI_TRAVEL_REVIEW_SUM_###Business question:
For each Review Status and Currency, how many reviews exist and what monetary values do they represent?
One result row represents:
text
Status
+
CurrencyCodeRequired dimensions:
text
Status
CurrencyCodeRequired measures:
text
ReviewCount
TotalBudgetLimit
TotalTravelValue
LowestBudgetLimit
HighestBudgetLimitUse:
text
COUNT
SUM
MIN
MAX
GROUP BY
HAVING20. Currency-safe aggregation
Do not aggregate monetary values across different currencies.
Therefore:
text
CurrencyCodemust be part of the aggregate grain.
The aggregate amount fields must retain amount/currency semantics.
Apply the currency reference to:
text
TotalBudgetLimit
TotalTravelValue
LowestBudgetLimit
HighestBudgetLimitusing:
text
CurrencyCodeas the reference field.
The summary must keep only groups containing at least:
text
2 reviewsUse:
text
HAVING count( * ) >= 2Do not move this filter into the Runner.
21. Aggregation reasoning
Before continuing, answer:
Why is Status part of the grain?
Why is CurrencyCode part of the grain?
Why can ReviewNo not simply be selected into this grouped result?
What does HAVING filter that WHERE does not?
Why would SUM( BudgetLimit ) be meaningless if different currencies were grouped together?
22. Build the SQL lifecycle class
Create:
text
ZCL_TRVREV_SQL_###Implement:
text
IF_OO_ADT_CLASSRUNThis class is responsible for the project's low-level persistence exercise.
The class must be repeatable.
Before inserting new project rows, it must either:
text
remove previous project rows selectivelyor:
text
detect that they already exist and handle that state safelyDo not allow repeated test runs to create duplicate Review Numbers.
There are two different safety rules in this project:
text
Lifecycle UPDATE / DELETE
→ use the UUID generated for the current run.
Startup / final maintenance cleanup
→ may use the reserved Review Numbers
91000001–91000003 selectively,
because UUIDs from a previous run may no longer be known.A cleanup statement must still target only the reserved project rows. It must never be an unrestricted table-wide DELETE.
23. Project Review Numbers
Use:
text
91000001
91000002
91000003All three should initially have:
text
Status = NChoose Budget Limits so that the project demonstrates all three assessment outcomes:
text
WITHIN_BUDGET
AT_LIMIT
OVER_BUDGETAt least one of the two Reviews that remain after the lifecycle:
text
91000001
91000002must produce:
text
OVER_BUDGETUse:
text
91000003for AT_LIMIT or whichever remaining assessment category is still needed.
The exact values depend on your selected /DMO/TRAVEL rows.
24. Validate Travel before INSERT
Before creating each review, verify the selected Travel exists.
Read enough Travel data to confirm:
text
Travel exists
Total Price
Currency CodeIf the Travel does not exist:
text
write a useful message
do not insert that ReviewBefore inserting, also verify:
text
Review Currency
=
Travel CurrencyNo currency conversion is part of this project.
25. Generate and keep three UUIDs
Generate one fresh UUID for each Review.
Use:
text
CL_SYSTEM_UUIDand handle:
text
CX_UUID_ERRORKeep the generated UUIDs available throughout the lifecycle:
text
uuid_1
uuid_2
uuid_3Do not hard-code UUID values.
26. INSERT and verify
Insert:
text
91000001
91000002
91000003Immediately capture:
text
sy-dbcntafter each relevant database modification.
Verify the inserted rows by reading them back.
Display at least:
text
Review Number
Travel ID
Budget Limit
Currency
StatusDo not treat a successful statement as proof that the resulting data is correct.
Before continuing to UPDATE and DELETE, open:
text
ZI_TRAVEL_REVIEW_###with CDS Data Preview:
text
F8Verify that the three inserted Reviews demonstrate all three calculated outcomes:
text
WITHIN_BUDGET
AT_LIMIT
OVER_BUDGETRecord which Review produced each result:
text
WITHIN_BUDGET:
AT_LIMIT:
OVER_BUDGET:Do this before deleting 91000003, because one of the three assessment examples may belong to that Review.
27. UPDATE using the technical UUID key
Change only:
text
91000001from:
text
N → Rusing:
text
uuid_1in the WHERE condition.
Then change only:
text
91000002from:
text
N → Rusing:
text
uuid_2in the WHERE condition.
Capture:
text
sy-dbcntimmediately after each UPDATE.
Then verify:
text
91000001 = R
91000002 = R
91000003 = NThe Review Number is useful for readable output.
The UUID is the technical identity used for the write.
28. DELETE using the technical UUID key
Delete only:
text
91000003using:
text
uuid_3in the WHERE condition.
Capture:
text
sy-dbcntimmediately.
Then verify:
text
91000001 still exists
91000002 still exists
91000003 no longer existsDo not use an unrestricted DELETE.
29. Expected state before Reader testing
After the SQL lifecycle:
text
91000001 → Reviewed
91000002 → Reviewed
91000003 → deletedThe first two should share:
text
Status = R
same CurrencyCodeTherefore:
text
ZI_TRAVEL_REVIEW_SUM_###should return at least one group where:
text
ReviewCount >= 230. Build the Reader
Create:
text
ZCL_TRVREV_READER_###The Reader must consume your CDS entities.
Normal reads must not use:
text
ZTRVREV_###
/DMO/TRAVELdirectly.
Required operations:
text
GET_ALL
GET_BY_STATUS
GET_OVER_BUDGET
GET_SUMMARYYou decide the exact method signatures.
31. Reader behavior
GET_ALL
Return all review-detail rows.
Use deterministic ordering such as:
text
ReviewNoGET_BY_STATUS
Input:
text
Review StatusReturn reviews matching that status.
GET_OVER_BUDGET
Return only rows where:
text
BudgetAssessment = OVER_BUDGETDo not reproduce the price comparison in ABAP.
GET_SUMMARY
Return rows from:
text
ZI_TRAVEL_REVIEW_SUM_###Use meaningful ordering such as:
text
Status
CurrencyCode32. Build the Runner
Create:
text
ZCL_TRVREV_RUN_###Implement:
text
IF_OO_ADT_CLASSRUNThe Runner must:
- instantiate the Reader;
- display all reviews;
- display Reviewed reviews;
- display Over-Budget reviews;
- display the summary;
- handle empty results clearly.
The Runner must contain no direct:
text
SELECT
INSERT
UPDATE
DELETEagainst project or /DMO/ data.
33. Required output
For each review, display enough information to understand the business case.
At minimum:
text
Review Number
Travel ID
Customer information
Agency information
Begin Date
End Date
Travel Total Price + Currency
Budget Limit + Currency
Budget Assessment
Review StatusExample:
text
Review Number: 91000001
Travel ID: 00000123
Customer: Jane Smith
Agency: Best Travel Agency
Travel Total Price: 1250.00 EUR
Budget Limit: 1500.00 EUR
Assessment: WITHIN_BUDGET
Review Status: RThe exact console formatting is your choice.
34. Required summary output
Display:
text
Status
Currency
Review Count
Total Budget Limit
Total Travel Value
Lowest Budget Limit
Highest Budget LimitExample:
text
Status: R
Currency: EUR
Review Count: 2
Total Budget Limit: 2600.00 EUR
Total Travel Value: 2250.00 EUR
Lowest Budget Limit: 1100.00 EUR
Highest Budget Limit: 1500.00 EUR35. Required test cases
For all five tests, record the input or setup, expected result, and actual result. Capture the detail and summary output before cleaning up project rows.
Test 1 — Detail result
Expected:
text
Remaining review rows appear with:
Travel
Customer
Agency
amounts
BudgetAssessment
Review StatusRecord:
text
Expected:
Actual:Test 2 — Reviewed filter
Input:
text
RExpected:
text
91000001 appears
91000002 appears
91000003 does not appearRecord:
text
Expected:
Actual:Test 3 — Over-Budget filter
Expected:
text
Only reviews whose Travel Total Price is greater
than Budget Limit are returned.If necessary, adjust one Budget Limit and rerun the project lifecycle.
Test 4 — Aggregate summary
Expected:
text
At least one Status + Currency group
has ReviewCount >= 2.Test 5 — Unknown Travel
Test a Travel ID that does not exist.
Expected:
text
useful message
no Review inserted36. Safe-write audit
For each lifecycle UPDATE and DELETE, document:
text
Target Review:
Technical UUID used:
Expected rows affected:
Actual sy-dbcnt:For the normal lifecycle:
text
Expected rows affected = 1For startup or final maintenance cleanup, document:
text
Reserved Review Numbers targeted:
Expected maximum rows affected:
Actual sy-dbcnt:Cleanup may use:
text
91000001
91000002
91000003because UUIDs from an earlier execution may no longer be available.
If a modification can unintentionally affect unrelated rows, the condition is not safe enough.
37. Architecture and design questions
Be prepared to explain:
Why does the Runner not query the database directly?
Why does the Reader consume CDS rather than the persistence table?
Why is the Review-to-Travel relationship modeled in CDS?
Why are Customer and Agency modeled as associations?
Why is BudgetAssessment calculated in CDS?
Why is aggregation performed in CDS?
Why is Review Number separate from UUID?
Why are UPDATE and DELETE performed with UUIDs?
Why is CurrencyCode part of the aggregate grain?
What business rule is still missing even though the SQL lifecycle class checks that Travel exists?
38. Technical constraints
Your solution must:
- use modern
define view entity; - use a custom Domain and Data Element;
- create a client-dependent custom table;
- use
SYSUUID_X16as the technical key type; - use reusable
/DMO/types where appropriate; - preserve amount/currency semantics;
- use at least one explicit join;
- use at least two CDS associations;
- use at least one path expression;
- expose at least one association;
- use a CDS
CASEexpression; - use filtering;
- use
COUNT,SUM,MIN, andMAX; - use
GROUP BY; - use
HAVING; - use safe
INSERT,UPDATE, andDELETE; - capture
sy-dbcnt; - use UUID-based conditions for lifecycle UPDATE and DELETE operations;
- use selective reserved Review Numbers only for startup/final maintenance cleanup;
- prevent duplicate project rows across repeated test runs;
- use one Reader class;
- use one Runner class;
- keep direct database access out of the Runner.
39. What is not part of the project
Do not create:
text
Behavior Definition
Behavior Pool
EML consumer
Projection View
Service Definition
Service Binding
OData service
Fiori Elements UIThose belong to the RAP part of the course.
40. Stretch goals
These are optional.
Complete them only after the mandatory requirements work.
Stretch goal 1 — Status label
Add a calculated field converting:
text
N → New
R → Reviewed
A → Approved
X → RejectedKeep the technical Status field too.
Stretch goal 2 — Travel duration
Add a calculated classification such as:
text
SHORT
MEDIUM
LONGChoose and document the rules.
Stretch goal 3 — Agency summary
Create a second aggregate question:
How many reviews exist per Agency and Currency?
Define the correct output grain before coding.
Stretch goal 4 — Approved-only CDS view
Create an additional View Entity exposing only Approved reviews.
Explain whether the extra reusable CDS entity is justified compared with using a Reader filter.
Stretch goal 5 — Data-integrity thought experiment
Explain what would happen if someone bypassed your SQL lifecycle class and directly inserted:
text
a Review with a Travel ID that does not existDiscuss:
text
what the custom table knows
what the CDS join would show
what business protection is missing41. End-of-project cleanup
After all tests and any optional stretch goals are complete, capture the expected and actual test results, detail and summary output, and safe-write audit for your submission. Then remove only your project rows.
Target:
text
91000001
91000002
91000003Use a selective condition.
For final maintenance cleanup, it is acceptable to target the reserved Review Numbers:
text
91000001
91000002
91000003because the UUIDs from earlier executions may no longer be available.
Do not delete unrelated rows.
Verify with Database Table Data Preview:
text
Alt+F8that your project rows are gone. Record the cleanup condition, actual sy-dbcnt, and verification result as evidence of selective cleanup.
42. Deliverables
Submit the eight required objects, using your own suffix:
text
ZDM_TRVSTAT_###
ZDE_TRVSTAT_###
ZTRVREV_###
ZI_TRAVEL_REVIEW_###
ZI_TRAVEL_REVIEW_SUM_###
ZCL_TRVREV_SQL_###
ZCL_TRVREV_READER_###
ZCL_TRVREV_RUN_###Include:
- the actual names of all objects you created;
- an architecture diagram and explanation covering the detail and aggregate read paths and the controlled write path;
- answers to the persistence, aggregation, architecture, and design questions;
- the input or setup, expected result, and actual result for all five required tests, including captured detail and summary output;
- the safe-write audit, including the targeted UUIDs or reserved Review Numbers, expected rows affected, and actual
sy-dbcnt; - evidence of selective final cleanup and verification that your project rows are gone.
Capture test and output evidence before deleting the project rows. Record cleanup evidence after the cleanup runs.
43. Submission checklist
Persistence
CDS detail model
CDS summary
SQL lifecycle
Reader / Runner
Documentation
44. Evaluation criteria
The project should be evaluated primarily on correctness and design rather than code volume.
Suggested weighting:
| Area | Weight |
|---|---|
| Custom persistence, types, and key design | 20% |
| CDS relationships, calculations, and currency-safe aggregation | 25% |
| Safe SQL lifecycle, repeatability, and selective cleanup | 25% |
| Reader/Runner architecture and output | 15% |
| Testing, evidence, and design explanation | 15% |
Optional stretch goals do not replace the mandatory requirements or their test evidence.
45. Final goal
Completing this project demonstrates that you can build this read architecture:
text
Runner
↓
Reader
↓
CDS Data Model
↓
Custom + SAP-owned persistenceYou also understand today's controlled write path:
text
SQL lifecycle class
↓
ABAP SQL
↓
Custom persistenceThat direct write path is useful for learning persistence mechanics.
It is not the architecture we want future consumers to use once the table becomes part of a RAP Business Object.
The next architectural step is:
text
Consumer
↓
EML / Business Service
↓
RAP Behavior
↓
CDS Business Object
↓
PersistenceRAP adds a transactional business contract around the data.