Pattern-matching the nested union types - elm

I'm modeling a count up/down timer. After the origin date is set, the timer displays how much time has passed since-, or remains till that origin date.
type OriginDefined
= Up Date
| Down Date
type Origin
= OriginDefined
| OriginUndefined
type Model
= Tick OriginDefined
| Edit Origin
Thus, the timer is ticking only when the origin date is defined. When the origin is edited though, it may or may not be previously defined.
Now I need a function to return the defaultValue for the date input, when we're in the Edit mode.
dateInputDefaultValue : Origin -> String
dateInputDefaultValue origin =
case origin of
OriginUndefined ->
""
OriginDefined ->
...
Here I struggle to destructure the origin further as an Up date or Down date. In the 2nd branch of the case expression the compiler refuses to treat the origin as anything more specific than just an Origin.
Here's an Ellie https://ellie-app.com/3zKCcX87wa1/0
How should I deal with a model like that? Should I model in a different way?

Your types compile, but you have two different definitions of OriginDefined: One is a type called OriginDefined which has two constructors, Up and Down. The other is a parameterless constructor on the type Origin.
My hunch is that you were trying to get the OriginDefined constructor on Origin to carry a value of type OriginDefined. In order to do that, you would have to define a parameter of type OriginDefined on the OriginDefined constructor:
type Origin
= OriginDefined OriginDefined
| OriginUndefined
Now you have a type that is isomorphic to Elm's Maybe type, so perhaps it would be less confusing and more idiomatic to remove the OriginDefined type and replace the definition of Origin with this:
type Origin
= Up Date
| Down Date
Now you can use Maybe in the places you were previously using defined/undefined nomenclature:
type Model
= Tick (Maybe Origin)
| Edit Origin
Pattern matching on Maybe Origin could look like this:
dateInputDefaultValue : Maybe Origin -> String
dateInputDefaultValue origin =
case origin of
Nothing ->
""
Just (Up date) ->
"…"
Just (Down date) ->
"…"

Related

How to call BAPI_MATERIAL_SAVEDATA with custom fields from NCo?

In our current project we are using SAPNCO3 with RFC calls. The requirement is to create material with the function "BAPI_MATERIAL_SAVEDATA" and some custom fields (via EXTENSIONIN). The problem now is how to extend the needed structures "BAPI_TE_MARA/X" so that they can carry the custom fields? I cannot found any function for this.
Please have a look at the Code snippet at the bottom.
Thank you!
Tobias
var BAPI_TE_MARA = repo.GetStructureMetadata("BAPI_TE_MARA");
IRfcStructure structure = BAPI_TE_MARA.CreateStructure();
structure.SetValue("MATERIAL", material.Number);
//structure.SetValue("ZMM_JOB_REFERENCE", "f");
BAPI_MATERIAL_SAVEDATA has two table parameters EXTENSIONIN and EXTENSIONINX to which you pass lines with the values of your custom fields.
These table parameters have to indicate what extension structures you want to use and their values.
As these custom fields may extend different tables of the material, you have to indicate different extension structures depending on which table these fields belong to:
For the table MARA, the extension structures are BAPI_TE_MARA and BAPI_TE_MARAX.
For the table MARC, the extension structures are BAPI_TE_MARC and BAPI_TE_MARCX.
These extension structures should preferably have character-like fields to simplify the programming (and to support IDocs, as rule-of-thumb).
For instance, if you have the custom fields ZZCNAME (7 characters) and ZZCTEXT (50 characters) in the table MARA, they will also be defined in BAPI_TE_MARA and have the same names and types. In BAPI_TE_MARAX, you also have two fields with the same names, but always of length 1 character and their values must be 'X' to indicate that a value is passed in BAPI_TE_MARA (useful in case a blank value is passed that must not be ignored). The X extension structures are essential especially in "change" BAPIs.
If you want to pass values to the BAPI, you must first initialize these structures:
BAPI_TE_MARA:
MATERIAL ZZCNAME ZZCTEXT
------------ ------- -------
000000012661 NAME TEXT
BAPI_TE_MARAX:
MATERIAL ZZCNAME ZZCTEXT
------------ ------- -------
000000012661 X X
Then, you must initialize the two parameters of the BAPI:
EXTENSIONIN (notice that there are 3 spaces in NAME TEXT because the technical length of ZZCNAME is 7 characters and its value "NAME" occupies only 4 characters):
STRUCTURE VALUEPART1 (240 Char) VALUEPART2 (240) VALUEPART3 (240) VALUEPART4 (240)
------------ ----------------------- ---------------- ---------------- ----------------
BAPI_TE_MARA 000000012661NAME TEXT
EXTENSIONINX:
STRUCTURE VALUEPART1 (240 Char) VALUEPART2 (240) VALUEPART3 (240) VALUEPART4 (240)
------------- --------------------- ---------------- ---------------- ----------------
BAPI_TE_MARAX 000000012661XX
Consequently, your program must:
concatenate all BAPI_TE_MARA fields together and copy the resulting string into fields VALUEPART1 to VALUEPART4 of EXTENSIONIN as if it was a 960 characters field
concatenate all BAPI_TE_MARAX fields together and copy the resulting string into fields VALUEPART1 to VALUEPART4 of EXTENSIONINX
I guess you may use ToString() to get one concatenated string of characters of all fields of a structure, and to set the value of VALUEPART1, VALUEPART2, etc., you'll probably need to initialize them individually from the string of characters with Substring.
My comment was half by half correct and incorrect, I wasn't aware of the extension technique in this BAPI, so I wasn't aware of this structure is really used in this BAPI.
You asked
The problem now is how to extend the needed structures "BAPI_TE_MARA/X" so that they can carry the custom fields?`
and what I said is indeed stays valid: you cant extend the interface from NCo, only on backend.
You writes:
At this If I load BAPI_TE_MARA there aren't any custom fields but the material
and this get me to the idea that your ABAP developers made only half of the work. The things to be done on the SAP backend:
Extend MARA table with custom Z fields (in SAP it is called Append structure)
Extend interface structure BAPI_TE_MARA with the fields which should exactly correspond to the MARA fields
This is how it must look like on backend
If you don't see any custom fields in BAPI_TE_MARA except MATERIAL probably step 2 is missing on SAP side. As what I got from your comments, they created structure ZMM_S_MATMAS_ADDITION but appended it only to MARA, but not to BAPI_TE_MARA.
What is missing from Sandra excellent holistic answer is step 3: for all this construction to work some customizing need to be done.
T130F table must contain your custom fields. To maintain the entry for T130F go to transaction SPRO or directly to maintenance view V_130F.
SPRO way: go to SPRO -> Logistics-General -> Material Master -> Field Selection -> Assign fields to field Selection Groups and maintain the entry in the table
Sample ABAP code that does the thing:
DATA: ls_headdata TYPE bapimathead,
lt_extensionin TYPE STANDARD TABLE OF bapiparex,
ls_extensionin LIKE LINE OF lt_extensionin,
lt_extensioninx TYPE STANDARD TABLE OF bapiparexx,
ls_extensioninx LIKE LINE OF lt_extensioninx,
lt_messages TYPE bapiret2_t,
ls_bapi_te_mara TYPE bapi_te_mara,
ls_bapi_te_marax TYPE bapi_te_marax.
ls_headdata-material = |{ ls_headdata-material ALPHA = IN }|.
ls_headdata-basic_view = 'X'.
ls_bapi_te_mara-material = ls_headdata-material.
ls_bapi_te_mara-zztest1 = '322223'.
ls_bapi_te_marax-material = ls_headdata-material.
ls_bapi_te_marax-zztest1 = 'X'.
ls_extensionin-structure = 'BAPI_TE_MARA'.
ls_extensionin-valuepart1 = ls_bapi_te_mara
APPEND ls_extensionin TO lt_extensionin.
ls_extensioninx-structure = 'BAPI_TE_MARAX'.
ls_extensioninx-valuepart1 = ls_bapi_te_marax-zztest1.
APPEND ls_extensioninx TO lt_extensioninx.
CALL FUNCTION 'BAPI_MATERIAL_SAVEDATA'
EXPORTING
headdata = ls_headdata
TABLES
returnmessages = lt_messages
extensionin = lt_extensionin
extensioninx = lt_extensioninx.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING
wait = 'X'.
Based on this you can model your .Net code for BAPI calling.
P.S. Pay attention to the first line with ALPHA = IN. The input to the material number field must be in fully qualified 18-char format with leading zeroes, e.g. 000000000000323, otherwise the update will fail.
Always extend structure EMARA and not MARA, BAPI_TE_MARA, ... directly.

How to get the current language environment (SET COUNTRY)? [duplicate]

As the ABAP documentation of Formatting Settings explains:
The formatting settings are set as follows:
At the start of an internal session they are determined by the related default settings in the fixed values in the user master record of the current user.
Using the statement SET COUNTRY, this default setting for the current internal session can be overwritten using country-specific formats.
But as the ABAP documentation of SET COUNTRY makes clear, there is no way to query what's actually been set with this statement:
Do not confuse the statement SET COUNTRY with the obsolete addition COUNTRY of the statement SET LOCALE LANGUAGE, used for setting the text environment. In particular, it does not have a corresponding GET COUNTRY statement.
Indeed, the ABAP documentation of GET LOCALE - obsolete parameters mentions:
The addition COUNTRY was intended for reading the country key of the current text environment explicitly. cntry expects a character-like data object. The function of this addition was not implemented in full and the result is undefined.
The addition COUNTRY of the statement GET LOCALE does not extract the formatting setting that can be set using SET COUNTRY.
Which leaves me with a bit of a conundrum. I could determine my user defaults with FM SUSR_GET_USER_DEFAULTS. I could figure out the setting for the country from table T005X. But I have no way of figuring out which specific country format was set, or even if one was set in the active session!
How do I determine which formatting settings are currently active?
Bonus question: is there a way to figure this out in the Debugger?
Maybe you can use the function module CLSE_SELECT_USR01.
The following example:
REPORT test.
START-OF-SELECTION.
DATA: decimal_sign , separator.
PERFORM output.
SET COUNTRY 'US'.
PERFORM output.
FORM output.
CALL FUNCTION 'CLSE_SELECT_USR01'
* EXPORTING
* USERNAME = sy-uname
* IV_DELETE_BUFFER = ' '
IMPORTING
* X_USR01 =
* DATE_FORMAT =
decimal_sign = decimal_sign
separator = separator.
WRITE: / 'DECIMAL_SIGN', decimal_sign, 'separator', separator.
ENDFORM.
shows:
My default locale is DE, so I get the actual setting for decimals.
From your comment:
Unfortunately I have to parse and analyse output data that's prepared for screen display from potentially dozens of different form sources.
Do you get the output at runtime or a previous run? Because there is no time machine to get the locale from a call in the past :)
The ABAP statement SET COUNTRY may change the date format, the time format (since ABAP 7.02) and the number format, but there's officially no reverse way to get the current active country code (as you quoted in your question, based on the ABAP documentation). It's quite logical because, for instance, the current number format may be different from the current country code, so it's better to test the directly the kind of format you need to use, as follows.
To detect the current date format, use the official way which returns a character, whose possible values are described in the ABAP documentation of Date Formats):
DATA(current_date_format) = CL_ABAP_DATFM=>GET_DATFM( ).
To detect the current time format, use the official way which returns a character:
DATA(current_time_format) = CL_ABAP_TIMEFM=>GET_ENVIRONMENT_TIMEFM( ).
It returns one of the following values, with an example value corresponding to noon + 5 minutes and 10 seconds (the example value is given if it's output on at least 11 characters):
0 : 12:05:10 (0 to 23)
1 : 12:05:10 PM (0 to 12)
2 : 12:05:10 pm (0 to 12)
3 : 00:05:10 PM (0 to 11)
4 : 00:05:10 pm (0 to 11)
To detect the current number format, based on the idea from #Gert Beukema, you may do as follows:
DATA(current_number_format) = SWITCH usr01-dcpfm(
|{ 1000 NUMBER = ENVIRONMENT DECIMALS = 1 }|
WHEN '1.000,00' THEN ' '
WHEN '1,000.00' THEN 'X'
WHEN '1 000,00' THEN 'Y' ).
NB: the values , X and Y which are returned by this expression are the same values as those used in tables-columns USR01-DCPFM and T005X-XDEZP.

Determining the active formatting settings in ABAP

As the ABAP documentation of Formatting Settings explains:
The formatting settings are set as follows:
At the start of an internal session they are determined by the related default settings in the fixed values in the user master record of the current user.
Using the statement SET COUNTRY, this default setting for the current internal session can be overwritten using country-specific formats.
But as the ABAP documentation of SET COUNTRY makes clear, there is no way to query what's actually been set with this statement:
Do not confuse the statement SET COUNTRY with the obsolete addition COUNTRY of the statement SET LOCALE LANGUAGE, used for setting the text environment. In particular, it does not have a corresponding GET COUNTRY statement.
Indeed, the ABAP documentation of GET LOCALE - obsolete parameters mentions:
The addition COUNTRY was intended for reading the country key of the current text environment explicitly. cntry expects a character-like data object. The function of this addition was not implemented in full and the result is undefined.
The addition COUNTRY of the statement GET LOCALE does not extract the formatting setting that can be set using SET COUNTRY.
Which leaves me with a bit of a conundrum. I could determine my user defaults with FM SUSR_GET_USER_DEFAULTS. I could figure out the setting for the country from table T005X. But I have no way of figuring out which specific country format was set, or even if one was set in the active session!
How do I determine which formatting settings are currently active?
Bonus question: is there a way to figure this out in the Debugger?
Maybe you can use the function module CLSE_SELECT_USR01.
The following example:
REPORT test.
START-OF-SELECTION.
DATA: decimal_sign , separator.
PERFORM output.
SET COUNTRY 'US'.
PERFORM output.
FORM output.
CALL FUNCTION 'CLSE_SELECT_USR01'
* EXPORTING
* USERNAME = sy-uname
* IV_DELETE_BUFFER = ' '
IMPORTING
* X_USR01 =
* DATE_FORMAT =
decimal_sign = decimal_sign
separator = separator.
WRITE: / 'DECIMAL_SIGN', decimal_sign, 'separator', separator.
ENDFORM.
shows:
My default locale is DE, so I get the actual setting for decimals.
From your comment:
Unfortunately I have to parse and analyse output data that's prepared for screen display from potentially dozens of different form sources.
Do you get the output at runtime or a previous run? Because there is no time machine to get the locale from a call in the past :)
The ABAP statement SET COUNTRY may change the date format, the time format (since ABAP 7.02) and the number format, but there's officially no reverse way to get the current active country code (as you quoted in your question, based on the ABAP documentation). It's quite logical because, for instance, the current number format may be different from the current country code, so it's better to test the directly the kind of format you need to use, as follows.
To detect the current date format, use the official way which returns a character, whose possible values are described in the ABAP documentation of Date Formats):
DATA(current_date_format) = CL_ABAP_DATFM=>GET_DATFM( ).
To detect the current time format, use the official way which returns a character:
DATA(current_time_format) = CL_ABAP_TIMEFM=>GET_ENVIRONMENT_TIMEFM( ).
It returns one of the following values, with an example value corresponding to noon + 5 minutes and 10 seconds (the example value is given if it's output on at least 11 characters):
0 : 12:05:10 (0 to 23)
1 : 12:05:10 PM (0 to 12)
2 : 12:05:10 pm (0 to 12)
3 : 00:05:10 PM (0 to 11)
4 : 00:05:10 pm (0 to 11)
To detect the current number format, based on the idea from #Gert Beukema, you may do as follows:
DATA(current_number_format) = SWITCH usr01-dcpfm(
|{ 1000 NUMBER = ENVIRONMENT DECIMALS = 1 }|
WHEN '1.000,00' THEN ' '
WHEN '1,000.00' THEN 'X'
WHEN '1 000,00' THEN 'Y' ).
NB: the values , X and Y which are returned by this expression are the same values as those used in tables-columns USR01-DCPFM and T005X-XDEZP.

elm-css: How to give a value to `opacity`

I am playing around with elm-css.
Most of the things work as I expect them.
But I am not able to give a correct value to the Css.opacity function.
Here is what I have tried:
Css.opacity 0.5
which gives the error:
Function `opacity` is expecting the argument to be:
Css.Number compatible
But it is:
Float
The Css.Number is a type alias in the following form:
type alias Number compatible =
{ compatible | value : String, number : Compatible }
But I don't understand how to create a valid value for the Css.opacity function...
You can create input for opacity by using one of the "unitless" functions, like Css.int or Css.num. For example:
-- 42% opaque
translucent = Css.opacity (Css.num 0.42)
It is "unitless" because the CSS property of opacity does not define a unit like px or percent.

Lossless assignment between Field-Symbols

I'm currently trying to perform a dynamic lossless assignment in an ABAP 7.0v SP26 environment.
Background:
I want to read in a csv file and move it into an internal structure without any data losses. Therefore, I declared the field-symbols:
<lfs_field> TYPE any which represents a structure component
<lfs_element> TYPE string which holds a csv value
Approach:
My current "solution" is this (lo_field is an element description of <lfs_field>):
IF STRLEN( <lfs_element> ) > lo_field->output_length.
RAISE EXCEPTION TYPE cx_sy_conversion_data_loss.
ENDIF.
I don't know how precisely it works, but seems to catch the most obvious cases.
Attempts:
MOVE EXACT <lfs_field> TO <lfs_element>.
...gives me...
Unable to interpret "EXACT". Possible causes: Incorrect spelling or comma error
...while...
COMPUTE EXACT <lfs_field> = <lfs_element>.
...results in...
Incorrect statement: "=" missing .
As the ABAP version is too old I also cannot use EXACT #( ... )
Example:
In this case I'm using normal variables. Lets just pretend they are field-symbols:
DATA: lw_element TYPE string VALUE '10121212212.1256',
lw_field TYPE p DECIMALS 2.
lw_field = lw_element.
* lw_field now contains 10121212212.13 without any notice about the precision loss
So, how would I do a perfect valid lossless assignment with field-symbols?
Don't see an easy way around that. Guess that's why they introduced MOVE EXACT in the first place.
Note that output_length is not a clean solution. For example, string always has output_length 0, but will of course be able to hold a CHAR3 with output_length 3.
Three ideas how you could go about your question:
Parse and compare types. Parse the source field to detect format and length, e.g. "character-like", "60 places". Then get an element descriptor for the target field and check whether the source fits into the target. Don't think it makes sense to start collecting the possibly large CASEs for this here. If you have access to a newer ABAP, you could try generating a large test data set there and use it to reverse-engineer the compatibility rules from MOVE EXACT.
Back-and-forth conversion. Move the value from source to target and back and see whether it changes. If it changes, the fields aren't compatible. This is unprecise, as some formats will change although the values remain the same; for example, -42 could change to 42-, although this is the same in ABAP.
To-longer conversion. Move the field from source to target. Then construct a slightly longer version of target, and move source also there. If the two targets are identical, the fields are compatible. This fails at the boundaries, i.e. if it's not possible to construct a slightly-longer version, e.g. because the maximum number of decimal places of a P field is reached.
DATA target TYPE char3.
DATA source TYPE string VALUE `123.5`.
DATA(lo_target) = CAST cl_abap_elemdescr( cl_abap_elemdescr=>describe_by_data( target ) ).
DATA(lo_longer) = cl_abap_elemdescr=>get_by_kind(
p_type_kind = lo_target->type_kind
p_length = lo_target->length + 1
p_decimals = lo_target->decimals + 1 ).
DATA lv_longer TYPE REF TO data.
CREATE DATA lv_longer TYPE HANDLE lo_longer.
ASSIGN lv_longer->* TO FIELD-SYMBOL(<longer>).
<longer> = source.
target = source.
IF <longer> = target.
WRITE `Fits`.
ELSE.
WRITE `Doesn't fit, ` && target && ` is different from ` && <longer>.
ENDIF.