DBA Data[Home] [Help]

PACKAGE BODY: APPS.HRI_OPL_SUPH_HST_INC

Source


1 PACKAGE BODY hri_opl_suph_hst_inc AS
2 /* $Header: hrioshhi.pkb 115.15 2003/05/27 14:45:06 jtitmas noship $ */
3 
4 /******************************************************************************/
5 /*                                                                            */
6 /* OUTLINE / DEFINITIONS                                                      */
7 /*                                                                            */
8 /* CHAINS                                                                     */
9 /* ======                                                                     */
10 /* A chain is defined for an employee as a list starting with the employee    */
11 /* which contains their supervisor, and successive higher level supervisors   */
12 /* finishing with the highest level (overall) supervisor.                     */
13 /*                                                                            */
14 /* Each chain is valid for the length of time it describes the supervisor     */
15 /* hierarchy between the employee it is defined for and the overall           */
16 /* supervisor in the hierarchy.                                               */
17 /*                                                                            */
18 /* The supervisor hierarchy table implements each link in the chain as a      */
19 /* row with the employee the chain is defined for as the subordinate. The     */
20 /* absolute levels refer to absolute positions within the overall hierarchy   */
21 /* whereas the relative level refers to the difference in the absolute levels */
22 /* for the row.                                                               */
23 /*                                                                            */
24 /* When an employee changes supervisor, their chain must change since their   */
25 /* immediate supervisor is different. However, the chains of all that         */
26 /* employee's subordinates must also change because a chain consists of       */
27 /* each higher level supervisor up to and including the overall supervisor.   */
28 /*                                                                            */
29 /* LEAF NODES                                                                 */
30 /* ==========                                                                 */
31 /* If a person is supervised but is not themselves a supervisor they are      */
32 /* termed a "leaf node". The supervisor hierarchy history table also tracks   */
33 /* whether the chain owner is a leaf node or not. Terminated people do not    */
34 /* have a leaf node status.                                                   */
35 /*                                                                            */
36 /* IMPLEMENTATION LOGIC                                                       */
37 /* ====================                                                       */
38 /* The supervisor hierarchy history table is populated by carrying out the    */
39 /* following steps:                                                           */
40 /*                                                                            */
41 /*  1) Empty out existing table                                               */
42 /*                                                                            */
43 /*  2) Loop through a view containing supervisor changes. Supervisor changes  */
44 /*     are:                                                                   */
45 /*             - New hires with a supervisor                                  */
46 /*             - Switching supervisor (before separation)                     */
47 /*             - Supervisors (including "leaf nodes") who terminate           */
48 /*             - Subordinates of supervisors which are finally processed      */
49 /*               without the subordinate being updated                        */
50 /*             - Plus the initialization which is done by selecting all the   */
51 /*               top supervisors at the start of the collection               */
52 /*                                                                            */
53 /*     Processing the changes sequentially in reverse date order:             */
54 /*                                                                            */
55 /*    i) Calculate new chain for the person who has changed supervisor        */
56 /*   ii) Get the end date for the chain (held in global, or if not then the   */
57 /*       chain owner's termination date, or if not then end of time)          */
58 /*  iii) Insert new chain into hierarchy table                                */
59 /*   iv) Store the same information in a global data structure                */
60 /*    v) If the supervisor change is not the first record of the person then  */
61 /*       propagate the change down to all that person's subordinates making   */
62 /*       use of the data structure to avoid recalculating the same            */
63 /*       information twice                                                    */
64 /*                                                                            */
65 /*  3) Global structures are used to:                                         */
66 /*                                                                            */
67 /*    i) Bulk fetch the main loop                                             */
68 /*   ii) Bulk insert the chains into the hierarchy table                      */
69 /*  iii) Store information about the current chain being processed            */
70 /*   iv) Keep a note of which chains have been processed on a particular      */
71 /*       date to avoid re-processing the same information                     */
72 /*    v) Keep a note of the date each chain starts, so that the next time     */
73 /*       a chain is processed (on an earlier date) the end date is known      */
74 /*   vi) Store the terminated assignment status types so that it is quick to  */
75 /*       find out which are invalid at insert time                            */
76 /*  vii) Keep track of whether a supervisor is a leaf node                    */
77 /*                                                                            */
78 /*  4) Errors encountered which are specifically handled arise from data      */
79 /*     inconsistencies:                                                       */
80 /*                                                                            */
81 /*    i) Loops in supervisor chain - error is output to log file with the     */
82 /*       date and assignment in looped chain                                  */
83 /*   ii) Overlapping assignment records - these mean a unique constraint      */
84 /*       error is encountered when inserting. This is recovered and the       */
85 /*       offending row found. An error is recorded in the log and processing  */
86 /*       continues.                                                           */
87 /*                                                                            */
88 /******************************************************************************/
89 
90 /* Information to be held for each link in a chain */
91 TYPE g_link_record_type IS RECORD
92   (business_group_id       per_all_assignments_f.business_group_id%TYPE
93   ,person_id               per_all_assignments_f.person_id%TYPE
94   ,assignment_id           per_all_assignments_f.assignment_id%TYPE
95   ,asg_status_id           per_all_assignments_f.assignment_status_type_id%TYPE
96   ,invalid_flag            VARCHAR2(30)
97   ,primary_asg_flag        per_all_assignments_f.primary_flag%TYPE
98   ,leaf_node               VARCHAR2(30)
99   ,end_date                per_all_assignments_f.effective_end_date%TYPE);
100 
101 /* Table type to hold information about the current chain */
102 TYPE g_chain_type IS TABLE OF g_link_record_type INDEX BY BINARY_INTEGER;
103 
104 /* Simple table types */
105 TYPE g_date_tab_type IS TABLE OF DATE INDEX BY BINARY_INTEGER;
106 TYPE g_number_tab_type IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
107 TYPE g_varchar2_tab_type IS TABLE OF VARCHAR2(30) INDEX BY BINARY_INTEGER;
108 
109 /* PLSQL table of terminated assignment status types */
110 g_term_asg_statuses        g_varchar2_tab_type;
111 
112 /* PLSQL table of end dates */
113 g_final_date_tab           g_date_tab_type;
114 
115 /* PLSQL tables for main bulk fetch */
116 g_fetch_asg_id             g_number_tab_type;
117 g_fetch_strt_dt            g_date_tab_type;
118 g_fetch_end_dt             g_date_tab_type;
119 g_fetch_bgr_id             g_number_tab_type;
120 g_fetch_psn_id             g_number_tab_type;
121 g_fetch_sup_id             g_number_tab_type;
122 g_fetch_prev_sup_id        g_number_tab_type;
123 g_fetch_ast_id             g_number_tab_type;
124 g_fetch_pos_id             g_number_tab_type;
125 g_fetch_chng_dt            g_date_tab_type;
126 g_fetch_evt_code           g_varchar2_tab_type;
127 g_fetch_term_dt            g_date_tab_type;
128 g_fetch_fprc_dt            g_date_tab_type;
129 
130 /* PLSQL table of top level supervisor checks */
131 g_top_level_check          g_date_tab_type;
132 
133 /* PLSQL table of dates assignments have already been processed */
134 /* Indexed by assignment id */
135 g_assgnmnts_prcssd         g_date_tab_type;
136 
137 g_collect_from_date        DATE;   -- Collection date range start
138 g_collect_to_date          DATE;   -- Collection date range end
139 
140 /* Information about current chain within the hierarchy */
141 g_crrnt_chain              g_chain_type;    -- Current chain
142 g_crrnt_chain_start_date   DATE;            -- Current chain start date
143 g_crrnt_chain_end_date     DATE;            -- Current chain end date
144 g_crrnt_chain_owner_lvl    PLS_INTEGER;     -- Chain owner's level within chain
145 g_crrnt_chain_orphan_flag  VARCHAR2(1);     -- Whether current chain is orphaned
146 g_crrnt_chain_top_pos_id   NUMBER;          -- Current top node period of service
147 
148 /* Set to true to output to a concurrent log file */
149 g_conc_request_flag       BOOLEAN := FALSE;
150 
151 /* Number of rows bulk processed at a time */
152 g_chunk_size              PLS_INTEGER := 2000;
153 g_chain_chunk_size        PLS_INTEGER := 500;
154 g_chain_transactions      PLS_INTEGER := 0;
155 
156 /* Stores end of time value */
157 g_end_of_time             DATE := hr_general.end_of_time;
158 
159 /* Stores current time value */
160 g_current_time            DATE;
161 g_current_date            DATE := TRUNC(SYSDATE);
162 
163 /******************************************************************************/
164 /* Inserts row into concurrent program log when the g_conc_request_flag has   */
165 /* been set to TRUE, otherwise does nothing                                   */
166 /******************************************************************************/
167 PROCEDURE output(p_text  VARCHAR2)
168   IS
169 
170 BEGIN
171 
172 /* Write to the concurrent request log if called from a concurrent request */
173   IF (g_conc_request_flag = TRUE) THEN
174 
175    /* Put text to log file */
176      fnd_file.put_line(FND_FILE.log, p_text);
177 
178   END IF;
179 
180 END output;
181 
182 /******************************************************************************/
183 /* Load g_term_asg_statuses with the assignment status types which are        */
184 /* terminated                                                                 */
185 /******************************************************************************/
186 PROCEDURE init_term_per_system_status IS
187 
188   CURSOR term_asg_statuses_csr IS
189     SELECT ast.assignment_status_type_id
190     FROM   per_assignment_status_types ast
191     WHERE  ast.per_system_status = 'TERM_ASSIGN';
192 
193 BEGIN
194 
195   FOR term_asg_status in term_asg_statuses_csr LOOP
196 
197     -- load g_term_asg_statuses element at index position of value returned
198     g_term_asg_statuses(term_asg_status.assignment_status_type_id) := 'Y';
199 
200   END LOOP;
201 
202 END init_term_per_system_status;
203 
204 /******************************************************************************/
205 /* Return whether the assignment status type id is terminated                 */
206 /******************************************************************************/
207 FUNCTION get_inv_flag_status(p_assignment_status_type_id
208                 IN per_assignment_status_types.assignment_status_type_id%TYPE)
209          RETURN VARCHAR2 IS
210 
211 BEGIN
212 
213   -- returns Y if the element exists otherwise a NO_DATA_FOUND exception
214   -- will be raised
215   RETURN(g_term_asg_statuses(p_assignment_status_type_id));
216 
217 EXCEPTION
218   WHEN NO_DATA_FOUND THEN
219 
220    -- element doesn't exist, return N
221    RETURN('N');
222 
223 END get_inv_flag_status;
224 
225 /******************************************************************************/
226 /* Checks whether a person is a leaf node on a given date                     */
227 /******************************************************************************/
228 FUNCTION is_a_leaf_node( p_person_id   IN NUMBER
229                        , p_on_date     IN DATE )
230            RETURN VARCHAR2 IS
231 
232 /* A supervisor is a leaf node if they have no non-terminated direct */
233 /* subordinates. Hint seems obvious but on GSIAPDEV it was required */
234   CURSOR is_a_leaf_csr IS
235   SELECT /*+ index(ast PER_ASSIGNMENT_STATUS_TYPE_PK) use_nl(asg ast) */
236    'N'
237   FROM per_all_assignments_f  asg,
238        per_assignment_status_types ast
239   WHERE asg.supervisor_id = p_person_id
240   AND asg.assignment_type = 'E'
241   AND asg.primary_flag = 'Y'
242   AND ast.assignment_status_type_id = asg.assignment_status_type_id
243   AND ast.per_system_status <> 'TERM_ASSIGN'
244   AND p_on_date BETWEEN asg.effective_start_date
245                 AND asg.effective_end_date;
246 
247   l_is_a_leaf_flag    VARCHAR2(1);
248 
249 BEGIN
250 
251 /* Check if the supervisor has any non terminated subordinates */
252   OPEN is_a_leaf_csr;
253   FETCH is_a_leaf_csr INTO l_is_a_leaf_flag;
254   CLOSE is_a_leaf_csr;
255 
256   RETURN NVL(l_is_a_leaf_flag, 'Y');
257 
258 END is_a_leaf_node;
259 
260 /******************************************************************************/
261 /* Inserts row into global temporary table                                    */
262 /******************************************************************************/
263 PROCEDURE insert_row( p_sup_business_group_id   IN NUMBER
264                     , p_sup_person_id           IN NUMBER
265                     , p_sup_assignment_id       IN NUMBER
266                     , p_sup_asg_status_id       IN NUMBER
267                     , p_sup_level               IN NUMBER
268                     , p_sup_inv_flag            IN VARCHAR2
269                     , p_sub_business_group_id   IN NUMBER
270                     , p_sub_person_id           IN NUMBER
271                     , p_sub_assignment_id       IN NUMBER
272                     , p_sub_asg_status_id       IN NUMBER
273                     , p_sub_primary_asg_flag    IN VARCHAR2
274                     , p_sub_level               IN NUMBER
275                     , p_sub_relative_level      IN NUMBER
276                     , p_sub_inv_flag            IN VARCHAR2
277                     , p_effective_start_date    IN DATE
278                     , p_effective_end_date      IN DATE
279                     , p_orphan_flag             IN VARCHAR2
280                     , p_sub_leaf_flag           IN VARCHAR2 ) IS
281 
282 BEGIN
283 
284   BEGIN
285 
286     INSERT INTO hri_cs_suph
287       (sup_business_group_id
288       ,sup_person_id
289       ,sup_assignment_id
290       ,sup_assignment_status_type_id
291       ,sup_level
292       ,sup_invalid_flag_code
293       ,sub_business_group_id
294       ,sub_person_id
295       ,sub_assignment_id
296       ,sub_assignment_status_type_id
297       ,sub_primary_asg_flag_code
298       ,sub_level
299       ,sub_relative_level
300       ,sub_invalid_flag_code
301       ,orphan_flag_code
302       ,sub_leaf_flag_code
303       ,effective_start_date
304       ,effective_end_date)
305         VALUES
306               (p_sup_business_group_id
307               ,p_sup_person_id
308               ,p_sup_assignment_id
309               ,p_sup_asg_status_id
310               ,p_sup_level
311               ,p_sup_inv_flag
315               ,p_sub_asg_status_id
312               ,p_sub_business_group_id
313               ,p_sub_person_id
314               ,p_sub_assignment_id
316               ,p_sub_primary_asg_flag
317               ,p_sub_level
318               ,p_sub_relative_level
319               ,p_sub_inv_flag
320               ,p_orphan_flag
321               ,p_sub_leaf_flag
322               ,p_effective_start_date
323               ,p_effective_end_date);
324 
325   EXCEPTION WHEN OTHERS THEN
326     output('Error inserting chain for:');
327     output('--: ' || to_char(p_sub_person_id) || ' between ' || to_char(p_effective_start_date) ||
328            ' and ' || to_char(p_effective_end_date));
329   END;
330 
331 END insert_row;
332 
333 /******************************************************************************/
334 /* Inserts chain from specified level                                         */
335 /******************************************************************************/
336 PROCEDURE insert_chain( p_level      IN NUMBER,
337                         p_end_date   IN DATE) IS
338 
339 BEGIN
340 
341   g_chain_transactions := g_chain_transactions + 1;
342 
343   FOR i IN 1..p_level LOOP
344 
345     insert_row
346         (p_sup_business_group_id => g_crrnt_chain(i).business_group_id
347         ,p_sup_person_id => g_crrnt_chain(i).person_id
348         ,p_sup_assignment_id => g_crrnt_chain(i).assignment_id
349         ,p_sup_asg_status_id => g_crrnt_chain(i).asg_status_id
350         ,p_sup_level => i
351         ,p_sup_inv_flag => g_crrnt_chain(i).invalid_flag
352         ,p_sub_business_group_id  => g_crrnt_chain(p_level).business_group_id
353         ,p_sub_person_id => g_crrnt_chain(p_level).person_id
354         ,p_sub_assignment_id => g_crrnt_chain(p_level).assignment_id
355         ,p_sub_asg_status_id => g_crrnt_chain(p_level).asg_status_id
356         ,p_sub_primary_asg_flag => 'Y'
357         ,p_sub_level => p_level
358         ,p_sub_relative_level => p_level - i
359         ,p_sub_inv_flag => g_crrnt_chain(p_level).invalid_flag
360         ,p_effective_start_date => g_crrnt_chain_start_date
361         ,p_effective_end_date => p_end_date
362         ,p_orphan_flag => g_crrnt_chain_orphan_flag
363         ,p_sub_leaf_flag => g_crrnt_chain(p_level).leaf_node);
364 
365   END LOOP;
366 
367 END insert_chain;
368 
369 /******************************************************************************/
370 /* End dates chain for person                                                 */
371 /******************************************************************************/
372 PROCEDURE end_date_chain( p_person_id      IN NUMBER,
373                           p_end_date       IN DATE) IS
374 
375 BEGIN
376 
377   g_chain_transactions := g_chain_transactions + 1;
378 
379   UPDATE hri_cs_suph
380   SET effective_end_date = p_end_date
381   WHERE sub_person_id = p_person_id
382   AND p_end_date BETWEEN effective_start_date AND effective_end_date;
383 
384 END end_date_chain;
385 
386 /******************************************************************************/
387 /* Processes incremental changes to the table for the current chain           */
388 /*                                                                            */
389 /* This procedure takes the current chain and change date and picks out the   */
390 /* existing date tracked chain in the table in which the change date falls.   */
391 /*                                                                            */
392 /*                                                                            */
393 /*                                                                            */
394 /******************************************************************************/
395 PROCEDURE process_chain( p_level     IN NUMBER,
396                          p_end_date  IN DATE) IS
397 
398 /* Bug 2670477 - join by person id */
399   CURSOR existing_chain_csr IS
400   SELECT
401    effective_start_date
402   ,effective_end_date
403   FROM hri_cs_suph
404   WHERE sub_person_id = g_crrnt_chain(p_level).person_id
405   AND g_crrnt_chain_start_date
406     BETWEEN effective_start_date AND effective_end_date;
407 
408 /* Bug 2670477 - join by person id */
409   CURSOR next_chain_start_csr IS
410   SELECT
411     MIN(effective_start_date)   next_chain_start_date
412   FROM hri_cs_suph
413   WHERE sub_person_id = g_crrnt_chain(p_level).person_id
414   AND effective_start_date > g_crrnt_chain_start_date;
415 
416   l_existing_chain_start      DATE;
417   l_existing_chain_end        DATE;
418   l_next_chain_start          DATE;
419   l_chain_end_date            DATE;
420 
421 BEGIN
422 
423 /* Get information about existing chain */
424   OPEN existing_chain_csr;
425   FETCH existing_chain_csr INTO l_existing_chain_start, l_existing_chain_end;
426   CLOSE existing_chain_csr;
427 
428 /* If a chain exists, take the earlier of the current and existing end dates */
429   IF (l_existing_chain_end IS NULL) THEN
430 
431   /* If there is no existing chain, check for a future dated one */
432     OPEN next_chain_start_csr;
433     FETCH next_chain_start_csr INTO l_next_chain_start;
434     CLOSE next_chain_start_csr;
435 
436   /* If there's a future dated chain, take the earlier of the current end */
440     ELSE
437   /* and one less than the future dated chain start */
438     IF (l_next_chain_start IS NULL) THEN
439       l_chain_end_date := p_end_date;
441       l_chain_end_date := LEAST(l_next_chain_start - 1, p_end_date);
442     END IF;
443 
444   ELSE
445 
446     l_chain_end_date := LEAST(l_existing_chain_end, p_end_date);
447 
448   END IF;
449 
450 /* End date existing chain if it is earlier then the current */
451   IF (l_existing_chain_start < g_crrnt_chain_start_date) THEN
452     g_chain_transactions := g_chain_transactions + 1;
453   /* End date existing chain */
454   /* Bug 2670477 - join by person id */
455     UPDATE hri_cs_suph
456     SET effective_end_date = g_crrnt_chain_start_date - 1
457     WHERE sub_person_id =
458              g_crrnt_chain(p_level).person_id
459     AND effective_start_date = l_existing_chain_start;
460 
461 /* Delete existing chain if it is the same date as the current */
462   ELSIF (l_existing_chain_start = g_crrnt_chain_start_date) THEN
463     g_chain_transactions := g_chain_transactions + 1;
464   /* Delete existing chain */
465   /* Bug 2670477 - join by person id */
466     DELETE FROM hri_cs_suph
467     WHERE sub_person_id = g_crrnt_chain(p_level).person_id
468     AND effective_start_date = l_existing_chain_start;
469 
470   END IF;
471 
472 /* Insert new chain */
473   insert_chain(p_level => p_level
474               ,p_end_date => l_chain_end_date);
475 
476 /* Remove any obsolete chain updates */
477   BEGIN
478     IF (g_final_date_tab(g_crrnt_chain(p_level).person_id) IS NOT NULL) THEN
479       g_chain_transactions := g_chain_transactions + 1;
480     /* Bug 2670477 - join by person id */
481       DELETE FROM hri_cs_suph
482       WHERE sub_person_id = g_crrnt_chain(p_level).person_id
483       AND effective_start_date > g_final_date_tab(g_crrnt_chain(p_level).person_id);
484     END IF;
485   EXCEPTION WHEN OTHERS THEN
486     null;
487   END;
488 
489 END process_chain;
490 
491 /******************************************************************************/
492 /* Returns the end date to use for an assignment                              */
493 /******************************************************************************/
494 FUNCTION get_end_date( p_index                   IN NUMBER,
495                        p_person_id               IN NUMBER,
496                        p_period_of_service_id    IN NUMBER,
497                        p_change_date             IN DATE)
498              RETURN DATE IS
499 
500   CURSOR pos_end_date_csr IS
501   SELECT actual_termination_date, final_process_date
502   FROM per_periods_of_service
503   WHERE period_of_service_id = p_period_of_service_id;
504 
505   l_return_date        DATE;
506   l_final_process      DATE;
507   l_actual_termination DATE;
508 
509 BEGIN
510 
511 /* If no end date recorded for assignment, get the termination date */
512   IF (p_person_id = g_fetch_psn_id(p_index)) THEN
513     l_actual_termination := g_fetch_term_dt(p_index);
514     l_final_process      := g_fetch_fprc_dt(p_index);
515   ELSE
516     OPEN pos_end_date_csr;
517     FETCH pos_end_date_csr INTO l_actual_termination, l_final_process;
518     CLOSE pos_end_date_csr;
519   END IF;
520 
521 /* If the change date is after the termination date and the event is */
522 /* not the leaf node termination return the final process date */
523   IF (p_change_date > l_actual_termination) THEN
524     l_return_date := NVL(l_final_process, g_end_of_time);
525 /* Otherwise return the actual termination date */
526   ELSE
527     l_return_date := NVL(l_actual_termination, g_end_of_time);
528   END IF;
529 
530 /* If the termination date is in the future return end of time */
531   IF (l_return_date > g_current_date) THEN
532     l_return_date := g_end_of_time;
533   END IF;
534 
535 /* Store final process date used */
536   g_final_date_tab(p_person_id) := l_final_process;
537 
538   RETURN l_return_date;
539 
540 END get_end_date;
541 
542 /******************************************************************************/
543 /* Tests whether the top level supervisor is new                              */
544 /******************************************************************************/
545 PROCEDURE test_top_supervisor( p_index        IN NUMBER,
546                                p_change_date  IN DATE,
547                                p_event_code   IN VARCHAR2) IS
548 
549 /***********************************************************************/
550 /* Cursor to find whether top level supervisor is new                  */
551 /***********************************************************************/
552   CURSOR top_manager_new_csr IS
553   SELECT 'N'
554   FROM per_all_assignments_f asg
555   WHERE asg.supervisor_id = g_fetch_sup_id(p_index)
556   AND asg.assignment_type = 'E'
557   AND asg.primary_flag = 'Y'
558   AND p_change_date - 1
559          BETWEEN asg.effective_start_date AND asg.effective_end_date;
560 
561   l_top_manager_new_flag   VARCHAR2(1);
562   l_end_date               DATE;
563 
564 BEGIN
565 
566 /* Catch errors accessing the global table in a PL/SQL block */
567   BEGIN
568   /* Raise an error if there is no record of a check (automatic) or */
572     END IF;
569   /* if the last check was later than the current change date (explicit) */
570     IF (g_top_level_check(g_fetch_sup_id(p_index)) > p_change_date) THEN
571       RAISE NO_DATA_FOUND;
573   EXCEPTION WHEN OTHERS THEN
574   /* Check whether the top manager was previously a supervisor */
575     OPEN top_manager_new_csr;
576     FETCH top_manager_new_csr INTO l_top_manager_new_flag;
577     CLOSE top_manager_new_csr;
578 
579   /* Log the check has been done */
580     g_top_level_check(g_fetch_sup_id(p_index)) := p_change_date;
581 
582   /* If the top level manager is new */
583     IF (l_top_manager_new_flag IS NULL) THEN
584     /* Get the end date to insert for the new top manager record */
585       l_end_date := get_end_date
586                 (p_index => p_index
587                 ,p_person_id => g_crrnt_chain(1).person_id
588                 ,p_period_of_service_id => g_crrnt_chain_top_pos_id
589                 ,p_change_date => p_change_date);
590 
591     /* Top manager is not leaf node because level 2 supervisor is */
592     /* non-terminated */
593       IF (g_crrnt_chain(1).invalid_flag = 'N') THEN
594         g_crrnt_chain(1).leaf_node := 'N';
595       END IF;
596 
597     /* Insert chain link in historical hierarchy table for new top  */
598     /* level manager */
599       process_chain(p_level => 1,
600                     p_end_date => l_end_date);
601 
602     END IF;
603 
604   END;
605 
606 END test_top_supervisor;
607 
608 /******************************************************************************/
609 /* Inserts and stores the chain of the current supervisor change person       */
610 /******************************************************************************/
611 FUNCTION insert_supv_change( p_index        IN NUMBER,
612                              p_change_date  IN DATE,
613                              p_event_code   IN VARCHAR2)
614                   RETURN PLS_INTEGER IS
615 
616 /***********************************************************************/
617 /* Cursor picking all managers above person who has changed supervisor */
618 /* LEVEL (wrt this cursor) will be 1 for this person                   */
619 /* Rows are returned with the topmost supervisor first                 */
620 /***********************************************************************/
621   CURSOR new_manager_chain_csr IS
622   SELECT
623    hier.business_group_id
624   ,hier.person_id
625   ,hier.assignment_id
626   ,hier.assignment_status_type_id   asg_status_id
627   ,hier.supervisor_id
628   ,hier.period_of_service_id
629   ,hier.primary_flag
630   ,LEVEL relative_level
631   FROM (SELECT
632          asg.business_group_id
633         ,asg.person_id
634         ,asg.assignment_id
635         ,asg.assignment_status_type_id
636         ,asg.supervisor_id
637         ,asg.period_of_service_id
638         ,asg.primary_flag
639         FROM
640          per_all_assignments_f        asg
641         WHERE asg.assignment_type = 'E'
642         AND asg.primary_flag = 'Y'
643         AND p_change_date
644           BETWEEN asg.effective_start_date AND asg.effective_end_date) hier
645   START WITH hier.assignment_id = g_fetch_asg_id(p_index)
646   CONNECT BY hier.person_id = PRIOR hier.supervisor_id
647   ORDER BY relative_level desc;
648 
649   l_person_level           PLS_INTEGER;
650   l_sup_lvl                PLS_INTEGER;
651 
652   l_fetch_bgr_id     g_number_tab_type;
653   l_fetch_psn_id     g_number_tab_type;
654   l_fetch_asg_id     g_number_tab_type;
655   l_fetch_ast_id     g_number_tab_type;
656   l_fetch_sup_id     g_number_tab_type;
657   l_fetch_pos_id     g_number_tab_type;
658   l_fetch_prm_flg    g_varchar2_tab_type;
659   l_fetch_level      g_number_tab_type;
660 
661   l_rows_fetched     PLS_INTEGER;
662 
663 BEGIN
664 
665 /* Get the end date */
666   g_crrnt_chain_end_date := get_end_date
667                   (p_index => p_index
668                   ,p_person_id => g_fetch_psn_id(p_index)
669                   ,p_period_of_service_id => g_fetch_pos_id(p_index)
670                   ,p_change_date => p_change_date);
671 
672 /* Bulk fetch from cursor without limit as there are never going */
673 /* to be many levels going up */
674   OPEN new_manager_chain_csr;
675   FETCH new_manager_chain_csr
676     BULK COLLECT INTO
677       l_fetch_bgr_id,
678       l_fetch_psn_id,
679       l_fetch_asg_id,
680       l_fetch_ast_id,
681       l_fetch_sup_id,
682       l_fetch_pos_id,
683       l_fetch_prm_flg,
684       l_fetch_level;
685   l_rows_fetched := new_manager_chain_csr%ROWCOUNT;
686   CLOSE new_manager_chain_csr;
687 
688 /* Loop through the links in the chain */
689   FOR i IN 1..l_rows_fetched LOOP
690 
691   /* If this is the first row, grab the level as it will be the */
692   /* absolute level for the person within the overall hierarchy */
693   /* Also note the top level supervisor period of service id    */
694     IF (l_person_level IS NULL) THEN
695       l_person_level := l_fetch_level(i);
696       g_crrnt_chain_top_pos_id := l_fetch_pos_id(i);
697     /* If the top level manager has a supervisor fk */
698     /* then they are an orphan */
699       IF (l_fetch_sup_id(i) IS NOT NULL) THEN
703       END IF;
700         g_crrnt_chain_orphan_flag := 'Y';
701       ELSE
702         g_crrnt_chain_orphan_flag := 'N';
704     END IF;
705 
706   /* Calculate the absolute level for the supervisor within the */
707   /* overall hierarchy */
708     l_sup_lvl := l_person_level - l_fetch_level(i) + 1;
709 
710   /* Store information in the global data structure */
711     g_crrnt_chain(l_sup_lvl).business_group_id := l_fetch_bgr_id(i);
712     g_crrnt_chain(l_sup_lvl).person_id         := l_fetch_psn_id(i);
713     g_crrnt_chain(l_sup_lvl).assignment_id     := l_fetch_asg_id(i);
714     g_crrnt_chain(l_sup_lvl).asg_status_id     := l_fetch_ast_id(i);
715     g_crrnt_chain(l_sup_lvl).primary_asg_flag  := l_fetch_prm_flg(i);
716     g_crrnt_chain(l_sup_lvl).invalid_flag := get_inv_flag_status(l_fetch_ast_id(i));
717     g_crrnt_chain(l_sup_lvl).leaf_node := null;
718 
719   END LOOP;
720 
721 /* Bug 2521182 (115.12) */
722 /* If the cursor returned no data, then there is probably a data problem */
723   IF (l_person_level IS NULL) THEN
724     output('Possible data corruption for person id:  ' ||
725            to_char(g_fetch_psn_id(p_index)) ||
726            ' on ' || to_char(p_change_date,'YYYY/MM/DD'));
727     RETURN -1;
728   END IF;
729 
730 /* Store information about the current chain */
731   g_crrnt_chain_owner_lvl := l_person_level;
732   g_crrnt_chain_start_date := p_change_date;
733 
734 /* Bug 2748797 - Check for top level supervisor changes */
735 /* If the insert is for a non-terminated level 2 supervisor then */
736 /* potentially the top level supervisor could be new. */
737   IF (l_person_level = 2 AND p_event_code <> 'TERM') THEN
738     test_top_supervisor(p_index => p_index,
739                         p_change_date => p_change_date,
740                         p_event_code => p_event_code);
741   END IF;
742 
743 /* If the change owner is non-terminated then default them to a leaf node */
744   IF (g_crrnt_chain(g_crrnt_chain_owner_lvl).invalid_flag = 'N') THEN
745     g_crrnt_chain(g_crrnt_chain_owner_lvl).leaf_node := 'Y';
746 
747   /* If the change owner has a non-terminated supervisor then the supervisor */
748   /* is not a leaf node */
749     IF (g_crrnt_chain_owner_lvl > 1) THEN
750       IF (g_crrnt_chain(g_crrnt_chain_owner_lvl - 1).invalid_flag = 'N') THEN
751       /* Update immediate supervisor to non-leaf node */
752         null;
753       END IF;
754     END IF;
755   END IF;
756 
757 /* Return without error */
758   RETURN 0;
759 
760 EXCEPTION
761   WHEN OTHERS THEN
762 
763   IF new_manager_chain_csr%ISOPEN THEN
764     CLOSE new_manager_chain_csr;
765   END IF;
766 
767 /* ORA 01436 - loop in tree walk */
768   IF (SQLCODE = -1436) THEN
769     output('Loop found in supervisor chain for person id:  ' ||
770             to_char(g_fetch_psn_id(p_index)) ||
771            ' on ' || to_char(p_change_date,'YYYY/MM/DD'));
772     RETURN -1;
773   ELSE
774 /* Some other error */
775     RAISE;
776   END IF;
777 
778 END insert_supv_change;
779 
780 /******************************************************************************/
781 /* Calls the chain processing procedure for each of the subordinate chains to */
782 /* process                                                                    */
783 /******************************************************************************/
784 PROCEDURE update_sub_chains( p_min_lvl     IN NUMBER,
785                              p_max_lvl     IN NUMBER,
786                              p_index       IN NUMBER) IS
787 
788 BEGIN
789 
790   FOR v_sub_lvl IN p_min_lvl..p_max_lvl LOOP
791 
792     process_chain(p_level => v_sub_lvl,
793                   p_end_date => g_crrnt_chain_end_date);
794 
795   END LOOP;
796 
797 END update_sub_chains;
798 
799 /******************************************************************************/
800 /* Updates all subordinates of the current supervisor change person           */
801 /* The cursor tree walk returns rows on a depth first basis. The global chain */
802 /* is kept updated with the latest information returned. For example, suppose */
803 /* the supervisor labelled X below changed supervisor. The subordinates of X  */
804 /* would be returned in the order they are numbered. This means that when 2   */
805 /* is processed it is guaranteed that the global chain will contain the       */
806 /* correct information for X and above, and then for 1 and 2.                 */
807 /*                                                                            */
808 /*                       X                                                    */
809 /*                      / \                                                   */
810 /*                     1   4                                                  */
811 /*                    / \                                                     */
812 /*                   2   3                                                    */
813 /*                                                                            */
814 /* A breadth first tree walk would return the subordinates of X in the order  */
815 /* 1 -> 4 -> 2 -> 3. This would mean that when 2 is processed the global      */
816 /* chain would contain information for X and above, and then for 4 and 2.     */
817 /* This would be wrong!!!                                                     */
818 /******************************************************************************/
819 PROCEDURE update_subordinates( p_index        IN NUMBER,
823 /* Cursor picks out all subordates of the person who has changed supervisor  */
820                                p_change_date  IN DATE,
821                                p_event_code   IN VARCHAR2) IS
822 
824 /* so that the chains of the subordinates can all be updated with the change */
825 /* This cursor MUST return rows in the default order                         */
826   CURSOR subordinates_csr IS
827   SELECT
828    hier.business_group_id
829   ,hier.person_id
830   ,hier.assignment_id
831   ,hier.assignment_status_type_id  asg_status_id
832   ,hier.supervisor_id
833   ,hier.period_of_service_id
834   ,hier.primary_flag
835   ,LEVEL-1+g_crrnt_chain_owner_lvl   actual_level
836   FROM (SELECT
837         asg.business_group_id
838        ,asg.person_id
839        ,asg.assignment_id
840        ,asg.assignment_status_type_id
841        ,asg.period_of_service_id
842        ,asg.supervisor_id
843        ,asg.primary_flag
844        FROM
845         per_all_assignments_f        asg
846        WHERE asg.assignment_type = 'E'
847        AND asg.primary_flag = 'Y'
848        AND p_change_date
849          BETWEEN asg.effective_start_date AND asg.effective_end_date) hier
850   WHERE hier.person_id <> g_fetch_psn_id(p_index)
851   START WITH hier.person_id = g_fetch_psn_id(p_index)
852   CONNECT BY hier.supervisor_id = PRIOR hier.person_id;
853 /******************************/
854 /* DO NOT ADD ORDER BY CLAUSE */
855 /******************************/
856 
857   l_end_date         DATE;
858   l_last_sub_lvl     PLS_INTEGER := 0;
859 
860   l_fetch_bgr_id     g_number_tab_type;
861   l_fetch_psn_id     g_number_tab_type;
862   l_fetch_asg_id     g_number_tab_type;
863   l_fetch_ast_id     g_number_tab_type;
864   l_fetch_sup_id     g_number_tab_type;
865   l_fetch_pos_id     g_number_tab_type;
866   l_fetch_prm_flg    g_varchar2_tab_type;
867   l_fetch_level      g_number_tab_type;
868 
869   l_rows_fetched     PLS_INTEGER := g_chunk_size;
870   l_exit_sub_loop    BOOLEAN := FALSE;
871 
872 BEGIN
873 
874   OPEN subordinates_csr;
875 
876   <<subordinates_loop>>
877   LOOP
878 
879     FETCH subordinates_csr
880     BULK COLLECT INTO
881       l_fetch_bgr_id,
882       l_fetch_psn_id,
883       l_fetch_asg_id,
884       l_fetch_ast_id,
885       l_fetch_sup_id,
886       l_fetch_pos_id,
887       l_fetch_prm_flg,
888       l_fetch_level
889     LIMIT g_chunk_size;
890     -- check to see if the last row has been fetched
891     IF subordinates_csr%NOTFOUND THEN
892       -- last row fetched, set exit loop flag
893       l_exit_sub_loop := TRUE;
894       -- do we have any rows to process?
895       l_rows_fetched := MOD(subordinates_csr%ROWCOUNT,g_chunk_size);
896       -- note: if l_rows_fetched > 0 then more rows are required to be
897       -- processed and the l_rows_fetched will contain the exact number of
898       -- rows left to process
899       IF l_rows_fetched = 0 THEN
900         -- no more rows to process so exit loop
901         EXIT subordinates_loop;
902       END IF;
903     END IF;
904 
905     FOR i IN 1..l_rows_fetched LOOP
906 
907       BEGIN
908 
909       /* If there is no data in the global, NO_DATA_FOUND will be raised.   */
910       /* If the data in the global is an earlier date the same exception is */
911       /* raised. Otherwise the processing has already been done.            */
912         IF (p_change_date = g_assgnmnts_prcssd(l_fetch_asg_id(i))) THEN
913           null;
914         ELSE
915           RAISE NO_DATA_FOUND;
916         END IF;
917 
918       /* If the subordinate is for the current chain, active and their supervisor */
919       /* is active, mark the supervisor as a non-leaf */
920         IF (g_crrnt_chain(l_fetch_level(i) - 1).person_id = l_fetch_sup_id(i) AND
921             get_inv_flag_status(l_fetch_ast_id(i)) = 'N' AND
922             g_crrnt_chain(l_fetch_level(i) - 1).invalid_flag = 'N') THEN
923           g_crrnt_chain(l_fetch_level(i) - 1).leaf_node := 'N';
924         END IF;
925 
926       EXCEPTION
927         WHEN NO_DATA_FOUND THEN
928 
929       /* Store information about assignment to be processed */
930         g_assgnmnts_prcssd(l_fetch_asg_id(i)) := p_change_date;
931 
932       /* If the end of a chain is reached then insert it */
933         IF (l_fetch_level(i) <= l_last_sub_lvl) THEN
934 
935         /* Insert the changed subordinate chains */
936           update_sub_chains(p_min_lvl  => l_fetch_level(i),
937                             p_max_lvl  => l_last_sub_lvl,
938                             p_index    => p_index);
939 
940         END IF;  -- End of chain reached
941 
942       /* Get the end date */
943         l_end_date := get_end_date
944                       (p_index => p_index
945                       ,p_person_id => l_fetch_psn_id(i)
946                       ,p_period_of_service_id => l_fetch_pos_id(i)
947                       ,p_change_date => p_change_date);
948 
949         g_crrnt_chain(l_fetch_level(i)).business_group_id := l_fetch_bgr_id(i);
950         g_crrnt_chain(l_fetch_level(i)).person_id         := l_fetch_psn_id(i);
951         g_crrnt_chain(l_fetch_level(i)).assignment_id     := l_fetch_asg_id(i);
952         g_crrnt_chain(l_fetch_level(i)).asg_status_id     := l_fetch_ast_id(i);
953         g_crrnt_chain(l_fetch_level(i)).primary_asg_flag  := l_fetch_prm_flg(i);
954         g_crrnt_chain(l_fetch_level(i)).end_date          := l_end_date;
955         g_crrnt_chain(l_fetch_level(i)).invalid_flag :=
956                                           get_inv_flag_status(l_fetch_ast_id(i));
957 
958       /* If the subordinate is valid then mark them a leaf node by default */
959         IF (g_crrnt_chain(l_fetch_level(i)).invalid_flag = 'N') THEN
960           g_crrnt_chain(l_fetch_level(i)).leaf_node := 'Y';
961 
962         /* In addition, if their supervisor is valid then mark their */
963         /* supervisor as not a leaf node */
964           IF (g_crrnt_chain(l_fetch_level(i) - 1).invalid_flag = 'N') THEN
968           g_crrnt_chain(l_fetch_level(i)).leaf_node := null;
965             g_crrnt_chain(l_fetch_level(i) - 1).leaf_node := 'N';
966           END IF;
967         ELSE
969         END IF;
970 
971         l_last_sub_lvl := l_fetch_level(i);
972 
973       END;
974 
975     /* Commit every so often */
976       IF (g_chain_transactions > (g_chain_chunk_size)) THEN
977         commit;
978         g_chain_transactions := 0;
979       END IF;
980 
981     END LOOP;
982 
983     -- exit loop if required
984     IF l_exit_sub_loop THEN
985       EXIT subordinates_loop;
986     END IF;
987 
988   END LOOP;
989 
990   CLOSE subordinates_csr;
991 
992   IF (l_last_sub_lvl > 0) THEN
993 
994   /* Insert the changed subordinate chains */
995     update_sub_chains(p_min_lvl  => g_crrnt_chain_owner_lvl+1,
996                       p_max_lvl  => l_last_sub_lvl,
997                       p_index    => p_index);
998 
999   END IF;  -- End of chain reached
1000 
1001 EXCEPTION
1002   WHEN OTHERS THEN
1003 
1004   IF subordinates_csr%ISOPEN THEN
1005     CLOSE subordinates_csr;
1006   END IF;
1007 
1008 /* ORA 01436 - loop in tree walk */
1009   IF (SQLCODE = -1436) THEN
1010     output('Loop found in supervisor chain for person id:  ' ||
1011             to_char(g_crrnt_chain(g_crrnt_chain_owner_lvl).person_id) ||
1012            ' on ' || to_char(p_change_date,'DD-MON-YYYY'));
1013   ELSE
1014 /* Some other error */
1015     RAISE;
1016   END IF;
1017 
1018 END update_subordinates;
1019 
1020 /******************************************************************************/
1021 /* Updates stored leaf node information                                       */
1022 /******************************************************************************/
1023 PROCEDURE update_leaf_node_change( p_person_id        IN NUMBER,
1024                                    p_change_date      IN DATE,
1025                                    p_from_leaf_flag   IN VARCHAR2,
1029   CURSOR chain_csr IS
1026                                    p_to_leaf_flag     IN VARCHAR2) IS
1027 
1028 /* Selects single link in chain for a non-terminated supervisor on a date */
1030   SELECT *
1031   FROM hri_cs_suph
1032   WHERE sub_person_id = p_person_id
1033   AND sup_person_id = p_person_id
1034   AND sub_invalid_flag_code = 'N'
1035   AND sub_leaf_flag_code = p_from_leaf_flag
1036   AND p_change_date BETWEEN effective_start_date AND effective_end_date;
1037 
1038 BEGIN
1039 
1040   FOR chain_rec IN chain_csr LOOP
1041 
1042   /* If the start dates match then update the existing chain */
1043     IF (chain_rec.effective_start_date = p_change_date) THEN
1044 
1045       g_chain_transactions := g_chain_transactions + 1;
1046 
1047     /* Update all links in chain at once */
1048       UPDATE hri_cs_suph
1049       SET sub_leaf_flag_code = p_to_leaf_flag
1050       WHERE sub_person_id = p_person_id
1051       AND effective_start_date = p_change_date
1052       AND sub_invalid_flag_code = 'N';
1053 
1054     /* Otherwise end date existing chain and insert new one */
1055       ELSE
1056 
1057         g_chain_transactions := g_chain_transactions + 2;
1058 
1059       /* Insert new chain */
1060         INSERT INTO hri_cs_suph
1061           (sup_business_group_id
1062           ,sup_person_id
1063           ,sup_assignment_id
1064           ,sup_assignment_status_type_id
1065           ,sup_level
1066           ,sup_invalid_flag_code
1067           ,sub_business_group_id
1068           ,sub_person_id
1069           ,sub_assignment_id
1070           ,sub_assignment_status_type_id
1071           ,sub_primary_asg_flag_code
1072           ,sub_level
1073           ,sub_relative_level
1074           ,sub_invalid_flag_code
1075           ,orphan_flag_code
1076           ,sub_leaf_flag_code
1077           ,effective_start_date
1078           ,effective_end_date)
1079           SELECT
1080            sup_business_group_id
1081           ,sup_person_id
1082           ,sup_assignment_id
1083           ,sup_assignment_status_type_id
1084           ,sup_level
1085           ,sup_invalid_flag_code
1086           ,sub_business_group_id
1087           ,sub_person_id
1088           ,sub_assignment_id
1092           ,sub_relative_level
1089           ,sub_assignment_status_type_id
1090           ,sub_primary_asg_flag_code
1091           ,sub_level
1093           ,sub_invalid_flag_code
1105         SET effective_end_date = p_change_date - 1
1094           ,orphan_flag_code
1095           ,p_to_leaf_flag
1096           ,p_change_date
1097           ,chain_rec.effective_end_date
1098           FROM hri_cs_suph
1099           WHERE sub_person_id = p_person_id
1100           AND effective_start_date = chain_rec.effective_start_date
1101           AND sub_invalid_flag_code = 'N';
1102 
1103       /* End date existing chain */
1104         UPDATE hri_cs_suph
1113 
1106         WHERE sub_person_id = p_person_id
1107         AND effective_start_date = chain_rec.effective_start_date
1108         AND sub_invalid_flag_code = 'N';
1109 
1110       END IF;
1111 
1112     END LOOP;
1114 END update_leaf_node_change;
1115 
1116 
1117 /******************************************************************************/
1118 /* Loops through supervisor changes                                           */
1119 /******************************************************************************/
1120 PROCEDURE collect_data( p_collect_from    IN DATE,
1121                         p_collect_to      IN DATE) IS
1122 
1123 /* Pick out all primary assignment supervisor changes */
1124   CURSOR supervisor_changes_csr IS
1125   SELECT
1126    asg.assignment_id                assignment_id
1127   ,asg.effective_start_date         asg_start
1128   ,asg.effective_end_date           asg_end
1129   ,asg.business_group_id            business_group_id
1130   ,asg.person_id                    person_id
1131   ,NVL(asg.supervisor_id , -1)      supervisor_id
1132   ,DECODE(prev_asg.assignment_id,
1133             to_number(null), to_number(null),
1134           NVL(prev_asg.supervisor_id, -1))  prev_supervisor_id
1135   ,asg.assignment_status_type_id    assignment_status_type_id
1136   ,pos.period_of_service_id         period_of_service_id
1137   ,asg.effective_start_date         change_date
1138   ,DECODE(asg.effective_start_date,
1139             pos.date_start, 'HIRE',
1140           'CHNG')                   event_code
1141   ,pos.actual_termination_date      termination_date
1142   ,pos.final_process_date           final_process_date
1143   FROM
1144    per_all_assignments_f        asg
1145   ,per_periods_of_service       pos
1146   ,per_all_assignments_f        prev_asg
1147   WHERE asg.primary_flag = 'Y'
1151   AND asg.period_of_service_id = pos.period_of_service_id (+)
1148   AND prev_asg.primary_flag (+) = 'Y'
1149   AND asg.assignment_type  = 'E'
1150   AND prev_asg.assignment_type (+) = 'E'
1152   AND prev_asg.person_id (+) = asg.person_id
1153   AND prev_asg.effective_end_date (+) = asg.effective_start_date - 1
1154 /* All non-terminated assignment supervisor changes within date range */
1155   AND ((asg.effective_start_date BETWEEN p_collect_from AND p_collect_to
1156         AND NVL(asg.supervisor_id, -1) <> NVL(prev_asg.supervisor_id, -1)
1157         AND NVL(prev_asg.assignment_id, -1) <> -1
1158         AND asg.effective_start_date <= NVL(pos.actual_termination_date, g_current_date))
1159 /* All initial hire assignments with a supervisor */
1160     OR (asg.effective_start_date = pos.date_start
1161         AND pos.date_start BETWEEN p_collect_from AND p_collect_to
1162         AND asg.supervisor_id IS NOT NULL))
1163   UNION ALL
1164 /* All terminations and final processes */
1165   SELECT /*+ leading(pos) use_hash(pos asg) */
1166    asg.assignment_id                assignment_id
1167   ,asg.effective_start_date         asg_start
1168   ,asg.effective_end_date           asg_end
1169   ,asg.business_group_id            business_group_id
1170   ,asg.person_id                    person_id
1171   ,to_number(null)                  supervisor_id
1172   ,NVL(asg.supervisor_id , -1)      prev_supervisor_id
1173   ,asg.assignment_status_type_id    assignment_status_type_id
1174   ,pos.period_of_service_id         period_of_service_id
1175   ,pos.actual_termination_date + 1  change_date
1176   ,'TERM'                           event_code
1177   ,pos.actual_termination_date      termination_date
1178   ,pos.final_process_date           final_process_date
1179   FROM
1180    per_all_assignments_f        asg
1181   ,per_periods_of_service       pos
1182   WHERE asg.effective_end_date = pos.actual_termination_date
1183   AND (pos.actual_termination_date BETWEEN p_collect_from AND p_collect_to
1184     OR pos.final_process_date BETWEEN p_collect_from AND p_collect_to)
1185   AND asg.period_of_service_id = pos.period_of_service_id
1186   UNION ALL
1187 /* All subordinates of supervisors who have separated (final process) */
1188 /* whose assignments have not been updated with a new supervisor and so */
1189 /* are invalid */
1190   SELECT /*+ leading(pps) use_hash(pps sub_asg sub_pps) */
1191    sub_asg.assignment_id                assignment_id
1192   ,sub_asg.effective_start_date         asg_start
1193   ,sub_asg.effective_end_date           asg_end
1194   ,sub_asg.business_group_id            business_group_id
1195   ,sub_asg.person_id                    person_id
1196   ,to_number(null)                      supervisor_id
1197   ,sub_asg.supervisor_id                prev_supervisor_id
1198   ,sub_asg.assignment_status_type_id    assignment_status_type_id
1199   ,sub_pps.period_of_service_id         period_of_service_id
1200   ,pps.final_process_date + 1           change_date
1201 /* Event code ORPH for subordinates orphaned by their supervisor's separation */
1202   ,'ORPH'                               event_code
1203   ,sub_pps.actual_termination_date      actual_termination_date
1204   ,sub_pps.final_process_date           final_process_date
1205   FROM
1206    per_all_assignments_f  sub_asg
1207   ,per_periods_of_service pps
1208   ,per_periods_of_service sub_pps
1209   WHERE pps.final_process_date BETWEEN p_collect_from AND p_collect_to
1210   AND sub_asg.supervisor_id = pps.person_id
1211   AND sub_asg.period_of_service_id = sub_pps.period_of_service_id
1212   AND sub_asg.assignment_type = 'E'
1213   AND sub_asg.primary_flag = 'Y'
1214   AND pps.final_process_date + 1
1218   l_return_code          PLS_INTEGER;
1215     BETWEEN sub_asg.effective_start_date AND sub_asg.effective_end_date
1216   ORDER BY change_date;
1217 
1219   l_exit_main_loop       BOOLEAN := FALSE;
1220   l_rows_fetched         PLS_INTEGER := g_chunk_size;
1221   l_leaf_end_date        DATE;
1222   l_leaf_flag            VARCHAR2(1);
1223 
1224 BEGIN
1225   -- set the global collection date range
1226   g_collect_from_date := p_collect_from;
1227   g_collect_to_date   := p_collect_to;
1228   -- load TERM assignment statuses
1229   init_term_per_system_status;
1230   -- open main cursor
1231   OPEN supervisor_changes_csr;
1232   -- enter main loop
1233   <<main_loop>>
1234   LOOP
1235     -- bulk fetch rows limit the fetch to value of g_chunk_size
1236     FETCH supervisor_changes_csr
1237     BULK COLLECT INTO
1238           g_fetch_asg_id,
1239           g_fetch_strt_dt,
1240           g_fetch_end_dt,
1241           g_fetch_bgr_id,
1242           g_fetch_psn_id,
1243           g_fetch_sup_id,
1244           g_fetch_prev_sup_id,
1245           g_fetch_ast_id,
1246           g_fetch_pos_id,
1247           g_fetch_chng_dt,
1248           g_fetch_evt_code,
1249           g_fetch_term_dt,
1250           g_fetch_fprc_dt
1251     LIMIT g_chunk_size;
1252     -- check to see if the last row has been fetched
1253     IF supervisor_changes_csr%NOTFOUND THEN
1254       -- last row fetched, set exit loop flag
1255       l_exit_main_loop := TRUE;
1256       -- do we have any rows to process?
1257       l_rows_fetched := MOD(supervisor_changes_csr%ROWCOUNT,g_chunk_size);
1258       -- note: if l_rows_fetched > 0 then more rows are required to be
1259       -- processed and the l_rows_fetched will contain the exact number of
1260       -- rows left to process
1261       IF l_rows_fetched = 0 THEN
1262         -- no more rows to process so exit loop
1263         EXIT main_loop;
1264       END IF;
1265     END IF;
1266 
1267     -- Loop through supervisor changes
1268     FOR i IN 1..l_rows_fetched LOOP
1269 
1270 /******************************************************************************/
1271 /* Orphans */
1272 /***********/
1273       IF (g_fetch_evt_code(i) = 'ORPH') THEN
1274         BEGIN
1275         /* Process orphan if the assignment hasn't already been */
1276         /* processed on the orphaning date */
1277           IF (g_assgnmnts_prcssd(g_fetch_asg_id(i)) = g_fetch_chng_dt(i)) THEN
1278             null;
1279           ELSE
1280             RAISE NO_DATA_FOUND;
1281           END IF;
1282         EXCEPTION WHEN NO_DATA_FOUND THEN
1283         /* Store record of processing */
1284           g_assgnmnts_prcssd(g_fetch_asg_id(i)) := g_fetch_chng_dt(i);
1285         /* Calculate new chain for orphan */
1286           l_return_code := insert_supv_change(p_index => i
1287                                              ,p_change_date => g_fetch_chng_dt(i)
1288                                              ,p_event_code => 'ORPH');
1289         /* If no error encountered then update chains for all their subordinates */
1290           IF (l_return_code = 0) THEN
1291           /* Process subordinates for assignment */
1292             update_subordinates(p_index => i
1293                                ,p_change_date => g_fetch_chng_dt(i)
1294                                ,p_event_code => 'ORPH');
1295           /* Insert chain for assignment */
1296             process_chain(p_level => g_crrnt_chain_owner_lvl,
1297                           p_end_date => g_crrnt_chain_end_date);
1298           END IF;
1299         END;
1300 
1301 /******************************************************************************/
1302 /* Hires */
1303 /*********/
1304       ELSIF (g_fetch_evt_code(i) = 'HIRE') THEN
1305         BEGIN
1306         /* Process hire if the assignment hasn't already been */
1307         /* processed on the hire date */
1308           IF (g_assgnmnts_prcssd(g_fetch_asg_id(i)) = g_fetch_chng_dt(i)) THEN
1309             null;
1310           ELSE
1311             RAISE NO_DATA_FOUND;
1312           END IF;
1313         EXCEPTION WHEN NO_DATA_FOUND THEN
1314         /* Store record of processing hire */
1315           g_assgnmnts_prcssd(g_fetch_asg_id(i)) := g_fetch_chng_dt(i);
1316         /* Process chain for new hire */
1320         /* Skip processing if an error is encountered */
1317           l_return_code := insert_supv_change(p_index => i
1318                                              ,p_change_date => g_fetch_chng_dt(i)
1319                                              ,p_event_code => 'HIRE');
1321           IF (l_return_code = 0) THEN
1322           /* Test whether the new hire is a leaf node */
1323             g_crrnt_chain(g_crrnt_chain_owner_lvl).leaf_node
1324                           := is_a_leaf_node(p_person_id => g_fetch_psn_id(i),
1325                                             p_on_date   => g_fetch_chng_dt(i));
1326           /* Update new hire's manager to be a non-leaf node */
1327           /* if they weren't before */
1328             update_leaf_node_change(p_person_id => g_fetch_sup_id(i),
1329                                     p_change_date => g_fetch_chng_dt(i),
1330                                     p_from_leaf_flag => 'Y',
1331                                     p_to_leaf_flag => 'N');
1332           /* Process subordinates for assignment */
1333             update_subordinates(p_index => i
1334                                ,p_change_date => g_fetch_chng_dt(i)
1335                                ,p_event_code => 'HIRE');
1336           /* Insert chain for new hire */
1337             process_chain(p_level => g_crrnt_chain_owner_lvl,
1338                           p_end_date => g_crrnt_chain_end_date);
1339           END IF;
1340         END;
1341 
1342 /******************************************************************************/
1343 /* Non-terminated Supervisor change */
1344 /************************************/
1345       ELSIF (g_fetch_evt_code(i) = 'CHNG') THEN
1346         BEGIN
1347         /* Process the change if the assignment hasn't already */
1348         /* been processed on the change date */
1349           IF (g_assgnmnts_prcssd(g_fetch_asg_id(i)) = g_fetch_strt_dt(i)) THEN
1350           /* Is the previous manager of the person still supervising? */
1351             l_leaf_flag := is_a_leaf_node
1352                              (p_person_id => g_fetch_prev_sup_id(i),
1353                               p_on_date   => g_fetch_strt_dt(i));
1354           /* If not then update chain */
1355             IF (l_leaf_flag = 'Y') THEN
1356               update_leaf_node_change(p_person_id => g_fetch_prev_sup_id(i),
1357                                       p_change_date => g_fetch_strt_dt(i),
1358                                       p_from_leaf_flag => 'N',
1359                                       p_to_leaf_flag => 'Y');
1360             END IF;
1361           ELSE
1362             RAISE NO_DATA_FOUND;
1363           END IF;
1364         EXCEPTION WHEN NO_DATA_FOUND THEN
1365         /* Record processing being done for assignment */
1366           g_assgnmnts_prcssd(g_fetch_asg_id(i)) := g_fetch_strt_dt(i);
1367         /* Process chain for supervisor change and all subordinates */
1368           l_return_code := insert_supv_change(p_index => i
1369                                              ,p_change_date => g_fetch_strt_dt(i)
1370                                              ,p_event_code => 'CHNG');
1371         /* If no error encountered then update chains for all their subordinates */
1372           IF (l_return_code = 0) THEN
1373           /* Process subordinates for assignment */
1374             update_subordinates(p_index => i
1375                                ,p_change_date => g_fetch_strt_dt(i)
1376                                ,p_event_code => 'CHNG');
1377           /* Insert chain for assignment */
1378             process_chain(p_level => g_crrnt_chain_owner_lvl,
1379                           p_end_date => g_crrnt_chain_end_date);
1380           /* Update new manager to be a non-leaf node */
1381           /* if they weren't before */
1382             update_leaf_node_change(p_person_id => g_fetch_sup_id(i),
1383                                     p_change_date => g_fetch_strt_dt(i),
1384                                     p_from_leaf_flag => 'Y',
1385                                     p_to_leaf_flag => 'N');
1386           END IF;
1387         /* Is the previous manager of the person still supervising? */
1388           IF (g_fetch_prev_sup_id(i) > 0) THEN
1389             l_leaf_flag := is_a_leaf_node
1390                              (p_person_id => g_fetch_prev_sup_id(i),
1391                               p_on_date   => g_fetch_strt_dt(i));
1392           /* If not then update chain */
1393             IF (l_leaf_flag = 'Y') THEN
1394               update_leaf_node_change(p_person_id => g_fetch_prev_sup_id(i),
1395                                       p_change_date => g_fetch_strt_dt(i),
1396                                       p_from_leaf_flag => 'N',
1397                                       p_to_leaf_flag => 'Y');
1398             END IF;
1399           END IF;
1400         END;
1401 
1402       ELSE  -- event code 'TERM'
1403 /******************************************************************************/
1404 /* Terminations */
1405 /****************/
1406         IF (g_fetch_term_dt(i) >= p_collect_from AND
1407             g_fetch_term_dt(i) <= p_collect_to) THEN
1408           BEGIN
1409           /* If the termination has been processed before */
1410           /* then don't process the termination */
1411             IF (g_assgnmnts_prcssd(g_fetch_asg_id(i)) = g_fetch_chng_dt(i)) THEN
1412               null;
1413             ELSE
1414               RAISE NO_DATA_FOUND;
1415             END IF;
1416           EXCEPTION WHEN NO_DATA_FOUND THEN
1417           /* Store record of processing */
1418             g_assgnmnts_prcssd(g_fetch_asg_id(i)) := g_fetch_chng_dt(i);
1419           /* If the termination occurs before the final process extra work required */
1420             IF (g_fetch_term_dt(i) <> g_fetch_fprc_dt(i) OR
1421                 g_fetch_fprc_dt(i) IS NULL) THEN
1422             /* Is the terminated supervisor still supervising? */
1423               l_leaf_flag := is_a_leaf_node
1424                                (p_person_id => g_fetch_psn_id(i),
1425                                 p_on_date   => g_fetch_term_dt(i) + 1);
1426             /* If the terminated supervisor is still supervising, update their chain */
1427             /* and those of all their subordinates */
1428               IF (l_leaf_flag = 'N') THEN
1429               /* Calculate new chain for terminated supervisor */
1430                 l_return_code := insert_supv_change(p_index => i
1431                                                    ,p_change_date => g_fetch_term_dt(i) + 1
1432                                                    ,p_event_code => 'TERM');
1433               /* If no error encountered then update chains for all their subordinates */
1434                 IF (l_return_code = 0) THEN
1435                 /* Process subordinates for assignment */
1436                   update_subordinates(p_index => i
1437                                      ,p_change_date => g_fetch_term_dt(i) + 1
1438                                      ,p_event_code => 'TERM');
1439                 /* Insert chain for assignment */
1440                   process_chain(p_level => g_crrnt_chain_owner_lvl,
1441                                 p_end_date => g_crrnt_chain_end_date);
1442                 END IF;
1443             /* otherwise end date the terminated supervisor chain */
1444               ELSE
1445                 end_date_chain(p_person_id => g_fetch_psn_id(i),
1446                                p_end_date => g_fetch_term_dt(i));
1447               END IF;
1448             END IF;  -- Termination before final process
1449           /* Is the manager of the terminated supervisor still supervising? */
1450             l_leaf_flag := is_a_leaf_node
1451                              (p_person_id => g_fetch_prev_sup_id(i),
1452                               p_on_date   => g_fetch_term_dt(i) + 1);
1453           /* If not then update chain */
1454             IF (l_leaf_flag = 'Y') THEN
1455               update_leaf_node_change(p_person_id => g_fetch_prev_sup_id(i),
1456                                       p_change_date => g_fetch_term_dt(i) + 1,
1457                                       p_from_leaf_flag => 'N',
1458                                       p_to_leaf_flag => 'Y');
1459             END IF;
1460           END;
1461         END IF;
1462 
1463 /******************************************************************************/
1464 /* Final process */
1465 /*****************/
1466         IF (g_fetch_fprc_dt(i) >= p_collect_from AND
1467             g_fetch_fprc_dt(i) <= p_collect_to AND
1471                          p_end_date => g_fetch_fprc_dt(i));
1468             g_fetch_term_dt(i) = g_fetch_end_dt(i)) THEN
1469         /* End date chain */
1470           end_date_chain(p_person_id => g_fetch_psn_id(i),
1472         END IF;
1473 
1477 
1474 /******************************************************************************/
1475 
1476       END IF; -- Event codes
1478     END LOOP;
1479     -- exit loop if required
1480     IF l_exit_main_loop THEN
1481       EXIT main_loop;
1482     END IF;
1483   /* Commit every so often */
1484     IF (g_chain_transactions > (g_chain_chunk_size)) THEN
1485       commit;
1486       g_chain_transactions := 0;
1487     END IF;
1488   END LOOP;
1489 
1490   CLOSE supervisor_changes_csr;
1491 
1492 EXCEPTION
1493   WHEN OTHERS THEN
1494     -- unexpected error has occurred so close down
1495     -- main bulk cursor if it is open
1496     IF supervisor_changes_csr%ISOPEN THEN
1497       CLOSE supervisor_changes_csr;
1498     END IF;
1499     -- re-raise error
1500     RAISE;
1501 END collect_data;
1502 
1503 /******************************************************************************/
1504 /* Main entry point to reload the historical supervisor hierarchy table       */
1505 /******************************************************************************/
1506 PROCEDURE load_managers( p_start_date    IN DATE,
1507                          p_end_date      IN DATE ) IS
1508 
1509   l_sql_stmt      VARCHAR2(2000);
1510   l_dummy1        VARCHAR2(2000);
1511   l_dummy2        VARCHAR2(2000);
1512   l_schema        VARCHAR2(400);
1513 
1514 BEGIN
1515 
1516 /* Time at start */
1517   output('PL/SQL Start:   ' || to_char(sysdate,'HH24:MI:SS'));
1518 
1519 /* Insert new supervisor hierarchy history records */
1520   collect_data
1521     (p_collect_from => TRUNC(p_start_date)
1522     ,p_collect_to   => TRUNC(p_end_date));
1523 
1524   COMMIT;
1525 
1526 /* Write timing information to log */
1527   output('Updated Supervisor History table:  '  ||
1528          to_char(sysdate,'HH24:MI:SS'));
1529 
1530 END load_managers;
1531 
1532 /******************************************************************************/
1533 /* Entry point to be called from the concurrent manager                       */
1534 /******************************************************************************/
1535 PROCEDURE load_managers( errbuf          OUT NOCOPY VARCHAR2,
1536                          retcode         OUT NOCOPY VARCHAR2,
1537                          p_start_date    IN DATE,
1538                          p_end_date      IN DATE )
1539 
1540 IS
1541 
1542 BEGIN
1543 
1544 /* Enable output to concurrent request log */
1545   g_conc_request_flag := TRUE;
1546 
1547 /* Call main function */
1548   load_managers
1549     (p_start_date => p_start_date
1550     ,p_end_date   => p_end_date);
1551 
1552 EXCEPTION
1553   WHEN OTHERS THEN
1554     errbuf := SQLERRM;
1555     retcode := SQLCODE;
1556 
1557 END load_managers;
1558 
1559 END hri_opl_suph_hst_inc;