пятница, 28 сентября 2012 г.
SAP GUI 7.30 Patch 1
21 сентября вышел Patch 1 для SAP GUI 7.30. Скачать можно с официального сайта или отсюда
понедельник, 6 августа 2012 г.
Встраивание своего обработчика в процесс вывода BSP приложения.
Возникла необходимость в существующем BSP приложении в заголовок каждой страницы добавить некое содержимое. Не ленивый программист открыл бы каждую страницу и добавил руками. Но такой лентяй как я решил, что это не круто и решил найти способ поменять результирующий HTML, отдаваемый BSP приложением.
Вначале была идея написать свой HTTP Request Handler и встроить его в систему. Но выяснились следующие вещи. Во-первых, просто присвоить свой обработчик в транзакции SICF к своему Z-BSP-приложению не получится, потому что все Z-BSP-приложения находятся на уровне /default_host/sap/bc/bsp/sap/*, а на раздел BSP уже назначен обработчик CL_HTTP_EXT_BSP, и все обработчике ниже этого узла работать не будут. Можно бы было добавить свой обработчик к узлу /default_host/sap/bc/bsp/, либо вообще его заменить своим, из которого уже вызывать изначальный. Но как показывает чужой опыт (вот, например) это может привести к тому, что сервис будет деактивирован после переноса запроса в целевую систему. Хотя его потом можно активировать вручную, хотелось бы избежать лишних телодвижений и дерганья базисников. И это - во-вторых.
Тогда был найден другой способ. Каждому BSP приложению можно назначить Класс приложения на вкладке Свойства. Этому классу (либо его родительскому классу) можно назначить интерфейс IF_BSP_APPLICATION_EVENTS и реализовать его. Реализовать нужно 4 метода (ON_REQUEST, ON_RESPONSE, ON_START и ON_STOP). Причем реализация может быть пустой - главное чтобы метод был.
Методы ON_START и ON_STOP вызываются при запуске и завершении всего BSP-приложения. Методы ON_REQUEST и ON_RESPONSE вызываются для каждого запроса, перед и после стандартного обработчика, соответственно.
В моем случае мне подходил ON_RESPONSE, так как мне нужно было менять готовый ответ. Текущую отдаваемую страницу можно было получить с помощью RESPONSE->GET_CDATA( ), а установить новую с помощью RESPONSE->SET_CDATA(), где RESPONSE - параметр метода ON_RESPONSE типа IF_HTTP_RESPONSE.
Вначале была идея написать свой HTTP Request Handler и встроить его в систему. Но выяснились следующие вещи. Во-первых, просто присвоить свой обработчик в транзакции SICF к своему Z-BSP-приложению не получится, потому что все Z-BSP-приложения находятся на уровне /default_host/sap/bc/bsp/sap/*, а на раздел BSP уже назначен обработчик CL_HTTP_EXT_BSP, и все обработчике ниже этого узла работать не будут. Можно бы было добавить свой обработчик к узлу /default_host/sap/bc/bsp/, либо вообще его заменить своим, из которого уже вызывать изначальный. Но как показывает чужой опыт (вот, например) это может привести к тому, что сервис будет деактивирован после переноса запроса в целевую систему. Хотя его потом можно активировать вручную, хотелось бы избежать лишних телодвижений и дерганья базисников. И это - во-вторых.
Тогда был найден другой способ. Каждому BSP приложению можно назначить Класс приложения на вкладке Свойства. Этому классу (либо его родительскому классу) можно назначить интерфейс IF_BSP_APPLICATION_EVENTS и реализовать его. Реализовать нужно 4 метода (ON_REQUEST, ON_RESPONSE, ON_START и ON_STOP). Причем реализация может быть пустой - главное чтобы метод был.
Методы ON_START и ON_STOP вызываются при запуске и завершении всего BSP-приложения. Методы ON_REQUEST и ON_RESPONSE вызываются для каждого запроса, перед и после стандартного обработчика, соответственно.
В моем случае мне подходил ON_RESPONSE, так как мне нужно было менять готовый ответ. Текущую отдаваемую страницу можно было получить с помощью RESPONSE->GET_CDATA( ), а установить новую с помощью RESPONSE->SET_CDATA(), где RESPONSE - параметр метода ON_RESPONSE типа IF_HTTP_RESPONSE.
пятница, 13 июля 2012 г.
среда, 13 июня 2012 г.
Изменение позиций финансовых документов.
Финансы в SAP писали совсем другие люди, что писали например MM-SD. В последних есть какие-то BADI, BAPI, User-Exit и прочие технологии, которые позволяют внедрится в процесс создания/изменения логистических документов, либо просто их менять из своих программ. В FI для внедрения служат как правило замещения. Есть еще OpenFI, но в каких-то случаях он работает в каких-то нет. В любом случае если нужно поменять финансовый документ из своей программы, то как правило для этого нет никаких ФМ или BAPI, и вместо них приходится применять "пакетники" (batch input). Если кто не в курсе - это аналог макросов в продуктах MS Office. "Пакетник" записывается на какую-то стандартную транзакцию и фактически эмулирует действия пользователя, когда он работает в этой транзакции. Это, конечно, относительно медленно, но других вариантов как правило нет. Вернее можно, конечно, напрямую обновлять BSEG, только это чревато получением неконсистентности данных. Но основной минус "пакетников" - это то, что они, скажем так, однонаправленные. То есть, можно передать транзакции команды и тем самым сказать что делать, но невозможно прочитать из транзакции ее текущее состояние - то есть нельзя понять, что получилось в вычисляемых полях, получить данные списка в том порядке, как он выведен на экране (это часто нужно для позиционирования или выделения), даже нельзя получить номер экрана на котором мы находимся, если, например, следующий экран зависит от введенных данных. Вот последнее особенно портит нервы, поскольку команды в "пакетнике" привязаны к экрану.
Например, записали вы "пакетник" на изменение текста позиции FI-документа в транзакции FB02. Проверили на примерах - все вроде работает. Отдали в тест и выясняется, что когда то работает, а когда то нет.
Включили интерактивный режим, запустили пример на котором не сработало, выполнение дошло до нужного экрана как вам кажется и остановилось, причем текст не поменялся, сохранение не производится. Непонятно.
На самом деле объяснение банальное - команда у вас записана для выполнения на определенном экране, а транзакция вывела вам другой. С виду он может даже похож на изначальный, но по факту - другой. Сделала она это по одной ей известной логике - может тип документа другой, может получатель какой-то особенный, может что-то с датами.
Можно, конечно, провести пару незабываемых часов в отладке выясняя почему уже оно так. Но к счастью для изменения позиций финансового документа есть ФМ
FI_ITEMS_MASS_CHANGE, в котором вся эта логика уже зашита. По факту он точно также выполняет пакетник, но все заморочки с экранами решает за вас.
Например, записали вы "пакетник" на изменение текста позиции FI-документа в транзакции FB02. Проверили на примерах - все вроде работает. Отдали в тест и выясняется, что когда то работает, а когда то нет.
Включили интерактивный режим, запустили пример на котором не сработало, выполнение дошло до нужного экрана как вам кажется и остановилось, причем текст не поменялся, сохранение не производится. Непонятно.
На самом деле объяснение банальное - команда у вас записана для выполнения на определенном экране, а транзакция вывела вам другой. С виду он может даже похож на изначальный, но по факту - другой. Сделала она это по одной ей известной логике - может тип документа другой, может получатель какой-то особенный, может что-то с датами.
Можно, конечно, провести пару незабываемых часов в отладке выясняя почему уже оно так. Но к счастью для изменения позиций финансового документа есть ФМ
FI_ITEMS_MASS_CHANGE, в котором вся эта логика уже зашита. По факту он точно также выполняет пакетник, но все заморочки с экранами решает за вас.
суббота, 26 мая 2012 г.
Строковые литералы и символьные литералы. Пробел
Хотя ABAP позволяет практически незаметно и безболезненно конвертировать одни типы данных в другие, а типы STRING и C вообще считает чуть ли не синонимами, все таки бывают ситуации, когда в их поведении есть отличия. Одним из таких отличий является то, как типы string и char воспринимают пробел.
На само деле С (char) - это всего 1 символ, так что правильнее говорить об элементе данных большей длины, но с внутренним типом C - например CHAR10. Фактически CHAR10 - это "статический" массив из 10 символов типа C, в тоже время STRING - это "динамический" массив ( то есть с переменной длиной) символов типа C.
Рассмотрим следующий код:
DATA lv_char TYPE char10 VALUE '12345'.
DATA lv_str TYPE string VALUE '12345'.
Здесь переменная lv_char хранит значение, равное '12345 '- то есть символы от 1 до 5 и еще 5 пробелов, потому что у нее фиксированная длина 10 символов. Переменная lv_str хранит только символы '12345'.
А теперь следующий код:
DATA lv_char TYPE char10 VALUE space.
DATA lv_str TYPE string VALUE space.
space - это заменитель ' '.
Здесь переменная lv_char хранит значение, равное 10 пробелам. Переменная lv_str хранит '', то есть ничего. Не один пробел, а именно ничего. Длина строки = 0.
Ну с lv_char думаю все понятно. Почему lv_str не хранит пробел?
Потому что мы инициализируем ее символьным литералом (символьной константой) - то есть неким набором символом (в данном случае пробелом), заключенным между одинарных кавычек. Прикол в том, что в символьных литералах граничные пробелы удаляются. Поскольку у нас ничего кроме пробела нет, то строка в итоге ничего и не хранит.
Рассмотрим пример, в котором проявляются особенности переменных типа CHAR* и сжатия пробелов.
Допустим у нас извне приходит некое значение в переменной lv_char типа CHAR10 - пользователь что-то вводит, IDOC пришел - что угодно. Нам нужно проверить, что это значение содержит только цифры. Если это не так, выдавать ошибку, писать логи - что угодно. Не суть важно.
Как это можно проверить? Например, так:
IF lv_char CO '0123456789'.
"только цифры.
ELSE.
"что-то еще
ENDIF.
Теперь допустим нам приходит в переменной lv_char число 42. Но после проверки мы попадаем в ветку ELSE. Удивительно? На первый взгляд да - ведь в переменной только цифры 4 и 2, проверка должна проходить. Но потом становится понятно, что на самом деле не только 4 и 2, а еще и 8 пробелов. Ладно, меняем условие на такое, что в переменной допускаются цифры и пробелы:
IF lv_char CO '0123456789 '. "После 9 стоит пробел
Но программа все равно упорно лезет в ветку ELSE. Ну поскольку про сжатие пробелов Вы уже в курсе, то можете догадаться, что наш трюк с добавлением пробела не сработал, потому что компилятор его все равно выкинул и в итоге в скомпилированном коде у нас все также проверка на 1-9. О пробелах ни слова, ни байта.
Ну это все замечательно, но что делать? Вариантов несколько:
На само деле С (char) - это всего 1 символ, так что правильнее говорить об элементе данных большей длины, но с внутренним типом C - например CHAR10. Фактически CHAR10 - это "статический" массив из 10 символов типа C, в тоже время STRING - это "динамический" массив ( то есть с переменной длиной) символов типа C.
Рассмотрим следующий код:
DATA lv_char TYPE char10 VALUE '12345'.
DATA lv_str TYPE string VALUE '12345'.
Здесь переменная lv_char хранит значение, равное '12345 '- то есть символы от 1 до 5 и еще 5 пробелов, потому что у нее фиксированная длина 10 символов. Переменная lv_str хранит только символы '12345'.
А теперь следующий код:
DATA lv_char TYPE char10 VALUE space.
DATA lv_str TYPE string VALUE space.
space - это заменитель ' '.
Здесь переменная lv_char хранит значение, равное 10 пробелам. Переменная lv_str хранит '', то есть ничего. Не один пробел, а именно ничего. Длина строки = 0.
Ну с lv_char думаю все понятно. Почему lv_str не хранит пробел?
Потому что мы инициализируем ее символьным литералом (символьной константой) - то есть неким набором символом (в данном случае пробелом), заключенным между одинарных кавычек. Прикол в том, что в символьных литералах граничные пробелы удаляются. Поскольку у нас ничего кроме пробела нет, то строка в итоге ничего и не хранит.
Рассмотрим пример, в котором проявляются особенности переменных типа CHAR* и сжатия пробелов.
Допустим у нас извне приходит некое значение в переменной lv_char типа CHAR10 - пользователь что-то вводит, IDOC пришел - что угодно. Нам нужно проверить, что это значение содержит только цифры. Если это не так, выдавать ошибку, писать логи - что угодно. Не суть важно.
Как это можно проверить? Например, так:
IF lv_char CO '0123456789'.
"только цифры.
ELSE.
"что-то еще
ENDIF.
Теперь допустим нам приходит в переменной lv_char число 42. Но после проверки мы попадаем в ветку ELSE. Удивительно? На первый взгляд да - ведь в переменной только цифры 4 и 2, проверка должна проходить. Но потом становится понятно, что на самом деле не только 4 и 2, а еще и 8 пробелов. Ладно, меняем условие на такое, что в переменной допускаются цифры и пробелы:
IF lv_char CO '0123456789 '. "После 9 стоит пробел
Но программа все равно упорно лезет в ветку ELSE. Ну поскольку про сжатие пробелов Вы уже в курсе, то можете догадаться, что наш трюк с добавлением пробела не сработал, потому что компилятор его все равно выкинул и в итоге в скомпилированном коде у нас все также проверка на 1-9. О пробелах ни слова, ни байта.
Ну это все замечательно, но что делать? Вариантов несколько:
- Поставить пробел не в конце, а, например, посередине:
IF lv_char CO '01234 56789'. "После 4 стоит пробел.
Поможет в данном случае, поскольку пробелы нам фактически не нужны - мы просто обходим особенность CHAR, но могут быть случаи, когда пробел ключевая фигура - ниже я приведу пример. - Помещать lv_char предварительно в переменную типа string. Это также прокатит, если вам фактически пробел не нужен. Но если нужен - то нет. Причем в отличии от п.1 это еще и лишняя переменная, лишнее присвоение, да и лишняя писанина.
- Использовать не символьный литерал, а строковый. Строковый в коде задается набором символов ограниченным апострофами (на русской клавиатуре он обычно на кнопке с буквой Ё). Напомню, символьный ограничивается одинарной кавычкой (на кнопке с Э). В строковых литералах пробелы НЕ сжимаются - что ввели, то и будет в коде. То есть, в нашем примере код может быть таким:
IF lv_char CO `0123456789 `. "После 9 стоит пробел
Теперь обещанный пример, когда пробел ключевая фигура. Задача найти первую позицию с пробелом в строке . Пишем например так:
FIND ' ' IN '12345 Text' MATCH OFFSET lv_offset MATCH LENGTH lv_length.
В результате в lv_offset у нас будет 0, а не ожидаемое 5. Почему? Все потому же - ' ' сжался до пустого места, а пустое место у нас где угодно, а первое пустое место в начале строки. Оба первых пункта из решения выше нам не подойдут здесь, только 3ий:
FIND ` ` IN '12345 Text' MATCH OFFSET lv_offset MATCH LENGTH lv_length.
FIND ' ' IN '12345 Text' MATCH OFFSET lv_offset MATCH LENGTH lv_length.
В результате в lv_offset у нас будет 0, а не ожидаемое 5. Почему? Все потому же - ' ' сжался до пустого места, а пустое место у нас где угодно, а первое пустое место в начале строки. Оба первых пункта из решения выше нам не подойдут здесь, только 3ий:
FIND ` ` IN '12345 Text' MATCH OFFSET lv_offset MATCH LENGTH lv_length.
среда, 25 апреля 2012 г.
Ручное изменение таблиц
Иногда в процессе разработки функциональности или в процессе отладки бывает необходимо что-то поменять в таблице базы данных. Но ракурса для изменения нет. И сделать его нельзя, либо не имеет смысла для одной правки.
Для этой цели существует несколько недокументированных возможностей.
Для этой цели существует несколько недокументированных возможностей.
- Транзакция SE16N. После запуска до выбора данных в окне команд нужно ввести "&sap_edit" (без кавычек), нажать Enter, после чего запустить выборку. При этом таблица откроется и в строке команд будут доступны команды создания/удаления строк и поля будут доступны на изменения. Но в последних версиях эту возможность пропатчили и она не работает.
- Если п.1, не работает, то можно попробовать вместо SE16N запускать транзакцию UASE16N и выполнять ту же последовательность действий. Но эта транзакция может быть объявлена как устаревшая, о чем будет соответствующее сообщение при запуске, и использовать ее не получится.
- Если вышеперечисленные способы не работают, можно попробовать в транзакции 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.
Соглашения хранятся в таблице KONA. Уникальный номер соглашения KONA-KNUMA.
Получить номер соглашения по номеру условия просто - он хранится в одном из полей KONH-KNUMA_* (например KNUMA_BO).
Получить все номера условий для заданного соглашения, конечно, можно, выполнив SELECT по KONH-KNUMA_*, но это очень медленно, потому что индекса по этим полям в KONH нет.
В принципе для этого существует индекcная таблица KONAIND, но судя по тому стандартному коду, который находится поиском, она используется только в случае, если тип соглашения (KONA-ABTYP) равен "C" (Мероприятие по стимулированию продаж).
Для бонусных соглашений правильным способом получения всех условий является использование ФМ SD_BONUS_READ. Если же "копнуть" глубже, то поиск условий в этом ФМ производится по таблицам KOTE*, в которых есть поля KNUMA и KNUMH и, как правило, есть индекс по KNUMA.
Особенности вывода PDF в Web Dynpro
При использовании Web Dynpro компонента INTERACTIVE_FORM для вывода PDF прямо в HTML страницу могут возникнуть некоторые проблемы.
Например, если на представлении помимо компонента INTERACTIVE_FORM присутствуют другие элементы, которые должны реагировать на действия пользователя - типичный вариант, кнопка Назад - то реагировать они не будут. Происходит это похоже потому, что используемый для вывода Adobe Reader перехватывает эти события и до обработчиков они не доходят. Возможно, это сделано для работы с интерактивными PDF-формами. Не знаю, пока с ними не сталкивался.
Но если у Вас обычная форма, которую нужно просто показать пользователю, то решением будет просто снятие флага Enabled у компонента INTERACTIVE_FORM.
Вторая тонкость заключается в способе формирования PDF. Компонент позволяет 2 варианта. В первом случае вы задаете в свойстве templateSource имя шаблона, а в свойство dataSource "байндите" данные. Очень похоже на то, как PDF выводится через свой сгенерированный ФМ при работе через SAP GUI. Но при выводе этим способом уже неоднократно сталкивались с тем, что при этом формируемый на сайте PDF либо вообще не содержит текста, либо кракозябры вместо него. Возможно проблема в отсутствии каких-то шрифтов или прописанных путей до них. Пока не нашли в чем дело. Да и не искали особо. Потому что есть более надежный и простой способ - сгенерировать PDF как обычно через ФМ, но не выводить его, а получить сформированный файл в виде XSTRING и присвоить свойству pdfSource компонента INTERACTIVE_FORM. Для получения файла в виде XSTRING необходимо при вызове ФМ FP_JOB_OPEN передать в параметре ie_outputparams установленные поля nodialog и getpdf. При этом вызов сгенерированного ФМ для формирования выходного документа в параметре /1bcdwb/formoutput вернет в поле pdf созданный файл.
Например, если на представлении помимо компонента INTERACTIVE_FORM присутствуют другие элементы, которые должны реагировать на действия пользователя - типичный вариант, кнопка Назад - то реагировать они не будут. Происходит это похоже потому, что используемый для вывода Adobe Reader перехватывает эти события и до обработчиков они не доходят. Возможно, это сделано для работы с интерактивными PDF-формами. Не знаю, пока с ними не сталкивался.
Но если у Вас обычная форма, которую нужно просто показать пользователю, то решением будет просто снятие флага Enabled у компонента INTERACTIVE_FORM.
Вторая тонкость заключается в способе формирования PDF. Компонент позволяет 2 варианта. В первом случае вы задаете в свойстве templateSource имя шаблона, а в свойство dataSource "байндите" данные. Очень похоже на то, как PDF выводится через свой сгенерированный ФМ при работе через SAP GUI. Но при выводе этим способом уже неоднократно сталкивались с тем, что при этом формируемый на сайте PDF либо вообще не содержит текста, либо кракозябры вместо него. Возможно проблема в отсутствии каких-то шрифтов или прописанных путей до них. Пока не нашли в чем дело. Да и не искали особо. Потому что есть более надежный и простой способ - сгенерировать PDF как обычно через ФМ, но не выводить его, а получить сформированный файл в виде XSTRING и присвоить свойству pdfSource компонента INTERACTIVE_FORM. Для получения файла в виде XSTRING необходимо при вызове ФМ FP_JOB_OPEN передать в параметре ie_outputparams установленные поля nodialog и getpdf. При этом вызов сгенерированного ФМ для формирования выходного документа в параметре /1bcdwb/formoutput вернет в поле pdf созданный файл.
SAP GUI 7.2 Patch 11 for Windows
SAP выпустил очередное обновление своего GUI 7.2 для Windows. Значимой вещью 11го патча является исправление проблемы с Adobe LiveCycle Designer - после установки толи 9, толи 10 патча в дизайнере PDF пропадало главное меню. Судя по ноте, это проявлялось только при наличии Microsoft Office 2010. Это исправлено. Скачать можно с официального сайта или отсюда .
понедельник, 9 апреля 2012 г.
О чем это?
Когда будет свободное время и мне будет не лень, то здесь будут появляться записи о тонкостях программирования на ABAP. Скорее даже обо всем, с чем приходится сталкиваться абаперу в процессе свой профессиональной жизнедеятельности - в том числе PDF, MS Office, Web, SQL etc.
Подписаться на:
Сообщения (Atom)