четверг, 9 апреля 2009 г.

Складские организации. Статус закрытия периодов

Как правило в системе есть несколько операционных единиц (ведь в мелких конторах ОЕБС не внедряется), а в каждой из них несколько складских организаций, причем в разных ORG_ID может быть разное количество складских организаций.

В отличии от Дебиторов/Кредиторов, где период закрывается сразу на всю операционную единицу, в Запасах периоды нужно закрывать в каждой конкретной складской организации.

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

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


SELECT t.period_name
-- Вместо 1,2,3 нужно подставить реальные значения.
-- полный список складских ORG_ID:
-- SELECT DISTINCT operating_unit FROM org_organization_definitions
,MAX(DECODE(t.org_id, 1, t.NAME, NULL)) AS ORG_ID_1
,MAX(DECODE(t.org_id, 2, t.NAME, NULL)) AS ORG_ID_2
,MAX(DECODE(t.org_id, 3, t.NAME, NULL)) AS ORG_ID_3
-- и так далее, для каждой ORG_ID
FROM (
SELECT oap.period_name
,ood.operating_unit AS org_id
,DECODE(oap.open_flag, 'N','Закрыто', 'Y','Открыто', oap.open_flag)
|| ' - ' || ood.organization_code||' '||ood.organization_name AS NAME
,oap.period_start_date AS start_date
,oap.schedule_close_date AS end_date
,rank() OVER (
PARTITION BY oap.period_name, ood.operating_unit
ORDER BY DECODE(oap.open_flag, 'N','Закрыто', 'Y','Открыто', oap.open_flag)
|| ' - ' || ood.organization_code||' '||ood.organization_name
) AS row_num
FROM org_organization_definitions ood
,org_acct_periods oap
WHERE oap.organization_id = ood.organization_id
-- диапазон дат можно задавать в несколько периодов
AND oap.period_start_date >= TO_DATE('01.01.2009', 'DD.MM.YYYY')
AND oap.schedule_close_date <= TO_DATE('31.03.2009', 'DD.MM.YYYY')
-- можно задать конкретный тип периода (если используется несколько)
--AND oap.period_set_name = ''
-- откинем "левые" органзицаии, типа мастер организации позиций и закрытые
AND ood.organization_code <> '000'
AND ood.disable_date IS NULL
ORDER BY 4,5,2,3
) t
GROUP BY t.start_date, t.end_date, t.period_name, t.row_num
ORDER BY t.start_date, t.end_date, t.period_name, t.row_num

вторник, 7 апреля 2009 г.

ОЕБС. Взгляд изнутри

А ведь для тех, кто не видел ОЕБС, будет весьма любопытно как "оно" устроено изнутри.


SQL> SELECT o.object_type, COUNT(*)
2 FROM dba_objects o
3 ,applsys.fnd_oracle_userid fou
4 WHERE o.owner = fou.oracle_username
5 AND fou.oracle_username NOT LIKE 'XX%'
6 GROUP BY ROLLUP (o.object_type)
7
SQL> /

OBJECT_TYPE COUNT(*)
------------------ ----------
DATABASE LINK 6
EVALUATION CONTEXT 28
FUNCTION 27
INDEX 38740
INDEX PARTITION 1124
INDEXTYPE 4
JAVA CLASS 1102
JAVA RESOURCE 42
JAVA SOURCE 4
LIBRARY 2
LOB 596
MATERIALIZED VIEW 386
OPERATOR 5
PACKAGE 40756
PACKAGE BODY 39725
PROCEDURE 30
QUEUE 141
RULE 29
RULE SET 59
SEQUENCE 9023
SYNONYM 30325
TABLE 21835
TABLE PARTITION 500
TRIGGER 3523
TYPE 792
TYPE BODY 30
VIEW 25767
214601

В версии 11.5.10 легко можно найти пару сотен тысяч объектов БД.
И это без MRC.

понедельник, 6 апреля 2009 г.

Сторно журналов ГК

Журналы ГК можно сторнировать.
Более того, можно делать сторно 'на сторно'.
И нет ничего удивительного в том, что можно делать сторно 'на сторно на сторно'.

Т.е. для того, чтобы проследить всю цепочку сторнирования журнала, нужен "деревянный" запрос.
Вот пример.


SQL> SELECT LEVEL
2 ,gjh.je_header_id
3 ,gjh.reversed_je_header_id
4 ,gjh.accrual_rev_status
5 ,gjh.accrual_rev_je_header_id
6 FROM gl_je_headers gjh
7 START WITH gjh.je_header_id = 219693 AND gjh.accrual_rev_status = 'R'
8 CONNECT BY PRIOR gjh.je_header_id = gjh.reversed_je_header_id
9 /

LEVEL JE_HEADER_ID REVERSED_JE_HEADER_ID ACCRUAL_REV_STATUS ACCRUAL_REV_JE_HEADER_ID
---------- ------------ --------------------- ------------------ ------------------------
1 219693 R 219694
2 219694 219693 R 219695
3 219695 219694 R 219697
4 219697 219695 219695

Столбец ACCRUAL_REV_STATUS.
Значение 'R' говорит о том, этот журнал был сторнирован
Значение NULL - журнал не был сторнирован

Столбец REVERSED_JE_HEADER_ID.
Значение NULL говорит о том, что это исходный в нашей цепочке журнал. Значение отличное от NULL - это JE_HEADER_ID того журнала, который был сторнирован этим журналом

Столбец ACCRUAL_REV_JE_HEADER_ID.
Если журнал был сторнирован (ACCRUAL_REV_STATUS='R'), то это JE_HEADER_ID того журнала, который сторнировал текущий. Но только если текущий журнал был сторнирован. Как видим у последнего несторнированного журнала ACCRUAL_REV_JE_HEADER_ID не пустой, а значение совпадает с REVERSED_JE_HEADER_ID.

пятница, 13 февраля 2009 г.

Полезные клавиши

Многие считают плохим интерфейс экранных форм в ОЕБС.
Имеются ввиду формы сделанные на Oracle Forms.

Заблуждение.

Интерфейс не плохой, он другой.
Им просто нужно уметь пользоваться.

Shift-F5 - Дублировать поле (конкретные клавиши на клавиатуре могут отличаться в зависимости от настроек сервера форм. Полный список: Справка->Использование клавиатуры)
Если нужно ввести несколько похожих записей, то это просто незаменимая клавиша.
Позволяет вставить в текущее поле значение из предыдущей записи.

Shift-F6 - Дублировать запись
Позволяет дублировать все поля предыдущей записи.
К сожалению, мало где работает, из-за лени/квалификации конкретных разработчиков форм. При разработке форму нужно чуть-чуть подкрутить для корректного дублирования записи.

Ctrl-E - Редактировать
Значение поля слишком большое и на экране всё не помещается?
Открываем редактор и смотрим.

Чисто ОЕБС-овые штучки:
Ctrl-L - Список значений
В Навигаторе не помните в каком меню/подменю запрятана нужная форма?
Открываем Список значений и смотрим на все доступные формы этих полномочий.

Досье или Папки
Суперфункциональность.
Позволяет настроить внешний вид данных (количество, порядок, размеры и заголовки столбцов), а также сортировку и фильтр данных.
Надоело при вводе счетов фактур пролистывать колонки до "Метода платежа"?
Можно создать несколько досье в этой форме для более удобной работы, например:
- Предоплаты.
Выводятся только предоплаты и колонки нужные для заполнения. Остальные колонки скрываем
- Стандартные счета-фактуры
И т.д.

Интерфейс не плохой, он другой.
Им просто нужно уметь пользоваться.

четверг, 12 февраля 2009 г.

FND: Override Directory

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

Если нужно поэкспериментировать с формой, так чтобы это не было заметно остальным, то можно выставить на уровне пользователя профиль "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. Ну даже если она поменялась, самое худшее что случится, так это то, что пункт меню "Экспорт" перестанет быть доступным из однозаписных блоков.