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

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


В средстве поиска (СП) могут быть входящие параметры, которыми можно ограничивать результат, выдаваемый пользователю для выбора.
Возьмем для примера СП для склада 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
👍😆

1 комментарий:

  1. Саша, спасибо тебе огромное за статью, я ждала))) твое решение, как всегда, красивое!

    ОтветитьУдалить