Skip to content

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 fields

The 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 handling

The 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/TRAVEL

Your 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:      R

3. Required architecture ​

Normal read path:

text
Runner
  ↓
Reader
  ↓
Review CDS
  ↓
Custom Review Table + /DMO/TRAVEL
  ↓
Customer / Agency associations

Aggregate read path:

text
Runner
  ↓
Reader
  ↓
Summary CDS

Controlled 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 model

Second 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 questions

If 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/TRAVEL

Use Database Table Data Preview:

text
Alt+F8

Choose three distinct existing Travel IDs.

For each Travel, record:

text
Travel ID
Customer ID
Agency ID
Total Price
Currency Code
Begin Date
End Date

For 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: 1

Fixed values:

ValueMeaning
NNew
RReviewed
AApproved
XRejected

Then create:

text
ZDE_TRVSTAT_###

Requirements:

  • use ZDM_TRVSTAT_###;
  • maintain useful labels;
  • activate successfully.

Suggested meaning:

text
Travel Review Status

8. 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 lessons

Use 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_at

The technical key must be:

text
client + uuid

Use:

text
SYSUUID_X16

for 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 Price

For budget_limit, use the same technical amount type as Travel Total Price where possible.

A suitable choice is:

text
/DMO/TOTAL_PRICE

For the review currency, use the same reusable currency type as Travel:

text
/DMO/CURRENCY_CODE

This keeps the two monetary values technically compatible for later comparison.


11. Amount and currency semantics ​

The custom table stores:

text
Budget Limit
Currency Code

The Budget Limit must reference the Currency Code with the correct Database Table semantic annotation.

Conceptually:

text
1500.00
+
EUR
=
1500.00 EUR

The 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_at

Use 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 entity

Do not use obsolete:

text
@AbapCatalog.sqlViewName

Expose:

text
Uuid

as 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/TRAVEL

The relationship is:

text
Review Travel ID
=
Travel Travel ID

The 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
ReviewerNote

Preserve amount/currency semantics for:

text
TravelTotalPrice
BudgetLimit

For this project:

text
CurrencyCode
=
the Review currency used for BudgetLimit and aggregation

The SQL lifecycle class must ensure it matches:

text
TravelCurrencyCode

before inserting a review.


17. Add Customer and Agency associations ​

Use associations from the Travel data to:

text
/DMO/I_CUSTOMER
/DMO/I_AGENCY

Use path expressions to expose useful descriptive information.

For example:

text
Customer name information
Agency name

Inspect 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
F8

and verify that Follow Association works.


18. Create BudgetAssessment ​

Add a calculated CDS element:

text
BudgetAssessment

Use a CDS:

text
CASE

expression.

Required values:

text
WITHIN_BUDGET
AT_LIMIT
OVER_BUDGET

Rules:

text
TravelTotalPrice < BudgetLimit
→ WITHIN_BUDGET

TravelTotalPrice = BudgetLimit
→ AT_LIMIT

TravelTotalPrice > BudgetLimit
→ OVER_BUDGET

Do not calculate the same result again in ABAP.

The comparison is meaningful only because the SQL lifecycle requires:

text
Review Currency
=
Travel Currency

This 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
+
CurrencyCode

Required dimensions:

text
Status
CurrencyCode

Required measures:

text
ReviewCount
TotalBudgetLimit
TotalTravelValue
LowestBudgetLimit
HighestBudgetLimit

Use:

text
COUNT
SUM
MIN
MAX
GROUP BY
HAVING

20. Currency-safe aggregation ​

Do not aggregate monetary values across different currencies.

Therefore:

text
CurrencyCode

must be part of the aggregate grain.

The aggregate amount fields must retain amount/currency semantics.

Apply the currency reference to:

text
TotalBudgetLimit
TotalTravelValue
LowestBudgetLimit
HighestBudgetLimit

using:

text
CurrencyCode

as the reference field.

The summary must keep only groups containing at least:

text
2 reviews

Use:

text
HAVING count( * ) >= 2

Do 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_CLASSRUN

This 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 selectively

or:

text
detect that they already exist and handle that state safely

Do 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
91000003

All three should initially have:

text
Status = N

Choose Budget Limits so that the project demonstrates all three assessment outcomes:

text
WITHIN_BUDGET
AT_LIMIT
OVER_BUDGET

At least one of the two Reviews that remain after the lifecycle:

text
91000001
91000002

must produce:

text
OVER_BUDGET

Use:

text
91000003

for 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 Code

If the Travel does not exist:

text
write a useful message
do not insert that Review

Before inserting, also verify:

text
Review Currency
=
Travel Currency

No currency conversion is part of this project.


25. Generate and keep three UUIDs ​

Generate one fresh UUID for each Review.

Use:

text
CL_SYSTEM_UUID

and handle:

text
CX_UUID_ERROR

Keep the generated UUIDs available throughout the lifecycle:

text
uuid_1
uuid_2
uuid_3

Do not hard-code UUID values.


26. INSERT and verify ​

Insert:

text
91000001
91000002
91000003

Immediately capture:

text
sy-dbcnt

after each relevant database modification.

Verify the inserted rows by reading them back.

Display at least:

text
Review Number
Travel ID
Budget Limit
Currency
Status

Do 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
F8

Verify that the three inserted Reviews demonstrate all three calculated outcomes:

text
WITHIN_BUDGET
AT_LIMIT
OVER_BUDGET

Record 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
91000001

from:

text
N → R

using:

text
uuid_1

in the WHERE condition.

Then change only:

text
91000002

from:

text
N → R

using:

text
uuid_2

in the WHERE condition.

Capture:

text
sy-dbcnt

immediately after each UPDATE.

Then verify:

text
91000001 = R
91000002 = R
91000003 = N

The 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
91000003

using:

text
uuid_3

in the WHERE condition.

Capture:

text
sy-dbcnt

immediately.

Then verify:

text
91000001 still exists
91000002 still exists
91000003 no longer exists

Do not use an unrestricted DELETE.


29. Expected state before Reader testing ​

After the SQL lifecycle:

text
91000001 → Reviewed
91000002 → Reviewed
91000003 → deleted

The first two should share:

text
Status = R
same CurrencyCode

Therefore:

text
ZI_TRAVEL_REVIEW_SUM_###

should return at least one group where:

text
ReviewCount >= 2

30. Build the Reader ​

Create:

text
ZCL_TRVREV_READER_###

The Reader must consume your CDS entities.

Normal reads must not use:

text
ZTRVREV_###
/DMO/TRAVEL

directly.

Required operations:

text
GET_ALL
GET_BY_STATUS
GET_OVER_BUDGET
GET_SUMMARY

You decide the exact method signatures.


31. Reader behavior ​

GET_ALL ​

Return all review-detail rows.

Use deterministic ordering such as:

text
ReviewNo

GET_BY_STATUS ​

Input:

text
Review Status

Return reviews matching that status.

GET_OVER_BUDGET ​

Return only rows where:

text
BudgetAssessment = OVER_BUDGET

Do not reproduce the price comparison in ABAP.

GET_SUMMARY ​

Return rows from:

text
ZI_TRAVEL_REVIEW_SUM_###

Use meaningful ordering such as:

text
Status
CurrencyCode

32. Build the Runner ​

Create:

text
ZCL_TRVREV_RUN_###

Implement:

text
IF_OO_ADT_CLASSRUN

The Runner must:

  1. instantiate the Reader;
  2. display all reviews;
  3. display Reviewed reviews;
  4. display Over-Budget reviews;
  5. display the summary;
  6. handle empty results clearly.

The Runner must contain no direct:

text
SELECT
INSERT
UPDATE
DELETE

against 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 Status

Example:

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:      R

The 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 Limit

Example:

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 EUR

35. 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 Status

Record:

text
Expected:
Actual:

Test 2 — Reviewed filter ​

Input:

text
R

Expected:

text
91000001 appears
91000002 appears
91000003 does not appear

Record:

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 inserted

36. 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 = 1

For startup or final maintenance cleanup, document:

text
Reserved Review Numbers targeted:
Expected maximum rows affected:
Actual sy-dbcnt:

Cleanup may use:

text
91000001
91000002
91000003

because 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_X16 as 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 CASE expression;
  • use filtering;
  • use COUNT, SUM, MIN, and MAX;
  • use GROUP BY;
  • use HAVING;
  • use safe INSERT, UPDATE, and DELETE;
  • 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 UI

Those 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 → Rejected

Keep the technical Status field too.

Stretch goal 2 — Travel duration ​

Add a calculated classification such as:

text
SHORT
MEDIUM
LONG

Choose 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 exist

Discuss:

text
what the custom table knows
what the CDS join would show
what business protection is missing

41. 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
91000003

Use a selective condition.

For final maintenance cleanup, it is acceptable to target the reserved Review Numbers:

text
91000001
91000002
91000003

because the UUIDs from earlier executions may no longer be available.

Do not delete unrelated rows.

Verify with Database Table Data Preview:

text
Alt+F8

that 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:

AreaWeight
Custom persistence, types, and key design20%
CDS relationships, calculations, and currency-safe aggregation25%
Safe SQL lifecycle, repeatability, and selective cleanup25%
Reader/Runner architecture and output15%
Testing, evidence, and design explanation15%

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 persistence

You also understand today's controlled write path:

text
SQL lifecycle class
      ↓
ABAP SQL
      ↓
Custom persistence

That 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
   ↓
Persistence

RAP adds a transactional business contract around the data.

Continue with Lesson 10 — RAP Foundations →