第 137 页 / 共 465 页
4.1 Core Data Services Views
calculation based on basic interface layer CDS views but is still intended for reuse. For
now, we’ll simply add all fields of the basic purchase document view to the selection
list of the new view.
Because the overall price calculation is already a calculation with moderate complex-
ity, we should develop this calculation using test-driven development (TDD) to verify
and safeguard our developed logic in an automated and repeatable way. Developing
tests in a test-driven fashion means that before we implement the actual calcula-
tions, we implement tests. TDD will allow us to incrementally implement calcula-
tions and will give us confidence that already-implemented logic of previous steps
isn’t breaking with subsequent steps or releases. It also has the advantage of intro-
ducing testing already during the development process so that test automation isn’t
simply omitted after a feature is working. In the end, we’ll also have a safety net for
future changes that might introduce regression bugs. Unit tests for CDS views are
based on ABAP Unit and the CDS Test Double Framework. With the CDS Test Double
Framework, it’s possible to dynamically create and configure test stubs or doubles for
views or tables that the CDS view under test depends on.
It’s very easy to create unit tests for CDS views because the ABAP development tools
in Eclipse already provide good unit test support. As shown in Figure 4.9, you can sim-
ply right-click on a CDS view to create a new global ABAP Unit test class for this view
by choosing New Test Class.
Figure 4.9 Generating a Unit Test Skeleton Class for a CDS View by Right-Clicking on the
CDS View in the Eclipse Project Explorer
This will bring up the test creation dialog wizard. On the first screen of the wizard, you
must provide a name for the global ABAP test class, the package, and a description
(Figure 4.10). The read-only CDS Under Test field denotes for which CDS view you’re
creating the test. It’s good practice to keep the tests close to the tested code so by
default the tests are put into the same package as the CDS view under test. To go to
the next screen of the wizard, click the Next button.
137
第 138 页 / 共 465 页
4 Developing an SAP Fiori Elements List Report and Object Page
Figure 4.10 Creating a New Test Class for the Purchase Documents Overall Price CDS View
(Z_I_PurchDocOverallPrice)
The next screen allows you to select the CDS views or tables your view under test
depends on and that are required as test doubles for the test cases you want to
implement (Figure 4.11). During test execution, the test doubles replace the actual
views or database tables and make it possible to dynamically provide different test
data for different test cases. In general, there are two test types: Unit Test, which
only considers first level dependencies, and Hierarchy Test, which allows test double
creation for the whole stack of CDS views the view under test depends on. In our
case, we’ll simply click on Next because we want to create a unit test and both basic
interface views our view under test depends on (Z_I_PurchaseDocument and Z_I_Pur-
chaseDocumentItem) are already preselected and required as dependencies for double
creation in our tests.
In addition to generating a global ABAP Unit test class and providing the required
dependencies for test double creation, the test class creation wizard also supports
138
第 139 页 / 共 465 页
4.1 Core Data Services Views
the test doubles with simple form-based test data creation (Figure 4.12). For our first
test case, which we’ll call overall_price_no_items, we won’t create any test data for
purchase document items and just a single table entry for purchase documents.
Figure 4.11 Dependency Selection Screen for the CDS Test
Figure 4.12 Form-Based Test Data Creation for CDS Test Doubles the View
Under Test Depends On
139
第 140 页 / 共 465 页
4 Developing an SAP Fiori Elements List Report and Object Page
When you click on Next, the newly generated global ABAP class will open in the
Eclipse source code editor. However, in the global test class, you’ll only find an empty
class definition and implementation. To look at the generated local ABAP Unit test
class containing the test fixture, data creation, and test methods, we must select the
Test Classes tab in the source code editor (Figure 4.13), which contains our just-
defined test method overall_price_no_items and a prepare_test_data_set method
that will insert our previously specified purchase document test data into the pur-
chase document test double of the test environment.
Figure 4.13 Generated Local ABAP Unit Test Class
Test data defined using the test data creation wizard won’t be inserted in the origi-
nal database tables of the dependent on views. Instead, the generated skeleton test
class will set up a separate test environment in its class_setup method that won’t
affect any productive tables and their data. The stubbed dependent on views and
their data will only be available during the runtime of the test method and are by
default cleared before each test execution in the setup test fixture method environ-
ment->clear_doubles( ).
140
第 141 页 / 共 465 页
4.1 Core Data Services Views
For our first test case, we haven’t created any items test data but only a single pur-
chase document and inserted it into the test environment. If a purchase document
has no items, the overall purchase document price should be 0. We can verify this by
adapting the generated test method, which doesn’t check for the correct value of the
overallprice field yet. In general, the test procedure for CDS views is always as fol-
lows:
1. Create test data for the views the view under test depends on (arrange or given
step).
2. Select data from the CDS view under test (act or when step).
3. Ensure that a calculated value has the expected value (assert or then step).
To check values calculated by a CDS view, we can use the standard CL_ABAP_UNIT_
ASSERT class, which provides many static methods for comparing expected and actual
values. The simple test ensuring that purchase documents without items have an
overall price of 0 is shown in Listing 4.5.
METHOD overall_price_no_items.
prepare_testdata_set_no_items( ).
SELECT * FROM z_i_purchdocoverallprice INTO TABLE @act_results.
cl_abap_unit_assert=>assert_equals( exp = 0 act = act_results[ 1 ]-
overallprice ).
ENDMETHOD.
Listing 4.5 ABAP Unit Test for the Overall Price of a Purchase Document without Items
It follows the test procedure: we first create test data in an analogous manner to the
generated test data creation method, then we select the data from the CDS view
under test using Open SQL, and finally we check whether the selected data has the
expected calculated value. Currently, the test will fail with a syntax error because we
haven’t yet added the field to the view. To fix the test, we can simply add the field to
the view like this:
0 as as OverallPrice,
Note
You can either execute unit tests by right-clicking on the global class and selecting
Run As (cid:2) ABAP Unit Test or by using the shortcut (Ctrl)+(Shift)+(F10).
141
| Note You can either execute unit tests by right-clicking on the global class and selecting (cid:2) (Ctrl) (Shift) (F10) |
| Run As ABAP Unit Test or by using the shortcut + + . |
第 142 页 / 共 465 页
4 Developing an SAP Fiori Elements List Report and Object Page
In our next test case, we’ll create a new test data creation method that has two items
with an overall item price of 10 (Listing 4.6).
METHOD prepare_testdata_set.
"Prepare test data for 'z_i_purchasedocument'
lt_z_i_purchasedocument = VALUE #(
(
purchasedocument = '1'
description = 'Doc with no items'
) ).
environment->insert_test_data( i_data = lt_z_i_purchasedocument ).
"Prepare test data for 'z_i_purchasedocumentitem'
lt_z_i_purchasedocumentitem = VALUE #(
( purchasedocument = '1' purchasedocumentitem = '1' overallitemprice =
'10' )
( purchasedocument = '1' purchasedocumentitem = '2' overallitemprice =
'10' ) ).
environment->insert_test_data( i_data = lt_z_i_purchasedocumentitem ).
ENDMETHOD.
Listing 4.6 Test Double Creation for a Purchase Document with Two Purchase Document
Items
Our second test method then will look as shown in Listing 4.7.
METHOD overall_price.
prepare_testdata_set( ).
SELECT * FROM z_i_purchdocoverallprice INTO TABLE @act_results.
cl_abap_unit_assert=>assert_equals( exp = 20 act = act_results[ 1 ]-
overallprice ).
ENDMETHOD.
Listing 4.7 ABAP Unit Test for the Overall Price of a Purchase Document with Two Purchase
Document Items
To fix this test, we must now actually aggregate the overall item prices of a purchase
document’s items. We’ll first add the purchase document item overall price field to
the view using a path expression:
_PurchaseDocumentItem.OverallItemPrice
142
第 143 页 / 共 465 页
4.1 Core Data Services Views
However, this will increase the cardinality of the data returned by the view from 1 pur-
chase document to 2 purchase documents with different overall item prices. To reduce
the cardinality, we must group the overall item prices by their common purchase doc-
ument fields and aggregate the overall item prices using a SUM aggregate function. We
assume that all purchase document items have the same currency. Listing 4.8 shows
an excerpt of the CDS view calculating the overall price of a purchase document.
@AbapCatalog.sqlViewName: 'ZIPURCHDOCPRICE'
@EndUserText.label: 'Purchase Document Item'
@VDM.viewType: #COMPOSITE
define view Z_I_PurchDocOverallPrice
as select from Z_I_PurchaseDocument
{
key PurchaseDocument,
@Semantics.amount.currencyCode: 'Currency'
@DefaultAggregation: #NONE
@ObjectModel.foreignKey.association: '_Currency'
sum( _PurchaseDocumentItem.OverallItemPrice )as OverallPrice,
@Semantics.currencyCode: true
_PurchaseDocumentItem.Currency,
…
// Associations
…
_Currency
}
group by
PurchaseDocument,
_PurchaseDocumentItem.Currency
…
Listing 4.8 Composite Interface View Calculating the Overall Price of a Purchase Document
Note
Field values calculated by a mathematical operator (+, -, /, etc.), aggregate function
(max, min, sum, etc.), or a CASE expression can’t be used in the same view for additional
calculations. In such cases, you always must create an additional view on top to
make use of the calculated value or field.
143
| Note Field values calculated by a mathematical operator (+, -, /, etc.), aggregate function (max, min, sum, etc.), or a CASE expression can’t be used in the same view for additional calculations. In such cases, you always must create an additional view on top to |
| make use of the calculated value or field. |
第 144 页 / 共 465 页
4 Developing an SAP Fiori Elements List Report and Object Page
Calculating the Overall Price Criticality by Introducing a Consumption View and
Using Test-Driven Development
Another calculated field that we’ll later need in the UI of our application is an indica-
tor for the overall price criticality. We’ll use the calculated criticality field to highlight
overall prices we consider as critical for the user checking purchase documents. The
logic to be implemented is shown in Table 4.1; for instance, if the overall price of the
document is below 1,000, we don’t consider this critical, which will be indicated by
the number 3. On the other hand, if the price is higher than 10,000, we consider this
very critical and indicate this by the number 1.
Overall Price Criticality
>= 0 and < 1,000 3 – Not critical
>= 1,000 and <= 10,000 2 – Medium criticality
> 10,000 1 – High criticality
Any other value 0 – Undefined
Table 4.1 Overall Price to Criticality Indicator Mappings
To implement this field, we’ll put a final consumption view on top of the previous
composite view, which we’ll name Z_C_PurchaseDocumentLrp where C_ stands for con-
sumption view. We’ll again do the implementation in a test-driven manner and cre-
ate a test case for each corner case before we implement the actual calculation logic
inside the CDS view. Listing 4.9 shows the unit test implementation for an overall
price of 1,000. In this case, we expect an output of 2 for the field indicating a medium
criticality.
METHOD price_1k_medium_critical.
prepare_testdata_price( 1000 ).
SELECT * FROM z_c_purchasedocumentlrp INTO TABLE @act_results.
cl_abap_unit_assert=>assert_equals( exp = 2 act = act_results[ 1 ]-
overallpricecriticality ).
ENDMETHOD.
Listing 4.9 ABAP Unit Test for the Overall Price (1,000) to Criticality (2) Mapping
The other tests will look the same except for the price parameter and the expected
value. The result of the overall price criticality field implementation in the CDS DDL
then looks like Listing 4.10.
144
| Overall Price | Criticality |
|---|---|
| >= 0 and < 1,000 | 3 – Not critical |
| >= 1,000 and <= 10,000 | 2 – Medium criticality |
| > 10,000 | 1 – High criticality |
| Any other value | 0 – Undefined |
第 145 页 / 共 465 页
4.1 Core Data Services Views
case when OverallPrice >= 0 and OverallPrice < 1000 then 3
when OverallPrice >= 1000 and OverallPrice <= 10000 then 2
when OverallPrice > 10000 then 1
else 0 end as OverallPriceCriticality,
Listing 4.10 Calculation Logic for the OverallPriceCriticality Field in the Z_C_PurchaseDocu-
mentLrp CDS Consumption View
Calculating Whether Approval Is Required or Not Using Test-Driven Development
Another simple indicator that we’ll later need in the UI indicates whether the pur-
chase document requires approval by the purchasing manager or not. We define that
a purchase document needs approval when the overall price exceeds 1,000. For this
simple indicator, we just need two tests: one where the overall price is 1,000 and
doesn’t require approval, and another where the overall price is 1,001 and does
requires approval. The implementation result looks like Listing 4.11.
case when OverallPrice > 1000 then 'X' else '' end as IsApprovalRequired,
Listing 4.11 Calculation Logic for the IsApprovalRequired Field in the Z_C_Purchase-
DocumentLrp CDS Consumption View
Figure 4.14 shows all successfully executed unit tests of the CDS view under test for
the overall price criticality field as well as for the approval required field.
Figure 4.14 Successfully Executed Unit Tests for the Overall Price Criticality and
Approval Required Fields
So far, after having added the calculated fields, the VDM of our purchasing app looks
like that shown in Figure 4.15. The database layer contains the transparent tables for
145
第 146 页 / 共 465 页
4 Developing an SAP Fiori Elements List Report and Object Page
the main entities of the application: purchase documents and items. Additionally, we
created separate tables for document priorities, status, and purchasing organizations
to store their keys and texts in a normalized and data redundancy-free way. On top of
the database tables, we introduced a basic view layer with a basic interface view for
each database table. Priorities, statuses, and purchasing organizations are linked to
the purchase document via foreign key associations. Documents and items also have
associations to the other respective entity. On top of the purchase document and
item views, we put a composite interface view to calculate the overall price for docu-
ments by aggregating the overall item prices of a document’s items. Finally, the top-
most layer consists of two consumption views for purchase documents and items. In
the consumption view for purchase documents, we introduced two more calculated
fields for the overall price criticality and whether approval of the document is
required or not.
ZPURCHDOC ZPURCH ZPURCH ZPURCH
ITEM DOCUMENT DOCPRIO DOCSTATUS
Figure 4.15 VDM of the Purchasing App Consisting of Four Different Layers
4.1.5 Adding Data Control Language Files
The access to data exposed by CDS views can be restricted using classic ABAP autho-
rization objects and the DCL of CDS. To do so, we first must create an authorization
field using Transaction SU20 and an authorization class and object using Transaction
SU21. For our app scenario, we’ll create an authorization object for the purchasing
146
noitpmusnoC
etisopmoC
cisaB
selbaT
Z_C_Purchase Z_C_Purchase
DocumentLRP DocumentItemLRP Selection Association
Z_I_PurchDoc
OverallPrice
Z_I_Purchase Z_I_Purchase Z_I_Purchase Z_I_Purchase Z_I_Purchasing
DocumentItem Document DocumentPriority DocumentStatus Organization
ZPURCHORG
| etisopmoC Z_I_PurchDoc OverallPrice | |||||
第 147 页 / 共 465 页
4.1 Core Data Services Views
organization, which is also a field exposed by the purchase document CDS view. We
only want to show the end users purchase documents of the purchasing organiza-
tions they are assigned to as they aren’t supposed to get access to all purchase docu-
ments in the system by default. The authorization object for the purchasing
organization is shown in Figure 4.16. It contains two authorization fields: one for the
actual purchasing organization (ZPURCHORGA) and one for the allowed activity (ACTVT),
for instance, “01” for create, “02” for change, and “03” for display.
Figure 4.16 Overview of the Connections between Authorization Object, CDS View, and
Access Control
To enforce access restrictions based on authorization objects, we must create an
access control using the DCL of CDS and link it to the CDS view. You can create a CDS
access control in Eclipse by carrying out the following steps:
147
第 148 页 / 共 465 页
4 Developing an SAP Fiori Elements List Report and Object Page
1. Open the ABAP perspective in the ABAP development tools in Eclipse.
2. In the ABAP project, select the package node in which you want to create the table
via the Project Explorer view.
3. Open the context menu by right-clicking on the package, and select New (cid:2) Other
ABAP Repository (cid:2) Core Data Services (cid:2) Access Control.
4. When the creation wizard opens, enter a Name and Description. By convention,
the DCL source name will be identical to the DCL source name. In our case, we’ll
therefore name the DCL file Z_I_PurchaseDocument.
5. You can select one of the provided templates to create a skeleton DCL access con-
trol, for instance, Define Instance Role, which will create a simple DCL skeleton file.
6. When you click the Finish button in the wizard, the source code editor will open so
you can now define access rules and conditions.
Note
To enable the access control from the CDS view side, you also must add the @Access-
Control.authorizationCheck: #CHECK annotation to the CDS view. This will lead to
implicit access control when data is selected from the CDS view using Open SQL.
The full access control definition for our purchase document basic interface view is
shown in Listing 4.12. By convention, the name of the role inside the DCL will be iden-
tical to the CDS view name in camel case. The role definition contains a Transaction
PFCG condition that will be evaluated each time the view is directly accessed using
Open SQL. The left side of the condition consists of the PurchasingOrganization field
of the CDS view where the right side consists of the name of the authorization object
(ZPURCHORGA) as the first parameter followed by its authorization fields (ZPURCHORGA,
ACTVT) to be evaluated.
@EndUserText.label: 'Role for Z_I_PurchaseDocument'
@MappingRole: true
define role Z_I_PurchaseDocument {
grant select
on Z_I_PurchaseDocument
where
( PurchasingOrganization ) =
aspect pfcg_auth(
ZPURCHORGA, // Authorization object
148
| Note To enable the access control from the CDS view side, you also must add the @Access- Control.authorizationCheck: #CHECK annotation to the CDS view. This will lead to |
| implicit access control when data is selected from the CDS view using Open SQL. |
第 149 页 / 共 465 页
4.1 Core Data Services Views
ZPURCHORGA , // Matched auth. object field
ACTVT = '03' // Fixed auth. object field
) ;
}
Listing 4.12 Access Control Definition for the Z_I_PurchaseDocument CDS View Using the
Data Control Language
During runtime, when performing an Open SQL select on the view, the user’s
assigned Transaction PFCG roles and authorizations will be fetched and compared
with the data provided by the CDS view. In our example, the user might have been
assigned to purchasing organization Org1, which will lead to the following transpar-
ent enhancement of the CDS SELECT statement.
SELECT * FROM Z_I_PurchaseDocument WHERE PurchaseOrganization = ‘Org1’;
Additionally, our Transaction PFCG condition also contains a hard-coded check
(ACTVT=’03’), which will ensure that the user has display rights. If his assigned ACTVT
value isn’t equal to “03,” the user won’t see any data at all. Figure 4.16 summarizes the
connections between the CDS view, the DCL role, and the authorization object.
To avoid repeating the same access rules and conditions for similar views, access con-
trols can be inherited. It’s also possible to combine inherited and new access condi-
tions. If we simply would like to reuse our access control file created for the purchase
document’s basic interface view in our composite interface view, introduced for cal-
culating the overall price, we could create the following access control shown in List-
ing 4.13 without having to repeat the Transaction PFCG condition.
@EndUserText.label: 'Purchase Document Overall Price'
@MappingRole: true
define role Z_I_Purchdocoverallprice {
grant select
on Z_I_PurchDocOverallPrice
inherit Z_I_PurchaseDocument;
}
Listing 4.13 Example of an Inherited Access Control
To assign certain authorizations to users, you must create a Transaction PFCG role
and include authorization objects and their values either manually or via an applica-
tion having assigned authorization default values provided via Transaction SU22.
149
第 150 页 / 共 465 页
4 Developing an SAP Fiori Elements List Report and Object Page
Finally, for the authorizations to become effective, the user must be assigned to the
Transaction PFCG role.
Note
DCL access controls are only considered on direct CDS view access. They are ignored
when queried indirectly via other CDS views. Access controls can be tested using
Transaction SACMSEL, which makes it possible to evaluate CDS access controls for
different users.
4.2 Transactional Processing with the Business Object
Processing Framework
So far, our app consists of a VDM based on a CDS view that provides access to pur-
chase documents and items using Open SQL, the query language of CDS. However, it
isn’t yet possible to create any documents or items. As of now, the application is com-
pletely read-only. If any transactional logic, for instance, creating, deleting, or updat-
ing (CUD) entities, is required, this is where the BOPF comes into play as, since SAP
NetWeaver 7.52, SP 02, it’s possible to generate a BOPF business object and its node
structure from a CDS data model using CDS business object model annotations
(@ObjectModel). Consequently, you’ll learn in this section how you can add transac-
tional capabilities to a read-only CDS-based VDM.
4.2.1 Generating a BOPF Business Object from a CDS Model
Because we want to keep our basic interface views reusable for all application scenar-
ios, we won’t add the semantic business object annotations required for generating
the BOPF business object to existing basic interface views; instead, we’ll create new
composite interface views on top of the basic interface views. We’ll create a transac-
tional view for purchase documents as well as for purchase document items, which
we’ll name Z_I_PurchaseDocumentTP and Z_I_PurchaseDocumentItemTP, where TP stands
for transactional processing. In both views, we add all fields of the underlying view to
the selection field list to do a simple projection of the underlying view.
To generate a BOPF business object based on the two transactional or business object
CDS views, we must add several @ObjectModel annotations to the views. In the pur-
chase document root view (Z_I_PurchaseDocumentTP), we must add the annotations
shown in Listing 4.14 on the header level.
150
| Note DCL access controls are only considered on direct CDS view access. They are ignored when queried indirectly via other CDS views. Access controls can be tested using Transaction SACMSEL, which makes it possible to evaluate CDS access controls for |
| different users. |
第 151 页 / 共 465 页
4.2 Transactional Processing with the Business Object Processing Framework
@ObjectModel.modelCategory: #BUSINESS_OBJECT
@ObjectModel.compositionRoot: true
@ObjectModel.transactionalProcessingEnabled: true
@ObjectModel.writeActivePersistence: 'ZPURCHDOCUMENT'
@ObjectModel.createEnabled: true
@ObjectModel.deleteEnabled: true
@ObjectModel.updateEnabled: true
define view Z_I_PurchaseDocumentTP
…
Listing 4.14 BOPF Business Object Root Node Header Annotations
The @ObjectModel.modelCategory: #BUSINESS_OBJECT annotation indicates that this
view represents a BOPF business object. The annotation doesn’t have any runtime
effect but helps in categorizing the view as a BOPF view, for instance, in view browsers.
A business object is represented as a hierarchical tree of nodes, and by adding the
@ObjectModel.compositionRoot: true annotation, a view is characterized as the root
view of the node hierarchy. To enable the transactional runtime, the annotation
@ObjectModel.transactionalProcessingEnabled: true must be added together with
the @ObjectModel.writeActivePersistence: 'ZPURCHDOCUMENT' annotation, which spec-
ifies where created or updated data must be stored or deleted from. In our case, we’re
directly modifying the data without creating any intermediary draft entities. The
@ObjectModel.transactionalProcessingEnabled: true can only be added to the root
view of the BOPF node hierarchy. With the @ObjectModel.createEnabled, @Object-
Model.deleteEnabled, and @ObjectModel.updateEnabled annotations, we specify which
operations will be supported by the transactional runtime. Because we set everything
to true, all operations are supported by our purchase documents root node. Addition-
ally, to build up the business object node hierarchy, we must also annotate the asso-
ciation to the purchase document items view as a child association (Listing 4.15).
/* Associations */
@ObjectModel.association.type: #TO_COMPOSITION_CHILD
_PurchaseDocumentItem,
Listing 4.15 BOPF Business Object Root Node Child Association
Furthermore, we must add several annotations on the header level in the purchase
document item view (Z_I_PurchaseDocumentItemTP) (Listing 4.16).
151
第 152 页 / 共 465 页
4 Developing an SAP Fiori Elements List Report and Object Page
@ObjectModel.writeActivePersistence: 'ZPURCHDOCITEM'
@ObjectModel.createEnabled: true
@ObjectModel.updateEnabled: true
@ObjectModel.deleteEnabled: true
define view Z_I_PurchaseDocumentItemTP
…
Listing 4.16 BOPF Business Object Child Node Header Annotations
Because purchase document items are stored in a different database table than purchase
documents, we must also provide the active persistence of items using the @Object-
Model.writeActivePersistence annotation. As we did for purchase documents, we also
enable all transactional operations for this business object node. The association back
to the purchase document must be annotated as a parent association. Moreover, views
that don’t represent the root node of the hierarchy must also specify their root node
association, which, in our case, is equivalent to the parent association (Listing 4.17).
/* Associations */
@ObjectModel.association.type: [ #TO_COMPOSITION_PARENT, #TO_COMPOSITION_
ROOT ]
_PurchaseDocument,
Listing 4.17 BOPF Business Object Child Node Parent and Root Association
Note
The field names of the business object CDS view and the field names of the underly-
ing database table must match (except for camel case). If the table is a legacy table
containing cryptic field names, you can hide those behind a classic database view
placed between the database table and the CDS view. You can find more information
on how to do this in the “Mapping CDS View-Elements onto Table Fields” guide at
http://bit.ly/2IdnOMo.
On activation of the transactional or business object views, the BOPF runtime will
generate a business object together with several structures and table types based on
the CDS view field structures. The generated BOPF object has the same name as the
root CDS view, in our case, Z_I_PurchaseDocumentTP. As you can see in Figure 4.17, the
node structure of the BOPF object exactly reflects our annotated CDS data model
hierarchy with the purchase document node being the root node and the purchase
document item node being the child node.
152
| Note The field names of the business object CDS view and the field names of the underly- ing database table must match (except for camel case). If the table is a legacy table containing cryptic field names, you can hide those behind a classic database view placed between the database table and the CDS view. You can find more information on how to do this in the “Mapping CDS View-Elements onto Table Fields” guide at |
| http://bit.ly/2IdnOMo. |
第 153 页 / 共 465 页
4.2 Transactional Processing with the Business Object Processing Framework
Figure 4.17 Generated BOPF Node Structure
To incorporate the new business object views into our VDM, we’ll make our existing
consumption views to select from the new business object views instead of the previ-
ous Interface views. The final VDM of our application looks like Figure 4.18. With the
new transactional business object views, we’ve introduced a new layer of composite
views below the consumption view layer.
ZPURCHDOC ZPURCH ZPURCHDOC ZPURCHDOC
ITEM DOCUMENT PRIO STATUS
Figure 4.18 Final VDM of the Application
To enable our existing consumption views for transactional processing via the gener-
ated BOPF business object, we also must add some of the @ObjectModel annotations
153
noitpmusnoC
etisopmoC
cisaB
selbaT
Selection Association
Z_C_Purchase Z_C_Purchase
DocumentLRP DocumentItemLRP
Z_I_Purchase Z_I_Purchase
DocumentTP DocumentItemTP
Z_I_PurchDoc
OverallPrice
Z_I_Purchase Z_I_Purchase Z_I_Purchase Z_I_Purchase Z_I_Purchasing
DocumentItem Document DocumentPriority DocumentStatus Organization
ZPURCHORG
| cisaB Z_I_Purchase Z_I_Purchase Z_I_Purchase Z_I_Purchase Z_I_Purchasing DocumentItem Document DocumentPriority DocumentStatus Organization | |||||||
|---|---|---|---|---|---|---|---|
| Z_I_Purchasing Organization | |||||||
第 154 页 / 共 465 页
4 Developing an SAP Fiori Elements List Report and Object Page
on the header level. To the purchase document consumption view (Z_C_PurchaseDoc-
umentLRP), we must add the annotations given in Listing 4.18.
…
@ObjectModel.transactionalProcessingDelegated: true
@ObjectModel.compositionRoot: true
@ObjectModel.createEnabled: true
@ObjectModel.updateEnabled: true
@ObjectModel.deleteEnabled: true
…
define view Z_C_PurchaseDocumentLrp
…
Listing 4.18 Purchase Document Consumption View Header Annotations to Delegate the
Transactional Processing to the Underlying BOPF Business Object Node
We must also mark the view as the root view of our hierarchy node structure by intro-
ducing @ObjectModel.compositionRoot: true. Additionally, we must enable all trans-
actional operations separately because they are already known from the interface
views. The only new annotation is the @ObjectModel.transactionalProcessingDele-
gated: true, which indicates that transactional processing is delegated to the trans-
actional runtime of the underlying view. In our case, this is the Z_I_PurchaseDocu-
mentTP business object and view. Moreover, we must also specify the same association
type annotations as in the underlying views, from document to item and back.
Additionally, we have to add annotations to the purchase document item consump-
tion view (Z_C_PurchaseDocumentItemLRP) to enable transactional processing delega-
tion (Listing 4.19).
…
@ObjectModel.transactionalProcessingDelegated: true
@ObjectModel.createEnabled: true
@ObjectModel.updateEnabled: true
@ObjectModel.deleteEnabled: true
…
define view Z_C_PurchaseDocumentItemLrp
…
Listing 4.19 Purchase Document Item Consumption View Header Annotations to Delegate
the Transactional Processing to the Underlying BOPF Business Object Node
154
第 155 页 / 共 465 页
4.2 Transactional Processing with the Business Object Processing Framework
Compared to the purchase document consumption view, we must omit the @Object-
Model.compositionRoot annotation but also set transactional processing as delegated
to the underlying business object while enabling transactional operations for CUD
separately.
4.2.2 Testing Business Object Nodes
To test the create, read, update, and delete (CRUD) operations provided by the trans-
actional runtime, we can make use of the BOPF test shell. The BOPF test shell can be
opened in your ABAP development tools in Eclipse by simply opening the context
menu of the previously generated business object and choosing Runs As (cid:2) Test Envi-
ronment (see Figure 4.19).
Figure 4.19 Opening the BOPF Test Shell via the Previously Generated Business Object
The BOPF test shell opens in Eclipse as an embedded SAP GUI window. To create a
new node instance, you can simply select the node in the Node Instances navigation
pane on the right side and click the Add Node Instance button in the toolbar of the
editor pane on the right side, as shown in Figure 4.20. To save a new node instance,
click the Save button in the main toolbar of the window.
155
第 156 页 / 共 465 页
4 Developing an SAP Fiori Elements List Report and Object Page
Figure 4.20 Creating New Node Instances Using the BOPF Test Shell
As you can see, the transactional runtime supports standard CUD operations out of
the box without the developer having to explicitly code those. To refine the behavior
of those operations, you can add BOPF validations or determinations to the corre-
sponding node in the hierarchy.
4.2.3 Creating a Quick Action Using Business Object Processing Framework
As you’ll remember, one of the requirements of the purchasing application was that
the purchasing manager can approve purchase documents if they require approval. To
enable the purchasing manager to quickly approve a purchase document, we’ll add a
custom BOPF action to the purchase document business object node. You can create
an action for an existing business object node by carrying out the following steps:
1. Open the business object (Z_I_PurchaseDocumentTP), for example, by using
(Ctrl)+(Shift)+(A).
2. In the business object editor, click on the Go to the Root Node link.
Figure 4.21 Create a New Action for a Node in the Business Object Node Hierarchy
156
第 157 页 / 共 465 页
4.2 Transactional Processing with the Business Object Processing Framework
3. Select the Actions tab, and choose New (Figure 4.21).
4. A dialog opens where you must specify the details of the action (Figure 4.22):
– Name: You must enter the name of the action; in our case, we enter “APPROVE”.
We’ll later enter an additional “CLOSE” action to close a purchase document.
– Description: Enter a short description that describes the purpose of the action,
for instance, “Approve Purchase Documents”.
– Implementation Class: You can simply accept the suggestion that will lead to
the generation of a global ABAP class or enter an already existing class that
implements the interface /bobf/if_frw_action~execute. We’ll call the class ZCL_
A_SET_STATUS to be able to reuse it for all status actions as we also want to imple-
ment a CLOSE status action for closing a purchase document.
– Instance Multiplicity: Select multiple node instances here as we want the action
to operate on several purchase documents to enable mass processing of
approvals.
– Parameter Structure: We leave this field empty because we’re not going to pro-
vide any parameters to the action in our scenario.
Figure 4.22 Dialog for Creating a BOPF Action
5. Click the Finish button of the dialog.
157
第 158 页 / 共 465 页
4 Developing an SAP Fiori Elements List Report and Object Page
6. To specify further details, you must click on the action name while pressing (Ctrl)
(Figure 4.23).
Figure 4.23 Opening the Action Overview Editor by Clicking on the Action Name
7. In the Action Overview editor of the action, you can set the exporting parameter
and multiplicity of the action (Figure 4.24).
Figure 4.24 Set the Exporting Parameters of the Action
158
第 159 页 / 共 465 页
4.2 Transactional Processing with the Business Object Processing Framework
8. Finally, save ((Ctrl)+(S)) and activate ((Ctrl)+(F3)) the business object, which will
also lead to the creation of the action implementation class if it doesn’t exist yet.
To implement the action, you must navigate to the global ABAP action implementa-
tion class by clicking on the action class name in the Actions editor while pressing the
(Ctrl) key. Listing 4.20 shows the ABAP implementation of the approval action. We
first read the purchase documents to be approved using the io_read parameter
object’s retrieve method. Subsequently, we loop over the retrieved documents and
change their status to approved (2). Finally, to update the changed approval status on
the database, we must write the changes back using the io_modify parameter object’s
update method.
METHOD /bobf/if_frw_action~execute.
DATA(lt_purchase_document) = VALUE ztipurchasedocumenttp( ).
" Retrieve purchase document node instances to be approved
io_read->retrieve(
EXPORTING
iv_node = is_ctx-node_key
it_key = it_key
IMPORTING
et_data = lt_purchase_document
).
LOOP AT lt_purchase_document ASSIGNING FIELD-SYMBOL(<fs_purchase_document>).
<fs_purchase_document>-status = '2'. " Set status to Approved
" Update the node instance with the changed status
io_modify->update(
EXPORTING
iv_node = is_ctx-node_key
iv_key = <fs_purchase_document>-key
iv_root_key = <fs_purchase_document>-root_key
is_data = REF #( <fs_purchase_document> )
it_changed_fields = VALUE #(
( zif_i_purchasedocumenttp_c=>sc_node_attribute-z_i_purchasedocumenttp-
status )
)
).
ENDLOOP.
ENDMETHOD.
Listing 4.20 Simple Implementation of the Approval Action for Purchase Documents
159
第 160 页 / 共 465 页
4 Developing an SAP Fiori Elements List Report and Object Page
4.3 Virtual Elements in Core Data Services
In Section 4.1.4, we learned that the expressive CDS data DDL enables us to push down
calculations to SAP HANA, which reduces calculations on the ABAP server and fosters
code pushdown and the code to data paradigm to exploit the SAP HANA database
and its capabilities. However, in some cases, it might still be necessary to call some
ABAP coding to calculate values that aren’t part of a persistent model, for instance,
legacy ABAP logic that can’t be migrated to CDS views that easily. In such cases, vir-
tual elements come into play that are defined on the CDS consumption view level but
are calculated using ABAP code exits.
4.3.1 Adding a Virtual Element to a Core Data Services View
The annotations required for defining a CDS field as a virtual element are shown in
Listing 4.21. As you can see, on the CDS level, the virtual element field is simply an
empty field. However, SADL will parse the annotations and, when the CDS view is
queried via its corresponding OData entity, delegate the data calculation to an ABAP
exit class (@ObjectModel.virtualElementCalculatedBy).
If it’s also possible to filter and sort the OData entity’s provided data by the virtual
element field, the filter (@ObjectModel.filter.transformedBy) and sort (@Object-
Model.sort.transformedBy) annotations and exits must be implemented.
@ObjectModel.readOnly: true
@ObjectModel.virtualElement
@ObjectModel.virtualElementCalculatedBy: 'ABAP:<ABAP_class>'
@ObjectModel.filter.transformedBy: 'ABAP:<ABAP_class>'
@ObjectModel.sort.transformedBy: 'ABAP:<ABAP_class>'
cast( '' as <data_type> preserving type) as <field_name>
Listing 4.21 Declare a Field as n Virtual Element in the CDS Field Selection List
4.3.2 Implementing an ABAP Code Exit to Populate the Virtual Element
To calculate, sort, and filter virtual elements, the respective ABAP exit classes must
implement the interfaces and methods listed in Table 4.2.
For the virtual element calculation, the GET_CALCULATION_INFO method is called before
the actual database SELECT is performed. The method is supposed to return all fields
that are required for the virtual element field to be calculated. SADL will then make
160
第 161 页 / 共 465 页
4.4 Creating an OData Service
sure that those fields are also selected from the database using its generic query
engine. In the CALCULATE method, which is called after the database SELECT, the calcu-
lation of the virtual element field value must be implemented and can make use of
the before specified and selected fields. The respective mapping methods of the filter
(MAP_ATOM) and sort (MAP_ELEMENT) exits are called before the actual database SELECT
takes place. Sort or filter conditions defined for the virtual element must in this case
be mapped onto other fields that are available in the persistent database table and
can be included in the database SELECT statement the SADL query engine generates.
Interface Method
Calculate IF_SADL_EXIT_CALC_ELEMENT_READ
Filter IF_SADL_EXIT_FILTER_TRANSFORM
Sort IF_SADL_EXIT_SORT_TRANSFORM
Table 4.2 Interfaces to Be Implemented for the Different Virtual Element Exits
4.4 Creating an OData Service
In this step, we’ll expose the previously developed CDS and BOPF model as an OData
service for later consumption by our SAP Fiori elements list report and object page UI.
4.4.1 Auto-Exposing the OData Service
Because our VDM is a coherent model, and all entities relevant for the application are
linked by associations, we can simply expose the CDS model using OData Auto-Expo-
sure. Therefore, we’ll add the @OData.publish: true annotation to the header section
of the purchase document consumption view:
…
@OData.publish: true
…
define view Z_C_PurchaseDocumentLrp
On activating the view, SADL will generate and store several artifacts on the ABAP
backend system that are required for later exposing the OData service on the SAP
Gateway hub or frontend system.
161
| Interface | Method |
|---|---|
| Calculate | IF_SADL_EXIT_CALC_ELEMENT_READ |
| Filter | IF_SADL_EXIT_FILTER_TRANSFORM |
| Sort | IF_SADL_EXIT_SORT_TRANSFORM |