/*  $Revision$
 *  $Id$
 */

/*
 * FAC.remember_: Functions voor het bewaren en teruggeven van primary keys binnen triggers.
 * Dit om in de after-update nog te weten welke records er nog een nabehandeling hoeven
 * Dit kan vaak niet direct omdat de muutatie dan nog bezig is of omdat je pas aan het einde weet of en wat.
 *
 * De volgende functies zijn beschikbaar:
 * - ResetSelectie(TableName) RETURN BOOLEAN:
 *   Verwijderd de vorige selectie van de Primary-keys van de tabel. Hou hier rekening met multi-user dmv. de
 *   huidige SessionId. Retourneerd of het gelukt (TRUE) is of niet (FALSE).
 *   Deze moet aangeroepen worden in de BEFORE-statement-trigger,
 *   bijvoorbeeld: BEFORE INSERT OR UPDATE OR DELETE ON mld_opdr.
 * - SavePrimaryKey(TableName, PrimaryKey) RETURN BOOLEAN:
 *   Onthou de PrimaryKey-waarde, hou hiet ook rekening met multi-user dmv. de huidige SessionId. Retourneerd
 *   of het gelukt (TRUE) is of niet (FALSE).
 *   Deze moet aangeroepen worden in de BEFORE-row-trigger,
 *   bijvoorbeeld: BEFORE INSERT OR UPDATE ON mld_opdr FOR EACH ROW
 *   EN          : BEFORE DELETE ON mld_opdr FOR EACH ROW
 * - GetPrimaryKey(TableName, IndexNr) RETURN NUMBER:
 *   Geef de nth onthouden PrimaryKey terug, hou hiet ook rekening met multi-user dmv. de huidige SessionId.
 *   Het IndexNr begint met 1 en loopt door totdat er geen volgende PrimaryKey onthouden is.
 *   Retourneert de gevonden PrimaryKey of NULL (als er geen volgende is).
 *   Deze moet aangeroepen worden in de AFTER-statement-trigger,
 *   bijvoorbeeld: AFTER INSERT OR UPDATE OR DELETE ON mld_opdr.
 *
 */

#include "comsql.h"
#include "fac_paca.src"
#include "fac_pacx.src"
#include "fac_pacf.src"
#include "fac_pacl.src"
#include "fac_pacflex.src"
#include "fac_pacano.src"
#include "fac_pacdel.src"
#include "fac_pactsk.src"

CREATE OR REPLACE PACKAGE fac
    AUTHID CURRENT_USER -- zodat CREATE TABLE lukt
  AS
    BeginWerkUur      NUMBER(4,2);
    EindWerkUur       NUMBER(4,2);

   -- Maak de vorige selectie van de tabel en de huidige sessie leeg.
   FUNCTION Remember_ResetSelectie( TableName IN VARCHAR2 ) RETURN BOOLEAN;
   -- Onthou de primary key van de tabel en de huidige sessie.
   FUNCTION Remember_SavePrimaryKey( TableName IN VARCHAR2, PrimaryKey IN NUMBER ) RETURN BOOLEAN;
   -- Geef de nth primary key van de tabel en de huidige sessie. IndexNr begint met 1.
   FUNCTION Remember_GetPrimaryKey( TableName IN VARCHAR2, IndexNr IN NUMBER ) RETURN NUMBER;
   -- Onthou naast Primary key ook de datum i.v.m. een eventuele UNDO vanuit AutoCAD.
   FUNCTION Remember_SavePrimaryKeyAndDate( TableName IN VARCHAR2, PrimaryKey IN NUMBER, Datum IN Date ) RETURN BOOLEAN;
   -- Geef verwijderdatum of NULL terug van primary key i.v.m. eventuele UNDO vanuit AutoCAD.
   FUNCTION Remember_GetDate( TableName IN VARCHAR2, PrimaryKey IN NUMBER ) RETURN DATE;
   PRAGMA RESTRICT_REFERENCES (Remember_GetPrimaryKey,WNDS, WNPS);


    FUNCTION addMonths (pdate1 IN DATE, padd_mon IN NUMBER) RETURN DATE;
    FUNCTION safe_To_Integer( str IN VARCHAR2 ) RETURN NUMBER;
    FUNCTION safe_To_Number( str IN VARCHAR2 ) RETURN NUMBER;
    FUNCTION safe_To_Date  ( pchar IN VARCHAR2, pfmt IN VARCHAR2 ) RETURN DATE;
    FUNCTION safe_concat   ( pchar1 IN VARCHAR2, pchar2 IN VARCHAR2, pmaxlen NUMBER DEFAULT 4000 ) RETURN VARCHAR2;
    FUNCTION safe_least ( p1 IN DATE, p2 IN DATE ) RETURN DATE;
    FUNCTION getweekdaynum (d IN DATE) RETURN NUMBER;
    FUNCTION getdomeinwaarde (dkey IN NUMBER, waarde IN VARCHAR2, ignorewhenxmlnode IN NUMBER DEFAULT 0) RETURN VARCHAR2;
    FUNCTION getscopeleveltext (plevel IN NUMBER, ptype IN VARCHAR) RETURN VARCHAR;
    FUNCTION count_Work_Days  ( pdate1 IN DATE , pdate2 IN DATE, pCalendarId IN VARCHAR2 DEFAULT 'Default'  ) RETURN NUMBER;
    FUNCTION count_Work_Days_InclTime ( pdate1 IN DATE , pdate2 IN DATE, pCalendarId IN VARCHAR2 DEFAULT 'Default'  ) RETURN NUMBER;
    FUNCTION iswerkdag (begindatum IN DATE, pCalendarId IN VARCHAR2 DEFAULT 'Default') RETURN NUMBER;
    FUNCTION iswerktijd RETURN NUMBER;
    FUNCTION datumtijdplusuitvoertijd (begindatum IN DATE, uitvoertijd IN NUMBER, uitvoertijdtype IN VARCHAR2, pCalendarId IN VARCHAR2 DEFAULT 'Default') RETURN DATE;
    FUNCTION datumtijdplusuitvoertijd (begindatum IN DATE, uitvoertijd IN NUMBER, uitvoertijdtype IN VARCHAR2,
                      pBeginWerkUur IN NUMBER, pEindWerkUur IN NUMBER, pMode IN NUMBER, pCalendarId IN VARCHAR2 DEFAULT 'Default') RETURN DATE;
    FUNCTION datumtijdnaaruitvoertijd (begindatum IN DATE, einddatum IN DATE, uitvoertijdtype IN VARCHAR2, pCalendarId IN VARCHAR2 DEFAULT 'Default') RETURN MLD_T_UITVOERTIJD;
    FUNCTION datumtijdnaaruitvoertijd (begindatum IN DATE, einddatum IN DATE, uitvoertijdtype IN VARCHAR2,
                      pBeginWerkUur IN NUMBER, pEindWerkUur IN NUMBER, pMode IN NUMBER, pCalendarId IN VARCHAR2 DEFAULT 'Default') RETURN MLD_T_UITVOERTIJD;
    FUNCTION getdatemillisec (i_date IN DATE) RETURN NUMBER;
    FUNCTION usrrap_query (p_tabelnaam IN VARCHAR2) RETURN VARCHAR2;
    FUNCTION usrrap_orderby (p_tabelnaam IN VARCHAR2) RETURN VARCHAR2;
    FUNCTION has_funccode(p_funccode IN VARCHAR2, p_optional IN BOOLEAN, p_perslid_key IN NUMBER DEFAULT NULL) RETURN NUMBER;
    FUNCTION getobjectstatus(p_viewname IN VARCHAR2, r_objecttype OUT VARCHAR2, r_objectstatus OUT VARCHAR2, r_objectddltime OUT DATE) RETURN NUMBER;
    PROCEDURE view2columns (p_usrrap_key IN NUMBER, p_last_refresh IN DATE DEFAULT NULL);
    PROCEDURE tabelize_usrraps;
    FUNCTION import_delimiter(p_import_key IN NUMBER) RETURN VARCHAR2;
    PROCEDURE imp_getfield ( p_parseline  IN OUT   VARCHAR2,
                             p_fielddelimitor   IN       VARCHAR2,
                             p_field            OUT      VARCHAR2
                           );

    PROCEDURE imp_getfield_nr (p_parseline IN VARCHAR2,
                               p_fielddelimitor   IN       VARCHAR2,
                               p_nr               IN       NUMBER,
                               p_field            OUT      VARCHAR2
                            );

    PROCEDURE imp_writelog( p_import_key IN NUMBER,
                            pLevel IN VARCHAR2,
                            pMsg IN VARCHAR2,
                            pHint IN VARCHAR2
                          );

    PROCEDURE writelog( pAppl IN VARCHAR2,
                            pLevel IN VARCHAR2,
                            pMsg IN VARCHAR2,
                            pHint IN VARCHAR2
                          );

    PROCEDURE imp_alg_delete_onrgoed (p_import_key IN NUMBER, p_mode IN NUMBER);
    PROCEDURE imp_prs_delete_organisatie (p_import_key IN NUMBER, p_mode IN NUMBER);
    PROCEDURE imp_mld_delete_mld (p_import_key IN NUMBER, p_mode IN NUMBER);
    PROCEDURE fac_perfmon_incr(limiet IN NUMBER, zwaarte IN NUMBER);
    PROCEDURE putnotificationprio (pfrom NUMBER, pto NUMBER, pmessage VARCHAR2, pmode NUMBER,
                                   poptemail VARCHAR2, poptmobile VARCHAR2, pprio NUMBER,
                                   pattach VARCHAR2 DEFAULT NULL,
                                   pbedrijfadreskey NUMBER DEFAULT NULL,
                                   pqueue VARCHAR2 DEFAULT 'DEFAULT',
                                   pgeturl VARCHAR2 DEFAULT NULL);
    PROCEDURE putsystemnotification (pmessage VARCHAR2, pmode NUMBER);
    PROCEDURE putnotificationsrtprio (pfrom NUMBER, pto NUMBER, pcode VARCHAR2, pref NUMBER,
                                      poptmessage VARCHAR2, poptstatus NUMBER, poptemail VARCHAR2, poptmobile VARCHAR2, pxref NUMBER,
                                      pprio NUMBER, psender VARCHAR2, pattach VARCHAR2 DEFAULT NULL,
                                      pbedrijfadreskey NUMBER DEFAULT NULL, pqueue VARCHAR2 DEFAULT 'DEFAULT',
                                      pgeturl VARCHAR2 DEFAULT NULL);
    PROCEDURE clrnotifications (pcode VARCHAR2, pref NUMBER, pxref NUMBER DEFAULT NULL, puitz VARCHAR2 DEFAULT NULL, puitz2 VARCHAR2 DEFAULT NULL);
    PROCEDURE clrnotifications (pcode VARCHAR2, pref NUMBER, psubject VARCHAR2, preceiver NUMBER, pextrakey NUMBER);
    PROCEDURE clrnotifications_xmlnode (pxmlnode VARCHAR2, prefkey NUMBER);
    PROCEDURE clrtracking_xmlnode (pxmlnode VARCHAR2, prefkey NUMBER);
    PROCEDURE cleannotifications;
    FUNCTION hasColumn(ptableview VARCHAR2, pcolumname VARCHAR2, pdatatype VARCHAR2 DEFAULT NULL) RETURN VARCHAR2;
    PROCEDURE putnotificationjobs (pqueue VARCHAR2 DEFAULT 'DEFAULT');
    PROCEDURE putjobnotifications (pviewname VARCHAR2, pmode NUMBER, pflags NUMBER, pqueue VARCHAR2 DEFAULT 'DEFAULT');
    FUNCTION getEmail  ( pkey IN NUMBER ) RETURN VARCHAR2;
    FUNCTION getMobile ( pkey IN NUMBER ) RETURN VARCHAR2;
    PROCEDURE fac_hourly;
    PROCEDURE fac_nightly;
    PROCEDURE fac_daily;
    PROCEDURE fac_weekly;
    PROCEDURE fac_monthly;
    PROCEDURE fac_pre_putorders;
    PROCEDURE executeschedules;
    PROCEDURE markorderassent(pxmlnode VARCHAR2, pkey NUMBER, presult NUMBER, presulttext VARCHAR2, pperslid_key NUMBER DEFAULT NULL);
    PROCEDURE marknotiassent(pnotikey NUMBER, presult NUMBER, presulttext VARCHAR2, pperslid_key NUMBER DEFAULT NULL);
    FUNCTION getSetting (pname IN VARCHAR2 ) RETURN VARCHAR2;
    PROCEDURE trackaction (pcode VARCHAR2, prefkey NUMBER, puserkey NUMBER, pdatum DATE, poms VARCHAR2);
    PROCEDURE trackaction (pcode VARCHAR2, prefkey NUMBER, puserkey NUMBER, pdatum DATE, poms VARCHAR2, new_fac_tracking_key OUT NUMBER);
    PROCEDURE trackaction_withkey (pcode VARCHAR2, prefkey NUMBER, puserkey NUMBER, pdatum DATE, poms VARCHAR2, psubjectkey NUMBER, new_fac_tracking_key OUT NUMBER);
    PROCEDURE backtrackaction (pcode VARCHAR2, prefkey NUMBER, puserkey NUMBER, pdatum DATE);
    PROCEDURE notifytracking(psrtnotificatiekey NUMBER, pperslid_key NUMBER, ptracking_oms VARCHAR2, prefkey NUMBER, ptrackingkey NUMBER DEFAULT NULL);
    PROCEDURE notifytrackingbedrijven(ptrackingkey NUMBER, pnotidelay NUMBER DEFAULT NULL);
    PROCEDURE notifytrackingbedrijven(peventcode IN VARCHAR2, prefkey IN NUMBER, pnotidelay NUMBER DEFAULT NULL);
    PROCEDURE notifygeneriekbedrijven(prefkey NUMBER, ptrackingkey NUMBER, pnotidelay NUMBER DEFAULT NULL);
    PROCEDURE notifybedrijf(pbedrijf_key           NUMBER,
                            pbedrijfadres_type     VARCHAR2,
                            prefkey                NUMBER,
                            ptrackingkey           NUMBER DEFAULT NULL,
                            psrtnoti_key           NUMBER DEFAULT NULL, -- als geen trackingkey
                            pnotidelay             NUMBER DEFAULT NULL,
                            pxrefkey               NUMBER DEFAULT NULL,
                            preference             VARCHAR2 DEFAULT NULL,
                            psender                VARCHAR2 DEFAULT NULL,
                            plocatie_key           NUMBER DEFAULT NULL,
                            pdiscipline_key        NUMBER DEFAULT NULL,
                            ptypeopdr_key          NUMBER DEFAULT NULL,
                            pbedrijf_key2          NUMBER DEFAULT NULL);
    FUNCTION gettrackingdate (peventcode IN VARCHAR2, pkey IN number) RETURN date;
    FUNCTION gettrackinguserkey (peventcode IN VARCHAR2, pkey IN number) RETURN number;
    PROCEDURE initsession (flcode IN VARCHAR2);
    PROCEDURE initsession (pkey NUMBER, flcode IN VARCHAR2, usertimezone IN VARCHAR2, realuser IN VARCHAR2 DEFAULT NULL);
    PROCEDURE registerversion (pschema IN VARCHAR2, plang IN VARCHAR2);
    PROCEDURE registercustversion (pcustid IN VARCHAR2, pcustnr IN NUMBER);
    FUNCTION getdbversion RETURN VARCHAR2;
    FUNCTION getDefaultBehandelteam(p_stdm IN NUMBER) RETURN NUMBER;
    PROCEDURE processemail (p_from            IN VARCHAR2
                          , p_to              IN VARCHAR2
                          , p_subject         IN VARCHAR2
                          , p_body            IN VARCHAR2
                          , p_session_id      IN VARCHAR2
                          , p_email_set_key   IN NUMBER);
    FUNCTION isdatefeestdag(p_date IN DATE, pCalendarId IN VARCHAR2 DEFAULT 'Default') RETURN BOOLEAN;
    FUNCTION calcnextcyclusdate (p_date IN DATE, p_mode IN NUMBER, p_eenheid IN NUMBER, p_periode IN NUMBER, p_bits IN NUMBER, pCalendarId IN VARCHAR2 DEFAULT 'Default') RETURN DATE;
    FUNCTION nextcyclusdate (p_date IN DATE, p_mode IN NUMBER, p_eenheid IN NUMBER, p_periode IN NUMBER, p_bits IN NUMBER, p_steps IN NUMBER DEFAULT 0, p_ismjob IN NUMBER DEFAULT 0, pCalendarId IN VARCHAR2 DEFAULT 'Default') RETURN DATE;
    FUNCTION nextcyclusdatedeel (p_insdeel IN NUMBER, p_srtcontrole IN NUMBER, p_steps IN NUMBER DEFAULT 0, pCalendarId IN VARCHAR2 DEFAULT 'Default') RETURN DATE;
    FUNCTION nextcyclusdatesteps (p_date IN DATE, p_mode IN NUMBER, p_eenheid IN NUMBER, p_periode IN NUMBER, p_bits IN NUMBER, p_steps IN NUMBER DEFAULT 1, p_enddate IN DATE DEFAULT SYSDATE, pCalendarId IN VARCHAR2 DEFAULT 'Default') RETURN NUMBER;
    PROCEDURE createlikebookmark (pxmlnode VARCHAR2, prefkey NUMBER);
    PROCEDURE sequence_nextval(p_seqname IN VARCHAR2, p_initval IN NUMBER DEFAULT 1, p_initexpire IN DATE DEFAULT NULL);
    PROCEDURE remove_tracking(p_xmlnode IN VARCHAR2, p_refkey IN NUMBER);
    FUNCTION exceeds_profiel (p_bestelbedrag IN NUMBER, p_prs_key IN NUMBER, p_disc_key IN NUMBER) RETURN BOOLEAN;
    FUNCTION exceeds_pgb (p_opdrachtbedrag IN NUMBER, p_prs_key IN NUMBER, p_disc_key IN NUMBER, disc_params_pgb IN NUMBER) RETURN BOOLEAN;
    FUNCTION exceeds_agb (p_opdrachtbedrag IN NUMBER, p_kp_key IN NUMBER, p_disc_key IN NUMBER, disc_params_pgb IN NUMBER) RETURN BOOLEAN;
    PROCEDURE fac_cleanup(delayhours NUMBER DEFAULT 24);
    FUNCTION getnoteoftracking(p_tracking_key IN NUMBER) RETURN VARCHAR2;
    FUNCTION getnoteofnotification(p_notificatie_key IN NUMBER) RETURN VARCHAR2;
END fac;
/

CREATE OR REPLACE PACKAGE BODY fac AS

    FUNCTION remember_resetselectie (tablename IN VARCHAR2)
        RETURN BOOLEAN
    IS
        sessionid   VARCHAR2 (20);
    BEGIN
        sessionid := USERENV ('SESSIONID');

        DELETE FROM fac_selectie
              WHERE fac_selectie_node = sessionid AND fac_selectie_tabel = tablename;

        RETURN TRUE;
    EXCEPTION
        WHEN OTHERS
        THEN
            RETURN FALSE;
    END;

    FUNCTION remember_saveprimarykey (tablename IN VARCHAR2, primarykey IN NUMBER)
        RETURN BOOLEAN
    IS
    BEGIN
        INSERT INTO fac_selectie (fac_selectie_node, fac_selectie_tabel, fac_selectie_key)
            VALUES (USERENV ('SESSIONID'), tablename, primarykey);

        RETURN TRUE;
    EXCEPTION
        WHEN OTHERS
        THEN
            RETURN FALSE;
    END;

    -- Geef de nth Fac_Selectie_Key van de geselecteerde records
    -- terug. IndexNr begint met 1.
    FUNCTION remember_getprimarykey (tablename IN VARCHAR2, indexnr IN NUMBER)
        RETURN NUMBER
    IS
        CURSOR selectie
        IS
              SELECT DISTINCT fac_selectie_key
                FROM fac_selectie
               WHERE fac_selectie_node = USERENV ('SESSIONID') AND fac_selectie_tabel = tablename
            ORDER BY fac_selectie_key;

        selectierec   selectie%ROWTYPE;
        tmpindex      NUMBER;
        returnkey     NUMBER (10);
    BEGIN
        returnkey := NULL;
        tmpindex := 1;

        FOR selectierec IN selectie
        LOOP
            IF indexnr = tmpindex
            THEN
                returnkey := selectierec.fac_selectie_key;
                EXIT;
            END IF;

            tmpindex := tmpindex + 1;
        END LOOP;

        RETURN returnkey;
    END;

    FUNCTION remember_saveprimarykeyanddate (tablename    IN VARCHAR2,
                                             primarykey   IN NUMBER,
                                             datum        IN DATE)
        RETURN BOOLEAN
    IS
    BEGIN
        INSERT INTO fac_selectie (fac_selectie_node,
                                  fac_selectie_tabel,
                                  fac_selectie_key,
                                  fac_selectie_datum)
            VALUES (USERENV ('SESSIONID'),
                    tablename,
                    primarykey,
                    datum);

        RETURN TRUE;
    EXCEPTION
        WHEN OTHERS
        THEN
            RETURN FALSE;
    END;

    FUNCTION remember_getdate (tablename IN VARCHAR2, primarykey IN NUMBER)
        RETURN DATE
    IS
        returndate    DATE;

        CURSOR selectie
        IS
            SELECT DISTINCT fac_selectie_datum
              FROM fac_selectie
             WHERE     fac_selectie_node = USERENV ('SESSIONID')
                   AND fac_selectie_key = primarykey
                   AND fac_selectie_tabel = tablename;

        selectierec   selectie%ROWTYPE;
        tmpindex      NUMBER;
        returnkey     NUMBER (10);
    BEGIN
        returndate := NULL;

        FOR selectierec IN selectie
        LOOP
            returndate := selectierec.fac_selectie_datum;
            EXIT;
        END LOOP;

        RETURN returndate;
    END;

    -- This function is to be used instead of the native ADD_MONTHS function
    -- If 29, 30 or 31 date doesn't exist in the  month then de first of the next month is taken
    FUNCTION addMonths (pdate1 IN DATE, padd_mon IN NUMBER) RETURN DATE IS
      pdate2 date;
    BEGIN
      pdate2:= ADD_MONTHS(pdate1-1, padd_mon);
      IF LAST_DAY(pdate1) = pdate1 OR LAST_DAY(pdate2) = pdate2
      THEN
        RETURN pdate2+1;
      ELSE
        RETURN ADD_MONTHS(pdate1, padd_mon);
      END IF;
    END;

    -- This function is to be used instead of the native TO_NUMBER in situations
    -- where the value might be an invalid number like '206E9547' ('Numeric overflow')
    FUNCTION safe_To_Integer( str IN VARCHAR2 ) RETURN NUMBER IS
    BEGIN
     RETURN TO_NUMBER(REGEXP_SUBSTR(str, '^\d+') ); -- alleen leading cijfers
    EXCEPTION
     WHEN OTHERS THEN RETURN NULL;
    END;

    -- This function is to be used instead of the native TO_NUMBER in situations
    -- where the value might be an invalid number. This is the case for (FSN#203)
    -- flexible properties as in FAC.SAFE_TO_NUMBER(alg_onrgoedkenmerk_waarde)<200
    -- It returns NULL instead of an ORA1722:error (or even another error)
    FUNCTION safe_To_Number( str IN VARCHAR2 ) RETURN NUMBER IS
    BEGIN
     RETURN TO_NUMBER(str);
    EXCEPTION
     WHEN OTHERS THEN RETURN NULL;
    END;

    FUNCTION safe_To_Date( pchar IN VARCHAR2, pfmt IN VARCHAR2 ) RETURN DATE IS
       vdate  DATE;
       vyear  NUMBER;
    BEGIN
       vdate := TO_DATE(pchar, pfmt);
       vyear := EXTRACT(year FROM vdate);
       IF (vyear < 1000 OR vyear > 9999)
       THEN
          vdate := NULL;
       END IF;
       RETURN vdate;
    EXCEPTION
       WHEN OTHERS THEN RETURN NULL;
    END;

    FUNCTION safe_concat(pchar1 IN VARCHAR2, pchar2 IN VARCHAR2, pmaxlen NUMBER DEFAULT 4000) RETURN VARCHAR2
    IS
        v_result   VARCHAR2 (4000);
    BEGIN
        IF pchar1 IS NULL
        THEN
            RETURN SUBSTR(pchar2, 1, pmaxlen);
        END IF;
        v_result := pchar1 || SUBSTR (pchar2, 1, pmaxlen - LENGTH (pchar1));
        RETURN SUBSTR(v_result, 1, pmaxlen);
    END;

    -- Lever de andere waarde op als er een parameter NULL is
    FUNCTION safe_least ( p1 IN DATE, p2 IN DATE ) RETURN DATE
    IS
    BEGIN
     IF p1 IS NULL THEN
        RETURN p2;
     ELSIF p2 IS NULL THEN
        RETURN p1;
     ELSE
        RETURN LEAST(p1, p2);
     END IF;
    END;

    -- Levert het dow-nummer op (1-7), op een nls-onafhankelijke wijze
    FUNCTION getweekdaynum (d IN DATE) RETURN NUMBER IS
    BEGIN
        RETURN MOD (TO_CHAR (d, 'J') + 1, 7) + 1;
    END;

    -- Vertaalt het autorisatielevel (-1..9) voor het type ('ALG', 'PRS') naar een tekst
    FUNCTION getscopeleveltext (plevel IN NUMBER, ptype IN VARCHAR) RETURN VARCHAR
    IS
    BEGIN
       IF plevel = -1
       THEN
          RETURN lcl.l ('lcl_all');
       ELSIF plevel = 9
       THEN
          RETURN lcl.l ('fac_functie_auth_none');
       ELSIF ptype = 'ALG'
       THEN
          IF plevel = 0
          THEN
                RETURN lcl.l ('lcl_region');
          ELSIF plevel = 1
          THEN
                RETURN lcl.l ('lcl_district');
          ELSIF plevel = 2
          THEN
                RETURN lcl.l ('lcl_location');
          ELSIF plevel = 3
          THEN
                RETURN lcl.l ('lcl_building');
          ELSIF plevel = 4
          THEN
                RETURN lcl.l ('lcl_floor');
          ELSIF plevel = 5
          THEN
                RETURN lcl.l ('lcl_room');
          ELSE
                RETURN TO_CHAR (plevel);
          END IF;
       ELSIF ptype = 'PRS'
       THEN
          IF plevel = 0
          THEN
                RETURN lcl.l ('lcl_company');
          ELSIF plevel = 1
          THEN
                RETURN lcl.l ('lcl_dep_name_level1');
          ELSIF plevel = 2
          THEN
                RETURN lcl.l ('lcl_dep_name_level2');
          ELSIF plevel = 3
          THEN
                RETURN lcl.l ('lcl_dep_name_level3');
          ELSIF plevel = 4
          THEN
                RETURN lcl.l ('lcl_dep_name_level4');
          ELSIF plevel = 5
          THEN
                RETURN lcl.l ('lcl_dep_name_level5');
          ELSE
                RETURN TO_CHAR (plevel);
          END IF;
       ELSE
          RETURN TO_CHAR (plevel);
       END IF;
    END;

    /*
     * Deprecated. Gebruik de flx versie.
     */
    FUNCTION getdomeinwaarde (dkey IN NUMBER, waarde IN VARCHAR2, ignorewhenxmlnode IN NUMBER DEFAULT 0)
       RETURN VARCHAR2
    IS
    BEGIN
       RETURN flx.getdomeinwaarde (dkey, waarde, ignorewhenxmlnode);
    END;

    -- Sluit weekend en vrije dagen uit
    FUNCTION iswerkdag (begindatum IN DATE, pCalendarId IN VARCHAR2 DEFAULT 'Default')
       RETURN NUMBER
    AS
       dagvdweek    NUMBER;
       vrijedagen   NUMBER;
    BEGIN
       dagvdweek := fac.getweekdaynum(begindatum);

       IF dagvdweek = 1 OR dagvdweek = 7
       THEN
          RETURN 0;
       END IF;

       -- kijk of de begindatum op een vrije dag valt.
       SELECT COUNT ( * )
         INTO vrijedagen
         FROM mld_vrije_dagen
        WHERE mld_vrije_dagen_datum = TRUNC (begindatum)
          AND mld_vrije_dagen_id = pCalendarId;

       IF vrijedagen = 1
       THEN
          RETURN 0;
       ELSE
          RETURN 1;
       END IF;
    END;

    FUNCTION iswerktijd
       RETURN NUMBER
    AS
       lBeginWerkUur NUMBER;
       lEindWerkUur NUMBER;
    BEGIN
       lBeginWerkUur := fac.safe_to_number(fac.getsetting('fac_t_startofworkday'));
       lEindWerkUur := fac.safe_to_number(fac.getsetting('fac_t_endofworkday'));
       IF SYSDATE BETWEEN TRUNC(SYSDATE) + (lBeginWerkUur / 24)
                      AND TRUNC(SYSDATE) + (lEindWerkUur  / 24)
       THEN
          RETURN 1;
       ELSE
          RETURN 0;
       END IF;
    END;

    -- Return number of workdays between two dates, not including the startdate, weekends and holidays
    FUNCTION count_Work_Days0( pdate1 IN DATE , pdate2 IN DATE, pInclTime IN BOOLEAN, pCalendarId IN VARCHAR2 DEFAULT 'Default' ) RETURN NUMBER IS
    eikdag NUMBER;
    v_date_from DATE;
    v_date_to DATE;
    v_time_from NUMBER;
    v_time_to NUMBER;
    v_extra_day NUMBER;
    lbackwards BOOLEAN;
    v_week NUMBER;
    v_mod NUMBER;
    v_day_from NUMBER;
    v_day_to NUMBER;
    v_holidays NUMBER;
    BEGIN
     -- Remove time-part from dates (ie. set to 0:00) and set dates chronologically
     IF pdate1 > pdate2 THEN
       v_date_from := TRUNC(pdate2);
       v_date_to := TRUNC(pdate1);
       v_time_from := to_char(pdate2,'sssss');
       v_time_to := to_char(pdate1,'sssss');
       lbackwards := TRUE;
     ELSE
       v_date_from := TRUNC(pdate1);
       v_date_to := TRUNC(pdate2);
       v_time_from := to_char(pdate1,'sssss');
       v_time_to := to_char(pdate2,'sssss');
       lbackwards := FALSE;
     END IF;

     -- Determine whole weeks (v_week) and remaining days (v_mod) between FROM and TO
     v_week := trunc((v_date_to - v_date_from)/7);
     v_mod := mod((v_date_to - v_date_from),7);

     -- Correct remaining days (v_mod) depending on the FROM and TO "day-number"
     v_day_from := getweekdaynum(v_date_from);
     v_day_to := getweekdaynum(v_date_to);
     IF v_day_from = 1 THEN               /* FROM is sunday */
       IF v_day_to = 7 THEN               /* TO is saturday */
         v_mod := v_mod - 1;              /* should always be 5! */
       END IF;
     ELSIF v_day_from = 7 THEN            /* FROM is saturday */
       IF v_day_to != 7 THEN              /* TO is not saturday */
         v_mod := v_mod - 1;              /* exclude sunday succeeding FROM-saturday */
       END IF;
     ELSE                                 /* FROM is workday */
       IF v_day_to = 7 THEN               /* TO is saturday */
         v_mod := v_mod - 1;              /* exclude TO-saturday */
       ELSIF v_day_to < v_day_from THEN   /* weekend between FROM and TO */
         v_mod := v_mod - 2;              /* exclude weekend */
       END IF;
     END IF;

     -- Tbv. incltime-variant geldt extra dag als TO-tijd > FROM-tijd, behalve als TO
     -- in het weekend of op een vrije dag valt!
     v_extra_day := 0;
     IF pInclTime THEN
       SELECT COUNT(*)
         INTO v_extra_day
         FROM DUAL
        WHERE v_time_to > v_time_from
          AND v_day_to NOT IN (1, 7)
          AND NOT EXISTS
                (SELECT 1
                   FROM MLD_VRIJE_DAGEN
                  WHERE TRUNC (MLD_VRIJE_DAGEN_DATUM) = v_date_to
                    AND mld_vrije_dagen_id = pCalendarId
                );
     END IF;

     -- Determine the registered holidays between FROM+1 (excluding startdate!) and TO
     SELECT count(*)
       INTO v_holidays
       FROM MLD_VRIJE_DAGEN
      WHERE MLD_VRIJE_DAGEN_DATUM BETWEEN v_date_from+1 AND v_date_to
        AND mld_vrije_dagen_id = pCalendarId
        AND getweekdaynum(MLD_VRIJE_DAGEN_DATUM) NOT IN (1, 7); -- Weekenden worden al niet meegeteld

     IF lbackwards THEN
       RETURN  0 - (5 * v_week + v_mod + v_extra_day - v_holidays);
     ELSE
       RETURN  5 * v_week + v_mod + v_extra_day - v_holidays;
     END IF;
    END;

   FUNCTION count_Work_Days_InclTime( pdate1 IN DATE , pdate2 IN DATE, pCalendarId IN VARCHAR2 DEFAULT 'Default' ) RETURN NUMBER IS
   	BEGIN
    	RETURN count_Work_Days0(pdate1,pdate2, TRUE, pCalendarId);
    END;

    FUNCTION count_Work_Days( pdate1 IN DATE , pdate2 IN DATE, pCalendarId IN VARCHAR2 DEFAULT 'Default' ) RETURN NUMBER IS
   	BEGIN
    	RETURN count_Work_Days0(pdate1,pdate2, FALSE, pCalendarId);
    END;

   /*
   ** Bepaal een nieuwe datum van de begindatum waarbij de uitvoertijd is
   ** opgeteld. De uitvoertijd (UitvoerTijdType) kan in 'UREN' of in 'DAGEN' zijn.
   ** Er wordt rekening gehouden met Werkuren, BeginWerkTijd en EindWerkTijd en
   ** Weekenddagen (Zondag = 1, Zaterdag = 7) en Vrije dagen (MLD_VRIJE_DAGEN).
   ** Testscript beschikbaar: /SRC/SQL/_UTIL/test_datumtijd.sql.
   */
    FUNCTION datumtijdplusuitvoertijd (begindatum IN DATE, uitvoertijd IN NUMBER, uitvoertijdtype IN VARCHAR2, pCalendarId IN VARCHAR2 DEFAULT 'Default') RETURN DATE
    IS
    BEGIN
      IF BeginWerkUur IS NULL OR EindWerkUur IS NULL
      THEN
          BeginWerkUur := fac.safe_to_number (fac.getsetting ('fac_t_startofworkday')); -- half 8 is 7.5 en niet 7.30
          EindWerkUur  := fac.safe_to_number (fac.getsetting ('fac_t_endofworkday'));
          DBMS_OUTPUT.put_line ('Recaching Werkuren ' || BeginWerkUur||'..'||EindWerkUur);
      END IF;
      RETURN datumtijdplusuitvoertijd (begindatum, uitvoertijd, uitvoertijdtype,
                      BeginWerkUur, EindWerkUur, 1, pCalendarId); -- 1=alleen werkdagen excl. vrije dagen
    END;

   -- Bereken de einddatum gegeven de begindatum en uitvoertijd. De mode bepaalt
   -- hoe dagen/tijden meetellen als volgt:
   -- 0 => alle dagen van de week excl. vrije dagen en uren tussen begin- en eindtijd
   -- 1 => alleen werkdagen excl. vrije dagen en uren tussen begin- en eindtijd
   -- 2 => 24/7 => alle 7 dagen van de week incl. vrije dagen en alle 24 uren per dag
   FUNCTION datumtijdplusuitvoertijd (begindatum        IN DATE,
                                      uitvoertijd       IN NUMBER,
                                      uitvoertijdtype   IN VARCHAR2,
                                      pBeginWerkUur     IN NUMBER,
                                      pEindWerkUur      IN NUMBER,
                                      pMode             IN NUMBER,
                                      pCalendarId       IN VARCHAR2 DEFAULT 'Default')
      RETURN DATE
   IS
      returnval         DATE;
      eikdag            NUMBER;
      vrijedag          NUMBER;
      datumbegin        DATE;
      dagenperweek      NUMBER;
      weekdagbegin      NUMBER;
      olddatumgereed    DATE;
      newdatumgereed    DATE;
      delta             NUMBER;
      aantalwerkweken   NUMBER;
      aantaldagenover   NUMBER;
      weekdaggereed     NUMBER;
      urenperdag        NUMBER (7, 4);  -- 4 decimalen i.v.m. afrondingsfouten
      aantalwerkdagen   NUMBER;
      aantalurenover    NUMBER;
      beginuur          NUMBER (7, 4);  -- 4 decimalen i.v.m. afrondingsfouten
      gereeduur         NUMBER (7, 4);  -- 4 decimalen i.v.m. afrondingsfouten
   BEGIN
      returnval := NULL;

      IF     begindatum IS NOT NULL
         AND uitvoertijd IS NOT NULL
         AND pMode BETWEEN 0 AND 2
      THEN
         IF pMode = 2
         THEN
            -- Zo simpel kan het zijn (igv. 24/7)!
            IF uitvoertijdtype IN ('D', 'DAGEN')
            THEN
               returnval := begindatum + uitvoertijd;
            ELSIF uitvoertijdtype IN ('U', 'UREN')
            THEN
               returnval := begindatum + (uitvoertijd / 24);
            ELSE
               returnval := NULL;
            END IF;
         ELSIF     pBeginWerkUur BETWEEN 0 AND 24
               AND pEindWerkUur BETWEEN 0 AND 24
               AND pEindWerkUur > pBeginWerkUur
         THEN
            datumbegin := begindatum;
            LOOP
               -- Verschuif datumbegin als deze valt
               -- [a] in het weekend (alleen als pMode = 1)
               -- [b] buiten de meegegeven uren, hier geldt Kantoortijden- of Openingstijden-regime!
               -- [c] op een vrije dag
               IF pMode = 1
               THEN
                  -- Verschuif (gewijzigde) datumbegin als deze in het weekend valt.
                  weekdagbegin := fac.getweekdaynum (datumbegin); -- zo=1 t/m za=7

                  IF weekdagbegin = 1
                  THEN
                     -- Als zo=1, dan schuif 1 dag door naar maandag 'pBeginWerkUur'.
                     datumbegin := TRUNC (datumbegin + 1) + (pBeginWerkUur / 24);
                     CONTINUE;
                  ELSIF weekdagbegin = 7
                  THEN
                     -- Als za=7, dan schuif 2 dagen door naar maandag 'pBeginWerkUur'.
                     datumbegin := TRUNC (datumbegin + 2) + (pBeginWerkUur / 24);
                     CONTINUE;
                  END IF;
               END IF;

               SELECT COUNT ( * )
                 INTO vrijedag
                 FROM mld_vrije_dagen
                WHERE mld_vrije_dagen_datum = TRUNC (datumbegin)
                  AND mld_vrije_dagen_id = pCalendarId;

               IF (datumbegin > TRUNC (datumbegin) + (pEindWerkUur / 24) OR vrijedag = 1)
               THEN
                  -- Als begintijd na 'pEindWerkUur', dan naar 'pBeginWerkUur' volgende dag;
                  -- hetzelfde als begindag is een vrije dag.
                  -- En als deze volgende dag is een vrije dag, dan volgt vanzelf compensatie!
                  datumbegin := TRUNC (datumbegin + 1) + (pBeginWerkUur / 24);
                  CONTINUE;
               ELSIF (datumbegin < TRUNC (datumbegin) + (pBeginWerkUur / 24))
               THEN
                  -- Als begintijd voor 'pBeginWerkUur', dan naar 'pBeginWerkUur' huidige dag.
                  datumbegin := TRUNC (datumbegin) + (pBeginWerkUur / 24);
                  CONTINUE;
               END IF;
               EXIT;
            END LOOP;

            IF pMode = 0
            THEN
               -- Alle dagen van de week excl. vrije dagen (= alle dagen-variant van
               -- Openingstijden-regime)!
               dagenperweek := 7;
            ELSE                                                  -- pMode = 1
               -- Alleen werkdagen excl. vrije dagen (= Kantoortijden-regime en alleen
               -- werkdagen-variant van Openingstijden-regime)!
               dagenperweek := 5;
            END IF;

            IF uitvoertijdtype IN ('D', 'DAGEN')
            THEN
               newdatumgereed := datumbegin;
               delta := uitvoertijd;

               -- Herhaal onderstaande zolang er een delta is:
               -- [a] bereken datumgereed = datumbegin + delta-werkdagen.
               -- [b] bepaal nieuwe delta = vrije dagen tussen datumbegin en datumgereed.
               LOOP
                  olddatumgereed := newdatumgereed;
                  weekdagbegin := fac.getweekdaynum (olddatumgereed); -- ma=2 t/m vr=6
                  aantalwerkweken := TRUNC (delta / dagenperweek);
                  aantaldagenover := delta MOD dagenperweek;
                  -- Een/elke werkweek zorgt voor een doorlooptijd van 7 kalenderdagen.
                  newdatumgereed := olddatumgereed + (aantalwerkweken * 7) + aantaldagenover;

                  -- Verschuif newdatumgereed als gereedtijd buiten de meegegeven uren valt.
                  -- LET OP: Komt alleen voor als uitvoertijd decimalen bevat! Kan dat?
                  IF (newdatumgereed < TRUNC (newdatumgereed) + (pBeginWerkUur / 24))
                  THEN
                     -- Als gereedtijd voor 'pBeginWerkUur', dan naar 'pBeginWerkUur' huidige dag.
                     newdatumgereed := TRUNC (newdatumgereed) + (pBeginWerkUur / 24);
                  ELSIF (newdatumgereed > TRUNC (newdatumgereed) + (pEindWerkUur / 24))
                  THEN
                     -- Als gereedtijd na 'pEindWerkUur', dan naar 'pBeginWerkUur' volgende dag.
                     newdatumgereed := TRUNC (newdatumgereed + 1) + (pBeginWerkUur / 24);
                  END IF;

                  -- Compenseer eventueel (eind)weekend afhankelijk van 'aantaldagenover'.
                  weekdaggereed := fac.getweekdaynum (newdatumgereed); -- zo=1 t/m za=7

                  IF     pMode = 1
                     AND (weekdaggereed = 7 OR weekdagbegin > weekdaggereed)
                     AND delta <> 0
                  THEN
                     -- Als za=7 of weekdagbegin > weekdaggereed, dan 2 dagen optellen!
                     newdatumgereed := newdatumgereed + 2;
                  END IF;

                  -- Bepaal nieuwe delta.
                  SELECT COUNT ( * )
                    INTO delta
                    FROM (SELECT * FROM mld_vrije_dagen WHERE mld_vrije_dagen_id = pCalendarId
                          MINUS -- Igv. pMode=1 za+zo niet meetellen; deze worden sowieso geskipt!
                          SELECT * FROM mld_vrije_dagen
                           WHERE pMode = 1 AND fac.getweekdaynum (mld_vrije_dagen_datum) IN (1,7)
                             AND mld_vrije_dagen_id = pCalendarId
                         )
                   WHERE mld_vrije_dagen_datum BETWEEN LEAST(TRUNC (olddatumgereed + 1), TRUNC (newdatumgereed)) AND TRUNC (newdatumgereed);

                  EXIT WHEN delta = 0;
               END LOOP;

               returnval := newdatumgereed;
            ELSIF uitvoertijdtype IN ('U', 'UREN')
            THEN
               -- Bepaal gereedtijd, dan hoeft daarna alleen nog maar in werkdagen te worden geschoven!
               urenperdag := pEindWerkUur - pBeginWerkUur;
               aantalwerkdagen := TRUNC (uitvoertijd / urenperdag); -- werkdagen
               aantalurenover := uitvoertijd MOD urenperdag;      -- rest uren
               beginuur := (datumbegin - TRUNC (datumbegin)) * 24;
               gereeduur := beginuur + aantalurenover;

               IF (gereeduur > pEindWerkUur)
               THEN
                  -- Als gereeduur na 'pEindWerkUur', dan corrigeren en dus een extra dag.
                  gereeduur := pBeginWerkUur + aantalurenover - (pEindWerkUur - beginuur);
                  aantalwerkdagen := aantalwerkdagen + 1; -- inclusief extra dag
               END IF;

               newdatumgereed := datumtijdplusuitvoertijd (datumbegin, aantalwerkdagen, 'DAGEN', pBeginWerkUur, pEindWerkUur, pMode, pCalendarId);

               --Belangrijk: nu nog de eerder bepaalde gereedtijd zetten!
               returnval := TRUNC (newdatumgereed) + (gereeduur / 24);
            END IF;
         END IF;
      END IF;
      RETURN returnval;
   END;

    -- datumtijdnaaruitvoertijd is bedoeld als 100% de inverse van datumtijdplusuitvoertijd
    -- Invariant:
    --      datumtijdplusuitvoertijd(begin, datumtijdnaaruitvoertijd(begin, eind, type), type) == eind
    --      datumtijdnaaruitvoertijd(begin, datumtijdplusuitvoertijd(begin, uitvoertijd, type), type) == uitvoertijd
    -- Is de luxe versie van count_Work_Days
    FUNCTION datumtijdnaaruitvoertijd (begindatum IN DATE, einddatum IN DATE, uitvoertijdtype IN VARCHAR2, pCalendarId IN VARCHAR2 DEFAULT 'Default') RETURN MLD_T_UITVOERTIJD
    AS
    BEGIN
      IF BeginWerkUur IS NULL OR EindWerkUur IS NULL
      THEN
          BeginWerkUur := fac.safe_to_number (fac.getsetting ('fac_t_startofworkday')); -- half 8 is 7.5 en niet 7.30
          EindWerkUur  := fac.safe_to_number (fac.getsetting ('fac_t_endofworkday'));
          DBMS_OUTPUT.put_line ('Recaching Werkuren ' || BeginWerkUur||'..'||EindWerkUur);
      END IF;
      RETURN datumtijdnaaruitvoertijd (begindatum, einddatum, uitvoertijdtype,
                      BeginWerkUur, EindWerkUur, 1, pCalendarId); -- 1=alleen werkdagen excl. vrije dagen
    END datumtijdnaaruitvoertijd;

    FUNCTION datumtijdnaaruitvoertijd (begindatum        IN DATE,
                                       einddatum         IN DATE,
                                       uitvoertijdtype   IN VARCHAR2,
                                       pBeginWerkUur     IN NUMBER,
                                       pEindWerkUur      IN NUMBER,
                                       pMode             IN NUMBER,
                                       pCalendarId       IN VARCHAR2 DEFAULT 'Default')
       RETURN MLD_T_UITVOERTIJD
    AS
       uitvoertijd   mld_stdmelding.mld_stdmelding_t_uitvoertijd%TYPE := MLD_T_UITVOERTIJD(NULL, NULL); -- constructor initialisatie
       urenperdag    NUMBER (10, 3);
       eerstedag     NUMBER (10, 3);
       laatstedag    NUMBER (10, 3);
       heledagen     NUMBER (10);
       vrijedagen    NUMBER;
    BEGIN
       IF     begindatum IS NOT NULL
          AND einddatum IS NOT NULL
          AND pMode BETWEEN 0 AND 2
       THEN
          IF begindatum > einddatum
          THEN
             uitvoertijd := datumtijdnaaruitvoertijd (einddatum, begindatum, uitvoertijdtype, pBeginWerkUur, pEindWerkUur, pMode);
             uitvoertijd.tijdsduur := -uitvoertijd.tijdsduur;
          ELSIF pMode = 2
          THEN
             -- Zo simpel kan het zijn igv. 24/7!
             IF uitvoertijdtype IN ('D', 'DAGEN')
             THEN
                -- Per 5.4.3 kunnen hier gebroken dagen worden teruggegeven!
                uitvoertijd.tijdsduur := einddatum - begindatum;
                uitvoertijd.eenheid := 'D';
             ELSE                  -- default/uitvoertijdtype IN ('U', 'UREN')
                uitvoertijd.tijdsduur := (einddatum - begindatum) * 24;
                uitvoertijd.eenheid := 'U';
             END IF;
          ELSIF     pBeginWerkUur BETWEEN 0 AND 24
                AND pEindWerkUur BETWEEN 0 AND 24
                AND pEindWerkUur > pBeginWerkUur
          THEN
             urenperdag := pEindWerkUur - pBeginWerkUur;
             eerstedag := pEindWerkUur - (TRUNC (begindatum, 'MI') - TRUNC (begindatum)) * 24;
             eerstedag := LEAST (GREATEST (0, eerstedag), urenperdag);
             laatstedag := (TRUNC (einddatum, 'MI') - TRUNC (einddatum)) * 24 - pBeginWerkUur;
             laatstedag := LEAST (GREATEST (0, laatstedag), urenperdag);

             IF pMode = 0
             THEN
                -- Oppassen als begin/eind op een vrije dag valt,
                -- dan moeten eerste/laatste dag gewoon 0 uur zijn.
                SELECT COUNT ( * )
                  INTO vrijedagen
                  FROM mld_vrije_dagen
                 WHERE mld_vrije_dagen_datum = TRUNC (begindatum)
                   AND mld_vrije_dagen_id = pCalendarId;

                IF vrijedagen = 1
                THEN
                   eerstedag := 0;
                END IF;

                SELECT COUNT ( * )
                  INTO vrijedagen
                  FROM mld_vrije_dagen
                 WHERE mld_vrije_dagen_datum = TRUNC (einddatum)
                   AND mld_vrije_dagen_id = pCalendarId;

                IF vrijedagen = 1
                THEN
                   laatstedag := 0;
                END IF;

                IF TRUNC (begindatum) = TRUNC (einddatum)
                THEN
                   IF eerstedag + laatstedag > 0            -- geen vrije dag!
                   THEN
                      -- Als begindatum=einddatum én werkdag, dan 1 dag corrigeren!
                      heledagen := -1;
                   ELSE
                      -- Als begindatum=einddatum én vrije dag, dan geen correctie!
                      heledagen := 0;
                   END IF;
                ELSE
                   -- Als begindatum<>einddatum, dan alleen nog vrije dagen tussen
                   -- begin+1 en eind-1 corrigeren (en altijd 0 als begin+1=eind)!
                   SELECT COUNT ( * )
                     INTO vrijedagen
                     FROM mld_vrije_dagen
                    WHERE mld_vrije_dagen_datum BETWEEN TRUNC (begindatum + 1) AND einddatum - 1
                      AND mld_vrije_dagen_id = pCalendarId;

                   IF TRUNC (begindatum+1) = TRUNC (einddatum)
                   THEN
                      heledagen := 0;
                   ELSE
                      heledagen := TRUNC (einddatum) - TRUNC (begindatum + 1) - vrijedagen;
                   END IF;
                END IF;
             ELSE                                                 -- pMode = 1
                -- Oppassen als begin/eind in het weekend of zo valt,
                -- dan moeten eerste/laatste dag gewoon 0 uur zijn.
                IF iswerkdag (begindatum) = 0
                THEN
                   eerstedag := 0;
                END IF;

                IF iswerkdag (einddatum) = 0
                THEN
                   laatstedag := 0;
                   heledagen := fac.count_Work_Days (TRUNC (begindatum), TRUNC (einddatum), pCalendarId);
                ELSE
                   heledagen := fac.count_Work_Days (TRUNC (begindatum), TRUNC (einddatum), pCalendarId) - 1;
                END IF;
             END IF;

             DBMS_OUTPUT.put_line ('Eerstedag ' || TO_CHAR (eerstedag));
             DBMS_OUTPUT.put_line ('Laatstedag ' || TO_CHAR (laatstedag));
             DBMS_OUTPUT.put_line ('Heledagen ' || TO_CHAR (heledagen));

             IF uitvoertijdtype IN ('D', 'DAGEN')
             THEN
                -- Per 5.4.3 kunnen hier gebroken dagen worden teruggegeven!
                uitvoertijd.tijdsduur := (eerstedag + laatstedag + heledagen * urenperdag) / urenperdag;
                uitvoertijd.eenheid := 'D';
             ELSE                  -- default/uitvoertijdtype IN ('U', 'UREN')
                uitvoertijd.tijdsduur := eerstedag + laatstedag + heledagen * urenperdag;
                uitvoertijd.eenheid := 'U';
             END IF;
          END IF;
       END IF;
       RETURN uitvoertijd;
    END datumtijdnaaruitvoertijd;

    FUNCTION getdatemillisec (i_date IN DATE)
        RETURN NUMBER
    IS
    BEGIN
        RETURN TO_NUMBER (TO_DATE (TO_CHAR ( (CAST (i_date AS TIMESTAMP WITH TIME ZONE)) AT TIME ZONE 'gmt', 'yyyy.mm.dd hh24:mi:ss'), 'yyyy.mm.dd hh24:mi:ss')
                          - TO_DATE ('01-01-1970', 'dd-mm-yyyy'))
               * (24 * 60 * 60 * 1000);
    END;

   FUNCTION usrrap_query ( p_tabelnaam IN VARCHAR2 ) RETURN VARCHAR2 IS
     QueryString VARCHAR2(4000);
     Kolomnaam  VARCHAR2(64);
     mainuser   VARCHAR2(80);
   BEGIN
     BEGIN
       DECLARE
         CURSOR Zoek_label IS
         SELECT column_name
         ,      data_type
         FROM   user_tab_columns
         WHERE  table_name = UPPER(p_tabelnaam)
         ORDER BY COLUMN_ID;

       BEGIN

         QueryString := 'Select ';
         FOR LabelRec in zoek_label LOOP

             IF LabelRec.data_type = 'NUMBER'
             THEN
               QueryString := QueryString ||'to_number(TO_CHAR('|| LabelRec.column_name ||',''99999999D9999'')) "'||LabelRec.column_name||'",';
             ELSE
               QueryString := QueryString ||' ' || LabelRec.column_name ||',';
             END IF;

         END LOOP;
       END;
     END;
       QueryString := Substr(QueryString,1,(LENGTH(QueryString)-1))||' from '|| p_tabelnaam ;
     RETURN QueryString;
   END usrrap_query;


  FUNCTION usrrap_orderby ( p_tabelnaam IN VARCHAR2 ) RETURN VARCHAR2 IS
    AantalRecords  NUMBER(10);
    OrderBy        VARCHAR2(255);
    mainuser       VARCHAR2(80);
    i              NUMBER(10);

  BEGIN
    SELECT count(*)
      INTO   AantalRecords
      FROM   user_tab_columns
      WHERE  table_name = UPPER(p_tabelnaam);
  --
    OrderBy := '';
    i := 1;

    FOR i IN 1..AantalRecords LOOP
        OrderBy := OrderBy||TO_CHAR(i)||',';
    END LOOP;
    OrderBy := SUBSTR(OrderBy,1,(LENGTH(OrderBy)-1));
    RETURN ' ORDER BY ' || OrderBy;
  END usrrap_orderby;

  FUNCTION has_funccode( p_funccode    IN VARCHAR2
                       , p_optional    IN BOOLEAN
                       , p_perslid_key IN NUMBER   DEFAULT NULL
                       ) RETURN NUMBER
  AS
      v_func_enabled     NUMBER;
      v_prs_perslid_key  prs_perslid.prs_perslid_key%TYPE;
  BEGIN
      IF (p_perslid_key IS NULL)
      THEN
          BEGIN
              SELECT TO_NUMBER(sys_context('USERENV', 'CLIENT_IDENTIFIER'))
                INTO v_prs_perslid_key
                FROM dual;
          EXCEPTION
              WHEN NO_DATA_FOUND
              THEN v_prs_perslid_key := NULL;
          END;
      END IF;

      IF (v_prs_perslid_key IS NULL)
      THEN
          v_func_enabled := 1;
      ELSE
          SELECT CASE WHEN COALESCE(MIN(fac_gebruiker_prs_level_read),  9) < 0
                        OR COALESCE(MIN(fac_gebruiker_prs_level_write), 9) < 0
                        OR COALESCE(MIN(fac_gebruiker_alg_level_read),  9) < 0
                        OR COALESCE(MIN(fac_gebruiker_prs_level_write), 9) < 0
                      THEN 1
                      ELSE 0
                 END
            INTO v_func_enabled
            FROM fac_v_webgebruiker w
               , fac_functie f
           WHERE w.fac_functie_key = f.fac_functie_key
             AND w.prs_perslid_key = v_prs_perslid_key
             AND f.fac_functie_code = p_funccode;

           IF (v_func_enabled = 0 AND p_optional)
           THEN
              v_func_enabled := NULL;
           END IF;
       END IF;

       RETURN v_func_enabled;
  END;

  FUNCTION getobjectstatus( p_viewname      IN  VARCHAR2
                          , r_objecttype    OUT VARCHAR2
                          , r_objectstatus  OUT VARCHAR2
                          , r_objectddltime OUT DATE
                          ) RETURN NUMBER
  AS
      v_hasFACFAC  NUMBER;
  BEGIN
      v_hasFACFAC := has_funccode('WEB_FACFAC', True); -- Die mag ook tabellen doen.
      BEGIN
      SELECT object_type
           , last_ddl_time
           , status
        INTO r_objecttype
           , r_objectddltime
           , r_objectstatus
        FROM user_objects
       WHERE object_type IN ( SELECT 'VIEW' FROM DUAL
                               UNION
                              SELECT 'TABLE' FROM DUAL WHERE v_hasFACFAC = 1
                            )
         AND object_name = UPPER(p_viewname);
       RETURN 0;
      EXCEPTION
          WHEN NO_DATA_FOUND THEN RETURN -1;
      END;
  END;

  PROCEDURE view2columns ( p_usrrap_key   IN NUMBER
                         , p_last_refresh IN DATE   DEFAULT NULL
                         )
  AS
      CURSOR old_rap
      IS
          SELECT fac_usrrap_cols_key          column_id
               , fac_usrrap_cols_volgnr       column_seq
               , fac_usrrap_cols_column_name  column_name
               , fac_usrrap_cols_caption      column_caption
               , fac_usrrap_cols_filter       column_filter
               , fac_usrrap_cols_datatype     column_datatype
            FROM fac_usrrap_cols
           WHERE fac_usrrap_key = p_usrrap_key
             AND (fac_usrrap_cols_filter IS NULL OR fac_usrrap_cols_filter <> 'P')  -- De parameters wil ik behouden met een refresh.
           ORDER BY fac_usrrap_cols_volgnr NULLS LAST;

      CURSOR new_rap(cp_viewname VARCHAR2)
      IS
          SELECT LOWER(column_name)  column_name
               , CASE WHEN REGEXP_SUBSTR(UPPER(column_name), '^FCLT_C_') IS NOT NULL THEN 'currency'
                      WHEN REGEXP_SUBSTR(UPPER(column_name), '^HTML_')   IS NOT NULL THEN 'html'
                      WHEN data_type = 'NUMBER' AND data_scale > 0 THEN 'float'
                      WHEN data_type = 'NUMBER' THEN 'number'
                      WHEN data_type = 'DATE'  THEN 'date'
                      ELSE 'varchar'
                 END column_datatype
               , column_id
               , NULL                 column_caption
               , 'V'                  column_visible
               , NULL                 column_filter
            FROM user_tab_columns
           WHERE table_name = UPPER(cp_viewname)
           ORDER BY column_id;

      TYPE t_rap_col IS VARRAY(200) OF old_rap%ROWTYPE;


      v_error_nr        NUMBER;
      v_viewname        fac_usrrap.fac_usrrap_view_name%TYPE;
      v_procname        fac_usrrap.fac_usrrap_view_name%TYPE;
      v_function        fac_usrrap.fac_usrrap_functie%TYPE;
      v_exec_ddl        VARCHAR2(4000);
      v_isview          NUMBER;
      v_hasFACFAC       NUMBER;
      v_object_type     user_objects.object_type%TYPE;
      v_object_ddltime  user_objects.last_ddl_time%TYPE;
      v_object_status   user_objects.status%TYPE;
      v_old_cols        NUMBER;
      v_found           BOOLEAN;
      v_special_col     VARCHAR2(20);
      v_column_name     fac_usrrap_cols.fac_usrrap_cols_column_name%TYPE;
      v_column_caption  fac_usrrap_cols.fac_usrrap_cols_caption%TYPE;
      v_column_visible  fac_usrrap_cols.fac_usrrap_cols_visible%TYPE;
      v_column_filter   fac_usrrap_cols.fac_usrrap_cols_filter%TYPE;

      v_rap_cols        t_rap_col := t_rap_col();
      v_column_prev     fac_usrrap_cols.fac_usrrap_cols_column_name%TYPE;
      v_newkey          fac_usrrap_cols.fac_usrrap_cols_key%TYPE;
      v_index           NUMBER := 0;
      v_is_param        NUMBER;

  BEGIN
      BEGIN
          SELECT SUBSTR(fac_usrrap_view_name, INSTR(fac_usrrap_view_name, '.')+1) viewname
               , CASE WHEN INSTR(fac_usrrap_view_name, '.') > 0 THEN fac_usrrap_view_name ELSE NULL END procname
               , fac_usrrap_functie
               , CASE WHEN BITAND(fac_usrrap_functie, 2) = 0 THEN 1 ELSE 0 END isview
            INTO v_viewname
               , v_procname
               , v_function
               , v_isview
            FROM fac_usrrap
           WHERE fac_usrrap_key = p_usrrap_key;

          IF (v_function = 3)
          THEN
              -- Bij styling optie Procedure+gestyled moet ook de view aanwezig zijn.
              SELECT COUNT(*)
                INTO v_isview
                FROM user_objects
               WHERE object_type IN ('VIEW','TABLE')
                 AND object_name = UPPER(v_viewname);
          END IF;

          IF (v_isview = 1)
          THEN
              -- Als het rapport alleen op een procedure gebaseerd is hebben we hier niets te zoeken.
              -- Een Procedure+gestyled dus wel.
              v_error_nr := getobjectstatus(v_viewname, v_object_type, v_object_status, v_object_ddltime);
              IF (v_error_nr = 0)
              THEN
                  IF (v_object_status <> 'VALID' AND v_object_type = 'VIEW') -- Gebeurt nog wel eens na een dump-refresh
                  THEN
                      BEGIN
                          dbms_output.put_line('Auto compile invalid ' || v_viewname);
                          EXECUTE IMMEDIATE 'ALTER VIEW ' || v_viewname || ' COMPILE';
                      EXCEPTION
                          WHEN OTHERS THEN NULL;
                          -- Als hercompileren fout gaat dan hier nog geen foutmelding geven. Later halen we alsnog de status op.
                      END;
                      v_error_nr := getobjectstatus(v_viewname, v_object_type, v_object_status, v_object_ddltime); -- Tweede poging
                  END IF;
              ELSE
                  raise_application_error(-20001, 'Unable to determine columns for report view '|| v_viewname ||'.');
              END IF;

              IF (v_object_status <> 'VALID')
              THEN
                  raise_application_error(-20001, 'Report view '|| v_viewname ||' is INVALID!');
              END IF;

              SELECT COUNT(*)
                INTO v_old_cols
                FROM fac_usrrap_cols
               WHERE fac_usrrap_key = p_usrrap_key;

              IF (v_old_cols = 0 OR p_last_refresh IS NULL OR v_object_ddltime > p_last_refresh)
              THEN
                  -- Vervallen kolommen
                  IF (p_last_refresh IS NOT NULL)
                  THEN
                      -- Nieuwe (geimporteerde) report heeft geen vervallen kolommen, maar nieuwe kolommen moeten wel toegevoegd worden.
                      FOR c_old IN old_rap
                      LOOP
                          v_found := False;
                          FOR c_new IN new_rap(v_viewname)
                          LOOP
                              IF (c_new.column_name = c_old.column_name) THEN
                                  v_found := True;
                              END IF;
                          END LOOP;
                          IF (v_found = False)
                          THEN
                              dbms_output.put_line('Dropping column ' || c_old.column_name);
                              DELETE fac_usrrap_cols
                               WHERE fac_usrrap_cols_expression IS NULL
                                 AND fac_usrrap_cols_key = c_old.column_id;
                          END IF;
                      END LOOP;
                  END IF;
                  --
                  -- Vul array met de huidige volgorde van kolommen
                  FOR rap_col IN old_rap
                  LOOP
                      v_index := v_index + 1;
                      v_rap_cols.EXTEND;
                      v_rap_cols(v_index) := rap_col;
                  END LOOP;
                  v_column_prev := '';
                  --
                  -- Nieuwe en gewijzigde kolommen
                  FOR c_new IN new_rap(v_viewname)
                  LOOP
                      v_found := False;
                      FOR c_old IN old_rap
                      LOOP
                          IF (c_new.column_name = c_old.column_name)
                          THEN
                              v_found := True;

                              IF (  (c_new.column_datatype = c_old.column_datatype)
                                 OR (c_new.column_datatype IN ('float','currency','number') AND c_old.column_datatype IN ('float','currency','number'))
                                 OR (c_new.column_datatype IN ('date','datetime','time')    AND c_old.column_datatype IN ('date','datetime','time'))
                                 )
                              THEN
                                  EXIT;
                              ELSE
                                  -- datatype aanpassen
                                  dbms_output.put_line('Updating datatype column ' ||c_old.column_name || ' to ' || c_new.column_datatype);
                                  UPDATE fac_usrrap_cols
                                     SET fac_usrrap_cols_datatype = c_new.column_datatype
                                   WHERE fac_usrrap_cols_key = c_old.column_id;
                              END IF;
                          END IF;
                      END LOOP;

                      SELECT COUNT(*)
                        INTO v_is_param
                        FROM fac_usrrap_cols
                       WHERE fac_usrrap_cols_filter = 'P'
                         AND fac_usrrap_key = p_usrrap_key
                         AND UPPER(fac_usrrap_cols_column_name) = UPPER(c_new.column_name);

                      IF (v_found = False AND v_is_param = 0)
                      THEN
                          -- Caption:
                          v_column_caption := c_new.column_name;
                          v_special_col := REGEXP_SUBSTR(UPPER(c_new.column_name), '^(HIDE_F_|FCLT_X_|FCLT_3D_|FCLT_KEY|FCLT_F_|HTML_|FCLT_D_|FCLT_C_)');
                          IF (v_special_col IS NOT NULL)
                          THEN
                              v_column_caption := SUBSTR(c_new.column_name, LENGTH(v_special_col)+1);
                          END IF;
                          v_column_caption := INITCAP(REPLACE(v_column_caption, '_', ' '));

                          -- Visible:
                          v_column_visible := c_new.column_visible;
                          IF (REGEXP_SUBSTR(UPPER(c_new.column_name), '^(HIDE_F_|FCLT_3D_|FCLT_KEY)') IS NOT NULL)
                          THEN
                              v_column_visible := 'H'; -- Hidden
                          END IF;

                          IF (REGEXP_SUBSTR(UPPER(c_new.column_name), '^(FCLT_X_)') IS NOT NULL)
                          THEN
                              v_column_visible := 'I'; -- Invisible, je mag hem wel aanzetten
                          END IF;

                          IF (REGEXP_SUBSTR(UPPER(v_viewname), '_V_UDR') IS NOT NULL)
                          THEN
                              v_column_visible := 'H'; -- Hidden, die hebben te veel kolommen
                          END IF;

                          -- Filter:
                          v_column_filter := c_new.column_filter;
                          IF (REGEXP_SUBSTR(UPPER(c_new.column_name), '^(FCLT_F_|FCLT_X_)') IS NOT NULL)
                          THEN
                              v_column_filter := 'A';
                          END IF;

                          IF (REGEXP_SUBSTR(UPPER(c_new.column_name), '^(FCLT_D_)') IS NOT NULL)
                          THEN
                              v_column_filter := 'R';
                          END IF;

                          -- insert kolom
                          dbms_output.put_line('Adding column ' || c_new.column_name);
                          INSERT INTO fac_usrrap_cols
                          ( fac_usrrap_key
                          , fac_usrrap_cols_volgnr
                          , fac_usrrap_cols_column_name
                          , fac_usrrap_cols_caption
                          , fac_usrrap_cols_filter
                          , fac_usrrap_cols_datatype
                          , fac_usrrap_cols_visible
                          )
                          VALUES
                          ( p_usrrap_key
                          , c_new.column_id * 10
                          , LOWER(c_new.column_name)
                          , v_column_caption
                          , v_column_filter
                          , c_new.column_datatype
                          , v_column_visible
                          )
                          RETURNING fac_usrrap_cols_key
                          INTO v_newkey;
                          --
                          -- Voeg nieuwe kolom toe aan array op de juiste plaats.
                          v_rap_cols.EXTEND;
                          FOR i IN REVERSE v_rap_cols.FIRST .. v_rap_cols.LAST
                          LOOP
                              IF ((i=1) OR (v_rap_cols(i-1).column_name = v_column_prev))
                              THEN
                                  v_rap_cols(i).column_name := c_new.column_name;
                                  v_rap_cols(i).column_id := v_newkey;
                                  EXIT;
                              ElSE
                                  v_rap_cols(i) := v_rap_cols(i-1);
                              END IF;
                          END LOOP;

                      END IF;
                      v_column_prev := c_new.column_name;
                  END LOOP;
                  -- Volgorde van kolommen opnieuw vastleggen.
                  FOR i IN v_rap_cols.FIRST .. v_rap_cols.LAST
                  LOOP
                      UPDATE fac_usrrap_cols
                         SET fac_usrrap_cols_volgnr = i * 10
                       WHERE fac_usrrap_cols_key = v_rap_cols(i).column_id;
                  END LOOP;
                  -- Update lastrefresh.
                  UPDATE fac_usrrap
                     SET fac_usrrap_col_refresh = SYSDATE
                   WHERE fac_usrrap_key = p_usrrap_key;
              END IF;
          END IF;
      EXCEPTION
          WHEN NO_DATA_FOUND THEN
              raise_application_error(-20001, 'Report key '|| p_usrrap_key ||' does not exist.');
      END;
  END;

  -- Re-tabelizes selected views (functie and 8) into tables
  PROCEDURE tabelize_usrraps
  IS
      PRAGMA AUTONOMOUS_TRANSACTION;

      CURSOR c1
      IS
          SELECT DISTINCT fac_usrrap_view_name
            FROM fac_usrrap
           WHERE BITAND (fac_usrrap_functie, 8) = 8;

      dropddl     VARCHAR2 (200);
      createddl   VARCHAR2 (200);
  BEGIN
      FOR rec1 IN c1
      LOOP
          dropddl := 'DROP TABLE T_' || SUBSTR (rec1.fac_usrrap_view_name, 1, 126)||' PURGE';
          createddl :=
                 'CREATE TABLE T_'
              || SUBSTR (rec1.fac_usrrap_view_name, 1, 126)
              || ' AS SELECT * FROM '
              || rec1.fac_usrrap_view_name;

          BEGIN
              DBMS_OUTPUT.put_line (dropddl);

              EXECUTE IMMEDIATE dropddl;
          EXCEPTION
              WHEN OTHERS
              THEN
                  IF SQLCODE = -942
                  THEN
                      NULL;
                  ELSE
                      raise_application_error(-20001, 'Error trying to DROP: ' || SQLERRM);
                  END IF;
          END;

          DBMS_OUTPUT.put_line (createddl);

          -- Dit moet om 1031 error te vermijden
          --EXECUTE IMMEDIATE 'grant create table to '||user;
          BEGIN
              EXECUTE IMMEDIATE createddl;
          EXCEPTION
              WHEN OTHERS
              THEN
                  IF SQLCODE = -1031                             --  insufficient privileges
                  THEN
                      raise_application_error(-20001, 'CREATE TABLE must be granted to this user: ' || USER);
                  ELSE
                      raise_application_error(-20001, 'Error trying to DROP: ' || SQLERRM);
                  END IF;
          END;
      END LOOP;
      DBMS_UTILITY.COMPILE_SCHEMA(USER, FALSE); -- Voor eventuele views op die tabellen
  END;

  FUNCTION import_delimiter(p_import_key IN NUMBER) RETURN VARCHAR2
  AS
      v_delimitor  VARCHAR2(2);
  BEGIN
      BEGIN
          SELECT COALESCE(fac_import_app_delimiter, ';')
            INTO v_delimitor
            FROM fac_import_app a
               , fac_import i
           WHERE a.fac_import_app_key = i.fac_import_app_key
             AND i.fac_import_key = p_import_key;
          RETURN v_delimitor;
      EXCEPTION
          WHEN NO_DATA_FOUND THEN RETURN ';';
      END;
  END;

  PROCEDURE imp_getfield (
       p_parseline        IN OUT   VARCHAR2,
       p_fielddelimitor   IN       VARCHAR2,
       p_field            OUT      VARCHAR2
    )
    AS
       v_firstdelim    NUMBER;
       v_first_quote   NUMBER;
       v_next_quote    NUMBER;
       v_quote_count   NUMBER;
       v_found         BOOLEAN;
    BEGIN
       v_firstdelim   := INSTR (p_parseline, p_fielddelimitor, 1);

       IF v_firstdelim = 0
       THEN
          -- no delimiter found, so this is the last field of the row
          -- check if we start with a quote
          IF INSTR (p_parseline, '"', 1) = 1
          THEN
             -- strip leading and trailing quote
             -- we assume that the last character is a quote
             p_field   := substr2 (p_parseline, 2, LENGTH (p_parseline) - 2);
             -- now we only have the unescape the double quotes
             p_field := replace(p_field, '""', '"');
          ELSE
             p_field   := substr2 (p_parseline, 1);
          END IF;
          p_parseline   := '';
       ELSE
          -- we found a delimiter
          p_field        := SUBSTR (p_parseline, 1, v_firstdelim - 1);
          v_first_quote   := INSTR (p_field, '"', 1);

          IF v_first_quote <> 1 OR p_field IS NULL
          THEN
             -- line does not start with a quote or field is empty
             -- (two delimiters after each other)
             p_parseline   := substr2 (p_parseline, v_firstdelim + 1);
          ELSE
             -- double quote found. Search for the end double quote.
             v_found         := FALSE;
             v_quote_count   := 2;

             WHILE v_found = FALSE
             LOOP
                -- look for the next double quote
                v_next_quote   := INSTR (p_parseline, '"', 1, v_quote_count);

                IF v_next_quote = 0
                THEN
                   -- no end quote found. So, it is the last field in the row
                   -- lets remove the leading quote
                   -- this should not happen since a quoted field should always have an end quote
                   p_field       := substr2 (p_parseline, 2, LENGTH (p_parseline) - 1);
                   p_parseline   := '';
                   v_found := TRUE;
                ELSE
                   -- we have found a double quote, lets see what the next char is?
                   IF SUBSTR (p_parseline, v_next_quote + 1, 1) = '"'
                   THEN
                      -- two quotes found, so lets unescape the quote
                      v_quote_count   := v_quote_count + 1;
                      p_parseline     :=
                         SUBSTR (p_parseline, 1, v_next_quote)
                         || SUBSTR (p_parseline, v_next_quote + 2);
                   ELSIF SUBSTR (p_parseline, v_next_quote + 1, 1) = p_fielddelimitor
                      OR v_next_quote = LENGTH(p_parseline)
                   THEN
                      -- quote + delimiter found OR quote found at end of p_parseline
                      p_field         := SUBSTR (p_parseline, 2, v_next_quote - 2);
                      p_parseline     := SUBSTR (p_parseline, v_next_quote + 2);
                      v_found         := TRUE;
                   ELSE
                      -- quote without delimiter found
                      -- this should never happen.
                      v_quote_count   := v_quote_count + 1;
                   END IF;
                END IF;
             END LOOP;
          END IF;
       END IF;
    END;

   -- Retourneert het gegevens uit de kolom met volgnummer 'p_nr', beginnend met kolomnr 1.
   -- 'Uiteraard' is deze functie gebaseerd op imp_getfield!
   -- Verschil is dat p_parseline hier alleen IN-parameter is (en niet IN-OUT), en natuurlijk de extra parameter p_nr.
   -- En de gelezen waarde wordt itt imp_getfield gewoon getrimd (weg met die overbodige spaties die alleen voor de rommel zorgen): VOD D'R MET!
   -- Gebaseerd op het gebruik in de importfuncties is de lengte van een line 4000 posities (chars of bytes, daar wil ik van af zijn)
   PROCEDURE imp_getfield_nr (
       p_parseline        IN       VARCHAR2,
       p_fielddelimitor   IN       VARCHAR2,
       p_nr               IN       NUMBER,
       p_field            OUT      VARCHAR2
   )
   AS
        l_parseline VARCHAR2(4000);
        l_nr        NUMBER(10);
        l_field     VARCHAR2(4000);
   BEGIN
        l_parseline :=p_parseline;
        l_nr        :=p_nr;
        WHILE l_nr >= 1
        LOOP
           -- look for the next kolom in p_parseline
           fac.imp_getfield (l_parseline, p_fielddelimitor, l_field);
           l_nr := l_nr -1;
        END LOOP;
        -- Trimmen met die hap!
        -- En nu pas het veld toekennen aan de parameter van de procedure
        -- omdat de lengte korter kan zijn dan de lengte van de eerdere velden.
        p_field:=TRIM(l_field);
   END;

   PROCEDURE imp_writelog (
      p_import_key IN NUMBER,
      plevel   IN   VARCHAR2,
      pmsg     IN   VARCHAR2,
      phint    IN   VARCHAR2
   )
   AS
   BEGIN
      INSERT INTO imp_log
                  (fac_import_key, imp_log_datum,
                   imp_log_status, imp_log_omschrijving, imp_log_hint
                  )
           VALUES (p_import_key, SYSDATE,
                   plevel, pmsg, phint
                  );
   END;

   PROCEDURE writelog (
      pAppl    IN VARCHAR2,
      plevel   IN   VARCHAR2,
      pmsg     IN   VARCHAR2,
      phint    IN   VARCHAR2
   )
   AS
   BEGIN
      INSERT INTO imp_log
                  (imp_log_applicatie, imp_log_datum,
                   imp_log_status, imp_log_omschrijving, imp_log_hint
                  )
           VALUES (pAppl, SYSDATE,
                   plevel, pmsg, phint
                  );
   END;

   PROCEDURE imp_alg_delete_onrgoed (p_import_key IN NUMBER, p_mode IN NUMBER)
   AS
      v_errormsg       VARCHAR2 (1000);
      v_errorhint      VARCHAR2 (1000);
      oracle_err_num   NUMBER;
      oracle_err_mes   VARCHAR2 (200);
   BEGIN
      DELETE FROM bez_bezoekers;

      DELETE FROM bez_afspraak;

      DELETE FROM res_rsv_deel;

      DELETE FROM res_rsv_artikel;

      DELETE FROM res_rsv_ruimte;

      DELETE FROM res_reservering;

      DELETE FROM res_kenmerkwaarde;

      DELETE FROM res_kenmerkartikel;

      DELETE FROM res_alg_ruimte;

      DELETE FROM res_ruimte_opstelling;

      DELETE FROM res_ruimte;

      DELETE FROM res_opstelling;

      DELETE FROM res_activiteitdiscipline;

      DELETE FROM res_disc_params;

      DELETE FROM ins_tab_discipline
            WHERE ins_discipline_module = 'RES' AND ins_discipline_min_level = '3';

      DELETE FROM res_srtartikel_onrgoed;

      DELETE FROM fin_factuur
            WHERE mld_opdr_key IS NOT NULL;

      DELETE FROM cnt_contract_plaats;

      DELETE FROM mld_kenmerkmelding;

      DELETE FROM mld_melding_object;

      DELETE FROM mld_opdr_materiaal;

      DELETE FROM mld_opdr;

      DELETE FROM mld_melding;

      DELETE FROM mld_adres;

      DELETE FROM prs_perslidwerkplek;

      DELETE FROM prs_werkplek;

      IF (p_mode < 2)
      THEN
         DELETE FROM prs_contactpersoon_locatie;
      END IF;

      DELETE FROM ins_verbinding;

      DELETE FROM ins_deelkoppeling;

      DELETE FROM ins_deel
            WHERE ins_deel_parent_key IS NOT NULL;

      DELETE FROM ins_deel
            WHERE ins_alg_ruimte_type IS NOT NULL;

      DELETE FROM alg_terreinsector;

      DELETE FROM alg_srtterreinsector;

      DELETE FROM alg_ruimte;

      DELETE FROM alg_srtruimte;

      DELETE FROM alg_verdieping;

      IF (p_mode < 2)
      THEN
         DELETE FROM alg_onrgoedkenmerk;

         DELETE FROM alg_gebouw;

         DELETE FROM alg_srtgebouw;

         DELETE FROM alg_locatie;

         DELETE FROM alg_district;

         DELETE FROM alg_regio;
      ELSE
         DELETE FROM alg_onrgoedkenmerk
               WHERE alg_onrgoed_niveau IN ('R', 'T');
      END IF;
   EXCEPTION
      WHEN OTHERS
      THEN
         oracle_err_num := SQLCODE;
         oracle_err_mes := SUBSTR (SQLERRM, 1, 200);

         IF (v_errormsg IS NULL)
         THEN
            v_errormsg := 'Niet alle onroerendgoed gegevens zijn verwijderd!';
         END IF;

         v_errorhint :=
               v_errorhint
            || ' (ORACLE error NUMBER = <'
            || oracle_err_num
            || '> ORACLE error message = <'
            || oracle_err_mes
            || '>)';
         fac.imp_writelog (p_import_key, 'E', v_errormsg, v_errorhint);
         COMMIT;
   END;

   PROCEDURE imp_prs_delete_organisatie (p_import_key IN NUMBER, p_mode IN NUMBER)
   AS
      v_errormsg       VARCHAR2 (1000);
      v_errorhint      VARCHAR2 (1000);
      oracle_err_num   NUMBER;
      oracle_err_mes   VARCHAR2 (200);

      v_bedrijf_key           NUMBER(10);

      -- PRS_CONTACTPERSOON_LOCATIE
      CURSOR c1
      IS
         SELECT prs_contactpersoon_locatie_key
           FROM prs_contactpersoon_locatie;

      rec1             c1%ROWTYPE;

      -- PRS_CONTACTPERSOON
      CURSOR c2
      IS
         SELECT prs_contactpersoon_key
           FROM prs_contactpersoon;

      rec2             c2%ROWTYPE;

      -- PRS_BEDRIJFDIENSTLOCATIE
      CURSOR c3
      IS
         SELECT prs_bedrijfdienstlocatie_key
           FROM prs_bedrijfdienstlocatie;

      rec3             c3%ROWTYPE;

      -- PRS_PERSLIDWERKPLEK
      CURSOR c4
      IS
         SELECT prs_perslidwerkplek_key
           FROM prs_perslidwerkplek;

      rec4             c4%ROWTYPE;

      -- PRS_WERKPLEK
      CURSOR c5
      IS
         SELECT prs_werkplek_key
           FROM prs_werkplek;

      rec5             c5%ROWTYPE;

      -- PRS_PERSLID
      CURSOR c6
      IS
         SELECT   prs_perslid_key
           FROM   prs_perslid
           WHERE  prs_perslid_oslogin not in ('_GAST','_FACILITOR') /* van vroeger */
              OR prs_perslid_oslogin IS NULL;

      rec6             c6%ROWTYPE;

      -- PRS_SRTPERSLID
      CURSOR c7
      IS
         SELECT   prs_srtperslid_key
           FROM   prs_srtperslid
           where  prs_srtperslid_upper <> 'ONBEKEND';

      rec7             c7%ROWTYPE;

      -- PRS_KOSTENPLAATS
      CURSOR c8
      IS
         SELECT prs_kostenplaats_key
           FROM prs_kostenplaats;

      rec8             c8%ROWTYPE;

   BEGIN
      IF (p_mode <= 1)
      THEN

        FOR rec1 IN c1
        LOOP
          BEGIN
            delete from prs_contactpersoon_locatie where prs_contactpersoon_locatie_key = rec1.prs_contactpersoon_locatie_key;
            EXCEPTION
            WHEN OTHERS THEN
              v_errormsg := 'Ok, dan niet. Neem het volgende record ....';
          END;
        END LOOP;

        commit;

        FOR rec2 IN c2
        LOOP
          BEGIN
            delete from prs_contactpersoon where prs_contactpersoon_key = rec2.prs_contactpersoon_key;
            EXCEPTION
            WHEN OTHERS THEN
              v_errormsg := 'Ok, dan niet. Neem het volgende record ....';
          END;
        END LOOP;

        commit;

        FOR rec3 IN c3
        LOOP
          BEGIN
            delete from prs_bedrijfdienstlocatie where prs_bedrijfdienstlocatie_key = rec3.prs_bedrijfdienstlocatie_key;
            EXCEPTION
            WHEN OTHERS THEN
              v_errormsg := 'Ok, dan niet. Neem het volgende record ....';
          END;
        END LOOP;

        commit;

      END IF;

      IF (p_mode <= 2)
      THEN

        FOR rec4 IN c4
        LOOP
          BEGIN
            delete from prs_perslidwerkplek where prs_perslidwerkplek_key = rec4.prs_perslidwerkplek_key;
            EXCEPTION
            WHEN OTHERS THEN
              v_errormsg := 'Ok, dan niet. Neem het volgende record ....'; -- agv prs_v_verplichting_all/fac_gebruikersgroep
          END;
        END LOOP;

        commit;

        FOR rec5 IN c5
        LOOP
          BEGIN
            delete from prs_werkplek where prs_werkplek_key = rec5.prs_werkplek_key;
            EXCEPTION
            WHEN OTHERS THEN
              v_errormsg := 'Ok, dan niet. Neem het volgende record ....'; --
          END;
        END LOOP;

        commit;

        FOR rec6 IN c6
        LOOP
          BEGIN
            delete from prs_perslid where prs_perslid_key = rec6.prs_perslid_key;
            EXCEPTION
            WHEN OTHERS THEN
              v_errormsg := 'Ok, dan niet. Neem het volgende record ....'; -- agv prs_v_verplichting_all/fac_gebruikersgroep/prs_perslidwerkplek
          END;
        END LOOP;

        commit;

        FOR rec7 IN c7
        LOOP
          BEGIN
            delete from prs_srtperslid where prs_srtperslid_key = rec7.prs_srtperslid_key;
            EXCEPTION
            WHEN OTHERS THEN
              v_errormsg := 'Ok, dan niet. Neem het volgende record ....'; -- agv prs_perslid
          END;
        END LOOP;

        commit;

      END IF;

      IF (p_mode <= 1)
      THEN

         -- Verwijder ALLE afdelingen indien mogelijk. Doe dit wel in de 'juiste' volgorde.

         DELETE FROM prs_afdeling
               WHERE prs_afdeling_key IN (SELECT prs_afdeling_key
                                            FROM prs_v_afdeling
                                           WHERE niveau = 5) AND prs_afdeling_key NOT IN (SELECT prs_afdeling_key
                                                                                            FROM prs_perslid);

         DELETE FROM prs_afdeling
               WHERE prs_afdeling_key IN (SELECT prs_afdeling_key
                                            FROM prs_v_afdeling
                                           WHERE niveau = 4)
                 AND prs_afdeling_key NOT IN (SELECT prs_afdeling_key
                                                FROM prs_perslid)
                 AND prs_afdeling_key NOT IN (SELECT prs_afdeling_parentkey
                                                FROM prs_v_afdeling
                                               WHERE niveau = 5);

         DELETE FROM prs_afdeling
               WHERE prs_afdeling_key IN (SELECT prs_afdeling_key
                                            FROM prs_v_afdeling
                                           WHERE niveau = 3)
                 AND prs_afdeling_key NOT IN (SELECT prs_afdeling_key
                                                FROM prs_perslid)
                 AND prs_afdeling_key NOT IN (SELECT prs_afdeling_parentkey
                                                FROM prs_v_afdeling
                                               WHERE niveau = 4);

         DELETE FROM prs_afdeling
               WHERE prs_afdeling_key IN (SELECT prs_afdeling_key
                                            FROM prs_v_afdeling
                                           WHERE niveau = 2)
                 AND prs_afdeling_key NOT IN (SELECT prs_afdeling_key
                                                FROM prs_perslid)
                 AND prs_afdeling_key NOT IN (SELECT prs_afdeling_parentkey
                                                FROM prs_v_afdeling
                                               WHERE niveau = 3);

         DELETE FROM prs_afdeling
               WHERE prs_afdeling_key IN (SELECT prs_afdeling_key
                                            FROM prs_v_afdeling
                                           WHERE niveau = 1)
                 AND prs_afdeling_upper <> 'ONBEKEND'
                 AND prs_afdeling_key NOT IN (SELECT prs_afdeling_key
                                                FROM prs_perslid)
                 AND prs_afdeling_key NOT IN (SELECT prs_afdeling_parentkey
                                                FROM prs_v_afdeling
                                               WHERE niveau = 2);

         -- Verwijder ALLE bedrijven voor zover mogelijk
         DELETE FROM prs_bedrijf
               WHERE prs_bedrijf_key NOT IN (SELECT prs_bedrijf_key
                                               FROM prs_afdeling)
               AND   prs_bedrijf_naam_upper <> 'ONBEKEND';

        commit;

      END IF;

      -- Verwijder ALLE kostenplaatsen voor zover mogelijk
      FOR rec8 IN c8
      LOOP
        BEGIN
          delete from prs_kostenplaats where prs_kostenplaats_key = rec8.prs_kostenplaats_key;
          EXCEPTION
          WHEN OTHERS THEN
            v_errormsg := 'Ok, dan niet. Neem het volgende record ....';
        END;
      END LOOP;

      COMMIT;

      -- Maak initiele vulling aan
      BEGIN
        INSERT INTO prs_srtperslid (prs_srtperslid_omschrijving, prs_bedrijf_key)
             VALUES ('Onbekend', NULL);
            EXCEPTION
              WHEN OTHERS THEN
                v_errormsg := 'Ok. Kennelijk bestaat het record al';
      END;

      BEGIN
        INSERT INTO prs_bedrijf (prs_bedrijf_naam, prs_bedrijf_intern)
             VALUES ('Onbekend', 1);
            EXCEPTION
              WHEN OTHERS THEN
                v_errormsg := 'Ok. Kennelijk bestaat het record al';
      END;

      SELECT  prs_bedrijf_key
      INTO    v_bedrijf_key
      FROM    prs_bedrijf
      WHERE   prs_bedrijf_naam_upper = 'ONBEKEND';

      BEGIN
        INSERT INTO prs_afdeling (prs_bedrijf_key, prs_afdeling_naam)
             VALUES (v_bedrijf_key, 'Onbekend');
            EXCEPTION
              WHEN OTHERS THEN
                v_errormsg := 'Ok. Kennelijk bestaat het record al';
      END;

      commit;

      EXCEPTION
        WHEN OTHERS
        THEN
           oracle_err_num := SQLCODE;
           oracle_err_mes := SUBSTR (SQLERRM, 1, 200);

           IF (v_errormsg IS NULL)
           THEN
              v_errormsg := 'Niet alle bedrijven zijn verwijderd';
           END IF;

           v_errorhint :=
                 v_errorhint
              || ' (ORACLE error NUMBER = <'
              || oracle_err_num
              || '> ORACLE error message = <'
              || oracle_err_mes
              || '>)';
           fac.imp_writelog (p_import_key, 'E', v_errormsg, v_errorhint);
           COMMIT;
   END;

    PROCEDURE imp_mld_delete_mld (p_import_key IN NUMBER, p_mode IN NUMBER)
    AS
      v_errormsg       VARCHAR2 (1000);
      v_errorhint      VARCHAR2 (1000);
      oracle_err_num   NUMBER;
      oracle_err_mes   VARCHAR2 (200);
    BEGIN

        delete from mld_opdr;

        delete from mld_melding;

        delete from mld_stdmelding;

        delete from prs_kostensoort;

        delete from mld_disc_params;

        delete from ins_tab_discipline where ins_discipline_module='MLD';

        delete from ins_srtdiscipline;

    EXCEPTION
      WHEN OTHERS
      THEN
        oracle_err_num := SQLCODE;
        oracle_err_mes := SUBSTR (SQLERRM, 1, 200);

        v_errormsg := 'Niet alle servicedesk gegevens zijn verwijderd!';

        v_errorhint :=
             v_errorhint
          || ' (ORACLE error NUMBER = <'
          || oracle_err_num
          || '> ORACLE error message = <'
          || oracle_err_mes
          || '>)';
        fac.imp_writelog (p_import_key, 'E', v_errormsg, v_errorhint);
        COMMIT;
    END; -- imp_mld_delete_mld

   PROCEDURE fac_perfmon_incr (limiet IN NUMBER, zwaarte IN NUMBER)
   AS
      dummy   VARCHAR2 (1);
   BEGIN
      BEGIN
         SELECT ''
           INTO dummy
           FROM fac_perfmon
          WHERE fac_perfmon_datum = TRUNC (SYSDATE);

         UPDATE fac_perfmon
            SET fac_perfmon_waarde = fac_perfmon_waarde + zwaarte,
                fac_perfmon_aantal = fac_perfmon_aantal + 1
          WHERE fac_perfmon_datum = TRUNC (SYSDATE);
      EXCEPTION
         WHEN NO_DATA_FOUND
         THEN
            -- a brandnew day. vul het aantal gebruikers van de vorige dag in en start een nieuwe
            UPDATE fac_perfmon
               SET fac_perfmon_users =
                         (SELECT COUNT (prs_perslid_key)
                            FROM prs_perslid
                           WHERE TRUNC (prs_perslid_login) = fac_perfmon_datum)
             WHERE fac_perfmon_users IS NULL;

            INSERT INTO fac_perfmon
                        (fac_perfmon_aantal, fac_perfmon_waarde,
                         fac_perfmon_datum, fac_perfmon_threshold
                        )
                 SELECT 1, zwaarte,
                         TRUNC (SYSDATE), limiet
                   FROM DUAL
                      WHERE TRUNC (SYSDATE) NOT IN -- multi-user safe maken
                        (SELECT fac_perfmon_datum FROM fac_perfmon);
      END;
   END;

   -- Retourneert string met emailadres van persoon pkey, '' indien niet beschikbaar
   FUNCTION getEmail (pkey IN NUMBER)
      RETURN VARCHAR2
   IS
      lemail   prs_perslid.prs_perslid_email%TYPE;
   BEGIN
      lemail := '';

      BEGIN
         -- emailadres van ontvanger
         SELECT prs_perslid_email
           INTO lemail
           FROM prs_perslid
          WHERE prs_perslid_key = pkey;
      EXCEPTION
         WHEN OTHERS
         THEN
            NULL;
      END;

      RETURN lemail;
   END;

   -- Retourneert string met mobielnummer van persoon pkey, '' indien niet beschikbaar
   FUNCTION getmobile (pkey IN NUMBER)
      RETURN VARCHAR2
   IS
      lphone   prs_perslid.prs_perslid_mobiel%TYPE;
   BEGIN
      lphone := '';

      BEGIN
         -- mobiele telefoon van ontvanger
         SELECT prs_perslid_mobiel
           INTO lphone
           FROM prs_perslid
          WHERE prs_perslid_key = pkey;
      EXCEPTION
         WHEN OTHERS
         THEN
            NULL;
      END;

      RETURN lphone;
   END;

   -- Retourneert de taal van een user
   FUNCTION getlang (pkey IN NUMBER)
      RETURN VARCHAR2
   IS
      llang   prs_perslid.prs_perslid_lang%TYPE;
   BEGIN
      llang := '';

      BEGIN
         -- taal van persoon; te overwegen (gelijk gedrag) is om alleen
         -- afwijking terug te geven, dus NULL als default ipv default
         SELECT COALESCE(prs_perslid_lang, lcl.getuserlanguage())
           INTO llang
           FROM prs_perslid
          WHERE prs_perslid_key = pkey;
      EXCEPTION
         WHEN OTHERS
         THEN
            NULL;
      END;

      RETURN llang;
   END;

   -- Zet een vrij bericht in de queue ter verspreiding volgens pmode. Dit is geen bestaande srtnotificatie
   -- In plaats van pto (een intern prs_perslid) kan optioneel ook expliciete adressering worden meegegeven.
   -- Bij een interne ontvanger wordt diens taal bijgezocht.
   PROCEDURE putnotificationprio (pfrom NUMBER, pto NUMBER, pmessage VARCHAR2, pmode NUMBER,
                                  poptemail VARCHAR2, poptmobile VARCHAR2, pprio NUMBER, pattach VARCHAR2 DEFAULT NULL,
                                  pbedrijfadreskey NUMBER DEFAULT NULL, pqueue VARCHAR2 DEFAULT 'DEFAULT',
                                  pgeturl VARCHAR2 DEFAULT NULL)
   AS
      lemail   fac_notificatie.fac_notificatie_receiver_email%TYPE;
      lphone   prs_perslid.prs_perslid_mobiel%TYPE;
      llang    prs_perslid.prs_perslid_lang%TYPE;
      lsysteem prs_perslid.prs_perslid_systeemadres%TYPE;
	  lmode    NUMBER;
   BEGIN
      lmode := pmode;
      IF poptemail IS NULL
      THEN
         lemail := fac.getemail (pto);
      ELSE
         lemail := poptemail;
      END IF;

      IF poptmobile IS NULL
      THEN
         lphone := fac.getmobile (pto);
      ELSE
         lphone := poptmobile;
      END IF;

      IF pto IS NOT NULL
      THEN
        llang := fac.getlang(pto);
      ELSE
        llang := lcl.getuserlanguage();
      END IF;

      IF pto IS NOT NULL
      THEN
          BEGIN
            SELECT prs_perslid_systeemadres
              INTO lsysteem
              FROM prs_perslid
             WHERE prs_perslid_key = pto;
          EXCEPTION
            WHEN NO_DATA_FOUND THEN NULL;
          END;
      END IF;

      -- Stuur notificatie
      IF BITAND(pmode, 2) = 2 AND lemail IS NULL
      THEN
        lmode := BITAND(lmode, 255-2);
      END IF;

      IF BITAND(pmode, 4) = 4 AND lphone IS NULL
      THEN
        lmode := BITAND(lmode, 255-4);
      END IF;

      IF lmode > 0 OR lsysteem IS NOT NULL OR pbedrijfadreskey IS NOT NULL THEN
      INSERT INTO fac_notificatie
                  (fac_srtnotificatie_key, fac_notificatie_status, prs_perslid_key_sender, prs_perslid_key_receiver,
                   fac_notificatie_receiver_email, fac_notificatie_receiver_phone, fac_notificatie_oms,
                   fac_notificatie_refkey, fac_notificatie_prioriteit, fac_notificatie_lang, fac_notificatie_systeemadres,
                   fac_notificatie_attachments, prs_bedrijfadres_key, fac_notificatie_queue,fac_notificatie_geturl
                  )
           VALUES (NULL, lmode, pfrom, pto,
                   lemail, lphone, SUBSTR(pmessage, 1, 2048),
                   NULL, pprio, llang, lsysteem,
                   pattach, pbedrijfadreskey, pqueue, pgeturl
                  );
      END IF;
   END;

   -- Zet een bericht in de queue ter verspreiding aan de applicatiebeheerders
   PROCEDURE putsystemnotification (pmessage VARCHAR2, pmode NUMBER)
   AS
      CURSOR c1
      IS
         SELECT DISTINCT wg.prs_perslid_key
           FROM fac_functie f, fac_v_webgebruiker wg
          WHERE f.fac_functie_key = wg.fac_functie_key
            AND f.fac_functie_code = 'WEB_PRSSYS';
   BEGIN
      FOR rec1 IN c1
      LOOP
         putnotificationprio (NULL, rec1.prs_perslid_key, pmessage, pmode, NULL, NULL, 2);
      END LOOP;
   END;

   -- Zet een bericht in de queue ter verspreiding volgens definitie van een bestaande srtnotificatie
   -- Eventueel gebeurt er dus niets, als de srtnotificatie uitgeschakeld (mode=0) is.
   -- Als het bericht wordt meegegeven in poptmessage dan wordt die tekst gebruikt ipv de standaardtekst van pcode
   -- Evenzo wordt de standaardmode overruled door poptstatus als die meegegeven is, en
   -- kan de email en mobile van pto optioneel worden overruled door poptemail en poptmobile
   -- Als email of sms gevraagd wordt (smode) en email of mobile kan niet bepaald worden, wordt niet gequeued
   -- pprio is de prioriteit (1, 2 of 3) die geen effect heeft op de quebehandeling, maar wel wordt meegegeven
   -- psender is een optioneel emailadres dat als afzender kan worden gehanteerd.
   -- pattach is a list of filenames (tbd)
   PROCEDURE putnotificationsrtprio (pfrom          NUMBER,
                                     pto            NUMBER,
                                     pcode          VARCHAR2,
                                     pref           NUMBER,
                                     poptmessage    VARCHAR2,
                                     poptstatus     NUMBER,
                                     poptemail      VARCHAR2,
                                     poptmobile     VARCHAR2,
                                     pxref          NUMBER,
                                     pprio          NUMBER,
                                     psender        VARCHAR2,
                                     pattach        VARCHAR2 DEFAULT NULL,
                                     pbedrijfadreskey NUMBER DEFAULT NULL,
                                     pqueue VARCHAR2 DEFAULT 'DEFAULT',
                                     pgeturl VARCHAR2 DEFAULT NULL)
   AS
      soms         fac_srtnotificatie.fac_srtnotificatie_oms%TYPE;
      smode        fac_srtnotificatie.fac_srtnotificatie_mode%TYPE;
      oldercode    fac_srtnotificatie.fac_srtnotificatie_code%TYPE;
      oldersoms    fac_srtnotificatie.fac_srtnotificatie_oms%TYPE;
      oldersmode   fac_srtnotificatie.fac_srtnotificatie_mode%TYPE;
      susermode    fac_srtnotificatie.fac_srtnotificatie_usermode%TYPE;
      lmode        fac_srtnotificatie.fac_srtnotificatie_mode%TYPE;
      any_tech     NUMBER;
      skey         fac_srtnotificatie.fac_srtnotificatie_key%TYPE;
      l_srtnoti_delay    fac_srtnotificatie.fac_srtnotificatie_delay%TYPE;
      lemail       fac_notificatie.fac_notificatie_receiver_email%TYPE;
      lphone       fac_notificatie.fac_notificatie_receiver_phone%TYPE;
      llang        prs_perslid.prs_perslid_lang%TYPE;
      lsysteem     prs_perslid.prs_perslid_systeemadres%TYPE;
      notbefore    fac_notificatie.fac_notificatie_notbefore%TYPE;
      delaymode    NUMBER;
      immediate    NUMBER(1);
   BEGIN
      IF pcode IS NOT NULL
      THEN
         BEGIN
            immediate := 0;
            IF poptstatus IS NULL
            THEN
               -- Geen status/mode meegegeven, bepaal em zelf
               SELECT lcl.x('fac_srtnotificatie_oms', sn.fac_srtnotificatie_key, sn.fac_srtnotificatie_oms),
                      sn.fac_srtnotificatie_mode,
                      sn.fac_srtnotificatie_key,
                      fac_srtnotificatie_usermode,
                      fac_srtnotificatie_delay
                 INTO soms,
                      smode,
                      skey,
                      susermode,
                      l_srtnoti_delay
                 FROM fac_srtnotificatie sn
                WHERE sn.fac_srtnotificatie_code = pcode;

               -- Als deze notificatiesoort userafhankelijk kan zijn, kijken we
               -- naar de status/mode die bij de user is aangegeven
               IF susermode = 1 AND pto IS NOT NULL
               THEN
                  BEGIN
                     SELECT prs_perslid_srtnoti_mode
                       INTO smode
                       FROM prs_perslid
                      WHERE prs_perslid_key = pto
                            AND prs_perslid_srtnoti_mode IS NOT NULL;
                  EXCEPTION
                     WHEN NO_DATA_FOUND
                     THEN
                        -- geen gebruikersoverrule, gebruik gewoon de centrale mode
                        NULL;
                  END;
               END IF;
               -- Misschien komt er nog een technisch adres
               --IF smode IS NULL OR smode = 0
               --THEN
               --   RETURN; -- Geen kanaal om te notificeren
               --END IF;
            ELSE
               -- Wel status meegegeven; die is dan vast
               -- Als status=2 en pcode=xxxMAI, dan moet de mail altijd verstuurd worden.
               smode := poptstatus;
               IF ((SUBSTR(pcode, -3) = 'MAI' AND poptstatus = 2) OR pcode = 'PRSREG')
               THEN
                 immediate := 1;
               END IF;

               SELECT lcl.x('fac_srtnotificatie_oms', sn.fac_srtnotificatie_key, sn.fac_srtnotificatie_oms),
                      sn.fac_srtnotificatie_key, sn.fac_srtnotificatie_delay
                 INTO soms, skey, l_srtnoti_delay
                 FROM fac_srtnotificatie sn
                WHERE sn.fac_srtnotificatie_code = pcode;
            END IF;
         EXCEPTION
            WHEN NO_DATA_FOUND
            THEN
               -- Nothing to do
               RETURN;
         END;

         IF poptmessage IS NOT NULL
         THEN
            soms := poptmessage;
         END IF;

         soms := REPLACE (soms, '##KEY##', TO_CHAR (pref));

         IF poptemail IS NULL
         THEN
            lemail := fac.getemail (pto);
         ELSE
            lemail := poptemail;
         END IF;

         IF poptmobile IS NULL
         THEN
            lphone := fac.getmobile (pto);
         ELSE
            lphone := poptmobile;
         END IF;

         IF pto IS NOT NULL
         THEN
           llang := fac.getlang(pto);
         ELSE
           llang := lcl.getuserlanguage();
         END IF;

         IF pto IS NOT NULL
         THEN
             BEGIN
               SELECT prs_perslid_systeemadres
                 INTO lsysteem
                 FROM prs_perslid
                WHERE prs_perslid_key = pto
                  AND prs_perslid_verwijder IS NULL;
             EXCEPTION
               WHEN NO_DATA_FOUND THEN
                 RETURN;
             END;

            IF pbedrijfadreskey IS NULL
            THEN -- Mogelijk naar technische adressen van het (interne) bedrijf waar deze persoon onder valt
               FOR bedrijfadresrec
                  IN (SELECT ba.prs_bedrijfadres_key
                        FROM prs_perslid p,
                             prs_v_afdeling_boom aa,
                             prs_bedrijfadres ba,
                             prs_bedrijfadres_srtnoti basrt
                       WHERE p.prs_perslid_key = pto
                         AND BITAND(prs_perslid_flags, 1) = 1 -- System user, voorkomt dat het naar elke user onder dit bedrijf gaat
                         AND prs_perslid_verwijder IS NULL
                         AND aa.prs_afdeling_key = p.prs_afdeling_key
                         AND ba.prs_bedrijf_key = aa.prs_bedrijf_key
                         AND ba.prs_bedrijfadres_startdatum <= SYSDATE
                         AND ba.prs_bedrijfadres_type = 'S' -- Systeem notificaties
                         AND basrt.prs_bedrijfadres_key = ba.prs_bedrijfadres_key
                         AND basrt.fac_srtnotificatie_key = skey
                     )
               LOOP
                  DBMS_OUTPUT.PUT_LINE('Notify systeembedrijf-adres: ' || bedrijfadresrec.prs_bedrijfadres_key);
                  IF (pto = pfrom) -- Toch maar niet
                  THEN
                     DBMS_OUTPUT.PUT_LINE('Maar ik ga niet naar mezelf terugsturen');
                     CONTINUE;
                  END IF;

                  notbefore := SYSDATE;
                  delaymode := fac.safe_to_number(fac.getsetting('noti_delay_mode'));
                  IF delaymode = 1 -- default uit, naar technische adres zonder vertraging
                  THEN
                     notbefore := notbefore + NVL(l_srtnoti_delay/ 60/ 60/ 24, 0);
                  ELSIF delaymode > 1 -- fixed delay
                  THEN
                     notbefore := notbefore + delaymode / 60/ 60/ 24;
                  END IF;
                  INSERT INTO fac_notificatie (fac_srtnotificatie_key,
                                             fac_notificatie_status,
                                             prs_perslid_key_sender,
                                             fac_notificatie_oms,
                                             fac_notificatie_refkey,
                                             fac_notificatie_extrakey,
                                             fac_notificatie_prioriteit,
                                             fac_notificatie_sender_email,
                                             fac_notificatie_lang,
                                             fac_notificatie_attachments,
                                             prs_bedrijfadres_key,
                                             fac_notificatie_notbefore)
                     VALUES (skey, -- deze is eerder al bepaald
                              0, -- nooit via andere opties versturen
                              pfrom,
                              soms,
                              pref,
                              pxref,
                              pprio,
                              psender,
                              llang,
                              pattach,
                              bedrijfadresrec.prs_bedrijfadres_key, -- enige bijzondere, rest gewoon blind doorgeven,
                              notbefore);
               END LOOP;
            END IF;
         END IF;

         IF smode IS NULL OR smode = 0
         THEN
            RETURN; -- Geen kanaal om te notificeren
         END IF;

         lmode := smode;

         IF BITAND (smode, 2) = 2 AND lemail IS NULL
         THEN
            lmode := BITAND (lmode, 255 - 2);
         END IF;

         IF BITAND (smode, 4) = 4 AND lphone IS NULL
         THEN
            lmode := BITAND (lmode, 255 - 4);
         END IF;

         -- Voor een gebruiker zonder app-registraties zouden we hier ooit bitje 32 nog kunnen wissen
         -- maar putorders lost dat later ook wel op.

         -- Als de omschrijving uit meerdere regels bestaat dan alleen de eerste regel notificeren.
         -- Empirisch gebleken dat chr(10) wel wat doet, en chr(13) niet echt
         -- hangt dat van iets af? Moeten we dan misschien allebei doen?
         IF INSTR(soms, CHR(10)) > 0
         THEN
            soms := SUBSTR(soms, 1, INSTR (soms, CHR(10)) - 1);
         END IF;

         -- Gooi bestaande identieke notificaties eerst maar eens uit de queue. Voor het gemak.
         -- De nieuwe is minstens zo goed en gaat per definitie via dezelfde kanalen
         fac.clrnotifications(pcode, pref, soms, pto, pxref); -- Dubbele notificaties verwijderen indien dezelfde omschrijving en voor dezelfde ontvanger.

         -- Voorkom e-mail/sms van deze zwakkere *UPD als de *NEW nog niet eens de deur uit is door Putorders
         -- of meer algemeen: elke e-mail/sms/app notificatie die nog niet de deur is.
         -- Je wilt wel op alle gedefinieerde kanalen precies een bericht houden
         IF ((BITAND (smode, 2 + 4 + 32) <> 0) AND (immediate = 0))
         THEN
            -- Zoek de lmode van een al/nog aanwezige fac_notificatie die over hetzelfde gaat
            BEGIN
               SELECT fac_srtnotificatie_code, fac_notificatie_status, fac_notificatie_oms
                 INTO oldercode, oldersmode, oldersoms
                 FROM fac_v_notifyqueue ander
                WHERE (pcode = ander.fac_srtnotificatie_code  -- die is meestal al gewist, zou nog kunnen met andere tekst
                       OR pcode = 'BESUPD'
                         AND ander.fac_srtnotificatie_code IN ('BESNEW')
                       OR pcode IN ('RESUPD','RESBEV','RESOPT','RESCLN')
                         AND ander.fac_srtnotificatie_code IN ('RESNEW')
                       OR pcode IN ('RESUPD') -- geen RESUPD als pending RESBEV of RESOPT
                         AND ander.fac_srtnotificatie_code IN ('RESBEV','RESOPT')
                       OR pcode = 'MLDNOT' -- no MLDNOT WHEN MLDNEW or MLDA2M is pending
                         AND ander.fac_srtnotificatie_code IN ('MLDNEW', 'MLDA2M')
                       OR pcode = 'MLDNOB' -- no MLDNOB WHEN MLDBE2 or MLDA2B or MLDNOT is pending
                         AND ander.fac_srtnotificatie_code IN ('MLDBE2', 'MLDA2B', 'MLDNOT')
                       OR pcode = 'ORDNOB' -- no ORDNOB WHEN ORDNEW or ORDA2B is pending
                         AND ander.fac_srtnotificatie_code IN ('ORDNEW', 'ORDA2B')
                       OR pcode = 'ORDNOL' -- no ORDNOL WHEN ORDA2U or MLDA2B or MLDNOT is pending
                         AND ander.fac_srtnotificatie_code IN ('ORDA2U')
                       OR pcode IN ('MLDUPD','MLDBEH','MLDABH') -- no MLDUPD | MLDBEH | MLDABH WHEN MLDNEW is pending
                         AND ander.fac_srtnotificatie_code IN ('MLDNEW')
                      )
                      AND ander.fac_notificatie_refkey = pref
                      AND COALESCE (ander.fac_notificatie_extrakey, -1) =
                            COALESCE (pxref, -1)
                      AND COALESCE (ander.fac_notificatie_receiver_email, '@') =
                            COALESCE (lemail, '@')
                      AND COALESCE (ander.fac_notificatie_receiver_phone, '@') =
                            COALESCE (lphone, '@')
                      AND BITAND (fac_notificatie_status, 2 + 4 + 32) <> 0
                      AND BITAND (fac_notificatie_status, 64) = 0 -- Bitje 64 (custom notificatie MARX) ongemoeid laten
                      AND ROWNUM=1; -- anders te ingewikkeld
            EXCEPTION
               WHEN NO_DATA_FOUND
               THEN
                  NULL;
            END;

            IF (oldercode IS NOT NULL)
            THEN
               -- Deze is zwakker dan de bestaande, deze hoeft dus niet nog eens via diezelfde kanalen.
               -- Het kan dus best zijn dat er niets over blijft.
               lmode := BITAND (lmode, 255 - oldersmode);
            END IF;

            -- Dan nog omgekeerd: trek eerdere zwakkere notificaties terug als deze pcode sterker is
            -- en die oudere nog niet de deur uit is. NB: de tracking blijft natuurlijk gewoon
            -- Ook portalberichten worden zo verminderd, en niet alleen die van de laatste 5 minuten
            -- De tracking tells it all! Gooi je zelf niks weg, dan resteert in de portal meestal de AFM, tof.
            -- Wellicht dat deze lijst nog wat groeit. Voor MLD durf ik alle weg te doen bij afmelden,
            -- Bij die andere is dat misschien wat riskanter als na TV nog KTV kan volgen en zo.
            CASE pcode
            WHEN 'MLDAFM'
            THEN
                fac.clrnotifications('MLD%', pref, NULL, 'MLDAFM', 'MLDMAI');
            WHEN 'MLD2BO'
            THEN
                fac.clrnotifications('MLD2B3', pref);
            WHEN 'MLDA2B'
            THEN
                fac.clrnotifications('MLDNOB', pref);
                fac.clrnotifications('MLDA2M', pref);
            WHEN 'MLDA2M'
            THEN
                fac.clrnotifications('MLDNOT', pref);
                fac.clrnotifications('MLDA2B', pref);
            WHEN 'RESAFM'
            THEN
                fac.clrnotifications('RESUPD', pref, pxref, 'RESMAI');
                fac.clrnotifications('RESOPT', pref, NULL,  'RESMAI');
                fac.clrnotifications('RESBEV', pref, NULL,  'RESMAI');
            WHEN 'RESOPT'
            THEN
                fac.clrnotifications('RESUPD', pref);
            WHEN 'RESBEV'
            THEN
                fac.clrnotifications('RESUPD', pref);
            WHEN 'RESCLN'
            THEN
                fac.clrnotifications('RESUPD', pref);
            WHEN 'BESAFM'
            THEN
                fac.clrnotifications('BESUPD', pref, NULL, 'BESMAI');
            WHEN 'ORDAFM'
            THEN
                fac.clrnotifications('ORDUPD', pref);
            WHEN 'ORDA2B'
            THEN
                fac.clrnotifications('ORDA2U', pref);
                fac.clrnotifications('ORDNOB', pref);
            WHEN 'ORDA2U'
            THEN
                fac.clrnotifications('ORDA2B', pref);
                fac.clrnotifications('ORDNOL', pref);
            ELSE
                NULL;
            END CASE;

         END IF;
      END IF;

      IF lmode > 0 OR lsysteem IS NOT NULL OR pbedrijfadreskey IS NOT NULL
      THEN
         -- Stuur notificatie.
         INSERT INTO fac_notificatie (fac_srtnotificatie_key,
                                      fac_notificatie_status,
                                      prs_perslid_key_sender,
                                      prs_perslid_key_receiver,
                                      fac_notificatie_receiver_email,
                                      fac_notificatie_receiver_phone,
                                      fac_notificatie_oms,
                                      fac_notificatie_refkey,
                                      fac_notificatie_extrakey,
                                      fac_notificatie_prioriteit,
                                      fac_notificatie_sender_email,
                                      fac_notificatie_lang,
                                      fac_notificatie_systeemadres,
                                      fac_notificatie_attachments,
                                      prs_bedrijfadres_key,
                                      fac_notificatie_queue,
                                      fac_notificatie_geturl,
                                      fac_notificatie_notbefore)
             VALUES (skey,
                     lmode,
                     pfrom,
                     pto,
                     lemail,
                     lphone,
                     soms,
                     pref,
                     pxref,
                     CASE WHEN l_srtnoti_delay = 0 THEN 1 ELSE pprio END, -- delay 0 altijd hoge prio en 'voorkruipen'
                     psender,
                     llang,
                     lsysteem,
                     pattach,
                     pbedrijfadreskey,
                     pqueue,
                     pgeturl,
                     SYSDATE + NVL(l_srtnoti_delay/ 60/ 60/ 24, 0));
      END IF;
   END;

   -- Ruim zoveel mogelijk notificatiespul op voor dit item uitgezonderd de meegegeven notificatie. Pcode mag een wildcard zijn!
   PROCEDURE clrnotifications (pcode VARCHAR2, pref NUMBER, pxref NUMBER DEFAULT NULL, puitz VARCHAR2 DEFAULT NULL, puitz2 VARCHAR2 DEFAULT NULL)
   AS
      skey     fac_srtnotificatie.fac_srtnotificatie_key%TYPE;
   BEGIN
      -- gequeuede berichten
      DELETE FROM fac_notificatie
       WHERE fac_srtnotificatie_key IN (SELECT sn.fac_srtnotificatie_key
                                          FROM fac_srtnotificatie sn
                                         WHERE sn.fac_srtnotificatie_code LIKE pcode
                                           AND sn.fac_srtnotificatie_code NOT IN (COALESCE(puitz, ' '), COALESCE(puitz2, ' '))
                                       )
         AND fac_notificatie_refkey = pref
         AND fac_notificatie_systeemadres IS NULL
         AND prs_bedrijfadres_key IS NULL
         AND BITAND (fac_notificatie_status, 64) = 0 -- Bitje 64 (custom notificatie MARX) ongemoeid laten
         AND COALESCE(fac_notificatie_extrakey, -1) = COALESCE(pxref, fac_notificatie_extrakey, -1);

      -- portalberichten. Hier moet je wel de dubbele webuser_messages verwijderen. Hier gaat het om ander notificaties als dan die net aangemaakt zijn.
      DELETE FROM web_user_messages
       WHERE fac_srtnotificatie_key IN (SELECT sn.fac_srtnotificatie_key
                                          FROM fac_srtnotificatie sn
                                         WHERE sn.fac_srtnotificatie_code LIKE pcode
                                           AND sn.fac_srtnotificatie_code NOT IN (COALESCE(puitz, ' '), COALESCE(puitz2, ' '))
                                       )
         AND web_user_mess_action_params = pref;

   END;
   -- Nu de functie met twee extra parameters, de omschrijving en de ontvanger van de notificatie.
   PROCEDURE clrnotifications (pcode VARCHAR2, pref NUMBER, psubject VARCHAR2, preceiver NUMBER, pextrakey NUMBER)
   AS
      skey     fac_srtnotificatie.fac_srtnotificatie_key%TYPE;
   BEGIN
      -- gequeuede berichten
      DELETE FROM fac_notificatie
       WHERE fac_srtnotificatie_key IN (SELECT sn.fac_srtnotificatie_key
                                          FROM fac_srtnotificatie sn
                                         WHERE sn.fac_srtnotificatie_code LIKE pcode)
         AND fac_notificatie_systeemadres IS NULL
         AND prs_bedrijfadres_key IS NULL
         AND fac_notificatie_refkey = pref
         AND fac_notificatie_oms = psubject
         AND prs_perslid_key_receiver = preceiver
         AND BITAND (fac_notificatie_status, 64) = 0 -- Bitje 64 (custom notificatie MARX) ongemoeid laten
         AND COALESCE (fac_notificatie_extrakey, -1) = COALESCE (pextrakey, -1);

      -- portalberichten: fac_notificatie verwijderd de dubbelen wel in zijn insert trigger (nieuwste notificatie wordt hierna geinsert).
   END;

   PROCEDURE clrnotifications_xmlnode (pxmlnode VARCHAR2, prefkey NUMBER)
   AS
   BEGIN
      -- Notificaties die niet vanzelf weg gaan als de xmlnode wordt verwijderd alsnog verwijderen.
      DELETE FROM fac_notificatie
       WHERE fac_srtnotificatie_key IN (SELECT sn.fac_srtnotificatie_key
                                          FROM fac_srtnotificatie sn
                                         WHERE sn.fac_srtnotificatie_xmlnode = pxmlnode)
         AND BITAND (fac_notificatie_status, 64) = 0 -- Bitje 64 (custom notificatie MARX) ongemoeid laten
         AND fac_notificatie_refkey = prefkey;
   END;

   PROCEDURE clrtracking_xmlnode (pxmlnode VARCHAR2, prefkey NUMBER)
   AS
   BEGIN
      -- Tracking verwijderen die niet weg gaat als de xmlnode wordt verwijderd.
      DELETE FROM fac_tracking
            WHERE fac_tracking_key IN (SELECT fac_tracking_key
                                         FROM fac_tracking ft,
                                              fac_srtnotificatie fs
                                        WHERE ft.fac_srtnotificatie_key = fs.fac_srtnotificatie_key
                                          AND fs.fac_srtnotificatie_xmlnode = pxmlnode
                                          AND ft.fac_tracking_refkey = prefkey);
   END;

   -- Kijk welke jobs gescheduled zijn en nu uitgevoerd moeten worden.
   -- Dit kan ook voor een specifieke jobqueue (niet voor meerdere queue's tegelijk)
   PROCEDURE putnotificationjobs (pqueue VARCHAR2 DEFAULT 'DEFAULT')
   AS
      CURSOR c1
      IS
         SELECT fac_notificatie_job_key, fac_notificatie_job_view, fac_notificatie_job_oms,
                fac_notificatie_job_interval, fac_notificatie_job_mode, fac_notificatie_job_nextrun,
                fac_notificatie_job_flags, fac_notificatie_job_seconds,
                fac_notificatie_job_geturl, prs_perslid_key, prs_perslid_key_receiver
           FROM fac_notificatie_job
          WHERE (fac_notificatie_job_nextrun <= SYSDATE OR fac_notificatie_job_nextrun IS NULL) -- eerste keer
            AND fac_notificatie_job_queue = pqueue;
       tnextrun fac_notificatie_job.fac_notificatie_job_nextrun%TYPE;
   BEGIN
      fac_hourly(); -- Klant specifieke en cleanup
      FOR rec1 IN c1
      LOOP
         -- Voer de job uit
         IF (rec1.fac_notificatie_job_view IS NOT NULL)
         THEN
            putjobnotifications (rec1.fac_notificatie_job_view, rec1.fac_notificatie_job_mode, rec1.fac_notificatie_job_flags, pqueue);
         ELSE -- moet fac_notificatie_job_geturl gevuld zijn
           putnotificationprio (rec1.prs_perslid_key,
                                rec1.prs_perslid_key_receiver,
                                rec1.fac_notificatie_job_oms,
                                2, -- e-mail
                                NULL,
                                NULL,
                                2, -- prio normaal
                                pqueue => pqueue,
                                pgeturl => rec1.fac_notificatie_job_geturl);
         END IF;

         UPDATE fac_notificatie_job
            SET fac_notificatie_job_lastrun = SYSDATE
          WHERE fac_notificatie_job_key = rec1.fac_notificatie_job_key;
         -- Notificeer de uitvoering van de job aan de beheerder(s)? Regel dat zelf maar!
         -- putsystemnotification ('Notificatiejob '  ||  rec1.fac_notificatie_job_oms  ||  ' uitgevoerd.', 1);
         -- Bepaal de volgende keer voor deze job, en garandeer dat dat altijd NA NU gaat gebeuren
         -- dus zonder dat eventueel overgeslagen keren nog zullen worden ingehaald.
         tnextrun := COALESCE (rec1.fac_notificatie_job_nextrun, SYSDATE);

         WHILE tnextrun <= SYSDATE
         LOOP
            IF rec1.fac_notificatie_job_interval IS NOT NULL AND rec1.fac_notificatie_job_interval > 0
            THEN -- Notify-interval in hrs, DATE arithmetics are in days, so we will have to divide by 24 hrs a day
                tnextrun := tnextrun + rec1.fac_notificatie_job_interval / 24;
            ELSIF rec1.fac_notificatie_job_interval IS NOT NULL AND rec1.fac_notificatie_job_interval < 0
            THEN -- Notify-interval in Months
                tnextrun := ADD_MONTHS(tnextrun, -rec1.fac_notificatie_job_interval);
            ELSIF rec1.fac_notificatie_job_seconds IS NOT NULL AND rec1.fac_notificatie_job_seconds > 0
            THEN -- Command-interval in seconds, DATE arithmetics are in days, so we will have to divide by 24*60*60 seconds a day
                tnextrun := tnextrun + rec1.fac_notificatie_job_seconds / (24*60*60);
            END IF;
         END LOOP;
         UPDATE fac_notificatie_job
            SET fac_notificatie_job_nextrun = tnextrun
          WHERE fac_notificatie_job_key = rec1.fac_notificatie_job_key;
      END LOOP;
   END;

   FUNCTION hasColumn(ptableview VARCHAR2, pcolumname VARCHAR2, pdatatype VARCHAR2 DEFAULT NULL) RETURN VARCHAR2
   AS
    datatype VARCHAR2(128);
   BEGIN
      BEGIN
          SELECT data_type INTO datatype
            FROM user_tab_cols
           WHERE table_name = UPPER(ptableview)
             AND column_name = UPPER(pcolumname);
      EXCEPTION
            WHEN NO_DATA_FOUND
            THEN RETURN NULL;
      END;
      IF pdatatype IS NOT NULL AND datatype <> UPPER(pdatatype)
      THEN
        RETURN NULL;
      END IF;

      RETURN datatype;
   END;

   FUNCTION add_jobview_column(psql VARCHAR2, ptableview VARCHAR2, pcolumname VARCHAR2, pdatatype VARCHAR2 DEFAULT NULL) RETURN VARCHAR2
   AS
      lsql VARCHAR2(4000);
   BEGIN
      lsql := psql;
      IF lsql IS NOT NULL THEN
        lsql := lsql || ', ';
      END IF;
      IF hasColumn(ptableview, pcolumname, pdatatype) IS NULL
      THEN
        RETURN lsql || 'NULL ' || pcolumname;
      ELSE
        RETURN lsql || 'jobview.' || pcolumname;
      END IF;
   END;

   PROCEDURE cleannotifications
   AS
   BEGIN
      -- Ruim alle totaal verwerkte notificaties op.
      -- Putorders heeft de meeste al wel gedaan maar iets als portal only komt niet per se langs putorders
      DELETE FROM fac_notificatie
       WHERE fac_notificatie_queue = 'DEFAULT'
         AND fac_notificatie_status IN (0, 16)
         AND fac_notificatie_systeemadres IS NULL
         AND prs_bedrijfadres_key IS NULL;
   END;

   -- Voer nu job met pviewname uit, die kennelijk nu aan de beurt is
   PROCEDURE putjobnotifications (pviewname VARCHAR2, pmode NUMBER, pflags NUMBER, pqueue VARCHAR2 DEFAULT 'DEFAULT')
   AS
       TYPE noticursortype IS REF CURSOR;

       messages           noticursortype;
       messages_fields    VARCHAR2(4000);
       messages_sql       VARCHAR2(4000);
       lsender            fac_notificatie.prs_perslid_key_sender%TYPE;
       lreceiver          fac_notificatie.prs_perslid_key_receiver%TYPE;
       ltext              fac_notificatie.fac_notificatie_oms%TYPE;
       lcode              fac_srtnotificatie.fac_srtnotificatie_code%TYPE;
       lsrt_key           fac_srtnotificatie.fac_srtnotificatie_key%TYPE;
       lrefkey            fac_notificatie.fac_notificatie_refkey%TYPE;
       lxkey              fac_notificatie.fac_notificatie_extrakey%TYPE;
       lxsender           fac_notificatie.fac_notificatie_sender_email%TYPE;
       lxemail            fac_notificatie.fac_notificatie_receiver_email%TYPE;
       lxmobile           fac_notificatie.fac_notificatie_receiver_phone%TYPE;
       attachments        fac_notificatie.fac_notificatie_attachments%TYPE;
       lbedrijfadreskey   fac_notificatie.prs_bedrijfadres_key%TYPE;
       geturl             fac_notificatie.fac_notificatie_geturl%TYPE;
       getbody            fac_notificatie.fac_notificatie_getbody%TYPE;
   BEGIN
       -- The flags are for compatibility mostly. Flags 2 and 4 cannot be combined (useless)
       -- flags: 0=default,  1=view has xemail and xmobile, 2=use fac.notifytracking instead of fac.putnotification(srtprio)
       --        4=view has attachments, 8=view has xsender
       --        16=do tracking of text (requires code, key and text to be NOT NULL)
       -- Kijk of de extra kolommen xemail en xmobile in de view zitten, dan gebruiken we die ook
       -- Merk op: met deze implementatie zijn niet alle combi's ondersteund, we wegen 32 het zwaarst

       messages_fields := add_jobview_column(messages_fields, pviewname, 'sender');
       messages_fields := add_jobview_column(messages_fields, pviewname, 'receiver');
       messages_fields := add_jobview_column(messages_fields, pviewname, 'text');
       messages_fields := add_jobview_column(messages_fields, pviewname, 'code');
       IF hasColumn(pviewname, 'fac_srtnotificatie_key') IS NOT NULL
       THEN
          messages_fields := messages_fields || ', jobview.fac_srtnotificatie_key'; -- meegegeven key gebruiken
       ELSIF hasColumn(pviewname, 'code') IS NOT NULL
       THEN
          messages_fields := messages_fields || ', fac_srtnotificatie.fac_srtnotificatie_key'; -- zelf code naar key vertaling proberen
       ELSE
          messages_fields := messages_fields || ', NULL fac_srtnotificatie_key';
       END IF;
       messages_fields := add_jobview_column(messages_fields, pviewname, 'key');
       messages_fields := add_jobview_column(messages_fields, pviewname, 'xkey');
       messages_fields := add_jobview_column(messages_fields, pviewname, 'xsender');
       messages_fields := add_jobview_column(messages_fields, pviewname, 'xemail');
       messages_fields := add_jobview_column(messages_fields, pviewname, 'xmobile');
       messages_fields := add_jobview_column(messages_fields, pviewname, 'attachments');
       messages_fields := add_jobview_column(messages_fields, pviewname, 'prs_bedrijfadres_key');
       messages_fields := add_jobview_column(messages_fields, pviewname, 'geturl');
       messages_fields := add_jobview_column(messages_fields, pviewname, 'getbody');

       messages_sql := 'SELECT ' || messages_fields
                    || '  FROM ' || pviewname || ' jobview';
       IF hasColumn(pviewname, 'fac_srtnotificatie_key') IS NULL AND hasColumn(pviewname, 'code') IS NOT NULL
       THEN
           messages_sql := messages_sql
                        || '     , fac_srtnotificatie '
                        || ' WHERE jobview.code = fac_srtnotificatie.fac_srtnotificatie_code(+)';
       END IF;
       DBMS_OUTPUT.put_line ('Job view sql: ' || messages_sql);

       OPEN messages FOR messages_sql;
       LOOP
           FETCH messages
           INTO lsender, lreceiver, ltext, lcode, lsrt_key, lrefkey,
                lxkey, lxsender, lxemail, lxmobile,
                attachments, lbedrijfadreskey, geturl, getbody;

           EXIT WHEN messages%NOTFOUND;

           -- we willen weten als we teveel sturen
           IF LENGTH(ltext) > 2048
           THEN
                raise_application_error(-20001, 'Facilitor warning: Jobcommand text too long');
           END IF;

           IF BITAND(pflags, 16) = 16 AND lcode IS NOT NULL AND lrefkey IS NOT NULL AND ltext IS NOT NULL
           THEN
                fac.trackaction (lcode, lrefkey, lsender /*user_key*/, NULL /* datum */, ltext);
           END IF;

           IF BITAND(pflags, 2) = 2
           THEN
               notifytracking(lsrt_key,
                              lsender,
                              ltext,
                              lxkey);

           ELSIF lcode IS NULL
           THEN
               putnotificationprio (lsender,
                                    lreceiver,
                                    ltext,
                                    pmode,
                                    lxemail,
                                    lxmobile,
                                    3, -- altijd lage prio
                                    pattach => attachments,
                                    pbedrijfadreskey => lbedrijfadreskey,
                                    pqueue => pqueue,
                                    pgeturl => geturl);
           ELSE
               putnotificationsrtprio (lsender,
                                       lreceiver,
                                       lcode,
                                       lrefkey,
                                       ltext,
                                       pmode,
                                       lxemail,
                                       lxmobile,
                                       lxkey,
                                       3, -- altijd lage prio
                                       lxsender,
                                       pattach => attachments,
                                       pbedrijfadreskey => lbedrijfadreskey,
                                       pqueue => pqueue,
                                       pgeturl => geturl);
           END IF;
       END LOOP;

       CLOSE messages;
   EXCEPTION
       WHEN OTHERS
       THEN
          DBMS_OUTPUT.put_line ('Facilitor notifier: Invalid notification job ' || pviewname ||' ('|| SQLERRM ||')');
          putsystemnotification ('Facilitor notifier: Invalid notification job ' || pviewname ||' ('|| SQLERRM ||')' , 3);
   END;


   PROCEDURE executeactiviteit (pxmlnode VARCHAR2, pactkey NUMBER)
   AS
      CURSOR berekendeflex (
         mkey      IN            NUMBER,
         stdmkey   IN            NUMBER)
      IS
         -- de gedefinieerde verplichte sql-kenmerken die niet ingevuld zijn
         SELECT k.mld_kenmerk_key,
                REPLACE (k.mld_kenmerk_default, '##SQL##', '') mld_kenmerk_defaultsql
           FROM mld_srtkenmerk t,
                mld_kenmerk k,
                mld_stdmelding s,
                mld_discipline d
          WHERE s.mld_stdmelding_key = stdmkey
                AND s.mld_ins_discipline_key = d.ins_discipline_key
                AND ((k.mld_kenmerk_niveau = 'A' AND k.mld_stdmelding_key IS NULL)
                  OR (k.mld_kenmerk_niveau = 'S' AND k.mld_stdmelding_key = s.mld_stdmelding_key)
                  OR (k.mld_kenmerk_niveau = 'D' AND k.mld_stdmelding_key = s.mld_ins_discipline_key)
                  OR (k.mld_kenmerk_niveau = 'T' AND k.mld_stdmelding_key = d.ins_srtdiscipline_key))
                AND k.mld_kenmerk_verwijder IS NULL
                AND k.mld_srtkenmerk_key = t.mld_srtkenmerk_key
                AND k.mld_kenmerk_default LIKE '%##SQL##%'
                AND k.mld_kenmerk_verplicht = 1
                AND t.mld_srtkenmerk_verwijder IS NULL
                AND BITAND(k.mld_kenmerk_rolcode, 3) > 0
                AND NOT EXISTS
                      (SELECT ''
                         FROM mld_kenmerkmelding km
                        WHERE     mld_kenmerkmelding_verwijder IS NULL
                              AND km.mld_kenmerk_key = k.mld_kenmerk_key
                              AND mld_melding_key = mkey);

      CURSOR rootflex (mkey IN NUMBER)
      IS
         SELECT fac_bijlagen_key,
                fac_bijlagen_kenmerk_key,
                fac_bijlagen_module
           FROM fac_bijlagen
          WHERE fac_bijlagen_verwijder IS NULL
            AND fac_bijlagen_module = CASE
                                      WHEN pxmlnode = 'bestelling'
                                      THEN 'BES'
                                      ELSE 'MLD'
                                      END
            AND fac_bijlagen_refkey = mkey
         ORDER BY fac_bijlagen_key;

      lkey               NUMBER (10);
      lstdmkey           mld_melding.mld_stdmelding_key%TYPE;
      behandelaar_key    mld_melding.mld_melding_behandelaar_key%TYPE;
      behandelaar2_key   mld_melding.mld_melding_behandelaar2_key%TYPE;
      behandelaar        prs_v_perslid_fullnames_all.prs_perslid_naam_full%TYPE;
      behandelaar2       prs_v_perslid_fullnames_all.prs_perslid_naam_full%TYPE;
      lmld_ins_disc_key  mld_melding.mld_ins_discipline_key%TYPE;
      lresolveddefault   mld_kenmerkmelding.mld_kenmerkmelding_waarde%TYPE;
      lkey_m             NUMBER (10);
      lkey_new           NUMBER (10);
      luserkey           NUMBER (10);
      lvolgnr            mld_opdr.mld_opdr_bedrijfopdr_volgnr%TYPE;
      leinddatum         mld_melding.mld_melding_einddatum_std%TYPE;
   BEGIN
      BEGIN
         CASE pxmlnode
            WHEN 'melding'
            THEN
               SELECT mld_melding_key,
                      mld_stdmelding_key,
                      mld_melding_behandelaar_key,
                      mld_melding_behandelaar2_key,
                      p_beh.prs_perslid_naam_full,
                      p_beh2.prs_perslid_naam_full
                 INTO lkey,
                      lstdmkey,
                      behandelaar_key,
                      behandelaar2_key,
                      behandelaar,
                      behandelaar2
                 FROM mld_melding                  m,
                      prs_v_perslid_fullnames_all  p_beh,
                      prs_v_perslid_fullnames_all  p_beh2
                WHERE     fac_activiteit_key = pactkey
                      AND m.mld_melding_behandelaar_key = p_beh.prs_perslid_key(+)
                      AND m.mld_melding_behandelaar2_key = p_beh2.prs_perslid_key(+);

               SELECT mld_s_mld_melding_key.NEXTVAL INTO lkey_new FROM DUAL;

               INSERT INTO mld_melding (mld_melding_key,
                                        mld_melding_module,
                                        mld_meldbron_key,
                                        mld_alg_locatie_key,
                                        mld_alg_onroerendgoed_keys,
                                        mld_melding_datum,
                                        mld_melding_onderwerp,
                                        mld_melding_omschrijving,
                                        mld_melding_opmerking,
                                        mld_melding_behandelaar_key,
                                        mld_melding_behandelaar2_key,
                                        mld_stdmelding_key,
                                        mld_melding_t_uitvoertijd,
                                        mld_melding_t_accepttijd,
                                        mld_kosten_klant,
                                        mld_melding_document,
                                        prs_kostenplaats_key,
                                        prs_perslid_key,
                                        mld_adres_key,
                                        mld_workflowstep_key,
                                        mld_melding_start_key,
                                        mld_melding_ordernr,
                                        mld_melding_spoed,
                                        mld_ins_discipline_key)
                  SELECT lkey_new,
                         mld_melding_module,
                         mld_meldbron_key,
                         mld_alg_locatie_key,
                         mld_alg_onroerendgoed_keys,
                         SYSDATE,
                         mld_melding_onderwerp,
                            lcl.l ('lcl_mld_activity_generated_by')
                         || pactkey
                         || CHR(10)
                         || mld_melding_omschrijving,
                         mld_melding_opmerking,
                         behandelaar_key,
                         behandelaar2_key,
                         mld_stdmelding_key,
                         mld_melding_t_uitvoertijd,
                         mld_melding_t_accepttijd,
                         mld_kosten_klant,
                         mld_melding_document,
                         prs_kostenplaats_key,
                         prs_perslid_key,
                         mld_adres_key,
                         NULL,
                         NULL,
                         mld_melding_ordernr,
                         mld_melding_spoed,
                         mld_ins_discipline_key
                    FROM mld_melding
                   WHERE mld_melding_key = lkey;

               -- Alle ingevulde kenmerken gewoon overnemen
               INSERT INTO mld_kenmerkmelding (mld_melding_key, mld_kenmerk_key, mld_kenmerkmelding_waarde)
                  SELECT lkey_new, mld_kenmerk_key, mld_kenmerkmelding_waarde
                    FROM mld_kenmerkmelding
                   WHERE mld_kenmerkmelding_verwijder IS NULL AND mld_melding_key = lkey;

               -- En van alle niet-ingevulde verplichte kenmerken de defaultwaarde berekenen als
               -- dat een dynamische waarde is. We ondersteunen hier niet de substitutie van
               -- ##PRS_PERSLID_KEY##, de melder is nl fixed en een eventueel daarvan afhangende
               -- waarde kan dat dan logischerwijs doorgaans ook zijn (versimpeling).
               FOR bf IN berekendeflex (lkey, lstdmkey) LOOP
                  BEGIN
                     EXECUTE IMMEDIATE (bf.mld_kenmerk_defaultsql) INTO lresolveddefault;

                     INSERT INTO mld_kenmerkmelding (mld_melding_key,
                                                     mld_kenmerk_key,
                                                     mld_kenmerkmelding_waarde)
                         VALUES (lkey_new, bf.mld_kenmerk_key, lresolveddefault);
                  END;
               END LOOP;

               -- Bijlagen.
               FOR rf IN rootflex (lkey) LOOP
                  BEGIN
                     flx.copyflexbijlage(rf.fac_bijlagen_module, lkey_new, rf.fac_bijlagen_kenmerk_key, rf.fac_bijlagen_key);
                  END;
               END LOOP;

               INSERT INTO mld_melding_object (mld_melding_key, ins_deel_key)
                  SELECT lkey_new, ins_deel_key
                    FROM mld_melding_object
                   WHERE mld_melding_object_verwijder IS NULL AND mld_melding_key = lkey;

               mld.setmeldingstatus (lkey_new, 2, NULL);

               SELECT mld_ins_discipline_key
                 INTO lmld_ins_disc_key
                 FROM mld_melding
                WHERE mld_melding_key = lkey_new;

               IF lmld_ins_disc_key IS NOT NULL -- mldbhg notificatie
               THEN
                  mld.notifybackoffice (lkey_new, 'MLDBHG', 2);
               END IF;

               IF behandelaar_key IS NOT NULL AND behandelaar IS NOT NULL -- Hieruit volgt een MLDBE2 notificatie (de notificatie van MLDBEH zelf vissen we er tussenuit bij MLDNEW)
               THEN
                  fac.trackaction ('MLDBEH', lkey_new, behandelaar_key, NULL, lcl.l('lcl_mld_is_mldbeh_to') || behandelaar);
               END IF;

               IF behandelaar2_key IS NOT NULL AND behandelaar2 IS NOT NULL -- Hieruit volgt een MLDAB2 notificatie (de notificatie van MLDABH zelf vissen we er tussenuit bij MLDNEW)
               THEN
                  fac.trackaction ('MLDABH', lkey_new, behandelaar2_key, NULL, lcl.l('lcl_mld_is_mldabh_to') || behandelaar2);
               END IF;

               -- Eventuele (gewijzigde) objecten zijn nu ook opgeslagen.
               -- Nu kunnen pas de definitieve mld_melding.mld_melding_einddatum en mld_melding.mld_melding_einddatum_std bepaald en gezet worden.
               leinddatum := mld.geteinddatum (lkey_new);

               UPDATE mld_melding
                  SET mld_melding_einddatum = leinddatum, mld_melding_einddatum_std = leinddatum
                WHERE mld_melding_key = lkey_new;

               -- Controleer of er automatisch een opdracht aangemaakt moet worden.
               mld.mld_addautoorder(lkey_new);
            WHEN 'opdracht'
            THEN
               SELECT MAX (mld_opdr_key), MAX (mld_melding_key)
                 INTO lkey, lkey_m
                 FROM mld_opdr
                WHERE fac_activiteit_key = pactkey;

               SELECT mld.bepaalopdrmeldingvolgnr (lkey_m) INTO lvolgnr FROM DUAL;

               SELECT mld_s_mld_opdr_key.NEXTVAL INTO lkey_new FROM DUAL;

               INSERT INTO mld_opdr (mld_opdr_key,
                                     mld_opdr_module,
                                     mld_melding_key,
                                     mld_uitvoerende_keys,
                                     mld_typeopdr_key,
                                     mld_statusopdr_key,
                                     mld_standaardopdr_key,
                                     mld_opdr_omschrijving,
                                     mld_opdr_datumbegin,
                                     mld_opdr_einddatum,
                                     mld_opdr_werkzaamheden,
                                     mld_opdr_uren,
                                     mld_opdr_materiaal,
                                     mld_opdr_kosten,
                                     mld_opdr_contactpersoon,
                                     mld_opdr_bedrijfopdr_volgnr,
                                     mld_opdr_uurloon,
                                     prs_perslid_key,
                                     prs_contactpersoon_key,
                                     cnt_contract_key,
                                     cnt_contract_dienst_key,
                                     prs_kostenplaats_key,
                                     mld_opdr_ordernr,
                                     mld_opdr_teverzenden)
                  SELECT lkey_new,
                         mld_opdr_module,
                         mld_melding_key,
                         mld_uitvoerende_keys,
                         mld_typeopdr_key,
                         mld_statusopdr_key,
                         mld_standaardopdr_key,
                         mld_opdr_omschrijving,
                         SYSDATE,
                         mld.geteinddatum(mld_melding_key, null, SYSDATE),
                            lcl.l ('lcl_mld_activity_generated_by')
                         || pactkey
                         || '. '
                         || mld_opdr_werkzaamheden,
                         mld_opdr_uren,
                         mld_opdr_materiaal,
                         mld_opdr_kosten,
                         mld_opdr_contactpersoon,
                         lvolgnr,
                         mld_opdr_uurloon,
                         prs_perslid_key,
                         prs_contactpersoon_key,
                         cnt_contract_key,
                         cnt_contract_dienst_key,
                         prs_kostenplaats_key,
                         mld_opdr_ordernr,
                         mld_opdr_teverzenden
                    FROM mld_opdr
                   WHERE mld_opdr_key = lkey;

               INSERT INTO mld_kenmerkopdr (mld_opdr_key, mld_kenmerk_key, mld_kenmerkopdr_waarde)
                  SELECT lkey_new, mld_kenmerk_key, mld_kenmerkopdr_waarde
                    FROM mld_kenmerkopdr
                   WHERE mld_kenmerkopdr_verwijder IS NULL AND mld_opdr_key = lkey;

               -- Bijlagen.
               FOR rf IN rootflex (lkey) LOOP
                  BEGIN
                     flx.copyflexbijlage(rf.fac_bijlagen_module, lkey_new, rf.fac_bijlagen_kenmerk_key, rf.fac_bijlagen_key);
                  END;
               END LOOP;

               mld.setopdrachtstatus (lkey_new, 2, NULL);
            WHEN 'bestelling'
            THEN
               SELECT MAX (bes_bestelling_key), MAX (prs_perslid_key)
                 INTO lkey, luserkey
                 FROM bes_bestelling
                WHERE fac_activiteit_key = pactkey;

               SELECT bes_s_bes_bestelling_key.NEXTVAL INTO lkey_new FROM DUAL;

               -- Bestelling kopiëren.
               INSERT INTO bes_bestelling (bes_bestelling_key,
                                           bes_bestelling_module,
                                           bes_bestelling_status,
                                           bes_bestelling_datum,
                                           prs_perslid_key,
                                           prs_perslid_key_voor,
                                           bes_bestelling_opmerking,
                                           bes_bestelling_ordernr,
                                           bes_bestelling_plaats,
                                           mld_adres_key_lev,
                                           prs_kostenplaats_key,
                                           bes_bestelling_leverdatum,
                                           bes_bestelling_kosten_klant)
                  SELECT lkey_new,
                         bes_bestelling_module,
                         bes_bestelling_status,
                         SYSDATE,
                         prs_perslid_key,
                         prs_perslid_key_voor,
                            lcl.l ('lcl_mld_activity_generated_by')
                         || pactkey
                         || '. '
                         || bes_bestelling_opmerking,
                         bes_bestelling_ordernr,
                         bes_bestelling_plaats,
                         mld_adres_key_lev,
                         prs_kostenplaats_key,
                         TRUNC (SYSDATE + (bes_bestelling_leverdatum - bes_bestelling_datum)),
                         bes_bestelling_kosten_klant
                    FROM bes_bestelling
                   WHERE bes_bestelling_key = lkey;

               -- Kenmerken van bestelling kopiëren.
               INSERT INTO bes_kenmerkbestell (bes_bestelling_key, bes_kenmerk_key, bes_kenmerkbestell_waarde)
                  SELECT lkey_new, bes_kenmerk_key, bes_kenmerkbestell_waarde
                    FROM bes_kenmerkbestell
                   WHERE bes_kenmerkbestell_verwijder IS NULL AND bes_bestelling_key = lkey;

               -- Bijlagen bestelling.
               FOR rf IN rootflex (lkey) LOOP
                  BEGIN
                     flx.copyflexbijlage(rf.fac_bijlagen_module, lkey_new, rf.fac_bijlagen_kenmerk_key, rf.fac_bijlagen_key);
                  END;
               END LOOP;

               -- Bestelregels kopiëren.
               INSERT INTO bes_bestelling_item (bes_bestelling_key,
                                                bes_bestelling_item_aantal,
                                                bes_bestelling_item_aantalontv,
                                                bes_bestelling_item_brutoprijs,
                                                bes_bestelling_item_prijs,
                                                bes_srtdeel_key)
                  SELECT lkey_new,
                         bes_bestelling_item_aantal,
                         bes_bestelling_item_aantalontv,
                         bes.getsrtdeelprijsinfo (bi.bes_srtdeel_key, SYSDATE, 1),
                         bes.getsrtdeelprijsinfo (bi.bes_srtdeel_key, SYSDATE, 1),
                         bi.bes_srtdeel_key
                    FROM bes_bestelling_item bi, bes_srtdeel isd
                   WHERE bes_bestelling_key = lkey AND isd.bes_srtdeel_key = bi.bes_srtdeel_key;

               -- Kenmerken van bestelregels kopiëren.
               INSERT INTO bes_kenmerkbesteli (bes_bestelling_item_key,
                                               bes_kenmerk_key,
                                               bes_kenmerkbesteli_waarde,
                                               bes_kenmerkbesteli_aanmaak)
                  SELECT tab1.bes_bestelling_item_key_new,
                         kbi.bes_kenmerk_key,
                         kbi.bes_kenmerkbesteli_waarde,
                         SYSDATE
                    FROM bes_kenmerkbesteli kbi,
                         (SELECT bi1.bes_bestelling_item_key bes_bestelling_item_key_new,
                                 bi.bes_bestelling_item_key,
                                 bi.bes_srtdeel_key
                            FROM bes_bestelling_item bi, bes_bestelling_item bi1
                           WHERE     bi.bes_bestelling_key = lkey
                                 AND bi1.bes_srtdeel_key = bi.bes_srtdeel_key
                                 AND bi1.bes_bestelling_key = lkey_new) tab1
                   WHERE kbi.bes_bestelling_item_key = tab1.bes_bestelling_item_key
                         AND kbi.bes_bestelling_item_key IN (SELECT bes_bestelling_item_key
                                                               FROM bes_bestelling_item bi
                                                              WHERE bi.bes_bestelling_key = lkey);

               -- Het gaat hier om een geplande actie. Je mag er van uit gaan dat deze goed gekeurd is voordat deze is aangemaakt.
               -- Daarom bestellingstatus op 4 zetten (Geaccepteerd) en bestelopdracht automatisch aanmaken.
               bes.setbestellingstatus (lkey_new, 4, luserkey);                      -- Geaccepteerd
               bes.makeorders (luserkey, lkey_new); -- bes_makeorders notificeert zelf de leveranciers.
         END CASE;
      EXCEPTION
         WHEN OTHERS
         THEN
            putsystemnotification (
                  'Facilitor scheduler: Unable to create planned activity '
               || pactkey
               || ' ('
               || SQLERRM||')',
               3);
      END;
   END;

   PROCEDURE fac_custprocedure(procname VARCHAR2)
   AS
      cust_proc      VARCHAR2(20);
      has_cust_proc  NUMBER(1);
   BEGIN
      SELECT fac_version_cust || '_' || procname
        INTO cust_proc
        FROM fac_version;

      SELECT COUNT(*)
        INTO has_cust_proc
        FROM user_procedures
       WHERE object_name = cust_proc;

      -- als xxxx_proc bestaat dan deze aanroepen.
      IF (has_cust_proc = 1)
      THEN
         BEGIN
            EXECUTE IMMEDIATE 'BEGIN ' || cust_proc || '; END;';
         EXCEPTION
            WHEN OTHERS
            THEN
               putsystemnotification ('Facilitor alert: Invalid ' || procname || ' procedure'
                                     || '<br>Procedure ' || cust_proc
                                     || '<br>Error: '|| SQLERRM
                                     || '<br>Please Contact Facilitor helpdesk for support' , 3);
         END;
      END IF;
   END;

   PROCEDURE fac_nightly
   AS
      cust_daily      VARCHAR2(20);
      has_cust_daily  NUMBER(1);
   BEGIN
      fac.executeschedules;

      -- dagelijks
      fac.fac_daily;

      -- als zondag (Julian-date: zondag=1) dan fac.fac_weekly
      IF (fac.getweekdaynum(SYSDATE) = 1)
      THEN
         fac.fac_weekly;
      END IF;

      -- als 1e van de maand dan fac.fac_monthly
      IF (TO_NUMBER(TO_CHAR(SYSDATE, 'dd')) = 1)
      THEN
         fac.fac_monthly;
      END IF;
   END;

   PROCEDURE fac_daily
   AS
      cust_daily      VARCHAR2(20);
      has_cust_daily  NUMBER(1);
      mld_assume_ok_days NUMBER(10);
   BEGIN

      fac.writelog ('FAC_DAILY', 'S', 'Start', '');

      -- Markeer relevante bestelopdrachten automatisch als 'geleverd'
      bes.autodeliver;

      -- Comprimeert of verwijdert de sensorwaarden ouder dan ins_state_history dagen
      ins.compress_states(1);

      -- verwijderen vervangers waarvan vervaldatum vervallen is.
      prs.personen_vervangers_schonen;

      -- Automatische starten en ter uitvoering zetten van taken die voor vandaag of eerder zijn gepland (via plandatum of berekend).
      -- Er wordt bij de taak ook direct een melding aangemaakt.
      ctr.melding_voor_geplande_taken_maken;

      IF (fac.getsetting('prs_purge_inactief') = '1')
      THEN
         -- verwijderen inactieve personen
         prs.personen_inactief_schonen;
      END IF;

      -- Automatische verlenging van contracten.
      cnt.autoverleng_contract();

      -- Automatische inactivering van contracten.
      cnt.autoinactiveer_contract();

      -- Het resetten van de Actie-bij-melder van het afmelden na "mld_assume_ok_days" dagen.
      mld_assume_ok_days := fac.safe_to_number(fac.getsetting('mld_assume_ok_days'));
      IF mld_assume_ok_days > 0
      THEN
         mld.resetpostafmeldactie(256, mld_assume_ok_days);
      END IF;

      fac_custprocedure('DAILY');

      fac.fac_cleanup(0); -- 0 = onvoorwaardelijk

      fac.writelog ('FAC_DAILY', 'S', 'End', '');
   END;

   PROCEDURE fac_weekly
   AS
      cust_weekly      VARCHAR2(20);
      has_cust_weekly  NUMBER(1);
   BEGIN
      fac.writelog ('FAC_WEEKLY', 'S', 'Start', '');

      -- Check wekelijks of er nog meer van vroeger kan worden weggegooid
      -- en doe dat dan. Logging in imp_log onder naam $PURGE$.
      -- By design gaan we vanaf februari over naar een nieuw jaar,
      -- dus de eerste run van februari zal intensief zijn (FCLT#79311)
      -- Dan is het fijn dat het op zondag gebeurt.
      del.fac_purge(1);

      fac_custprocedure('WEEKLY');

      fac.writelog ('FAC_WEEKLY', 'S', 'End', '');
   END;

   PROCEDURE fac_monthly
   AS
      cust_monthly      VARCHAR2(20);
      has_cust_monthly  NUMBER(1);
   BEGIN
      fac.writelog ('FAC_MONTHLY', 'S', 'Start', '');

      -- Er is geen default maandelijkse actie
      fac_custprocedure('MONTHLY');

      fac.writelog ('FAC_MONTHLY', 'S', 'End', '');
   END;

   -- Vlak voor Putorders run aangeroepen.
   PROCEDURE fac_pre_putorders
   AS
      cust_monthly      VARCHAR2(20);
      has_cust_monthly  NUMBER(1);
   BEGIN

      fac_custprocedure('PRE_PUTORDERS');
   END;

   -- Wordt aangeroepen vanuit putnotificationjobs welke doorgaans elk uur draait
   -- Zou netter zijn om fac_hourly aan te roepen vanuit \UTILS\gen_notify\gen_notify.js
   -- en dan van hieruit putnotificationjobs aan te roepen. Maar niet nu.
   PROCEDURE fac_hourly
   AS
   BEGIN
      fac.cleannotifications(); -- Eens per uur beetje opruimen is aardig
      -- fac.putnotificationjobs('DEFAULT');
      fac_custprocedure('HOURLY');
   END;

   PROCEDURE executeschedules
   AS
      newaantal   NUMBER;

      -- De functie fac.calcnextcyclusdate berekent de volgende datum na de meegegeven datum.
      -- Om te bepalen of de startdatum (fac_activiteit_eerste) een geldige waarde eerste actiedatum is voor activiteiten waarvan de eerste actie nog moet plaatsvinden,
      -- wordt de dag voor deze startdatum meegegeven aan de functie.
      -- Omdat de eenheid uurlijks niet wordt ondersteund is de eerst mogelijke volgende dag deze startdatum (fac_activiteit_eerste) of later.
      --
      -- Let op:
      -- Als bijvoorbeeld 1 januari 2019 als startdatum van een acie wordt opgegeven en de actie elke 2-de woensdag van de maand moet plaatsvinden,
      -- dan is de werkelijke eerste actie op woensdag 9 januari 2019.
      -- Dus de datum van de werkelijke eerste actie kan afwijken van de startdatum (fac_activiteit_eerste).
      -- Als fac_activiteit_volgende NULL is, dan is de eerste geplande actie nog niet geweest.
      CURSOR c1
      IS
        SELECT fac_activiteit_key,
               fac_activiteit_xmlnode,
               fac_activiteit_status_key,
               fac_activiteit_aantal
          FROM fac_activiteit
         WHERE ((fac_activiteit_volgende <= SYSDATE AND fac_activiteit_einddatum IS NULL)
             OR (fac_activiteit_volgende <= SYSDATE AND SYSDATE < fac_activiteit_einddatum)
             OR (fac_activiteit_volgende IS NULL AND SYSDATE BETWEEN fac.calcnextcyclusdate(TRUNC(fac_activiteit_eerste, 'DD') - 1,
                                                                                            0, -- mode: moment-modus
                                                                                            COALESCE(fac_activiteit_eenheid, 1),
                                                                                            fac_activiteit_periode,
                                                                                            fac_activiteit_bits,
                                                                                            'Default'
                                                                                           )
                                                                 AND fac_activiteit_einddatum)
             OR (fac_activiteit_volgende IS NULL AND fac_activiteit_einddatum IS NULL
                                                 AND SYSDATE > fac.calcnextcyclusdate(TRUNC(fac_activiteit_eerste, 'DD') - 1,
                                                                                      0, -- mode: moment-modus
                                                                                      COALESCE(fac_activiteit_eenheid, 1),
                                                                                      fac_activiteit_periode,
                                                                                      fac_activiteit_bits,
                                                                                      'Default'
                                                                                     )
                )
               )
           AND fac_activiteit_status_key IN (2, 3)
           AND fac_activiteit_xmlnode IS NOT NULL
           AND NVL (fac_activiteit_aantal, 1) > 0
           AND fac_activiteit_verwijder IS NULL;
   BEGIN
      FOR rec1 IN c1
      LOOP
         -- Voer de job uit
         newaantal := NULL;
         IF rec1.fac_activiteit_aantal IS NOT NULL
         THEN
            newaantal := rec1.fac_activiteit_aantal - 1;
         END IF;

         IF rec1.fac_activiteit_status_key = 2
         THEN
            executeactiviteit (rec1.fac_activiteit_xmlnode, rec1.fac_activiteit_key);

            -- Remove time-part from volgende (ie. set to 0:00)!
            -- De geplande actie is net aangemaakt en de volgende geplande actie vanaf nu (SYSDATE) berekenen.
            UPDATE fac_activiteit
               SET fac_activiteit_volgende = fac.calcnextcyclusdate(TRUNC(SYSDATE, 'DD'),
                                                                    0, -- mode: moment-modus
                                                                    COALESCE(fac_activiteit_eenheid, 1),
                                                                    COALESCE(fac_activiteit_periode, 1),
                                                                    fac_activiteit_bits,
                                                                    'Default'
                                                                   ),
                   fac_activiteit_laatste = SYSDATE,
                   fac_activiteit_aantal = newaantal
             WHERE fac_activiteit_key = rec1.fac_activiteit_key;
         ELSE
            -- 1 keer overslaan, de volgende keer weer wel dus. Ook hier: remove time-part from volgende (ie. set to 0:00)!
            -- De geplande actie is net aangemaakt en de volgende geplande actie vanaf nu (SYSDATE) berekenen.
            UPDATE fac_activiteit
               SET fac_activiteit_volgende = fac.calcnextcyclusdate(TRUNC(SYSDATE, 'DD'),
                                                                    0, -- mode: moment-modus
                                                                    COALESCE(fac_activiteit_eenheid, 1),
                                                                    COALESCE(fac_activiteit_periode, 1),
                                                                    fac_activiteit_bits,
                                                                    'Default'
                                                                   ),
                   fac_activiteit_status_key = 2,
                   fac_activiteit_aantal = newaantal
             WHERE fac_activiteit_key = rec1.fac_activiteit_key;
         END IF;
      END LOOP;
   END;

   -- MarkOrderAsSent
   -- Markeert dat een opdracht verzonden is, zoals door PutOrders.exe
   -- Hoe dat moet is afhankelijk van het soort opdracht: bestelopdracht, meldingsopdracht e.d.
   -- pxmlnode is (lowercase!) de xml-naam van de entiteit (opdracht, bestelopdr)
   -- pkey is de key daarvan
   -- presult geeft het succes van de verzending aan: 0=succesvol, 1=geweigerd, 2=geeninhoudelijkrespons
   --  wat gebruikt *kan* worden bij het bepalen van de status die gezet moet worden.
   -- Schijnbaar is ook presult<0 nog mogelijk als er technisch wat mis ging. Die tracken we ook maar eens gewoon?
   -- presulttext bevat (optioneel) de feedback van de leverancier (bijv. reden van afwijzing bestelopdracht)
   -- Voor customer-orderqueues dient overeenkomstige CUST.markorderassent() te worden gedefinieerd.
   -- Bij presult=2 weet je niet zeker wat de leverancier heeft gedaan, bij mail veronderstel je ontvangst, maar weet je
   --   het niet zeker. Met 5.1.2 (UWVA#21074) is daarvoor de status 4 tussengevoegd
   PROCEDURE markorderassent (pxmlnode VARCHAR2, pkey NUMBER, presult NUMBER, presulttext VARCHAR2, pperslid_key NUMBER DEFAULT NULL)
   AS
      order_confirm prs_bedrijf.prs_bedrijf_order_confirm%TYPE;
      melding_key   mld_opdr.mld_melding_key%TYPE;
   BEGIN
      CASE pxmlnode
         WHEN 'opdracht'
         THEN
            IF presult = 0
            THEN
               UPDATE   mld_opdr
                  SET   mld_opdr_verzonden = SYSDATE
                WHERE   mld_opdr_key = pkey;

               fac.trackaction ('ORDSNT', pkey, pperslid_key, NULL, presulttext);
               mld.setopdrachtstatus (pkey, 5, NULL);
            ELSIF presult = 1 -- (Logische) fout (expliciet afgewezen door uitvoerende)
            THEN
               UPDATE   mld_opdr
                  SET   mld_opdr_verzonden = SYSDATE
                WHERE   mld_opdr_key = pkey;

               fac.trackaction ('ORDSNT', pkey, pperslid_key, NULL, presulttext);
               -- status van de opdracht wordt afgewezen.
               mld.setopdrachtstatus (pkey, 1, NULL);
            ELSIF presult = 2
            THEN
               fac.trackaction ('ORDSNT', pkey, pperslid_key, NULL, presulttext);
               -- De leverancier heeft geen feedback gegeven, wat is het gedrag dan voor deze leverancier?
               SELECT b.prs_bedrijf_order_confirm
                 INTO order_confirm
                 FROM prs_bedrijf b, mld_opdr o
                WHERE b.prs_bedrijf_key = o.mld_uitvoerende_keys
                  AND o.mld_opdr_key = pkey;

               -- NOTA BENE
               -- Zo is deze nog prima compatible. Status 8 is in het huidige gebruik nog niet identiek, dus
               -- daar wachten we nog even mee op een diepgaander design, vandaar if true.
               IF TRUE OR order_confirm = 1
               THEN
                  -- Er volgt later nog een expliciete bevestiging (5=Ter Bevestiging)
                  UPDATE   mld_opdr
                     SET   mld_opdr_verzonden = SYSDATE
                   WHERE   mld_opdr_key = pkey;
                  mld.setopdrachtstatus (pkey, 5, pperslid_key);
               ELSE
                  UPDATE   mld_opdr
                     SET   mld_opdr_verzonden = SYSDATE
                   WHERE   mld_opdr_key = pkey;
                  mld.setopdrachtstatus (pkey, 8, pperslid_key);
               END IF;
            ELSIF presult < 0
            THEN
               -- just track, nothing changes really
               fac.trackaction ('ORDSNX', pkey, pperslid_key, NULL, presulttext);
            END IF;

            SELECT mld_melding_key
              INTO melding_key
              FROM mld_opdr o
             WHERE o.mld_opdr_key = pkey;

            -- als laatste de status van de melding aanpassen.
            mld.updatemeldingstatus (melding_key, 0, pperslid_key);
         WHEN 'bestelopdr'
         THEN
            IF presult = 0
            THEN
               -- Succesvol
               fac.trackaction ('BES2SN', pkey, pperslid_key, NULL, presulttext);
               bes.acceptopdracht(pkey, NULL, presulttext);
            ELSIF presult = 1 -- (Logische) fout
            THEN
               fac.trackaction ('BES2SX', pkey, pperslid_key, NULL, presulttext);
               bes.rejectopdracht(pkey, NULL, presulttext);
            ELSIF presult = 2
            THEN
                -- De leverancier heeft geen feedback gegeven, wat is het gedrag dan voor deze leverancier?
               SELECT b.prs_bedrijf_order_confirm
                 INTO order_confirm
                 FROM prs_bedrijf b, bes_bestelopdr o
                WHERE b.prs_bedrijf_key = o.prs_bedrijf_key
                  AND o.bes_bestelopdr_key = pkey;
                fac.trackaction ('BES2SN', pkey, pperslid_key, NULL, presulttext);

                IF order_confirm = 1
                THEN
                  -- Er volgt later nog een expliciete bevestiging (5=Ter Bevestiging)
                  UPDATE   bes_bestelopdr
                     SET   bes_bestelopdr_status = 5
                   WHERE   bes_bestelopdr_key = pkey;
                  fac.trackaction ('BES2BE', pkey, pperslid_key, NULL, presulttext);
                ELSE
                    bes.acceptopdracht(pkey, pperslid_key, presulttext);
                END IF;
            ELSIF presult < 0
            THEN
               -- just track, nothing changes really
               fac.trackaction ('BES2SX', pkey, pperslid_key, NULL, presulttext);
            END IF;
         WHEN 'contract'
         THEN
            IF presult = 1
            THEN  -- (Logische) fout; kan nog niet voorkomen voorlopig, maar toch
               fac.trackaction ('CNTSNX', pkey, pperslid_key, NULL, presulttext);
            ELSE
               -- Succesvol
               fac.trackaction ('CNTSNT', pkey, pperslid_key, NULL, presulttext);
            END IF;
            -- reset
            UPDATE cnt_contract
               SET cnt_contract_teverzenden = NULL
             WHERE cnt_contract_key = pkey;
         WHEN 'reservering'
         THEN
            IF presult = 0
            THEN               -- Succesvol
               fac.trackaction ('RESSNT', pkey, pperslid_key, NULL, presulttext);
               -- reset
               UPDATE res_rsv_ruimte
                  SET res_rsv_ruimte_verzonden = SYSDATE
               WHERE res_rsv_ruimte_key = pkey;
            ELSE  -- Iets is fout
               fac.trackaction ('RESSNX', pkey, pperslid_key, NULL, presulttext);
            END IF;
      END CASE;
   END;

   -- Markeer een custom-notificatie (fac_notificatie_status & 64) als verzonden
   -- presult geeft het succes van de verzending aan: >= 0 succesvol (hij wordt uit de queue verwijderd),
   --                                                 < 0: technisch gefaald (hij blijft in de queue staan)
   -- presulttext bevat eventueel tekst die bij de xmlnode (notikey --> notiifactie --> srtnotificatie --> xmlnode) getrackt wordt

   -- *** Future use: vooralsnog doe ik alles in API\api_marknotisent.asp ***
   PROCEDURE marknotiassent (pnotikey        NUMBER,
                             presult         NUMBER,
                             presulttext     VARCHAR2,
                             pperslid_key    NUMBER DEFAULT NULL)
   AS
       lxmlnode   fac_srtnotificatie.fac_srtnotificatie_xmlnode%TYPE;
       lkey       fac_srtnotificatie.fac_srtnotificatie_xmlnode%TYPE;
       ltrackcode fac_srtnotificatie.fac_srtnotificatie_code%TYPE;
   BEGIN
       IF presulttext IS NOT NULL
       THEN
           SELECT fac_notificatie_refkey, fac_srtnotificatie_xmlnode
             INTO lkey, lxmlnode
             FROM fac_notificatie fs, fac_srtnotificatie fsn
            WHERE fs.fac_notificatie_key = pnotikey AND fs.fac_srtnotificatie_key = fsn.fac_srtnotificatie_key;

           CASE lxmlnode
               WHEN 'opdracht'
               THEN
                   ltrackcode := 'ORDTRK';
               WHEN 'bestelopdr'
               THEN
                   ltrackcode := 'BESTRK';
               WHEN 'contract'
               THEN
                   ltrackcode := 'CNTTRK';
               ELSE
                raise_application_error(-20001, 'FACILITOR: Unknown notification');
           END CASE;
           fac.trackaction (ltrackcode,
                            lkey,
                            pperslid_key,
                            NULL,
                            presulttext);
       END IF;
       IF presult >= 0
       THEN
           UPDATE fac_notificatie
              SET fac_notificatie_status = BITAND (fac_notificatie_status, 255 - 64)
            WHERE fac_notificatie_key = pnotikey;
       END IF;
   END;

   -- Returnt de geldende waarde voor setting pname, bepaald uit de settingstabel
   FUNCTION getSetting (pname IN VARCHAR2 ) RETURN VARCHAR2
   AS
     lres     fac_setting.fac_setting_default%TYPE;
   BEGIN
     SELECT COALESCE(fac_setting_pvalue, fac_setting_default)
       INTO lres
       FROM fac_setting
      WHERE fac_setting_name = pname;

     RETURN lres;
   EXCEPTION
      WHEN NO_DATA_FOUND
      THEN raise_application_error(-20001, 'FACILITOR: Non existing setting: '||pname);
   END;

   -- Add a trackingrecord for action pcode, entity prefkey on behalf of user puserkey
   -- pdatum may be NULL, then SYSDATE is used. A specific trackingtext can be given in poms,
   -- else the standard text of the pcode is apparently sufficent enough.
   -- Returns the fac_tracking_key of the record that has been created
   -- psubjectkey is the optional and context-dependent key of the entitity mentioned in poms
   -- which can be a discipline_key or a perslid_key or maybe others. This is semantically
   -- hardcoded (with pcode MLDBHG the subjectkey is the disciplinekey of the Behandelteam)
   --
   -- Doet niets als er een onbekende pcode wordt meegegeven
   -- Als we willen dat een tracking per se NIET wordt genotificeerd, doen we dat
   -- door de pcode(!) te prefixen met #. Deze wordt dan stiekem voor aan poms geplakt
   -- (want anders kunnen we niet inserten) en de trigger op fac_tracking doet dan de rest.
   -- Voor compatibiliteit zijn er een paar parameter-varianten
   --
   PROCEDURE trackaction (pcode VARCHAR2, prefkey NUMBER, puserkey NUMBER, pdatum DATE, poms VARCHAR2)
   AS
        new_fac_tracking_key fac_tracking.fac_tracking_key%TYPE; -- dummy
   BEGIN
        fac.trackaction_withkey (pcode, prefkey, puserkey, pdatum, poms, NULL, new_fac_tracking_key);
   END;

   PROCEDURE trackaction (pcode VARCHAR2, prefkey NUMBER, puserkey NUMBER, pdatum DATE, poms VARCHAR2, new_fac_tracking_key OUT NUMBER)
   AS
   BEGIN
        fac.trackaction_withkey (pcode, prefkey, puserkey, pdatum, poms, NULL, new_fac_tracking_key);
   END;

   PROCEDURE trackaction_withkey (pcode VARCHAR2, prefkey NUMBER, puserkey NUMBER, pdatum DATE, poms VARCHAR2, psubjectkey NUMBER, new_fac_tracking_key OUT NUMBER)
   AS
      tkey   fac_srtnotificatie.fac_srtnotificatie_key%TYPE;
      loms   fac_tracking.fac_tracking_oms%TYPE;
      lcode  VARCHAR2(10);  -- tabelsize is 6

   BEGIN
      IF pcode IS NULL
      THEN
         RETURN;
      END IF;

      IF prefkey IS NULL
      THEN
         RETURN;
      END IF;

      lcode := pcode;
      loms := poms;

      IF SUBSTR(lcode,1,1) = '#' -- later niemand notificeren
      THEN
        lcode := SUBSTR(lcode, 2);
        IF poms IS NULL
        THEN
            loms := '#';
        ELSE
            loms := '#' || SUBSTR(loms, 1, 2047);
        END IF;
      END IF;

      IF SUBSTR(lcode,1,1) = '@' -- later niet naar technische adressen notificeren
      THEN
        lcode := SUBSTR(lcode, 2);
        IF poms IS NULL
        THEN
            loms := '@';
        ELSE
            loms := '@' || SUBSTR(loms, 1, 2047);
        END IF;
      END IF;

      SELECT   fac_srtnotificatie_key
        INTO   tkey
        FROM   fac_srtnotificatie
       WHERE   fac_srtnotificatie_code = lcode;

      IF pdatum IS NULL
      THEN
         INSERT INTO fac_tracking (fac_tracking_refkey, prs_perslid_key, fac_srtnotificatie_key, fac_tracking_oms, fac_tracking_subject_refkey
                                  )
           VALUES   (prefkey, puserkey, tkey, loms, psubjectkey
                    )
           RETURNING fac_tracking_key INTO new_fac_tracking_key;
           -- LET OP: Dit activeert fac_t_fac_tracking_b_i aan waar fac.notifytracking wordt aangeroepen.
      ELSE
         INSERT INTO fac_tracking (fac_tracking_refkey, prs_perslid_key, fac_srtnotificatie_key, fac_tracking_datum, fac_tracking_oms, fac_tracking_subject_refkey
                                  )
           VALUES   (prefkey, puserkey, tkey, pdatum, loms, psubjectkey
                    )
           RETURNING fac_tracking_key INTO new_fac_tracking_key;
      END IF;
   EXCEPTION
      WHEN NO_DATA_FOUND
      THEN
         NULL;
   END;

   -- Pas de datum van een tracking record aan
   -- Userkey wordt nog genegeerd
   PROCEDURE backtrackaction (pcode       VARCHAR2,
                              prefkey     NUMBER,
                              puserkey    NUMBER,
                              pdatum      DATE)
   AS
      tkey   fac_srtnotificatie.fac_srtnotificatie_key%TYPE;
   BEGIN
      SELECT fac_srtnotificatie_key
        INTO tkey
        FROM fac_srtnotificatie
       WHERE fac_srtnotificatie_code = pcode;

      UPDATE fac_tracking
         SET fac_tracking_datum = pdatum
       WHERE fac_tracking_refkey = prefkey AND fac_srtnotificatie_key = tkey
             AND fac_tracking_key =
                   (SELECT MAX (fac_tracking_key)
                      FROM fac_tracking T
                     WHERE t.fac_srtnotificatie_key = tkey
                           AND fac_tracking_refkey = prefkey);
   END;

   -- create a notification using only trackrecord parameters
   -- Indeed, we could do this using only the fac_tracking_key, but we do not
   -- if no ptracking_oms is given, we construct one!
   -- Binnen deze procedure weten we nog net wat we doen
   -- (nl. notificeren van een trackrecord van een bekende entiteit)
   -- Een stap verder niet meer, dan is het gewoon een bericht met
   -- een zender en ontvanger.

   -- Dit is de 'gewone' notificatie naar mensen (en systeemadressen).
   -- Notificatie naar technische adressen gaat via notifytrackingbedrijven
    PROCEDURE notifytracking (psrtnotificatiekey    NUMBER,
                              pperslid_key          NUMBER,
                              ptracking_oms         VARCHAR2,
                              prefkey               NUMBER,
                              ptrackingkey          NUMBER DEFAULT NULL)
    AS
        lxmlnode                 fac_srtnotificatie.fac_srtnotificatie_xmlnode%TYPE;
        lcode                    fac_srtnotificatie.fac_srtnotificatie_code%TYPE;
        lcode2                   fac_srtnotificatie.fac_srtnotificatie_code%TYPE;
        loms                     fac_srtnotificatie.fac_srtnotificatie_oms%TYPE;
        loms2                    fac_srtnotificatie.fac_srtnotificatie_oms%TYPE;
        lsrtnotigebouw           fac_srtnotificatie.fac_srtnotificatie_gebouw%TYPE;
        bericht                  fac_srtnotificatie.fac_srtnotificatie_oms%TYPE;
        bericht2                 fac_srtnotificatie.fac_srtnotificatie_oms%TYPE;
        lreceiver                fac_notificatie.prs_perslid_key_receiver%TYPE;
        lreceiver2               fac_notificatie.prs_perslid_key_receiver%TYPE;
        lsender                  VARCHAR2 (255 CHAR);
        lmld_stdmelding_key      mld_stdmelding.mld_stdmelding_key%TYPE;
        lnotify                  mld_disc_params.mld_disc_params_notify%TYPE;
        lmnotify                 mld_stdmelding.mld_stdmelding_notify%TYPE;
        lbonotify                mld_disc_params.mld_disc_params_bonotify%TYPE;
        lbenotify                mld_disc_params.mld_disc_params_benotify%TYPE;
        lworkflowstep_key        mld_melding.mld_workflowstep_key%TYPE;
        lstart_key               mld_melding.mld_melding_start_key%TYPE;
        luitvtype                mld_v_uitvoerende.TYPE%TYPE;
        lemail                   fac_notificatie.fac_notificatie_receiver_email%TYPE;
        lemail3                  alg_gebouw.alg_gebouw_email%TYPE;
        lphone                   prs_bedrijf.prs_bedrijf_telefoon2%TYPE;
        lreskey                  res_rsv_ruimte.res_reservering_key%TYPE;
        lres_ruimte_opstel_key   res_rsv_ruimte.res_ruimte_opstel_key%TYPE;
        lrefkey                  NUMBER (10);
        lxrefkey                 NUMBER (10);  -- res_rsv_ruimte_key, fac_tracking_key, mld_opdr_note_key
        lalg_ruimte_key          res_rsv_ruimte.alg_ruimte_key%TYPE;
        lalglocatiekey           alg_locatie.alg_locatie_key%TYPE;
        lalgonrgoedkey           mld_melding.mld_alg_onroerendgoed_keys%TYPE;
        lmld_adres_key           mld_adres.mld_adres_key%TYPE;
        lprs_bedrijf_key         prs_bedrijf.prs_bedrijf_key%TYPE;
        lmldbehandelaar          mld_melding.mld_melding_behandelaar_key%TYPE;
        lmldbehandelaar2         mld_melding.mld_melding_behandelaar2_key%TYPE;
        lafs_key                 bez_afspraak.bez_afspraak_key%TYPE;
        lins_deel_ley            ins_deel.ins_deel_key%TYPE;
        lpoolnotified            NUMBER;
        lspoed                   mld_melding.mld_melding_spoed%TYPE;

    BEGIN
        -- Het gaat om een notificatie bij een entiteit prefkey,
        -- waarvan het type alleen nog moet worden bepaald via
        -- de srtnotificatie. Dan kan van het record van die
        -- entiteit de ontvanger van de notificatie worden bepaald
        -- (normaal de aanvrager) en wordt het bericht gequeueud.
        -- De afzender is nu ook bekend (vroeger niet)
        -- DIT VERVANGT (5I) ALLE NOTIFICATIETRIGGERS bij de verschillende
        -- entiteiten (met uitzondering van opdrachtnotificatie naar buiten!?
        IF psrtnotificatiekey IS NULL
        THEN
            RETURN;
        END IF;

        IF prefkey IS NULL
        THEN
            RETURN;
        END IF;

        lrefkey := prefkey;                            -- dan kunnen we bij reservering nog wijzigen
        bericht := ptracking_oms;                                  -- als die leeg is zoeken we zelf
        lpoolnotified := 0;

        -- welk type?
        SELECT fac_srtnotificatie_xmlnode,
               fac_srtnotificatie_code,
               lcl.x('fac_srtnotificatie_oms', fac_srtnotificatie_key, fac_srtnotificatie_oms),
               fac_srtnotificatie_gebouw
          INTO lxmlnode,
               lcode,
               loms,
               lsrtnotigebouw
          FROM fac_srtnotificatie
         WHERE fac_srtnotificatie_key = psrtnotificatiekey;

        -- als we zeker weten dat het nergens naartoe gestuurd gaat worden
        -- (fac_srtnotificatie_mode = 0) hoeven we ook niet meer te bepalen
        -- welk bericht we niet naar niemand gaan sturen..
        -- Voorkomt ook mutating-errors als het niet hoeft.
        -- Althans, dat dacht ik, maar los van deze notificatie kunnen er ook
        -- afgeleide notificaties gestuurd worden (naar de BO en zo)
        -- dus zo simpel is het niet. Daarom maar specifiek uitsluiten.
        --
        -- Deze leiden intrinsiek niet tot notificaties
        IF (lcode = 'BESLIK' OR lcode = 'MLDLIK' OR lcode ='RESLIK')
        THEN
            RETURN;
        END IF;

        -- Bepaal de ontvanger enzo van dit bericht
        CASE
            WHEN lxmlnode = 'melding'
            THEN
                IF ptracking_oms IS NULL -- OR lcode = 'MLDUPD'?
                THEN
                    bericht := mld.mldsprintf (loms, prefkey);
                END IF;

                -- basisgegevens, de ontvanger enzo
                SELECT CASE WHEN fac.getsetting ('progress_notification_for') = 'true'
                            THEN m.prs_perslid_key_voor
                            ELSE m.prs_perslid_key
                            END  receiver,
                       m.mld_stdmelding_key,
                       m.mld_adres_key,
                       m.mld_alg_locatie_key,
                       m.mld_alg_onroerendgoed_keys,
                       mld_disc_params_notify,
                       stdm.mld_stdmelding_notify,
                       d.ins_discipline_email,
                       mp.mld_disc_params_bonotify,
                       mp.mld_disc_params_benotify,
                       m.mld_melding_behandelaar_key,
                       m.mld_melding_behandelaar2_key,
                       m.mld_workflowstep_key,
                       m.mld_melding_start_key,
                       m.mld_melding_spoed
                  INTO lreceiver,
                       lmld_stdmelding_key,
                       lmld_adres_key,
                       lalglocatiekey,
                       lalgonrgoedkey,
                       lnotify,
                       lmnotify,
                       lsender,
                       lbonotify,
                       lbenotify,
                       lmldbehandelaar,
                       lmldbehandelaar2,
                       lworkflowstep_key,
                       lstart_key,
                       lspoed
                  FROM mld_melding m,
                       mld_stdmelding stdm,
                       ins_tab_discipline d,
                       mld_disc_params mp
                 WHERE     stdm.mld_stdmelding_key = m.mld_stdmelding_key
                       AND d.ins_discipline_key = stdm.mld_ins_discipline_key
                       AND mp.mld_ins_discipline_key = d.ins_discipline_key
                       AND m.mld_melding_key = prefkey;

                IF ((lspoed<=2 OR lbonotify > 0) AND (lcode = 'MLDNEW' OR lcode = 'MLDFWD' OR lcode = 'MLDACP')) -- Bij aanmaak, doorsturen of accepteren van de melding.
                THEN
                    -- merk op: bij doorsturen binnen dezelfde discipline of met dezelfde bo-ers ontvangt men
                    -- dan dus nogmaals een notificatie. Dat accepteren we vooralsnog gewoon maar.
                    mld.notifybackoffice (prefkey, lcode);
                END IF;
                -- Notificatie bij nieuwe melding of doorsturen melding per prioriteit.
                IF (lcode = 'MLDNEW' OR lcode = 'MLDFWD')
                THEN
                    mld.notifyprio (prefkey, lspoed, pperslid_key);
                END IF;
                IF lcode = 'MLDA2B' -- Actie2Backoffice
                THEN
                    lreceiver := lmldbehandelaar;
                END IF;

                -- We sturen soms TWEE notificaties, naar melder en nieuwe behandelaar
                -- De normale notificatie gaat naar de melder
                -- Moeten dus kijken of psrtnotificatiekey naar MLDBEH wijst
                -- Dan moeten we ook MLDBE2 versturen naar de behandelaar, al was ik het zelf.
                IF lbenotify > 0
                THEN
                   IF lcode = 'MLDBEH' AND lmldbehandelaar IS NOT NULL
                   THEN
                       mld.notifybehandelaar (lmldbehandelaar, 'MLDBE2', prefkey);
                   ELSIF lcode = 'MLDABH' AND lmldbehandelaar2 IS NOT NULL
                   THEN
                       mld.notifybehandelaar (lmldbehandelaar2, 'MLDAB2', prefkey);
                   END IF;
                END IF;
                -- Meldingen worden eventueel niet genotificeerd naar de melder als
                -- settings daartoe aanleiding geven. We weten genoeg om te checken of dat zo is
                -- Maar een MLDNOT-notificatie moet altijd, ongeacht deze settings
                IF lcode <> 'MLDNOT'
                   AND (lnotify = 0
                       OR (lnotify = 2 AND lmnotify = 0)
                       OR (    lworkflowstep_key IS NOT NULL
                           AND lstart_key <> prefkey
                           AND fac.getsetting ('mld_notify_workflowsteps') = 0)
                       )
                THEN
                    -- No first notification: disc says no OR disc says: see stdmelding and stdmelding says no
                    -- OR this is part of a workflow and settings says not to notify workflowsteps
                    IF lcode2 IS NULL
                    THEN
                        -- Also no second notification
                        RETURN;
                    ELSE
                        -- Only a second notification, so that will be the first
                        lcode := lcode2;
                        lreceiver := lreceiver2;
                        bericht := bericht2;
                        lcode2 := NULL;
                        lreceiver2 := NULL;
                        bericht2 := NULL;
                    END IF;
                END IF;

                IF lsender IS NULL
                THEN
                    -- (kennelijk) is de verantwoordelijke voor van het afleveradres iets sterker of zo
                    IF lmld_adres_key IS NOT NULL
                    THEN
                        SELECT l1.alg_locatie_email
                          INTO lsender
                          FROM mld_adres ma, alg_locatie l1
                         WHERE ma.mld_adres_key = lmld_adres_key
                               AND ma.alg_locatie_key = l1.alg_locatie_key(+);
                    END IF;

                    IF lsender IS NULL AND lalglocatiekey IS NOT NULL         -- nog steeds onbekend
                    THEN
                        SELECT l2.alg_locatie_email
                          INTO lsender
                          FROM alg_locatie l2
                         WHERE l2.alg_locatie_key = lalglocatiekey;
                    END IF;
                END IF;

                -- Controleren of er ook naar het gebouw een mailtje gestuurd moet worden.
                -- LET OP: Voor een notitie wordt geen tracking gedaan en wordt in de code gekeken of er een mailtje naar het gebouw moet worden gestuurd.
                --         Onderstaande select code en code voor het ophalen van lsrtnotigebouw en lalgonrgoedkey (hiervoor al opgehaald) komt dus ook in mld_edit_note.inc voor.
                IF lsrtnotigebouw = 1 AND lalgonrgoedkey IS NOT NULL
                THEN
                    -- Stuur een notificatie naar het e-mail adres van het gebouw.
                    SELECT g.alg_gebouw_email
                      INTO lemail3
                      FROM alg_v_allonroerendgoed aog,
                           alg_gebouw g
                     WHERE aog.alg_gebouw_key = g.alg_gebouw_key
                       AND aog.alg_onroerendgoed_keys = lalgonrgoedkey;
                END IF;

                -- voor meldingen (en opdrachten) hebben we een toepassing voor de extrakey in de queue, nl. de verwijzing
                -- naar de trackingtabel, dat is specifieker dan we terug kunnen bepalen. De notitie daarbij kan nl. van belang zijn
                lxrefkey := ptrackingkey;

            WHEN lxmlnode = 'opdracht'
            THEN
                IF ptracking_oms IS NULL
                THEN
                    bericht := mld.opdrsprintf (loms, prefkey);
                END IF;
                -- voor opdrachten (en meldingen) hebben we een toepassing voor de extrakey in de queue, nl. de verwijzing
                -- naar de trackingtabel, dat is specifieker dan we terug kunnen bepalen. De notitie daarbij kan nl. van belang zijn
                lxrefkey := ptrackingkey;

                -- In 4i notificeerden we alleen toekenning aan de uitvoerder
                -- In 5i tracken we veel meer, en is er dus ook de mogelijkheid
                -- tot notificeren van tracking
                -- Echter: wie houden we hiervan nou actief op de hoogte?
                -- Dat wordt de interne contacpersoon van de opdracht
                -- behalve:
                -- 1) ORDA2U, ORDNOL, ORDMLE/ORDMLI, ORDCAE/ORDCAI en ORDONO/ORDOOK, die naar de uitvoerder
                -- 2) ORDDAT, die naar de gebouwverantwoordelijke van het betreffende gebouw van de onderliggende melding
                IF lcode IN ('ORDA2U', 'ORDNOL', 'ORDMLI', 'ORDMLE', 'ORDONO', 'ORDOOK', 'ORDCAE', 'ORDCAI')
                THEN
                    SELECT mld_uitvoerende_keys
                      INTO lreceiver
                      FROM mld_opdr
                     WHERE mld_opdr_key = prefkey;

                    SELECT u.TYPE
                      INTO luitvtype
                      FROM mld_v_uitvoerende u
                     WHERE u.mld_uitvoerende_key = lreceiver;

                    SELECT fac_tracking_subject_refkey
                      INTO lxrefkey
                      FROM fac_tracking
                     WHERE fac_tracking_key = ptrackingkey;

                    IF luitvtype = 'B'
                    THEN
                        -- Het gaat om een bedrijf receiver
                        BEGIN
                            SELECT prs_bedrijf_email, prs_bedrijf_telefoon2
                              INTO lemail, lphone
                              FROM prs_bedrijf
                             WHERE prs_bedrijf_key = lreceiver;

                            -- Nooit op een portal voor externen
                            -- want dan zou iedereen die notificatie zien ivm lege receiver
                            -- dit overrulet dus iedere setting!
                            -- TODO smode :=    BITAND (smode, 255 - 1);
                            -- voorkom dat er een bericht wordt gestuurd
                            -- aan een perslid_key als deze bedrijf_key
                            lreceiver := NULL;
                        EXCEPTION
                            WHEN NO_DATA_FOUND
                            THEN
                                RETURN;
                        END;
                    END IF;
                ELSIF lcode = 'ORDDAT'
                THEN
                    BEGIN
                        SELECT COALESCE( ( SELECT COALESCE(g.prs_perslid_key_verantw, g.prs_perslid_key_verantw2)
                                             FROM alg_gebouw g
                                            WHERE g.alg_gebouw_key = org.alg_gebouw_key
                                         )
                                       , ( SELECT COALESCE(t.prs_perslid_key_verantw, t.prs_perslid_key_verantw2)
                                             FROM alg_terreinsector t
                                            WHERE t.alg_terreinsector_key = org.alg_terreinsector_key
                                         )
                                       ) verantwoordelijke
                          INTO lreceiver
                          FROM mld_opdr o,
                               mld_melding m,
                               alg_v_onroerendgoed org
                         WHERE o.mld_melding_key = m.mld_melding_key
                           AND m.mld_alg_onroerendgoed_keys = org.alg_onroerendgoed_keys
                           AND o.mld_opdr_key = prefkey;
                    EXCEPTION
                        WHEN NO_DATA_FOUND
                        THEN
                            RETURN;
                    END;
                ELSE
                    -- ons intern contactpersoon krijgt in alle andere gevallen het bericht
                    SELECT prs_perslid_key
                      INTO lreceiver
                      FROM mld_opdr
                     WHERE mld_opdr_key = prefkey;
                END IF;

                -- NOTE TO SELF:
                -- Bij ORDMLI/ORDMLE en eigenlijk ook ORDGOE worden derden ingelicht,
                -- bij ORDGOE tevens de opdrachtcontactpersoon, bij die andere twee niet
                -- De mode (mail e.d.) van de notificatie bij dat event kan maar 1 waarde bevatten
                -- Momenteel is dat
                -- ORDMLI/ORDMLE: hoe ontvangt de uitvoerder dit bericht, contactpersoon krijgt niks
                -- ORDGOE: hoe ontvangt de contactpersoon
                -- ORD2GO: hoe ontvangt de uitvoerder dit bericht
                -- Bij ORDGOE willen we *ook* de goedkeurder inlichten
                IF lcode = 'ORDGOE'
                THEN
                    mld.notifyopdrgoedkeurders (prefkey);
                END IF;

                -- de afzender
                BEGIN
                    SELECT ins_discipline_email,
                           m.mld_alg_onroerendgoed_keys
                      INTO lsender,
                           lalgonrgoedkey
                      FROM ins_tab_discipline d,
                           mld_stdmelding sm,
                           mld_melding m,
                           mld_opdr o
                     WHERE     d.ins_discipline_key = sm.mld_ins_discipline_key
                           AND sm.mld_stdmelding_key = m.mld_stdmelding_key
                           AND o.mld_melding_key = m.mld_melding_key
                           AND o.mld_opdr_key = prefkey;

                    IF lsender IS NULL
                    THEN
                        SELECT COALESCE (l1.alg_locatie_email, l2.alg_locatie_email),
                               m.mld_alg_onroerendgoed_keys
                          INTO lsender,
                               lalgonrgoedkey
                          FROM mld_melding m,
                               mld_adres ma,
                               alg_locatie l1,
                               alg_locatie l2,
                               mld_opdr o
                         WHERE     m.mld_adres_key = ma.mld_adres_key(+)
                               AND ma.alg_locatie_key = l1.alg_locatie_key(+)
                               AND m.mld_alg_locatie_key = l2.alg_locatie_key(+)
                               AND o.mld_melding_key = m.mld_melding_key
                               AND o.mld_opdr_key = prefkey;
                    END IF;
                EXCEPTION
                    WHEN NO_DATA_FOUND
                    THEN
                        NULL;
                END;

                -- Controleren of er ook naar het gebouw een mailtje gestuurd moet worden.
                IF lsrtnotigebouw = 1 AND lalgonrgoedkey IS NOT NULL
                THEN
                    -- Stuur een notificatie naar het e-mail adres van het gebouw.
                    SELECT g.alg_gebouw_email
                      INTO lemail3
                      FROM alg_v_allonroerendgoed aog,
                           alg_gebouw g
                     WHERE aog.alg_gebouw_key = g.alg_gebouw_key
                       AND aog.alg_onroerendgoed_keys = lalgonrgoedkey;
                END IF;

            WHEN lxmlnode = 'reservering' OR lxmlnode = 'xreservering'
            THEN
                IF ptracking_oms IS NULL OR lcode = 'RESUPD'
                THEN
                    bericht := res.sprintf (loms, prefkey);
                END IF;

                BEGIN
                    SELECT res_rsv_ruimte_contact_key,
                           res_ruimte_opstel_key,
                           alg_ruimte_key,
                           res_reservering_key
                      INTO lreceiver,
                           lres_ruimte_opstel_key,
                           lalg_ruimte_key,
                           lreskey
                      FROM res_rsv_ruimte
                     WHERE res_rsv_ruimte_key = prefkey
                       AND (res_rsv_ruimte_dirtlevel = 0 OR lcode = 'RESDIR');
                EXCEPTION
                    WHEN NO_DATA_FOUND
                    THEN
                        RETURN;         -- Normaliter geen niet bestaande of dirty reserveringen notificeren
                END;

                -- de afzender, en moeten we wel?
                lnotify := NULL;
                lbonotify := 0;
                BEGIN
                    -- Bepaal afzender, afhankelijk van catalogus (als res_ruimte) of anders de locatie
                    -- en check of er voor deze ruimtecatalogus uberhaupt moet worden genotificeerd
                    IF lres_ruimte_opstel_key IS NOT NULL
                    THEN
                        SELECT COALESCE (MAX (d.ins_discipline_email), MAX (l.alg_locatie_email)),
                               MAX (res_disc_params_notify),
                               MAX (res_disc_params_bonotify)
                          INTO lsender,
                               lnotify,
                               lbonotify
                          FROM res_ruimte_opstelling ro,
                               res_ruimte rr,
                               res_alg_ruimte rag,
                               alg_ruimte r,
                               alg_verdieping v,
                               alg_gebouw g,
                               alg_locatie l,
                               ins_tab_discipline d,
                               res_disc_params rdp
                         WHERE     l.alg_locatie_key = g.alg_locatie_key
                               AND g.alg_gebouw_key = v.alg_gebouw_key
                               AND r.alg_verdieping_key = v.alg_verdieping_key
                               AND r.alg_ruimte_key = rag.alg_ruimte_key
                               AND rag.res_ruimte_key = rr.res_ruimte_key
                               AND d.ins_discipline_key = rr.res_discipline_key
                               AND rr.res_discipline_key = rdp.res_ins_discipline_key
                               AND rr.res_ruimte_key = ro.res_ruimte_key
                               AND ro.res_ruimte_opstel_key = lres_ruimte_opstel_key;
                    ELSIF lalg_ruimte_key IS NOT NULL
                    THEN
                        -- Een voorzieningenreservering kan uit meerdere catalogi items hebben,
                        -- maar bij maar 1 afzender. Zwaarst geldt een cateringcatalogusafzender
                        SELECT MAX (d.ins_discipline_email),
                               MAX (res_disc_params_notify),
                               MAX (res_disc_params_bonotify)
                          INTO lsender,
                               lnotify,
                               lbonotify
                          FROM ins_tab_discipline d,
                               res_artikel a,
                               res_rsv_artikel ra,
                               res_disc_params rdp
                         WHERE     d.ins_discipline_key = a.res_discipline_key
                               AND a.res_discipline_key = rdp.res_ins_discipline_key
                               AND a.res_artikel_key = ra.res_artikel_key
                               AND ra.res_rsv_artikel_verwijder IS NULL
                               AND ra.res_rsv_ruimte_key = prefkey;

                        -- Als die er niet is, dan de voorzieningscatalogusafzender
                        IF lsender IS NULL
                        THEN
                            SELECT MAX (d.ins_discipline_email),
                                   MAX (res_disc_params_notify),
                                   MAX (res_disc_params_bonotify)
                              INTO lsender,
                                   lnotify,
                                   lbonotify
                              FROM ins_tab_discipline d,
                                   res_deel r,
                                   res_rsv_deel rd,
                                   res_disc_params rdp
                             WHERE     d.ins_discipline_key = r.res_discipline_key
                                   AND r.res_discipline_key = rdp.res_ins_discipline_key
                                   AND r.res_deel_key = rd.res_deel_key
                                   AND rd.res_rsv_deel_verwijder IS NULL
                                   AND rd.res_rsv_ruimte_key = prefkey;
                        END IF;

                        -- Als die er niet is, dan de locatieafzender
                        IF lsender IS NULL
                        THEN
                            SELECT MAX (l.alg_locatie_email)
                              INTO lsender
                              FROM alg_ruimte r,
                                   alg_verdieping v,
                                   alg_gebouw g,
                                   alg_locatie l
                             WHERE     l.alg_locatie_key = g.alg_locatie_key
                                   AND g.alg_gebouw_key = v.alg_gebouw_key
                                   AND r.alg_verdieping_key = v.alg_verdieping_key
                                   AND r.alg_ruimte_key = lalg_ruimte_key;
                        END IF;
                    ELSE
                        lsender := NULL;
                    END IF;
                EXCEPTION
                    WHEN NO_DATA_FOUND
                    THEN
                        lsender := NULL;
                END;

                IF (lbonotify > 0) AND (lcode = 'RESNEW') -- Bij aanmaken van de reservering, of bij wijzigen van een bestaande reservering door toevoegen van voorzieningen.
                THEN
                    res.notifybackoffice (prefkey, lcode);
                END IF;

                -- Reserveringen worden eventueel niet genotificeerd als settings daartoe aanleiding geven.
                -- We weten genoeg om te checken of dat zo is
                IF (lnotify = 0)
                THEN
                    -- Nothing to do: disc says no
                    RETURN;
                END IF;

                lxrefkey := lrefkey;                                           -- de deelreservering
                lrefkey := lreskey;                                           -- de hoofdreservering
            WHEN lxmlnode = 'bestelling'
            THEN
                IF ptracking_oms IS NULL
                THEN
                    bericht := bes.bessprintf (loms, prefkey, '');
                END IF;
                --
                SELECT CASE WHEN fac.getsetting ('progress_notification_for') = 'true'
                            THEN prs_perslid_key_voor
                            ELSE prs_perslid_key
                            END  receiver
                     , mld_adres_key_lev
                  INTO lreceiver
                     , lmld_adres_key
                  FROM bes_bestelling bb
                 WHERE bb.bes_bestelling_key = prefkey;
                --
                -- Bij BESACP willen we *ook* de goedkeurders inlichten
                IF lcode = 'BESACP'
                THEN
                    bes.notifybesgoedkeurders (prefkey);
                END IF;
                --
                -- Bepaal de afzender
                -- die komt van de (strikt: een) catalogus van de artikelen uit deze bestelling
                -- of anders bepaald door de locatie van het afleveradres
                BEGIN
                    -- Merk op dat tijdens insert nog niet bekend is welke items nog gaan worden toegevoegd
                    -- en dan is het dus niet mogelijk hier de catalogus te bepalen en te gebruiken
                    -- Tijdens update zou dat wel kunnen, maar de update wordt soms veroorzaakt door
                    -- een andere update, in het bijzonder makeorders(). Dat levert dan weer een
                    -- table is mutating error op. Dat betekent dus dat de notificatie eigenlijk dieper
                    -- moet worden gegenereerd, op bestelling-item. Dit valt nu buiten de scope van RWSN#14861
                    -- Met 5i kunnen we dit hier nu wel doen toch? Nee, ook niet!
                    -- SELECT   MAX (ins_discipline_email)
                    --  INTO   lsender
                    --  FROM   ins_tab_discipline d, bes_bestelling_item bi, ins_srtdeel sd, ins_srtgroep sg
                    -- WHERE       bi.ins_srtdeel_key = sd.ins_srtdeel_key
                    --         AND sd.ins_srtgroep_key = sg.ins_srtgroep_key
                    --         AND sg.ins_discipline_key = d.ins_discipline_key
                    --         AND bi.bes_bestelling_key = prefkey;
                    IF lsender IS NULL
                    THEN
                        SELECT l.alg_locatie_email
                          INTO lsender
                          FROM mld_adres ma, alg_locatie l
                         WHERE lmld_adres_key = ma.mld_adres_key(+)
                           AND ma.alg_locatie_key = l.alg_locatie_key(+);
                    END IF;
                EXCEPTION
                    WHEN NO_DATA_FOUND
                    THEN
                        lsender := NULL;
                END;
            WHEN lxmlnode = 'bestelopdr'
            THEN
                IF ptracking_oms IS NULL
                THEN
                    bericht := bes.opdrsprintf (loms, prefkey);
                END IF;

                -- Wie ontvangt deze? (besteller/leverancier)
                lreceiver := NULL;
                lnotify := NULL;                                                   -- de leverancier

                IF lcode IN ('BES2SN', 'BES2SX')
                THEN
                    -- Ontvanger = misschien de besteller (afh. van catalogus)
                    -- Mochten er (technisch) meerdere zijn, pak er maar eentje
                    BEGIN
                        SELECT b.prs_perslid_key,
                               b.prs_perslid_key_voor,
                               b.mld_adres_key_lev,
                               p.prs_perslid_email,
                               p.prs_perslid_telefoonnr,
                               bdp.bes_disc_params_noti_opdr
                          INTO lreceiver,
                               lreceiver2,
                               lmld_adres_key,
                               lemail,
                               lphone,
                               lnotify
                          FROM prs_perslid p,
                               bes_bestelling b,
                               bes_bestelling_item bi,
                               bes_bestelopdr_item boi,
                               bes_srtdeel sd,
                               bes_srtgroep sg,
                               bes_disc_params bdp
                         WHERE     p.prs_perslid_key = b.prs_perslid_key
                               AND bi.bes_bestelling_key = b.bes_bestelling_key
                               AND sd.bes_srtdeel_key = bi.bes_srtdeel_key
                               AND sg.bes_srtgroep_key = sd.bes_srtgroep_key
                               AND bdp.bes_ins_discipline_key = sg.ins_discipline_key
                               AND boi.bes_bestelopdr_item_key = bi.bes_bestelopdr_item_key
                               AND boi.bes_bestelopdr_key = prefkey
                               AND ROWNUM = 1;
                    EXCEPTION
                        WHEN NO_DATA_FOUND
                        THEN
                            RETURN;
                    END;

                    -- Geen dubbele sturen; ik zeg zelfs: de bestel_voor moet m.i. helemaal niet ingelicht worden?!
                    --IF (lreceiver2 = lreceiver)
                    --THEN
                    lreceiver2 := NULL;
                --END IF;
                END IF;

                IF (lnotify != 1 OR lnotify IS NULL)
                THEN
                    -- Ontvanger = leverancier, toch niet de besteller
                    lreceiver := NULL;
                    lreceiver2 := NULL;

                    SELECT prs_bedrijf_key
                      INTO lprs_bedrijf_key
                      FROM bes_bestelopdr
                     WHERE bes_bestelopdr_key = prefkey;

                    BEGIN
                        SELECT prs_bedrijf_email, prs_bedrijf_telefoon2
                          INTO lemail, lphone
                          FROM prs_bedrijf
                         WHERE prs_bedrijf_key = lprs_bedrijf_key;
                    EXCEPTION
                        WHEN NO_DATA_FOUND
                        THEN
                            RETURN;
                    END;
                END IF;

                -- Afzender
                BEGIN
                    SELECT MAX (ins_discipline_email)
                      INTO lsender
                      FROM ins_tab_discipline d,
                           bes_bestelling_item bi,
                           bes_srtdeel sd,
                           bes_srtgroep sg,
                           bes_bestelopdr_item boi
                     WHERE     bi.bes_srtdeel_key = sd.bes_srtdeel_key
                           AND sd.bes_srtgroep_key = sg.bes_srtgroep_key
                           AND sg.ins_discipline_key = d.ins_discipline_key
                           AND boi.bes_bestelopdr_item_key = bi.bes_bestelopdr_item_key
                           AND boi.bes_bestelopdr_key = prefkey;

                    IF lsender IS NULL
                    THEN
                        SELECT l.alg_locatie_email
                          INTO lsender
                          FROM mld_adres ma, alg_locatie l
                         WHERE lmld_adres_key = ma.mld_adres_key(+)
                               AND ma.alg_locatie_key = l.alg_locatie_key(+);
                    END IF;
                EXCEPTION
                    WHEN NO_DATA_FOUND
                    THEN
                        NULL;
                END;
            WHEN lxmlnode = 'afspraak' OR lxmlnode = 'bezoeker'
            THEN
                IF lxmlnode = 'afspraak'
                THEN
                    IF ptracking_oms IS NULL
                    THEN
                        bericht := bez.afssprintf (loms, prefkey);
                    END IF;

                    lafs_key := prefkey;
                ELSE                                                        -- lxmlnode = 'bezoeker'
                    IF ptracking_oms IS NULL
                    THEN
                        bericht := bez.bzksprintf (loms, prefkey);
                    END IF;

                    SELECT bez_afspraak_key
                      INTO lafs_key
                      FROM bez_bezoekers
                     WHERE bez_bezoekers_key = prefkey;
                END IF;

                -- We sturen bij bezoekers soms TWEE notificaties, naar contact en host
                -- De normale notificatie gaat naar de contact (UWVA#19008)
                -- Moeten dus kijken of psrtnotificatiekey naar BEZDON of BEZOUT wijst
                -- Dan moeten we ook BEZDO2 of BEZOU2 versturen naar de host.
                IF lcode = 'BEZDON'
                THEN
                    lcode2 := 'BEZDO2';
                    lpoolnotified := res.notifypool(prefkey, lcode2);
                ELSIF lcode = 'BEZUNI'
                THEN
                    lcode2 := 'BEZUN2';
                ELSIF lcode = 'BEZOUT'
                THEN
                    lcode2 := 'BEZOU2';
                END IF;

                IF lcode2 IS NOT NULL
                THEN
                    SELECT lcl.x ('fac_srtnotificatie_oms',
                                  fac_srtnotificatie_key,
                                  fac_srtnotificatie_oms)
                      INTO loms2
                      FROM fac_srtnotificatie
                     WHERE fac_srtnotificatie_code = lcode2;

                    bericht2 := bez.bzksprintf (loms2, prefkey);
                END IF;

                SELECT bez_afspraak_contact_key, bez_afspraak_host_key, alg_locatie_key
                  INTO lreceiver, lreceiver2, lalglocatiekey
                  FROM bez_afspraak
                 WHERE bez_afspraak_key = lafs_key;

                -- afzender
                BEGIN
                    SELECT l.alg_locatie_email
                      INTO lsender
                      FROM alg_locatie l
                     WHERE l.alg_locatie_key = lalglocatiekey;
                EXCEPTION
                    WHEN NO_DATA_FOUND
                    THEN
                        lsender := NULL;
                END;
            WHEN lxmlnode = 'deel'
            THEN
                IF ptracking_oms IS NULL
                THEN
                    bericht := ins.sprintf (loms, prefkey);
                END IF;

                IF lcode IN ('INSOUT', 'INSINN', 'INSIN2') OR lcode IN ('INSNEW', 'INSUPD', 'INSDEL')
                THEN
                    -- Beheeracties notificeren we naar de (nieuwe) eigenaar, if any. Je zou ook de objectbeheerder
                    -- kunnen bedenken, maar die beheert het juist, is de aanname, dus al op de hoogte.
                    -- Bij uitlenen sturen we een notificatie naar de tijdelijke nieuwe eigenaar (UWVA#21528)
                    -- Bij innemen sturen we een notificatie naar de oorspronkelijke eigenaar (indien persoon; zeldzaam)
                    -- Die bepalen we dus altijd met ins_alg_ruimte_key (niet ins_alg_ruimte_key_org)
                    SELECT ins_alg_ruimte_key, ins_alg_locatie_key
                      INTO lreceiver, lalglocatiekey
                      FROM ins_deel
                     WHERE ins_alg_ruimte_type = 'P' AND ins_deel_key = prefkey;

                    -- Afzender
                    BEGIN
                        SELECT MAX (ins_discipline_email)
                          INTO lsender
                          FROM ins_tab_discipline d,
                               ins_deel dl,
                               ins_srtdeel sd,
                               ins_srtgroep sg
                         WHERE     dl.ins_srtdeel_key = sd.ins_srtdeel_key
                               AND sd.ins_srtgroep_key = sg.ins_srtgroep_key
                               AND sg.ins_discipline_key = d.ins_discipline_key
                               AND dl.ins_deel_key = prefkey;

                        IF lsender IS NULL
                        THEN
                            SELECT l.alg_locatie_email
                              INTO lsender
                              FROM alg_locatie l
                             WHERE l.alg_locatie_key = lalglocatiekey;
                        END IF;
                    EXCEPTION
                        WHEN NO_DATA_FOUND
                        THEN
                            NULL;
                    END;
                END IF;
            WHEN lxmlnode = 'controle'
            THEN
                IF ptracking_oms IS NULL
                THEN
                    bericht := ins.contrsprintf (loms, prefkey);
                END IF;

                -- Bij Controle, Vervanging of Certificering sturen we een notificatie naar diegene die de Controle, Vervanging of Certificering heeft uitgevoerd.
                SELECT prs_perslid_key, ins_deel_key
                  INTO lreceiver, lins_deel_ley
                  FROM ins_deelsrtcontrole
                 WHERE ins_deelsrtcontrole_key = prefkey;

                -- Afzender via ins_deel.
                BEGIN
                    SELECT MAX (ins_discipline_email)
                      INTO lsender
                      FROM ins_tab_discipline d,
                           ins_deel dl,
                           ins_srtdeel sd,
                           ins_srtgroep sg
                     WHERE     dl.ins_srtdeel_key = sd.ins_srtdeel_key
                           AND sd.ins_srtgroep_key = sg.ins_srtgroep_key
                           AND sg.ins_discipline_key = d.ins_discipline_key
                           AND dl.ins_deel_key = lins_deel_ley;

                    IF lsender IS NULL
                    THEN
                        SELECT l.alg_locatie_email
                          INTO lsender
                          FROM alg_locatie l
                         WHERE l.alg_locatie_key = lalglocatiekey;
                    END IF;
                EXCEPTION
                    WHEN NO_DATA_FOUND
                    THEN
                        NULL;
                END;
            WHEN lxmlnode = 'contract'
            THEN
                IF ptracking_oms IS NULL
                THEN
                    bericht := cnt.sprintf (loms, prefkey);
                END IF;

                -- Ontvanger = contractbeheerder
                SELECT prs_perslid_key_beh
                  INTO lreceiver
                  FROM cnt_contract
                 WHERE cnt_contract_key = prefkey;

                -- Bepaal de afzender
                -- Afzender is het email adres van de contractsoort (discipline)
                -- Of wordt anders bepaald door de locatiescope van het contract
                BEGIN
                    SELECT MAX (cd.ins_discipline_email)
                      INTO lsender
                      FROM cnt_contract c, cnt_discipline cd
                     WHERE c.ins_discipline_key = cd.ins_discipline_key
                       AND c.cnt_contract_key = prefkey;
                    IF lsender IS NULL
                    THEN
                        SELECT MAX(email)
                          INTO lsender
                          FROM (SELECT l.alg_locatie_email email
                                  FROM cnt_contract_plaats,
                                       alg_locatie l
                                 WHERE cnt_alg_plaats_key = l.alg_locatie_key
                                   AND cnt_alg_plaats_code = 'L'
                                   AND cnt_contract_plaats_verwijder IS NULL
                                   AND cnt_contract_key = prefkey
                                UNION
                                SELECT l.alg_locatie_email email
                                  FROM cnt_contract_plaats,
                                       alg_locatie l,
                                       alg_gebouw g
                                 WHERE cnt_alg_plaats_code = 'G'
                                   AND cnt_alg_plaats_key = g.alg_gebouw_key
                                   AND g.alg_locatie_key = l.alg_locatie_key
                                   AND cnt_contract_plaats_verwijder IS NULL
                                   AND cnt_contract_key = prefkey
                                UNION
                                SELECT l.alg_locatie_email email
                                  FROM cnt_contract_plaats,
                                       alg_locatie l,
                                       alg_terreinsector t
                                 WHERE cnt_alg_plaats_code = 'T'
                                   AND cnt_alg_plaats_key = t.alg_terreinsector_key
                                   AND t.alg_locatie_key = l.alg_locatie_key
                                   AND cnt_contract_plaats_verwijder IS NULL
                                   AND cnt_contract_key = prefkey);
                    END IF;
                EXCEPTION
                    WHEN NO_DATA_FOUND
                    THEN
                        lsender := NULL;
                END;
            WHEN lxmlnode = 'perslid'
            THEN
                IF ptracking_oms IS NULL
                THEN
                    bericht := '??';
                END IF;

                lreceiver := pperslid_key; -- email en mobiel nummer worden in putnotificationsrtprio wel opgehaald.

            WHEN lxmlnode = 'message'
            THEN
                NULL;     -- kan nog niet via tracking worden veroorzaakt (later: ontvangstbericht?)
            ELSE
                NULL;                                                                -- force error?
        END CASE;

        fac.putnotificationsrtprio (pperslid_key,
                                    lreceiver,
                                    lcode,
                                    lrefkey,
                                    bericht,
                                    NULL,
                                    lemail,
                                    lphone,
                                    lxrefkey,
                                    2,
                                    lsender);

        IF lcode2 IS NOT NULL AND lreceiver2 IS NOT NULL
        THEN
            IF lpoolnotified = 0 THEN
                fac.putnotificationsrtprio (pperslid_key,
                                            lreceiver2,
                                            lcode2,
                                            lrefkey,
                                            bericht2,
                                            NULL,
                                            lemail,
                                            lphone,
                                            lxrefkey,
                                            2,
                                            lsender);
            END IF;
        END IF;

        IF lemail3 IS NOT NULL
        THEN
            fac.putnotificationsrtprio (pperslid_key,
                                        NULL,
                                        lcode,
                                        lrefkey,
                                        bericht,
                                        NULL,
                                        lemail3,
                                        lphone,
                                        lxrefkey,
                                        2,
                                        lsender);
        END IF;
    EXCEPTION
        WHEN NO_DATA_FOUND
        THEN
            NULL;
    END;

   -- Zoek de nieuwste 'peventcode' tracking van prefkey en notificeer daarvan de bedrijven
   PROCEDURE notifytrackingbedrijven(peventcode IN VARCHAR2, prefkey IN NUMBER, pnotidelay NUMBER DEFAULT NULL)
   AS
       l_trackingkey  fac_tracking.fac_tracking_key%TYPE;
   BEGIN
      SELECT MAX(t.fac_tracking_key)
        INTO l_trackingkey
        FROM fac_tracking t,
             fac_srtnotificatie fsn
       WHERE t.fac_srtnotificatie_key = fsn.fac_srtnotificatie_key
         AND t.fac_tracking_refkey = prefkey
         AND fsn.fac_srtnotificatie_code = peventcode;
      IF l_trackingkey IS NOT NULL
      THEN
          fac.notifytrackingbedrijven (l_trackingkey, pnotidelay);
      END IF;
   END;

   -- Notificeren via een technisch adres
   PROCEDURE notifytrackingbedrijven (ptrackingkey NUMBER, pnotidelay NUMBER DEFAULT NULL)
   AS
        lxmlnode  fac_srtnotificatie.fac_srtnotificatie_xmlnode%TYPE;
        lcode     fac_srtnotificatie.fac_srtnotificatie_code%TYPE;
        l_refkey  fac_tracking.fac_tracking_refkey%TYPE;
   BEGIN
        SELECT fac_srtnotificatie_xmlnode,
               fac_srtnotificatie_code,
               fac_tracking_refkey
          INTO lxmlnode,
               lcode,
               l_refkey
          FROM fac_tracking ft, fac_srtnotificatie fsn
         WHERE ft.fac_srtnotificatie_key = fsn.fac_srtnotificatie_key
           AND ft.fac_tracking_key = ptrackingkey;
        -- we maken een uitstapje naar een module specifieke mld.notifymeldingbedrijven
        -- die bijvoorbeeld locatie_key, opdrachttype en afzender kan bepalen
        -- Daarmee worden we vervolgens weer doorgeleid naar PROCEDURE notifybedrijf (iets verderop)
        CASE
            WHEN lxmlnode = 'melding'
            THEN
                mld.notifymeldingbedrijven(l_refkey, ptrackingkey, pnotidelay);
            WHEN lxmlnode = 'opdracht'
            THEN
                mld.notifyopdrbedrijven(l_refkey, ptrackingkey, pnotidelay);
            WHEN lxmlnode = 'reservering' OR lxmlnode = 'xreservering'
            THEN
                res.notifyreserveringbedrijven(l_refkey, ptrackingkey, pnotidelay);
            WHEN lxmlnode = 'bestelling'
            THEN
                bes.notifybestellingbedrijven(l_refkey, ptrackingkey, pnotidelay);
            WHEN lxmlnode = 'bestelopdr'
            THEN
                bes.notifybestelopdrbedrijven(l_refkey, ptrackingkey, pnotidelay);
            WHEN lxmlnode = 'contract'
            THEN
                cnt.notifycontractbedrijven(l_refkey, ptrackingkey, pnotidelay);
            WHEN lxmlnode = 'afspraak'
            THEN
                bez.notifyafspraakbedrijven(l_refkey, ptrackingkey, pnotidelay);
            WHEN lxmlnode = 'bezoeker'
            THEN
                bez.notifybezoekerbedrijven(l_refkey, ptrackingkey, pnotidelay);
            ELSE
               fac.notifygeneriekbedrijven(l_refkey, ptrackingkey, pnotidelay);

        END CASE;
   END;

   -- Notificeer genrieke bedrijven die luisteren naar onze notificatie
   PROCEDURE notifygeneriekbedrijven (prefkey NUMBER, ptrackingkey NUMBER, pnotidelay NUMBER DEFAULT NULL)
   AS
   BEGIN
      FOR bedrijfrec
         IN (SELECT b.prs_bedrijf_key, 'G' bedrijfadres_type
               FROM prs_bedrijf b, fac_tracking ft
               WHERE     ft.fac_tracking_key = ptrackingkey
                     AND EXISTS
                              (SELECT 1
                                 FROM prs_bedrijfadres ba, prs_bedrijfadres_srtnoti basrt
                              WHERE     b.prs_bedrijf_key = ba.prs_bedrijf_key
                                    AND ba.prs_bedrijfadres_key = basrt.prs_bedrijfadres_key
                                    AND basrt.fac_srtnotificatie_key = ft.fac_srtnotificatie_key
                                    AND ba.prs_bedrijfadres_startdatum <= SYSDATE
                                    AND ba.prs_bedrijfadres_type = 'G'))
      LOOP
         fac.notifybedrijf (pbedrijf_key       => bedrijfrec.prs_bedrijf_key,
                            pbedrijfadres_type => bedrijfrec.bedrijfadres_type,
                            prefkey            => prefkey,
                            ptrackingkey       => ptrackingkey,
                            pnotidelay         => pnotidelay);
      END LOOP;
   END;

   -- Notificeren tracking naar een specifiek bedrijf via een technisch adres
   PROCEDURE notifybedrijf (pbedrijf_key           NUMBER,
                            pbedrijfadres_type     VARCHAR2,
                            prefkey                NUMBER,
                            ptrackingkey           NUMBER DEFAULT NULL,
                            psrtnoti_key           NUMBER DEFAULT NULL, -- als geen trackingkey, bijvoorbeeld vanuit mld_t_mld_opdr_B_IU als teverzenden op 2 wordt gezet
                            pnotidelay             NUMBER DEFAULT NULL,
                            pxrefkey               NUMBER DEFAULT NULL,
                            preference             VARCHAR2 DEFAULT NULL,
                            psender                VARCHAR2 DEFAULT NULL,  -- alleen bij opdrachten alg_locatie_email?
                            plocatie_key           NUMBER DEFAULT NULL,
                            pdiscipline_key        NUMBER DEFAULT NULL,
                            ptypeopdr_key          NUMBER DEFAULT NULL,
                            pbedrijf_key2          NUMBER DEFAULT NULL)
   AS
      l_bdradr_key       prs_bedrijfadres.prs_bedrijfadres_key%TYPE;
      l_basrt_key        prs_bedrijfadres_srtnoti.prs_bedrijfadres_srtnoti_key%TYPE;
      l_srtnoti_code     fac_srtnotificatie.fac_srtnotificatie_code%TYPE;
      l_srtnoti_chkcode  fac_srtnotificatie.fac_srtnotificatie_code%TYPE;
      l_srtnoti_key      fac_srtnotificatie.fac_srtnotificatie_key%TYPE;
      l_srtnoti_xmlnode  fac_srtnotificatie.fac_srtnotificatie_xmlnode%TYPE;
      l_srtnoti_delay    fac_srtnotificatie.fac_srtnotificatie_delay%TYPE;
      l_oms              fac_notificatie.fac_notificatie_oms%TYPE;
      l_perslid_key      fac_tracking.prs_perslid_key%TYPE;
      l_extra_key        fac_tracking.fac_tracking_subject_refkey%TYPE;
      aantal             NUMBER (10);
      dummy              VARCHAR2 (1);
      notbefore          fac_notificatie.fac_notificatie_notbefore%TYPE;
      delaymode          NUMBER;

   BEGIN
      IF ptrackingkey IS NULL
      THEN
         l_oms := NULL;
         l_srtnoti_key := psrtnoti_key;
         l_extra_key := pxrefkey;
      ELSE
         SELECT ft.fac_srtnotificatie_key, fac_tracking_oms, prs_perslid_key, fac_tracking_subject_refkey
           INTO l_srtnoti_key, l_oms, l_perslid_key, l_extra_key
           FROM fac_tracking ft
          WHERE ft.fac_tracking_key = ptrackingkey;

         -- als de tracking.prs_perslid_key een systeem contactpersoon is van het prs_bedrijf
         -- dan geen notificatie terugsturen (ARAI/van Dorp case)
         SELECT COUNT(*)
           INTO aantal
           FROM prs_contactpersoon pcp
          WHERE pcp.prs_bedrijf_key = pbedrijf_key
            AND pcp.prs_perslid_key = l_perslid_key
            AND prs_contactpersoon_systeem = 1;
         IF aantal > 0
         THEN
            DBMS_OUTPUT.put_line ('NOT sending notification initiated by System contact person');
            RETURN;
         END IF;

         -- als de tracking.prs_perslid_key zelf een system user is en van het prs_bedrijf
         -- dan ook geen notificatie terugsturen
         SELECT COUNT(*)
           INTO aantal
           FROM prs_perslid p,
                prs_v_afdeling_boom aa
          WHERE p.prs_perslid_key = l_perslid_key
            AND BITAND(p.prs_perslid_flags, 1) = 1 -- System user
            AND aa.prs_afdeling_key = p.prs_afdeling_key
            AND aa.prs_bedrijf_key = pbedrijf_key;
         IF aantal > 0
         THEN
            DBMS_OUTPUT.put_line ('NOT sending notification initiated by System user');
            RETURN;
         END IF;
      END IF;

      -- Merk op: per bedrijf notificeren we hooguit één enkel technisch adres
      -- Dat adres wordt eerst bepaald op basis van locatie/district/opdrachttype/discipline/bedrijfadres_type
      -- Als er meerdere adressen zijn die aan zo'n combinatie wordt gekeken welke srtnotificaties er aan hangen
      -- Dit is om het begrijpbaar te houden.
      -- Ook: je kunt onder een bedrijf een adres maken voor alle locaties maar specifiek opdrachttype
      --      en eentje voor specifieke locatie en algemene opdracht
      --      Dat ondersteunen we niet en geeft ongedefinieerd gedrag.
      BEGIN
         SELECT ba.prs_bedrijfadres_key
            INTO l_bdradr_key
            FROM prs_bedrijfadres ba
         WHERE     ba.prs_bedrijf_key = pbedrijf_key
               AND ba.prs_bedrijfadres_type = pbedrijfadres_type
               AND prs_bedrijfadres_startdatum <= SYSDATE
               AND (ba.mld_typeopdr_key = ptypeopdr_key OR ba.mld_typeopdr_key IS NULL)
               AND (ba.ins_discipline_key = pdiscipline_key OR ba.ins_discipline_key IS NULL)
               AND (ba.alg_locatie_key = plocatie_key OR ba.alg_locatie_key IS NULL)
               AND (ba.prs_bedrijf_key2 = pbedrijf_key2 OR ba.prs_bedrijf_key2 IS NULL)
               AND (   ba.alg_district_key = (SELECT d.alg_district_key
                                                FROM alg_locatie d
                                                WHERE d.alg_locatie_key = plocatie_key)
                     OR ba.alg_district_key IS NULL)
               AND (l_srtnoti_key IN  (SELECT basrt.fac_srtnotificatie_key
                                         FROM prs_bedrijfadres_srtnoti basrt
                                        WHERE basrt.prs_bedrijfadres_key = ba.prs_bedrijfadres_key)
                   )
         ORDER BY ba.mld_typeopdr_key NULLS LAST,                     -- door de NULLS LAST heeft een specifieke altijd voorkeur
                  ba.ins_discipline_key NULLS LAST,
                  ba.alg_locatie_key NULLS LAST,
                  ba.alg_district_key NULLS LAST,
                  ba.prs_bedrijf_key2 NULLS LAST,
                  ba.prs_bedrijfadres_startdatum DESC
         FETCH FIRST 1 ROW ONLY;
      EXCEPTION
         WHEN NO_DATA_FOUND
         THEN
            DBMS_OUTPUT.Put_line ('Geen geschikt technisch adres gevonden onder bedrijf ' || pbedrijf_key);
            RETURN;
      END;

      DBMS_OUTPUT.Put_line ('Gevonden technisch adres key ' || l_bdradr_key);

      -- Nu de srtnotificatie info zoeken
      SELECT basrt.prs_bedrijfadres_srtnoti_key,
               srtnoti.fac_srtnotificatie_code,
               srtnoti.fac_srtnotificatie_delay,
               srtnoti.fac_srtnotificatie_xmlnode
         INTO l_basrt_key,
               l_srtnoti_code,
               l_srtnoti_delay,
               l_srtnoti_xmlnode
         FROM prs_bedrijfadres_srtnoti basrt, fac_srtnotificatie srtnoti
      WHERE     basrt.prs_bedrijfadres_key(+) = l_bdradr_key
            AND basrt.fac_srtnotificatie_key(+) = srtnoti.fac_srtnotificatie_key
            AND srtnoti.fac_srtnotificatie_key = l_srtnoti_key;

      -- ORDUPD of wat dan ook sturen we niet als er nog geen ORDNEW tracking (en naar we aannamen notificatie) is geweest
      -- (DECODE mag alleen binnen een SELECT, domme Oracle)
      SELECT DECODE(l_srtnoti_xmlnode, 'opdracht',    'ORDNEW',
                                       'bestelopdr',  'BES2NE',
                                       'contract',    'CNTNEW',
                                       'reservering', 'RESSND')  -- RESNEW gaat niet vaak naar externen
         INTO l_srtnoti_chkcode
         FROM DUAL;
      IF (l_srtnoti_chkcode IS NOT NULL)
      THEN
         BEGIN
            SELECT MIN(NULL) INTO dummy -- Dubbele is nooit logisch maar is héél sporadisch wel aanwezig. Dan geen ORA error nodig dus de MIN
               FROM fac_tracking ft, fac_srtnotificatie fsn
               WHERE ft.fac_srtnotificatie_key = fsn.fac_srtnotificatie_key
               AND ft.fac_tracking_refkey = prefkey
               AND fac_srtnotificatie_code = l_srtnoti_chkcode;
         EXCEPTION
            WHEN NO_DATA_FOUND
            THEN
               DBMS_OUTPUT.Put_line ('Nog geen tracking van ' || l_srtnoti_chkcode || ' dus stuur ik ' || l_srtnoti_code || ' nog maar niet');
               RETURN;
         END;
      END IF;

      -- Ok, het technisch adres luistert naar onze notificatie!
      -- Geen putnotificationsrtprio. Die is veel te agressief met ontdubbelen
      delaymode := fac.safe_to_number(fac.getsetting('noti_delay_mode'));
      IF pnotidelay IS NOT NULL
      THEN
         notbefore := SYSDATE + pnotidelay / 60/ 60/ 24;
      ELSIF delaymode = 1
      THEN
         notbefore := SYSDATE + NVL(l_srtnoti_delay/ 60/ 60/ 24, 0);
      ELSIF delaymode > 1 -- fixed delay
      THEN
         notbefore := SYSDATE + delaymode / 60/ 60/ 24;
      ELSE -- delaymode = 0 -- default uit, naar technische adres zonder vertraging
         notbefore := SYSDATE;
      END IF;
      INSERT INTO fac_notificatie (fac_srtnotificatie_key,
                                    fac_notificatie_oms,
                                    fac_notificatie_refkey,
                                    fac_notificatie_extrakey,
                                    fac_notificatie_naam,
                                    fac_notificatie_sender_email,
                                    fac_notificatie_status, -- don't care when by prs_bedrijfadres_key
                                    prs_bedrijfadres_key,
                                    fac_notificatie_notbefore)
            VALUES (l_srtnoti_key,
                     l_oms,
                     prefkey,
                     l_extra_key,
                     preference,
                     psender,
                     0,
                     l_bdradr_key,
                     notbefore);
   END;

    -- Levert de (nieuwste) datum op van een getrackt event op key
    -- bv de laatste wijzigingsdatum van melding 123 is gettrackingdate('MLDUPD', 123)
    FUNCTION gettrackingdate (peventcode IN VARCHAR2, pkey IN number) RETURN date
    AS
     retdatum date := NULL;
    BEGIN
       IF peventcode IS NOT NULL AND pkey IS NOT NULL THEN
       BEGIN
         SELECT MAX(fac_tracking_datum)
           INTO retdatum
           FROM fac_tracking t, fac_srtnotificatie st
          WHERE t.fac_srtnotificatie_key = st.fac_srtnotificatie_key
            AND st.fac_srtnotificatie_code = peventcode
            AND fac_tracking_refkey=pkey;
       EXCEPTION
           WHEN NO_DATA_FOUND
            THEN NULL;
       END;
       END IF;
    RETURN retdatum;
    END;

    -- Levert de (nieuwste) muteerder op van een getrackt event op key
    -- bv de laatste wijziger van melding 123 is gettrackinguserkey('MLDUPD', 123)
    FUNCTION gettrackinguserkey (peventcode IN VARCHAR2, pkey IN NUMBER)
       RETURN NUMBER
    AS
       retkey fac_tracking.prs_perslid_key%TYPE := NULL;
    BEGIN
       BEGIN
          SELECT   prs_perslid_key
            INTO   retkey
            FROM   fac_tracking t, fac_srtnotificatie st
           WHERE       t.fac_srtnotificatie_key = st.fac_srtnotificatie_key
                   AND st.fac_srtnotificatie_code = peventcode
                   AND fac_tracking_refkey = pkey;
       EXCEPTION
          WHEN NO_DATA_FOUND
          THEN
             NULL;
          WHEN TOO_MANY_ROWS
          THEN
             -- Veronderstel dat de tracking meestal uniek is, daarom slechts in
             -- uitzonderingsgevallen de nieuwste bepalen
             SELECT   prs_perslid_key
               INTO retkey
               FROM   fac_tracking t, fac_srtnotificatie st
              WHERE       t.fac_srtnotificatie_key = st.fac_srtnotificatie_key
                      AND st.fac_srtnotificatie_code = peventcode
                      AND fac_tracking_refkey = pkey
                      AND fac_tracking_key =
                            (SELECT   MAX (fac_tracking_key)
                               FROM   fac_tracking t, fac_srtnotificatie st
                              WHERE       t.fac_srtnotificatie_key = st.fac_srtnotificatie_key
                                      AND st.fac_srtnotificatie_code = peventcode
                                      AND fac_tracking_refkey = pkey);
       END;

       RETURN retkey;
    END;

   -- Keep initsession (flcode IN VARCHAR2) for branches (OLD)
   PROCEDURE initsession (flcode IN VARCHAR2)
   AS
      setsql VARCHAR2(200);
   BEGIN
        -- Initialiseert deze Oracle session met de vereiste parameters
        -- De flcode (FACILITOR Language code) mag null zijn, dan wordt de databasedefault gebruikt
        lcl.setuserlanguage(flcode);
        setsql := 'ALTER SESSION SET '
             || ' NLS_NUMERIC_CHARACTERS = ''.,'''
             || ' NLS_SORT = ''BINARY'''
             || ' NLS_TERRITORY = ''AMERICA'''
             || ' NLS_DATE_FORMAT = ''YYYY-MM-DD HH24:MI:SS''';
        EXECUTE IMMEDIATE setsql;
   END;

   PROCEDURE initsession (pkey NUMBER, flcode IN VARCHAR2, usertimezone IN VARCHAR2, realuser IN VARCHAR2 DEFAULT NULL)
   AS
      setsql VARCHAR2(200);
   BEGIN
        DBMS_SESSION.SET_IDENTIFIER(pkey);
        dbms_application_info.SET_CLIENT_INFO(realuser);

        -- Initialiseert deze Oracle session met de vereiste parameters
        -- De flcode (FACILITOR Language code) mag null zijn, dan wordt de databasedefault gebruikt
        lcl.setuserlanguage(flcode);
        setsql := 'ALTER SESSION SET '
             || ' NLS_NUMERIC_CHARACTERS = ''.,'''
             || ' NLS_SORT = ''BINARY'''
             || ' NLS_TERRITORY = ''AMERICA'''
             || ' NLS_DATE_FORMAT = ''YYYY-MM-DD HH24:MI:SS'''
             || ' TIME_ZONE = ''' || COALESCE(REPLACE(usertimezone, '''', ''''''), 'Europe/Amsterdam') || '''';
        EXECUTE IMMEDIATE setsql;
   END;

   PROCEDURE registerversion (pschema IN VARCHAR2, plang IN VARCHAR2)
   AS
       verstring fac_module.fac_module_version%TYPE;
       revisionnr fac_version.fac_version_schema%TYPE;
     BEGIN
       IF pschema IS NOT NULL
       THEN
          UPDATE   fac_module
             SET   fac_module_version = TO_CHAR(pschema);

          UPDATE   fac_version
             SET   fac_version_schema = pschema,
                   fac_version_date = SYSDATE;

          UPDATE   fac_message
             SET   fac_message_text = 'FACILITOR  DB' || TO_CHAR(pschema)
           WHERE   fac_message_code = 'FAC_M002';
       END IF;

       IF plang IS NOT NULL
       THEN
          UPDATE   fac_version
             SET   fac_version_lang = plang;
       END IF;
    END;

    -- fac_version_custnr is deprecated sinds 2016.3
    PROCEDURE registercustversion (pcustid IN VARCHAR2, pcustnr IN NUMBER)
    AS
    BEGIN
       IF pcustid IS NOT NULL AND pcustnr IS NOT NULL
       THEN
          UPDATE   fac_version
             SET   fac_version_cust = pcustid,
                   fac_version_custnr = pcustnr,
                   fac_version_date = SYSDATE;
       END IF;
    END;

    FUNCTION getdbversion RETURN VARCHAR2
    AS
       verstring VARCHAR2(100);
       maxdate DATE;
    BEGIN
       -- Hoogste klant-sql. Onderken ook uwva_prs.sql
       SELECT MAX(adm_tracking_date)
         INTO maxdate
         FROM adm_tracking
        WHERE UPPER(adm_tracking_name) LIKE '%.SQL';

       SELECT 'DB'||fac_version_schema||'/'||fac_version_cust||'@'||TO_CHAR(maxdate, 'DD-MM-YYYY')||'-'||COALESCE(fac_version_otap,'P*')
         INTO verstring
         FROM fac_version;
      RETURN verstring;
    END;


   -- Hulpfuncties voor processemail
    -- Haalt default Behandelteam op
    FUNCTION getdefaultbehandelteam (p_stdm IN NUMBER)
        RETURN NUMBER
    IS
        stdm_default_disc   mld_stdmelding.mld_stdmelding_default_disc%TYPE;
    BEGIN
        SELECT mld_stdmelding_default_disc
          INTO stdm_default_disc
          FROM mld_stdmelding
         WHERE mld_stdmelding_key = p_stdm;

        RETURN stdm_default_disc;
    EXCEPTION
        WHEN NO_DATA_FOUND
        THEN
            RETURN NULL;
    END;

    PROCEDURE processemail (p_from            IN VARCHAR2
                          , p_to              IN VARCHAR2
                          , p_subject         IN VARCHAR2
                          , p_body            IN VARCHAR2
                          , p_session_id      IN VARCHAR2
                          , p_email_set_key   IN NUMBER)
    AS
        -- Local variables
        l_sender                prs_perslid.prs_perslid_key%TYPE;
        l_cost_center           prs_afdeling.prs_kostenplaats_key%TYPE;
        l_new_key               mld_melding.mld_melding_key%TYPE;
        l_std_message_key       mld_stdmelding.mld_stdmelding_key%TYPE;
        l_std_thirdparty_flag   mld_stdmelding.mld_stdmelding_thirdparty%TYPE;
        l_std_discipline_key    mld_stdmelding.mld_ins_discipline_key%TYPE;
        l_fallback_sender       fac_email_setting.prs_perslid_key_auth%TYPE;
        l_using_fallback        BOOLEAN := FALSE;
        l_existing_mld_key      mld_melding.mld_melding_key%TYPE;
        l_sender_email_ext      mld_melding.mld_melding_email_ext%TYPE;
        l_kenmerk_key           mld_kenmerk.mld_kenmerk_key%TYPE;
        l_error_msg             fac_result.fac_result_waarde%TYPE;
        l_default_disc          mld_stdmelding.mld_ins_discipline_key%TYPE;
        -- Precomputed cleaned body
        l_clean_body            VARCHAR2 (4000);

        -- Helper function: extract existing ticket key
        FUNCTION get_mld_key_from_subject (subj IN VARCHAR2, sndr IN NUMBER)
            RETURN NUMBER
        IS
            m   VARCHAR2 (4000);
            d   VARCHAR2 (10);
            k   NUMBER;
        BEGIN
            m :=
                REGEXP_SUBSTR (subj
                             , '([A-Za-z]{1,3})?#?\d{1,10}'
                             , 1
                             , 1
                             , 'i');

            IF m IS NOT NULL
            THEN
                d := REGEXP_SUBSTR (m, '\d{1,10}');
            END IF;

            IF d IS NOT NULL
            THEN
                SELECT MAX (mld_melding_key)
                  INTO k
                  FROM mld_melding
                 WHERE mld_melding_key = fac.safe_to_number (d) AND prs_perslid_key = sndr;

                RETURN k;
            END IF;

            RETURN NULL;
        EXCEPTION
            WHEN NO_DATA_FOUND
            THEN
                RETURN NULL;
        END;

        -- Helper function: clean body text
        FUNCTION clean_body (bod IN VARCHAR2)
            RETURN VARCHAR2
        IS
            t   VARCHAR2 (4000);
        BEGIN
            t := REPLACE (bod, CHR (13) || CHR (10) || CHR (13) || CHR (10), CHR (13) || CHR (10));
            RETURN SUBSTR (t, 1, 4000);
        END;
    BEGIN
        -- Precompute body variants
        l_clean_body := clean_body (p_body);

        -- Alle criteria zijn al 'verbruikt' om de juiste configuratie te vinden
        -- dus p_email_set_key is voldoende om de juiste standaardmelding te vinden
        -- In principe definieert de afzender de melder, maar als die niet bestaat kan
        -- dat de bij de configuratie behorende fallback persoon zijn.

        -- 1. Validate sender (and get the key)
        BEGIN
            SELECT p.prs_perslid_key, d.prs_kostenplaats_key
              INTO l_sender, l_cost_center
              FROM prs_perslid p JOIN prs_afdeling d ON p.prs_afdeling_key = d.prs_afdeling_key
             WHERE     p.prs_perslid_verwijder IS NULL
                   AND (p.prs_perslid_inactief IS NULL OR p.prs_perslid_inactief > SYSDATE)
                   AND (p.prs_perslid_einddatum IS NULL OR p.prs_perslid_einddatum > SYSDATE)
                   AND UPPER (p.prs_perslid_email) = UPPER (p_from);
        EXCEPTION
            WHEN NO_DATA_FOUND
            THEN
                l_sender := NULL;
                -- fac.writelog ('PROCESSEMAIL', 'I', 'Unknown sender: ' || p_from, NULL);
        END;

        -- 2. Fetch standard stdmelding props from configuration
        BEGIN
            SELECT s.mld_stdmelding_key
                 , s.mld_stdmelding_thirdparty
                 , s.mld_ins_discipline_key
                 , e.prs_perslid_key_auth
              INTO l_std_message_key
                 , l_std_thirdparty_flag
                 , l_std_discipline_key
                 , l_fallback_sender
              FROM fac_email_setting e JOIN mld_stdmelding s ON e.mld_stdmelding_key = s.mld_stdmelding_key
             WHERE     e.fac_email_setting_key = p_email_set_key
                   AND s.mld_stdmelding_verwijder IS NULL
                   AND (s.mld_stdmelding_vervaldatum IS NULL OR s.mld_stdmelding_vervaldatum > SYSDATE);
        EXCEPTION
            WHEN NO_DATA_FOUND
            THEN
                l_error_msg := 'Onvolledige email configuratie: geen standaardmelding gevonden voor configuratie ' || p_email_set_key;
                fac.writelog ('PROCESSEMAIL'
                            , 'E'
                            , l_error_msg
                            , '');
               INSERT INTO fac_result (fac_result_sessionid, fac_result_naam, fac_result_waarde)
                  VALUES (p_session_id, 'errormsg', 'Dit mailadres is onbekend');  -- zichtbaar voor de mailer
                RETURN;
        END;

        -- 3. Fallback if third-party
        IF l_sender IS NULL AND l_std_thirdparty_flag = 1 AND l_fallback_sender IS NOT NULL
        THEN
            l_sender := l_fallback_sender;
            l_using_fallback := TRUE;
            fac.writelog ('PROCESSEMAIL'
                        , 'I'
                        , 'Fallback melder ('||l_sender||') wordt gebruikt voor ' || p_from
                        , NULL);
        END IF;

        IF l_sender IS NULL
        THEN
            l_error_msg := 'Afzender '|| p_from ||' is onbekend en er kan geen fallback worden afgeleid uit de email configuratie';
            fac.writelog ('PROCESSEMAIL'
                        , 'E'
                        , l_error_msg
                        , NULL);
            INSERT INTO fac_result (fac_result_sessionid, fac_result_naam, fac_result_waarde)
               VALUES (p_session_id, 'errormsg', 'De afzender is onbekend');  -- zichtbaar voor de mailer
            RETURN;
        END IF;

        -- 4. Authorization check
        BEGIN
            SELECT 1
              INTO l_kenmerk_key
              FROM fac_v_webgebruiker v
             WHERE     v.fac_functie_key = (SELECT fac_functie_key
                                              FROM fac_functie
                                             WHERE fac_functie_code = 'WEB_MLDUSE')
                   AND v.ins_discipline_key = l_std_discipline_key
                   AND v.prs_perslid_key = l_sender
                   AND v.fac_gebruiker_prs_level_write < 9
                   AND v.fac_gebruiker_alg_level_write < 9;
        EXCEPTION
            WHEN NO_DATA_FOUND
            THEN
                l_error_msg := 'De melder is niet geautoriseerd voor melding van discipline ' || l_std_discipline_key;
                fac.writelog ('PROCESSEMAIL'
                            , 'E'
                            , l_error_msg
                            , NULL);
               INSERT INTO fac_result (fac_result_sessionid, fac_result_naam, fac_result_waarde)
                  VALUES (p_session_id, 'errormsg', 'De afzender is niet geautoriseerd');  -- zichtbaar voor de mailer
                RETURN;
        END;

        -- 5. Create new or append
        l_existing_mld_key := get_mld_key_from_subject (p_subject, l_sender);

        IF l_existing_mld_key IS NULL
        THEN
            -- New melding
            l_default_disc := fac.getdefaultbehandelteam (l_std_message_key);

            IF l_using_fallback
            THEN
                -- Store mail address as external email
                l_sender_email_ext := p_from;
            END IF;

            INSERT INTO mld_melding (mld_melding_module
                                   , mld_meldbron_key
                                   , mld_melding_datum
                                   , mld_melding_onderwerp
                                   , mld_melding_omschrijving
                                   , mld_melding_status
                                   , mld_stdmelding_key
                                   , prs_perslid_key
                                   , prs_perslid_key_voor
                                   , mld_melding_email_ext
                                   , prs_kostenplaats_key
                                   , mld_melding_spoed
                                   , mld_ins_discipline_key)
                 VALUES ('MLD'
                       , 4
                       , SYSDATE
                       , SUBSTR(p_subject, 1, 200)
                       , l_clean_body
                       , NULL
                       , l_std_message_key
                       , l_sender
                       , l_sender
                       , l_sender_email_ext
                       , l_cost_center
                       , 3
                       , l_default_disc)
              RETURNING mld_melding_key
                   INTO l_new_key;

            mld.setmeldingstatus (l_new_key, 2, l_sender);

            -- En nu dan?
            IF l_default_disc IS NOT NULL
            THEN
                mld.notifybackoffice (l_new_key, 'MLDBHG', 2);
            END IF;

            mld.mld_nextworkflowstep (l_new_key, -1);
            mld.mld_addautoorder(l_new_key);


            -- logging voor de mailserver..
            INSERT INTO fac_result (fac_result_sessionid, fac_result_naam, fac_result_waarde)
                 VALUES (p_session_id, 'maillog', 'Mail geregistreerd onder melding ' || TO_CHAR (l_new_key));
        ELSE
            -- Append note
            INSERT INTO mld_melding_note (mld_melding_key
                                        , mld_melding_note_omschrijving
                                        , prs_perslid_key
                                        , mld_melding_note_flag)
                 VALUES (l_existing_mld_key
                       , l_clean_body
                       , l_sender
                       , 1);

            fac.trackaction ('MLDA2B'
                           , l_existing_mld_key
                           , l_sender
                           , NULL
                           , '#Notitie via email');  -- TODO testen of okay zo

            UPDATE mld_melding
               SET mld_melding_actiecode = 128 + 1
             WHERE mld_melding_status != 5
               AND mld_melding_key = l_existing_mld_key;

            -- logging voor de mailserver..
            INSERT INTO fac_result (fac_result_sessionid, fac_result_naam, fac_result_waarde)
                 VALUES (p_session_id, 'maillog', 'Notitie toegevoegd aan melding ' || TO_CHAR (l_existing_mld_key));

            fac.writelog ('PROCESSEMAIL'
                        , 'I'
                        , 'Mail als notitie toegevoegd aan ' || l_existing_mld_key
                        , NULL);
            l_new_key := l_existing_mld_key;

        END IF;

         l_error_msg := NULL;
        -- Alleen van een nieuwe melding bewaren we de originele mail body
         l_error_msg := NULL;
        IF l_existing_mld_key IS NULL
        THEN
            -- 6a. ORGMAIL kenmerk
            BEGIN
                SELECT k.mld_kenmerk_key
                  INTO l_kenmerk_key
                  FROM mld_kenmerk         k
                     , mld_srtkenmerk      sk
                     , mld_stdmelding      std
                     , ins_tab_discipline  d
                 WHERE     mld_srtkenmerk_kenmerktype = 'M'
                       AND sk.mld_srtkenmerk_key = k.mld_srtkenmerk_key
                       AND std.mld_stdmelding_key = l_std_message_key
                       AND std.mld_ins_discipline_key = d.ins_discipline_key
                       AND (   (k.mld_kenmerk_niveau = 'A' AND k.mld_stdmelding_key IS NULL)
                            OR (k.mld_kenmerk_niveau = 'S' AND k.mld_stdmelding_key = std.mld_stdmelding_key)
                            OR (k.mld_kenmerk_niveau = 'D' AND k.mld_stdmelding_key = d.ins_discipline_key)
                            OR (k.mld_kenmerk_niveau = 'T' AND k.mld_stdmelding_key = d.ins_srtdiscipline_key))
                       AND k.mld_kenmerk_verwijder IS NULL
                       AND BITAND (k.mld_kenmerk_rolcode, 1) = 1
                       AND k.mld_kenmerk_code = '*ORGMAIL';

                INSERT INTO fac_result (fac_result_sessionid, fac_result_naam, fac_result_waarde)
                     VALUES (p_session_id, 'kenmerkorgmailpath', 'MLD\M' || TO_CHAR (TRUNC (l_new_key / 1000), 'FM0000') || '___\M' || l_new_key || '\' || l_kenmerk_key || '\');
            EXCEPTION
                WHEN NO_DATA_FOUND
                THEN
                    fac.writelog ('PROCESSEMAIL'
                                , 'I'
                                , 'Geen bijlagenkenmerk (code *ORGMAIL) voor origineel bericht geconstateerd.'
                                , l_error_msg);
                WHEN TOO_MANY_ROWS
                THEN
                    fac.writelog ('PROCESSEMAIL'
                                , 'W'
                                , 'Niet eenduidig bijlagenkenmerk (code *ORGMAIL) voor origineel bericht geconstateerd.'
                                , l_error_msg);
            END;
        END IF;

         -- 6b. Attachment kenmerk, bij nieuwe en bestaande melding
        BEGIN
              SELECT k.mld_kenmerk_key
              INTO l_kenmerk_key
              FROM mld_kenmerk         k
                 , mld_srtkenmerk      sk
                 , mld_stdmelding      std
                 , ins_tab_discipline  d
              WHERE     mld_srtkenmerk_kenmerktype = 'M'
                    AND sk.mld_srtkenmerk_key = k.mld_srtkenmerk_key
                    AND std.mld_stdmelding_key = l_std_message_key
                    AND std.mld_ins_discipline_key = d.ins_discipline_key
                    AND (   (k.mld_kenmerk_niveau = 'A' AND k.mld_stdmelding_key IS NULL)
                          OR (k.mld_kenmerk_niveau = 'S' AND k.mld_stdmelding_key = std.mld_stdmelding_key)
                          OR (k.mld_kenmerk_niveau = 'D' AND k.mld_stdmelding_key = d.ins_discipline_key)
                          OR (k.mld_kenmerk_niveau = 'T' AND k.mld_stdmelding_key = d.ins_srtdiscipline_key))
                    AND k.mld_kenmerk_verwijder IS NULL
                    AND BITAND (k.mld_kenmerk_rolcode, 1) = 1
                    AND k.mld_kenmerk_code = '*MAILATT';
              INSERT INTO fac_result (fac_result_sessionid, fac_result_naam, fac_result_waarde)
                    VALUES (p_session_id, 'kenmerkpath', 'MLD\M' || TO_CHAR (TRUNC (l_new_key / 1000), 'FM0000') || '___\M' || l_new_key || '\' || l_kenmerk_key || '\');
             l_error_msg := NULL;
        EXCEPTION
              WHEN NO_DATA_FOUND
              THEN
                 fac.writelog ('PROCESSEMAIL'
                             , 'I'
                             , 'Geen bijlagenkenmerk (code *MAILATT) voor bijlagen geconstateerd.'
                             , l_error_msg);
              WHEN TOO_MANY_ROWS
              THEN
                 fac.writelog ('PROCESSEMAIL'
                             , 'W'
                             , 'Niet eenduidig bijlagenkenmerk (code *MAILATT) voor bijlagen geconstateerd.'
                             , l_error_msg);
        END;

    EXCEPTION
        WHEN OTHERS
        THEN
            fac.writelog ('PROCESSEMAIL'
                        , 'E'
                        , 'Error: ' || SQLERRM
                        , NULL);
            INSERT INTO fac_result (fac_result_sessionid, fac_result_naam, fac_result_waarde)
                 VALUES (p_session_id, 'errormsg', 'Unexpected error');  -- zichtbaar voor de mailer
    END processemail;


    -- Is de meegegeven datum een feestdag
    FUNCTION isdatefeestdag(p_date IN DATE, pCalendarId IN VARCHAR2 DEFAULT 'Default')
        RETURN BOOLEAN
    IS
        inspdate   DATE;
    BEGIN
      SELECT mvd.mld_vrije_dagen_datum
        INTO inspdate
        FROM mld_vrije_dagen mvd
       WHERE mvd.mld_vrije_dagen_datum = TRUNC(p_date, 'DD')
         AND mvd.mld_vrije_dagen_id = pCalendarId;
      RETURN TRUE;
    EXCEPTION
      WHEN NO_DATA_FOUND
      THEN
         -- Datum is geen feestdag.
         RETURN FALSE;
    END;

    -- Calculation of next cyclus date
    -- Deze functie moet snel en efficient zijn.
    FUNCTION calcnextcyclusdate(p_date      IN DATE,
                                p_mode      IN NUMBER,
                                p_eenheid   IN NUMBER,
                                p_periode   IN NUMBER,
                                p_bits      IN NUMBER,
                                pCalendarId IN VARCHAR2 DEFAULT 'Default')
        RETURN DATE
    IS
        fromdate            DATE;
        fromdateplusperiode DATE;
        fromdow             NUMBER; -- Day of week.
        fromdom             NUMBER; -- Day of month.
        frommoy             NUMBER; -- Month of year.
        todaydow            NUMBER;
        inspdate            DATE;
        nextday             NUMBER;
        nextmonth           NUMBER;
        lastday             DATE;
        w_bits              NUMBER;
        m_bits              NUMBER;
        y_bits              NUMBER;
        w_day_n             NUMBER;
        w_day_v             VARCHAR2(10);
        gevonden            BOOLEAN;
        month1              NUMBER;
        month2              NUMBER;
    BEGIN
        IF (p_periode = 0 OR p_eenheid = 5)
        THEN
          RETURN p_date;
        END IF;
        -- moment-modus of interval-modus?
        IF p_mode = 0
        THEN
            -- momentmodus UIT TE WERKEN!
            --fromdate := GREATEST (SYSDATE, p_date);
            fromdate := p_date;
            CASE p_eenheid
                WHEN 0
                THEN
                    BEGIN
                        -- uurlijks
                        inspdate := fromdate + p_periode / 24;
                        IF (BITAND(p_bits, 128) = 128) AND isdatefeestdag(inspdate, pCalendarId)
                        THEN
                          -- Checkbox "Niet op feestdagen" is aangevinkt en de datum is een feestdag. De datum overslaan en bepaal de volgende inspectiedatum.
                          inspdate := calcnextcyclusdate (inspdate, p_mode, p_eenheid, p_periode, p_bits, pCalendarId);
                        END IF;
                        -- werking van cyclus_bits tbd
                    END;
                WHEN 1
                THEN
                    BEGIN
                        -- dagelijks, elke cperiode dagen
                        inspdate := fromdate + p_periode;
                        IF (BITAND(p_bits, 128) = 128) AND isdatefeestdag(inspdate, pCalendarId)
                        THEN
                          -- Checkbox "Niet op feestdagen" is aangevinkt en de datum is een feestdag. De datum overslaan en bepaal de volgende inspectiedatum.
                          inspdate := calcnextcyclusdate (inspdate, p_mode, p_eenheid, p_periode, p_bits, pCalendarId);
                        END IF;
                    -- cyclus_bits heeft geen effect
                    END;
                WHEN 2
                THEN
                    BEGIN
                        -- wekelijks
                        -- werking van cyclus_bits
                        -- bits voor elke dag (als in: tweewekelijks elke dinsdag en donderdag = 4+16=20)
                        -- 0 = geen dag (gewoon 7 dagen verder)
                        -- 1 = zondags (2^0): 1 dow
                        -- 2 = maandags (2^1): 2 dow
                        -- 4 = dinsdags (2^2): 3 dow
                        -- 8 == woensdag (2^3): 4 dow
                        -- 16 = donderdags (2^4): 5 dow
                        -- 32 = vrijdags (2^5): 6 dow
                        -- 64 = zaterdags (2^6): 7 dow

                        -- Is er deze week nog een inspectiedag?
                        --   Ja:  De volgende bits dag.
                        --   Nee: Als bits is 0: exact x (eenheid) weken verder.
                        --     Als bits > 0:  x (eenheid) weken verder de eerste bits dag.
                        fromdow := fac.getweekdaynum (fromdate);
                        IF p_bits IS NULL OR p_bits = 0
                        THEN
                          -- Dan maar een waarde kiezen. We kiezen vandaag.
                          todaydow := fac.getweekdaynum (SYSDATE);
                          w_bits := POWER(2, fromdow - 1) ;
                        ELSIF p_bits >= 128
                        THEN
                          w_bits := BITAND(p_bits, 127);
                        ELSE
                          w_bits := p_bits;
                        END IF;
                        IF w_bits IS NOT NULL AND w_bits >= POWER(2, fromdow)  -- Bijvoorbeeld donderdag(5): p_bits >= 2^5(32). (32 is vrijdags)
                        THEN
                          -- Er is deze week nog een inspectiedag.
                          -- Bepaal de volgende bits dag deze week.
                          FOR i IN fromdow..6 LOOP
                            nextday := i;
                            EXIT WHEN BITAND(w_bits, POWER(2, i)) = POWER(2, i);
                          END LOOP;
                          inspdate := fromdate - fromdow + 1 + nextday;
                        ELSE
                          -- Er is deze week geen inspectiedag meer.
                          -- Eerste bits dag x (eenheid) weken verder.
                          IF w_bits IS NULL OR w_bits = 0
                          THEN
                            -- Exact eenheid weken verder.
                            inspdate := fromdate + p_periode * 7;
                          ELSE
                            -- Bepaal de eerste bits dag x (eenheid) weken verder.
                            inspdate := fromdate + p_periode * 7 + 1 - fromdow; -- zondag
                            FOR i IN 0..6 LOOP
                              nextday := i;
                              EXIT WHEN BITAND(w_bits, POWER(2, i)) = POWER(2, i);
                            END LOOP;
                            inspdate := inspdate + nextday;
                          END IF;
                        END IF;
                        IF (BITAND(p_bits, 128) = 128) AND isdatefeestdag(inspdate, pCalendarId)
                        THEN
                          -- Checkbox "Niet op feestdagen" is aangevinkt en de datum is een feestdag. De datum overslaan en bepaal de volgende inspectiedatum.
                          inspdate := calcnextcyclusdate (inspdate, p_mode, p_eenheid, p_periode, p_bits, pCalendarId);
                        END IF;
                    END;
                WHEN 3
                THEN
                    BEGIN
                        -- Maandelijks
                        -- werking van cyclus_bits
                        -- bits 1 t/m 8 gebruiken we op dezelfde wijze als bij eenheid week
                        -- bits voor elke dag (als in: tweemaandelijks elke dinsdag en donderdag = 4+16=20)
                        -- 0 = geen dag (gewoon 7 dagen verder)
                        -- 1 = zondags (2^0): 1 dow (day of week/dag van de week)
                        -- 2 = maandags (2^1): 2 dow
                        -- 4 = dinsdags (2^2): 3 dow
                        -- 8 == woensdag (2^3): 4 dow
                        -- 16 = donderdags (2^4): 5 dow
                        -- 32 = vrijdags (2^5): 6 dow
                        -- 64 = zaterdags (2^6): 7 dow
                        -- 128 = niet op een feestdag
                        -- bits 9 t/m 11 geven de dag van de maand aan
                        -- 1 = eerste xxxdag van de maand (2^9 = 256)
                        -- 2 = tweede xxxdag van de maand (2^10 = 512)
                        -- 3 = derde xxxdag van de maand (2^9 + 2^10 = 256 + 512 = 768)
                        -- 4 = vierde xxxdag van de maand (2^11 = 1024)
                        -- 5 = vijfde xxxdag van de maand (2^9 + 2^11 = 256 + 1024 = 1280)
                        -- 6 = één na laatste xxxdag van de maand (2^10 + 2^11 = 512 + 1024 = 1536)
                        -- 7 = laatste dag van de maand (2^9 + 2^10 + 2^11 = 256 + 512 + 1024 = 1792)

                        fromdow := fac.getweekdaynum (fromdate);
                        -- Alleen de gezette bits van de dagen van de week.
                        w_bits := BITAND(p_bits, 127);
                        -- Alleen de gezette bits van de y-ste xxxdag van de maand.
                        m_bits := BITAND(p_bits, 256 + 512 + 1024) / 256;

                        -- NEXT_DAY verwacht als tweede parameter de dagnaam als string. De dag van de week (nummer) werkt niet in een package.
                        -- Bepaal de taalonafhankelijke weekdag die je zoekt, zodat je NEXT_DAY kunt gaan gebruiken.
                        CASE
                        WHEN BITAND(w_bits, 1) = 1
                        THEN w_day_n := 1; -- 'ZONDAG' / 'SUNDAY';
                        WHEN BITAND(w_bits, 2) = 2
                        THEN w_day_n := 2; -- 'MAANDAG' / 'MONDAY';
                        WHEN BITAND(w_bits, 4) = 4
                        THEN w_day_n := 3; -- 'DINSDAG' / 'TUESDAY';
                        WHEN BITAND(w_bits, 8) = 8
                        THEN w_day_n := 4; -- 'WOENSDAG' / 'WEDNESDAY';
                        WHEN BITAND(w_bits, 16) = 16
                        THEN w_day_n := 5; -- 'DONDERDAG' / 'THURSDAY';
                        WHEN BITAND(w_bits, 32) = 32
                        THEN w_day_n := 6; -- 'VRIJDAG' / 'FRIDAY';
                        WHEN BITAND(w_bits, 64) = 64
                        THEN w_day_n := 7; -- 'ZATERDAG' / 'SATURDAY';
                        ELSE w_day_n := fac.getweekdaynum (SYSDATE); -- bits is NULL of 0: Dag van vandaag nemen. Wordt in dit geval verder niets mee gedaan.
                        END CASE;
                        -- Tweede parameter van NEXT_DAY is de dag naam in de ingestelde taal
                        -- En we willen niet taal afhankelijk zijn. Dus de naam onafhankelijk bepalen
                        -- 01-06-2014 is een ZONDAG
                        w_day_v := TO_CHAR(TO_DATE('01-06-2014', 'DD-MM-YYYY') + w_day_n - 1, 'DAY');  -- Levert de naam van de weekdag op, taalonafhankelijk (ZONDAG.....ZATERDAG).

                        -- Is er deze maand nog een inspectiedag?
                        -- Eerst de eerste inspectiedag bepalen
                        IF w_bits IS NULL OR w_bits = 0
                        THEN
                          -- Exact eenheid maanden verder.
                          inspdate := ADD_MONTHS (fromdate, p_periode);
                        ELSIF m_bits > 5
                        THEN
                          CASE
                          WHEN m_bits = 6 OR m_bits = 7
                          THEN
                            -- Één na laatste (6) of laatste (7) xxxdag van de maand bepalen
                            -- Bepaal de eerste bits dag van de volgende maand de y-ste xxxdag van de maand.
                            -- en neem dan de datum van twee weken of één week daarvoor.
                            inspdate := NEXT_DAY (TRUNC(ADD_MONTHS (fromdate, 1), 'MONTH') - 1, w_day_v) - ((8 - m_bits) * 7); -- m_bits 6 resp. 7 dan 2 resp. 1 week/weken terug.

                            -- Als inspectie datum niet in de toekomst ligt dan de eerste inspectie datum bepalen in de volgende periode maanden verder.
                            IF (inspdate <= fromdate)
                            THEN
                              inspdate := NEXT_DAY (TRUNC(ADD_MONTHS (inspdate, p_periode + 1), 'MONTH') - 1, w_day_v) - ((8 - m_bits) * 7); -- m_bits 6 resp. 7 dan 2 resp. 1 week/weken terug.
                            ELSIF p_periode > 1  -- (inspdate > fromdate) geldt automatisch.
                            THEN
                              inspdate := NEXT_DAY (TRUNC(ADD_MONTHS (inspdate, p_periode), 'MONTH') - 1, w_day_v) - ((8 - m_bits) * 7); -- m_bits 6 resp. 7 dan 2 resp. 1 week/weken terug.
                            END IF;
                          ELSE
                            -- Niet ondersteunende instelling.
                            -- Exact eenheid maanden verder.
                            inspdate := ADD_MONTHS (fromdate, p_periode);
                          END CASE;
                        ELSE -- 0 < m_bits <= 5
                          gevonden := FALSE;
                          IF (p_periode = 1)
                          THEN
                            -- Eerste bits dag.
                            -- Bepaal de eerste bits dag deze maand de y-ste xxxdag van de maand. Misschien is er deze maand nog een dag te vinden.
                            inspdate := NEXT_DAY (TRUNC(fromdate, 'MONTH') - 1 + ((m_bits - 1) * 7), w_day_v);

                            -- Als de nieuwe dag in de toekomst is en in dezelfde maand (i.v.m. 5de dag) dan heb je de volgende datum gevonden.
                            -- Maar dan mag de datum niet een feestdag zijn als de checkbox "Niet op feestdagen" is aangevinkt.
                            month1 := TO_NUMBER(TO_CHAR (fromdate, 'MM'));
                            month2 := TO_NUMBER(TO_CHAR (inspdate, 'MM'));
                            IF (inspdate > fromdate AND month1 = month2) AND NOT ((BITAND(p_bits, 128) = 128) AND isdatefeestdag(inspdate, pCalendarId))
                            THEN
                              gevonden := TRUE;
                            END IF;
                          END IF;

                          IF NOT gevonden
                          THEN
                            -- Voor (p_periode = 1) niets gevonden of (p_periode > 1).
                            inspdate := NEXT_DAY (TRUNC(ADD_MONTHS (fromdate, p_periode), 'MONTH') - 1 + ((m_bits - 1) * 7), w_day_v);

                            IF (m_bits = 5)
                            THEN
                              -- Is er wel een vijfde dag in de maand. Is de fromdate + periode in dezelfde maand als de nieuw berekende inspdate?
                              fromdateplusperiode := ADD_MONTHS (fromdate, p_periode);
                              month1 := TO_NUMBER(TO_CHAR (fromdateplusperiode, 'MM'));
                              month2 := TO_NUMBER(TO_CHAR (inspdate, 'MM'));
                              IF (month1 <> month2)
                              THEN
                                -- Er is geen vijfde dag in de maand. De volgende bepalen.
                                inspdate := calcnextcyclusdate (fromdateplusperiode, p_mode, p_eenheid, p_periode, p_bits, pCalendarId);
                              ELSIF (BITAND(p_bits, 128) = 128) AND isdatefeestdag(inspdate, pCalendarId)
                              THEN
                                -- De datum is een feestdag en de checkbox "Niet op feestdagen" is aangevinkt. De datum overslaan en bepaal de volgende inspectiedatum.
                                inspdate := calcnextcyclusdate (inspdate, p_mode, p_eenheid, p_periode, p_bits, pCalendarId);
                              END IF;
                            ELSE
                              IF (BITAND(p_bits, 128) = 128) AND isdatefeestdag(inspdate, pCalendarId)
                              THEN
                                -- De datum is een feestdag en de checkbox "Niet op feestdagen" is aangevinkt. De datum overslaan en bepaal de volgende inspectiedatum.
                                inspdate := calcnextcyclusdate (inspdate, p_mode, p_eenheid, p_periode, p_bits, pCalendarId);
                              END IF;
                            END IF;
                          END IF;
                        END IF;
                    END;
                WHEN 4
                THEN
                    BEGIN
                        -- jaarlijks
                        -- werking van cyclus_bits
                        -- bits voor elke maand (als in: tweejaarlijks elke maand maart en mei = 4+16=20)
                        --  0 = geen maand (gewoon 12 maanden verder)
                        --  1 = januari (2^0): 1 moy
                        --  2 = februari (2^1): 2 moy
                        --  4 = maart (2^2): 3 moy
                        --  8 == april (2^3): 4 moy
                        --  16 = mei (2^4): 5 moy
                        --  32 = juni (2^5): 6 moy
                        --  64 = juli (2^6): 7 moy
                        -- 128 = augustus (2^7): 8 moy
                        -- 256 = september (2^8): 9 moy
                        -- 512 = oktober (2^9): 10 moy
                        -- 1024 = november (2^10): 11 moy
                        -- 2048 = december (2^11): 12 moy

                        -- Is er dit jaar nog een inspectiemaand?
                        --   Ja:  De volgende bits maand.
                        --   Nee: Als bits is 0: exact x (eenheid) jaren verder.
                        --        Als bits > 0:  x (eenheid) jaren verder de eerste bits maand.

                        frommoy := EXTRACT(month FROM fromdate);
                        IF p_bits IS NULL OR p_bits = 0
                        THEN
                          -- Dan maar een waarde kiezen. We kiezen deze maand.
                          todaydow := fac.getweekdaynum (SYSDATE);
                          y_bits := POWER(2, frommoy - 1) ;
                        ELSIF p_bits >= 4096
                        THEN
                          y_bits := BITAND(p_bits, 2048);
                        ELSE
                          y_bits := p_bits;
                        END IF;
                        IF y_bits IS NOT NULL AND y_bits >= POWER(2, frommoy)  -- Bijvoorbeeld mei(5): p_bits >= 2^5(32). (32 is juni)
                        THEN
                          -- Er is dit jaar nog een inspectiemaand.
                          -- Bepaal de volgende bits maand dit jaar.
                          FOR i IN frommoy..11 LOOP
                            nextmonth := i;
                            EXIT WHEN BITAND(y_bits, POWER(2, i)) = POWER(2, i);
                          END LOOP;
                          inspdate := ADD_MONTHS(fromdate, nextmonth - EXTRACT(month FROM fromdate) + 1);
                        ELSE
                          -- Er is dit jaar geen inspectiemaand meer.
                          -- Eerste bits maand x (eenheid) jaren verder.
                          IF y_bits IS NULL OR y_bits = 0
                          THEN
                            -- Exact eenheid maanden verder.
                            inspdate := ADD_MONTHS(fromdate, p_periode * 12);
                          ELSE
                            -- Bepaal de eerste bits maand x (eenheid) jaren verder.
                            inspdate := ADD_MONTHS(fromdate, (p_periode * 12) - (1 * EXTRACT(month FROM fromdate)) + 1); -- Januari
                            FOR i IN 0..11 LOOP
                              nextmonth := i;
                              EXIT WHEN BITAND(y_bits, POWER(2, i)) = POWER(2, i);
                            END LOOP;
                            inspdate := ADD_MONTHS(inspdate, nextmonth);
                          END IF;
                        END IF;

                        -- werking van cyclus_bits tbd
                    END;
            END CASE;
        ELSE
            -- intervalmodus, p_bits doet er niet toe
            fromdate := p_date;
            CASE p_eenheid
                WHEN 0
                THEN
                    BEGIN
                        -- uurlijks
                        inspdate := fromdate + p_periode / 24;
                    END;
                WHEN 1
                THEN
                    BEGIN
                        -- dagelijks, elke cperiode dagen
                        inspdate := fromdate + p_periode;
                    END;
                WHEN 2
                THEN
                    BEGIN
                        -- wekelijks
                        inspdate := fromdate + p_periode * 7;
                    END;
                WHEN 3
                THEN
                    BEGIN
                        -- maandelijks
                        inspdate := ADD_MONTHS (fromdate, p_periode);
                    END;
                WHEN 4
                THEN
                    BEGIN
                        -- jaarlijks
                        inspdate := ADD_MONTHS (fromdate, 12 * p_periode);
                    END;
            END CASE;
        END IF;
        -- Stukje veiligheid inbouwen die een oneindige loop (of recursie) van de datumberekening voorkomt zodat de Oracle Server niet gaat hangen.
        -- Berekende datum moet later zijn als de meegegeven datum.
        IF p_date >= inspdate AND p_periode <> 0
        THEN
          raise_application_error(-20001, 'FACILITOR: Invalid inspection date calculated! Check interval parameters');
        END IF;
        RETURN inspdate;
    END;

    -- Berekent de n-de (p_steps) controledatum na p_date.
    -- Indien p_steps niet is meegegeven dan wordt de eerstvolgende controledatum na nu teruggegeven.
    -- steps = 0, (NULL): 1-ste vandaag of in de toekomst (als meegegeven datum (p_date) de huidite datum is, dan deze weer teruggeven).
    -- steps = n: n-volgende vanaf meegegeven datum (p_date). Als deze berekende datum in de toekomst ligt en steps is nog niet null, dan wordt NULL teruggegeven).
    FUNCTION nextcyclusdate (p_date      IN DATE,
                             p_mode      IN NUMBER,
                             p_eenheid   IN NUMBER,
                             p_periode   IN NUMBER,
                             p_bits      IN NUMBER,
                             p_steps     IN NUMBER DEFAULT 0,
                             p_ismjob    IN NUMBER DEFAULT 0,
                             pCalendarId IN VARCHAR2 DEFAULT 'Default')
        RETURN DATE
    IS
        inspdate       DATE;
        inspdate_trunc DATE;
        vandaagdate    DATE;
        toekomst       BOOLEAN;
        nuoftoekomst   BOOLEAN;
    BEGIN
        inspdate := p_date;
        IF p_periode = 0
        THEN
          RETURN NULL;
        END IF;
        IF p_eenheid IS NULL
        THEN
          RETURN NULL;
        ELSIF p_eenheid = 0
        THEN
            inspdate_trunc := TRUNC(p_date, 'HH24');
            vandaagdate := TRUNC(SYSDATE, 'HH24');
        ELSE
            inspdate_trunc := TRUNC(p_date, 'DDD');
            vandaagdate := TRUNC(SYSDATE, 'DDD');
        END IF;
        toekomst := inspdate_trunc > vandaagdate;
        -- De volgende inspectiedatum berekenen.
        -- Maar let op, er kan een datum in de toekomst meegegeven zijn. Dan deze ook weer opleveren en niet de volgende berekenen.
        -- Bij het meegegeven van de huidige datum en steps is 0 de huidige controledatum weer teruggeven.
        IF NOT (p_steps = 0 AND toekomst)  -- p_steps != 0 || !toekomst
        THEN
            -- Calculate next cyclus date m.b.v. volledige datum met tijd.
            inspdate := calcnextcyclusdate (inspdate, p_mode, p_eenheid, p_periode, p_bits, pCalendarId);
        END IF;

        -- Nu insdate_trunc van de next cyclus date weer bepalen
        IF p_eenheid = 0
        THEN
            inspdate_trunc := TRUNC(inspdate, 'HH24');
        ELSE
            inspdate_trunc := TRUNC(inspdate, 'DDD');
        END IF;

        -- Voor MJOB is nu het hele jaar.
        IF p_ismjob = 1
        THEN
            nuoftoekomst := EXTRACT (YEAR FROM inspdate_trunc) >= EXTRACT (YEAR FROM vandaagdate);
        ELSE
            nuoftoekomst := inspdate_trunc >= vandaagdate;
        END IF;

        IF p_steps = 0
        THEN
            IF nuoftoekomst
            THEN
                RETURN inspdate;
            ELSE
                RETURN nextcyclusdate (inspdate, p_mode, p_eenheid, p_periode, p_bits, 0, 0, pCalendarId);  -- volledige datum met tijd gebruiken
            END IF;
        ELSIF p_steps > 1 AND nuoftoekomst
        THEN
            RETURN NULL;
        ELSIF p_steps = 1 OR nuoftoekomst
        THEN
            RETURN inspdate;
        ELSE
            RETURN nextcyclusdate (inspdate, p_mode, p_eenheid, p_periode, p_bits, p_steps - 1, 0, pCalendarId);  -- volledige datum met tijd gebruiken
        END IF;
    END;

    -- Berekent de n-de (p_steps) controledatum van een object na p_date.
    -- Indien p_steps niet is meegegeven dan wordt de eerstvolgende controledatum na nu teruggegeven.
    -- steps = 0, (NULL): 1-ste vandaag of in de toekomst (als meegegeven datum (p_date) de huidige datum is, dan deze weer teruggeven).
    -- steps = n: n-volgende vanaf meegegeven datum (p_date).
    -- Als deze berekende datum in de toekomst ligt en steps is nog niet null, dan wordt NULL teruggegeven).
    -- De einddatum van het object (xcp.ins_srtcontroledl_xcp_eind) wordt niet meegenomen. De volgende inspectiedatum wordt gewoon teruggeven.
    FUNCTION nextcyclusdatedeel (p_insdeel     IN NUMBER,
                                 p_srtcontrole IN NUMBER,
                                 p_steps       IN NUMBER DEFAULT 0,
                                 pCalendarId IN VARCHAR2 DEFAULT 'Default') -- Pas op: diverse klant-sql gebruiken deze functie
        RETURN DATE
    IS
        inspdate   DATE;
        scenario   ins_scenario.ins_scenario_key%TYPE := 1; -- Altijd actuele situatie

    BEGIN
        SELECT fac.nextcyclusdate (COALESCE ( (SELECT CASE xcp.ins_srtcontrole_mode
                                                      WHEN 0
                                                      THEN idsc.ins_deelsrtcontrole_datum_org
                                                      ELSE idsc.ins_deelsrtcontrole_datum
                                                      END ins_deelsrtcontrole_datum
                                                 FROM ins_deelsrtcontrole idsc
                                                WHERE idsc.ins_srtcontrole_key = xcp.ins_srtcontrole_key
                                                  AND idsc.ins_deelsrtcontrole_status IN (5,6)
                                                  AND idsc.ins_scenario_key = 1
                                                  AND idsc.ins_deel_key = xcp.ins_deel_key
                                                  -- Voor mjob moeten de NIET succesvolle inspecties niet worden meegenomen.
                                                  -- Voor periodieke inspectie laten we het vooralsnog hetzelfde.
                                                  AND idsc.ins_deelsrtcontrole_key = (SELECT MAX(ins_deelsrtcontrole_key)
                                                                                        FROM (SELECT idsc2.ins_deelsrtcontrole_key
                                                                                                   , idsc2.ins_deel_key
                                                                                                   , idsc2.ins_srtcontrole_key
                                                                                                   , 0 ctr_ismjob
                                                                                                FROM ins_deelsrtcontrole idsc2
                                                                                               WHERE idsc2.ins_deelsrtcontrole_status IN (5,6)
                                                                                                 AND idsc2.ins_scenario_key = 1
                                                                                              UNION
                                                                                              SELECT idsc2.ins_deelsrtcontrole_key
                                                                                                   , idsc2.ins_deel_key
                                                                                                   , idsc2.ins_srtcontrole_key
                                                                                                   , 1 ctr_ismjob
                                                                                                FROM ins_deelsrtcontrole idsc2,
                                                                                                     ins_controlemode cm
                                                                                               WHERE idsc2.ins_controlemode_key = cm.ins_controlemode_key
                                                                                                 AND idsc2.ins_deelsrtcontrole_status IN (5,6)
                                                                                                 AND idsc2.ins_scenario_key = 1
                                                                                                 AND cm.ins_controlemode_success = 1) aa
                                                                                       WHERE aa.ins_deel_key = xcp.ins_deel_key
                                                                                         AND aa.ins_srtcontrole_key = xcp.ins_srtcontrole_key
                                                                                         AND aa.ctr_ismjob = xcp.ctr_ismjob)),
                                              xcp.ins_srtcontroledl_xcp_startdat,
                                              xcp.ins_deel_aanmaak),
                                   xcp.ins_srtcontrole_mode,
                                   xcp.ins_srtcontrole_eenheid,
                                   xcp.ins_srtcontrole_periode,
                                   xcp.ins_srtcontrole_bits,
                                   p_steps,
                                   xcp.ctr_ismjob,
                                   pCalendarId
                                  ) inspectie_next
           INTO inspdate
           FROM ins_v_defined_inspect_xcp xcp
          WHERE xcp.ins_deel_key = p_insdeel
            AND xcp.ins_srtcontrole_key = p_srtcontrole
            AND xcp.ins_scenario_key = scenario;

        RETURN inspdate;
    END;

    -- Bereken hoeveel controlestappen het is naar de eerste controle datum in de toekomst (standaard) of na de meegegeven datum.
    FUNCTION nextcyclusdatesteps (p_date      IN DATE,
                                  p_mode      IN NUMBER,
                                  p_eenheid   IN NUMBER,
                                  p_periode   IN NUMBER,
                                  p_bits      IN NUMBER,
                                  p_steps     IN NUMBER DEFAULT 1,     -- Standaard beginnen te tellen bij 1.
                                  p_enddate   IN DATE DEFAULT SYSDATE,
                                  pCalendarId IN VARCHAR2 DEFAULT 'Default')
        RETURN NUMBER
    IS
        fromdate   DATE;
        fromdow    NUMBER;
        fromdom    NUMBER;
        inspdate   DATE;
        enddate    DATE;
    BEGIN
        IF p_periode = 0
        THEN
          RETURN NULL;
        END IF;
        -- Calculate next cyclus date
        inspdate := calcnextcyclusdate (p_date, p_mode, p_eenheid, p_periode, p_bits, pCalendarId);
        IF ((p_eenheid = 0 AND TRUNC(inspdate, 'HH24') >= TRUNC(p_enddate, 'HH24')) OR
            (p_eenheid > 0 AND TRUNC(inspdate, 'DDD') >= TRUNC(p_enddate, 'DDD')))
        THEN
            RETURN p_steps;
        ELSE
            RETURN nextcyclusdatesteps (inspdate, p_mode, p_eenheid, p_periode, p_bits, p_steps + 1, p_enddate, pCalendarId);
        END IF;
    END;

   PROCEDURE createlikebookmark (pxmlnode IN VARCHAR2,
                                 prefkey  IN NUMBER)
   IS
       expiredays   NUMBER (10);
       likerkey     NUMBER(10);
       bookmark_id  fac_bookmark.fac_bookmark_id%TYPE;
    BEGIN
       CASE pxmlnode
            WHEN 'melding'
            THEN
                expiredays := fac.safe_To_Number (fac.getsetting ('fac_mld_deeplike_expire'));
                SELECT   prs_perslid_key_voor
                  INTO   likerkey
                  FROM   mld_melding
                 WHERE   mld_melding_key = prefkey;
            WHEN 'bestelling'
            THEN
                SELECT   prs_perslid_key_voor
                  INTO   likerkey
                  FROM   bes_bestelling
                 WHERE   bes_bestelling_key = prefkey;
            WHEN 'reservering'
            THEN
                SELECT   res_rsv_ruimte_host_key
                  INTO   likerkey
                  FROM   res_rsv_ruimte
                 WHERE   res_rsv_ruimte_key = prefkey;
       END CASE;

       IF expiredays > 0
       THEN
           BEGIN
              -- Als onvoorzien de bookmark er al was geven we weer iets meer tijd
              -- We laten hem wel staan omdat er mogelijk al een bon bestaat die hem gebruikt.
              UPDATE fac_bookmark
                 SET fac_bookmark_expire = fac.datumtijdplusuitvoertijd (SYSDATE, expiredays, 'DAGEN')
                WHERE fac_bookmark_xmlnode = pxmlnode
                  AND fac_bookmark_refkey = prefkey
                  AND fac_bookmark_naam = 'faclikedeeplink';
              IF sql%rowcount = 0
              THEN
                  INSERT INTO fac_bookmark (fac_bookmark_id,
                                        fac_bookmark_naam,
                                        fac_bookmark_path,
                                        fac_bookmark_query,
                                        fac_bookmark_xmlnode,
                                        fac_bookmark_refkey,
                                        fac_bookmark_expire,
                                        prs_perslid_key_auth)
                  VALUES (DBMS_RANDOM.string ('a', 16),
                          'faclikedeeplink',
                          'appl/fac/fac_like.asp',
                          'node=' || pxmlnode || '&key=' || prefkey, -- is straks beschermd met HMAC
                          pxmlnode,
                          prefkey,
                          fac.datumtijdplusuitvoertijd (SYSDATE, expiredays, 'DAGEN'),
                          likerkey);
              END IF;
           END;
       END IF;
    END;

   PROCEDURE sequence_nextval(p_seqname IN VARCHAR2, p_initval IN NUMBER DEFAULT 1, p_initexpire IN DATE DEFAULT NULL)
   IS
     v_nextval  NUMBER;
     v_expire   DATE;
   BEGIN
      SELECT fac_sequence_lastval
           , fac_sequence_expire
        INTO v_nextval
           , v_expire
        FROM fac_sequence
       WHERE fac_sequence_name = p_seqname;
      --
      IF ((p_initexpire IS NOT NULL) AND ( v_expire < p_initexpire))
      THEN
         -- Reset teller wegens overschrijden van expire_date.
         v_nextval := p_initval;
      ELSE
         -- Alleen teller ophogen.
         v_nextval := v_nextval + 1;
      END IF;
      UPDATE fac_sequence
         SET fac_sequence_lastval = v_nextval
           , fac_sequence_expire = p_initexpire
       WHERE fac_sequence_name = p_seqname;
   EXCEPTION
      WHEN NO_DATA_FOUND
      THEN
         INSERT INTO fac_sequence (fac_sequence_name,
                                   fac_sequence_lastval,
                                   fac_sequence_expire)
              VALUES (p_seqname,
                      p_initval,
                      p_initexpire);
   END;

   PROCEDURE remove_tracking(p_xmlnode IN VARCHAR2, p_refkey IN NUMBER)
   IS
   BEGIN
       DELETE FROM fac_tracking
        WHERE fac_tracking_refkey = p_refkey
          AND fac_srtnotificatie_key IN
              (SELECT fac_srtnotificatie_key
                 FROM fac_srtnotificatie
                WHERE fac_srtnotificatie_xmlnode = p_xmlnode
              );
   END;

   -- Geeft true als het bestelde bedrag boven de profiel-limiet van prs_key komt
   -- en dus goedkeuring vereist zal zijn.
   FUNCTION exceeds_profiel (p_bestelbedrag IN NUMBER, p_prs_key IN NUMBER, p_disc_key IN NUMBER)
      RETURN BOOLEAN
   AS
      l_limiet                   NUMBER;
   BEGIN
      SELECT prs.getprofiellimiet(p_prs_key, p_disc_key) limiet
        INTO l_limiet
        FROM DUAL;

      IF l_limiet IS NOT NULL
      THEN
         RETURN l_limiet < p_bestelbedrag;
      ELSE
         RETURN TRUE; -- Geen profiel is altijd te hoog.
      END IF;
   END;

   -- Geeft true als het bestelde bedrag boven de PGB-limiet van prs_key uit komt
   -- en dus (eventueel) goedkeuring vereist zal zijn.
   FUNCTION exceeds_pgb (p_opdrachtbedrag IN NUMBER, p_prs_key IN NUMBER, p_disc_key IN NUMBER, disc_params_pgb IN NUMBER)
      RETURN BOOLEAN
   AS
      l_limiet                   NUMBER;
   BEGIN
      IF disc_params_pgb = 1 OR disc_params_pgb = 3
      THEN
         SELECT prs.getprofiellimiet(p_prs_key, p_disc_key)
           INTO l_limiet
           FROM DUAL;
         IF l_limiet IS NOT NULL
         THEN
            RETURN l_limiet < (p_opdrachtbedrag + bes.bes_besteed_budget_pgb(p_prs_key, p_disc_key) + mld.mld_besteed_budget_pgb(p_prs_key, p_disc_key));
         ELSE
            RETURN TRUE; -- Geen profiel is altijd te hoog.
         END IF;
      ELSE
         -- Geen pgb. Dan kan je ook niet boven de pgb zitten.
         RETURN FALSE;
      END IF;
   END;

   -- Geeft true als het bestelde bedrag boven de AGB-limiet van prs_key uit komt
   -- en dus (eventueel) goedkeuring vereist zal zijn.
   -- Negeer evt bestaand bestelbedrag van bes_key (indien > -1)
   FUNCTION exceeds_agb (p_opdrachtbedrag IN NUMBER, p_kp_key IN NUMBER, p_disc_key IN NUMBER, disc_params_pgb IN NUMBER)
      RETURN BOOLEAN
   AS
      l_limiet                   NUMBER;
   BEGIN
      IF disc_params_pgb = 2 OR disc_params_pgb = 3
      THEN
         SELECT prs.getlimietagb(p_kp_key, fac.getsetting('prs_approvemethod'), p_disc_key) limiet
           INTO l_limiet
           FROM DUAL;
         IF l_limiet IS NOT NULL
         THEN
            RETURN l_limiet < (p_opdrachtbedrag + bes.bes_besteed_budget_agb(p_kp_key) + mld.mld_besteed_budget_agb(p_kp_key));
         ELSE
            RETURN TRUE; -- Geen profiel is altijd te hoog.
         END IF;
      ELSE
         -- Geen agb. Dan kan je ook niet boven de agb zitten.
         RETURN FALSE;
      END IF;
   END;

   PROCEDURE fac_cleanup(delayhours NUMBER DEFAULT 24)
   AS
      v_cleanup_date  DATE;
   BEGIN
      SELECT fac_version_cleanup_date
        INTO v_cleanup_date
        FROM fac_version;

      IF v_cleanup_date IS NULL OR SYSDATE > (v_cleanup_date + delayhours / 24)
      THEN
         BEGIN
            DELETE FROM fac_session WHERE fac_session_expire < SYSDATE - 2;
            DELETE FROM aut_client_perslid WHERE aut_client_perslid_login < SYSDATE - 60;
            DELETE FROM fac_bookmark WHERE fac_bookmark_expire < SYSDATE - 2;
            DELETE FROM web_user_messages WHERE web_user_mess_action_datum < SYSDATE - 60;
            DELETE fac_result WHERE fac_result_aanmaak < SYSDATE - 7;

            DELETE fac_rapport WHERE fac_rapport_datum < SYSDATE - 90;
            DELETE fac_audit WHERE fac_audit_datum < SYSDATE - 3 * 365;
            DELETE fac_auditfail WHERE fac_auditfail_datum < SYSDATE - 3 * 365;
            DELETE fac_gui_counter WHERE fac_gui_counter_date < SYSDATE - 3 * 365;
            DELETE fac_gui_counter
             WHERE fac_gui_counter_date < (SYSDATE - 100)
               AND fac_gui_counter_group <> 'audit' -- die mag je echt niet zo snel opruimen
               AND prs_perslid_key IS NOT NULL;
            DELETE fac_import WHERE fac_import_datum_gelezen < SYSDATE - 90;
            DELETE imp_log
             WHERE fac_import_key IS NULL
               AND imp_log_datum < SYSDATE - 90
               AND imp_log_applicatie <> '$PURGE$'; --$PURGE$ wil je echt wel langer bewaren
            DELETE fac_kenmwaarden WHERE fac_kenmwaarden_aanmaak < SYSDATE - 7;
            DELETE fac_gebruikersgroep WHERE fac_gebruikersgroep_vervaldatum < TRUNC(SYSDATE);

            COMMIT;

            -- Heel soms voegt iemand een bijlage toe maar slaat het uiteindelijk niet goed op.
            -- De fac_bijlagen_disk_directory wijst nog (relatief) naar de TEMP folder
            -- die UTILS\gen_scheduler\gen_cleanup.js op een andere manier opruimt
            DELETE FROM fac_bijlagen
             WHERE fac_bijlagen_refkey = -1
               AND fac_bijlagen_key = fac_bijlagen_root_key -- safety
               AND fac_bijlagen_aanmaak < TRUNC (SYSDATE);

            -- Wissen oude (gisteren) popups van Putorders.
            UPDATE fac_notificatie n
               SET n.fac_notificatie_status = BITAND(n.fac_notificatie_status, 255 - 8)
             WHERE n.fac_notificatie_queue = 'DEFAULT'
               AND BITAND (n.fac_notificatie_status, 8) = 8
               AND n.fac_notificatie_datum < TRUNC(SYSDATE);

            DELETE FROM fac_notificatie
               WHERE fac_notificatie_queue = 'EXCHANGE'
                  AND fac_notificatie_datum < TRUNC(SYSDATE);

            -- nu records deleten die niets meer doen
            fac.cleannotifications();

            -- Bij impliciete werkplekken horen er geen werkplekken te zijn zonder bezetting
            -- Er waren echter (cust)scripts rond die wel de bezetting maar niet de werkplek zelf verwijderen
            -- Dat met gepaste tegenzin gewoon automatisch doen.
            IF fac.getsetting ('prs_werkplek_implicit') = '1'  --impliciet
            THEN
               DELETE
                  FROM prs_werkplek pw
                  WHERE prs_werkplek_type = 0 -- van de flex afblijven voor de zekerheid
                    AND NOT EXISTS
                       (SELECT 1
                          FROM prs_perslidwerkplek ppw
                          WHERE ppw.prs_werkplek_key = pw.prs_werkplek_key);
            END IF;

            -- Import opschonen.
            DECLARE
                v_count         NUMBER;
            BEGIN
                v_count := 0;
                FOR c_impapp IN (SELECT fac_import_app_key,
                                        (SELECT MIN (fac_import_key)
                                           FROM (  SELECT fac_import_key
                                                     FROM fac_import fi2
                                                    WHERE fi2.fac_import_app_key = f.fac_import_app_key
                                                     ORDER BY fac_import_key DESC)
                                          WHERE ROWNUM <= 10) last10key,
                                        (SELECT MAX(fac_import_key)
                                           FROM fac_import fi2
                                          WHERE fi2.fac_import_app_key = f.fac_import_app_key
                                            AND fac_import_datum_gelezen <= TRUNC(SYSDATE - 1)) lastdaykey
                                   FROM fac_import_app f)
                LOOP
                    FOR c_imp IN (SELECT fac_import_key
                                    FROM fac_import f
                                   WHERE f.fac_import_app_key = c_impapp.fac_import_app_key
                                     AND fac_import_key < c_impapp.lastdaykey -- altijd minstens een dag laten staan
                                     AND fac_import_key < c_impapp.last10key) -- laatste 10 blijven altijd
                    LOOP
                        DELETE FROM fac_import
                              WHERE fac_import_key = c_imp.fac_import_key; -- cascadeert fac_imp_file en imp_log vanzelf mee
                        IF v_count >= 1000
                        THEN
                            COMMIT;
                            v_count := 0;
                        ELSE
                            v_count := v_count + 1;
                        END IF;
                    END LOOP;
                END LOOP;
            END;

            UPDATE fac_version
               SET fac_version_cleanup_date = SYSDATE;
         END;
      END IF;
   END;

   FUNCTION getnoteoftracking (p_tracking_key IN NUMBER)
      RETURN VARCHAR2
   IS
      l_srtnotificatie_xmlnode   fac_srtnotificatie.fac_srtnotificatie_xmlnode%TYPE;
      l_note_oms                 mld_opdr_note.mld_opdr_note_omschrijving%TYPE;
   BEGIN
      IF (p_tracking_key IS NOT NULL)
      THEN
         BEGIN
               -- determine the node of this tracking
               SELECT st.fac_srtnotificatie_xmlnode
               INTO l_srtnotificatie_xmlnode
               FROM fac_tracking t, fac_srtnotificatie st
               WHERE     t.fac_srtnotificatie_key = st.fac_srtnotificatie_key
                     AND t.fac_tracking_key = p_tracking_key;

               IF l_srtnotificatie_xmlnode = 'opdracht'
               THEN
                  SELECT mld_opdr_note_omschrijving
                     INTO l_note_oms
                     FROM mld_opdr_note n
                  WHERE n.fac_tracking_key = p_tracking_key;
               ELSIF l_srtnotificatie_xmlnode = 'melding'
               THEN
                  SELECT mld_melding_note_omschrijving
                     INTO l_note_oms
                     FROM mld_melding_note n
                  WHERE n.fac_tracking_key = p_tracking_key;
               END IF;

               RETURN l_note_oms;
         EXCEPTION
               WHEN NO_DATA_FOUND
               THEN
                  RETURN NULL;
         END;
      ELSE
         RETURN NULL;
      END IF;
   END;

   FUNCTION getnoteofnotification (p_notificatie_key IN NUMBER)
      RETURN VARCHAR2
   IS
      l_extratracking_key   fac_notificatie.fac_notificatie_extrakey%TYPE;
   BEGIN
      IF (p_notificatie_key IS NOT NULL)
      THEN
         BEGIN
               -- determine the tracking of this notification
               -- which is optional and can be found in extrakey
               SELECT fac_notificatie_extrakey
               INTO l_extratracking_key
               FROM fac_notificatie q
               WHERE q.fac_notificatie_key = p_notificatie_key;

               RETURN getnoteoftracking (l_extratracking_key);
         EXCEPTION
               WHEN NO_DATA_FOUND
               THEN
                  RETURN NULL;
         END;
      ELSE
         RETURN NULL;
      END IF;
   END;

END fac;
/

REGISTERRUN('$Id$')
