How to call BAPI_MATERIAL_SAVEDATA with custom fields from NCo? - bapi

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.

Related

Error trying to screen an internal table abap

I´m learning ABAP and I keep trying to write that internal table and show it. There's syntax error message at line WRITE: / I_EJSEIS:
"I_EJSEIS" cannot be converted to a character-like value
I just don't understand it.
TYPES: S_EJSEIS LIKE SPFLI.
DATA: I_EJSEIS TYPE TABLE OF S_EJSEIS WITH HEADER LINE,
WA_EJSEIS TYPE S_EJSEIS.
SELECT FLTYPE
FROM SPFLI
INTO TABLE I_EJSEIS
WHERE CARRID = 'LH'.
LOOP AT I_EJSEIS.
WRITE: / I_EJSEIS.
ENDLOOP.
According to the ABAP documentation of WRITE dobj:
For dobj, those data types are allowed that are grouped under the generic type simple:
All flat data types; flat structures are handled like a data object of type c and can only contain any character-like components.
The data types string and xstring
enumerated types; the name (maximum three characters) of the enumerated constant is used in uppercase letters, which defines the current enumerated value.
In your case, I_EJSEIS is a (flat) structure containing at least one non-character component (e.g. fltime, distance, period), so it doesn't fall in any of the categories above.
The workaround is to display the fields individually:
WRITE: / I_EJSEIS-FLTYPE, I_EJSEIS-FLTIME.

Building a (process) variable in Appian using the value of another one?

As far as I understand, it is not possible in Appian to dynamically construct (process) variable names, just like you would do e.g. with bash using backticks like MY_OBJECT=pv!MY_CONS_`extract(valueOfPulldown)`. Is that correct? Is there a workaround?
I have set of Appian constants, let's call them MY_CONS_FOO, MY_CONS_BAR, MY_CONS_LALA, all of which are e.g. refering to an Appian data store entity. I would like to write an Appian expression rule which populates another variable MY_OBJECT of the same type (here: data store entity), depending e.g. of the options of a pull-down menu having the possible options stored in an array MY_CONS_OPTIONS looking as follows
FOO
BAR
LALA
I could of course build a lengthy case-structure which I have to maintain in addition to MY_CONS_OPTIONS, so I am searching for a more dynanmic approach using the extract() function depending on valueOfPulldown as the chosen value of the pulldown-menu.
Edit: Here the expression-rule (in pseudo-code) I want to avoid:
if (valueOfPulldown = 'FOO') then MY_OBJECT=pv!MY_CONS_FOO
if (valueOfPulldown = 'BAR') then MY_OBJECT=pv!MY_CONS_BAR
if (valueOfPulldown = 'LALA') then MY_OBJECT=pv!MY_CONS_LALA
The goal is to be able to change the data store entity via pulldown-menu.
This can help you find what is behind your constant.
fn!typeName(fn!typeOf(cons!YOUR_CONSTANT)).
Having in mind additional details I would do as follows:
Create separate expression that will combine details into list of Dictionary like below:
Expression results (er):
{
{dd_label: "label1", dd_value: 1, cons: "cons!YOUR_CONSTANT1" }
,{dd_label: "label2", dd_value: 2, cons: "cons!YOUR_CONSTANT2" }
}
on UI for your dropdown control use er.dd_label as choiceLabels and er.dd_value as choiceValues
when user selects value on Dropdown save dropdown value to some local variable and then use it to find your const by doing:
property( index(er, wherecontains(local!dropdownselectedvalue, tointeger(er.dd_value))), "cons")
returned value of step 3 is your constant
This might not be perfect as you still have to maintain your dictionary but you can avoid long if...else statements.
As a alternative have a look on Decisions Tables in Appian https://docs.appian.com/suite/help/21.1/Appian_Decisions.html

Modifying text on input field without using of TABLES keyword

I want to show text on input field in the screen, which is value from work area, respectivetly name, age and city, as you can see. When I declare znew_fdkey01 and znew_fdkey02 (which are transparent tables) with using TABLES keyword like this:
TABLES: znew_fdkey01, znew_fdkey02.
it works perfectly. But when I want to obtain the same effect without using this keyword, and when I declare variables like this:
DATA: znew_fdkey01 TYPE znew_fdkey01,
znew_fdkey02 TYPE znew_fdkey02.
it does not show me text in the input field.
Why?
NB: here is the code to initialize the screen fields (the same in both cases):
LOOP AT SCREEN INTO screen_wa.
IF screen_wa-name = 'ZNEW_FDKEY01-NAME'.
znew_fdkey01-name = lr_znewfdkey3-name.
ENDIF.
IF screen_wa-name = 'ZNEW_FDKEY01-AGE'.
znew_fdkey01-age = lr_znewfdkey3-age.
ENDIF.
IF screen_wa-name = 'ZNEW_FDKEY02-CITY'.
znew_fdkey02-city = lr_znewfdkey3-city.
ENDIF.
MODIFY SCREEN FROM screen_wa.
ENDLOOP.
This is correct, TABLES defines work areas and at the same time it is necessary to ensure the automatic communication between the screen (dynpro) and the ABAP program, as documented in the ABAP Help:
Table work areas declared using TABLES are interface work areas...
The statement TABLES is required for exchanging data between dynpro fields and the ABAP program, if the fields were defined in a dynpro in the program by being taken from ABAP Dictionary, . In the dynpro event PBO, the content of the table work area is passed to identically named dynpro fields. In PAI, the system takes the data from identically named dynpro fields.
(Otherwise don't use TABLES to declare work areas, that is obsolete)
You must use TABLES only if you define your screen input/output field as a "DDIC" field (Data/ABAP Dictionary). This is a checkbox defined in the Screen Painter for each screen field.
There are two possibilities:
Either you name your screen I/O field like according to an existing DDIC structure field (as you did, ZNEW_FDKEY01-NAME) and you define it as a "DDIC" field (it's a field attribute in the Screen Painter) and you must use TABLES to transfer values.
Or you don't define your screen input field as a "DDIC" field, and you define the field name as a global variable in your program, using TABLES is completely optional. But in that case, you lose some features in the screen that are unique to the DDIC, like the reuse of DDIC labels (any change impacts all screens), the Value Help/F4 like the search help implying multiple fields (but you can still code it in ABAP from scratch), etc.
Example:
Create this program:
TABLES spfli. " Mandatory as screen field SPFLI-CARRID is connected to the "DDIC"
DATA scarr TYPE scarr. " or TABLES scarr if you wish
spfli-carrid = 'LH'.
scarr-carrname = 'Name of company'.
CALL SCREEN 100.
MODULE screen_0100_pai INPUT.
LEAVE TO SCREEN 0.
ENDMODULE.
Create the dynpro 0100 with these fields (pay attention to "Dict.field")
Name Type Line Col. Def Vis Format Inp Out Dict.field
SPFLI-CARRID I/O 1 18 3 3 CHAR X X X
SCARR-CARRNAME I/O 2 18 20 20 CHAR X X
and this flow logic:
PROCESS BEFORE OUTPUT.
PROCESS AFTER INPUT.
MODULE SCREEN_0100_PAI.
Activate and run the program.
Result as expected:

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.

Get Text Symbol Programmatically With ID

Is there any way of programmatically getting the value of a Text Symbol at runtime?
The scenario is that I have a simple report that calls a function module. I receive an exported parameter in variable LV_MSG of type CHAR1. This indicates a certain status message created in the program, for instance F (Fail), X (Match) or E (Error). I currently use a CASE statement to switch on LV_MSG and fill another variable with a short description of the message. These descriptions are maintained as text symbols that I retrieve at compile time with text-MS# where # is the same as the possible returns of LV_MSG, for instance text-MSX has the value "Exact Match Found".
Now it seems to me that the entire CASE statement is unnecessary as I could just assign to my description variable the value of the text symbol with ID 'MS' + LV_MSG (pseudocode, would use CONCATENATE). Now my issue is how I can find a text symbol based on the String representation of its ID at runtime. Is this even possible?
If it is, my code would look cleaner and I wouldn't have to update my actual code when new messages are added in the function module, as I would simply have to add a new text symbol. But would this approach be any faster or would it in fact degrade the report's performance?
Personally, I would probably define a domain and use the fixed values of the domain to represent the values. This way, you would even get around the string concatenation. You can use the function module DD_DOMVALUE_TEXT_GET to easily access the language-dependent text of a domain value.
To access the text elements of a program, use a function module like READ_TEXT_ELEMENTS.
Be aware that generic programming like this will definitely slow down your program. Whether it would make your code look cleaner is in the eye of the beholder - if the values change rarely, I don't see why a simple CASE statement should be inferior to some generic text access.
Hope I understand you correctly but here goes. This is possible with a little trickery, all the text symbols in a report are defined as variables in the program (with the name text-abc where abc is the text ID). So you can use the following:
data: lt_all_text type standard table of textpool with default key,
lsr_text type ref to textpool.
"Load texts - you will only want to do this once
read textpool sy-repid into lt_all_text language sy-langu.
sort lt_all_Text by entry.
"Find a text, the field KEY is the text ID without TEXT-
read table lt_all_text with key entry = i_wanted_text
reference into lsr_text binary search.
If you want the address you can add:
field-symbols: <l_text> type any.
data l_name type string.
data lr_address type ref to data.
concatenate 'TEXT-' lsr_text->key into l_name.
assign (l_name) to <l_text>.
if sy-subrc = 0.
get reference of <l_text> into lr_address.
endif.
As vwegert pointed out this is probably not the best solution, for error handling rather use message classes or exception objects. This is useful in other cases though so now you know how.