Since I last published a blog post on this topic, SAP Gateway Foundation has continued to evolve. Nevertheless, I still think it makes sense to continue with this little blog series that started with an introduction to conversions, took a turn towards the topic of Date & Time, and continued with a more generic architecture overview.
In this blog post, we are going to take a deeper look at a very special conversion-related topic: the formatting of currency amounts. Formatting? Hang on, isn’t that a pure user interface topic? Did I mix it up with currency conversion?
Let’s start by shedding some light on those questions. We will then move on to discuss modeling aspects in OData version 2.0 and version 4.0, before digging deeper into the corresponding data handling in SAP Gateway Foundation.
We will focus on currency amounts. But you can easily translate the statements and examples to quantities, for example. Just replace amount by measure and currency (code) by unit of measure.
Handling currency amounts is a key requirement to business applications. One aspect is known as currency conversion: calculating the amount in currency A based on the amount in currency B with a given currency exchange rate between A and B.
1 USD = 0.90 EUR (2019/08/05)
But what might sound like primary school mathematics is far from simplistic or trivial. There are different types of exchange rates and different notations. As exchange rates change, you need to consider time-dependency. Different processes need to observe different update frequencies. Rates might not be available for currency pairs, and so on.
All of that is business functionality – deeply embedded into the logic of the different application components. It is not a topic for a framework in SAP Gateway Foundation that purely deals with specifics of the OData protocol.
Now, currencies are often provided in units and sub-units, that is, the currency amounts allow for different numbers of decimals to the right of the decimal point. The Euro (EUR) has two decimal places to refer to cents. The Japanese Yen (JPY) has no sub-units and hence no decimal places shall be shown. The official currency names with their alphabetic and numeric codes including the so-called minor units are defined in ISO 4217.
On occasion, a deviating number of decimal places is required to specify prices, for example, at German petrol stations the price per liter in EUR has three decimals to the right of the decimal point.
The presentation of currency amounts on a user interface or in print forms must observe those specifications. You want to see 100 JPY (or 100 ¥) and not 100.00 JPY but you would like to get 100.00 EUR or 100.000 TND (Tunisian Dinar).
This is known as currency amount formatting. As it is just a topic of presentation, one might argue that this formatting is UI client logic. This would be one option. But you would need to manage all the currency definitions with their formatting-related properties within the UI client, while other parts of the currency definitions are required in the backend. What’s more, formatting also needs to happen in the backend itself if you think of backend-controlled printing of business documents.
In ABAP-based SAP software, currency information is stored in the
TCUR* tables and it makes sense to handle the described formatting in the backend.
Nevertheless, other (locale-dependent) formatting features would still be implemented in the UI client, for example, a user-specific decimal separator setting to choose between 100.00 EUR and 100,00 USD.
Currency Amounts in OData and SAP Gateway Foundation
SAP Fiori applications use OData services to communicate with the backend. The implementation framework in SAP Gateway Foundation supports the creation of OData services in an ABAP-based backend. You would expect that such a framework would support currency amounts. It does, and it has also been decided to include some of the formatting features in that layer.
Currency Amounts in OData
The OData standard does not provide a specific primitive type for properties that represent currency amounts. But those amounts are decimal values. Hence,
Edm.Decimal is the natural primitive type to be used.
We have learned that we do require different numbers of decimal places depending on the currency. OData version 4.0 knows the symbolic value
variable for the facet scale of
Edm.Decimal to express that the number of decimals to the right of the decimal point can vary between 0 and a precision-dependent maximum.
In OData versions 2.0, the situation is a little more difficult. Here,
Edm.Decimal only allows for fixed precision and scale. Specifying a scale of 2 would support most currencies, such as EUR or USD, but would not fit to JPY and TND (see above). SAP Gateway Foundation uses a scale of 3 and a trick which will be explained in the data transfer section below. With 3 decimal places, most currencies can be covered. Currently, there are only two exceptions: CLF and UYW with a minor unit of 4. You also need to consider that table
TCURC may contain entries that do not correspond to ISO currencies and may specify any number of decimal places, for example, USDN with 5 decimal places.
But a primitive property for an amount does not make sense without reference to the currency. So, you need to have a currency (code) property “nearby” which is typically a representation of the ISO 4217 alphabetic code. A primitive property of type
Edm.String with a maximum length of 3 would be suitable.
In OData services delivered by SAP, the maximum length of a currency code property is 5. Because the primary key field
WAERS in currency code table
TCURC is the SAP-specific coded representation of currencies. Its internal data type
CUKY has length 5. Table
TCURC connects this SAP-specific code to the alphabetic ISO code. For all currencies according to ISO 4712, the field
WAERS contains the alphabetic ISO code, that is, the content of
WAERS is equal to the content of
Clients might need the information which property X contains the currency for a given property Y that holds an amount.
For OData version 2.0, SAP services use a specific property attribute
sap:unit at the amount property. The value of that attribute denotes the name of the currency (code) property that must be part of the same entity type.
For OData version 4.0, SAP services use annotations. With the annotation term
SAP__measures.ISOCurrency, an amount property (annotation target) gets a path assigned that points to the currency property.
The annotations can also be set for OData version 2.0 services depending on your UI client technology. But with the OData version 4.0 stack the complex annotations are created automatically.
Reference Currency Determination
In the past, SAPGUI as a user interface technology had similar formatting requirements. ABAP developers may know about the possibility to connect an amount with the corresponding currency field in the ABAP dictionary:
In the first blog post of the series, we looked at how conversion exit information in the ABAP dictionary is used in SAP Gateway Foundation through structure or property binding. Similarly, the framework evaluates reference field information in the dictionary and translates this into proper OData metadata. Here is an example for an OData version 2.0 service.
The first lines of code in the metadata provider class would create the two properties in the entity type – the currency amount and the currency. They are not connected at that point in time but reference ABAP field names
lo_entity_type->create_property( iv_property_name = 'TotalNetAmount' iv_abap_fieldname = 'NETWR'). lo_entity_type->create_property( iv_property_name = 'TransactionCurrency' iv_abap_fieldname = 'WAERK').
These two field names represent two fields in structure
VBAK. If we apply structure binding
lo_entity_type->bind_structure( iv_structure_name = 'VBAK' iv_bind_conversions = 'X' ).
the framework will use the ABAP dictionary information of the ABAP structure (database table
VBAK in this case). The framework updates the service metadata with the information required to control amount formatting. By the way, the code above is automatically generated if you create an entity type based on
VBAK in the SAP Gateway Service Builder (transaction
Please observe the parameter
iv_bind_conversions in the last method call. If the parameter is not set, the system will not determine “conversion-related” information from the ABAP dictionary (refer to the first blog post of the series). Neither will it determine reference field information. Hence, your service will not show the relationship and the SAP Gateway Foundation framework will not use it for amount formatting. Nevertheless, the
sap:semantics attribute will be there – it is purely derived from the data type of the field in the ABAP structure and not regarded as “conversion-related”.
The determination of the reference currency property works with structure binding only because reference fields information is tied to ABAP structures. If you do not want to use structure binding, you need to explicitly specify the relationship in the model provider class. Use method
SET_UNIT_PROPERTY at the property that represents the currency amount.
Later versions of SAP Gateway Foundation allow for creation of OData services based on CDS (Core Data Services) through mapped data sources (MDS), referenced data source (RDS), or auto exposure. Like the reference fields in the ABAP dictionary, CDS views provide annotations to express if a field contains a currency code and to which currency code field an amount relates to:
@Semantics.amount.currencyCode: 'TransactionCurrency' TotalNetAmount, @Semantics.currencyCode: true TransactionCurrency,
Again, the SAP Gateway Foundation framework evaluates this information to create appropriate service metadata.
I will not go into details about OData version 4.0. If you would program your model provider class, then the mechanisms will be quite similar. But the future programming model for OData services will nevertheless be CDS-based, allowing the frameworks to do the magic.
Now, we know something about the service model. But how does the framework behave during OData request execution? Let’s start by looking at the outbound case, that is, the data transfer from the data provider to the http response (refer to this blog post for an overview).
In ABAP, currency amounts are typically stored in the so-called BCD format based on dictionary data type
CURR. This is equivalent to the built-in data type P (packed number). A typical domain is
WERTV8 with length 15 and two decimals. The automated service metadata determination described in the previous section looks precisely for such a configuration. If you use other data types or other decimal settings, the determination will not work as expected. SAP Business ByDesign, for example, stores currency amounts as decimal floating points (mainly dictionary type
The content of such an ABAP field is transferred into a string when the data is handed over to the internal data container of the SAP Gateway Foundation framework. At this point in time, the formatting is done using function module
To repeat: the result is a string with the formatted currency amount. To finalize the formatting process, the minus sign for negative values is re-positioned from the end of the string to the front. Based on the specific internal metadata settings, the ABAP OData Library can handle this string information in the context of the
Edm.Decimal property. It just checks whether the string contains a valid value and inserts the formatted result into the XML/JSON http response. The facet scale that has been randomly selected for OData version 2.0 is not considered! With currency USDN (see example above), you would obtain a result with 5 decimals places, even though this contradicts the service metadata. Your UI client must be able to handle that.
VBAK-NETWR the currency information from
VBAK-WAERK is used. Let
VBAK-NETWR be 123.45. If
VBAK-WAERK contains EUR, the JSON response is shown as:
VBAK-WAERK contains JPY, the JSON response is:
and for TND it would be:
Please observe that the 2 decimal places in the backend are not interpreted as such. If
VBAK-WAERK is empty, a formatting with “these” 2 decimal places will happen:
Tipp: Always test your service with currencies such as JPY or TND.
If it does not work as just described, ensure that you check the data type used in the backend, the reference field information in the ABAP dictionary, and the structure binding or the unit property setting in your metadata provider class. Check the correct usage of the
iv_bind_conversions parameter and check if you did not switch off conversions on other levels of the service metadata (refer to this blog post).
For the inbound case, it is necessary to distinguish between URI content, for example, within
$filter query options and data in the http request body.
In the latter case, the ABAP OData library deserializes the request body into the ABAP structure where the currency amount is stored as a string. The SAP Gateway Foundation framework calls function module
CURRENCY_AMOUNT_IDOC_TO_SAP returning the value that can be handled in your data provider class.
In the URI, currency amounts may appear as filter values. They are not permitted as key properties of entity types. The question of how the filter query option is evaluated is strongly dependent on your data provider class implementation. Let’s look at one version: select options. The SAP Gateway Foundation framework offers a method to retrieve filter information as select options in
GET_ENTITYSET methods of the data provider class.
lo_filter = io_tech_request_context->get_filter( ). lt_select_option = lo_filter->get_filter_select_options( ).
At this point in time, the select options just contain “unconverted” data. For example,
$filter=TotalNetAmount gt 123 would still appear as
You may receive the converted information for a specific property in the table of select options in the following ways:
data: ltr_netwr type range of netwr_ak. * get total net amount line from lt_select_option into ls_select_option lo_filter->convert_select_option( exporting is_select_option = ls_select_option importing et_select_option = ltr_netwr ).
Internally, this method uses a two-step approach. It first derives the “internal” value in string format using function module
CURRENCY_AMOUNT_IDOC_TO_SAP_L and then moves the string value into the correctly typed field using the built-in ABAP type conversions.
In our example, the “unconverted” string 123 become a string 123.00 for EUR, and this is moved as 123.00 to the field typed with
NETWR_AK. You may want to compare this to the example provided in the second blog post of the series.
But where does the currency code come from? It needs to be part of the filter expression, too. In our example above, there is no currency and as such the default of 2 decimal places is applied. A currency would be considered with the following filter expression:
$filter=TotalNetAmount gt 123 and TransactionCurrency eq ‘JPY’.
Not all combinations of amounts and currency codes make sense in a filter query option. What would
$filter=TotalNetAmount gt 123 or TransactionCurrency eq ‘JPY’ mean? Would you interpret all amounts in JPY? Or, would you consider 123.00 in any currency or only in those with 2 decimal places? In any case, the SAP Gateway Foundation framework is fundamentally not able to return select options if two different properties are combined with ‘or’ (note 1671893). Hence, the reverse amount formatting cannot be executed either.
To conclude, currency amount formatting is a function that helps to display amounts with the correct number of decimal places according to the specification of an associated currency. It is not to be confused with currency conversion.
It is however part of the “conversion” functions provided by SAP Gateway Foundation to ease UI client development and to efficiently and consistently access the currency specifications in the backend.
Comparing OData versions 2.0 and 4.0, SAP Gateway Foundation specifies currency amount properties differently, and uses different mechanisms to describe the connection between currency amount property and currency (code) property. The formatting is executed at runtime when the SAP Gateway Foundation framework transfers the data between the data container used in the data provider class and the framework-internal data container.
With the ABAP RESTful Programming model and OData version 4.0 you just need to specify appropriate CDS annotations and the formatting works out of the box.
 P LENGTH 8 can store 2 * 8 – 1 (the sign) = 15 digits.