So far in our RAP series, we've built a solid foundation. We created a Billing Document Header app with full CRUD operations, then extended it with a child Item entity using composition and associations.
Now it's time to add the real business logic. Any transactional application needs rules that prevent invalid data from being saved. In this blog, we'll implement two critical mechanisms in our billing document app: validation and precheck. These mechanisms ensure data integrity and provide early feedback to users, making our app more robust and user-friendly.
Why Do We Need Validation and Precheck?
Before diving into the code, let's understand the problem we're solving.
Imagine a user creates a billing document with:
-
An invalid customer ID that doesn't exist
-
A negative or zero net amount
-
Missing required fields
Without validation and precheck, these invalid records would make it to the database, causing downstream issues. Validation and precheck prevent this by enforcing business rules at the application layer.
Precheck acts as a gatekeeper. It performs quick, early checks before the operation even begins. Think of it as a bouncer at the door checking IDs before letting customers in.
Validation is more thorough. It runs during the save sequence and checks complex business logic after data has been entered into the transactional buffer. Think of it as a final quality check before shipping a product.
Implementing Validation and Precheck in Our Billing Document App
Let's now add both mechanisms to our billing document application. We'll add:
-
A precheck on the CREATE operation to verify the customer exists
-
A validation on the NetAmount field to ensure it meets business rules
Step 1: Update the Behavior Definition
Open your behavior definition for the Billing Document Header (ZSAC_I_BILL_HEADER) and update it as follows:
managed implementation in class ZBP_SAC_R_BILL_HEADTP unique;
strict ( 2 );
define behavior for zsac_i_bill_header
persistent table zsac_bill_header
lock master
authorization master ( instance )
{
create ( precheck );
update;
delete;
field ( mandatory : create, readonly : update ) BillId;
// Validation definition
validation validateAmount on save { field NetAmount; create; update; }
mapping for zsac_bill_header
{
BillId = bill_id;
BillType = bill_type;
BillDate = bill_date;
CustomerId = customer_id;
NetAmount = net_amount;
Currency = currency;
SalesOrg = sales_org;
CreatedBy = createdby;
CreateDat = createdat;
LastChangedBy = lastchangedby;
LastChangeDat = lastchangedat;
LocalLastChangeDat = locallastchangedat;
}
}
Notice two new additions:
create ( precheck ); - This declares that the CREATE operation should include a precheck phase
validation validateAmount on save { field NetAmount; create; update; } - This declares a validation that triggers when NetAmount is created or updated
Step 2: Implement the Behavior Class
Now, let's implement both the precheck and validation methods in our behavior implementation class. Open ZBP_SAC_I_BILL_HEADER and add the following:
CLASS lhc_zsac_r_bill_headtp DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS validateamount FOR VALIDATE ON SAVE
IMPORTING keys FOR zsac_r_bill_headtp~validateamount.
METHODS precheck_create FOR PRECHECK
IMPORTING entities FOR CREATE zsac_r_bill_headtp.
ENDCLASS.
CLASS lhc_zsac_r_bill_headtp IMPLEMENTATION.
METHOD validateAmount.
READ ENTITIES OF zsac_r_bill_headtp IN LOCAL MODE
ENTITY zsac_r_bill_headtp
FIELDS ( NetAmount ) WITH CORRESPONDING #( keys )
RESULT DATA(lt_billdoc).
LOOP AT lt_billdoc INTO DATA(ls_billdoc).
IF ls_billdoc-NetAmount IS NOT INITIAL AND ls_billdoc-NetAmount < 1000.
APPEND VALUE #( %tky = ls_billdoc-%tky ) TO failed-zsac_r_bill_headtp.
APPEND VALUE #( %tky = ls_billdoc-%tky
%element-NetAmount = if_abap_behv=>mk-on
%msg = new_message_with_text( severity = if_abap_behv_message=>severity-error
text = 'Net Amount cannot be less than 1000' )
) TO reported-zsac_r_bill_headtp.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD precheck_create.
* Pre-check will not work after draft implementation. Why?
LOOP AT entities INTO DATA(ls_entity).
IF ls_entity-CustomerId IS NOT INITIAL.
SELECT SINGLE * FROM /DMO/I_Customer
WHERE CustomerID = @ls_entity-CustomerId
INTO @DATA(ls_customer).
IF sy-subrc NE 0.
APPEND VALUE #( %key = ls_entity-%key ) TO failed-zsac_r_bill_headtp.
APPEND VALUE #( %key = ls_entity-%key
%element-CustomerId = if_abap_behv=>mk-on
%msg = new_message_with_text( severity = if_abap_behv_message=>severity-error
text = 'Customer does not exists' )
) TO reported-zsac_r_bill_headtp.
ENDIF.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
Code Explanation
Implementation Class Breakdown
Precheck Method (precheck_create):
-
Purpose: Performs quick customer existance checks before expensive business logic
-
Timing: Executed early in the processing cycle
-
Logic: Simple database lookup at table
/DMO/I_Customer -
Performance: Lightweight operation, no database reads for business data
-
Error Handling: Immediately blocks processing if customer does not exists
Validation Method (validateAmount):
-
Purpose: Validates business rules and data consistency
-
Timing: Executed during the save sequence after business logic
-
Logic: Reads entity data and performs business rule validation for minumum amount
-
Performance: Can perform complex operations including database access
-
Error Handling: Prevents save operation if validation fails
Common Error Handling Pattern: Both methods use the same pattern for error reporting:
-
Add failed keys to
failed-zsac_r_bill_headtptable -
Add descriptive error messages to
reported-zsac_r_bill_headtptable -
Use appropriate message severity levels
Key Differences
| Aspect | Validation | Precheck |
| Execution Timing | Trigger during the save sequence (specifically, in the check_before_save phase), after data has entered the transactional buffer. | Trigger before operation execution (CREATE, UPDATE, DELETE or any custom action) before the transactional buffer is updated. |
| Purpose | Validate business rules and data consistency | Check if operation is allowed to proceed |
| Trigger Point | Automatically triggered during save during create and/or update operation | Triggered before each operation (if specified) |
| Error Handling | Can prevent save operation | Can prevent operation from starting |
| Performance Impact | Executed once during save as it's part of save sequence | Executed for each operation as it's part of interaction phase |
| Method Signature | METHODS validateamount FOR VALIDATE ON SAVE IMPORTING keys FOR zsac_r_bill_headtp~validateAmount. |
METHODS precheck_create FOR PRECHECK IMPORTING entities FOR CREATE zsac_r_bill_headtp. |
| Trigger Specificity | Trigger only when specified field is changed. | Trigger when the operation is executed. Prechecks are not applicable on field level. |
When to Use Each
Use Precheck when:
-
Performing authorization checks
-
Quick data format validation
-
Early rejection of obviously invalid requests
-
Avoiding expensive operations for unauthorized users
Use Validation when:
-
Checking business rules
-
Validating data consistency across entities
-
Performing complex validation logic
-
Ensuring data integrity before save
Important Note: Validation and Precheck Without Draft
The implementation shown above works in a non-draft scenario. Notice the precheck method includes a comment: * Pre-check will not work after draft implementation. Why?
This is important. In our past blog, we enabled draft in our billing document app. Draft changes how precheck behaves because precheck validates against the database, but in a draft scenario, the "active" data is still in the database while draft data is in separate draft tables.
Your Task: Observe Precheck Behavior in Draft-Enabled Apps
Now that you understand how validation and precheck work in a non-draft scenario, here's a practical exercise:
-
Take your current billing document app (which now has validation and precheck) and enable draft capabilities
-
Test precheck behavior in draft mode:
-
Create a new billing document with an invalid customer ID
-
Observe what happens when you try to save the draft
-
Compare this with the precheck behavior you see now (before draft)
This exercise will deepen your understanding of how draft fundamentally changes the RAP processing flow and when validations occur.
If you enjoyed this walkthrough and want to explore more SAP RAP scenarios, from simple CRUD apps to real-world enhancements and API integrations, check out the full blog series:
π Explore the SAP RAP Series
Each post is crafted to be beginner-friendly yet practical enough for working professionals trying to get hands-on with ABAP RAP.
Thanks for reading βΊοΈ
