Validations and Determinations — Business Rules in RAP
Learn to implement RAP validations (reject bad data) and determinations (auto-calculate values) for TechMart's Sales Order app.
Validations and Determinations — Business Rules in RAP
What You'll Learn
- Validations: reject invalid data before save
- Determinations: auto-fill or calculate fields
- Trigger timing:
on savevson modify - Implementing TechMart business rules
- Error messages and the
failed/reportedpattern
Validations — Reject Bad Data
A validation checks data and rejects the save if something is wrong. It doesn't change data — it only accepts or rejects.
Step 1: Declare in behavior definition
define behavior for ZI_TM_SalesOrder alias SalesOrder
{
...
validation validateCustomer on save { create; update;
field CustomerId; }
validation validateStatus on save { create; update;
field Status; }
}
on save means the validation runs when the user clicks Save. field CustomerId means it triggers when that field changes.
Step 2: Implement in handler class
METHOD validateCustomer.
" Read the orders being validated
READ ENTITIES OF ZI_TM_SalesOrder IN LOCAL MODE
ENTITY SalesOrder
FIELDS ( CustomerId )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
" Check each order
LOOP AT lt_orders INTO DATA(ls_order).
IF ls_order-CustomerId IS INITIAL.
" Reject — Customer ID is required
APPEND VALUE #( %tky = ls_order-%tky ) TO failed-salesorder.
APPEND VALUE #( %tky = ls_order-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Customer ID is required| )
%element-CustomerId = if_abap_behv=>mk-on
) TO reported-salesorder.
ELSE.
" Check if customer exists
SELECT SINGLE @abap_true FROM ztm_customer
WHERE customer_id = @ls_order-CustomerId
INTO @DATA(lv_exists).
IF lv_exists <> abap_true.
APPEND VALUE #( %tky = ls_order-%tky ) TO failed-salesorder.
APPEND VALUE #( %tky = ls_order-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Customer { ls_order-CustomerId } does not exist| )
%element-CustomerId = if_abap_behv=>mk-on
) TO reported-salesorder.
ENDIF.
ENDIF.
ENDLOOP.
ENDMETHOD.
The %element-CustomerId = if_abap_behv=>mk-on highlights the specific field in the Fiori UI with a red error marker.
Determinations — Auto-Calculate Values
A determination automatically sets field values when data changes. It modifies data — the opposite of a validation.
Step 1: Declare in behavior definition
define behavior for ZI_TM_SalesOrder alias SalesOrder
{
...
determination determineOrderId on save { create; }
determination determineTotal on modify { field Currency;
create; }
}
on save runs once when saving. on modify runs immediately when a field changes (useful for real-time calculations in draft mode).
Step 2: Implement — Generate a human-readable Order ID
METHOD determineOrderId.
" Read orders that need an ID
READ ENTITIES OF ZI_TM_SalesOrder IN LOCAL MODE
ENTITY SalesOrder
FIELDS ( OrderId )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
" Only set ID if it's empty (new orders)
DELETE lt_orders WHERE OrderId IS NOT INITIAL.
CHECK lt_orders IS NOT INITIAL.
" Get the next number
SELECT MAX( order_id ) FROM ztm_salesorder INTO @DATA(lv_max_id).
DATA(lv_next) = lv_max_id + 1.
" Update each new order
MODIFY ENTITIES OF ZI_TM_SalesOrder IN LOCAL MODE
ENTITY SalesOrder
UPDATE FIELDS ( OrderId )
WITH VALUE #(
FOR ls_order IN lt_orders (
%tky = ls_order-%tky
OrderId = lv_next
) )
REPORTED DATA(ls_reported).
ENDMETHOD.
Step 3: Implement — Auto-calculate total from line items
METHOD determineTotal.
" Read orders and their items
READ ENTITIES OF ZI_TM_SalesOrder IN LOCAL MODE
ENTITY SalesOrder
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
READ ENTITIES OF ZI_TM_SalesOrder IN LOCAL MODE
ENTITY SalesOrder BY \_Items
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_items).
" Calculate totals
LOOP AT lt_orders INTO DATA(ls_order).
DATA(lv_total) = REDUCE ztm_amount(
INIT sum = CONV ztm_amount( 0 )
FOR ls_item IN lt_items WHERE ( OrderUUID = ls_order-OrderUUID )
NEXT sum = sum + ( ls_item-Quantity * ls_item-Price )
).
" Update the order total
MODIFY ENTITIES OF ZI_TM_SalesOrder IN LOCAL MODE
ENTITY SalesOrder
UPDATE FIELDS ( TotalAmount )
WITH VALUE #( (
%tky = ls_order-%tky
TotalAmount = lv_total
) )
REPORTED DATA(ls_rep).
ENDLOOP.
ENDMETHOD.
On Save vs On Modify
| Timing | When It Runs | Best For |
|---|---|---|
on save |
User clicks Save | Final checks, number generation, validation |
on modify |
Immediately on field change | Real-time calculations, auto-fill, UI responsiveness |
on modify is more responsive but runs more often. Keep the logic lightweight.
Common Mistakes
- Validations that modify data — Validations should only check and report. Use determinations to change data.
- Determinations that reject — Determinations should only set values. Use validations to reject.
- Not using IN LOCAL MODE — Inside handlers, always read/modify with
IN LOCAL MODEto avoid authorization loops. - Heavy logic in on modify —
on modifyruns on every field change. If your determination does a complex aggregation, it'll fire constantly during editing. Useon savefor expensive operations. - Missing %tky — Always use
%tky(transactional key) from thekeysparameter. Using%keycan fail in draft scenarios.
Key Takeaways
- Validations reject bad data (check + report errors). Determinations auto-fill data (read + modify).
on saveruns at save time.on modifyruns immediately on field change.- Always append to
failedandreportedin validations. Use%elementto highlight specific fields. - Determinations use
MODIFY ENTITIES IN LOCAL MODEto update the current BO data. - Keep
on modifydeterminations lightweight; useon savefor expensive operations.
Next Lesson
Validations prevent bad data. Determinations auto-calculate values. But users also need custom operations — "Approve this order", "Copy this order", "Generate a report." In Lesson 20, we'll build RAP Actions — custom buttons that execute business logic.