воскресенье, 26 апреля 2009 г.

Не по теме: Подвиг

С детства люблю все фильмы Марка Захарова, в том числе и про барона Мюнхгаузена.
И никогда не считал барона выдумщиком.

Ну вот смотрите. Визит к стоматологу очень для многих является героическим поступком.
Вот и в моем календаре имеется запись:
Понедельник, 09:00. Подвиг

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

ОЕБС Форум 2009

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

Организаторам и участникам придется сегодня доказать что они к этому готовы.
Уложить 5-6 докладов в 1,5 часовые секции - это результат.

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

Сторно журналов ГК. Продолжение

В продолжении темы сторнирования журналов ГК.

Постановка задачи.
Получить список "честных" журналов ГК за период.
Что значит честных?
Предположим у нас 5 журналов. И пятый журнал оказался ошибочным (не важно по какой причине). Мы его сторнировали. Т.е. появился шестой журнал. Пятый и шестой - взаимно гасят друг друга и нам более не интересны. А вот первые 4 - это и есть список честных журналов. Из него исключены сторнированные журналы и то, чем их сторнировали.
Как получить такой список?

По идее, нам нужны несторнированные журналы (accrual_rev_status IS NULL). Однако под такое условие попадает и последний шестой журнал, а он - лишний.

Можно добавить условие, что журналы не должы быть сторнирующими (reversed_je_header_id IS NULL). И для нашего примера с шестью журналами этого достаточно. Однако нельзя исключать ситуацию, когда пользователь ошибся выполняя операцию сторнирования и сторнировал не то что нужно. А потом, поняв ошибку, сделал сторно на сторно. Т.е. появился 7-й журнал. И он нам нужен! 5-й и 6-ой гасят друг друга, а 7-й - "настоящий" и он нам нужен.

Здесь сделаем допущение, что из связки 5-6-7 журналы, нам нужен последний - 7-й. Хотя можно было бы говорить и об исходном - 5-ом.

Т.к. сторно на сторно можно делать не единожды, то в общем случае получается такая картина. Если есть цепочка сторнирования: исходный журнал(1)->сторно(2)->сторно(3)->сторно(4)->сторно(5)->сторно(6)-сторно(7)-... то нас в ней интересует последний журнал, но только в том случае, если он нечетный в цепочке.

Результирующий список честных журналов за период получается как то так.


SELECT * /* Обычные несторнированные и несторнирующие журналы */
FROM gl_je_headers gjh
WHERE gjh.period_name = 'ЯНВ-2009'
AND gjh.accrual_rev_status IS NULL
AND gjh.reversed_je_header_id IS NULL
UNION ALL
SELECT * /* дополнительные сторнировочные журналы */
FROM gl_je_headers gjh
WHERE gjh.period_name = 'ЯНВ-2009'
AND gjh.accrual_rev_status IS NULL
AND gjh.reversed_je_header_id IS NOT NULL
AND MOD((SELECT COUNT(*)
FROM gl_je_headers gjh2
WHERE gjh.period_name = 'ЯНВ-2009'
START WITH gjh2.je_header_id = gjh.je_header_id
CONNECT BY gjh2.je_header_id = PRIOR gjh2.reversed_je_header_id)
,2) <> 0

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

четверг, 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.