Have you ever seen prefixing zeroes in a Fiori application?

NUMC Values

An IDoc number would work well as an example:

Prefixing zeroes in IDoc number

Since the ID of our IDoc is really just 4, it does not make sense to display it with 15 prefixing zeroes. The reason why it has those extra zeroes in the first place comes down to how IDoc numbers are technically defined in SAP:

IDoc number data element

IDoc numbers are NUMC values, ie. “numerical text”. NUMC values are essentially character strings with some special properties. Like normal character strings, NUMC values have a defined length. Unlike normal character strings, the set of valid characters in NUMC values is restricted: only characters 0123456789 are allowed.

This means that whereas an initial CHAR value would just be an empty string, an initial NUMC value with length 5 would be 00000. The character set restrictions are enforced when values are assigned to NUMC fields, although the rules are somewhat surprising:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
DATA lv_idoc_number TYPE edi_docnum.


" All characters except digits 0-9 are simply discarded
" when a character string is assigned to NUMC variable.
lv_idoc_number = '/123-456/'. " lv_idoc_number = '0000000000123456'
ASSERT '123456' = lv_idoc_number.


" If there are no digits in the character string,
" NUMC is initial.
lv_idoc_number = '/'. " lv_idoc_number = '0000000000000000'
ASSERT lv_idoc_number IS INITIAL.

According to ABAP documentation, the rules of assigning character strings to NUMC variables are:

The characters in the source field that represent digits are passed right-justified to the target field. Other characters are ignored. If the target field is longer than the number of digits in the source field, it is padded on the left with the character “0”. If the target field is shorter, the characters are cut off on the left.

Me, personally, would have expected to get an error when trying to assign invalid characters to a NUMC variable… What the readers with keen senses just heard was the monkey’s paw curling.

Because NUMC fields can, by definition, only contain numerical data, ABAP assumes that they should be compared to other character strings as numerical values. Ie, if NUMC and CHAR values are to be compared, both are converted to packed numbers and their values are compared. Insofar as this ignores any prefixing spaces and zeroes in the CHAR field, this will probably be the right thing to do. However, given that there’s absolutely no guarantee that CHAR field contains a valid numerical value, this can also be a source for crashes if one is not careful:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
DATA lv_idoc_number TYPE edi_docnum VALUE '1'.
DATA lv_char TYPE char10 VALUE '         1'.


" When compared, both represent the numerical value 1
ASSERT lv_idoc_number = lv_char.


" This comparison will crash because '/' cannot be interpreted as a number.
" It also appears that the exception cannot be caught & handled
IF lv_idoc_number = '/'.
ENDIF.

ABAP documentation presents the following table for comparing between types:

Comparison rules for character-like data

In practice these rules tend not to cause problems, since NUMC fields are mostly used in keys and keys are usually only ever compared to other keys.

The enforcement of digits-only values is not quite foolproof:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
TYPES: BEGIN OF ty_s_without_second_field,
         field1 TYPE text10,
       END OF ty_s_without_second_field.

TYPES: BEGIN OF ty_s_with_char,
         field1 TYPE text10,
         field2 TYPE text10,
       END OF ty_s_with_char.

TYPES: BEGIN OF ty_s_with_numc,
         field1 TYPE text10,
         field2 TYPE numc10,
       END OF ty_s_with_numc.


DATA(ls_without_second_field) = VALUE ty_s_without_second_field(
  field1 = 'ABCDEF'
).

" Assigning a shorter structure to a longer structure
" will cause the extra fields of the longer structure
" to be empty.
DATA ls_with_numc TYPE ty_s_with_numc.
ls_with_numc = ls_without_second_field.

" field2 contains an empty string (not '0000000000'), which is not understood to be
" the INITIAL value of a NUMC field, but would be interpreted as value 0
" for comparisons.
ASSERT ls_with_numc-field2 IS NOT INITIAL AND ls_with_numc-field2 = '0000000000'.


DATA(ls_with_char) = VALUE ty_s_with_char(
  field1 = 'ABCDEF'
  field2 = 'GHIJKL'
).

" Assigning a structure of equal length to
" another will copy the values, regardless of the
" types of the components. The NUMC field of
" ls_with_numc will contain whatever was in the
" second CHAR field in ls_with_char.
ls_with_numc = ls_with_char.

WRITE / ls_with_numc-field2. " Will print 'GHIJKL'

" This comparison will crash, since the current
" value of ls_with_numc-field2 cannot be interpreted as a number
*IF ls_with_numc-field2 = '1'.
*ENDIF.

ASSERT ls_with_numc-field2 IS NOT INITIAL.
ASSERT ls_with_numc-field2 CN '0123456789'.
ASSERT ls_with_numc-field2 CS 'GHIJKL'.
ASSERT CONV char10( ls_with_numc-field2 ) = 'GHIJKL'.


ls_with_numc-field2 = 'GHIJKL'.
WRITE / ls_with_numc-field2. " Will print '0000000000'


" MOVE-CORRESPONDING works like assignments
" between fields, the allowed character set is enforced.
ls_with_numc = CORRESPONDING #( ls_with_char ).

WRITE / ls_with_numc-field2. " Will print '0000000000'

NUMC Values Cause Prefixing Zeroes in Fiori Applications

NUMC values are normally displayed without the prefixing zeroes in SAPGUI:

IDoc number in WE02

Although even this is not always consistent:

Prefixing zeroes appear in screen title and tree

While the OData framework converts some values for output (see How-To Enable Input Conversion for OData Function Parameters), somewhat perplexingly the prefixing zeroes of NUMC fields are left unaltered and returned to the caller. The value that we saw in our Fiori application (0000000000000004) was the exact value that SAP sent us:

SAP returns prefixing zeroes

This is strange, given that SAP has clearly tried to hide the in-memory representation of other values in OData and has also hidden the NUMC in-memory representation in SAPGUI.

Avoiding Prefixing Zeroes in Fiori

In the Fiori application, you can change the way that values are displayed by using formatters or expression bindings. For example, we could choose to use JavaScript function parseInt to get rid of the prefixing zeroes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- Old binding -->

<ObjectHeader
    title="IDoc {IDocNumber}"
/>


<!-- New binding -->

<ObjectHeader
    title="IDoc {= parseInt(${IDocNumber}) }"
/>

No more prefixing zeroes

This does not, of course, solve the root-cause of why we have the undesired prefixing zeroes in the first place. For that, we need to create a CHAR -type version of the IDoc number and we need to use that in our OData definition:

IDoc number as text

After we have changed our OData entity to use a data element based on this domain and re-imported the the property in SEGW, the OData framework will return the value without prefixing zeroes:

No more prefixing zeroes

Extra: Using WBS Element Number in OData

The ALPHA conversion routine performs fairly modest alterations on the value, pretty much just adding or removing prefixing zeroes. The conversion routine for WBS element numbers, ABPSP, represents the other extreme: the before-conversion and after-conversion values have seemingly nothing to do with one another:

No obvious connection between the two representations of the key

The only connection between 00003510 and L.0166.E.M.1.CE is the project structure stored in SAP database. The conversion exit is so complex because the numerical id needs to be converted into a form which represents the hierarchical structure of the project: the actual WBS element is actually just the last CE part of the converted key, the other parts representing the hierarchy.

However, using the standard data element for WBS element number produces some strange results in OData:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<entry>
    <id>https://some-sap-system/sap/opu/odata/sap/zproject_service_srv/WbsElements('000000000000000000001661')</id>
    <title type="text">WbsElements('000000000000000000001661')</title>
    <updated>2025-02-22T08:49:27Z</updated>
    <category term="ZPROJECT_SERVICE_SRV.WbsElement" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
    <link href="WbsElements('000000000000000000001661')" rel="self" title="WbsElement"/>
    <content type="application/xml">
        <m:properties xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">
            <d:WbsElementNumber>000000000000000000001661</d:WbsElementNumber>
        </m:properties>
    </content>
</entry>

Clearly, 000000000000000000001661 matches neither 00003510 nor L.0166.E.M.1.CE. What is going on here?

The length, 24 characters, appears to come from the WBS element number domain:

WBS element number domain

Here the output length is defined as 24 characters. Also, because the value has prefixing zeroes, we can deduce that it was probably a NUMC variable in ABAP.

Finally, we note that the digits that actually appear in the value are those that also appeared in the converted WBS element number L.0166.E.M.1.CE, ie. 0, 1, 6, 6 and 1.

Putting all this together, it seems that:

  • When performing output conversions, the OData framework takes the output length and data type from the domain -> WBS element number thus becomes a NUMC field with length 24
  • But because of the assignment rules we already glanced through, trying to perform the WBS element number output conversion into a NUMC field results in only the digit characters being preserved, producing the corrupted value

Ie. it appears that in SAPGUI, the output conversion is always performed to a CHAR field of the specified length, whereas the OData framework incorrectly inherits the NUMC type when performing output conversions.

Creating a CHAR domain for WBS element numbers allows us to take advantage of the automatic input-output -conversion:

WBS element CHAR domain

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<entry xml:base="https://some-sap-system/sap/opu/odata/sap/zproject_service_srv/" xmlns="http://www.w3.org/2005/Atom" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">
    <id>https://some-sap-system/sap/opu/odata/sap/zproject_service_srv/WbsElements('L.0166.E.M.1.CE')</id>
    <title type="text">WbsElements('L.0166.E.M.1.CE')</title>
    <updated>2025-02-22T09:15:20Z</updated>
    <category term="ZPROJECT_SERVICE_SRV.WbsElement" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
    <link href="WbsElements('L.0166.E.M.1.CE')" rel="self" title="WbsElement"/>
    <content type="application/xml">
        <m:properties>
            <d:WbsElementNumber>L.0166.E.M.1.CE</d:WbsElementNumber>
        </m:properties>
    </content>
</entry>

This is another strange shortcoming of the OData framework. While preserving the prefixing zeroes of NUMC may be an intentional behavior, the way the conversion exit does not work strikes me more as a bug than anything intentional.