In this exercise, you will adapt the data model of the newly generated RAP business object ZR_ONLINESHOP_###
- 3.1 - Enhance the data model
- 3.2 - Adapt the behavior definition
- 3.3 - Define constants for lhc_onlineshop
- 3.4 - Define determinations
- 3.5 - Define validations
- Summary
- Appendix
In this step we will use a value help ZI_PRODUCT_VH_REUSE
that has been provided for your in this workshop in the demo system. In addition some fields that are accessible via this association (e.g. the product description) will be included in the data model.
When working with a system provided as part of a SAP workshop this view will be provided. If you try out this script in your own SAP S/4HANA 2022 on prem system you would have to create this view manually.
The source code of ZI_PRODUCT_VH_REUSE can be found here:
ZI_PRODUCT_VH_REUSE
Click to expand!
- Open the cds view ZR_ONLINESHOP_###
- add an association to the released CDS view for product data to it.
association [1..1] to ZI_PRODUCT_VH_REUSE as _Product on $projection.OrderItemID = _Product.Product
- add the fields
_Product.ProductGroup
,_Product.ProductText
,_Product.BaseUnit
and the association_Product
to the field list.
, _Product.ProductGroup, _Product.ProductText, _Product.BaseUnit, _Product
-
Open the cds view
ZC_ONLINESHOP_###
- add a value help for the field
OrderItemID
.
- add a value help for the field
@Consumption.valueHelpDefinition: [{ entity: { name: 'ZI_PRODUCT_VH_REUSE', element: 'Product' }, useForValidation: true } ] OrderItemID,
- add the fields
ProductGroup
,ProductText
,BaseUnit
,CreatedAt
and the association_Product
to the field list.
, ProductGroup, ProductText, BaseUnit, _Product
Note:
When a starter project has been generated the fieldCreatedAt
has already been added to the projection view.
You will thus get the following error message: The name CreatedAt is not unique
In this case simply delete or comment out the duplicateCreatedAt
entry.
-
Replace the complete source code of the Metadata Extension File ZC_ONLINESHOP_###.
We have prepared a meta data extension file that nicely groups your fields.
-
Replace the placeholder
###
with your group number and save and activate your changes.
@Metadata.layer: #CUSTOMER @UI: { headerInfo: { typeName: 'Order', typeNamePlural: 'Orders', title: { value: 'OrderID', type: #STANDARD } // , // imageUrl: 'Attachment' ,description: { value: 'ProductText', label: 'Order', type: #STANDARD } }, presentationVariant: [ { sortOrder: [{ by: 'OrderID', direction: #DESC }], visualizations: [{type: #AS_LINEITEM}] } ] } annotate view ZC_ONLINESHOP_### with { @UI.facet: [ //Facet for Order general information { label : 'General Information', id : 'GeneralInfoFacet', purpose : #STANDARD, type : #COLLECTION, position : 10 } //Facet for Product information ,{ label : 'Product', id : 'ProductFacet', purpose : #STANDARD, type : #COLLECTION, position : 20 } //Facet for Product Quantity and Delivery Date ,{ label : 'Quantity and Delivery Date', id : 'QuantityDateFacet', purpose : #STANDARD, type : #COLLECTION, position : 30 } //Facet for PR information ,{ label : 'Purchase Requisition', id : 'PurchReqFacet', purpose : #STANDARD, type : #COLLECTION, position : 40 } // #FIELDGROUP_REFERENCE - define the fieldgroups ,{ //label : 'Order Info', id : 'OrderInfo', purpose : #STANDARD, parentId : 'GeneralInfoFacet', type : #FIELDGROUP_REFERENCE, targetQualifier: 'OrderInfo', position : 10 } ,{ //label : 'Product', id : 'Product', purpose : #STANDARD, parentId : 'ProductFacet', type : #FIELDGROUP_REFERENCE, targetQualifier: 'Product', position : 20 } ,{ // label : 'Quantity and Date', id : 'QuantityDate', purpose : #STANDARD, parentId : 'QuantityDateFacet', type : #FIELDGROUP_REFERENCE, targetQualifier: 'QuantityDate', position : 30 } ,{ //label : 'Purchase Requisition', id : 'PurchReq', purpose : #STANDARD, parentId : 'PurchReqFacet', type : #FIELDGROUP_REFERENCE, targetQualifier : 'PurchReq', position : 40 } ] @UI.hidden: true OrderUUID; @UI.lineItem: [{ position: 10, label: 'Order ID', importance: #HIGH }] @UI.selectionField: [{ position: 10 }] @UI.fieldGroup: [{ qualifier: 'OrderInfo', position: 10, label: 'Order ID' }] //OrderInfo OrderID; @EndUserText.label: 'Product' @UI.lineItem: [{ position: 20, label: 'Product', importance: #HIGH }] @UI.selectionField: [{ position: 20 }] @UI.fieldGroup: [{ qualifier: 'Product', position: 40, label: 'Product' }] //Product OrderItemID; @UI.lineItem: [{ position: 21, label: 'Product description', importance: #HIGH }] @UI.fieldGroup: [{ qualifier: 'Product', position: 55, label: 'Product description' }] //Product ProductText; @UI.identification: [{ position: 50, label: 'Quantity' } ] @UI.fieldGroup: [{ qualifier: 'QuantityDate', position: 50, label: 'Quantity' }] //QuantityDate OrderItemQuantity; @UI.fieldGroup: [{ qualifier: 'Product', position: 70, label: 'Valuation price per unit' }] //Product OrderItemPrice; @UI.fieldGroup: [{ qualifier: 'OrderInfo', position: 20, label: 'Total value' }] //OrderInfo TotalPrice; @UI.fieldGroup: [{ qualifier: 'QuantityDate', position: 52, label: 'Delivery date' }] //QuantityDate DeliveryDate; @UI.lineItem: [{ position: 85, label: 'Order status', criticality: 'OverallStatusIndicator', importance: #HIGH }] @UI.fieldGroup: [{ qualifier: 'OrderInfo', position: 30, criticality: 'OverallStatusIndicator', label: 'Order status' }] //OrderInfo OverallStatus; @UI.hidden: true OverallStatusIndicator; @UI.lineItem: [{ position: 84, label: 'Purchase requisition nbr.', importance: #HIGH }, { type: #FOR_ACTION, dataAction: 'createPurchaseRequisition', label: 'Create purchase requisition' } ] //Submit Order | Create purchase requisition| @UI.identification: [ { type: #FOR_ACTION, dataAction: 'createPurchaseRequisition', label: 'Create purchase requisition' }] //Submit Order | Create purchase requisition| @UI.fieldGroup: [{ qualifier: 'PurchReq', position: 22, criticality: 'OverallStatusIndicator', label: 'Purchase requisition number' }] //PurchReq PurchaseRequisition; @UI.fieldGroup: [{ qualifier: 'PurchReq',position: 31, label: 'Purchase reqn creation date' }] //PurchReq PurchRqnCreationDate; @UI.multiLineText: true @UI.fieldGroup: [{ qualifier: 'OrderInfo', position: 60, label: 'Notes' }] //OrderInfo Notes; @UI.fieldGroup: [{ qualifier: 'OrderInfo', position: 70, label: 'Order creation date' }] //OrderInfo CreatedAt; @UI.hidden: true LocalLastChangedAt; }
Since we have changed the field list of the projection view ZC_ONLINESHOP_###
we have to regenerate the draft table.
In the behavior definition we will also set several fields as readonly.
For fields that are read-only and that are not read from the value help we have to create determinations.
Click to expand!
-
Open the behavior definition
ZR_ONLINESHOP_###
.- Select the draft table name and press Ctrl + 1 to show the quick fix.
- Select the quick fix that offers you to regenerate the draft table
- Change the data elements of the fields
productgroup
andproducttext
to build in typesabap.char(9)
andabap.char(40)
.productgroup : abap.char(9); producttext : abap.char(40);
- Activate the regenerated draft table.
Hint: If you try to activate the draft table without changing the data element definition you will get the following error messages: The use of Data Element PRODUCTDESCRIPTION is not permitted.
The use of Data Element PRODUCTGROUP is not permitted.-
Add the following list of fields to mark them as read-only.
field ( readonly ) OrderID, OverallStatus, TotalPrice, Currency, //order OrderItemPrice, ProductGroup, ProductText, BaseUnit, //product; PurchaseRequisition, PurchRqnCreationDate; //purchase rqn
Note:
When a starter project has already been generated you might get the error message:
"readonly" is specified more than once for "OrderID".
In this case simply comment out the duplicate entryOrderID,
-
Check the UI using the Fiori Elements preview.
As a preparation for implementing determinations and validations we will add constants in the local handler class lhc_onlineshop
of the the behavior implementation class ZBP_R_ONLINESHOP_###
so that these variables can be used by all implementations.
Click to expand!
-
Navigate to the behavior implementation class
ZBP_R_ONLINESHOP_###
in the Project Explorer and then click on the tab Local Types. -
Start the implementation by adding the following constants in the private section of your local handler class
lhc_onlineshop
.
CLASS lcl_OnlineShop DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. CONSTANTS: BEGIN OF is_draft, false TYPE abp_behv_flag VALUE '00', " active (not draft) true TYPE abp_behv_flag VALUE '01', " draft END OF is_draft. CONSTANTS: BEGIN OF c_overall_status, new TYPE string VALUE 'New / Composing', new_code TYPE int1 VALUE 2, "'New / Composing' submitted TYPE string VALUE 'Submitted / Approved', submitted_code TYPE int1 VALUE 3, "'Submitted / Approved' END OF c_overall_status.
We now have to create several determinations that will do the following:
- Set initial values for certain fields (e.g. the delivery date)
- Calculate the total price when the OrderItemID or the OrderItemQuantity is changed by the user
Click to expand!
- Add the following determinations to your behavior definition.
determination setInitialOrderValues on modify { create; } determination updateProductDetails on modify { field OrderItemID; } determination calculateTotalPrice on modify { create; field OrderItemID; field OrderItemQuantity; }
- Use the quick fix Ctrl+1 to generate the appropriate methods in the behavior definition class.
- Add the following code snippet to implement the determination
calculateTotalPrice
. The code updates the fieldTotalPrice
when the fieldOrderItemID
and thus theOrderItemPrice
has changed or if the fieldOrderItemQuantity
has changed.
METHOD calculateTotalPrice. DATA total_price TYPE ZR_OnlineShop_###-TotalPrice. " read transfered instances READ ENTITIES OF ZR_OnlineShop_### IN LOCAL MODE ENTITY OnlineShop FIELDS ( OrderID TotalPrice ) WITH CORRESPONDING #( keys ) RESULT DATA(OnlineShops). LOOP AT OnlineShops ASSIGNING FIELD-SYMBOL(<OnlineShop>). " calculate total value <OnlineShop>-TotalPrice = <OnlineShop>-OrderItemPrice * <OnlineShop>-OrderItemQuantity. ENDLOOP. "update instances MODIFY ENTITIES OF ZR_OnlineShop_### IN LOCAL MODE ENTITY OnlineShop UPDATE FIELDS ( TotalPrice ) WITH VALUE #( FOR OnlineShop IN OnlineShops ( %tky = OnlineShop-%tky TotalPrice = OnlineShop-TotalPrice ) ). ENDMETHOD.
- Add the following code snippet to implement the determination
setInitialOrderValues
. The code selects the next weekday in two weeks as a delivery day, it sets the initial status and it calculates a semantic key for the fieldOrderID
METHOD setInitialOrderValues. DATA delivery_date TYPE I_PurchaseReqnItemTP-DeliveryDate. "read transfered instances via EML READ ENTITIES OF ZR_OnlineShop_### IN LOCAL MODE ENTITY OnlineShop FIELDS ( OrderID OverallStatus DeliveryDate ) WITH CORRESPONDING #( keys ) RESULT DATA(OnlineShops). "delete entries with assigned order ID DELETE OnlineShops WHERE OrderID IS NOT INITIAL. CHECK OnlineShops IS NOT INITIAL. " ** ABAP logic to determine order IDs and delivery date** " get max order ID from the relevant active and draft table entries SELECT MAX( order_id ) FROM zaonlineshop_### INTO @DATA(max_order_id). "active table SELECT SINGLE FROM zdonlineshop_### FIELDS MAX( orderid ) INTO @DATA(max_orderid_draft). "draft table IF max_orderid_draft > max_order_id. max_order_id = max_orderid_draft. ENDIF. "set delivery date proposal cl_scal_api=>date_compute_day( EXPORTING iv_date = cl_abap_context_info=>get_system_date( ) IMPORTING ev_weekday_number = DATA(weekday_number) ev_weekday_name = DATA(weekday_name) ). CASE weekday_number. WHEN 6. delivery_date = cl_abap_context_info=>get_system_date( ) + 16. WHEN 7. delivery_date = cl_abap_context_info=>get_system_date( ) + 15. WHEN OTHERS. delivery_date = cl_abap_context_info=>get_system_date( ) + 14. ENDCASE. "set initial values of new instances via EML MODIFY ENTITIES OF ZR_OnlineShop_### IN LOCAL MODE ENTITY OnlineShop UPDATE FIELDS ( OrderID OverallStatus DeliveryDate OrderItemPrice ) WITH VALUE #( FOR OnlineShop IN OnlineShops INDEX INTO i ( %tky = OnlineShop-%tky OrderID = max_order_id + i OverallStatus = c_overall_status-new "'New / Composing' DeliveryDate = delivery_date ) ). ENDMETHOD.
- Add the following code snippet to implement the determination
updateProductDetails
. The code selects data from the value helpzi_product_vh_reuse
.
METHOD updateProductDetails. "read transfered instances READ ENTITIES OF ZR_OnlineShop_### IN LOCAL MODE ENTITY OnlineShop FIELDS ( OrderItemID ) WITH CORRESPONDING #( keys ) RESULT DATA(OnlineShops). "read and set product details LOOP AT OnlineShops ASSIGNING FIELD-SYMBOL(<OnlineShop>). "read and set relevant product information SELECT SINGLE * FROM zi_product_vh_reuse WHERE product = @<OnlineShop>-OrderItemID INTO @DATA(product). <OnlineShop>-OrderItemPrice = product-price. <OnlineShop>-Currency = product-Currency. ENDLOOP. "update instances MODIFY ENTITIES OF ZR_OnlineShop_### IN LOCAL MODE ENTITY OnlineShop UPDATE FIELDS ( OrderItemPrice Currency ) WITH VALUE #( FOR OnlineShop IN OnlineShops INDEX INTO i ( %tky = OnlineShop-%tky OrderItemPrice = OnlineShop-OrderItemPrice Currency = OnlineShop-Currency ) ). ENDMETHOD.
- Open the service binding
ZUI_ONLINESHOP_O4_###
to test your implementation by using the ADT Fiori preview.
Finally we have to implement validations to make sure that the data that is sent to the API is valid.
In addition there will be a check that make sure that not too many items can be ordered
Click to expand!
- Add the following code snippet into your behavior definition, so that the following three validations will be added:
validation checkOrderedItem on save { create; field OrderItemID; } validation checkOrderedQuantity on save { create; field OrderItemQuantity; } validation checkDeliveryDate on save { create; field DeliveryDate; }
- Use the quick fix Ctrl+1 to generate the appropriate methods in the behavior definition class.
-
Add the validations to the draft determine action Prepare
// draft determine action Prepare; draft determine action Prepare { validation checkOrderedItem; validation checkOrderedQuantity; validation checkDeliveryDate; }
For more details about the syntax you can check the online help.
-
Validation checkOrderedItem
METHOD checkOrderedItem. "read relevant order instance data READ ENTITIES OF ZR_OnlineShop_### IN LOCAL MODE ENTITY OnlineShop FIELDS ( OrderID OrderItemID ) WITH CORRESPONDING #( keys ) RESULT DATA(OnlineShops). DATA products TYPE SORTED TABLE OF zi_product_vh_reuse WITH UNIQUE KEY Product. "optimization of DB select: extract distinct non-initial product IDs products = CORRESPONDING #( OnlineShops DISCARDING DUPLICATES MAPPING Product = OrderItemID EXCEPT * ). DELETE products WHERE Product IS INITIAL. IF products IS NOT INITIAL. "check if product ID exists SELECT FROM zi_product_vh_reuse FIELDS product FOR ALL ENTRIES IN @OnlineShops WHERE product = @OnlineShops-OrderItemID INTO TABLE @DATA(valid_ordereditem). ENDIF. "raise msg for non existing and initial order id LOOP AT OnlineShops INTO DATA(OnlineShop). APPEND VALUE #( %tky = OnlineShop-%tky %state_area = 'VALIDATE_PRODUCTID' ) TO reported-onlineshop. IF OnlineShop-OrderItemID IS INITIAL. APPEND VALUE #( %tky = OnlineShop-%tky ) TO failed-onlineshop. APPEND VALUE #( %tky = OnlineShop-%tky %state_area = 'VALIDATE_PRODUCTID' %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Select the product to be ordered| ) %element-orderitemid = if_abap_behv=>mk-on ) TO reported-onlineshop. ELSEIF OnlineShop-OrderItemID IS NOT INITIAL AND NOT line_exists( valid_ordereditem[ product = OnlineShop-OrderItemID ] ). APPEND VALUE #( %tky = OnlineShop-%tky ) TO failed-onlineshop. APPEND VALUE #( %tky = OnlineShop-%tky %state_area = 'VALIDATE_PRODUCTID' %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Product unknown| ) %element-orderitemid = if_abap_behv=>mk-on ) TO reported-onlineshop. ENDIF. ENDLOOP. ENDMETHOD.
- validation checkDeliveryDate
METHOD checkdeliverydate. READ ENTITIES OF zr_onlineshop_### IN LOCAL MODE ENTITY OnlineShop FIELDS ( DeliveryDate ) WITH CORRESPONDING #( keys ) RESULT DATA(OnlineOrders). DATA(today_date) = cl_abap_context_info=>get_system_date( ). LOOP AT OnlineOrders INTO DATA(online_order). cl_scal_api=>date_compute_day( EXPORTING iv_date = online_order-DeliveryDate IMPORTING ev_weekday_number = DATA(weekday_number) ev_weekday_name = DATA(weekday_name) ). "raise msg if no delivery date is selected IF online_order-DeliveryDate IS INITIAL OR online_order-DeliveryDate = ' '. APPEND VALUE #( %tky = online_order-%tky ) TO failed-onlineshop. APPEND VALUE #( %tky = online_order-%tky %state_area = 'VALIDATE_DELIVERYDATE' %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'Select a delivery date' ) %element-deliverydate = if_abap_behv=>mk-on ) TO reported-onlineshop. "raise msg if selected delivery date is less than 14 days from today ELSEIF ( ( online_order-DeliveryDate ) - today_date ) < 14. APPEND VALUE #( %tky = online_order-%tky ) TO failed-onlineshop. APPEND VALUE #( %tky = online_order-%tky %state_area = 'VALIDATE_DELIVERYDATE' %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Delivery date must be at least 14 days from today.| ) %element-deliverydate = if_abap_behv=>mk-on ) TO reported-onlineshop. ELSEIF weekday_number = 5 OR weekday_number = 6. APPEND VALUE #( %tky = online_order-%tky ) TO failed-onlineshop. APPEND VALUE #( %tky = online_order-%tky %state_area = 'VALIDATE_DELIVERYDATE' %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = | No delivery on a weekend ({ weekday_name })| ) %element-deliverydate = if_abap_behv=>mk-on ) TO reported-onlineshop. ENDIF. ENDLOOP. ENDMETHOD.
- validation checkOrderedQuantity
METHOD checkOrderedQuantity. "read relevant order instance data READ ENTITIES OF ZR_OnlineShop_### IN LOCAL MODE ENTITY OnlineShop FIELDS ( OrderID OrderItemID OrderItemQuantity ) WITH CORRESPONDING #( keys ) RESULT DATA(OnlineShops). "raise msg if 0 > qty <= 10 LOOP AT OnlineShops INTO DATA(OnlineShop). APPEND VALUE #( %tky = OnlineShop-%tky %state_area = 'VALIDATE_QUANTITY' ) TO reported-onlineshop. IF OnlineShop-OrderItemQuantity IS INITIAL OR OnlineShop-OrderItemQuantity = ' ' OR OnlineShop-OrderItemQuantity <= 0. APPEND VALUE #( %tky = OnlineShop-%tky ) TO failed-onlineshop. APPEND VALUE #( %tky = OnlineShop-%tky %state_area = 'VALIDATE_QUANTITY' %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |Enter a valid quantity (up to 10)| ) %element-orderitemquantity = if_abap_behv=>mk-on ) TO reported-onlineshop. ELSEIF OnlineShop-OrderItemQuantity > 10. APPEND VALUE #( %tky = OnlineShop-%tky ) TO failed-onlineshop. APPEND VALUE #( %tky = OnlineShop-%tky %state_area = 'VALIDATE_QUANTITY' %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = |More than 10 items cannot be ordered| ) %element-orderitemquantity = if_abap_behv=>mk-on ) TO reported-onlineshop. ENDIF. ENDLOOP. ENDMETHOD.
You have implemented checks (validations) and determinations to prepare the data that is sent via EML to the released API.
you can continue with the next exercise - Exercise 4: Developer extensibility.