В средстве поиска (СП) могут быть входящие параметры, которыми можно ограничивать результат, выдаваемый пользователю для выбора.
Возьмем для примера СП для склада H_T001L. Склады LGORT лежат в таблице T001L, склады принадлежат какому либо заводу WERKS. В СП по складу WERKS помечен как импортируемый параметр (IMP):
Также WERKS присутствует в диалоге ограничений:
Обратите внимание, когда завод не задан, выводятся склады всех заводов. Но если завод указать, будут выведены только его склады - например укажем завод 0002:
Также на скриншоте выше видно, что у поля Склад в ограничениях также есть кнопка средства поиска, но при её нажатии мы увидим список складов без ограничения по заводу (предприятию):
Таким образом, ограничения из первого СП не передаются во второе СП (хотя по сути тут одно и тоже СП, просто рекурсивно вызванное, в данном случае это не важно).
В данном примере нет особого смысла, поскольку мы и так ищем склад в первом СП, но представьте другую ситуацию.
Допустим мы ищем товар (MATNR) по таблице MARA. В критериях поиска у нас сам товар (MATNR), название (MARK-MAKTX) и Группа товаров (MATKL). Кроме того, у нас есть ограничения по 2м Z-полям, которые не присутствуют в MARA, и вообще не лежат в каких либо таблицах SAP - скажем это данные из CDS, или просто откуда то извне - ну например мы получаем их по RFC или через Proxy. Не суть важно, главное что мы не можем их использовать непосредственно в выборке по MARA или построить ракурс с их участием.
Допустим, первый параметр будет Направление (ZDIRECTION, CHAR1), а второй параметр Бренд (ZBRAND, CHAR3). И, допустим, логика такая - от Бренда зависит первые символы в ограничении по MATKL - MATKL(3) = ZBRAND. Также, Бренд у нас зависит от Направления, Ну скажем, если направление Розница ('R'), то в ней Бренды '010','020','090', если направление Опт ('O'), то там бренды '230','240' и также '090'.
Конкретная логика не важна в данном случае, главное принцип, что параметры не лежат в таблицах SAP и второй зависит от первого.
Набросаем это СП.
Создали домен ZD_DIRECTION (фиксированные значения из домена - это для простоты примера, в реальности они могут быть откуда угодно):
На основе домена создали элемент данных ZE_DIRECTION
Создали элемент данных ZE_BRAND
Создали заготовку ФМ для search-help exit ZF4IF_MAT_SAMPLE_EXIT:
И создали само СП для товара ZSH_MAT_SAMPLE:
В принципе после активации СП уже работает. Естественно, наши Z-поля игнорируются, потому что мы не написали никакой логики. Давайте её напишем в ФМ ZF4IF_MAT_SAMPLE_EXIT. Ну, скажем, так :
TYPES:
BEGIN OF ts_dir_brand,
direction TYPE ze_direction,
brand TYPE ze_brand,
END OF ts_dir_brand,
tt_dir_brand TYPE STANDARD TABLE OF ts_dir_brand WITH DEFAULT KEY.
DATA lr_brand TYPE RANGE OF ze_brand.
DATA lr_direction TYPE RANGE OF ze_direction.
DATA lt_dir_brand TYPE tt_dir_brand.
CHECK callcontrol-step = 'SELECT'.
APPEND LINES OF VALUE tt_dir_brand( direction = 'R'
( brand = '010' )
( brand = '020' )
( brand = '090' )
) TO lt_dir_brand.
APPEND LINES OF VALUE tt_dir_brand( direction = 'O'
( brand = '230' )
( brand = '540' )
( brand = '090' )
) TO lt_dir_brand.
LOOP AT shlp-selopt REFERENCE INTO DATA(lps_selopt).
CASE lps_selopt->shlpfield.
WHEN 'ZBRAND'.
APPEND CORRESPONDING #( lps_selopt->* ) TO lr_brand.
DELETE shlp-selopt.
WHEN 'ZDIRECTION'.
APPEND CORRESPONDING #( lps_selopt->* ) TO lr_direction.
DELETE shlp-selopt.
WHEN OTHERS.
ENDCASE.
ENDLOOP.
IF lr_brand[] IS NOT INITIAL OR lr_direction[] IS NOT INITIAL.
DELETE lt_dir_brand
WHERE NOT ( direction IN lr_direction AND brand IN lr_brand ).
SORT lt_dir_brand BY brand.
DELETE ADJACENT DUPLICATES FROM lt_dir_brand COMPARING brand.
LOOP AT lt_dir_brand REFERENCE INTO DATA(lps_dir_brand).
APPEND VALUE #(
shlpfield = 'MATKL'
sign = 'I' option = 'CP'
low = |{ lps_dir_brand->brand }*|
) TO shlp-selopt.
ENDLOOP.
ENDIF.
Если мы теперь запустим наше СП и введем ограничение в поля Направление или Бренд, то список значений будет ограничен соответствующим образом (по MATKL).
Давайте теперь создадим СП для Бренда, в котором можно ограничивать список по Направлению:
ФМ ZF4IF_BRAND_SAMPLE_EXIT содержит следующий код:
TYPES:
BEGIN OF ts_dir_brand,
direction TYPE ze_direction,
brand TYPE ze_brand,
END OF ts_dir_brand,
tt_dir_brand TYPE STANDARD TABLE OF ts_dir_brand WITH DEFAULT KEY.
DATA lr_direction TYPE RANGE OF ze_direction.
DATA lt_dir_brand TYPE tt_dir_brand.
DATA lr_brand TYPE RANGE OF ze_brand.
CASE callcontrol-step.
WHEN 'SELECT'.
LOOP AT shlp-selopt REFERENCE INTO DATA(lps_selopt).
CASE lps_selopt->shlpfield.
WHEN 'BRAND'.
APPEND CORRESPONDING #( lps_selopt->* ) TO lr_brand.
DELETE shlp-selopt.
WHEN 'DIRECTION'.
APPEND CORRESPONDING #( lps_selopt->* ) TO lr_direction.
DELETE shlp-selopt.
WHEN OTHERS.
ENDCASE.
ENDLOOP.
IF 'R' IN lr_direction.
APPEND LINES OF VALUE tt_dir_brand( direction = 'R'
( brand = '010' )
( brand = '020' )
( brand = '090' )
) TO lt_dir_brand.
ENDIF.
IF 'O' IN lr_direction.
APPEND LINES OF VALUE tt_dir_brand( direction = 'O'
( brand = '230' )
( brand = '540' )
( brand = '090' )
) TO lt_dir_brand.
ENDIF.
DELETE lt_dir_brand WHERE brand NOT IN lr_brand.
FREE record_tab.
IF lt_dir_brand[] IS NOT INITIAL.
CALL FUNCTION 'F4UT_RESULTS_MAP'
* EXPORTING
* source_structure = source_structure " DDIC structure that SOURCE_TAB describes
* apply_restrictions = apply_restrictions " Take only entries that fulfill the selection requirements
TABLES
shlp_tab = shlp_tab " Table of Elementary Search Helps
record_tab = record_tab " Hit list
source_tab = lt_dir_brand
CHANGING
shlp = shlp " Single (Current) Search Help
callcontrol = callcontrol " Control of the F4 process
EXCEPTIONS
illegal_structure = 1
OTHERS = 2.
ENDIF.
callcontrol-step = 'DISP'.
WHEN OTHERS.
ENDCASE.
В итоге наше СП по бренду работает:
и Можно ограничивать список Направлением:
Пропишем элементу данных для Бренда наше средство поиска:
Теперь в СП для поиска товара доступен выбор бренда через СП бренда:
И теперь мы подходим к ключевому моменту этого поста, потому что все, что было выше, это банальщина и служит только для наглядной демонстрации проблемы. А проблема у нас в следующем - если в СП по товару мы зададим ограничение по направлению, то в СП для бренда оно не будет передано:
Причина в том, что у нас возникает ситуация "СП из СП" (F4 on F4), и поля не связаны как это бывает, если делать например селекционный экран по структуре/таблице, где СП задается для поля с учетом связанных полей (как было в ситуации СП для Склада, описанной в начале поста).
Кроме того, вывод экранов в средствах поиска динамический и построен на технологии OCX (в принципе это ActiveX) и многие вещи, актуальные для обычных диалогов SAP, не работают.
А теперь займемся решением данной проблемы - то есть передачей ограничения по Направлению из СП по товару в СП по бренду.
Во время работы диалога СП возникают разные низкоуровневые системные события (закрытие окна, переход на другое СП в комплексном, начало поиска, вызов вложенного СП). На эти события навешаны обработчики. В частности, нас интересует событие вызова СП (в данном случае нашего СП по бренду). Обработка данного события находится в подпрограмме BUTTON_EVENT в include WDTMFORS:
На callback не обращайте внимание - это просто макрос, внутри там FORM.
В этой подпрограмме обрабатывается много разных кнопок, нас интересует то, что начинается где-то с 748 строки (F4 on F4):
Вызов вложенного СП идет в подпрограмме OCX_FUNCTION_CALL:
а перед этим в GET_SEL_OPTS происходит получение значений из диалога текущего СП в SELOPT текущего СП и также запись их в INTERFACE-часть текущего СП в рабочую область глобальной таблицы TABC_SHLPTAB в SHLP_CURR.
И казалось бы решение так близко - достаточно в нашем ФМ для СП по бренду про-ASSIGN-ить эту таблицу и получить значение, но нет... При инициализации вложенного СП таблица TABC_SHLPTAB инициализируется и введенное значение теряется. 😱
Решение заключается в том, чтобы частично повторить логику данного куска BUTTON_EVENT. Фактически нам нужно заново вызвать подпрограмму GET_SEL_OPTS и снова получить значения из основного СП. Но что в неё передавать на вход?
H_CMX - это некий дескриптор OCX-объекта, отвечающего за СП. В самом начале BUTTON_EVENT он экспортируется в память под ID 'SHLP_CON'. Значит его мы импортируем из памяти.
DIALOGPAGE - это таблица задействованных СП. Она также лежит в памяти по 'ID MATCHCODE_OCX_DLGPAGES', а в ней по имени основного СП можно найти и TAB_ID и DIALOGNR.
Теперь у нас все есть для вызова GET_SEL_OPTS. Перепишем наш ФМ для СП по бренду ZF4IF_BRAND_SAMPLE_EXIT добавив в него логику на шаге PRESEL1 :
TYPES:
BEGIN OF ts_dir_brand,
direction TYPE ze_direction,
brand TYPE ze_brand,
END OF ts_dir_brand,
tt_dir_brand TYPE STANDARD TABLE OF ts_dir_brand WITH DEFAULT KEY.
DATA lr_direction TYPE RANGE OF ze_direction.
DATA lt_dir_brand TYPE tt_dir_brand.
DATA lr_brand TYPE RANGE OF ze_brand.
DATA lv_h_mcx TYPE cntl_handle.
DATA lt_dialogpage TYPE STANDARD TABLE OF mctbc_dp WITH DEFAULT KEY.
DATA ls_help_descr TYPE shlp_descr_t.
DATA lv_maxrecords TYPE i.
DATA lv_rc LIKE sy-subrc.
CASE callcontrol-step.
WHEN 'PRESEL1'.
IMPORT h_mcx TO lv_h_mcx FROM MEMORY ID 'SHLP_CON'.
IMPORT dialogpage TO lt_dialogpage FROM MEMORY ID 'MATCHCODE_OCX_DLGPAGES'.
READ TABLE lt_dialogpage REFERENCE INTO DATA(lps_dialog_page) WITH KEY
shlp_name = 'ZSH_MAT_SAMPLE'
.
IF sy-subrc = 0.
PERFORM get_sel_opts IN PROGRAM saplwdtm IF FOUND
USING
lv_h_mcx lps_dialog_page->tabctrl lps_dialog_page->dialognr
CHANGING
ls_help_descr lv_maxrecords lv_rc.
LOOP AT ls_help_descr-selopt REFERENCE INTO DATA(lps_selopt).
CASE lps_selopt->shlpfield.
WHEN 'ZDIRECTION'.
APPEND VALUE #(
BASE CORRESPONDING #( lps_selopt->* )
shlpfield = 'DIRECTION'
) TO shlp-selopt.
WHEN OTHERS.
ENDCASE.
ENDLOOP.
ENDIF.
WHEN 'SELECT'.
LOOP AT shlp-selopt REFERENCE INTO lps_selopt.
CASE lps_selopt->shlpfield.
WHEN 'BRAND'.
APPEND CORRESPONDING #( lps_selopt->* ) TO lr_brand.
DELETE shlp-selopt.
WHEN 'DIRECTION'.
APPEND CORRESPONDING #( lps_selopt->* ) TO lr_direction.
DELETE shlp-selopt.
WHEN OTHERS.
ENDCASE.
ENDLOOP.
IF 'R' IN lr_direction.
APPEND LINES OF VALUE tt_dir_brand( direction = 'R'
( brand = '010' )
( brand = '020' )
( brand = '090' )
) TO lt_dir_brand.
ENDIF.
IF 'O' IN lr_direction.
APPEND LINES OF VALUE tt_dir_brand( direction = 'O'
( brand = '230' )
( brand = '540' )
( brand = '090' )
) TO lt_dir_brand.
ENDIF.
DELETE lt_dir_brand WHERE brand NOT IN lr_brand.
FREE record_tab.
IF lt_dir_brand[] IS NOT INITIAL.
CALL FUNCTION 'F4UT_RESULTS_MAP'
* EXPORTING
* source_structure = source_structure " DDIC structure that SOURCE_TAB describes
* apply_restrictions = apply_restrictions " Take only entries that fulfill the selection requirements
TABLES
shlp_tab = shlp_tab " Table of Elementary Search Helps
record_tab = record_tab " Hit list
source_tab = lt_dir_brand
CHANGING
shlp = shlp " Single (Current) Search Help
callcontrol = callcontrol " Control of the F4 process
EXCEPTIONS
illegal_structure = 1
OTHERS = 2.
ENDIF.
callcontrol-step = 'DISP'.
WHEN OTHERS.
ENDCASE.
Теперь, если мы в СП по товару в Направлении укажем ограничение, оно будет применяться и в СП по Бренду:Ссылка на ФМ ZF4IF_BRAND_SAMPLE_EXIT
👍😆
Саша, спасибо тебе огромное за статью, я ждала))) твое решение, как всегда, красивое!
ОтветитьУдалить