Интересный профиль для разработчиков.
Если нужно поэкспериментировать с формой, так чтобы это не было заметно остальным, то можно выставить на уровне пользователя профиль "FND: Override Directory" (он же "БОП: переопределение каталога"), указав в нем каталог на сервере приложений с модифицированной формой.
четверг, 12 февраля 2009 г.
FND: Override Directory
среда, 11 февраля 2009 г.
Файл -> Экспорт. Продолжение
Проблема экспорта данных из однострочных блоков уже затрагивалась.
Есть еще одна тема, связанная с реализацией экспорта, вызывающая недоумение.
А именно.
Если данные в блоке расположены на нескольких закладках, то экспортируются только те колонки, которые расположены на экране (на активной закладке).
Совершенно непонятно зачем так делать.
Кажется очевидным, что лучше выгрузить всё что есть, а пользователь затем сам в Excel оставит нужное.
Так вот оказалось, что не всё так плохо.
Просто "не повезло" в том, что пришлось иметь дело с 11i.ATG_PF.H Rollup 3.
А именно в нем используется такой алгоритм экспорта.
В более свежих версиях, вообще сделано красиво.
При помощи небольшой персонализации можно устанавливать псевдо-свойство блока EXPORT_HIDDEN_CANVASES, которым можно задавать всё ли столбцы блока будут экспортироваться или только видимые.
Ну а счастливые обладатели 12 версии могут еще играться профилем "FND Export All Block Data"
Подробности здесь: 391414.1 Export Behavior in Oracle Applications Forms
Профили на полномочия
Еще одна обертка для fnd_profile.save
На этот раз для того, чтобы было удобно готовить скрипты по массовой устаноке значений профилей на уровне полномочий.
DECLARE
PROCEDURE set_profile_at_resp_level (
p_resp_name fnd_responsibility_vl.responsibility_name%TYPE
,p_user_profile_option_name fnd_profile_options_vl.user_profile_option_name%TYPE
,p_profile_option_value fnd_profile_option_values.profile_option_value%TYPE
) IS
l_responsibility_id fnd_responsibility_vl.responsibility_id%TYPE;
l_application_id fnd_responsibility_vl.application_id%TYPE;
l_profile_option_name fnd_profile_options_vl.profile_option_name%TYPE;
BEGIN
SELECT fr.responsibility_id
,fr.application_id
INTO l_responsibility_id
,l_application_id
FROM fnd_responsibility_vl fr
WHERE fr.responsibility_name = p_resp_name;
SELECT fpo.profile_option_name
INTO l_profile_option_name
FROM fnd_profile_options_vl fpo
WHERE fpo.user_profile_option_name = p_user_profile_option_name;
IF Fnd_Profile.save (
x_name => l_profile_option_name
,x_value => p_profile_option_value
,x_level_name => 'RESP'
,x_level_value => l_responsibility_id
,x_level_value_app_id => l_application_id
)
THEN
NULL;
END IF;
END set_profile_at_resp_level;
BEGIN
set_profile_at_resp_level ('Системный администратор', 'Служебные программы: диагностика', 'Y');
-- set_profile_at_resp_level ('...', 'НО: операционная единица', '...');
COMMIT;
END;
четверг, 11 декабря 2008 г.
Полезные профили. Автоматизация установки
Продолжая тему полезных профилей.
Если каждый день приходится делать одни и те же операции, то возникает естественная потребность в автоматизации этого процесса. Именно эта потребность имеется ввиду, когда некоторые программисты уверяют, что "лень это двигатель прогресса".
Ну разумеется, кому понравится постоянно заходить в форму с системными профилями и устанавливать для них значения. Если нужно ежедневно устанавливать одни и те же значения для одних и тех же профилей, то проще написать небольшую программку, которая будет это делать.
В основе программы вызов функции fnd_profile.save
Небольшая "обертка" сверху позволяет сделать установку профиля на уровне пользователя еще более удобной (опять же подтверждая тезис о лени).
DECLARE
PROCEDURE set_profile_at_user_level (
p_user_name fnd_user.user_name%TYPE
,p_user_profile_option_name fnd_profile_options_vl.user_profile_option_name%TYPE
,p_profile_option_value fnd_profile_option_values.profile_option_value%TYPE
) IS
l_user_id fnd_user.user_id%TYPE;
l_profile_option_name fnd_profile_options_vl.profile_option_name%TYPE;
BEGIN
SELECT fu.user_id
INTO l_user_id
FROM fnd_user fu
WHERE fu.user_name = p_user_name;
SELECT fpo.profile_option_name
INTO l_profile_option_name
FROM fnd_profile_options_vl fpo
WHERE fpo.user_profile_option_name = p_user_profile_option_name;
IF Fnd_Profile.save (
x_name => l_profile_option_name
,x_value => p_profile_option_value
,x_level_name => 'USER'
,x_level_value => l_user_id
)
THEN
NULL;
END IF;
END set_profile_at_user_level;
BEGIN
set_profile_at_user_level ('L_ELLISON', 'ВЕБ: время ожидания для сеанса', '300');
set_profile_at_user_level ('L_ELLISON', 'Служебные программы: диагностика', 'Y');
set_profile_at_user_level ('L_ELLISON', 'Цветовая схема Java', 'OLIVE');
COMMIT;
END;
Проверочный запрос, показывает все профили установленные для пользователя
SELECT fpo.profile_option_name
,fpo.user_profile_option_name
,fpov.profile_option_value
FROM fnd_profile_option_values fpov
,fnd_profile_options_vl fpo
WHERE fpov.application_id = fpo.application_id
AND fpov.profile_option_id = fpo.profile_option_id
AND fpov.level_id = 10004
AND fpov.level_value = (
SELECT fu.user_id
FROM fnd_user fu
WHERE fu.user_name = 'L_ELLISON'
)
среда, 3 декабря 2008 г.
Полезные профили
1. Если забота о ресурсах сервера вынудила вашего администратора ограничить время сессии в ОЕБС и вы устали заново вводить пароль возвращаясь с обеда, то на помощь приходит профиль:
"ВЕБ: время ожидания для сеанса" (profile_option_name = 'ICX_SESSION_TIMEOUT')
Значением профиля является время жизни сессии в минутах. Собственно этим профилем системный администратор и ограничивает время жизни сессии, но делает это на уровне Отделения. Нам же нужно поставить большее значение (зависит от вашей наглости :-)) на уровне пользователя.
2. Если вы устали вводить пароль APPS всякий раз при обращении к пунктам меню Справка -> Диагностика , то на помощь приходит профиль:
"Служебные программы: диагностика" (profile_option_name = 'DIAGNOSTICS')
В качестве значения нужно установить "Да"
3. Если вы хотите чтобы внешний вид экраннных форм для разных экземпляров системы (DEV, TEST, etc) явно различались друг от друга (ну чтобы не перепутать), то на помощь приходит профиль:
"Цветовая схема Java" (profile_option_name = 'FND_COLOR_SCHEME')
Значение выбираем из предложенного списка.
Разумеется речь идет не о Продуктивных экземплярах системы.
На Продуктиве консультантам делать нечего.
Речь идет об экземплярах, на которые у консультантов есть доступ (в том числе и пароль APPS), и которые они могут настроить под себя.
пятница, 21 ноября 2008 г.
Файл->Экспорт
Замечательный пункт меню.
Позволяет получать огромное количество нестандартных отчетов из системы. Данные экспортируются в текстовый файл, в котором значения разделены табуляцией (tsv) и который отлично понимается Excel.
Но есть одно но. Экспорт доступен только из многострочных(многозаписных) блоков данных. А для однозаписных блоков этот пункт меню недоступен. А как же быть с формами где на экране одновременно только одна запись? Организации, Лица, Пользователи системы, Полномочия, Определения параллельных программ, и т.д., и т.д.
Самое удивительное в этом то, что с технической точки зрения для реализации экспорта строк нет никакой разницы сколько на экране записей у блока - одна или несколько. Это вам подтвердит любой, кто знаком с Oracle Forms.
Поиск "правды" принес следующие результаты.
Библиотека APPCORE.pll (номер строк ниже видимо может отличаться в зависимости от версии билиотеки, но сами строки врядли)
package body APP_SYNCH, 81 строка
package body APP_EXPORT, 327 строка
Обе эти строки имеют следующий вид:
if get_item_property(..., RECORDS_DISPLAYED) > 1 then
И вот именно из-за этих двух строк мы имеем то что имеем!
Строка из APP_SYNCH делает пункт меню "Файл->Экспорт" недоступным для однозаписных блоков и доступным для многозаписных. Дополнительное удивление вызывет то, что срабатывает эта синхронизация не при переходе в новый блок, а при переходе в новый элемент (WHEN-NEW-ITEM-INSTANCE)?! Но это уже так, мелочи.
А строчка из APP_EXPORT дополнительно гарантирует, что враг не пройдет. Ведь предыдущее ограничение можно легко обойти персонализацией, делая в WHEN-NEW-ITEM-INSTANCE пункт меню "Файл->Экспорт" доступным. И пункт меню становится действительно доступным в однозаписных блоках, только данные не экспортируются :-) Одним словом, честную кастомизацию сделать не получается.
Если же заменить эти две строки, так чтобы они имели вид:
if get_item_property(..., RECORDS_DISPLAYED) > 0 then
т.е. заменить "> 1" на "> 0"
А после этого перекомпилировать APPCORE.pll, то Экспорт будет доступен и будет работать из любых блоков!
Но конечно же, это unsupported.
Однако, если выбирать между здравым смыслом и unsupported...
Как всё-таки жаль, что бывает нужно выбирать между здравым смыслом и unsupported!
Напоследок еще пара аргументов в защиту здравого смысла:
1. Как часто в патчах изменяется APPCORE.pll? Вопрос скорее риторический, ибо происходить это должно крайне редко.
2. Ну даже если она поменялась, самое худшее что случится, так это то, что пункт меню "Экспорт" перестанет быть доступным из однозаписных блоков.
пятница, 17 октября 2008 г.
Вопросы безопасности
То что в ОЕБС для ведения пользователей не используются встроенные возможности СУБД Oracle всегда казалось подозрительным.
Но что на столько!!!
Желающие могут убедиться сами.
Достаточно загуглить:
oracle.apps.fnd.security.WebSessionManagerProc.decrypt
среда, 15 октября 2008 г.
Внешнее соединение
Человек старой закалки (кто видел СУБД Oracle версии меньше чем 9) на вопрос о внешнем соединении в SQL запросе уверенно ответит, что где то нужно поставить "плюсик".
И это правда.
Не смотря на появившийся в 9-й версии синтаксис ANSI для внешних соединений, "плюсик" привычнее и роднее. Вот, только, не всегда помнишь куда ж его нужно поставить. Собственно далее идет памятка о постановке "плюсика" во внешнем соединении.
Предположим есть у нас две таблицы: FND_USER и HR_EMPLOYEES. Вообще-то, HR_EMPLOYEES это view, но пусть для упрощения немного побудет таблицей. Соединяются они по столбцу EMPLOYEE_ID, который присутствует в обеих. Причем в HR_EMPLOYEES это еще и первичный ключ, а в FND_USER этот столбец не является обязательным.
Соединение этих таблиц в запросе выглядит так:
SELECT fu.*
,he.*
FROM hr_employees he
,fnd_user fu
WHERE he.employee_id = fu.employee_id
А теперь вопрос.
Где же ставить "плюсик"?
Ответ оказывается не однозначным и всё зависит от того, что мы хотим получить в запросе.
Допустим, нам нужно для ряда пользователей (скажем, имя которых начинается с 'S') определить их Фамилию Имя Отчество. Информация о ФИО содержится в столбце HR_EMPLOYEES.full_name
В этом случае запрос будет выглядеть так:
SELECT fu.user_name
,he.full_name
FROM hr_employees he
,fnd_user fu
WHERE he.employee_id(+) = fu.employee_id
AND fu.user_name LIKE 'S%'
Правило гласящее о том, что "плюсик" ставится с той стороны где может отсутствовать значение, иногда вводит в заблуждение.
Ведь в таблице HR_EMPLOYEES столбец employee_id является первичным ключом, и значение в нем отсутствовать не может в принципе. Однако "плюсик" нужно поставить именно с этой стороны.
В чём же дело.
В данном случае, мы говорим о том, что в указанной постановке задачи (для пользователя найти ФИО) таблица FND_USER "главнее". Мы изначально имеем дело со списком пользователей, для части из которых, нужно найти дополнительную информацию (ФИО), если она существует.
Почему она может не существовать?
Да потому, что у пользователя в таблице FND_USER может отсутствовать значение в столбце EMPLOYEE_ID. Значит для ряда записей в FND_USER (где employee_id IS NULL) в HR_EMPLOYEES можно ничего и не искать. Именно поэтому "плюсик" ставится со стороны таблицы HR_EMPLOYEES
Кстати.
Аналогом этого запроса является:
SELECT fu.user_name
,(SELECT he.full_name
FROM hr_employees he
WHERE he.employee_id = fu.employee_id)
FROM fnd_user fu
WHERE fu.user_name LIKE 'S%'
В таком виде наиболее очевидно, что справочник пользователей "главнее". Ведь FND_USER единственная таблица во фразе FROM. А информация о ФИО вытаскивается подзапросом, который либо её найдет, либо нет.
Основным минусом такого запроса является то, что подзапрос позволяет вернуть только одно значение. А ведь нас могло бы интересовать не только full_name, но и employee_num, creation_date и т.д.
Другая ситуация.
Для ряда сотрудников, у которых фамилия начинается на 'А' нужно найти имена пользователей, с которыми они работают в системе. Имена пользователей содержатся в столбце FND_USER.user_name.
В этом случае запрос будет выглядеть так:
SELECT fu.user_name
,he.full_name
FROM hr_employees he
,fnd_user fu
WHERE he.employee_id = fu.employee_id(+)
AND he.full_name LIKE 'А%'
Запрос вроде похожий, однако "плюсик" переехал к таблице FND_USER.
В данном случае, мы говорим о том, что в указанной постановке задачи (для ряда сотрудников найти user_name) таблица HR_EMPLOYEES "главнее". Мы изначально имеем дело со списком сотрудников, для части из которых, нужно найти дополнительную информацию (user_name), если она существует.
Почему она может не существовать?
Да потому, что для сотрудника из таблицы HR_EMPLOYEES может отсутствовать запись в таблице FND_USER с соответствующим значением столбца EMPLOYEE_ID. Значит для ряда записей в HR_EMPLOYEES мы ничего не найдем в FND_USER. Именно поэтому "плюсик" ставится со стороны таблицы FND_USER.
Однако и это еще не всё.
А что если мы хотим получить полный список и сотрудников, и пользователей зарегистрированных в системе. Теперь мы уже знаем, что для ряда сотрудников могут быть не найдены пользователи, а для ряда пользователей могут быть не найдены сотрудники. Т.е. "плюсик" нужен как бы с обеих сторон. Однако такой синтаксис команды не поддерживается.
Существуют разные способы переписать этот запрос по другому(без внешнего соединения), однако наша цель до конца разобраться именно во внешнем соединении.
Для реализации такого запроса необходимо полное внешнее соединение (FULL OUTER JOIN). И в этом случае придется воспользоваться синтаксисом ANSI.
А выглядеть это будет так:
SELECT fu.user_name
,he.full_name
FROM hr_employees he FULL OUTER JOIN fnd_user fu
ON he.employee_id = fu.employee_id
Подводя итоги, можно сказать следующее.
Если речь не идет о полном внешнем соединении, то нужно понять какая из таблиц является главной, а какая дополняющей. "Плюсик" в условии соединения таблиц (фраза WHERE) ставим со стороны дополняющей таблицы.
Ну и напоследок, чтобы окончательно запутаться.
А что если в запросе больше чем две таблицы? Допустим нам нужно для текущего пользователя (функция FND_GLOBAL.user_id) найти номер рабочего телефона сотрудника.
Информация о телефонах хранится в таблице PER_PHONES. Эта таблица связывается с HR_EMPLOYEES по условию:
per_phones.parent_table = 'PER_ALL_PEOPLE_F'
AND per_phones.parent_id = hr_employees.employee_id
Но при этом очевидно, что не у всех сотрудников есть записи о телефонах.
Итоговый запрос будет выглядеть так:
SELECT fu.user_name
,he.full_name
,pp.phone_number
FROM fnd_user fu
,hr_employees he
,per_phones pp
WHERE /* Соединяем fnd_user и hr_employees */
he.employee_id(+) = fu.employee_id
/* Соединяем hr_employees и per_phones */
AND pp.parent_id(+) = he.employee_id
AND pp.parent_table(+) = 'PER_ALL_PEOPLE_F'
/* Прочие ограничения */
AND pp.phone_type(+) = 'W1' -- рабочий телефон
AND SYSDATE BETWEEN pp.date_from(+) AND NVL(pp.date_to(+), SYSDATE) -- актуальная запись
AND fu.user_id = FND_GLOBAL.user_id -- для текущего пользователя
Отметим следующее.
Таблицы соединяем попарно.
В каждой паре определяем главную и дополняющую таблицу.
В паре fnd_user и hr_employees главная - fnd_user, поэтому "плюсик" со стороны hr_employees.
В паре hr_employees и per_phones главная hr_employees, поэтому "плюсик" ставится со стороны per_phones и что особенно важно "плюсик" не ставится со стороны hr_employees.
В прочих условиях "плюсик" ставится для тех таблиц, которые хоть где-нибудь были дополнительными и не ставится у главной таблицы запроса.
понедельник, 13 октября 2008 г.
Поиск полномочий для запуска параллельной программы
Если нужно выполнить параллельную программу, запускать которую еще не доводилось,
то для начала не плохо было бы понять из каких полномочий она доступна.
Вот болванка запроса для этих целей.
Отметим, что имя параллельной программы нужно задать дважды:
SELECT fr.responsibility_name
,(SELECT fa.application_name
FROM fnd_application_vl fa
WHERE fa.application_id = fr.application_id) AS application_name
,fr.responsibility_id
,fr.application_id
FROM fnd_responsibility_vl fr
WHERE SYSDATE BETWEEN fr.start_date AND NVL(fr.end_date, SYSDATE)
AND (fr.group_application_id, fr.request_group_id) IN (
SELECT frgu.application_id
,frgu.request_group_id
FROM fnd_request_group_units frgu
WHERE (frgu.request_unit_type = 'P' -- Program
AND
(frgu.unit_application_id, frgu.request_unit_id) IN (
SELECT fcp.application_id
,fcp.concurrent_program_id
FROM fnd_concurrent_programs_vl fcp
WHERE fcp.user_concurrent_program_name LIKE '%'
)
)OR
(frgu.request_unit_type = 'A' -- Application
AND frgu.unit_application_id IN (
SELECT fcp.application_id
FROM fnd_concurrent_programs_vl fcp
WHERE fcp.user_concurrent_program_name LIKE '%'
)
)
)
ORDER BY fr.responsibility_name
четверг, 9 октября 2008 г.
FND_CONCURRENT_REQUESTS
Параллельные программы в ОЕБС (concurrent programs) - замечательная особенность системы. Любые отчеты и процедуры обработки данных всегда оформляются, а затем и выполняются используя единый механизм. Так ведь мало того, всё это еще и протоколируется.
Поэтому, говоря о параллельных программах, мы можем ответить не только на вопросы популярной телепередачи, но и понять кто, сколько и во сколько :-)
Анализ запуска параллельных программ может быть полезен для ответов на многие вопросы.
Ну например:
1) Списки наиболее часто используемых отчетов
2) Списки наиболее медленно работающих отчетов
3) Какие программы работали в заданный промежуток времени.
4) Кто и что "забило" очередь конкарент менеджера
и т.д.
Специалисты поддержки разбирая жалобы пользователей на плохую работу того или иного отчета, могут предварительно проанализировать когда, с какими параметрами, из под каких полномочий этот пользователь запускал отчет (да и запускал ли?).
И всё это счастье доступно в одной таблице, имя которой FND_CONCURRENT_REQUESTS.
Вот заготовка запроса к этой таблице.
Во фразе WHERE подготовлены (закомментарены) наиболее популярные виды выборок: по пользоватедю, по периоду, по параллельной программе.
SELECT
fcr.request_id
,(SELECT fcp.user_concurrent_program_name
FROM apps.fnd_concurrent_programs_vl fcp
WHERE fcp.application_id = fcr.program_application_id
AND fcp.concurrent_program_id = fcr.concurrent_program_id
) AS concurrent_name
,fcr.actual_start_date
,fcr.actual_completion_date
,fcr.completion_text
,(SELECT fu.user_name FROM applsys.fnd_user fu
WHERE fu.user_id = fcr.requested_by
) AS user_name
,(SELECT fr.responsibility_name
FROM apps.fnd_responsibility_vl fr
WHERE fr.application_id = fcr.responsibility_application_id
AND fr.responsibility_id = fcr.responsibility_id
) AS responsibility_name
,fcr.argument_text
,fcr.*
FROM applsys.fnd_concurrent_requests fcr
WHERE 1=1
/* Start. Фильтр по пользователю */
-- AND fcr.requested_by IN (
-- SELECT fu.user_id
-- FROM applsys.fnd_user fu
-- WHERE fu.user_name LIKE '%'
-- )
/* End. Фильтр по пользователю */
/* Start. Фильтр по периоду */
-- AND fcr.actual_start_date >= TO_DATE('01.01.2008', 'DD.MM.YYYY')
-- AND fcr.actual_completion_date < TO_DATE('31.12.2008', 'DD.MM.YYYY')
/* End. Фильтр по периоду */
/* Start. Фильтр по concurrent_name */
-- AND (fcr.program_application_id, fcr.concurrent_program_id) IN (
-- SELECT fcp.application_id, fcp.concurrent_program_id
-- FROM apps.fnd_concurrent_programs_vl fcp
-- WHERE fcp.user_concurrent_program_name LIKE '%'
-- )
/* End. Фильтр по concurrent_name */
/* Start. Фильтр по работающим сейчас конкарентам */
-- AND fcr.phase_code = 'R'
/* End. Фильтр по работающим сейчас конкарентам */
ORDER BY fcr.request_id DESC