#ifdef FIN
/*  FIN_PAC.SRC
 * $Revision$
 * $Id$
 */

/* Formatted on 16-7-2010 17:57:48 (QP5 v5.136.908.31019) */
CREATE OR REPLACE PACKAGE fin
AS
   PROCEDURE setfactuurstatus (pfactuurkey IN NUMBER, pstatus IN NUMBER, puserkey IN NUMBER);

   FUNCTION tolerantiematch (pdisckey IN NUMBER, pftype IN VARCHAR2, pfactbedrag IN NUMBER, pbestelbedrag IN NUMBER)
      RETURN NUMBER;

   FUNCTION matchfactuur (pfactuur_key IN NUMBER)
      RETURN NUMBER;

   PROCEDURE matchandsetfactuur (pfactuur_key IN NUMBER, prejecttoo IN BOOLEAN);

   PROCEDURE autoapprovefactuur (pfactuur_key IN NUMBER);

   FUNCTION getdefaultbtwtabelkey
       RETURN NUMBER;

   FUNCTION getbtwtabelkey(p_bkey IN NUMBER, p_ckey IN NUMBER, p_okey IN NUMBER, p_otherwise_default IN BOOLEAN)
       RETURN NUMBER;

   FUNCTION getbtwtabelwaardekey_perc (pperc IN NUMBER, pbtwtabelkey IN NUMBER, pbtwverlegd IN NUMBER)
      RETURN NUMBER;

   FUNCTION getbtwtabelwaardekey(pbedragexc IN NUMBER, pbtwbedrag IN NUMBER, pbtwtabelkey IN NUMBER, pbtwverlegd IN NUMBER)
       RETURN NUMBER;

   FUNCTION getbtw_bedrag_percentage (pbedragexc IN NUMBER, pperc IN NUMBER)
      RETURN NUMBER;

   FUNCTION getbtw_bedrag (pbedragexc IN NUMBER, pbtwtabelwaardekey IN NUMBER)
      RETURN NUMBER;

   FUNCTION getfactuurkostenplaats (pfactuur_key IN NUMBER)
      RETURN NUMBER;

   FUNCTION getfiatteur (pfactuur_key IN NUMBER,
                         pexcludekey  IN NUMBER DEFAULT NULL)
      RETURN NUMBER;

  FUNCTION sprintf (ps IN VARCHAR2 , p_factuur_key IN NUMBER) RETURN VARCHAR2;
END fin;
/

/* Formatted on 12-7-2011 14:49:08 (QP5 v5.136.908.31019) */
CREATE OR REPLACE PACKAGE BODY fin
AS
   -- Geef meldingkey, gewenste status en userkey, en de status wortdt
   -- gezet, waarbij zonodig (indien verandering) een trackrecord wordt gemaakt
   -- Als de status niet wijzigt, wordt ook geen trackrecord gemaakt
   -- ZIE schema StateDiagramOpdrachten.vsd
   PROCEDURE setfactuurstatus (pfactuurkey IN NUMBER, pstatus IN NUMBER, puserkey IN NUMBER)
   AS
      currentstatus fin_factuur.fin_factuur_statuses_key%TYPE;
      newstatus fin_factuur.fin_factuur_statuses_key%TYPE;
      eventcode fac_srtnotificatie.fac_srtnotificatie_code%TYPE;
   BEGIN
      SELECT   fin_factuur_statuses_key
        INTO   currentstatus
        FROM   fin_factuur
       WHERE   fin_factuur_key = pfactuurkey;

      CASE pstatus
         WHEN 1                                                                                             -- Afgewezen
         THEN
            IF currentstatus IS NULL OR currentstatus = 2 OR currentstatus = 5
            THEN
               newstatus := pstatus;
               IF currentstatus = 5
               THEN
                 eventcode := 'FINGNO';
               ELSE
                 eventcode := 'FINFNO';
               END IF;
            END IF;
         WHEN 2                                                                                             -- Ingevoerd
         THEN
            IF currentstatus IS NULL
            THEN
               newstatus := pstatus;
               eventcode := 'FINNEW';
            ELSIF currentstatus = 1 OR currentstatus = 5
            THEN
               newstatus := pstatus;
               eventcode := 'FINFUN';
            ELSIF currentstatus = 6
            THEN
               newstatus := pstatus;
               eventcode := 'FINGUN';
            END IF;
         WHEN 5                                                                                             -- Ter goedkeuring
         THEN
            IF currentstatus = 2
            THEN
               newstatus := pstatus;
               eventcode := 'FINFOK';
            END IF;
         WHEN 6                                                                                             -- Verwerkt
         THEN
            IF currentstatus = 2 OR currentstatus = 5
            THEN
               newstatus := pstatus;
               eventcode := 'FINGOE';
            END IF;
         WHEN 7                                                                                             -- Uitgegeven
         THEN
            IF currentstatus = 6
            THEN
               newstatus := pstatus;
               eventcode := 'FINVER';
            END IF;
         ELSE
            -- Invalid statuschange or no change
            newstatus := NULL;
      END CASE;

      IF newstatus IS NOT NULL
      THEN
         -- vooralsnog lopen de notificaties 1-op-1 met de tracking
         -- noticode := eventcode;
         UPDATE   fin_factuur
            SET   fin_factuur_statuses_key = newstatus
          WHERE   fin_factuur_key = pfactuurkey;

         -- We know that trackaction doesnt do tracking if eventcode is null
         fac.trackaction (eventcode, pfactuurkey, puserkey, NULL, NULL);
      END IF;
   END;

   -- Gegeven de 2 referentiekeys, lever het type 'B', 'O' of 'C' op
   -- Interne functie in de package vooralsnog
   FUNCTION getreferentietype (po_key IN NUMBER, pc_key IN NUMBER, pb_key IN NUMBER)
      RETURN VARCHAR2
   IS
      rtype VARCHAR2 (1) := '?';
   BEGIN
      IF po_key IS NOT NULL
      THEN
         rtype := 'O';
         DBMS_OUTPUT.put_line ('opdrachtkey: ' || po_key);
      ELSIF pc_key IS NOT NULL
      THEN
         rtype := 'C';
         DBMS_OUTPUT.put_line ('contractkey: ' || pc_key);
      ELSIF pb_key IS NOT NULL
      THEN
         rtype := 'B';
         DBMS_OUTPUT.put_line ('besteloprachtkey: ' || pb_key);
      END IF;

      RETURN rtype;
   END;

   -- Geef 2 of 3 terug voor tweeweg of drieweg factuurmatchingsmode
   FUNCTION getfactmatchmode (pdisckey IN NUMBER, pftype IN VARCHAR2)
      RETURN NUMBER
   IS
      mmode VARCHAR2 (1) := '?';
   BEGIN
      CASE
         WHEN pftype = 'B'
         THEN
            SELECT   p.bes_disc_params_factmatchmode
              INTO   mmode
              FROM   bes_disc_params p
             WHERE   p.bes_ins_discipline_key = pdisckey;
         WHEN pftype = 'C'
         THEN
            mmode := 2;
         WHEN pftype = 'O'
         THEN
            SELECT   p.mld_disc_params_factmatchmode
              INTO   mmode
              FROM   mld_disc_params p
             WHERE   p.mld_ins_discipline_key = pdisckey;
         ELSE
            mmode := 2;
      END CASE;

      IF mmode IS NULL
      THEN
         mmode := 2;                                                                                          -- default
      END IF;

      DBMS_OUTPUT.put_line ('>matchm(eth)ode: ' || mmode);
      RETURN mmode;
   END;

   -- Functie kan nu ook van buitenaf worden aangeroepen, en dan is BOOLEAN niet meer mogelijk
   -- true=match=1, false=geen match=0
   FUNCTION tolerantiematch (pdisckey IN NUMBER, pftype IN VARCHAR2, pfactbedrag IN NUMBER, pbestelbedrag IN NUMBER)
      RETURN NUMBER
   IS
      discpct NUMBER;
      discmarge NUMBER;
      upperlimit NUMBER;
      lowerlimit NUMBER;
      match_lowerlimit fac_setting.fac_setting_default%TYPE;
      franchise NUMBER;
   BEGIN
      CASE
         WHEN pftype = 'B'
         THEN
            SELECT   p.bes_disc_params_factuurpct, p.bes_disc_params_factuurmarge, p.bes_disc_params_factuurgrens
              INTO   discpct, discmarge, franchise
              FROM   bes_disc_params p
             WHERE   p.bes_ins_discipline_key = pdisckey;
         WHEN pftype = 'C'
         THEN
            SELECT   p.cnt_disc_params_factuurpct, p.cnt_disc_params_factuurmarge, p.cnt_disc_params_factuurgrens
              INTO   discpct, discmarge, franchise
              FROM   cnt_disc_params p
             WHERE   p.cnt_ins_discipline_key = pdisckey;
         WHEN pftype = 'O'
         THEN
            SELECT   p.mld_disc_params_factuurpct, p.mld_disc_params_factuurmarge, p.mld_disc_params_factuurgrens
              INTO   discpct, discmarge, franchise
              FROM   mld_disc_params p
             WHERE   p.mld_ins_discipline_key = pdisckey;
         ELSE
            discpct := 0;
            discmarge := 0;
      END CASE;

      IF franchise IS NOT NULL AND pfactbedrag <= franchise
      THEN
         RETURN 1;
      -- klaar
      END IF;

      -- TODO Nagaan of je hiervoor toch nog iets met eerder gefactureerd moet?
      -- Je wilt het gedrag alsof alle deelfacturen in 1 keer waren gestuurd
      -- maar dat regelt de aanroeper al. Wat doet dit bij CNT?
      IF pbestelbedrag IS NULL                                                              -- niks van te zeggen verder
      THEN
         RETURN 0;
      END IF;

      IF discpct IS NULL AND discmarge IS NULL
      THEN
         upperlimit := pbestelbedrag;
         lowerlimit := pbestelbedrag;
      ELSIF discpct IS NULL
      THEN
         upperlimit := pbestelbedrag + discmarge;
         lowerlimit := pbestelbedrag - discmarge;
      ELSE
         upperlimit := pbestelbedrag * (100 + discpct) / 100;
         lowerlimit := pbestelbedrag * (100 - discpct) / 100;
      END IF;

      match_lowerlimit := fac.getSetting('fin_match_lowerlimit');

      DBMS_OUTPUT.put_line ('discipline: ' || pdisckey);
      DBMS_OUTPUT.put_line ('tolerantie%: ' || discpct);
      DBMS_OUTPUT.put_line ('tolerantie-abs: ' || discmarge);
      DBMS_OUTPUT.put_line ('bestel/opdracht/contactbedrag: ' || pbestelbedrag);
      DBMS_OUTPUT.put_line ('bovengrens: ' || upperlimit);
      IF match_lowerlimit = 1
      THEN
        DBMS_OUTPUT.put_line ('ondergrens: ' || lowerlimit);
      END IF;
      DBMS_OUTPUT.put_line ('vrijstelling: ' || franchise);
      DBMS_OUTPUT.put_line ('factuurbedrag: ' || pfactbedrag);

      IF pfactbedrag <= upperlimit
      THEN
         -- niet te hoog
         IF match_lowerlimit = 1 AND pfactbedrag < lowerlimit
         THEN
            -- ondergrens doet er toe en het is te laag = nok
            RETURN 0;
         ELSE
            -- ondergrens doet er niet toe of het is hoger dan de ondergrens = ok
            RETURN 1;
         END IF;
      ELSE
         -- te hoog = nok
         RETURN 0;
      END IF;
   END;

   FUNCTION matchfactuur (pfactuur_key IN NUMBER)
      RETURN NUMBER
   IS
      -- controleert deze nieuwe factuur, en levert een oordeel op:
      -- -1 factuur niet (automatisch) goed
      --  0 niks van te zeggen of reeds goed-/afgekeurd/verwerkt
      --  1 factuur okay
      ftype                          VARCHAR2 (1);
      c_key                          fin_factuur.cnt_contract_key%TYPE;
      b_key                          fin_factuur.bes_bestelopdr_key%TYPE;
      o_key                          fin_factuur.mld_opdr_key%TYPE;
      factuurstatus                  fin_factuur.fin_factuur_statuses_key%TYPE;
      factuurdatum                   fin_factuur.fin_factuur_datum%TYPE;
      factuurboekmaand               fin_factuur.fin_factuur_boekmaand%TYPE;
      discipline                     ins_tab_discipline.ins_discipline_key%TYPE;
      factmatchmode                  mld_disc_params.mld_disc_params_factmatchmode%TYPE;
      korting                        bes_bestelopdr.bes_bestelopdr_korting%TYPE;
      levkosten                      bes_bestelopdr.bes_bestelopdr_levkosten%TYPE;
      contracttermijnkosten          cnt_contract.cnt_contract_termijnkosten%TYPE;
      factuurschemabedrag            cnt_factuurschema.cnt_factuurschema_bedrag%TYPE;
      contractkosten                 cnt_contract.cnt_contract_kosten%TYPE;
      opdrachtkosten                 mld_opdr.mld_opdr_kosten%TYPE;
      matchtype                      mld_typeopdr.mld_typeopdr_matchtype%TYPE;
      factuurbedrag                  NUMBER (12, 2);
      bestelbedrag                   NUMBER (12, 2);  -- van de opdracht waar deze factuur over gaat
      totaalgefactureerd             NUMBER (12, 2); -- met deze factuur erbij eerder gefactureerd van dezelfde B(R)OC
      totaaltermijngefactureerd      NUMBER (12, 2); -- met deze factuur erbij eerder gefactureerd van dezelfde C igv factuurschema-gebruik
      totaaltermijngefactureerdbtw   NUMBER (12, 2); -- met deze factuur erbij eerder gefactureerd van dezelfde C igv factuurschema-gebruik
      totaalgefactureerdbtw          NUMBER (12, 2);
      opdrstatus                     mld_opdr.mld_statusopdr_key%TYPE;
      retval                         NUMBER := 0;
      inclbtw                        NUMBER := 0;
   -- Bepaal te tolerantie van de vakgroep
   -- waarschijnlijk in procenten of zo?
   -- of geven we de factuurwaarde mee en leveren we true/false op?
   BEGIN
      --------------------------------------------
      ------------ START MAIN ROUTINE ------------
      --------------------------------------------
      ---
      --- Over wat voor soort factuur hebben we het?
      ---
      IF pfactuur_key IS NULL
      THEN
         DBMS_OUTPUT.put_line ('STOP, factuurkey: NULL');
         RETURN 0;
      END IF;

      DBMS_OUTPUT.put_line ('\nfactuurkey: ' || pfactuur_key);

      SELECT mld_opdr_key,
             cnt_contract_key,
             bes_bestelopdr_key,
             fin_factuur_totaal,
             fin_factuur_statuses_key,
             fin_factuur_datum,
             fin_factuur_boekmaand
        INTO o_key,
             c_key,
             b_key,
             factuurbedrag,
             factuurstatus,
             factuurdatum,
             factuurboekmaand
        FROM fin_factuur
       WHERE fin_factuur_key = pfactuur_key;

      IF factuurstatus <> 2                                      -- is al verwerkt, matching zinloos
      THEN
         DBMS_OUTPUT.put_line ('STOP, factuurstatus: ' || factuurstatus);
         RETURN 0;
      END IF;

      ftype := fin.getreferentietype (o_key, c_key, b_key);
      DBMS_OUTPUT.put_line ('>opdrachttype: ' || ftype);

      --
      -- Ga maar eens matchen
      --
      CASE
         --
         -- BESTELLING
         --
         WHEN ftype = 'B'
         THEN
            -- Factuur ok als binnen de toleranties van de bestelcatalogus
            -- en het totaal met de eerder bestellingfacturen ook,
            -- waarbij afhankelijk van 2-weg/3-weg wordt geteld.
            -- Dat moeten we dus eerst bepalen
            SELECT MAX (isg.ins_discipline_key)
              INTO discipline
              FROM bes_bestelopdr_item boi,
                   bes_bestelling_item bbi,
                   bes_srtdeel isd,
                   bes_srtgroep isg
             WHERE     isd.bes_srtgroep_key = isg.bes_srtgroep_key
                   AND bbi.bes_srtdeel_key = isd.bes_srtdeel_key
                   AND boi.bes_bestelopdr_item_key = bbi.bes_bestelopdr_item_key
                   AND boi.bes_bestelopdr_key = b_key;

            factmatchmode := fin.getfactmatchmode (discipline, 'B');

            -- Zijn de bestelbedragen incl. of excl. BTW.
            SELECT MAX( (SELECT ks.prs_kostensoort_btw
                           FROM prs_kostensoort ks
                          WHERE ks.prs_kostensoort_key =
                                   COALESCE (bsg.prs_kostensoort_key, bd.prs_kostensoort_key)))
              INTO inclbtw
              FROM bes_discipline bd, bes_srtgroep bsg
             WHERE bd.ins_discipline_key = bsg.ins_discipline_key
                   AND bd.ins_discipline_key = discipline;

            -- Bereken dan nu de voor de matching relevante bedragen
            -- Voor bestellingen nemen we de INKOOPPRIJS (het is immers een factuur die wij ontvangen)
            -- Maar als die niet gevuld is (ook bv bij punchout), dan de enige andere prijs die we weten
            -- de verkoopprijs
            CASE
               WHEN factmatchmode = 2
               THEN
                  SELECT SUM(boi.bes_bestelopdr_item_aantal
                             * COALESCE (boi.bes_bestelopdr_item_inkprijs,
                                         boi.bes_bestelopdr_item_prijs)),
                         MAX (COALESCE (bo.bes_bestelopdr_korting, 0)),
                         MAX (COALESCE (bo.bes_bestelopdr_levkosten, 0))
                    INTO bestelbedrag, korting, levkosten
                    FROM bes_bestelopdr_item boi, bes_bestelling_item bbi, bes_bestelopdr bo
                   WHERE     boi.bes_bestelopdr_item_key = bbi.bes_bestelopdr_item_key
                         AND bo.bes_bestelopdr_key = boi.bes_bestelopdr_key
                         AND bo.bes_bestelopdr_status NOT IN (1, 3, 8) -- als die maar niet afgewezen/geannuleerd is
                         AND boi.bes_bestelopdr_key = b_key;
               WHEN factmatchmode = 3
               THEN
                  SELECT SUM(boi.bes_bestelopdr_item_aantal
                             * COALESCE (boi.bes_bestelopdr_item_inkprijs,
                                         boi.bes_bestelopdr_item_prijs)),
                         MAX (COALESCE (bo.bes_bestelopdr_korting, 0)),
                         MAX (COALESCE (bo.bes_bestelopdr_levkosten, 0))
                    INTO bestelbedrag, korting, levkosten
                    FROM bes_bestelopdr_item boi, bes_bestelling_item bbi, bes_bestelopdr bo
                   WHERE     boi.bes_bestelopdr_item_key = bbi.bes_bestelopdr_item_key
                         AND bo.bes_bestelopdr_key = boi.bes_bestelopdr_key
                         AND boi.bes_bestelopdr_item_status IN (6, 7) -- alleen de geleverde regels meetellen
                         AND boi.bes_bestelopdr_key = b_key;
            END CASE;

            bestelbedrag := bestelbedrag - korting + levkosten;          -- Bedragen zijn excl. BTW?

            SELECT SUM (fin_factuur_totaal), SUM (fin_factuur_totaal_btw)
              INTO totaalgefactureerd, totaalgefactureerdbtw
              FROM fin_factuur
             WHERE     bes_bestelopdr_key = b_key
                   AND fin_factuur_statuses_key <> 1
                   AND fin_factuur_verwijder IS NULL
                   AND fin_factuur_datum <= factuurdatum;

            IF inclbtw = 1    -- Artikelen zijn al inclusief, dan ook maar factuur inclusief matchen
            THEN
               DBMS_OUTPUT.put_line ('Inclusief BTW');
               totaalgefactureerd := totaalgefactureerd + totaalgefactureerdbtw;
            END IF;

            IF fin.tolerantiematch (discipline,
                                    ftype,
                                    totaalgefactureerd,
                                    bestelbedrag) = 1
            THEN
               retval := 1;
            ELSE
               retval := -1;
            END IF;
         --
         -- CONTRACT
         --
         WHEN ftype = 'C'
         THEN
            -- om te beginnen zit deze factuur per definitie in deze termijn (welke dan ook)
            totaaltermijngefactureerd := factuurbedrag;

            -- Zijn deze contractbedragen incl. of excl. BTW.
            SELECT MAX( (SELECT ks.prs_kostensoort_btw
                           FROM prs_kostensoort ks
                          WHERE ks.prs_kostensoort_key =
                                   COALESCE (c.prs_kostensoort_key, cd.prs_kostensoort_key)))
              INTO inclbtw
              FROM cnt_discipline cd, cnt_contract c
             WHERE cd.ins_discipline_key = c.ins_discipline_key AND c.cnt_contract_key = c_key;

            -- Controle op termijn bedrag en totaal, je kunt discussieren of _verwijder moet worden gecontroleerd.
            BEGIN
               SELECT c.cnt_contract_termijnkosten, c.cnt_contract_kosten, c.ins_discipline_key
                 INTO contracttermijnkosten, contractkosten, discipline
                 FROM cnt_contract c
                WHERE cnt_contract_status = 0                                              -- actief
                                             AND cnt_contract_key = c_key;
            EXCEPTION
               WHEN NO_DATA_FOUND
               THEN
                  DBMS_OUTPUT.put_line ('Bijbehorend contract(status?) is ongeldig: ' || c_key);
                  RETURN -1;
            END;

            -- Er kan ook gebruik gemaakt worden van een factuurschema, dan moet het termijnbedrag anders bepaald worden
            BEGIN
               SELECT s.cnt_factuurschema_bedrag
                 INTO factuurschemabedrag
                 FROM cnt_factuurschema s
                WHERE s.cnt_factuurschema_boekmaand = factuurboekmaand
                      AND s.cnt_contract_key = c_key;
            -- Ok, er is dus een factuurschema-bedrag voor deze periode: dan controleren:
            EXCEPTION
               WHEN NO_DATA_FOUND
               THEN
                  DBMS_OUTPUT.put_line(   'Geen factuurschema van toepassing voor contract '
                                       || c_key
                                       || ' in periode '
                                       || factuurboekmaand);
            -- factuurschemabedrag is nu null
            END;

            -- waren er niet al facturen voor dezelfde periode en komt het totaal er met deze dan niet overheen?
            SELECT SUM (fin_factuur_totaal), SUM (fin_factuur_totaal_btw)
              INTO totaaltermijngefactureerd, totaaltermijngefactureerdbtw
              FROM fin_factuur
             WHERE     cnt_contract_key = c_key
                   AND fin_factuur_statuses_key <> 1
                   AND fin_factuur_verwijder IS NULL
                   AND fin_factuur_datum <= factuurdatum
                   AND fin_factuur_boekmaand = factuurboekmaand;

            -- totaaltermijngefactureerd(btw) is dus voor deze periode inclusief deze nieuwe, dus
            -- dus mogelijk hoger dan deze ene factuur

            -- We doen er toch niks mee: factmatchmode := fin.getfactmatchmode (discipline, 'C');

            -- hoeveel is er met deze factuur erbij totaal voor dit contract gefactureerd
            SELECT SUM (fin_factuur_totaal), SUM (fin_factuur_totaal_btw)
              INTO totaalgefactureerd, totaalgefactureerdbtw
              FROM fin_factuur
             WHERE     cnt_contract_key = c_key
                   AND fin_factuur_statuses_key <> 1
                   AND fin_factuur_verwijder IS NULL
                   AND fin_factuur_datum <= factuurdatum;

            DBMS_OUTPUT.put_line ('Totaal gefactureerd: ' || totaalgefactureerd);
            DBMS_OUTPUT.put_line ('Totaal gefactureerd periode: ' || totaaltermijngefactureerd);

            IF inclbtw = 1 -- Contractbedragen zijn al inclusief, dan ook  factuur inclusief matchen
            THEN
               DBMS_OUTPUT.put_line ('Inclusief BTW');
               totaalgefactureerd := totaalgefactureerd + totaalgefactureerdbtw;    -- inclusief BTW
               totaaltermijngefactureerd := totaaltermijngefactureerd + totaaltermijngefactureerdbtw; -- deze periode(maand) incl. BTW
               DBMS_OUTPUT.put_line ('Totaal gefactureerd: ' || totaalgefactureerd);
               DBMS_OUTPUT.put_line ('Totaal gefactureerd periode: ' || totaaltermijngefactureerd);
            END IF;

            IF totaalgefactureerd <=
                  contractkosten + fac.safe_to_number (fac.getsetting ('cnt_factuur_totaalmarge'))
               AND fin.tolerantiematch (discipline,
                                        ftype,
                                        totaaltermijngefactureerd,
                                        COALESCE (factuurschemabedrag, contracttermijnkosten)) = 1
            THEN
               retval := 1;
            ELSE
               retval := -1;
            END IF;
         --
         -- OPDRACHT
         --
         WHEN ftype = 'O'
         THEN
            -- controleer op basis van typeopdr.matchtype
            BEGIN
               SELECT o.mld_opdr_kosten,
                      sm.mld_ins_discipline_key,
                      top.mld_typeopdr_matchtype,
                      o.mld_statusopdr_key
                 INTO opdrachtkosten,
                      discipline,
                      matchtype,
                      opdrstatus
                 FROM mld_opdr o,
                      mld_melding m,
                      mld_typeopdr top,
                      mld_stdmelding sm
                WHERE     o.mld_melding_key = m.mld_melding_key
                      AND m.mld_stdmelding_key = sm.mld_stdmelding_key
                      AND o.mld_typeopdr_key = top.mld_typeopdr_key
                      AND top.mld_typeopdr_kosten = 1                    -- daar mag een factuur bij
                      AND mld_statusopdr_key NOT IN (1, 2, 3, 10) -- dat zijn (nog) ongeldige opdrachten
                      AND mld_opdr_key = o_key;
            EXCEPTION
               WHEN NO_DATA_FOUND
               THEN
                  DBMS_OUTPUT.put_line (
                     'Bijbehorende opdracht(status/kosten?) is ongeldig: ' || o_key);
                  RETURN -1;
            END;

            DBMS_OUTPUT.put_line ('opdrachtkosten: ' || opdrachtkosten);
            DBMS_OUTPUT.put_line ('matchtype: ' || matchtype);
            DBMS_OUTPUT.put_line ('opdrstatus: ' || opdrstatus);

            SELECT MAX( (SELECT ks.prs_kostensoort_btw
                           FROM prs_kostensoort ks
                          WHERE ks.prs_kostensoort_key =
                                   COALESCE (o.prs_kostensoort_key,
                                             stdm.prs_kostensoort_key,
                                             md.prs_kostensoort_key)))
              INTO inclbtw
              FROM mld_discipline md,
                   mld_opdr o,
                   mld_stdmelding stdm,
                   mld_melding m
             WHERE     md.ins_discipline_key = stdm.mld_ins_discipline_key
                   AND o.mld_melding_key = m.mld_melding_key
                   AND m.mld_stdmelding_key = stdm.mld_stdmelding_key
                   AND mld_opdr_key = o_key;

            factmatchmode := fin.getfactmatchmode (discipline, 'O');

            -- Een opdracht is maar 1 ding, dus die status bepaalt al veel bij 3-weg
            -- De opdracht moet nl. so wie so afgemeld zijn.
            IF factmatchmode = 3 AND opdrstatus NOT IN (6, 7, 9)
            THEN
               RETURN -1;
            END IF;

            IF matchtype = 3
            THEN
               DBMS_OUTPUT.put_line ('matchtype unsupported yet!');
               RETURN 0;
            END IF;

            SELECT SUM (fin_factuur_totaal), SUM (fin_factuur_totaal_btw)
              INTO totaalgefactureerd, totaalgefactureerdbtw
              FROM fin_factuur
             WHERE     mld_opdr_key = o_key
                   AND fin_factuur_statuses_key <> 1
                   AND fin_factuur_verwijder IS NULL
                   AND fin_factuur_datum <= factuurdatum;

            IF inclbtw = 1   -- Opdrachtbedragen zijn dus inclusief, dan ook factuur inclusief matchen
            THEN
               DBMS_OUTPUT.put_line ('Inclusief BTW');
               totaalgefactureerd := totaalgefactureerd + totaalgefactureerdbtw;
            END IF;

            IF fin.tolerantiematch (discipline,
                                    ftype,
                                    totaalgefactureerd,
                                    opdrachtkosten) = 1
            THEN
               retval := 1;
            ELSE
               retval := -1;
            END IF;
         ELSE
            retval := 0;
      END CASE;

      RETURN retval;
   END;

   -- Keurt een ingevoerde factuur (onvoorwaardelijk) goed of (als rejecttoo) af op basis van de matchingscriteria
   PROCEDURE matchandsetfactuur (pfactuur_key IN NUMBER, prejecttoo IN BOOLEAN)
   IS
      lgoedkeurder fin_factuur.prs_perslid_key_goedkeur%TYPE;
   BEGIN
      CASE fin.matchfactuur (pfactuur_key)
         WHEN 1
         THEN
            IF fac.getsetting('fin_enable_goedkeuren') = 0     -- validation unavailable
            THEN
               fin.setfactuurstatus (pfactuur_key, 6, NULL);
            ELSIF fac.getsetting('fin_enable_goedkeuren') = 2  -- validation required
            THEN
               fin.setfactuurstatus (pfactuur_key, 5, NULL);
            ELSE                                               -- validation optional (fin_enable_goedkeuren = 1)
               SELECT f.prs_perslid_key_goedkeur
                 INTO lgoedkeurder
                 FROM fin_factuur f
                WHERE fin_factuur_key = pfactuur_key;

               IF lgoedkeurder IS NULL
               THEN
                  fin.setfactuurstatus (pfactuur_key, 6, NULL);
               ELSE
                  fin.setfactuurstatus (pfactuur_key, 5, NULL);
               END IF;
            END IF;
         WHEN -1
         THEN
            IF prejecttoo
            THEN
               fin.setfactuurstatus (pfactuur_key, 1, NULL);
            END IF;
         ELSE
            NULL;
      END CASE;
   END;

   -- Behandelt een ingevoerde factuur conform settings en matchingscriteria
   PROCEDURE autoapprovefactuur (pfactuur_key IN NUMBER)
   IS
      ftype VARCHAR2 (1);
      c_key fin_factuur.cnt_contract_key%TYPE;
      b_key fin_factuur.bes_bestelopdr_key%TYPE;
      o_key fin_factuur.mld_opdr_key%TYPE;
      factuurappr bes_disc_params.bes_disc_params_factuurappr%TYPE;
   BEGIN
      SELECT   mld_opdr_key, cnt_contract_key, bes_bestelopdr_key
        INTO   o_key, c_key, b_key
        FROM   fin_factuur
       WHERE   fin_factuur_key = pfactuur_key;

      ftype := fin.getreferentietype (o_key, c_key, b_key);

      -- Jammer dat ik ook hier nog eens de discipline moet bepalen
      CASE
         WHEN ftype = 'B'
         THEN
            SELECT   p.bes_disc_params_factuurappr
              INTO   factuurappr
              FROM   bes_disc_params p
             WHERE   p.bes_ins_discipline_key =
                        (SELECT   MAX (isg.ins_discipline_key)
                           FROM   bes_bestelopdr_item boi, bes_bestelling_item bbi, bes_srtdeel isd, bes_srtgroep isg
                          WHERE       boi.bes_bestelopdr_item_key = bbi.bes_bestelopdr_item_key
                                  AND bbi.bes_srtdeel_key = isd.bes_srtdeel_key
                                  AND isd.bes_srtgroep_key = isg.bes_srtgroep_key
                                  AND boi.bes_bestelopdr_key = b_key);
         WHEN ftype = 'C'
         THEN
            SELECT   p.cnt_disc_params_factuurappr
              INTO   factuurappr
              FROM   cnt_disc_params p
             WHERE   p.cnt_ins_discipline_key = (SELECT   c.ins_discipline_key
                                                   FROM   cnt_contract c
                                                  WHERE   cnt_contract_key = c_key);
         WHEN ftype = 'O'
         THEN
            SELECT   p.mld_disc_params_factuurappr
              INTO   factuurappr
              FROM   mld_disc_params p
             WHERE   p.mld_ins_discipline_key =
                        (SELECT   sm.mld_ins_discipline_key
                           FROM   mld_opdr o, mld_melding m, mld_typeopdr top, mld_stdmelding sm
                          WHERE       o.mld_melding_key = m.mld_melding_key
                                  AND m.mld_stdmelding_key = sm.mld_stdmelding_key
                                  AND o.mld_typeopdr_key = top.mld_typeopdr_key
                                  AND mld_opdr_key = o_key);
         ELSE
            factuurappr := 0;
      END CASE;

      IF factuurappr = 1
      THEN
         matchandsetfactuur (pfactuur_key, FALSE);
      END IF;
   END;

   -- Bepaalt de key van de DEFAULT btw-tabel
   FUNCTION getdefaultbtwtabelkey
      RETURN NUMBER
   IS
      lkey fin_btwtabel.fin_btwtabel_key%TYPE;
   BEGIN
      BEGIN
         SELECT   fin_btwtabel_key
           INTO   lkey
           FROM   fin_btwtabel
          WHERE   fin_btwtabel_default = 1 AND fin_btwtabel_verwijder IS NULL;
      EXCEPTION
         WHEN NO_DATA_FOUND
         THEN
            NULL;
      END;

      RETURN lkey;
   END;

   -- Bepaalt de key van de overeenkomstige btw-tabel, gegeven de key van (XOR) of contract, of meldingopdracht, of bestelopdracht
   -- Wanneer geen p_bkey, p_ckey, p_okey wordt meegegeven (allemaal NULL), dan wordt de default btw-tabel opgeleverd indien p_otherwise_default = TRUE,
   -- en anders (als p_otherwise_default = FALSE) dan wordt NULL opgeleverd.
   -- PF: en ik constateer dat ook als er bij de gevonden entititeit geen table is, ook de default wordt gegegeven, dus
   FUNCTION getbtwtabelkey (p_bkey IN NUMBER, p_ckey IN NUMBER, p_okey IN NUMBER, p_otherwise_default IN BOOLEAN)
      RETURN NUMBER
   IS
      lkey fin_btwtabel.fin_btwtabel_key%TYPE;

      CURSOR bes_btwtabel
      IS
         SELECT   DISTINCT COALESCE (btw.fin_btwtabel_key, fin.getdefaultbtwtabelkey ()) btwtabel_key
           FROM   bes_bestelling_item bi,
                  bes_srtdeel isd,
                  bes_bestelopdr_item boi,
                  bes_bestelopdr bo,
                  prs_bedrijf bd,
                  fin_btwtabel btw
          WHERE       btw.fin_btwtabel_key(+) = bd.fin_btwtabel_key
                  AND bd.prs_bedrijf_key = isd.prs_bedrijf_key
                  AND isd.bes_srtdeel_key = bi.bes_srtdeel_key
                  AND bi.bes_bestelopdr_item_key = boi.bes_bestelopdr_item_key
                  AND boi.bes_bestelopdr_key = bo.bes_bestelopdr_key
                  AND bo.bes_bestelopdr_key = p_bkey;

      CURSOR cnt_btwtabel
      IS
         SELECT   DISTINCT COALESCE (btw.fin_btwtabel_key, fin.getdefaultbtwtabelkey ()) btwtabel_key
           FROM   cnt_contract c, prs_bedrijf bd, fin_btwtabel btw
          WHERE       btw.fin_btwtabel_key(+) = bd.fin_btwtabel_key
                  AND bd.prs_bedrijf_key(+) = c.cnt_prs_bedrijf_key
                  AND c.cnt_contract_key = p_ckey;

      CURSOR mld_btwtabel
      IS
         SELECT   DISTINCT COALESCE (btw.fin_btwtabel_key, fin.getdefaultbtwtabelkey ()) btwtabel_key
           FROM   mld_opdr o, prs_bedrijf bd, fin_btwtabel btw
          WHERE       btw.fin_btwtabel_key(+) = bd.fin_btwtabel_key
                  AND bd.prs_bedrijf_key(+) = o.mld_uitvoerende_keys
                  AND o.mld_opdr_key = p_okey;
   BEGIN
      lkey := NULL;

      IF p_bkey IS NOT NULL
      THEN
         -- BTW-tabel is te zoeken vanaf de bestelopdracht
         FOR rc IN bes_btwtabel
         LOOP
            lkey := rc.btwtabel_key;
            EXIT WHEN lkey IS NOT NULL;
         END LOOP;
      ELSIF p_ckey IS NOT NULL
      THEN
         -- BTW-tabel is te zoeken bij contract
         FOR rc IN cnt_btwtabel
         LOOP
            lkey := rc.btwtabel_key;
            EXIT WHEN lkey IS NOT NULL;
         END LOOP;
      ELSIF p_okey IS NOT NULL
      THEN
         -- BTW-tabel is te zoeken bij de meldingopdracht
         FOR rc IN mld_btwtabel
         LOOP
            lkey := rc.btwtabel_key;
            EXIT WHEN lkey IS NOT NULL;
         END LOOP;
      ELSIF p_otherwise_default
      THEN
         lkey := fin.getdefaultbtwtabelkey ();
      END IF;

      RETURN lkey;
   END;

   -- Zoekt de key van het overeenkomstige btwpercentage op, gegeven het percentage 'pperc' en de btwtabel 'pbtwtabelkey'
   -- Geeft NULL indien het percentage niet voorkomt.
   FUNCTION getbtwtabelwaardekey_perc (pperc IN NUMBER, pbtwtabelkey IN NUMBER, pbtwverlegd IN NUMBER)
      RETURN NUMBER
   IS
      lkey fin_btwtabelwaarde.fin_btwtabelwaarde_key%TYPE;
   BEGIN
      BEGIN
         SELECT   fin_btwtabelwaarde_key
           INTO   lkey
           FROM   fin_btwtabelwaarde
          WHERE       fin_btwtabel_key = pbtwtabelkey
                  AND fin_btwtabelwaarde_perc = pperc
                  AND COALESCE (fin_btwtabelwaarde_verlegd, 0) = pbtwverlegd
                  AND fin_btwtabelwaarde_verwijder IS NULL;
      EXCEPTION
         WHEN NO_DATA_FOUND
         THEN
            NULL;
      END;

      RETURN lkey;
   END;

   -- Zoekt de key van het overeenkomstige btwpercentage op, gegeven het excl bedrag, btwbedrag en tabel
   -- Geeft NULL indien het percentage niet voorkomt.
   FUNCTION getbtwtabelwaardekey (pbedragexc IN NUMBER, pbtwbedrag IN NUMBER, pbtwtabelkey IN NUMBER, pbtwverlegd IN NUMBER)
      RETURN NUMBER
   IS
      lpercentage NUMBER;
      lkey fin_btwtabelwaarde.fin_btwtabelwaarde_key%TYPE;
   BEGIN
      IF pbedragexc <> 0
      THEN
         lpercentage := ROUND (100 * pbtwbedrag / pbedragexc);
         lkey := getbtwtabelwaardekey_perc (lpercentage, pbtwtabelkey, pbtwverlegd);
      END IF;

      RETURN lkey;
   END;

   -- Berekent het BTW-bedrag, gegeven het bedrag 'pbedragexc' en het percentage 'pperc'
   FUNCTION getbtw_bedrag_percentage (pbedragexc IN NUMBER, pperc IN NUMBER)
      RETURN NUMBER
   IS
      lbtwbedrag NUMBER;
   BEGIN
      RETURN ROUND (pbedragexc * pperc / 100, 2);
   END;

   -- Berekent het BTW-bedrag, gegeven het bedrag 'pbedragexc', de btwtabelwaarde 'pbtwtabelwaardekey'
   FUNCTION getbtw_bedrag (pbedragexc IN NUMBER, pbtwtabelwaardekey IN NUMBER)
      RETURN NUMBER
   IS
      lperc fin_btwtabelwaarde.fin_btwtabelwaarde_perc%TYPE;
      lverlegd fin_btwtabelwaarde.fin_btwtabelwaarde_verlegd%TYPE;
      lbtwbedrag NUMBER;
   BEGIN
      BEGIN
         SELECT   fin_btwtabelwaarde_perc, COALESCE(fin_btwtabelwaarde_verlegd, 0)
           INTO   lperc, lverlegd
           FROM   fin_btwtabelwaarde
          WHERE   fin_btwtabelwaarde_key = pbtwtabelwaardekey;

         IF lverlegd = 1 THEN
            lbtwbedrag := 0;
         ELSE
            lbtwbedrag := getbtw_bedrag_percentage (pbedragexc, lperc);
         END IF;
      EXCEPTION
         WHEN NO_DATA_FOUND
         THEN
            NULL;
      END;

      RETURN lbtwbedrag;
   END;

   -- zoek de kostenplaats van een (gekoppelde) factuur op
   -- Bij voorkeur niet meer gebruiken sinds v2017.2
   FUNCTION getfactuurkostenplaats (pfactuur_key IN NUMBER)
      RETURN NUMBER
   IS
      ftype VARCHAR2 (1);
      c_key fin_factuur.cnt_contract_key%TYPE;
      b_key fin_factuur.bes_bestelopdr_key%TYPE;
      o_key fin_factuur.mld_opdr_key%TYPE;
      kpkey prs_kostenplaats.prs_kostenplaats_key%TYPE;
   BEGIN
      SELECT   mld_opdr_key, cnt_contract_key, bes_bestelopdr_key
        INTO   o_key, c_key, b_key
        FROM   fin_factuur
       WHERE   fin_factuur_key = pfactuur_key;

      ftype := fin.getreferentietype (o_key, c_key, b_key);

      CASE
         WHEN ftype = 'B'
         THEN
            SELECT   MAX (b.prs_kostenplaats_key)
              INTO   kpkey
              FROM   bes_bestelopdr_item boi, bes_bestelling_item bbi, bes_bestelopdr bo, bes_bestelling b
             WHERE       bbi.bes_bestelling_key = b.bes_bestelling_key
                     AND boi.bes_bestelopdr_item_key = bbi.bes_bestelopdr_item_key
                     AND bo.bes_bestelopdr_key = boi.bes_bestelopdr_key
                     AND boi.bes_bestelopdr_key = b_key;
         WHEN ftype = 'C'
         THEN
            SELECT   c.prs_kostenplaats_key
              INTO   kpkey
              FROM   cnt_contract c
             WHERE   cnt_contract_key = c_key;
         WHEN ftype = 'O'
         THEN
            SELECT   COALESCE (o.prs_kostenplaats_key, m.prs_kostenplaats_key)
              INTO   kpkey
              FROM   mld_opdr o, mld_melding m
             WHERE   o.mld_melding_key = m.mld_melding_key AND mld_opdr_key = o_key;
         ELSE
            kpkey := -1;
      END CASE;

      RETURN kpkey;
   END;
   -- zoek de fiatteur van een (gekoppelde) factuur op
   -- -1 als niet gevonden
   FUNCTION getfiatteur (pfactuur_key IN NUMBER,
                         pexcludekey  IN NUMBER DEFAULT NULL)
      RETURN NUMBER
   IS
      c_key    fin_factuur.cnt_contract_key%TYPE;
      b_key    fin_factuur.bes_bestelopdr_key%TYPE;
      o_key    fin_factuur.mld_opdr_key%TYPE;
      ftot     fin_factuur.fin_factuur_totaal%TYPE;
      ftotb    fin_factuur.fin_factuur_totaal_btw%TYPE;
      kpkey    prs_kostenplaats.prs_kostenplaats_key%TYPE;
      ldisckey ins_tab_discipline.ins_discipline_key%TYPE;
      kskey    prs_kostensoort.prs_kostensoort_key%TYPE;
      linclbtw prs_kostensoort.prs_kostensoort_btw%TYPE;
      lbedrag  fin_factuur.fin_factuur_totaal%TYPE;
   BEGIN
      SELECT   mld_opdr_key, cnt_contract_key, bes_bestelopdr_key, fin_factuur_totaal, fin_factuur_totaal_btw
        INTO   o_key, c_key, b_key, ftot, ftotb
        FROM   fin_factuur
       WHERE   fin_factuur_key = pfactuur_key;

      CASE
         WHEN b_key IS NOT NULL
         THEN
            SELECT   MAX (b.prs_kostenplaats_key)
              INTO   kpkey
              FROM   bes_bestelopdr_item boi, bes_bestelling_item bbi, bes_bestelopdr bo, bes_bestelling b
             WHERE       bbi.bes_bestelling_key = b.bes_bestelling_key
                     AND boi.bes_bestelopdr_item_key = bbi.bes_bestelopdr_item_key
                     AND bo.bes_bestelopdr_key = boi.bes_bestelopdr_key
                     AND boi.bes_bestelopdr_key = b_key;
         WHEN c_key IS NOT NULL
         THEN
            SELECT   c.prs_kostenplaats_key
              INTO   kpkey
              FROM   cnt_contract c
             WHERE   cnt_contract_key = c_key;
         WHEN o_key IS NOT NULL
         THEN
            SELECT   COALESCE (o.prs_kostenplaats_key, m.prs_kostenplaats_key)
              INTO   kpkey
              FROM   mld_opdr o, mld_melding m
             WHERE   o.mld_melding_key = m.mld_melding_key AND mld_opdr_key = o_key;
         ELSE
            RETURN -1;
      END CASE;

      IF linclbtw = 1 THEN
        lbedrag := ftot + ftotb;
      ELSE
        lbedrag := ftot;
      END IF;

      RETURN prs.getfiatteur(kpkey, pexcludekey, lbedrag, ldisckey);
   END;

   FUNCTION sprintf (ps IN VARCHAR2 , p_factuur_key IN NUMBER)
      RETURN VARCHAR2
   IS
      s VARCHAR2 (2048 CHAR);
   BEGIN
      s :=  ps;
      -- We do support substitution of placeholders in the messages
      -- ##KEY##    = fin_factuur_key
      IF INSTR (s, '#') > 0
      THEN
         s := REPLACE (s, '##KEY##', p_factuur_key);
      END IF;
      RETURN s;
   END;
END fin;
/

REGISTERRUN('$Id$')

#endif // FIN
