Показаны сообщения с ярлыком 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

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

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

вторник, 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 г.

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

Итак, у нас есть примерно вот такой код:

Код притянут за уши, но смысл его примерно такой. Есть некий базовый класс данных (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:
И, допустим, нас интересует только значение тега material (которого, кстати, может и не быть). Напишем такую версию simple transformation:

Применение данной трансформации вызовет исключительную ситуацию, так как в XML нам встретиться тег prod и тег foo, которые мы не предусмотрели в трансформации.

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

Как пропускать все что нам не нужно или мы не знаем что это? Для этого нужно в конец tt:group добавить tt:skip следующим образом:
Здесь мы "говорим", что если нам встречается тег 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: Не понятно почему, но данный подход срабатывает, и после обработки dummyEnd парсер не пытается выполнить tt:skip.

Если же работать перестанет, то есть более сложный, но более логичный вариант трансформации (в XML также все еще нужно добавлять фиктивный тег):
Здесь мы заводим переменную 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, но мне удобнее прожку набросать типа вот такую

среда, 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.