ZDOC_2536

В погоне за большой кнопкой

«Самый лучший гуй — одна большая кнопка
в центре экрана с надписью «Сделай мне хорошо!»
(с) Опытный дизайнер

«Самый лучший гуй — одна большая надпись
в центре экрана «Тебе уже хорошо!»
(с) Опытный программист

Цитата #437964
Неудобства: При работе с программой
приходится нажимать на различные кнопки
(с) bash.org.ru

 

В идеале каждая система должна стремиться к минимизации своего интерфейса.

В качестве плохого примера в системе SAP ERP можно вспомнить транзакцию проводки амортизации AFAB:

Стандартный экран проводки амортизации

Первый экран выглядит очень типично для SAP ERP, поэтому и проблемы у этого экрана старые идеологические:

1. Первый экран (экран выбора) не даёт никакой информации о текущем состоянии

2. Все проверки происходят после заполнения всех параметров и запуска процесса

3. Объем задачи большой, заранее не просчитывается, поэтому установлено простое ограничение на тестовый прогон – 1000 записей

4. Формирование фонового задания и отслеживание его статуса – не самая простая пользовательская операция: пять кликов на запуск, пять кликов на просмотр, причём через непростые малознакомые экраны.

5. Нет никакой отчётливой сигнализации об ошибках расчёта

 

Я просто удивляюсь, почему за последние десять лет никто так и не решился переписать междумордие этой важной задачи.

BTW: Вполне вероятно, что в “Пульте управления закрытием” это и сделано, вот только это тема совсем другого разговора.

Приступим?

 

Предпосылки

Сначала обратим внимание на предпосылки, которые должны оказать влияние на интерфейс.

* * *

В этой транзакции пользователь обычно выполняет две рутинных операции:

  • Один раз в месяц пользователь запускает расчет за последний месяц в режиме “по плану”.
  • Иногда пользователь запускает расчет за предыдущий месяц  в режиме “повторение”

* * *

Пользователь всегда прикреплён к одной определённой балансовой единице.

* * *

Амортизация выполняется по периодам строго последовательно и только вперёд. В системе контролируются статусы расчета по периодам.

* * *

Самый первый прогон всегда запускается вручную под пристальным наблюдением.

* * *

При прогоне амортизации не должны возникать ошибки. Если вдруг ошибки всё-таки возникают, то пользователь не может их решить самостоятельно.

* * *

Пользователю не нужно запускать тестовый прогон (режим моделирования без реальных проводок), всё сразу и взаправду.

* * *

Прогон амортизации выполняется не сразу, а занимает продолжительное время. Условный ориентир – пять минут.

* * *

В особо сложных случаях старая функциональность никуда не девается.

 

Интерфейс

При первом подходе с учетом требований выше нарисовался такой интерфейс:

Новый экран проводки амортизации

 

Что показываем:

  • Балансовую единицу не запрашиваем, просто показываем без вариантов.
  • Текущий месяц определяется автоматически, без вариантов.
  • Показываем дату последнего расчета.

 

Целых две кнопки:

  • В предыдущий раз было не очень хорошо, давай ещё раз!
  • Всё нормально, идём вперёд!

 

Можно ли сделать всего лишь одну кнопку? При большем количестве допущений – да. Например:

  • Если последний месяц расчета  = текущий, то можно выполнить только “Повтор”.
  • Если последний месяц расчета < текущий, то можно выполнить только “По плану”(Вперёд). Если при этом можно проверить, что (а) последний месяц расчета всё ещё открыт в бухгалтерском учёте, (б) с момента последнего расчета есть влияющие изменения, тогда можно предложить предварительный запуск в режиме “Повтор”.
  • При закрытии периода можно проверять наличие изменений, при которых желательно сделать прогон в режиме “Повтор”.

Это большие допущения, требующие более сложной реализации и обсуждения. Пока отложим.

 

Итак, если наш пользователь всё-таки нажимает одну их кнопок, то справа появляется журнал:

Новый экран проводки амортизации 2

Журнал автоматически обновляется каждые пять секунд, чтобы пользователь мог проконтролировать результат, если ему хочется. Записи журнала лучше выводить в обратном порядке, таким образом самая последняя запись должна отражаться первой.

Если пользователь уверен в результате, то он может закрыть окно, не дожидаясь окончания. Фоновое задание продолжит работу в любом случае.

Реализация

А теперь рассмотрим на реализацию типичных задач, спрятанную под этим капотом.

Кроме обыкновенного ABAP-кода нам понадобятся следующие запчасти и велосипеды:

  • Изменение надписей на кнопках в рантайме
  • Формирование фонового задания
  • Автообновление интерфейса
  • Вывод результатов в виде журнала

 

Добавим мегакнопки

Обычно кнопки имеют неизменяемую надпись, но мы можем изменять надпись в рантайме.

Сначала нам понадобится пара переменных, по одной на кнопку:

data: bt_refresh type char40.
data: bt_next type char40.

Затем надо нарисовать кнопки на экране:

Динамическая надпись на кнопке

Остаётся только написать код, который склеит хитрое наименование для кнопки в нужный момент:

concatenate 
  gc_icon_refresh
  ‘Перерасчитать'(G01)
  ls_t247-ltx
  taba-afblgj
  into bt_refresh
  separated by space.

Здесь кроме обыкновенных переменных можно увидеть константу с кодом иконки:

constants: gc_icon_refresh type icon-id value ‘@42@’. "ICON_REFRESH

С иконками интерфейс выглядит красивее.

BTW: Кнопки могут быть и на панели инструментов, но это реализуется иначе.

Добавим фоновое задание

Если есть супертяжёлые вычисления, то мы можем выделить их в фоновое задание (JOB). В этом случае работа будет выполняться в отдельном потоке, не связанном с пользовательской сессией.

Один из самых простых способов сформировать задание: воспользоваться функциями JOB_OPEN и JOB_CLOSE. При этом подразумевается, что все вычисления вынесены в отдельную программу, которую можно вызвать используя конструкцию SUBMIT REPORT.

Шаг первый. Придумываем имя и  открываем задание:

  data: lv_jobname        type tbtcjob-jobname.
  data: lv_jobcount       type tbtcjob-jobcount.

  concatenate 'RAPOST2000' iv_bukrs into lv_jobname separated by '_'.

  call function 'JOB_OPEN'
    exporting
      jobname          = lv_jobname
    importing
      jobcount         = lv_jobcount
    exceptions
      others           = 99.

Шаг второй. Формулируем содержимое задания:

 
  submit rapost2000                                    
    with p_bukrs = ...
    with p_gjahr = ...

    via job lv_jobname number lv_jobcount
    and return. 

Шаг третий. Закрываем задание:

  call function 'JOB_CLOSE'
    exporting
      jobcount             = lv_jobcount
      jobname              = lv_jobname
      strtimmed            = 'X'
    exceptions
      others               = 99.

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

Мы можем отследить дальнейшую судьбу этого задания по имени и идентификатору.

Добавим автообновление

Один из способов сделать автообновление экрана – реализация через таймер.

Сначала определяем таймер и его обработчик с событием:

data: ob_timer type ref to cl_gui_timer.

class lcl_event_handler definition.
  public section.
    class-methods: on_finished for event finished of cl_gui_timer.
endclass.
class lcl_event_handler implementation.
  method on_finished.
...
    "Если нам пора перерисовать экран, отправляем код экшена 
    "Он не обязан существовать
    call method cl_gui_cfw=>set_new_ok_code
      exporting
        new_code = 'DUMMY'.
...
    "Если надо продолжать автообновление, то снова взводим таймер
    ob_timer->run( ).
...
  endmethod.
endclass.

data: event_handler type ref to lcl_event_handler.

После этого осталось в нужный момент инициировать сам процесс автообновления:

  create object ob_timer.
  create object event_handler .
  set handler  event_handler->on_finished for ob_timer.
  ob_timer->interval = 5.
  ob_timer->run( ).

Добавим журнал

Журнал можно реализовать при помощи HTML-элемента.

Сначала надо инициализировать элемент управления:

  data: gr_html_cont type ref to cl_gui_custom_container.
  data: gr_html type ref to cl_gui_html_viewer.

  if gr_html_cont is not bound.
    create object gr_html_cont
      exporting
        container_name = 'HTML_CONTROL'.
  endif.

  if gr_html is not bound.
    create object gr_html
      exporting
        parent = gr_html_cont.
  endif.

А затем можно вставить внутрь нашего элемента любые HTML-фрагменты, например так:

  data: lt_html type table of char100 with header line.
  data: lv_url type char255.

  append '<html><head><meta charset="utf-8"></head>' to lt_html.
  append ... to lt_html....
  append '</html>' to lt_html.

  gr_html->load_data(
      exporting encoding = 'utf-8'
                charset = 'utf-8'
      importing  assigned_url = lv_url
      changing data_table = lt_html[] ).

  gr_html->show_url( exporting   url   = lv_url ).

И если все эти ингредиенты смешать в ABAP-редакторе, влить наполнителей и вкусовых добавок, то получится работоспособное решение.