Skip to Content
Technical Articles

Simple application to convert number to words in SAP S/4HANA Cloud

In this blog, I will take you through steps fro creating an API to convert number to words in SAP S/4HANA Cloud. I will then provide an example on how to make use of this API to display amount in words in a Form Template.

Introduction

When this requirement came up initially, my first instinct was to search for available APIs on R3 and the first API that I found was the function module SPELL_AMOUNT, which was universally used. One option to consume this would be to wrap this API in an RFC and call it via cloud connector

https://blogs.sap.com/2019/02/28/how-to-call-a-remote-function-module-in-your-on-premise-sap-system-from-sap-cloud-platform-abap-environment/

But this option felt too cumbersome for me and I wanted to try something simpler. That is when I realized I could create a reusable API in Custom Reusable Elements found under Extensibility Tile and use this.

I found a similar API in python, which converts any number to words and I decided to reuse it. You can find the python APIs at https://stackoverflow.com/questions/8982163/how-do-i-tell-python-to-convert-integers-into-words, and try the APIs on your own.

You could follow the extensive list of tutorials written by Ulrike Liebherr to know more about the extensibility options available in SAP S/4HANA Cloud

https://blogs.sap.com/2017/01/20/sap-s4hana-extensibility-tutorial/

Without any further ado, let me get into what I did.

As a pre-perquisite, please note that your user should need the business role with business catalog Extensibility SAP_CORE_BC_EXT for performing the following steps

Creation of a Custom Business Object

The first step would be to create a Custom Business Object to store the words that are reused for every number. These are as follows.

0 Zero
1 One
2 Two
3 Three
4 Four
5 Five
6 Six
7 Seven
8 Eight
9 Nine
10 Ten
11 Eleven
12 Twelve
13 Thirteen
14 Fourteen
15 Fifteen
16 Sixteen
17 Seventeen
18 Eighteen
19 Nineteen
20 Twenty
30 Thirty
40 Forty
50 Fifty
60 Sixty
70 Seventy
80 Eighty
90 Ninety

I will not be covering the steps on creating a custom business object here and that can be found in this link which I had mentioned earlier.

Create a Custom Business Object from the tile Custom Business Objects under the group Extensibility.

Create the business object with the following structure

Check the field UI Generation so that a UI is created where you could maintain the entries that are mentioned above. You could also maintain Determination and Validation, if you need to perform any data validation and check Service Generation so that you could use the oData, but these are optional.

Save and Publish the Custom Business Object.

Once this is done, click on Maintain catalogs to assign the Business Object to a Business catalog.

Add this under the Business catalog SAP_CORE_BC_EXT and click on OK and the Publish it. This will take a few minutes, but once this is published, you can close this screen.

The Custom Business Object application will now be added under the tile Extensibility

Open the App and maintain the entries.

You can add these entries by clicking on Create.

Click on save once the entries are maintained

 

The entries should look like as given below, once maintained.

Creation of Custom Reusable Element

The second step will be to create the actual logic to convert the number to words.

For this, select the tile Custom Reusable Elements under the group Extensibility

 

Create a new custom library by clicking on the + button under the tab Custom Library.

Maintain the following and click on create

Add a new method under the newly created custom library

Click on the method to add signature for the method as follows

Save and publish the custom library. You will be able to add logic to the method only after Publishing.

Once the library is published, click on the method id

This will open the method implementation.

Click on Create Draft and enter the following code.

    TYPES: BEGIN OF str_d,
             num   TYPE i,
             word1 TYPE string,
             word2 TYPE string,
           END OF str_d.

    DATA: ls_h TYPE str_d,
          ls_k TYPE str_d,
          ls_m TYPE str_d,
          ls_b TYPE str_d,
          ls_t TYPE str_d,
          ls_o TYPE str_d.

    DATA lv_int TYPE i.
    DATA lv_inp1 TYPE string.
    DATA lv_inp2 TYPE string.

    IF iv_num IS INITIAL.
        RETURN.
    ENDIF.

    ls_h-num = 100.
    ls_h-word1 = 'Hundred'.
    ls_h-word2 = 'Hundred and'.

    ls_k-num = ls_h-num * 10.
    ls_k-word1 = 'Thousand'.
    ls_k-word2 = 'Thousand'.

    ls_m-num = ls_k-num * 1000.
    ls_m-word1 = 'Million'.
    ls_m-word2 = 'Million'.

    ls_b-num = ls_m-num * 1000.
    ls_b-word1 = 'Billion'.
    ls_b-word2 = 'Billion'.

*    Use the following if this is required in Lakhs/Crores instead of Millions/Billions
* 
*    ls_h-num = 100.
*    ls_h-word1 = 'Hundred'.
*    ls_h-word2 = 'Hundred and'.

*    ls_k-num = ls_h-num * 10.
*    ls_k-word1 = 'Thousand'.
*    ls_k-word2 = 'Thousand'.

*    ls_m-num = ls_k-num * 100.
*    ls_m-word1 = 'Lakh'.
*    ls_m-word2 = 'Lakh'.

*    ls_b-num = ls_m-num * 100.
*    ls_b-word1 = 'Crore'.
*    ls_b-word2 = 'Crore'.

    lv_int = iv_num.

    SELECT * FROM yy1_number2string INTO TABLE @DATA(lt_d).

    IF lt_d IS NOT INITIAL.
      IF lv_int <= 20.
        READ TABLE lt_d REFERENCE INTO DATA(ls_d) WITH KEY num = lv_int.
        rv_words = ls_d->word.
        RETURN.
      ENDIF.

      IF lv_int < 100 AND lv_int > 20.
        DATA(mod) = lv_int MOD 10.
        DATA(floor) = floor( lv_int DIV 10 ).
        IF mod = 0.
          READ TABLE lt_d REFERENCE INTO ls_d WITH KEY num = lv_int.
          rv_words = ls_d->word.
          RETURN.
        ELSE.
          READ TABLE lt_d REFERENCE INTO ls_d WITH KEY num = floor * 10.
          DATA(pos1) = ls_d->word.
          READ TABLE lt_d REFERENCE INTO ls_d WITH KEY num = mod.
          DATA(pos2) = ls_d->word.
          rv_words = |{ pos1 } | && |{ pos2 } |.
          RETURN.
        ENDIF.
      ELSE.
        IF lv_int  < ls_k-num.
          ls_o = ls_h.
        ELSEIF lv_int < ls_m-num.
          ls_o = ls_k.
        ELSEIF lv_int < ls_b-num.
          ls_o = ls_m.
        ELSE.
          ls_o = ls_b.
        ENDIF.
        mod = lv_int MOD ls_o-num.
        floor = floor( iv_num DIV ls_o-num ).
        lv_inp1 = floor.
        lv_inp2 = mod.

        IF mod = 0.
          DATA(output2) = num2words( lv_inp1 ).
          rv_words =  |{ output2 } | && |{ ls_o-word1 } |.
          RETURN.
        ELSE.
          output2 = num2words( lv_inp1 ).
          DATA(output3) = num2words( lv_inp2 ).
          rv_words = |{ output2 } | && |{ ls_o-word2 } | && |{ output3 } |.
          RETURN.
        ENDIF.
      ENDIF.
    ENDIF.

Save and Publish the code.

You could reuse this code in use cases within your SAP S/4HANA Cloud Instance.

Use Case

Display amount in words in Purchase Order output form.

Create a Custom Field

Create a custom field by selecting the tile Custom fields and Logic under the Group Extensibility

Maintain the following along with the following usages

UI and Reports

Form Templates

Save and Publish the custom field.

Add the column Amount in words to the form via Adobe LiveCycle Designer and bind it to the field YY1_AmountInWords_PDI

After the custom form is updated, this will have to be uploaded back to the SAP S/4HANA Cloud system.

Details on how to download the form, edit and the reupload back to the SAP S/4HANA system can be found in Steps 2 and 3 of the blog by Arun Nair https://blogs.sap.com/2018/03/12/extend-form-template-using-sap-s4-hana-cloud-in-app-extensibility/

You could refer to the Best Practices for 1LQ for more details on forms and template.

Populate the Custom Field

This field can now be populated by creating an Enhancement implementation via Custom Flows and Logic

For this, selecting the tile Custom fields and Logic under the Group Extensibility and Go to the Tab Custom Logic.

Add a new entry by selecting the following and click on create

Enter the following code, save and Publish

DATA: lv_am1 TYPE string,
      lv_am2 TYPE string,
      lv_amount TYPE string.

lv_amount = purchaseorderitem-netpriceamount.

SPLIT lv_amount AT '.' INTO lv_am1 lv_am2.

purchaseorderitemchange-yy1_amountinwords_pdi =  yy1_num2words=>num2words( iv_num = lv_am1 ).

You can see that the reusable library is called from the enhancement as

yy1_num2words=>num2words( iv_num = lv_am1 )

 

Now, create a new Purchase order and generate the output form from the Output Management Tab.

You will be able to see the Net Value converted to words as follows.

 

Please note that this logic does not handle decimals as of now, but you could add this by tweaking the existing logic 🙂

 

Hope this helped

6 Comments
You must be Logged on to comment or reply to a post.
  • Interesting and well presented, but it would be nice if modern ABAP were used throughout

    • No need for CONCATENATE for example, when && is available.
    • Try READ TABLE lt_d INTO REFERENCE data(ls_d) WITH KEY …, then you can use instead of ls_d-word, ls_d->word. It’s just that little bit better performing.

    Meaningful variable names will make it easier to understand the code, and of course in a real implementation, you’d modularise far more (I hope!). Also, I prefer to follow the SAP style guide advice and not use prefixes for variables.

    Otherwise, a good read and I learned something. Thanks.

  • A very, very, very cool blog.

    Just one comment from my side. Instead of using a custom BO, you could also use a custom code list (created in the Fiori app Custom Reusable Elements). The advantage of a custom code list over a custom BO is that the content (= mapping table between number and words) is transported from Q to P system.