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

пятница, 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 тоже живут компании и не собираются обновляться. Что делать?

понедельник, 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 * не всегда выбирает все поля.

четверг, 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. 

среда, 25 апреля 2012 г.

Ручное изменение таблиц

Иногда в процессе разработки функциональности или в процессе отладки бывает необходимо что-то поменять в таблице базы данных. Но ракурса для  изменения нет. И сделать его нельзя, либо не имеет смысла для одной правки.

Для этой цели существует несколько недокументированных возможностей.

  1. Транзакция SE16N. После запуска до выбора данных в окне команд нужно ввести "&sap_edit" (без кавычек), нажать Enter, после чего запустить выборку. При этом таблица откроется и в строке команд будут доступны команды создания/удаления строк и поля будут доступны на изменения. Но в последних версиях эту возможность пропатчили и она не работает. 
  2. Если п.1, не работает, то можно попробовать вместо SE16N запускать транзакцию UASE16N и выполнять ту же последовательность действий. Но эта транзакция может быть объявлена как устаревшая, о чем будет соответствующее сообщение при запуске, и использовать ее не получится.
  3. Если вышеперечисленные способы не работают, можно попробовать в транзакции SE37 запустить ФМ SE16N_INTERFACE, указав ему в параметре I_TAB имя таблицы, а в параметре I_EDIT значение 'X'. Остальное можно оставить по умолчанию. В результате получим тоже окно для редактирования, что и в SE16N с &SAP_EDIT.

понедельник, 23 апреля 2012 г.

Связь бонусных соглашений и условий

Заголовки условий хранятся в таблице KONH. Уникальный номер условия KONH-KNUMH.
Соглашения хранятся в таблице KONA. Уникальный номер соглашения KONA-KNUMA.

Получить номер соглашения по номеру условия просто - он хранится в одном из полей  KONH-KNUMA_* (например KNUMA_BO).

Получить все номера условий для заданного соглашения, конечно, можно, выполнив SELECT по  KONH-KNUMA_*, но это очень медленно, потому что индекса по этим полям в KONH нет.
В принципе для этого существует  индекcная таблица KONAIND, но судя по тому стандартному коду, который находится поиском, она используется только в случае, если тип соглашения (KONA-ABTYP) равен "C" (Мероприятие по стимулированию продаж).

Для бонусных соглашений правильным способом получения всех условий  является использование ФМ SD_BONUS_READ. Если же "копнуть" глубже, то поиск условий в этом ФМ производится по таблицам KOTE*,  в которых есть поля KNUMA и KNUMH и, как правило, есть индекс по KNUMA.