In this blog, I will take you through the steps for 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-...
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