Skip to Content
Technical Articles
Author's profile photo Thomas Nitschke

Conversions in SAP Gateway Foundation – Part 3 (Currency Amounts)

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.

Requirements

Currency Conversion

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.

Amount Formatting

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 ISOCD.

Referenced Currency

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 NETWR and WAERK.

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 SEGW).

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.

Data Transfer

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[1]. 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 DF34_DEC).

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 CURRENCY_AMOUNT_SAP_TO_IDOC.

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.

Example: For 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:

If 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

SIGN OPTION LOW HIGH
I EQ 123

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.

Summary

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.

[1] P LENGTH 8 can store 2 * 8 – 1 (the sign) = 15 digits.

Assigned Tags

      9 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Sérgio Fraga
      Sérgio Fraga

      Hello Thomas Nitschke ,

      I have an issue with Function Import parameters length.

      I have a parameter of type MATNR (18 chars) and in the request I send a value with 19 chars:

      • get_parameters has the value with 18 chars but the type is char(19)
      • get_parameters_converted gets a value with 18 chars since it truncates at 18th position

      I was expecting a behaviour similar with what we have on the Entities, if you pass more chars then the ones defined in the metadata, gateway framework handles this by default.

      Seems like that with Function Import it doesn’t work.

      I have found the doc Function Import Parameter Length Check but it’s only for GW ABAP 2020, where here I am at version GW ABAP 7.50 SP17.

      Do you have any suggestion for this case?

      Maybe Andre Fischer has some idea?

      Thank you/Andre in advance.

      Sérgio

      Author's profile photo Andre Fischer
      Andre Fischer

      You can use a max length which is even bigger, say 100, but you would nevertheless check in your application code that the payload does not exceed 18 characters.

      And you would then simply through an error message as the SAP Gateway framework does it using the new model feature which is by the way called USE_STRICT_FUNCTION_PARAM_CHK and not USE_STRICT_FUNCTION_PARAMETER_CHECK.

      The documentation will be updated ;-).

       

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Thanks for checking out and commenting, Sérgio Fraga.

      I think it is a good idea to post those topics as 'Ask a Question' in the 'Answers' section of the community. They get more attention there and experts find them easier. It is more likely to get a quick answer.

      I cannot provide one ad hoc. Andre Fischer : do you know someone who could have a quick look? Thanks.

      Author's profile photo Sérgio Fraga
      Sérgio Fraga

      Thanks for the answers,

      In fact that was what I have done.

      I went a little bit further and got the exact length from the MPC complex structure (in my case named serialdetail) used in the Function Import.

      "Fix for Function Import parameter length control
      data article type zcl_zui5srs_mpc=>serialdetail-matnr.
      describe field article length data(article_length) in character mode.
      
      if strlen( ls_parameter-value ) > article_length.
         raise exception type /iwbep/cx_mgw_busi_exception
            exporting
               textid            = /iwbep/cx_mgw_busi_exception=>business_error_unlimited
               http_status_code  = /iwbep/cx_mgw_busi_exception=>gcs_http_status_codes-precondition_failed
               message_unlimited = |{ 'Material Number has more then 18 characters'(001) }|.
      
         clear article_length.
      endif.

      I was just challenging this since we actually delegate this control/check to the GW Framework for typical EntitySets.

      I will create a blog on this one for future colleagues.

      Regarding the USE_STRICT_FUNCTION_PARAM_CHK feature, where will it be? Is it just a parameter of /iwbep/if_mgw_appl_srv_runtime~execute_action?

      Thank you again for the support!

      Sérgio

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Great. Thanks for the contribution.

      Do you mean the possibility of switching the max. length check on and off? This characteristic can be set for function import parameters in the model provider class. You may call method SET_FUNC_IMP_MAX_LEN_CHECK at the parameter (interface /IWBEP/IF_MGW_ODATA_PARAMETER) to set it to ABAP_TRUE or ABAP_FALSE.

      The information will be evaluated by the OData library implementation when the parameters are transferred to the Gateway framework implementation. If set to ABAP_FALSE (default) the maximum length facet for Edm.String is not considered and input with greater length is accepted.

      Author's profile photo Andre Fischer
      Andre Fischer

      I described it in some more detail in the following blog post.

      https://blogs.sap.com/2020/11/03/how-to-check-the-maximum-length-of-a-function-import-parameter-in-segw-based-odata-services

      Sérgio Fraga. please ping me when you have published your blog post so I can refer to it. And thanks for your contribution.

      Author's profile photo Nils Janßen
      Nils Janßen

      Hi Thomas,

      thank you very much for this detailled explanation.

      My question might be at the wrong place but you might be able to refer me to the right place.

      Since a currency will have the OData V2 type EDM.Decimal with a scale of 3, a SmartFilterBar  will change any proper input to a number with 3 decimals. Do you know if and how that can be changed to show 2 decimals only?

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Thanks, Nils. Cannot answer off the top of my head but I am looking for an expert.

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Nils Janßen The colleagues suggested to post the question in the Fiori Elements Community: https://community.sap.com/topics/fiori-elements