Привет, Хабр! Меня зовут Сергей Шумаков, я архитектор разработки в команде ИТ «Северстали», которая занимается программным модулем ремонтов SAP Plant Maintenance (он же PM, он же EAM, он же ТОРО/ТОиР). Хорошая штука, этот модуль — помогает автоматизировать различные процессы техобслуживания и ремонта оборудования на производстве. Многое в этой деятельности связано с документацией, и значительная часть алгоритмов SAP PM состоит из работы со статусами документов. Но вот неприятность: штатные средства для этой работы совершенно ужасны. 

Неподготовленный разработчик обязательно наделает здесь кучу ошибок и попадёт как минимум в одну из ловушек, заботливо расставленных SAP’ом. Но наши руки не для скуки, поэтому мы с коллегами придумали подход, который позволил сэкономить море времени, облегчил труд разработчиков и консультантов, а ещё значительно повысил надёжность и читаемость программного кода. О нём и поведаю. А чтобы была понятна значимость нашей разработки, начну с небольшой вводной.

Чем важны статусы документов в SAP PM 

Любое ПО для автоматизации обслуживания и ремонта (ТОРО) автоматизирует не сам физический ремонт, а всё, что окружает этот процесс: 

  • ведение технологических карт, спецификаций, историй ремонтов; 

  • различные учёты и детализации (объектов ремонта, технических мест и т. д.);

  • планирование работ;

  • работа с заказами и наряд-заданиями;

  • работа с регламентами;

  • обращение к нормативам, справочникам и прочим «бумагам».  

Документы здесь — основа, и всем участникам этих процессов важно видеть, на каком этапе исполнения они (и связанные с ними работы) находятся. Состояние документа в данный момент как раз и показывают наши с вами статусы. 

Статусы документов — это, по сути, набор флажков. В процессе работы с документом его статус меняется: от только что созданного (ОТКР НУТВ — открыт, не утверждён) к обрабатываемому (ДЕБЛ УТВ — деблокирован, утверждён) и далее к выполненному (ТЗКР ПДТВ УТВ — технически закрыт, подтверждён, утверждён).

Статусы бывают номерные и неномерные. В один момент времени в документе может стоять только один из номерных. Неномерные могут быть установлены и сняты независимо.

Статусы бывают системные и пользовательские. У статуса есть код-префикс (например, I0001 для системных, E0001 для пользовательских) и есть текст, состоящий из четырёх символов и ведущийся на разных языках (например, ОТКР, ЗАКР). Код системного статуса всегда уникален, то есть I0001 ОТКР всегда и везде имеет смысл «открыто». Коды пользовательских статусов, напротив, неуникальны. Под одним и тем же кодом E0001 могут скрываться совершенно разные по сути статусы в разных статусных схемах.

Ох уж эти двуличные статусные схемы пользовательских статусов!

Статусная схема — это такая штука, которая определяет набор пользовательских статусов, установленных в определённом виде документа (например, в заказе). У документов одного типа могут одновременно существовать несколько статусных схем. В системе можно переключить статусную схему, но тогда вчерашний заказ будет иметь схему PM1, а сегодняшний, новый, точно такой же заказ, будет создан со схемой PM2. При этом код Е0001 во вчерашнем заказе имел текст и смысл ГТВ (Готов), а в сегодняшнем имеет совершенно другой смысл — УТВ (Утверждён). 

Это ловушка №1 для разработчика. С ней связана и ловушка №2, которая заключается в том, что в интерфейсе штатной функции STATUS_CHECK вообще нет статусной схемы ни в каком виде! Извини, дорогой разработчик, но, проверяя код статуса E0001 в заказе, ты должен сам заранее узнать, что именно означает этот код в данном конкретном документе — готов, утверждён или даже удалён?

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

Нормальные статусные схемы системных статусов, но ненормальное проставление

Системные статусы проверять гораздо проще, потому что они… не имеют статусной схемы. Хоть тут проблем нет. Однако проставлять системные статусы не так просто — здесь проблемы есть. И много. 

Технически можно проставить любой системный статус любому документу. Но, как правило, для каждого типа документа есть специфические стандартные функциональные модули, которые кроме проставления статусов совершают ещё ряд обязательных действий. Проставление системного статуса напрямую, а не через нужный функциональный модуль, с большой вероятностью приведёт к порче документа. Это ловушка №3: догадайся, что статус нельзя ставить напрямую, а потом пойди найди необходимую функцию для нужного документа.

Количество статусов, которые можно установить для документа, практически не ограничено. Но стандарт предусматривает выдачу текстовых строк со статусами: 40 символов для вывода системных и 40 — для пользовательских статусов. С учётом четырёхсимвольного текста статуса и пробела в каждую из строк влезает до восьми статусов. Остальные, увы, за бортом. И да, приоритет (порядок) вывода статусов в строку — это тоже отдельная и весьма неочевидная настройка.

Казалось бы, если есть возможность вывода статусов в текстовом виде, давайте проверять текст статуса, а не его код! Проверяем по наличию текста «ГТВ» в строке статусов, и… попадаем в ловушку №4: где мой статус, я же его ставил?! Ой, не влез в 40 символов… 

А потом сразу попадёшь в ловушку №5: консультант тихонько добавил статус «НГТВ» (не готов) в статусную схему, и твой поиск подстроки «ГТВ» удачно на неё наткнулся… Тут же всплывёт и ловушка №6: пользователь зашёл в систему на английском, а поиск подстроки «ГТВ» не смог опознать статус RDY (Ready).

Кроме этих весёлых ловушек, есть ещё и проблема с большим количеством строк кода, который требуется для каждой отдельной проверки

Простейший пример проверки статуса занимает минимум 9 строк кода:

CALL FUNCTION 'STATUS_CHECK'

  	EXPORTING

    	objnr         	= caufvd-objnr "номер объекта документа

    	status        	= stk_cugt 	"код статуса

  	EXCEPTIONS

    	object_not_found  = 01

    	status_not_active = 02.

	IF sy-subrc = 0.                 	"Результат проверки

	"Требуемые действия

	ENDIF.

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

Конечно же, многие разработчики пишут «обёртки» для стандарта, чтобы облегчить свою жизнь. И именно огромное количество таких обёрток, ни одна из которых не была достаточно универсальной, побудила нас написать средство для проверки статусов. Резюмируем предпосылки разработки решения.

7 причин разработки своего решения для проверки статусов документов в SAP PM 

  1. Проблема с разными статусными схемами к одному виду документа: одинаковый код, но разные тексты (и смыслы!) статусов.

  2. Проблема проверки через коды: какой код статуса проверять в конкретном документе?

  3. Проблема проверки через тексты статусов: разные языки входа.

  4. Проблема с количеством строк кода, а значит, с читаемостью и надёжностью. Сюда же: нечитаемость кодов E0001 I0002 — что означают эти буквы и цифры? А что они означали год назад и что будут означать завтра?

  5. Проблема с пониманием, что как должно работать. С технической точки зрения всё устроено слишком сложно и плохо документировано.

  6. Проблема с толкованием техзадания: «проверить статус “ПДТВ”» — это системный или пользовательский? Надо вспомнить, в каких таблицах лежат описания тех и других, найти таблицу и статусную схему, убедиться что она подходит для нужного документа…

  7. Работа с новыми (впервые устанавливаемыми), старыми, актуальными и изменёнными статусами: требует запуска совершенно разных функций с различными параметрами и подходом к проверке результатов.

Вот что мы решили сделать

Во-первых, договорились, что мы перестаём использовать коды статусов (Е0001/I0001) и переходим с пары «статусная схема + код статуса» на пару «язык + текст статуса». Уникальность текста статуса в пределах статусной схемы была возложена на консультанта (ага, это была ловушка №7: для разных кодов статусов Е0001, Е0002 в пределах одной статусной схемы может быть одинаковый текст на одном языке, причём вообще неочевидном; к примеру, пустой текст у всех статусов — на зимбабвийском).

Во-вторых, определили набор методов, который будет необходим и достаточен для полноценной проверки статусов.

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

Вот как это выглядит с точки зрения разработчика

Для начала в среде разработки Eclipse мы пытаемся сделать простую проверку на статус «ОТКР» (открыто).

1. Вводим zcl_status=>, нажимаем Ctrl+Space, видим список доступных фабричных методов. RU EN DE отличаются только языком, на котором будут проверяться тексты статусов. Для остальных языков есть CREATE с дополнительным входным параметром «язык». Каждый из этих методов возвращает ссылку на создаваемую инстанцию me, то есть сам на себя, через интерфейс zif_status_updkz.

2. Выбираем RU, вводим параметр (он тут один, ошибиться невозможно — это номер объекта) и переходим к методам интерфейса zif_status_updkz. Тут у нас есть выбор, какие именно статусы объекта нас интересуют:

  • актуальные — то, что есть на данный момент;

  • изменённые — те, что были изменены по сравнению с сохранёнными в базе данных;

  • новые — те, что устанавливаются для этого документа впервые;

  • старые — те, что сохранены в базе данных.

Каждый из этих методов возвращает ссылку на следующий интерфейс: zif_status. Параметров тут нет (за ненадобностью).

3. Выбираем актуальные статусы, метод actual. Теперь у нас появляется список основных методов по проверке. Он большой, в скриншот целиком не помещается. Основные методы:

  1. CHECK — проверка по коду статуса (например, Е0001).

  2. TEXT_CHECK — проверка по тексту статуса (например, «ОТКР»).

  3. GET_STATLINE — получение строки статусов (как в стандарте).

  4. GET_STATUSES — получение статусов в виде таблицы. В данном случае — актуальных.

  5. Всяческие преобразования — текст в описание, код в текст и т. п.

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

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

Такой способ позволяет не только быстро и безошибочно писать программу, но и наглядно читать программный код, в том числе далёкими от программирования консультантами: «Если на языке RU установлен актуальный статус “ОТКР”, то…»

IF zcl_status=>ru( iv_objnr = iv_order_objnr )->actual( )->text_check( 'ОТКР' ).

  MESSAGE 'Открыто' TYPE 'S'.

ENDIF.

Сравните это с традиционным подходом:

CALL FUNCTION 'STATUS_CHECK'

  EXPORTING

	objnr         	= iv_order_objnr

	status        	= 'I0001'   "Код статуса ОТКР

  EXCEPTIONS

	object_not_found  = 1

	status_not_active = 2

	OTHERS        	= 3.

IF sy-subrc = 0.

  MESSAGE 'Открыто' TYPE 'S'.

ENDIF.

Даже в элементарнейшей ситуации, когда проверяем системный статус (то есть, никаких статусных схем!), видно значительное упрощение кода. А уж в случаях, когда статусные схемы разъехались, и нужно проверять код пользовательского статуса, выигрыш просто разгромный.

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

А что с ловушками?

Ловушки 1 и 2. Статусные схемы теперь не интересуют разработчика от слова «совсем». Консультант отвечает за то, что пользовательский статус «ГТВ» — это всегда «Готово». И какая тут статусная схема, какой код статуса — E0001 или E1510, совершенно неважно.

Ловушка 3, где статус нельзя ставить напрямую остаётся, так как класс ZCL_STATUS не имеет функциональности по проставлению статусов. Связано это с тем, что изменение статуса используется в десятки раз реже, чем проверка.

Ловушки 4 и 5. Проверка по текстовой строке с несколькими статусами заменена на индивидуальную проверку. Поэтому случайно перепутать «ГТВ»  и «НГТВ»  невозможно. Также невозможно потерять статус из-за того, что он не влез в 40 символов.

Ловушка 6. Другой язык. У нас все тексты статусов ведутся и используются на русском языке. Однако если какая-то разработка принципиально должна использовать английские тексты, никто не мешает это делать. Для переводов есть метод TRANSLATE, возвращающий интерфейс с соответствующим набором методов.

Ловушка 7. Одинаковые (пустые) тексты статусов в пределах статусной схемы для определённого языка. Не используйте этот язык. Или попросите консультантов настроить статусную схему должным образом.

Эффекты от внедрения подхода

Очевидные плюсы

У консультантов теперь появилась возможность неограниченного расширения статусных схем. Можно добавлять пользовательские статусы в любые схемы, не заботясь о том, чтобы их коды совпадали. Можно размножать, объединять, удалять статусные схемы — лишь бы набор текстов и смысл каждого текста статуса оставались уникальными. Изменения в статусных схемах теперь никак не затрагивают программную часть, всё работает, как прежде.

Класс ZCL_STATUS был использован в разработках примерно 350 раз, и даже если предположить, что труд программиста + консультанта + тестировщика сокращается на 10 минут за одно использование в одной программе, то это уже полных 7 сэкономленных рабочих дней. На самом деле экономия выходит в разы, а то и в десятки раз больше. Не говоря уже про то, что работать с классом ZCL_STATUS просто комфортнее.

Возможные минусы 

Были подозрения, что если вместо подхода «статусная схема+код» применять подход «язык+текст», то это приведёт к ошибкам. Но, пока вся работа ведётся на одном языке, количество ошибок минимально. Будут ли проблемы при разработках на разных языках, неизвестно. Однако как минимум мы к ним готовы, все инструменты имеются.

Остались одинаковые тексты системных статусов. Так, в нашей системе текст «ОТКР» есть для кодов I0001 и I1002. Однако в реальности таких проблем не возникало ни разу, ни в одном документе такого не встретилось.

Остались одинаковые тексты для системных и пользовательских статусов. Например, к системному «I0002 ДЕБЛ» добавился пользовательский «E0004 ДЕБЛ». Правильным решением будет изменить текст пользовательского статуса. Но на всякий случай есть методы etext_check, itext_check, которые проверяют только пользовательский и только системный статус соответственно.

Скорость работы не отличается от стандарта, так как класс, по сути, является обёрткой. Внутренности практически полностью стандартные, основаны на функциях STATUS_READ, STATUS_CHANGES_GET, STATUS_TEXT_CONVERSION.

Главный итог 

  1. Мы перешли от проверки документов по кодам статусов к проверке по текстам на выбранном языке.

  2. Мы добились гибкости в настройке статусов, наглядности и надёжности ABAP-кода, сокращения количества строк, единообразия в применении. 

  3. Все ожидаемые преимущества подтвердились, риски и минусы, напротив, себя не никак не проявили.

  4. И главное — год прошёл, полёт нормальный!

Комментарии (4)