Показаны сообщения с ярлыком abap. Показать все сообщения
Показаны сообщения с ярлыком abap. Показать все сообщения

пятница, 12 апреля 2024 г.

Генерация "широких" диалогов ведения таблиц

Стандартный генератор ведения таблиц (и ракурсов) в SE11 генерирует табличный компонент на экране не шире 83 символов. Наверное, когда-то давно, когда компьютеры были большими, а экраны маленьким, это было нормально. Но сейчас во времена всяких FullHD и выше это уже выглядит неудобным, поскольку для таблиц, где суммартная длина полей выходит за эти 83 символа, появляется прокрутка, хотя места на экране еще больше половины.

Конечно, после генерации всегда можно вручную поправить экран и сделать компонент шире (до 255 символов).

Но настоящему ленивому програмисту всегда хочется оптимизировать такую рутину. И вот оно решение ниже. Исходники есть в Github Gist.

Нужно создать пул подпрограмм ZBC_MAINTANCE_VIEW_EXITS и в него запихать весь код из ZBC_MAINTANCE_VIEW_EXITS.abap.

Далее нужно в программе SAPMSVIM создать в 3х подпрограммах неявные расширения и вставить в них вызовы подпрограмм из ZBC_MAINTANCE_VIEW_EXITS. Что конкретно расширять и что вставлять, указано в MSVIMF21.abap

После активации всего этого добра, нужно сгенерировать (или перегенерировать) экран ведения.

ЗЫ. Кроме "расширения" табличного компонента, для таблиц, имеющих связанные текстовые таблицы, все поля текстовых таблиц "переезжают" вправо.

"1
"Add Implicit Enhancement at the end of form CREATE_DYNP_HEADER (INCLUDE MSVIMF21)
"Put this code inside created enhancement:
PERFORM post_create_dynp_header in PROGRAM ZBC_MAINTANCE_VIEW_EXITS if FOUND
USING p_dyname p_gencb p_detail dropdown_length_tcrl max_dynp_fieldlen CHANGING p_header.
"2
"Add Implicit Enhancement at the begin of form CREATE_DYNP_FIELDLIST (INCLUDE MSVIMF21)
"Put this code inside created enhancement:
PERFORM pre_create_dynp_fieldlist IN PROGRAM ZBC_MAINTANCE_VIEW_EXITS IF FOUND
USING p_make_detail p_skip_fields p_flds_to_cont CHANGING p_dfies_tab.
"3
"Add Implicit Enhancement at the end of form CREATE_DYNP_CONT (INCLUDE MSVIMF21)
"Put this code inside created enhancement:
IF p_mode = '11' and <w_cont> is ASSIGNED.
PERFORM post_create_dynp_cont in PROGRAM ZBC_MAINTANCE_VIEW_EXITS if FOUND
CHANGING <w_cont>.
ENDIF.
view raw MSVIMF21.abap hosted with ❤ by GitHub
*&---------------------------------------------------------------------*
*& Subroutinenpool ZBC_MAINTANCE_VIEW_EXITS
*&
*&---------------------------------------------------------------------*
PROGRAM zbc_maintance_view_exits.
TYPES:
BEGIN OF ts_dynpid,
prog LIKE d020s-prog,
dnum LIKE d020s-dnum,
END OF ts_dynpid.
TYPES tt_dfies TYPE STANDARD TABLE OF dfies WITH KEY tabname fieldname langu position.
TYPES tt_flds_to_cont_type TYPE STANDARD TABLE OF rpy_dyfatc WITH DEFAULT KEY.
DATA gps_header TYPE REF TO rpy_dyhead.
DATA gpt_fields TYPE REF TO tt_flds_to_cont_type.
FORM post_create_dynp_header
USING is_dyname TYPE ts_dynpid
is_gencb TYPE vimgencb
iv_detail TYPE xfeld
iv_dropdown_length_tcrl TYPE scrndeflg
iv_max_dynp_fieldlen TYPE scrnvislg
CHANGING cs_header TYPE rpy_dyhead.
CLEAR gps_header.
CLEAR gpt_fields.
CHECK iv_detail IS INITIAL.
"Add some check of enabling this functionality if you need. Something like it:
"SELECT SINGLE @abap_true FROM tvarvc WHERE name = 'ZDISABLE_WIDE_VIEW_MAINT_GEN' INTO @DATA(lv_disable).
"CHECK lv_disable IS INITIAL.
gps_header = REF #( cs_header ).
ENDFORM.
FORM pre_create_dynp_fieldlist USING iv_make_detail TYPE xfeld
iv_skip_fields TYPE xfeld
it_flds_to_cont TYPE tt_flds_to_cont_type
CHANGING ct_dfies TYPE tt_dfies.
DATA lt_dfies_txt TYPE tt_dfies.
IF iv_make_detail = abap_true OR ct_dfies[] IS INITIAL OR gps_header IS NOT BOUND.
RETURN.
ENDIF.
gpt_fields = REF #( it_flds_to_cont ).
*"Move text table fields to end
DATA(lv_tabname) = ct_dfies[ 1 ]-tabname.
LOOP AT ct_dfies REFERENCE INTO DATA(lps_dfies)
WHERE tabname <> lv_tabname.
APPEND lps_dfies->* TO lt_dfies_txt.
ENDLOOP.
IF sy-subrc = 0.
DELETE ct_dfies WHERE tabname <> lv_tabname.
APPEND LINES OF lt_dfies_txt TO ct_dfies.
ENDIF.
ENDFORM.
FORM post_create_dynp_cont CHANGING cs_cont TYPE rpy_dycatt.
DATA lps_field TYPE REF TO rpy_dyfatc.
CHECK gps_header IS BOUND AND gpt_fields IS BOUND.
DATA(lv_size) = 0.
LOOP AT gpt_fields->* REFERENCE INTO lps_field
WHERE tc_heading IS INITIAL AND tc_title IS INITIAL.
ADD lps_field->vislength TO lv_size.
ENDLOOP.
lv_size = nmin( val1 = lv_size val2 = 255 ).
IF lv_size > gps_header->columns.
gps_header->columns = lv_size.
ENDIF.
IF lv_size > cs_cont-length.
cs_cont-length = lv_size.
ENDIF.
CLEAR gps_header.
CLEAR gpt_fields.
ENDFORM.

вторник, 6 апреля 2021 г.

Регистро-независимый поиск по полю БД, которое это не предусматривает (также костыль в версиях SAP BASIS ниже 7.51)

Постановка

Допустим есть таблица ZDOCUMENTS c полями DOCNR типа BELNR и полем SENDERNAME типа TEXT255. DOCNR - ключевое, SENDERNAME заполняется как угодно и может содержать разное написание одного и того же, например "ООО Компания" и "ООО КОМПАНИЯ". То есть принудительно SENDERNAME ни к какому регистру не приводится.

Необходимо построить отчет с селекционным экраном с 2 полями типа SELECT-OPTION и для DOCNR и для SENDERNAME. 
При запуске отчета должны выбираться данные из ZDOCUMENTS по заданным параметрам и выводится на экран.
Причем поиск по SENDERNAME должен быть регистронезависимым. То есть, по приведенному выше примеру, при вводе "*компания*" должны быть найдены документы и "ООО Компания", и "ООО КОМПАНИЯ".

БД Oracle ( на самом деле не важно, тут главное что не HANA)

Решение

В 7.51 и в OpenSQL и в CDS была добавлена функция UPPER, которая решает нашу задачу. Подробности тут

Проблема

Ниже 7.51 тоже живут компании и не собираются обновляться. Что делать?

понедельник, 8 февраля 2021 г.

SALV с DropDown кнопками в панели инструментов

Когда мы строим вывод таблицы на базе SALV, мы можем в панель инструментов добавить свои кнопки и потом обработать нажатие на них. Примерно вот так:
DATA lo_alv TYPE REF TO cl_salv_table READ-ONLY.
...
DATA(lo_funcs) = lo_alv->get_functions( ).
lo_funcs->add_function(
  EXPORTING
    name     = 'ZREFRESH'
 	icon     = CONV #( ICON_REFRESH )
	text     = 'Обновить'
	tooltip  = 'Обновить'
	position = if_salv_c_function_position=>left_of_salv_functions
).
...
DATA(lo_event) = lo_alv->get_event( ).
SET HANDLER on_alv_function FOR lo_event ACTIVATION abap_true.
...
METHODS on_alv_function FOR EVENT added_function OF cl_salv_events IMPORTING sender e_salv_function.
...
METHOD on_alv_function.
  CASE e_salv_function.
    WHEN 'ZREFRESH'.
      ...
    WHEN OTHERS.
  ENDCASE.
ENDMETHOD.
Но данный способ не подойдет, если вы хотите добавить не простую кнопку, а кнопку с выпадающими действиями типа такой:
Поскольку в классе CL_SALV_FUNCTIONS_LIST просто нет подходящих методов для этого.

понедельник, 2 ноября 2020 г.

Странное поведение приведения классов

Итак, у нас есть примерно вот такой код:
REPORT ztest_class_ref_data.
CLASS lcl_abstract_data DEFINITION ABSTRACT.ENDCLASS.
CLASS lcl_abstract_processor DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS constructor IMPORTING io_data TYPE REF TO lcl_abstract_data.
PROTECTED SECTION.
DATA mo_data TYPE REF TO lcl_abstract_data.
ENDCLASS.
CLASS lcl_abstract_processor IMPLEMENTATION.
METHOD constructor.
mo_data = io_data.
ENDMETHOD.
ENDCLASS.
CLASS lcl_data DEFINITION INHERITING FROM lcl_abstract_data.
PUBLIC SECTION.
TYPES tt_data TYPE STANDARD TABLE OF i WITH EMPTY KEY.
DATA mt_data TYPE tt_data.
CLASS-METHODS cast
IMPORTING io_data TYPE REF TO lcl_abstract_data
RETURNING VALUE(ro_data) TYPE REF TO lcl_data.
METHODS print .
ENDCLASS.
CLASS lcl_data IMPLEMENTATION.
METHOD cast.
ro_data ?= io_data.
ENDMETHOD.
METHOD print.
LOOP AT mt_data INTO DATA(lv_value).
WRITE lv_value. NEW-LINE.
ENDLOOP.
ULINE.
ENDMETHOD.
ENDCLASS.
CLASS lcl_processor DEFINITION INHERITING FROM lcl_abstract_processor.
PUBLIC SECTION.
METHODS process .
METHODS process2.
METHODS process3.
ENDCLASS.
CLASS lcl_processor IMPLEMENTATION.
METHOD process.
DATA(lo_data) = lcl_data=>cast( mo_data ).
LOOP AT lo_data->mt_data ASSIGNING FIELD-SYMBOL(<v_value>).
ADD 1 TO <v_value>.
ENDLOOP.
ENDMETHOD.
METHOD process2.
LOOP AT CAST lcl_data( mo_data )->mt_data ASSIGNING FIELD-SYMBOL(<v_value>).
ADD 1 TO <v_value>.
ENDLOOP.
ENDMETHOD.
METHOD process3.
LOOP AT lcl_data=>cast( mo_data )->mt_data ASSIGNING FIELD-SYMBOL(<v_value>).
ADD 1 TO <v_value>.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA(lo_data) = NEW lcl_data( ).
lo_data->mt_data = VALUE #( ( 1 ) ( 2 ) ).
DATA(lo_processor) = NEW lcl_processor( lo_data ).
lo_data->print( ).
lo_processor->process( ).
lo_data->print( ).
lo_processor->process2( ).
lo_data->print( ).
lo_processor->process3( ).
lo_data->print( ).

Код притянут за уши, но смысл его примерно такой. Есть некий базовый класс данных (LCL_ABSTRACT_DATA). От него наследуется конкретный класс с конкретными данными (LCL_DATA).Данные тут таблица целых чисел. Для простоты она сделана публичной. Также есть метод PRINT, который выводит содержимое таблицы на экран.

Также есть некий абстрактный обработчик данных (LCL_ABSTRACT_PROCESSOR), который при создании на вход получает ссылку на инстанцию класса с данными и сохраняет ее у себя в атрибуте (MO_DATA). От него наследуется обработчик для конкретных данных (LCL_PROCESSOR), который работает именно с LCL_DATA. Для этого внутри своих методов он приводит ссылку на LCL_ABSTRACT_DATA к ссылке на LCL_DATA и работает именно с ней. У него есть 3 разных метода (PROCESS,PROCESS2,PROCESS3), где приведение происходит разными способами. И это, как говорится, влияет! В остальном методы делают одно и тоже - прибавляют 1 к каждому числу в таблице

В начале программы создается класс с данными и заполняется двумя числами 1 и 2.
Далее создается класс процессора, куда передается ссылка на созданный класс с данными, после чего выводится начальное содежимое таблицы класса с данными.
Далее последовательно вызываются методы PROCESS,PROCESS2,PROCESS3 у процессора и после каждого вызова происходит вывод текущего состояния таблицы с данными на экран.

Рассмотрим первый метод:

  METHOD process.
    DATA(lo_data) = lcl_data=>cast( mo_data ).
    LOOP AT lo_data->mt_data ASSIGNING FIELD-SYMBOL(<v_value>).
      ADD 1 TO <v_value>.
    ENDLOOP.
  ENDMETHOD.
Здесь вначале вызывается метод lcl_data=>cast для приведения ссылки на LCL_ABSTRACT_DATA к ссылке на LCL_DATA. Полученная ссылка запоминается в переменной LO_DATA. После чего идет цикл по LO_DATA->MT_DATA и к каждом числу в таблице добавляется 1. В итоге значения в таблице должны стать 2 и 3: (1,2) + 1 -> (2,3). Забегая вперед - так и есть. Тут все работает.

Рассмотрим второй метод:

  METHOD process2.
    LOOP AT CAST lcl_data( mo_data )->mt_data ASSIGNING FIELD-SYMBOL(<v_value>).
      ADD 1 TO <v_value>.
    ENDLOOP.
  ENDMETHOD.
Здесь вначале для приведения ссылки используется конструкция CAST lcl_data( mo_data ) непосредственно в теле цикла. В логике все то же самое - к каждом числу в таблице добавляется 1. В итоге значения в таблице должны стать 3 и 4: (2,3) + 1 -> (3,4). Забегая вперед - так и есть. Тут тоже все работает.

И, наконец, третий метод:

  METHOD process3.
    LOOP AT lcl_data=>cast( mo_data )->mt_data ASSIGNING FIELD-SYMBOL(<v_value>).
      ADD 1 TO <v_value>.
    ENDLOOP.
  ENDMETHOD.
Здесь вначале для приведения ссылки используется конструкция lcl_data=>cast( mo_data ) как и в первом методе, но ссылка предварительно не запоминается, а результат вызова метода CAST используется непосредственно в теле цикла. В логике все то же самое - к каждом числу в таблице добавляется 1. В итоге значения в таблице должны стать 4 и 5: (3,4) + 1 -> (4,5). И вот тут все не так.

Вот как выглядит вывод программы:

Как видите, последний метод почему-то не меняет данные в таблице, хотя в отладке все вроде происходит: 3 становится 4, а 4 становится 5. Но после выхода из цикла эти изменения испаряются. Такое чувство, что в этом случае создается какая-то локальная копия класса именно для цикла и изменения происходят в ней. После чего она благополучно поглощается сборщиком мусора. И главное не понятно по какой причине. Да, returning возращает value, то есть, значение, а не ссылку, но это значение и содержит ссылку. Ну, то есть, даже если это копия оригинальной ссылки, то указывать-то она все равно должна на то же самое!

В общем толи я чего-то не понимаю, толи это баг в SAP.

SELECT * и CORRESPONDING

Часто в регламентах на разработку или в BestPracties нас пугают тем, что писать SELECT * - это плохо. Аргументируется это тем, что не надо выбирать лишнии данные чтобы избегать проблем с производительностью (передача большего числа полей из БД понятно приводит к замедлению этого процесса) и занимаемой памятью (понятно что переденное где то в итоге хранится). И аргументация правильная, но не многие знают, что SELECT * не всегда выбирает все поля.

пятница, 8 ноября 2019 г.

Запись VBKD-TRATY при использовании BAPI_SALESORDER_CREATEFROMDAT2

BAPI  BAPI_SALESORDER_CREATEFROMDAT2  не содержит входящих параметров чтобы сохранять в создаваемом заказе VBKD-TRATY. В иторнетах встречается предложение вместо этого использовать ФМ SD_SALESDOCUMENT_CREATE (который внутри BAPI и вызывается), передавая TRATY в BUSINESS_EX. Это бред, поскольку BUSINESS_EX это исходящий параметр. Еще есть вариант обновлять VBKD-TRATY прямым update после вызова BAPI и сохранения по полученному номеру заказа. Это в принципе вариант, но все можно сделать прямо через BAPI описанным ниже способом.

VBKD может хранить данные как на уровне позиции, так и на уровне заголовка - в последнем случае в VBKD-POSNR  будут 0. От того куда нужно записать TRATY зависит, что нужно расширять и что передавать в параметрах. В примерах будет про позиции, но для заголовка тоже указано что и как.


вторник, 10 сентября 2019 г.

Связь средств поиска между собой внутри другого средства поиска (F4 on F4)


В средстве поиска (СП) могут быть входящие параметры, которыми можно ограничивать результат, выдаваемый пользователю для выбора.
Возьмем для примера СП для склада H_T001L. Склады LGORT лежат в таблице T001L, склады принадлежат какому либо заводу WERKS. В СП по складу WERKS помечен как импортируемый параметр (IMP):




Также WERKS присутствует в диалоге ограничений:
 

Обратите внимание, когда завод не задан, выводятся склады всех заводов. Но если завод указать, будут выведены только его склады - например укажем завод 0002:

Также на скриншоте выше видно, что у поля Склад в ограничениях также есть кнопка средства поиска, но при её нажатии мы увидим список складов без ограничения по заводу (предприятию):


Таким образом, ограничения из первого СП не передаются во второе СП (хотя по сути тут одно и тоже СП, просто рекурсивно вызванное, в данном случае это не важно).
В данном примере нет особого смысла, поскольку мы и так ищем склад в первом СП, но представьте другую ситуацию.

четверг, 20 июня 2019 г.

Ловля CX_SY_CONVERSION_OVERFLOW

Поймал странное (на мой взгляд) поведение кода при ловле CX_SY_CONVERSION_OVERFLOW.
Допустим есть метод, который принимает строку и возвращает из нее число. Тип возвращаемого числа DECFLOAT34, чтобы влезло как можно больше. Результат метода присваивается переменной имеющей меньшую разрядность. В случае, когда итоговый результат превышает максимально возможный для этого типа переменной, должно выбрасываться CX_SY_CONVERSION_OVERFLOW. Это вроде как все учтено.
При присвоении результата вызова метода напрямую в переменную CX_SY_CONVERSION_OVERFLOW не ловится и лезет дамп.
Но, если переписать присвоение через промежуточную переменную, CX_SY_CONVERSION_OVERFLOW ловится, дампа нет.
В чем прикол?

REPORT ztest_overflow.
DATA gv_value TYPE string VALUE '123456789123456'.
DATA gv_wrbtr TYPE wrbtr_d.

CLASS lcl_test DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS convert
      IMPORTING iv_value        TYPE clike
      RETURNING VALUE(rv_value) TYPE decfloat34
      RAISING   cx_static_check.
ENDCLASS.

CLASS lcl_test IMPLEMENTATION.
  METHOD convert.
    DATA(lv_value) = iv_value.
    DO 2 TIMES.
      TRY .
          rv_value = lv_value.
          EXIT.

        CATCH cx_root INTO DATA(lx_error).
          IF sy-index = 1.
            TRANSLATE lv_value USING ',..,'.
          ELSE.
            RAISE EXCEPTION TYPE cx_sral.
          ENDIF.
      ENDTRY.
    ENDDO.
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.

  BREAK-POINT.
  TRY .
      DATA(lv_value) = lcl_test=>convert( gv_value ).
      gv_wrbtr = lv_value.
    CATCH cx_root.
      WRITE 'Overflow'. NEW-LINE.
  ENDTRY.


  TRY .
      gv_wrbtr = lcl_test=>convert( gv_value ). "!!!!DUMP
    CATCH cx_root.
      WRITE 'Overflow'. NEW-LINE.
  ENDTRY.

вторник, 18 сентября 2018 г.

Парсинг XML с помощью SimpleTransformation с пропуском тегов.

Итак, допустим, есть такой XML:
<root>
<values>
<material>1</material>
<prod>XXXX</prod>
<foo>bar</foo>
<values>
</root>
view raw data.xml hosted with ❤ by GitHub
И, допустим, нас интересует только значение тега material (которого, кстати, может и не быть). Напишем такую версию simple transformation:

<?sap.transform simple?>
<tt:transform xmlns:tt="http://www.sap.com/transformation-templates">
<tt:root name="ROOT"/>
<tt:template>
<root>
<values>
<tt:group>
<tt:d-cond frq="?">
<material tt:value-ref="material"/>
</tt:d-cond>
</tt:group>
</values>
</root>
</tt:template>
</tt:transform>
view raw transform.xslt hosted with ❤ by GitHub
Применение данной трансформации вызовет исключительную ситуацию, так как в XML нам встретиться тег prod и тег foo, которые мы не предусмотрели в трансформации.

Конечно, их можно добавить в трансформацию и никак не обрабатывать их значения. Но таких ненужных нам тегов может быть много и прописывать их все это некомильфо. Кроме того, могут быть ситуации, когда XML описан не четко и подразумевает, что внутри values могут появляться не известные нам на этапе разработки теги.

Как пропускать все что нам не нужно или мы не знаем что это? Для этого нужно в конец tt:group добавить tt:skip следующим образом:
<?sap.transform simple?>
<tt:transform xmlns:tt="http://www.sap.com/transformation-templates">
<tt:root name="ROOT"/>
<tt:template>
<root>
<values>
<tt:group>
<tt:d-cond frq="?">
<material tt:value-ref="material"/>
</tt:d-cond>
<tt:d-cond frq="*">
<tt:skip count="1"/>
</tt:d-cond>
</tt:group>
</values>
</root>
</tt:template>
</tt:transform>
view raw transform.xslt hosted with ❤ by GitHub
Здесь мы "говорим", что если нам встречается тег material, то мы берем его значение и помещаем в ROOT.MATERIAL, иначе мы пропускаем 1 тег.
Тег tt:group работает как цикл + switch/case - берет на каждом шаге следующий тег в values и сравнивает по условиям tt:d-cond, если условие проходит - выполняется то, что внутри tt:d-cond, и происходит следующая итерация "цикла". Таким образом, по примеру выше, мы найдем на первой итерации тег material, запишем его значение в root.material, найдем на следующей итерации prod и пропустим его, далее найдем на следующей итерации foo и также пропустим его (и аналогично для других тегов если они будут) и выйдем из values. Тег tt:skip должен быть в конце tt:group.

Но с данной реализацией есть одна неприятность. Толи это баг в реализации SimpleTransfomation, толи это by design. Заключается она в следующем:

Если в исходном XML будет только тег material, это также вызовет ошибку трансформации. Отладка данного случая показывает, что обработав тег material, парсер все равно пытается выполнить tt:skip хотя бы один раз. Делает он это для следующего тега, которым будет закрывающий тег </values>. В итоге правило для самого values не найдет своего закрывающего тега и будет выброшено исключение. Но если в XML есть хотя бы один тег, который нам надо пропустить, то tt:skip сработает 1 раз и далее не будет пытаться пропустить </values>.

Обойти это можно, явно добавив еще один фиктивный тег в конец набора тегов в values в исходном XML. Сделать это можно перед вызовом трансформации например так (XML находится в переменной gv_xml):

REPLACE ALL OCCURRENCES OF '</values>' IN gv_xml WITH '<dummyEnd /></values>' IGNORING CASE.'

а в трансформацию явно добавить обработку тега dummyEnd:
<?sap.transform simple?>
<tt:transform xmlns:tt="http://www.sap.com/transformation-templates">
<tt:root name="ROOT"/>
<tt:template>
<root>
<values>
<tt:group>
<tt:d-cond frq="?">
<material tt:value-ref="material"/>
</tt:d-cond>
<tt:d-cond frq="?">
<dummyEnd />
</tt:d-cond>
<tt:d-cond frq="*">
<tt:skip count="1"/>
</tt:d-cond>
</tt:group>
</values>
</root>
</tt:template>
</tt:transform>
view raw transform.xslt hosted with ❤ by GitHub
Не понятно почему, но данный подход срабатывает, и после обработки dummyEnd парсер не пытается выполнить tt:skip.

Если же работать перестанет, то есть более сложный, но более логичный вариант трансформации (в XML также все еще нужно добавлять фиктивный тег):
<?sap.transform simple?>
<tt:transform xmlns:tt="http://www.sap.com/transformation-templates">
<tt:root name="ROOT"/>
<tt:variable name="skip"/>
<tt:template>
<root>
<values>
<tt:assign to-var="skip" val="0"/>
<tt:group>
<tt:d-cond frq="?">
<material tt:value-ref="material"/>
</tt:d-cond>
<tt:d-cond frq="?">
<dummyEnd>
<tt:assign to-var="skip" val="1"/>
</dummyEnd>
</tt:d-cond>
<tt:d-cond frq="*">
<tt:cond-var check="skip=0">
<tt:skip count="1"/>
</tt:cond-var>
</tt:d-cond>
</tt:group>
</values>
</root>
</tt:template>
</tt:transform>
view raw transform.xslt hosted with ❤ by GitHub
Здесь мы заводим переменную skip. Далее при начале обработки тега values мы записываем в переменную skip значение 0. В условии tt:cond-var check="skip=0" мы проверяем, что skip равно 0 и, если это так, то пропускаем один тег с помощью tt:skip. Если мы встречаем тег dummyEnd, то записываем в переменную skip значение 1. Таким образом, после последнего тега в values (а мы позаботились чтобы им был dummyEnd), больше не требуется пропускать теги, tt:skip не будет выполнен, и будет обработан закрывающий </values>

пятница, 23 марта 2018 г.

QR код с многострочным тестом в PDF

Обычно я говорю, что все, что в Adobe могут делать хорошо, это Photoshop. Хотя, наверняка это преувеличение, и всякие там AfterEffects и Illustrator достойные представители своего жанра. Но поскольку абаперу в основном приходится сталкиваться с продукций Adobe в виде LiveCycleDesign при создании PDF форм, то весь негатив вызван в основном этим продуктом.

LiveCycleDesign ужасен и полон багов, а связка с SAP GUI через внедрение по COM стабильности не добавляет. Но все равно PDF лучше чем SAPScript и SmartForms.

Кстати небольшое отступление. Часто в LiveCycleDesign при открытии какой-нибудь панели (как правило Info или Drawing Aids) интерфейс зависает, вернее не реагирует, и панель эту не убрать, и не сдвинуть даже. Как правило помогает выбрать в меню сброс layout (Palletes->Workspace->Reset Pallete Locations), но естественно все ваши тщательно расположенные панельки слетают. Так вот можно добиться восстановления работоспособности просто открыв в меню Help->About и закрыв окно About.  Вот такой милый глюк. Между прочим уже 11 версия программы от солидной фирмы.

На днях столкнулся с очередной особенностью. Хотя тут виноват скорее даже не сам  LiveCycleDesigner, а сервис генерации PDF.

Возникла задача в PDF выводить QR-код, в котором закодирован многострочный текст. Но в PDF перенос текста стабильно заменялся на пробел (или что-то аналогичное) и переноса строк не было. Есть нота 2358186, но она относится только к SAPScript и SmartForm.
В результате копания и использования метода научного тыка выяснилось, что для достижения необходимого результата нужно выполнить следующие условия:
  1. Строки при передаче в ФМ формирования PDF необходимо разделять через перевод строки и только. Это управляющий символ с кодом 10 (0A в 16-ной системе). Во многих языках программирования это "\n", в ABAP нужно юзать cl_abap_char_utilities=>newline (ну или %_NEWLINE). 
  2. Для тестирования (preview) можно еще в событии initialize написать следующий код на JavaScript:
    this.rawValue = this.rawValue.replace(/\\n/g,"\n");
    


    Естественно, при наличии такого кода для разделения строк в ABAP также можно применять строку '\n'
  3. В свойствах формы  Edit->Form Properties... необходимо везде указать генерацию статического PDF.
    А именно здесь (для непосредственной генерации):


     и здесь (для предпросмотра в LiveCycleDesigner):
  4. При вызове ФМ генерации PDF необходимо чтобы в передаваемом параметре /1bcdwb/docparams поля Fillable и Dynamic были пустыми.
Возникает вопрос. Что же такого есть в динамических (интерактивных) формах, что при генерации QR сервис Adobe не может корректно обработать перенос строки? И что делать если форма нужна именно интерактивная? Только страдать. 


среда, 14 февраля 2018 г.

Исключение дубликатов. Сравнение производительности.

Часто в солидных компаниях для abap-еров выпускают некий документ, регламентирующий разработку в них. Помимо всяких требований к наименованию объектов и запросов, зачастую там бывают общие правила. Обычно, все подобные документы однотипны, поскольку слизаны где-то у кого-то, слегка заточены под специфику конкретной компании и включают всякую корпоративную лапшу типа "командная работа" и "нетерпимость к некачественным решениям", а также банальности типа "проверяйте таблицу на пустоту перед FOR ALL ENTRIES" и "не делайте SELECT в цикле". Хотя большинство пунктов в такой памятке вполне разумны, пусть и не несут опытному специалисту чего-то нового, иногда встречаются эксклюзивы.

Например, недавно увидел такой пункт, который фигурировал как типовая ошибка:

Использование SORT+DELETE ADJACENT DUPLICATES вместо цикла со вставкой в хеш-таблицу.

Где-то глубоко в подсознании давно засела мысль, что просто накидать в таблицу строк, сортирнуть один раз и потом грохнуть дубли  - это быстрее чем на каждую строчку искать есть ли такая уже (либо бинарным поиском, либо хэш считать, не суть).

Ну и одно дело, когда ты делаешь так, как считаешь правильным, а другое  - когда тебе это запрещают. И поскольку данное требование я встретил впервые, я решил разобраться, что же реально быстрее и насколько.

Внимание! Сразу хочу уточнить, что ранее здесь был другой текст с диаметрально противоположными результатами, поскольку я забыл про одну особенность SORT, на что мне и было указано в комментариях. Так что если вы его читали, а теперь видите совсем другое - не удивляйтесь. А выводы в принципе те же остались. 


Для тестирования была накатана небольшая программка, которая измеряет время выполнения двух вариантов. В первом из исходной таблицы в обычную добавляются все строки, потом сортируются и потом удаляются дубликаты. Во втором варианте строки исходной таблицы добавляются в хэшированную (попадают туда, естественно, только уникальные). Количество строк в исходной таблице задается параметром LV_COUNT, количество выполнений - параметром LV_TIMES. Оба параметра увеличиваются в 10 раз на каждой итерации цикла.
Исходная таблица заполняется случайными целыми числами до выполнения измерений по любому из вариантов. Чтобы там заведомо были совпадения числа берутся по модулю 10.
В итоге выводится результат в виде таблицы. Это некие "попугаи", приблизительно соответствующие микросекундам, но нам это не очень важно - нам важно соотношение двух вариантов. Если программу будете сами запускать, она, скорее всего. упадет по таймауту, поскольку общее время выполнения около 20 минут - запускайте в фоне, потом смотрите в спул.

Смотрим на результаты:
  • Одна строка в исходной таблице. 

    Что мы тут видим? Прежде всего примерный паритет между обоими вариантами. На самом деле эти измерения с 1 строкой  - туфта... Во-первых, чисто логически, тут не было никаких поисков и сравнений, чтобы удалять там какие-то дубликаты - строка тупо одна, она просто добавилась и все. Все затраты времени тут скорее на накладные расходы - цикл, добавление строки, очистка таблицы. Во-вторых, реально цифры тут прыгали как хотели, то есть в том же варианте с 100000 повторов иногда вариант S+D вырывался вперед, иногда Hash.Смотрим дальше

  • 10 и более строк в исходной таблице
  •  
    Итак самое главное - вариант с Hash действительно раза в 1,5 быстрее S+D! (там, где деление на 0, я замеры не проводил поскольку просто долго, а картинка и так уже складывалась)
    Также видно, что результат на 1 повторе можно не рассматривать. Это следует из значений соотношения времени к количеству повторов (S+D/time и Hash/time). Например видно, что в случае S+D затраты на 1 прогон при 10-1000000 повторах в среднем одинаковые. А в случае 1 прогона цифра значительно выше - это опять же играют роль накладные расходы, а не основная логика. Вот поэтому и нужно несколько прогонов, чтобы нивелировать время на накладные расходы.
    Почему то большее ускорение Hash получает с увеличением числа строк в исходной таблице. Возможно тут сказывается выделение/освобождение памяти для S+D, которое и дает это отставание. Также на 10 строках Hash тоже почему то "более быстрый", чем на Hash на 100. Причина не понятна, да и не важно это. В среднем алгоритм Hash на ~50% быстрее чем S+D

Итак, я был обескуражен результатами, но давайте смотреть на вещи реально. Возьмем последний вариант, когда в исходной таблице было 100тыс строк (что уже довольно редко бывает). И, как правило, нескольких прогонов как в этом тесте не бывает - он один. Поэтому, посмотрев на значение 5 и 6 столбцах, мы увидим, что S+D отработает за примерно 27 миллисекунд, а Hash - примерно за 16 миллисекунд. Милли-секунд! Вы не заметите эту разницу никак вообще! А значит требование как и запрет использовать то или другое - бред. Как удобнее в каждом конкретном случае, так и нужно делать. Кто знает что вы там дальше с этой таблицей делаете - может там потом поиск по полному ключу 100500 раз? А может по неполному? Нет и не может быть тут универсального совета.

Я не прав (опять)? - welcome в комменты. 

пятница, 18 августа 2017 г.

Разблокировка редактора для программы

Иногда разработчики блокируют программу от модификации другими разработчиками с помощью установки соответствующей "галки" в свойствах программы/ФМ/класса.
Как правило, это бывает когда копируют CUSTOMER-EXIT ФМ в Z - на CUSTOMER-EXIT эта галка стоит (это даже где то в коде захардкожено, что если ФМ начинается на EXIT_ - то галку поставить).
В большинстве случаев это не проблема, поскольку все разработчики работают под одним пользователем. Но есть богатые компании, которые каждому разрабу заводят своего юзверя. И бывает, что разработчик увольняется, возвращается на свою старую галеру, например, а на проекте его пользователя блокируют или вообще удаляют нафиг. И бывает, что за уволившимся приходится допиливать что-то.
И вот когда все это (блокировка редактора, удаление уникального пользователя, допилки) складывается воедино, возникает проблемка - как поправить то, что править не дают?
Не, конечно, вариантов решения масса, можно писать письма базисникам, чтобы те опять завели/разблокировали юзера, сбросили ему пароль, выслали тебе, аргументировать зачем. Можно попробовать обойти это в отладке, если вам дают это обходить в отладке (да есть такие "оригинальные" компании, где в системе разработке по умолчанию нельзя менять переменные и порядок выполнения. Это они типа про безопасность думают, в итоге, как ни удивительно, но они почти единственные полностью "ложаться" под WannaCry или как его там... но речь не об этом). На крайняк, можно в Z прогу вкорячить неявное расширение...
 Но вообще, по мне так самое верное - это снять эту чертову галку прямо в БД. Хотя на истину в последней инстанции, конечно не претендую.
И тут есть тоже загвоздка. Вообще данные по проге лежат в таблице REPOSRC. За блокировку отвечает поле EDTX. (ФМ-ники ищите по группе функций типа LZ*). Но таблица помечена системной и править вам ее не дадут. Даже через собственную прогу - все ваши UPDATE REPOSRC даже активироваться не будут, ну или будут падать, если принудительно активируете.
На таблицу есть довольно известный ракурс TRDIR - но с ним та же история - ридонли.
Но сам то SAP как-то работает с этой таблицей, и недолгий поиск выводит на ракурс PROGDIR.
Править наверное можно и через SE16N, но мне удобнее прожку набросать типа вот такую
REPORT zaz_unlock_editor.
PARAMETERS p_prog TYPE trdir-name OBLIGATORY.
START-OF-SELECTION.
UPDATE PROGDIR SET edtx = space WHERE name = p_prog AND edtx = 'X'.
COMMIT WORK.
WRITE 'Done'.

пятница, 16 декабря 2016 г.

Вызываем FBLN1 с неколькими BELNR, BUKRS, LIFNR через пакетник

CLASS zcl_call_fbl1n DEFINITION
PUBLIC
ABSTRACT
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
TYPES:
tt_bukrs TYPE STANDARD TABLE OF bukrs WITH DEFAULT KEY .
TYPES:
tt_belnr TYPE STANDARD TABLE OF belnr_d WITH DEFAULT KEY .
TYPES:
tt_gjahr TYPE STANDARD TABLE OF gjahr WITH DEFAULT KEY .
TYPES:
tt_lifnr TYPE STANDARD TABLE OF lifnr WITH DEFAULT KEY .
CLASS-DATA st_bdc TYPE bdcdata_tab READ-ONLY .
CLASS-METHODS call
IMPORTING
!it_bukrs TYPE tt_bukrs OPTIONAL
!it_belnr TYPE tt_belnr OPTIONAL
!it_gjahr TYPE tt_gjahr OPTIONAL
!it_lifnr TYPE tt_lifnr OPTIONAL
VALUE(is_ctu_params) TYPE ctu_params OPTIONAL
EXPORTING
!et_msg TYPE tab_bdcmsgcoll .
CLASS-METHODS fill_bdc
IMPORTING
!it_bukrs TYPE tt_bukrs OPTIONAL
!it_belnr TYPE tt_belnr OPTIONAL
!it_gjahr TYPE tt_gjahr OPTIONAL
!it_lifnr TYPE tt_lifnr OPTIONAL .
protected section.
private section.
class-methods _SCREEN
importing
!IV_VALUE type CLIKE .
class-methods _FIELD
importing
!IV_VALUE type CLIKE .
class-methods _SELOPT
importing
!IT_DATA type STANDARD TABLE .
ENDCLASS.
CLASS ZCL_call_FBL1N IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_call_FBL1N=>CALL
* +-------------------------------------------------------------------------------------------------+
* | [--->] IT_BUKRS TYPE TT_BUKRS(optional)
* | [--->] IT_BELNR TYPE TT_BELNR(optional)
* | [--->] IT_GJAHR TYPE TT_GJAHR(optional)
* | [--->] IT_LIFNR TYPE TT_LIFNR(optional)
* | [--->] IS_CTU_PARAMS TYPE CTU_PARAMS(optional)
* | [<---] ET_MSG TYPE TAB_BDCMSGCOLL
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD call.
FREE et_msg.
fill_bdc(
EXPORTING
it_bukrs = it_bukrs " Балансовая единица
it_belnr = it_belnr
it_gjahr = it_gjahr
it_lifnr = it_lifnr
).
IF is_ctu_params-dismode is INITIAL .
is_ctu_params-dismode = 'E'.
ENDIF.
IF is_ctu_params-nobinpt is INITIAL and sy-batch is INITIAL.
is_ctu_params-nobinpt = 'X'.
ENDIF.
CALL TRANSACTION 'FBL1N'
WITH AUTHORITY-CHECK
USING st_bdc
OPTIONS FROM is_ctu_params
MESSAGES INTO et_msg
.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_call_FBL1N=>FILL_BDC
* +-------------------------------------------------------------------------------------------------+
* | [--->] IT_BUKRS TYPE TT_BUKRS(optional)
* | [--->] IT_BELNR TYPE TT_BELNR(optional)
* | [--->] IT_GJAHR TYPE TT_GJAHR(optional)
* | [--->] IT_LIFNR TYPE TT_LIFNR(optional)
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD fill_bdc.
FREE st_bdc.
IF it_lifnr[] IS NOT INITIAL.
_screen('RFITEMAP 1000 X ').
_field('BDC_CURSOR KD_LIFNR-LOW').
_field('BDC_OKCODE =%004').
* _field(' KD_LIFNR-LOW').
* _field(' X_OPSEL X').
* _field(' PA_STIDA 16.12.2016').
* _field(' X_NORM X').
_field(' BDC_SUBSCR SAPLSSEL 2001%_SUBSCREEN_%_SUB%_CONTAINER').
_field(' BDC_SUBSCR SAPLSSEL 2002SUBSCREEN_CONTAINER2').
_selopt( it_lifnr ).
ENDIF.
IF it_bukrs[] IS NOT INITIAL.
_screen('RFITEMAP 1000 X').
_field(' BDC_CURSOR KD_LIFNR-LOW').
_field(' BDC_OKCODE =%005').
* _field(' KD_LIFNR-LOW 1').
* _field(' X_OPSEL X').
* _field(' PA_STIDA 16.12.2016').
* _field(' X_NORM X').
_field(' BDC_SUBSCR SAPLSSEL 2001%_SUBSCREEN_%_SUB%_CONTAINER').
_field(' BDC_SUBSCR SAPLSSEL 2002SUBSCREEN_CONTAINER2').
_selopt( it_bukrs ).
ENDIF.
IF it_belnr[] IS NOT INITIAL.
_screen('RFITEMAP 1000 X').
_field(' BDC_CURSOR KD_LIFNR-LOW').
_field(' BDC_OKCODE =DYNS').
* _field(' KD_LIFNR-LOW 1').
* _field(' KD_BUKRS-LOW 2000').
* _field(' X_OPSEL X').
* _field(' PA_STIDA 16.12.2016').
* _field(' X_NORM X').
_field(' BDC_SUBSCR SAPLSSEL 2001%_SUBSCREEN_%_SUB%_CONTAINER').
_field(' BDC_SUBSCR SAPLSSEL 2002SUBSCREEN_CONTAINER2').
_screen('RFITEMAP 1000 X').
_field(' BDC_CURSOR KD_LIFNR-LOW').
_field(' BDC_OKCODE =%02411060000707020').
* _field(' KD_LIFNR-LOW 1').
* _field(' KD_BUKRS-LOW 2000').
* _field(' X_OPSEL X').
* _field(' PA_STIDA 16.12.2016').
* _field(' X_NORM X').
_field(' BDC_SUBSCR SAPLSSEL 2001%_SUBSCREEN_%_SUB%_CONTAINER').
_field(' BDC_SUBSCR SAPLSSEL 2000SUBSCREEN_CONTAINER2').
_field(' BDC_SUBSCR SAPLSSEL 1106SUBSCREEN_CONTAINER').
_selopt( it_belnr ).
ENDIF.
_screen('RFITEMAP 1000 X').
_field(' BDC_OKCODE =ONLI').
_field(' BDC_CURSOR KD_LIFNR-LOW').
_field(' X_OPSEL X').
_field(' X_NORM X').
* _field(' X_SHBV X').
* _field(' X_MERK X').
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_call_FBL1N=>_FIELD
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_VALUE TYPE CLIKE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD _field.
DATA ls_bdc TYPE bdcdata.
CHECK iv_value IS NOT INITIAL.
DATA(lv_value) = condense( iv_value ).
CHECK lv_value IS NOT INITIAL.
* SPLIT lv_value AT cl_abap_char_utilities=>horizontal_tab INTO ls_bdc-fnam ls_bdc-fval .
* SPLIT lv_value AT space INTO ls_bdc-fnam ls_bdc-fval .
FIND REGEX '^\s*(\S+)\s?(.*)$' IN lv_value SUBMATCHES ls_bdc-fnam ls_bdc-fval.
IF sy-subrc = 0.
CONDENSE ls_bdc-fval.
APPEND ls_bdc TO st_bdc.
ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_call_FBL1N=>_SCREEN
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_VALUE TYPE CLIKE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD _screen.
DATA ls_bdc TYPE bdcdata.
CHECK iv_value IS NOT INITIAL.
* SPLIT iv_value AT cl_abap_char_utilities=>horizontal_tab INTO ls_bdc-program ls_bdc-dynpro ls_bdc-dynbegin.
* SPLIT iv_value AT space INTO ls_bdc-program ls_bdc-dynpro ls_bdc-dynbegin.
FIND REGEX '^\s*(\S+)\s+(\d+)(.*)$' IN iv_value SUBMATCHES ls_bdc-program ls_bdc-dynpro DATA(lv_end).
IF sy-subrc = 0.
ls_bdc-dynbegin = condense( lv_end ).
APPEND ls_bdc TO st_bdc.
ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_call_FBL1N=>_SELOPT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IT_DATA TYPE STANDARD TABLE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD _selopt.
DATA lt_value TYPE STANDARD TABLE OF bdcdata-fval.
DATA lv_value TYPE bdcdata-fval.
DATA lv_index TYPE i.
DATA lv_ok_code TYPE string VALUE 'BDC_OKCODE /00'.
CONSTANTS cv_accept TYPE string VALUE 'BDC_OKCODE =ACPT'.
LOOP AT it_data ASSIGNING FIELD-SYMBOL(<ls_data>).
CHECK <ls_data> IS NOT INITIAL.
write <ls_data> to lv_value.
APPEND lv_value TO lt_value.
ENDLOOP.
CHECK lt_value[] IS NOT INITIAL.
SORT lt_value.
DELETE ADJACENT DUPLICATES FROM lt_value.
IF lines( lt_value ) < 9.
lv_ok_code = cv_accept.
ENDIF.
_screen('SAPLALDB 3000 X').
_field( lv_ok_code ).
_field(' BDC_SUBSCR SAPLALDB 3010SCREEN_HEADER').
_field(' BDC_CURSOR RSCSEL_255-SLOW_I(08)').
LOOP AT lt_value ASSIGNING FIELD-SYMBOL(<v_value>) FROM 1 TO 8.
_field( |RSCSEL_255-SLOW_I({ sy-tabix WIDTH = 2 PAD = '0' ALIGN = right }) { <v_value> }| ).
ENDLOOP.
LOOP AT lt_value FROM 9 TRANSPORTING NO FIELDS WHERE table_line IS NOT INITIAL.
if sy-tabix = lines( lt_value ).
lv_ok_code = cv_accept.
ENDif.
lv_index = sy-tabix - 8.
_screen('SAPLALDB 3000 X').
_field( lv_ok_code ).
_field(' BDC_SUBSCR SAPLALDB 3010SCREEN_HEADER').
_field(' BDC_CURSOR RSCSEL_255-SLOW_I(08)').
DO 8 TIMES.
ADD 1 TO lv_index.
READ TABLE lt_value ASSIGNING <v_value> INDEX lv_index.
_field( |RSCSEL_255-SLOW_I({ sy-index WIDTH = 2 PAD = '0' ALIGN = right }) { <v_value> }| ).
ENDDO.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

среда, 20 января 2016 г.

ASSIGN и SY-SUBRC

DATA lv_matnr TYPE matnr.

FIELD-SYMBOLS <s_matnr> TYPE matnr.

sy-subrc = 4.

ASSIGN lv_matnr TO <s_matnr>.
IF sy-subrc IS INITIAL.
  WRITE 'Success!'.
ELSE.
  WRITE 'Failed'.
ENDIF.

понедельник, 21 декабря 2015 г.

Пользовательские поля в товаре

Когда вы расширяете какую-то стандартную сущность (заказ там или товар) добавляя свои ZZ-поля в стандартные таблицы с помощью доп.структур, то часто приходится решать задачу передачи значений этих полей извне. То есть фактически надо как-то передавать эти поля в BAPI.
Стандарт для этих целей в "приличных" BAPI предусматривает поля EXTENSIONIN или типа того. Чтобы все работало на автомате, как правило, этими же ZZ-полями нужно еще расширить определенную структуру. Ну, и структуру с X-полями (это которые надо ставить в X там где реально данные)
"Внутре" данные из EXTENSIONIN перекладываюся в эту структуру (либо move-correcponging'ом, либо через assign componet), а потом и в реальную структуру таблицы.
Правда иногда этого бывает мало - надо реализовывать либо user-exit, либо BADI и перекладывать там самим.
А бывает еще веселее. Вот, например, BAPI BAPI_MATERIAL_SAVEDATA. Для создания и изменения товара. Она же используется в IDOC MATMAS (ФМ IDOC_INPUT_MATMAS_BAPI). В наличии параметры EXTENSIONIN и EXTENSIONINX. Дополнительно надо расширять структуры BAPI_TE_* и BAPI_TE_*X. Ну, то есть, если расширяли MARA то расширяем и BAPI_TE_MARA, если MARM - то BAPI_TE_MARM. Ну и так далее.
Но просто так все равно работать не будет. Стандартные поля будут обновляться, а ваши нет. Чтобы побороть это есть транзакция OMSR. В ней надо прописать поля, которые можно редактировать (там и стандартные присутствуют).  Тогда они будут "проходить" через BAPI.
Подробностей не подскажу - не абаперское это дело настройки настраивать. Но куда послать консультанта вы теперь знаете =)

пятница, 5 июня 2015 г.

Значения в варианте для фонового задания

Когда мы создаем фоновое задание через JOB_OPEN/JOB_CLOSE и вызываем там отчет с селекционным экраном (например, через submit), система генерит вариант запуска автоматически. Имя варианта потом можно увидеть в SM37 в шаге. Что сохранено в варианте можно посмотреть через "Перейти к -> Вариант" в просмотре шага задания (также можно получить с помощью ФМ RS_VARIANT_CONTENTS).

четверг, 9 января 2014 г.

Передача динамической таблицы через память.

Сразу скажу, что если у вас возникла такая задача, как написано в заголовке, то скорее всего у вас что-о не то с архитектурой. Но иногда это бывает полезным.

Итак задача. У нас есть 2 отчета. Первый может запускаться самостоятельно. Он выбирает данные по параметрам селекционного экрана и выводит их, например, в ALV-GRID. Во втором отчете, логика выборки практически такая же, но возможно отличия в выводе. Или он вообще ничего не выводит, а, например, посылает данные в другую систему через IDOC. "Копипастить" логику во втором отчете - плохо. Самое правильное - это вынести логику в отдельный ФМ/Класс. Но допустим первый отчет уже написан, переписывать его не хочется, а хочется "дернуть" его из второго и получить от него данные с минимальными изменениями.

На первый взгляд все просто. Добавляем в отчете №1 поле на "селекционник" типа такого:

parameter p_export type flag no-display default space.

А после того как выбрали данные, проверяем этот параметр. И если он установлен, то вместо того чтобы выводить данные, экспортируем их в память. Примерно так:

if p_export is initial
  perform show_data. 
else
  export data from gt_alv to memory id 'ZDYNTEST'
endif.

Второй отчет выглядит примерно так:

submit zreport1 and return
  with ...
  with ...
  with p_export eq 'X'
.
import data to gt_data from memory id 'ZDYNTEST'.

Так вроде бы все работает.
А теперь представим, что таблицу мы генерируем динамически с использованием create data, cl_abap_tabledescr и прочего. Допустим у нас есть указатель на нее в GPT_ALV (data gpt_alv type ref to data). Экспортировать сам указатель в память мы не можем, потому что даже если бы это можно было сделать, то при импорте в другой LUW (Logical Unit Work,  а submit создает именно новый LUW) это был бы указатель неизвестно на что, потому что там свое адресное пространство. А использовать память в пределах одного LUW смысла не имеет - удобнее воспользоваться статическими атрибутами класса.
Итак указатель не можем, но мы его можем привести к FIELD-SYMBOL на таблицу и ее уже передать в память. Ну примерно так:

field-symbol <t_alv> type standard table.

if p_export is initial
  perform show_data. 
else
   assign gpt_alv->* to <t_alv>.   
   export data from <t_alv> to memory id 'ZDYNTEST'
endif.

Отлично. Экспортнули. А как теперь во втором отчете эту таблицу импортировать?
Структуры мы ее не знаем - поскольку она динамическая. А в IMPORT нужно уже существующую инициализированную таблицу передавать с конкретной структурой.
Ну ОК. Можно  было бы ее построить заново, если бы можно было передать описатель типа CL_ABAP_TABLEDESCR. Но его не передать так как опять же это ссылка. Можно было бы передать описание по компонентам строки таблицы полученным через CL_ABAP_STRUCTDESCR=>GET_COMPONENTS. Но там в таблице компонентов тоже ссылки на описатели типов - и тоже их не передать. Единственным вариантом остается передача именно компонентов, но в собственных структурах с более детальным содержанием и без ссылок на описатели. Но это такой "велосипед" получается, что дешевле по затратам выйдет все-таки вынести логику из отчета №1 в отдельный ФМ или класс.

К счастью SAP уже написал такой велосипед и находится он в классе CL_SALV_BS_RUNTIME_INFO. С его использованием наш код превращается в следующее.
Отчет №1:

field-symbol <t_alv> type standard table.

if p_export is initial
  perform show_data. 
else.
  CL_SALV_BS_RUNTIME_INFO=>SET(
      display = space
      metadata = space
      data = 'X' ).

  assign gpt_alv->* to <t_alv>. 

  CL_SALV_BS_RUNTIME_INFO=>SET_DATA( DATA = <T_ALV> ).
endif.

Отчет №2:

data lpt_data type ref to data.

submit zreport1 and return
  with ...
  with ...
  with p_export eq 'X'
.

CL_SALV_BS_RUNTIME_INFO=>GET_DATA_REF(
  importing R_DATA = lpt_data ).

Вот и все - в lpt_data у нас указатель на таблицу с данными, которая была создана в отчете №1. 

вторник, 12 февраля 2013 г.

Заполнение LIKP-TDDAT при создании поставки

Создаем поставки с помощью ФМ BAPI_DELIVERYPROCESSING_EXEC.
Кроме всего прочего пытаемся прописать в поставке дату планирования транспортировки LIKP-TDDAT. В параметрах ФМ она лежит в поле TRANSP_PLAN_DATE в таблице, которая передается как параметр REQUEST.
Проблема в том, что ФМ отрабатывает, поставка создается, но  поле TDDAT пустое.

Исследования показали, что проблема комплексная, и нужно обратить внимание как минимум на две вещи:

  1. Кроме заполнения поля TRANSP_PLAN_DATE, которое потом запишется в LIKP-TDDAT, при вызове ФМ необходимо заполнять также поля MAT_AVAILABIL_DATE, LOADING_DATE, GOODS_ISSUE_DATE, DELIVERY_DATE. В принципе есть и другие варианты - например когда заполняются TIMESTAMPы (подробнее можно посмотреть по исходному коду в form MATCH_INTERFACE_DATES_MM в include LV50R_CREAF30). Как альтернативу можно использовать user-exit EXIT_SAPLV50R_CREA_003 и дописать там нужное.
  2. В таблице TVCPL по виду поставки LFART, виду заказа AUART (определяется по виду поставки из таблицы TVLK)  и пустому типу позиции образца (поле PSTYV) выбирается поле GRUAK "Групповое условие копирования данных VBAK". Значение этого поля используется при формировании имени подпрограммы, которая будет вызвана из программы SAPLV50S по шаблону DATEN_KOPIEREN_<GRUAK> для копирования данных в поставку. Таким образом, если в GRUAK было 201, то вызовется подпрограмма  DATEN_KOPIEREN_201. От подпрограммы зависит то, какие поля будут скопированы в поставку. В частности, копирование TDDAT поддерживают только 301 и 311 подпрограммы. 


четверг, 24 января 2013 г.

Отправка EMAIL. Подтверждения доставки и прочтения

Программно Email из SAP удобно создавать и отправлять с помощью  Business Communication Service. Примеры можно найти в программах BCS_EXAMPLE_*.

По умолчанию к письмам почему-то добавляются запросы о доставке и прочтении. Иногда это совсем не нужно. Отменить их можно так:

lo_request->SET_STATUS_ATTRIBUTES( I_REQUESTED_STATUS = 'E'  ). ,
где lo_request - экземпляр класса CL_BCS.