<%
 /*
    $Revision$
    $Id$

    File:               res.inc
    Description:        res ondersteunende functies
    Context:
    Note:
 */
if (!this.JSON_Result && !Request.QueryString("api2").Count)
    FCLTHeader.Requires({css: ["../res/res.css"] });

res = {
       // sterke relatie met z:\Project\Sm44\VSSSRC\SQL\RES\RES_PAC.SRC
       //  merk op dat niet alle leveld gebruikt/ ondersteund worden
       dirtlevel:
       {
         ruimte:
         {
           clean:0,
           //licht
           //middel
           nocatering    : 0x010, // 0b000100000000, // Niets zinvols (onder een CV)
           //zwaar
           noroom        : 0x100, // 0b000100000000, // Geen alg_ruimte/opstel_key ingevuld
           notavailable  : 0x200  // 0b001000000000  // Niet beschikbaar op gekozen tijdstip
         },
         artikel:
         {
           clean:0,
           //licht
           //middel
           timechanged   : 0x000000010000, // Parent gewijzigd en wij mee gewijzigd
           timenotchanged: 0x000000100000, // Parent gewijzigd maar wij niet mee gewijzigd
           //zwaar
           notallowed    : 0x100  // Niet toegestaan in deze ruimte
         },
         deel:
         {
           clean:0,
           //licht
           //middel
           timechanged   : 0x000000010000, // Parent gewijzigd en wij mee gewijzigd
           timenotchanged: 0x000000100000, // Parent gewijzigd maar wij niet mee gewijzigd
           //zwaar
           notallowed    : 0x100, // Niet toegestaan in deze ruimte
           notavailable  : 0x200  // Niet beschikbaar op tijdstip
         }
       },

        // Verwijder de afspraak die eventueel gekoppeld was aan rsv_ruimte_key
        // (JGL: Eigenlijk moet dit gewoon cascading door een trigger gebeuren)
        DeleteResAfspraak: function DeleteResAfspraak (rsv_ruimte_key)
        {
          var sql;
          // delete parkeerplaatsen gebeurt wel met behulp van CASCADE (bez_bezoekers)

          // delete bezoekers
          var sql = "DELETE FROM bez_bezoekers"
                  + " WHERE bez_afspraak_key in "
                  + "     (SELECT ba.bez_afspraak_key"
                  + "        FROM bez_afspraak ba"
                  + "       WHERE ba.res_rsv_ruimte_key = " + rsv_ruimte_key + ")";
          Oracle.Execute(sql);

          // delete afspraak
          sql = "DELETE FROM bez_afspraak"
              + " WHERE res_rsv_ruimte_key = " + rsv_ruimte_key;
          Oracle.Execute(sql);
        },

       trackreserveringupdate:
       // Let op: dit moet wellicht niet altijd (aangeroepen na save)
       function (rsv_ruimte_key, ptxt)
       {
         shared.trackaction("RESUPD", rsv_ruimte_key, ptxt);
       },

       trackobject:
       function (deel_key, actie)
       {
         sql = "SELECT " + lcl.xsqla('rd.res_deel_omschrijving', 'rd.res_deel_key')
             + "  FROM res_deel rd"
             + " WHERE rd.res_deel_key = " + deel_key;
         var oRs = Oracle.Execute(sql);
         var obj_oms = oRs("res_deel_omschrijving").Value;
         oRs.Close();
         var track = "";
         switch (actie)
         {
           case "delete": track = L("lcl_res_insdelete") + ": " + obj_oms;
                          break;
           case "insert": track = L("lcl_res_insinsert") + ": " + obj_oms;
                          break;
         }
         return track;
       },

       trackartikel:
       function (artikel_key, actie, params)
       {
         sql = "SELECT ra.res_artikel_omschrijving"
             + "  FROM res_artikel ra"
             + " WHERE ra.res_artikel_key = " + artikel_key;
         var oRs = Oracle.Execute(sql);
         var art_oms = oRs("res_artikel_omschrijving").Value;
         oRs.Close();
         var track = "";
         switch (actie)
         {
           case "delete": track = L("lcl_res_artdelete") + ": " + art_oms;
                          break;
           case "insert": track = L("lcl_res_artinsert") + ": " + art_oms;
                          break;
           case "update": track = L("lcl_res_artupdate") + ": " + art_oms
                                + (params.old_number != params.new_number
                                  ? "\n" + buildTrackText("number", params.old_number, params.new_number, { nodiff: true })
                                  : "")
                                + (params.old_price != params.new_price
                                  ? "\n" + buildTrackText("float", params.old_price, params.new_price, { nodiff: true })
                                  : "")
                                + (params.old_date.getTime() != params.new_date.getTime()
                                  ? "\n" + buildTrackText("datetime", params.old_date, params.new_date, { nodiff: true })
                                  : "");
                          break;
         }
         return track;
       },

       res_set_dialect:
         function (rsv_ruimte_key)
         {
             var sql = "SELECT ra.res_srtactiviteit_key"
                     + "  FROM res_rsv_ruimte rrr, "
                     + "       res_activiteit ra"
                     + " WHERE res_rsv_ruimte_key = " + rsv_ruimte_key
                     + "   AND rrr.res_activiteit_key = ra.res_activiteit_key";
             var oRs = Oracle.Execute(sql);
             lcl.set_dialect(oRs("res_srtactiviteit_key").Value, "RES_SRTACTIVITEIT_KEY");
             oRs.Close();
         },

       res_ruimte_info: function _res_ruimte(res_ruimte_key)
       {
         var result = { res_ruimte_key: res_ruimte_key,
                        RoomConfigs: [],
                        default_opstel_key : -1 };

         var sql = "SELECT " + lcl.xsqla('rr.res_ruimte_nr', 'rr.res_ruimte_key')
                 + "     , " + lcl.xsqla('rr.res_ruimte_omschrijving', 'rr.res_ruimte_key')
                 + "     , res_ruimte_info_url"
                 + "     , res_ruimte_image"
                 + "     , ac.res_activiteit_aantalverplicht"
                 + "     , ac.res_activiteit_notfrontend"
                 + "     , res_disc_params_kosten"
                 + "     , ins_discipline_kpnverplicht"
                 + "     , COALESCE(res_ruimte_begintijd, " + S("res_t1") + ") begintijd"
                 + "     , COALESCE(res_ruimte_eindtijd, " + S("res_t2") + ") eindtijd"
                 + "     , res_ruimte_begintijdblok"
                 + "     , res_ruimte_eindtijdblok"
                 + "     , rr.res_discipline_key"
                 + "     , rr.res_status_fo_key"
                 + "     , res_ruimte_vervaldatum"
                 + "     , res_ruimte_extern_id"
                 + "     , res_ruimte_min_duur"
                 + "  FROM res_ruimte rr"
                 + "     , res_disc_params rdp"
                 + "     , res_discipline rd"
                 + "     , res_activiteitdiscipline ad"
                 + "     , res_activiteit ac"
                 + " WHERE rr.res_ruimte_key = " + res_ruimte_key
                 + "   AND rr.res_discipline_key = rd.ins_discipline_key "
                 + "   AND rr.res_discipline_key = rdp.res_ins_discipline_key"
                 + "   AND rd.ins_discipline_key = ad.res_discipline_key"
                 + "   AND ad.res_activiteit_key = ac.res_activiteit_key";

         var oRs = Oracle.Execute(sql);
         if (!oRs.eof)
         {
           result.ruimte_nr = oRs("res_ruimte_nr").Value;
           result.ruimte_omschrijving = oRs("res_ruimte_omschrijving").Value;
           result.ruimte_info_url = oRs("res_ruimte_info_url").Value;
           result.aantalreq = oRs("res_activiteit_aantalverplicht").Value;
           result.notfrontend = oRs("res_activiteit_notfrontend").Value;
           if (oRs("res_ruimte_image").Value)
             result.imagePath = S("res_image_path") + oRs("res_ruimte_image").Value;
           result.kosten = oRs("res_disc_params_kosten").Value;
           result.kpnverplicht = oRs("ins_discipline_kpnverplicht").Value;
           result.discipline_key = oRs("res_discipline_key").Value;
           result.begintijd = oRs("begintijd").Value;
           result.eindtijd = oRs("eindtijd").Value;
           result.begintijdblok = oRs("res_ruimte_begintijdblok").Value;
           result.eindtijdblok = oRs("res_ruimte_eindtijdblok").Value;
           if (result.begintijd < S("res_t1")) result.begintijd = S("res_t1");
           if (result.eindtijd > S("res_t2")) result.eindtijd = S("res_t2");
           if (result.begintijd > result.eindtijd) result.begintijd = result.eindtijd;
           result.status_fo_key = oRs("res_status_fo_key").Value || -1;
           if (result.status_fo_key > 0)
             result.status_fo_string = res.getfostatustext(result.status_fo_key);
           result.vervaldatum = oRs("res_ruimte_vervaldatum").Value != null? new Date(oRs("res_ruimte_vervaldatum").Value) : null;
           result.extern_id = oRs("res_ruimte_extern_id").Value;
           result.min_duur = oRs("res_ruimte_min_duur").Value;
         }
         sql = "SELECT res_ruimte_opstel_key"
             + "     , res_ruimte_opstel_bezoekers"
             + "     , res_ruimte_opstel_default"
             + "     , ro.res_opstelling_key"
             + "     , res_ruimte_opstel_image"
             + "     , " + lcl.xsqla('res_opstelling_omschrijving','ro.res_opstelling_key')
             + "     , rro.res_ruimte_opstel_verwijder"
             + "  FROM res_ruimte rr"
             + "     , res_ruimte_opstelling rro"
             + "     , res_opstelling ro"
             + " WHERE rr.res_ruimte_key = " + res_ruimte_key
             + "   AND rr.res_ruimte_key = rro.res_ruimte_key"
             + "   AND rro.res_opstelling_key = ro.res_opstelling_key"
             + " ORDER BY res_opstelling_volgnr";
         oRs = Oracle.Execute(sql);

         while (!oRs.eof)
         {
           var rc = { ruimte_opstel_key: oRs("res_ruimte_opstel_key").Value,
                      opstelling_omschrijving: oRs("res_opstelling_omschrijving").Value,
                      opstelling_key: oRs("res_opstelling_key").Value,
                      bezoekers: oRs("res_ruimte_opstel_bezoekers").Value,
                      verwijderd: oRs("res_ruimte_opstel_verwijder").Value != null }
           if (oRs("res_ruimte_opstel_image").Value)
             rc.imagePath = S("res_image_path") + oRs("res_ruimte_opstel_image").Value;
           result.RoomConfigs.push(rc);
           if (oRs("res_ruimte_opstel_default").Value)
             result.default_opstel_key = oRs("res_ruimte_opstel_key").Value;
           oRs.MoveNext();
         }
         oRs.close();

         return result;
       },

       res_rsv_ruimte_info: function res_rsv_ruimte_info(rsv_ruimte_key)
       {
         var result = {rsv_ruimte_key: rsv_ruimte_key };

         var sql = "SELECT rrr.res_ruimte_opstel_key"
                 + ", rrr.prs_kostenplaats_key"
                 + ", rrr.res_rsv_ruimte_bezoekers"
                 + ", rrr.res_status_fo_key"
                 + ", rrr.res_activiteit_key"
                 + ", rrr.res_reservering_key"
                 + ", rrr.res_rsv_ruimte_volgnr"
                 + ", rrr.res_rsv_ruimte_van"
                 + ", rrr.res_rsv_ruimte_tot"
                 + ", rrr.res_rsv_ruimte_omschrijving"
                 + ", rrr.res_rsv_ruimte_opmerking"
                 + ", rrr.res_rsv_ruimte_contact_key"
                 + ", rrr.res_rsv_ruimte_host_key"
                 + ", rrr.res_rsv_ruimte_telefoon"
                 + ", " + lcl.xsqla("ra.res_activiteit_omschrijving", "ra.res_activiteit_key")
                 + " FROM res_rsv_ruimte rrr,"
                 + "  res_activiteit ra"
                 + " WHERE rrr.res_activiteit_key = ra.res_activiteit_key"
                 + " AND rrr.res_rsv_ruimte_key = " + rsv_ruimte_key;
         var oRs = Oracle.Execute(sql);
         result.opstel_key      = oRs("res_ruimte_opstel_key").Value;
         result.account         = oRs("prs_kostenplaats_key").Value;
         result.bezoekers       = oRs("res_rsv_ruimte_bezoekers").Value;
         result.status_fo       = oRs("res_status_fo_key").Value;
         result.act_key         = oRs("res_activiteit_key").Value;
         result.reservering_key = oRs("res_reservering_key").Value;
         result.ruimte_volgnr   = oRs("res_rsv_ruimte_volgnr").Value;
         result.ruimte_van      = oRs("res_rsv_ruimte_van").Value;
         result.ruimte_tot      = oRs("res_rsv_ruimte_tot").Value;
         result.omschrijving    = oRs("res_rsv_ruimte_omschrijving").Value;
         result.opmerking       = oRs("res_rsv_ruimte_opmerking").Value;
         result.contact_key     = oRs("res_rsv_ruimte_contact_key").Value;
         result.host_key        = oRs("res_rsv_ruimte_host_key").Value;
         result.telefoon        = oRs("res_rsv_ruimte_telefoon").Value;
         result.activiteit      = oRs("res_activiteit_omschrijving").Value;
         oRs.Close();

         var sql = "SELECT COALESCE (opstelalg.res_ruimte_nr, ruimte_geg.alg_ruimte_aanduiding) res_ruimte_nr"
                 + "     , opstelalg.res_ruimte_key"
                 + "     , ruimte_geg.alg_locatie_key"
                 + "     , ruimte_geg.alg_ruimte_key"
                 + "     , rsa.res_srtactiviteit_metomschr"
                 + "     , rsa.res_srtactiviteit_metopmerk"
                 + "     , rsa.res_srtactiviteit_metaantal"
                 + "  FROM res_rsv_ruimte rrr"
                 + "     , alg_v_ruimte_gegevens_all ruimte_geg"
                 + "     , res_activiteit ra"
                 + "     , res_srtactiviteit rsa"
                 + "     , (SELECT res_ruimte_opstel_key"
                   + "           , rr.res_ruimte_nr res_ruimte_nr"
                   + "           , rr.res_ruimte_key"
                   + "           , MIN (alg_ruimte_key) alg_ruimte_key"
                   + "        FROM res_ruimte_opstelling rro"
                   + "           , res_alg_ruimte rar"
                   + "           , res_ruimte rr"
                   + "       WHERE rro.res_ruimte_key = rar.res_ruimte_key"
                   + "         AND rr.res_ruimte_key = rar.res_ruimte_key"
                   + "         AND rar.res_alg_ruimte_verwijder IS NULL"
                   + "      GROUP BY res_ruimte_opstel_key"
                   + "             , rr.res_ruimte_nr"
                   + "             , rr.res_ruimte_key) opstelalg"
                 + " WHERE rrr.res_rsv_ruimte_verwijder IS NULL"
                 + "   AND rrr.res_ruimte_opstel_key = opstelalg.res_ruimte_opstel_key(+)"
                 + "   AND rrr.res_activiteit_key = ra.res_activiteit_key"
                 + "   AND ra.res_srtactiviteit_key = rsa.res_srtactiviteit_key"
                 + "   AND ruimte_geg.alg_ruimte_key = COALESCE (rrr.alg_ruimte_key, opstelalg.alg_ruimte_key)"
                 + "   AND rrr.res_rsv_ruimte_key = " + rsv_ruimte_key;
         var oRs = Oracle.Execute(sql);
         result.ruimtenr  = oRs("res_ruimte_nr").Value;
         result.ruimtekey = oRs("res_ruimte_key").Value;
         result.loc_key   = oRs("alg_locatie_key").Value;
         result.room_key  = oRs("alg_ruimte_key").Value;
         result.toonOms   = oRs("res_srtactiviteit_metomschr").Value || 0;
         result.toonOpm   = oRs("res_srtactiviteit_metopmerk").Value || 0;
         result.toonBez   = oRs("res_srtactiviteit_metaantal").Value || 0;
         oRs.Close();

         return result;
       },

       res_getnewdates: function _res_getnewdates(params)
       {
         var result = {};

         var res_new_dates = [];
         var limitDate = null;
         var limit = ((params.urole == "fe" && S("res_fe_reservering_limiet") > 0)? S("res_fe_reservering_limiet") : S("res_reservering_limiet"))

         var ruimte_vervaldatum = params.ruimte_vervaldatum > 0? new Date(params.ruimte_vervaldatum) : null;
         var daysToExpire = null;
         if (ruimte_vervaldatum != null)
           daysToExpire = (ruimte_vervaldatum.midnight().getTime() - new Date().midnight().getTime())/(24*3600*1000);

         if (daysToExpire != null && daysToExpire <= 0)
           return {res_new_dates: [], message: L("lcl_res_date_limit_reached") + toDateString(new Date())}; // Geen geldige vervolgdatums mogelijk.

         if (daysToExpire != null && daysToExpire >= 0 && daysToExpire < limit)
           limit = daysToExpire;

         if (limit > 0)
         {
           limitDate = new Date();
           limitDate.setDate(limitDate.getDate() + limit);
         }

         var nextDate = new Date(params.nextDate);
         var overflow = false;
         var message = "";
         for (var i = 0; i < params.np; i++)
         { // Maandelijks (Eerste dinsdag van de maand). Geen vast interval dus.
           // fac.nextcyclusdate (begindatum, mode, eenheid, periode, bits, 1);
           var ora_date1 = nextDate.beginToSQL();
           var sql = "SELECT fac.nextcyclusdate (" + ora_date1 + ", 0, 3, 1, " + params.bits + ", 1) datum FROM DUAL"; // Eenheid Week(3)
           var oRs = Oracle.Execute(sql);
           nextDate = new Date(oRs("datum").Value); // Datum heeft al tijd van 0:00 uur
           oRs.Close();

           var date2 = new Date(nextDate); // zelfde tijdstip gebruiken als rrr.rsv_ruimte_van
           date2 = date2.setFloatHours(new Date(params.rsv_ruimte_van).getFloatHours(), safe.jsfloat(S("res_h")));

           var earliest_expire_change = new Date(params.earliest_expire_change);
           if (params.chk_lastmin && params.urole == "fe" && earliest_expire_change && date2 < earliest_expire_change)
           {
             message = L("lcl_res_repeat_lastmin_n");
             continue;
           }
           if (limitDate && nextDate > limitDate)
           {
             overflow = true;
             break;
           }
           // Checkbox "Alleen werkdagen" is niet aanwezig voor "Maandelijks". Hiermee dus geen rekening houden.
           res_new_dates.push(nextDate.getTime());
         };

         var result = {res_new_dates: res_new_dates, message: message};
         if (overflow)
           result.message += (message != ""? "<br>" : "") + L("lcl_res_date_limit_reached") + toDateString(limitDate);

         return result;
       },

       res_getnrperiods: function _res_getnrperiods(params)
       {
         var nextDate = new Date(params.nextDate);
         var lastDate = new Date(params.lastDate);
         // De dag zelf telt ook nog mee als stap. Daarom bij gebruik van de functie fac.nextcyclusdatesteps() er nog 1 dag bij optellen.
         lastDate.setDate(lastDate.getDate() + 1);
         var message = "";
         var res_steps = 0;
         if (nextDate < lastDate)
         {
           var ora_date1 = nextDate.beginToSQL();
           var ora_date2 = lastDate.beginToSQL();
           var sql = "SELECT fac.nextcyclusdatesteps (" + ora_date1 + ", 0, 3, 1, " + params.bits + ", 0, " + ora_date2 + ") steps FROM DUAL";
           var oRs = Oracle.Execute(sql);
           res_steps = oRs("steps").Value;
           oRs.Close();
         }
         var result = {res_steps: res_steps, message: message};

         return result;
       },

       res_voorzieningen_info: function _res_voorzieningen_info(rsv_ruimte_key)
       {
         var result = {};

         // TODO: In de toekomst algemene voorzieningen gegevens ophalen indien ze nodig zijn.
         // Nu hebben we alleen de verplichtheid van de kostenplaats nog nodig.
         if (!result.kpnverplicht)
         { // Is de kostenplaats verplicht via de voorzieningen en/of verbruiksartikelen.
           var sql = "SELECT MAX(kpnverplicht) kpnverplicht"
                   + "  FROM (SELECT MAX(rd.ins_discipline_kpnverplicht) kpnverplicht"
                   + "          FROM res_rsv_artikel ra"
                   + "             , res_rsv_ruimte rr"
                   + "             , res_artikel a"
                   + "             , res_discipline rd"
                   + "         WHERE ra.res_rsv_ruimte_key = rr.res_rsv_ruimte_key"
                   + "           AND ra.res_artikel_key = a.res_artikel_key"
                   + "           AND a.res_discipline_key = rd.ins_discipline_key"
                   + "           AND ra.res_rsv_artikel_verwijder IS NULL"
                   + "           AND rr.res_rsv_ruimte_key = " + rsv_ruimte_key
                   + " UNION"
                   + "        SELECT MAX (dis.ins_discipline_kpnverplicht) kpnverplicht"
                   + "          FROM res_rsv_deel rd"
                   + "             , res_rsv_ruimte rr"
                   + "             , res_deel d"
                   + "             , res_discipline dis"
                   + "         WHERE rd.res_rsv_ruimte_key = rr.res_rsv_ruimte_key"
                   + "           AND rd.res_deel_key = d.res_deel_key"
                   + "           AND d.res_discipline_key = dis.ins_discipline_key"
                   + "           AND rd.res_rsv_deel_verwijder IS NULL"
                   + "           AND rr.res_rsv_ruimte_key = " + rsv_ruimte_key + ")";
          oRs = Oracle.Execute(sql);
          if (!oRs.eof)
            result.kpnverplicht = oRs("kpnverplicht").Value;
          oRs.Close();
         }

         return result;
       },

       // Als een datum voor (discipline_expire(disc_key) ligt dan is hij 'te vroeg'
       discipline_expire: function _discipline_expire(discipline_key, mode)
       {
         // Mode 0 = Use expire dagen (res_disc_params_expire_dagen)
         //      1 = Use cancel dagen (res_disc_params_cancel_dagen)
         //      2 = 24/7 organisatie
         // res_disc_params_expire_tijd wordt duaal gebruikt. Rol A: kloktijd en rol B: na wanneer moet levertijd zijn
         // als res_disc_params_expire_tijd (A) nog niet verstreken:
         //        dagen = 0 ==> levertijd voorziening moet na res_disc_params_expire_tijd (B) liggen
         //        dagen > 0 ==> levertijd voorziening moet morgenvroeg/overmorgenvroeg etc. (wel altijd na S("res_cat_t1")) liggen
         // anders als res_disc_params_expire_tijd (A) wel verstreken:
         //        dagen = 0 ==> levertijd voorziening moet morgenvroeg na S("res_cat_t1") liggen
         //        dagen > 0 ==> levertijd voorziening moet overmorgen etc. na S("res_cat_t1") liggen
         // (Als het model nog verder uitgewerkt zou worden zou je voor (B) een apart tijdstip hebben,
         //      bijvoorbeeld 12:00h voor lunchpakketten of: 2 uur, altijd minimaal twee uren van tevoren)
         // Effectief is 00:15h en 0 dag gelijk aan 23:45 en 1 dag

         // dagen  expire_tijd   hetisnu     resultaat (veronderstel cat_t1 8:00)
         // --------------------------------------------------------------------------------------
         //   -1      nvt           nvt      vandaag middernacht (0:00)  Effectief: je mag zelfs *nu*

         //   0       10.00        9:00      vandaag 10:00             \ Effectief: lunchpakketen Akzo
         //   0       10.00       11:00      morgen 8:00               /
         //   1       16.00       15:00      morgen 8:00               \ Effectief: voor 16:00h morgen nog
         //   1       16.00       17:00      overmorgen 8:00           /            anders overmorgen

         //   0       00.25        9:00      morgen 8:00               \ Effectief: morgen mag altijd
         //   0       00.25       11:00      morgen 8:00               /
         //   1       00.25        9:00      overmorgen 8:00           \ Effectief: overmorgen mag altijd
         //   1       00.25       11:00      overmorgen 8:00           /

         //   23.75 is nooit nodig?
         //   0       23.75        9:00      unsupported (vandaag 23.75)
         //   0       23.75       11:00      unsupported (vandaag 23.75)
         //   1       23.75        9:00      morgen 8:00          \ Effectief: morgen mag altijd
         //   1       23.75       11:00      morgen 8:00          /

         //   0       leeg         9:00      vandaag 9:00 (nu)    \ Effectief: je mag altijd nog *in de toekomst*
         //   0       leeg        11:00      vandaag 11:00 (nu)   /
         //   1       leeg         9:00      morgen 9:00          \ Effectief: over exact 24 uur
         //   1       leeg        16:00      morgen 16:00         /
         // Als de mode niet is meegegeven dan standaard 0 nemen (expired)
         if (typeof mode == "undefined") mode = 0;
         if (mode == 2)
           var sql = "SELECT fac.DatumTijdPlusUitvoerTijd(SYSDATE, rdp.res_disc_params_expire_dagen, 'DAGEN', null, null, 2) datum";
         else
           var sql = "SELECT fac.DatumTijdPlusUitvoerTijd(SYSDATE, COALESCE(" + (mode == 1? "res_disc_params_cancel_dagen" : "res_disc_params_expire_dagen") + ", 1),"
                 + "                                    'DAGEN', " + ((restype == "CV" && S("res_cat_t1") >= 0)? S("res_cat_t1") : S("res_t1")) + ", "
                 + "                                    COALESCE(CASE"
                 + "                                             WHEN res_disc_params_expire_tijd = -1 OR res_disc_params_expire_tijd IS NULL"
                 + "                                             THEN NULL"
                 + "                                             ELSE res_disc_params_expire_tijd"
                 + "                                             END, " + ((restype == "CV" && S("res_cat_t2") >= 0)? S("res_cat_t2") : S("res_t2")) + "), 1) datum"

         sql    += "     , res_disc_params_expire_tijd"
                 + "     , COALESCE(" + (mode == 1? "res_disc_params_cancel_dagen" : "res_disc_params_expire_dagen") + ", 1) dagen"
                 + "     , ins_discipline_min_level"
                 + "     , ins_discipline_omschrijving"
                 + "  FROM res_disc_params rdp, res_discipline rd"
                 + " WHERE rdp.res_ins_discipline_key = rd.ins_discipline_key"
                 + "   AND rd.ins_discipline_key = " + discipline_key;
         var oRs = Oracle.Execute(sql);
         if (oRs("dagen").Value < 0)
         {
           oRs.Close();
           return (new Date).midnight();
         }
         var restype = oRs("ins_discipline_min_level").Value == 3? "R" : "CV";
         var omschrijving = oRs("ins_discipline_omschrijving").Value; // handig voor logging

         var exp_time = oRs("res_disc_params_expire_tijd").Value;
         var exp_datum = new Date(oRs("datum").Value);
         if (exp_time > 0) // BLCC#34448: hij is nog wel eens -1
         {
           var now = (new Date).getFloatHours();
           if (now <= exp_time) // (exp_time rol A)
           { // grens-tijdstip nog niet vertreken.
             if (oRs("dagen").Value == 0) // we blijven vandaag
               exp_datum = exp_datum.setFloatHours(exp_time); // (exp_time rol B)
             else
               exp_datum = exp_datum.setFloatHours((restype == "CV" && S("res_cat_t1") >= 0)? S("res_cat_t1") : S("res_t1"));
           }
           // else grens-tijdstip verstreken. Naar "8 uur 's ochtends". Tijd staat al goed.
         }
         if (exp_datum < new Date)
             exp_datum = new Date;
         oRs.Close();
         __Log("discipline_expire " + omschrijving + " (key: " + discipline_key + "): " + toDateTimeString(exp_datum) + ", mode: " + mode + (mode == 1? " (cancel)" : " (expire)"));
         return exp_datum;
       },

       keepFlexDocuments:
        function(res_key, old_act_key, act_key)
        {
          /* Match de srtkenmerk_key-s van de oude en nieuwe mld_stdmelding */
          if (old_act_key != act_key)
          {
            __Log("Old res_activiteit_key: " + old_act_key + " New: " + act_key);
            var flexsql = "SELECT k_old.res_kenmerk_key k_old"
                        + "     , k_new.res_kenmerk_key k_new"
                        + "  FROM res_srtkenmerk t"
                        + "     , res_kenmerk k_old"
                        + "     , res_kenmerk k_new"
                        + "     , res_activiteit s_old"
                        + "     , res_activiteit s_new"
                        + " WHERE s_old.res_activiteit_key = " + old_act_key
                        + "   AND s_new.res_activiteit_key = " + act_key
                        + "   AND k_old.res_activiteit_key = s_old.res_activiteit_key"
                        + "   AND k_new.res_activiteit_key = s_new.res_activiteit_key"
                        + "   AND k_old.res_kenmerk_verwijder IS NULL"
                        + "   AND k_new.res_kenmerk_verwijder IS NULL"
                        + "   AND k_old.res_srtkenmerk_key = t.res_srtkenmerk_key"
                        + "   AND k_new.res_srtkenmerk_key = t.res_srtkenmerk_key"
                        + "   AND t.res_srtkenmerk_verwijder IS NULL";

            var subfolder = "RES/" + subfolderKey("R", res_key)
            RenameFlexFolders(subfolder, flexsql);
          }
        },

        getfostatustext:
           function (p) {
              var statustekst = "??";
              var s = parseInt(p, 10);
              // pseudo status_key -1 kan worden gebruikt om dirty te signaleren
              switch (s) {
                  case 1: { statustekst = L("lcl_optie"); break; }
                  case 2: { statustekst = L("lcl_def"); break; }
                  case 3: { statustekst = L("lcl_blokkade"); break; }
                  case 4: { statustekst = L("lcl_vervallen"); break; }
                  case -1: { statustekst = L("lcl_res_status_dirty"); break; }
              }
              return statustekst;
        },

        getbostatustext:
           function (p) {
              var statustekst = "??";
              var s = parseInt(p, 10);
              switch (s) {
                  case 2: { statustekst = L("lcl_res_resnew"); break; }
                  case 5: { statustekst = L("lcl_res_resafm"); break; }
                  case 6: { statustekst = L("lcl_res_resver"); break; }
              }
              return statustekst;
        },


        // Controleert/bepaalt de rechten op het 'Algemeen' blok bij een (deel)reservering
        // Altijd worden de disciplines van de catering/voorziening bekeken
        // Voor restype=="R" wordt ook de discipline van de RES_RUIMTE bekeken
        // We kijken pessimistisch: je moet rechten hebben voor alle disciplines
        // Optioneel pdisc_key: controleer alleen voor deze discipline
        func_enabled: function res_func_enabled(rsv_ruimte_key, pdisc_key, params)
        {
          var params = params || {};
          // Wat informatie om rechten te kunnen bepalen
          var sql = "SELECT r.res_ruimte_opstel_key"
                  + "     , r.alg_ruimte_key"
                  + "     , r.res_rsv_ruimte_van"
                  + "     , r.res_rsv_ruimte_tot"
                  + "     , r.res_rsv_ruimte_contact_key"
                  + "     , r.res_rsv_ruimte_host_key"
                  + "     , r.res_status_fo_key"
                  + "     , r.res_status_bo_key"
                  + "     , r.res_reservering_key"
                  + "     , r.res_rsv_ruimte_verwijder"
                  + "     , r.res_activiteit_key"
                  + "     , r.res_rsv_ruimte_afgerond"
                  + "     , ra.res_activiteit_notfrontend"
                  + "     , ra.res_activiteit_meteindtijd"
                  + "  FROM res_rsv_ruimte r,"
                  + "       res_activiteit ra"
               	+ " WHERE res_rsv_ruimte_key = " + rsv_ruimte_key
               	+ "   AND r.res_activiteit_key = ra.res_activiteit_key";
          var roRs = Oracle.Execute(sql);

          var rsv_ruimte_verwijder = roRs("res_rsv_ruimte_verwijder").Value;
          var res_reservering_key = roRs("res_reservering_key").Value;
          var alg_ruimte_key = roRs("alg_ruimte_key").Value;
          var status_fo_key = roRs("res_status_fo_key").Value;
          var status_bo_key = roRs("res_status_bo_key").Value;
          var rsv_ruimte_van = new Date(roRs("res_rsv_ruimte_van").Value);
          var rsv_ruimte_tot = new Date(roRs("res_rsv_ruimte_tot").Value);
          var rsv_ruimte_contact_key = roRs("res_rsv_ruimte_contact_key").Value;
          var rsv_ruimte_host_key = roRs("res_rsv_ruimte_host_key").Value;
          var res_activiteit_key = roRs("res_activiteit_key").Value;
          var res_goedgekeurd = roRs("res_rsv_ruimte_afgerond").Value == 1;
          var res_opstel_key = roRs("res_ruimte_opstel_key").Value;
          var notfrontend = roRs("res_activiteit_notfrontend").Value;
          var met_eindtijd = roRs("res_activiteit_meteindtijd").Value == 1;

          // Bepaal discipline parkeerplaatsen voor reserveringen. Deze moet uitgesloten worden voor de autorisatie controle.
          // Als de reservering *voor* earliest_expire ligt gaan we moeilijk doen
          var earliest_expire_changeCV = new Date(1000, 1, 1); // Wijzigingshorizon CV
          var earliest_expire_change = new Date(1000, 1, 1); // Wijzigingshorizon
          var earliest_expire_cancel = new Date(1000, 1, 1); // Annuleringshorizon

          var hasCV = false;
          var max_dbkosten = 0; // Maximale doorberekeningskosten van alle disciplines binnen een reservering;
          var canWritePresentPrk = true; // Kan ik de parkeerplaatsen die AANWEZIG zijn zien en wijzigen.
          var disc_key_arr = [];
          var expire_mode = (S("res_247organisation")==1? 2 : 0);

          if (typeof pdisc_key != "undefined" && pdisc_key != null) // alleen pdisc, ongeacht of je hem al had
          {
            disc_key_arr = [pdisc_key];
            var expire = res.discipline_expire(pdisc_key, expire_mode);
            if (expire > earliest_expire_change) earliest_expire_change = expire;
            var expire_cancel = res.discipline_expire(pdisc_key, 1);
            if (expire_cancel > earliest_expire_cancel) earliest_expire_cancel = expire_cancel;
            if (rsv_ruimte_van < expire_cancel)
            { // Doorberekeningskosten van de meegegeven discipline
              var sql = "SELECT rdp.res_disc_params_kosten"
                      + "  FROM res_disc_params rdp"
                   	  + " WHERE rdp.res_ins_discipline_key = " + pdisc_key;
              var oRs1 = Oracle.Execute(sql);
              max_dbkosten = oRs1("res_disc_params_kosten").Value;
              oRs1.Close();
            }
          }
          else // alle disciplines die je al had
          { // Bepaal alle CV disciplines waar we iets van hebben
            var discs = "SELECT DISTINCT ra.res_discipline_key"
                      + "     , res_disc_params_kosten"
                      + "  FROM res_rsv_artikel rra"
                      + "     , res_artikel ra"
                      + "     , res_disc_params rdp"
                      + " WHERE rra.res_artikel_key = ra.res_artikel_key"
                      + "   AND ra.res_discipline_key = rdp.res_ins_discipline_key"
                      + "   AND rra.res_rsv_ruimte_key = " + rsv_ruimte_key
                      + " UNION " // geen dubbelen nodig
                      + "SELECT res_discipline_key"
                      + "     , 0" // Voor voorziening reserveringen met alleen reserveerbare objecten zijn de kosten altijd 0 en wordt niets doorberekend. Kostendoorberekening alleen voor ruimten en catering.
                      + "  FROM res_deel rd, res_rsv_deel rrd"
                      + " WHERE rd.res_deel_key = rrd.res_deel_key"
                      + "   AND rrd.res_rsv_ruimte_key = " + rsv_ruimte_key;
            var oRs = Oracle.Execute(discs);

            if (!oRs.eof)
            {
              hasCV = true; // Er bestaan artikelen bij de reservering.
            }
            else
            { // Als er nog geen artikelen bij de reservering bestaan, dan kijken welke disciplines er mogelijk zijn.
              oRs.Close();
              // hasCV = false // we weten in deze tak al dat er geen CV is
              var discs = "SELECT rdp.res_ins_discipline_key res_discipline_key"
                        + "     , rdp.res_disc_params_kosten"
                        + "  FROM res_disc_params rdp"
                        + "    ,  res_v_aanwezigDISCIPLINE d"
                        + " WHERE ins_discipline_min_level = 2"
                        + "   AND d.ins_discipline_key IN"
                        + "      (SELECT ins_discipline_key"
                              + "   FROM fac_v_webgebruiker g"
                              + "      , fac_functie f"
                              + "  WHERE g.fac_functie_key = f.fac_functie_key"
                              + "    AND f.fac_functie_code IN ('WEB_RESFOF','WEB_RESBOF','WEB_RESUSE')"
                              + "    AND g.prs_perslid_key = " + user_key
                              + "    AND fac_gebruiker_prs_level_write < 9"
                              + "    AND fac_gebruiker_alg_level_write < 9"
                              + ")"
                        + "    AND d.ins_discipline_key IN"
                        + "       (SELECT res_discipline_key"
                               + "   FROM res_v_srtartikel_onrgoed"
                               + "  WHERE alg_ruimte_key IN"
                               + "       (SELECT alg_ruimte_key"
                                      + "   FROM res_v_rsv_ruimte_2_alg_ruimte "
                                      + "  WHERE res_alg_ruimte_verwijder IS NULL"
                                      + "    AND res_rsv_ruimte_key = " + rsv_ruimte_key
                                      + ")"
                               + ")"
                        + "   AND EXISTS (SELECT res_activiteit_key"
                                      + "   FROM res_activiteitdiscipline rad"
                                      + "  WHERE rad.res_discipline_key = d.ins_discipline_key"
                                      + "    AND rad.res_activiteit_key = " + res_activiteit_key
                                      + ")"
                        + " AND d.ins_discipline_key = rdp.res_ins_discipline_key";
              var oRs = Oracle.Execute(discs);
            }

            while (!oRs.eof)
            {
              // Nieuwe catering wordt clientside gecontroleerd of deze binnen de expire tijd zitten. Dat is dus zo anders kom je hier niet.
              // Disciplines met catering die binnen de expire tijd liggen kunnen niet open geklapt worden.
              // 1) Als de reservering al voorzieningen had (hasCV = true) dan bevat oRs de discipline van deze voorzieningen
              //    Voor deze disciplines de expire (en cancel) tijd bepalen.
              // 2) Als de reservering nog geen voorzieningen had (hasCV = false) dan bevat oRs alle disciplines waar user_key recht op heeft.
              //    Voor deze disciplines niet de expire (en cancel) tijd bepalen want je weet niet of er op deze disciplines nieuwe voorzieningen zijn geselecteerd.
              //    Als er nieuwe voorzieningen zijn geselecteerd dan vallen deze binnen de expire tijd (clientside gecontroleerd).
              var expireCV = res.discipline_expire(oRs("res_discipline_key").Value, expire_mode);
              var expire = expireCV;
              var expire_cancel = res.discipline_expire(oRs("res_discipline_key").Value, 1);
              if (hasCV)
              { // Wijzig earliest_expire_change, earliest_expire_changeCV en earliest_expire_cancel alleen als voor deze discipline ook echt catering/voorziening aanwezig is
                if (expireCV > earliest_expire_changeCV) earliest_expire_changeCV = expireCV;
                if (expire > earliest_expire_change) earliest_expire_change = expireCV;
                if (expire_cancel > earliest_expire_cancel) earliest_expire_cancel = expire_cancel;
                if ((rsv_ruimte_van < expire_cancel) && (max_dbkosten < oRs("res_disc_params_kosten").Value))
                { // Annuleringshorizon is verstreken en doorberekeningskosten zijn hoger dan die ik al heb (gevonden)
                  max_dbkosten = oRs("res_disc_params_kosten").Value;
                }
              }

              if (oRs("res_discipline_key").Value != S("vis_parking_key"))
                // Dicipline toevoegen
                disc_key_arr.push(oRs("res_discipline_key").Value);
              else
              { // Er is een parkeerplaats gereserveerd die ik niet moet toevoegen.
                // Heb ik lees en wijzig rechten op "WEB_BEZPRK" (Kan ik parkeerplaatsen zien en reserveren).
                var bresult = user.func_enabled("BEZ",
                                                -1, // discipline key
                                                -1, // alg_key
                                                user_key,
                                                false, // pessimist
                                                false); // isOptional

                // Kan ik de aanwezige parkeerplaatsen zien en reserveren (Alleen als parkeerplaatsen aanwezig zijn kan canWritePresentPrk false worden).
                canWritePresentPrk = bresult.canRead("WEB_BEZPRK") && bresult.canWrite("WEB_BEZPRK")
              }
              oRs.MoveNext();
            }
            oRs.Close();
          }

          // Na de disciplines van de voorzieningen nu als laatste de discipline van de ruimte van de reservering.
          var canWrite_ruimte = true;
          var res_ruimte_extern = false;
          if (alg_ruimte_key == null && (typeof pdisc_key == "undefined" || pdisc_key == null))
          { // "R" reservering, bepaal *ook* discipline van de ruimte en alg_ruimte_key
            var sql = "SELECT MIN (alg_ruimte_key) alg_ruimte_key"
                    + "     , rr.res_discipline_key"
                    + "     , rdp.res_disc_params_kosten"
                    + "     , rr.res_ruimte_extern_id"
                    + "  FROM res_ruimte_opstelling rro"
                    + "     , res_ruimte rr"
                    + "     , res_alg_ruimte rar"
                    + "     , res_disc_params rdp"
                    + " WHERE rar.res_ruimte_key = rr.res_ruimte_key "
                    + "   AND rr.res_ruimte_key = rro.res_ruimte_key "
                    + "   AND rr.res_discipline_key = rdp.res_ins_discipline_key"
                    + "   AND rro.res_ruimte_opstel_key = " + res_opstel_key;
            var sqlG =" GROUP BY res_discipline_key"
                    + "        , rdp.res_disc_params_kosten"
                    + "        , rr.res_ruimte_extern_id";
            var oRs = Oracle.Execute(sql + " AND res_alg_ruimte_verwijder IS NULL" + sqlG );
            if (oRs.Eof) // Voor als *alle* onderliggende res_rsv_ruimte verwijderd
                var oRs = Oracle.Execute( sql + sqlG );
            var expire = res.discipline_expire(oRs("res_discipline_key").Value, expire_mode);
            if (expire > earliest_expire_change) earliest_expire_change = expire;
            var expire_cancel = res.discipline_expire(oRs("res_discipline_key").Value, 1);
            if (expire_cancel > earliest_expire_cancel) earliest_expire_cancel = expire_cancel;
            if (max_dbkosten < oRs("res_disc_params_kosten").Value)
            { // De doorberekeningskosten zijn hoger dan die ik al heb (gevonden).
              if (((rsv_ruimte_van < expire_cancel) && (S("res_delete_ask_kosten") != 2)) || ((S("res_delete_ask_kosten") == 2) && (status_fo_key == 2)))
              { // Annuleringshorizon is verstreken OF definitieve reservering.
                max_dbkosten = oRs("res_disc_params_kosten").Value;
              }
            }
            var disc_key_r = oRs("res_discipline_key").Value;
            alg_ruimte_key = oRs("alg_ruimte_key").Value;
            res_ruimte_extern = oRs("res_ruimte_extern_id").Value != null;
            disc_key_arr.push(disc_key_r);
            oRs.Close();
            // Rechten op ruimte controleren met afzonderlijke query (niet samen met catering).
            // Dat gaat namelijk 'mis' als je *helemaal* geen rechten hebt voor de ruimte
            // Dan geven we je uiteindelijk impliciet wel leesrechten
            var auth_r = user.func_enabled2( "RES"
                                           , { ins_discipline_key: disc_key_r
                                             , alg_key: alg_ruimte_key
                                             , prs_key: roRs("res_rsv_ruimte_contact_key").Value
                                             , isOptional: true
                                             }
                                           );
            canWrite_ruimte = auth_r.anyfound &&
                                auth_r.canWrite("WEB_RESFOF") ||
                                auth_r.canWrite("WEB_RESBOF") ||
                                auth_r.canWrite("WEB_RESUSE");
          }

          var rresult = user.func_enabled2("RES"
                                           , { ins_discipline_key: disc_key_arr.join(",")
                                           , alg_key: alg_ruimte_key
                                           , prs_key: roRs("res_rsv_ruimte_contact_key").Value
                                           , isOptional: (params.isOptional? params.isOptional : false)
                                           , checkOptimistic: (params.checkOptimistic ? params.checkOptimistic : false)
                                           }
                                         );
          roRs.Close();
          // earliest_expire_change en earliest_expire_cancel hebben zeker een goede waarde van de ruimte discipline (startdatum 1/1/1000 is vervangen).
          rresult.earliest_expire_change = earliest_expire_change;
          rresult.earliest_expire_cancel = earliest_expire_cancel;
          // Voorkom dat in earliest_expire_changeCV startdatum 1/1/1000 blijft staan.
          // Als de earliest_expire_changeCV startdatum niet is gewijzigd, dan waren er geen artikelen geselecteerd. Dan op vandaag/nu zetten.
          // Clientsite wordt wel gecontroleerd welke catalogus opengeklapt mag worden.
          // MGE: Kunnen we niet initeel de waar de op vandaag/nu zetten?
          rresult.earliest_expire_changeCV = (earliest_expire_changeCV.getTime() == (new Date(1000, 1, 1)).getTime() ? new Date(): earliest_expire_changeCV);

          // Als er geen verbruiksartikelen zijn geboekt dan de canChange (ruimte) niet afhankelijk maken van de canChangeCV (artikelen).
          if (hasCV && rresult.earliest_expire_changeCV > rresult.earliest_expire_change)
            rresult.earliest_expire_change = rresult.earliest_expire_changeCV;

          rresult.max_dbkosten = max_dbkosten;
          rresult.res_reservering_key = res_reservering_key;
          rresult.rsv_ruimte_verwijder = rsv_ruimte_verwijder;
          rresult.res_met_eindtijd = met_eindtijd;

          rresult.canReadNoShow = rresult.canRead("WEB_RESNOS");
          rresult.canWriteNoShow = rresult.canWrite("WEB_RESNOS");

          // PAS OP!!!: WEB_RESMSU is disciplineloos dus niet in cresult! Daardoor zijn cresult.canWrite("WEB_RESMSU") en cresult.canRead("WEB_RESMSU") altijd false.
          var haveRESMSUrights = user.checkAutorisation("WEB_RESMSU", true) != null; // Pas op: disciplineloos dus niet in cresult!
          var haveRESAFRrights = user.checkAutorisation("WEB_RESAFR", true) != null; // Pas op: disciplineloos dus niet in cresult!

          if (S("facilities_flike_past") >= 0)
          {
              var vandaag = new Date();
              var flike_days = vandaag.getTime() - rsv_ruimte_tot.getTime();
              var flike_past = S("facilities_flike_past") * 1000 * 60 * 60 * 24;
              flike_days = (flike_days < 0 ? flike_past+1 : flike_days); // Als flike_days < 0 dan is de reservering nog niet afgelopen, dus mag nog niet worden beoordeeld
              rresult.canLike = (status_bo_key == 5 || status_bo_key == 6 || (flike_days < flike_past)) &&
                                (rsv_ruimte_contact_key == user_key || rsv_ruimte_host_key == user_key);
          }

          // -- CONTROLE LEESRECHTEN --
          // als disc_key_arr leeg is hebben we nog 'niets' en zijn we heel soepel
          rresult.canReadAny = !disc_key_arr.length||
                               rresult.canRead("WEB_RESBAC") ||
                               rresult.canRead("WEB_RESFOF") ||
                               rresult.canRead("WEB_RESBOF") ||
                               rresult.canRead("WEB_RESUSE");

          // (als alleen) Frontend-rechten-->we controleren nog strenger
          rresult.canReadFEOnly = false;
          if (    rresult.canReadAny
              && !rresult.canRead("WEB_RESBAC")
              && !rresult.canRead("WEB_RESFOF")
              && !rresult.canRead("WEB_RESBOF")
              )
          { // frontend controles
            rresult.canReadFEOnly = true;
            if (   rsv_ruimte_contact_key != user_key
                && rsv_ruimte_host_key != user_key
                && !user.isCollega(rsv_ruimte_contact_key)
                && !user.isCollega(rsv_ruimte_host_key))
            {
              rresult.canReadAny = false;// JGL: Kan dit nog legaal gebeuren?
            }
          }

          rresult.canReadFO = rresult.canRead("WEB_RESFOF");
          rresult.canReadBO = rresult.canRead("WEB_RESBOF");

          // -- CONTROLE SCHRIJFRECHTEN --
          // Je moet tenminste schrijfrechten hebben
          rresult.canChange = rresult.canReadAny &&
                              (rresult.canWrite("WEB_RESFOF") ||
                               rresult.canWrite("WEB_RESBOF") ||
                               rresult.canWrite("WEB_RESUSE")) &&
                               canWritePresentPrk &&
                               (!res_goedgekeurd || (res_goedgekeurd && haveRESAFRrights)) &&
                               (!notfrontend || rresult.canWrite("WEB_RESFOF") || rresult.canWrite("WEB_RESBOF"));
          rresult.canChangeCV = rresult.canChange;

          // als disc_key_arr leeg is hebben we nog 'niets' en zijn we heel soepel
          rresult.canChange = rresult.canChange || !disc_key_arr.length; // canChange is een boolean!
          rresult.canChangeUrole = (typeof urole == "undefined" ? "" : urole);  // (mld_close kent deze niet, en change-t ook niet interactief)

          if (rresult.canChange && rresult.canChangeUrole == "mi") // mi kan zeker niet wijzigen
          {
            if (rresult.canWrite("WEB_RESFOF"))
              rresult.canChangeUrole = "fo";
            else if (rresult.canWrite("WEB_RESBOF"))
              rresult.canChangeUrole = "bo";
            else
              rresult.canChangeUrole = "fe";
          }

          if (!rresult.canChange)
              rresult.readoReason = L("lcl_res_reado_parts"); // geen schrijfrechten op alle onderdelen

          // Vanaf hier heeft canChange betrekking op het bovenste deel van het scherm
          // canChangeCV gaat over het onderste deel
          rresult.canChange = rresult.canChange && canWrite_ruimte;

          // Had ik deze kunnen aanmaken? Dan mag hij als bron van multi's dienen
          rresult.couldCreate = rresult.canChange && !res_ruimte_extern;

          // Reserveringen met openstaande meldingen kunnen niet worden verwijderd.
          var openstaandemld = false;
          if (S("res_with_mld") == 1)
          { // Er zijn meldingen bij een reservering mogelijk
            var sql = "SELECT m.mld_melding_key"
                    + "  FROM mld_melding m"
                    + " WHERE mld_melding_status IN (0, 2, 3, 4, 7)" // Openstaande melding
                    + "   AND m.res_rsv_ruimte_key = " + rsv_ruimte_key;
            var oRs = Oracle.Execute(sql);
            if (!oRs.eof) // Er is nog een openstaande melding
              openstaandemld = true;
            oRs.Close();
          }

          rresult.canDelete = rresult.canChange && !openstaandemld && (!res_ruimte_extern || haveRESMSUrights); // Dan kun je ook alles er afhalen tenslotte met hetzelfde effect

          rresult.canChangeFEOnly = false;
          // (als alleen) Frontend-rechten-->we controleren nog strenger
          if (    rresult.canChange
              && !rresult.canWrite("WEB_RESFOF")
              && !rresult.canWrite("WEB_RESBOF")
              )
          { // frontend controles
            rresult.canChangeFEOnly = true;
            if (S("res_fe_edit_option_only") == 1 // Setting: Alleen opties mogen bewerkt
                && status_fo_key != 1)
            {
              rresult.canChange = false;
              rresult.readoReason = L("lcl_res_fe_edit_option_only");
            }

            if (rresult.canChange && rsv_ruimte_van < rresult.earliest_expire_change)
            {
              rresult.canChange = false;
              rresult.readoReason = L("lcl_res_reado_late"); // Ruimte of sommige voorzieningen mogen niet meer gewijzigd worden
            }
            rresult.canDelete = rresult.canChange && !openstaandemld && !res_ruimte_extern; // Annuleringshorizon (canDelete) i.p.v. wijzigingshorizon (canChange)

            if (rresult.canDelete && rsv_ruimte_van < rresult.earliest_expire_cancel)
            {
              rresult.canDelete = false;
            }

            var sql_d = "SELECT SUM(aantal) aantal_cv"
                      + "  FROM (SELECT count(*) aantal"
                              + "  FROM res_rsv_deel"
                              + " WHERE res_rsv_deel_verwijder IS NOT NULL"
                              + "   AND res_rsv_ruimte_key = " + rsv_ruimte_key
                              + " UNION "
                              + "SELECT count(*) aantal"
                              + "  FROM res_rsv_artikel"
                              + " WHERE res_rsv_artikel_verwijder IS NULL"
                              + "   AND res_rsv_ruimte_key = " + rsv_ruimte_key
                              + ")";
            oRs_d = Oracle.Execute(sql_d);
            if (oRs_d("aantal_cv").value > 0)
              rresult.canChangeCV = rresult.canChangeCV && (rsv_ruimte_van < rresult.earliest_expire_changeCV? false : true);
            oRs_d.Close();
            rresult.canChangeCV = (S("res_cat_with_expired_room") ? rresult.canChangeCV : rresult.canChange);

            if (   rresult.canChange && rresult.canChangeUrole != "fo"
                && rsv_ruimte_contact_key != user_key
                && !user.isCollega(rsv_ruimte_contact_key)
                && !user.isCollega(rsv_ruimte_host_key))
            { // JGL: Volgens mij kan dit niet meer legaal gebeuren sinds 5.1.2RC2
              rresult.canChange = false;
              rresult.couldCreate = false;
              rresult.readoReason = L("lcl_res_notself");
            }

            // Als FE mag je een aantal velden en kenmerken wel wijzigen in een reservering.
            rresult.canChangeFEExtended = false;
            // reservering is van mij of een collega (user.isCollega is altijd inclusief mezelf).
            if (   user.isCollega(rsv_ruimte_contact_key)
                || user.isCollega(rsv_ruimte_host_key)
               )
            {
              // status_bo is nog niet "afgemeld" (5) of "verwerkt" (6)
              // status_fo is niet "vervallen" (4)
              // en je bent nog niet voorbij de reserveringstijd
              if (status_bo_key < 5 && status_fo_key != 4 && rsv_ruimte_tot > new Date() )
              {
                rresult.canChangeFEExtended = true;
              }
            }
          }

          // Als *alle*  R/C/V onderdelen 'verwerkt' zijn dan worden we ook readonly
          // Let op: CV -reservering heeft geen bo-status in res_rsv_ruimte
          var sql1 = "SELECT rd.res_status_bo_key"
                   + "  FROM res_v_aanwezigrsv_deel rd"
                   + " WHERE rd.res_rsv_ruimte_key = " + rsv_ruimte_key
                   + " UNION "
                   + "SELECT ra.res_status_bo_key"
                   + "  FROM res_v_aanwezigrsv_artikel ra"
                   + " WHERE ra.res_rsv_ruimte_key = " + rsv_ruimte_key;

          var sql2 = "";
          if (res_opstel_key != null)
          {
            sql2 = "SELECT res_status_bo_key"
                 + "  FROM res_v_aanwezigrsv_ruimte rs"
                 + " WHERE rs.res_rsv_ruimte_key = " + rsv_ruimte_key;
          }
          sql = "SELECT MAX(res_status_bo_key) max_bo"
              + "     , MIN (res_status_bo_key) min_bo"
              + "  FROM (" + sql1 + (sql2 != ""? " UNION " + sql2 : "") + ")";

          oRs = Oracle.Execute(sql);
          rresult.min_bo = oRs("min_bo").Value;
          rresult.max_bo = oRs("max_bo").Value;
          oRs.Close();
          if (rresult.min_bo && rresult.min_bo == 6) // verwerkt
          {
            rresult.canChange = false;
            rresult.canChangeCV = false;
            rresult.readoReason = L("lcl_res_reado_completed"); // Alles is al verwerkt;
          }
          else if (res_opstel_key != null) // CV of R kan ook afzonderlijk al de bo status 6 hebben en dus niet meer wijzigbaar zijn.
          {                                // Maar dan moet het wel een ruimte reservering zijn. Dan heeft sql2 ook een waarde.
            sql = "SELECT MIN (res_status_bo_key) min_bo"
                + "  FROM (" + sql1 + ")";
            oRs = Oracle.Execute(sql);
            if (oRs("min_bo").Value && oRs("min_bo").Value == 6)
              rresult.canChangeCV = false;

            sql = "SELECT MIN (res_status_bo_key) min_bo"
                + "  FROM (" + sql2 + ")";
            oRs = Oracle.Execute(sql);
            if (oRs("min_bo").Value && oRs("min_bo").Value == 6)
              rresult.canChange = false;
            oRs.Close();
          }

          if (rresult.canDelete)
          { // Hij mag geen onderdelen hebben met status 6 (verwerkt)
            if (rresult.max_bo && rresult.max_bo == 6)
              rresult.canDelete = false;
          }

          rresult.canChangeKorting = rresult.canChange
                                   && (status_bo_key != 6)
                                   && (   (rresult.canWrite("WEB_RESFOF") && ((S("res_roompricingkorting") & 1) == 1))
                                       || (rresult.canWrite("WEB_RESBOF") && ((S("res_roompricingkorting") & 2) == 2))
                                      );

          rresult.canChangeTotaal = rresult.canChange
                                   && (status_bo_key != 6)
                                   && (   (rresult.canWrite("WEB_RESFOF") && ((S("res_roompricingtotaal") & 1) == 1))
                                       || (rresult.canWrite("WEB_RESBOF") && ((S("res_roompricingtotaal") & 2) == 2))
                                      );

          // Goedkeuren door aanmaker/besteller (of zijn/haar vervanger) nadat de reservering voorbij is (in het verleden ligt).
          rresult.canGoedkeur = ((S("res_approval") == 1 && rsv_ruimte_tot < new Date()) ||               // Setting res_approval = 1: Als reservering is afgelopen.
                                  (S("res_approval") == 2 && (rresult.min_bo && rresult.min_bo == 5))) && // Setting res_approval = 2: Als reservering is geheel verwerkt.
                                !res_goedgekeurd &&
                                rresult.canReadAny &&
                                (user.isCollega(rsv_ruimte_contact_key) || user.isCollega(rsv_ruimte_host_key));

          if ((status_bo_key && status_bo_key < S("res_bo_status_key")) || // ruimte is af te melden
              (rresult.min_bo && rresult.min_bo < S("res_bo_status_key"))) // CV is af te melden
          {
            rresult.canClose = rresult.canWrite("WEB_RESBOF"); // je moet wel rechten ervoor hebben
          }

          rresult.canWriteFlags = rresult.canWrite("WEB_RESBOF") || rresult.canWrite("WEB_RESFOF"); // Kan ik flags aanpassen.

          return rresult;
        },

        // fnStep kan bijvoorbeeld 'parent.planStep' zijn en wordt (clientside!) aangeroepen
        // als iemand 'eerder' of 'later' klikt met als parameter resp. (-1, -1) en (+1, +1)
        build_planbord_tijdbalk : function (res_van, nr_days, hour_px, p_res_t1, p_res_t2, fnStep)
        {
          var html = [];
          var size_px = hour_px * (p_res_t2-p_res_t1) + 1;
          for (dagen = 1; dagen <= nr_days; dagen++)
          {
            var dttxt = "";
            if (res_van)
            {
              var dt2 = new Date(res_van);
              dt2.setDate(dt2.getDate() + dagen - 1);
              dttxt = toDateString(dt2);
            }
            html.push("<td class='plantd tijdbalk' title='" + dttxt + "'><div class='tddiv' style='height:" + (dttxt? 32 : 16) + "px;'>");
            html.push("<div style='height:" + (dttxt? 32 : 16) + "px;width:" + size_px + "px'>&nbsp;</div>");

            var txtnp; // 'naast' de next-prev buttons
            if (hour_px < 16) // Komt het op eigen regel
              txtnp = ""
            else
              txtnp = "<span id='plandate'>"+dttxt+"</span>";

            if (fnStep && dagen==1) // eerste dag
            {
              txtnp = "<span title='" + L("lcl_res_plan_tt_meer_eerder")
                    + "' class='details' id='resplanmoreprev' onclick='" + fnStep + "(-1, 0)'>"
                    + I("fa-plus-circle")
                    + "</span> "
                    + txtnp;
              txtnp = "<span title='" + L("lcl_res_plan_tt_eerder")
                    + "' class='details' id='resplanprev' onclick='" + fnStep
                    + "(-1, -1)'>"
                    + I("fa-arrow-left")
                    + "</span> "
                    + txtnp;
            }
            if (fnStep && dagen==nr_days) // laatste dag
            {
              txtnp = txtnp + " <span title='"+L("lcl_res_plan_tt_meer_later")+"' class='details' id='resplanmorenext' onclick='"+fnStep+"(0, +1)'>"+I("fa-plus-circle")+"</span>";
              txtnp = txtnp + " <span title='"+L("lcl_res_plan_tt_later")+"' class='details' id='resplannext' onclick='"+fnStep+"(+1, +1)'>"+I("fa-arrow-right")+"</span>";
            }

            if (hour_px < 16 && dttxt) // alleen nog datum op losse regel
            {
              var px_start = (p_res_t2 - p_res_t1) / 2 * hour_px;
              var txt = dttxt;
              var width = 90;
              if (hour_px<8)
              {
                txt = txt.substring(0, 8); // haal zelfs jaar er af
                width=90;
              }
              var div = "<div class='plandate'"
                            +" style='left:" + (px_start-width / 2) + "px;width:" + width + "px'>"
                            +txt + "<br>" + txtnp + "</div>";
              html.push("\n" + div);
            }
            else
            {
              if (txtnp)
              {
                var div = "<div class='plandate' style='width:100%'>"
                            + txtnp + "</div>";
                html.push(div);
              }

              var skiphours = 1;
              if (hour_px < 48)
                skiphours = 2;
              if (hour_px < 24)
                skiphours = 3;

              var s = p_res_t1;
              var i0 = -2;
              for (s = p_res_t1; s <= p_res_t2; s += skiphours)
              {
                var h = Math.floor(s);
                if (h < 10) h = "0" + h;
                var m = Math.floor((s - h)*60);
                if (m < 10) m = "0" + m;
                m = ":" + m

                var px_start = (s - p_res_t1) * hour_px;
                var div = "<div class='plantime'"
                              +" style='left:" + (px_start - 19) + "px; top:" + (dttxt? 16 : 0) + "px'>"
                              +h + m + "</div>";
                html.push("\n"+div);
              }
            }

            html.push("</div></td>");
            if (dagen<nr_days)
            {
              html.push('<td class="between"><div>&nbsp;</div></td>');
            }
            else
            {
              html.push('<td class="post"><div>&nbsp;</div></td>');
            }
          } // dagen
          return html.join("");
        },

        // Deze view wil ik graag nog kwijt (of vooral: fac_v_my_res_rooms wil ik kwijt)
        // Met 5.3.1 geen tijd meer ervoor..
        fac_v_my_res_ruimte_write: // 'view' die je rechten op een res_ruimte oplevert
             "(SELECT  rr.res_ruimte_key,"
           + "         fr.prs_perslid_key,"
           + "         g.fac_functie_key,"
           + "         rr.res_ruimte_verwijder"
           + "  FROM   res_ruimte rr,"
           + "         fac_v_my_res_rooms fr,"
           + "         fac_v_webgebruiker g"
           + " WHERE   rr.res_ruimte_key = fr.res_ruimte_key"
           + "     AND fr.prs_perslid_key = g.prs_perslid_key"
           + "     AND niveau = g.fac_gebruiker_alg_level_write"
           + "     AND rr.res_discipline_key = g.ins_discipline_key"
           + "     AND g.fac_gebruiker_alg_level_write < 9)",

        insert_reservering: function _insert_reservering(params)
        {
          // Placeholder, invulling te geven en te gebruiken vanuit de API en dergelijke
          // fields = ...
          /*
                  var resIns = buildInsert("res_reservering", resfields);
              var reservering_key = resIns.sequences["res_reservering_key"];
              var volgnr = 1;
              Oracle.Execute(resIns.sql);
          */
        },

        get_rooms_for_planbord: function _get_rooms_for_planbord(params)
        {
          if (params.alg_locatie_plaats)
          {
            params.loc_key_arr = [-1]; // Voorkom dat we 'alles' krijgen bij niet gevonden plaats
            var sql = "SELECT alg_locatie_key"
                    + "  FROM alg_v_aanweziglocatie"
                    + " WHERE UPPER(alg_locatie_plaats) LIKE " + safe.quoted_sql_wild(params.alg_locatie_plaats + '%');
            var oRs = Oracle.Execute(sql);
            while (!oRs.Eof)
            {
              params.loc_key_arr.push(oRs("alg_locatie_key").Value);
              oRs.MoveNext();
            }
            oRs.Close();
          }
          params.ruimtecat = params.ruimtecat || [];
          params.loc_key_arr = params.loc_key_arr || [];
          var sql_loc_from = "";
          var sql_loc_where = "";
          if (params.rui > 0)
            sql_loc_from = ", alg_ruimte r";
          else if (params.dist < 0 && params.reg > 0)
            sql_loc_from = ", alg_district d";

          if ((params.res_ruimte_key > 0) && (params.loc > 0))
            sql_loc_where = " AND fr.res_ruimte_key = " + params.res_ruimte_key;
          if (params.rui > 0)
            sql_loc_where = " AND r.alg_verdieping_key = rg.alg_verdieping_key"
                          + " AND r.alg_ruimte_key = " + params.rui;
          else if (params.ver > 0)
            sql_loc_where = " AND rg.alg_verdieping_key = " + params.ver;
          else if (params.bld > 0)
            sql_loc_where = " AND rg.alg_gebouw_key = " + params.bld;
          else if (params.loc > 0)
            sql_loc_where = " AND rg.alg_locatie_key = " + params.loc;
          else if (params.loc_key_arr.length)
            sql_loc_where = " AND rg.alg_locatie_key IN (" + params.loc_key_arr.join(", ") + ")";
          else if (params.dist > 0)
            sql_loc_where = " AND l.alg_district_key = " + params.dist;
          else if (params.reg > 0)
            sql_loc_where = " AND l.alg_district_key = d.alg_district_key"
                          + " AND d.alg_regio_key = " + params.reg;

          var sati_sql = "  (SELECT AVG(res_rsv_ruimte_satisfaction) "
                       + "     FROM res_rsv_ruimte rrr, res_ruimte_opstelling rro"
                       + "    WHERE rrr.res_ruimte_opstel_key = rro.res_ruimte_opstel_key"
                       + "      AND rro.res_ruimte_key = fr.res_ruimte_key"
                       + "      AND res_rsv_ruimte_van > SYSDATE - 180)";

          var ora_date1 = params.res_van.beginToSQL();
          // RES_RUIMTE informatie
          // Deze query bepaalt het aantal regels in het planbord
          var sql = "SELECT DISTINCT fr.res_ruimte_key"
                  + "     , " + lcl.xsqla('rg.res_ruimte_nr', 'rg.res_ruimte_key')
                  + "     , rg.res_ruimte_volgnummer"
                  + "     , bmin"
                  + "     , bmax"
                  + "     , alg_locatie_omschrijving"
                  + "     , alg_locatie_plaats"
                  + "     , alg_locatie_code"
                  + "     , " + lcl.xsqla('fr.res_ruimte_omschrijving', 'fr.res_ruimte_key')
                  + "     , fr.res_ruimte_begintijd"
                  + "     , fr.res_ruimte_eindtijd"
                  + "     , fr.res_ruimte_begintijdblok"
                  + "     , fr.res_ruimte_eindtijdblok"
                  + "     , fr.res_ruimte_image"
                  + "     , res_disc_params_preposttime"
                  + "     , fr.res_ruimte_prijs"
                  + "     ," + (params.with_satisfaction?sati_sql:"0") + " satisfaction"
                  + "     , rg.res_discipline_key"
                  + "     , fr.res_ruimte_extern_id"
                  + "  FROM alg_locatie l"
                  + "     , alg_gebouw g"
                  + "     , alg_srtgebouw sg"
                  + "     , opstel_bez"
                  + "     , res_ruimte fr"
                  + "     , res_v_res_ruimte_gegevens rg"
                  + "     , res_disc_params"
                  + sql_loc_from
                  + " WHERE rg.alg_locatie_key = l.alg_locatie_key "
                  + "   AND rg.alg_gebouw_key = g.alg_gebouw_key "
                  + "   AND sg.alg_srtgebouw_key(+) = g.alg_srtgebouw_key"
                  + "   AND sg.alg_srtgebouw_passief IS NULL"
                  + "   AND fr.res_ruimte_key = opstel_bez.res_ruimte_key"
                  + "   AND fr.res_ruimte_key = rg.res_ruimte_key"
                  + "   AND fr.res_ruimte_verwijder IS NULL"
                  + "   AND res_disc_params.res_ins_discipline_key = rg.res_discipline_key "
                  + "   AND (fr.res_ruimte_vervaldatum IS NULL OR fr.res_ruimte_vervaldatum > " + ora_date1 + ")"
                  + sql_loc_where;

          // Altijd 3D Rechten ook meenemen
          sql = discxalg3d (sql,
                            "fr.res_discipline_key",
                            "rg.alg_regio_key",
                            "rg.alg_district_key",
                            "rg.alg_locatie_key",
                            "rg.alg_gebouw_key",
                            "rg.alg_verdieping_key",
                            "rg.alg_ruimte_key",
                            params.authparams.autfunction,
                            params.ruimtecat.length?params.ruimtecat.join(",") : '',
                            params.forSelectRoom, // forWrite
                            3); // minlevel 3: alleen ruimte catalogie beschouwen

          sql = "WITH opstel_bez"
              + "  AS (SELECT res_ruimte_key"
              + "           , MIN (res_ruimte_opstel_bezoekers) bmin"
              + "           , MAX (res_ruimte_opstel_bezoekers) bmax"
              + "        FROM res_ruimte_opstelling"
              + "       WHERE res_ruimte_opstel_verwijder IS NULL"
              + "      GROUP BY  res_ruimte_key)"
              + " SELECT * FROM (" + sql + ") rg WHERE"; // 1 = 1;

          sql += (params.init_res_ruimte_key ? " (rg.res_ruimte_key = " + params.init_res_ruimte_key + ") OR" : "") + " (1 = 1"
               + (params.ruimtecat.length ? " AND res_discipline_key IN (" + params.ruimtecat.join(",") + ")" : "")
               + ((params.res_ruimte_key && !params.forSelectRoom)? " AND rg.res_ruimte_key = " + params.res_ruimte_key : "")
               + ((params.vis)? " AND bmax >= " + params.vis : "")
               + ((params.activiteit_key)
                 ? " AND EXISTS (SELECT res_activiteit_key FROM res_activiteitdiscipline rad"
                   + "            WHERE rad.res_discipline_key = rg.res_discipline_key"
                   + "              AND rad.res_activiteit_key = " + params.activiteit_key + ")"
                 : "")
               + ((!params.activiteit_key && params.srtact > 0)
                 ? " AND EXISTS (SELECT ra.res_activiteit_key"
                   + "  FROM res_activiteitdiscipline rad"
                   + "     , res_activiteit ra"
                   + " WHERE rad.res_discipline_key = rg.res_discipline_key"
                   + "   AND rad.res_activiteit_key = ra.res_activiteit_key"
                   + "   AND ra.res_srtactiviteit_key = " + params.srtact + ")"
                 : "")
               + ")"
               + " ORDER BY "
               + " rg.res_ruimte_volgnummer,"  // Sorteren op volgnummer gaat voor, dan afhankelijk van instelling res_room_order:
               + (S("res_room_order") == 0
                 ? (params.forSelectRoom? " bmin," : "") + lcl.xsql('rg.res_ruimte_nr', 'rg.res_ruimte_key') + ", alg_locatie_omschrijving"// Eerst sorteren op capaciteit
                 : lcl.xsql('rg.res_ruimte_nr', 'rg.res_ruimte_key') + ", alg_locatie_omschrijving" + (params.forSelectRoom? ", bmin" : "")); // Eerst sorteren op ruimte nummer
          return sql;
        },

        get_deepurl: function (params)
        {
          if (params.mobile)
          {
            var deepurl = HTTP.urlzelf() + "/pda/reservering.asp";
          }
          else
          {
            var deepurl = HTTP.urlzelf() + "/?fac_id=" + customerId + "&u=reservering";
          }
          return deepurl;
        },

        // Levert voldoende informatie op voor een planbord voor een bepaalde periode
        // In het bijzonder: alle mogelijke zalen en de bestaande bezetting
        // Let op: S("res_t1") en S("res_t2") kunnen tijdelijk gewijzigd zijn
        plan_rooms_info: function _plan_rooms_info (params)
        {
          var sql = res.get_rooms_for_planbord(params);

          // Verklein de setting S("res_t1") en S("res_t2") indien geen enkele ruimte zo ruim mag.
          var sqlminmax = "SELECT MIN(coalesce(res_ruimte_begintijd," + S("res_t1") + ")) tmin"
                        + "     , MAX(coalesce(res_ruimte_eindtijd, " + S("res_t2") + ")) tmax"
                        + "  FROM (" + sql + ")";
          var oRs = Oracle.Execute(sqlminmax);
          settings.overrule_setting("res_t1", oRs("tmin").Value || S("res_t1"));
          settings.overrule_setting("res_t2", oRs("tmax").Value || S("res_t2"));

          // Extra wrapper omdat ADO anders komt met 'Operation not allowed when object is closed'
          // Blijkbaar snapt die de WITH constructie vooraan niet zo
          sql = "SELECT * FROM (" + sql + ")"
          oRs = Oracle.Execute(sql);

          var all_rooms = []; // Hierin bouwen alle ruimtes op
          if (params.get_deepurl)
          { // Bepaal de URL waarmee straks een boeking uitgevoerd kan worden
            var deepurl = res.get_deepurl(params);
          }

          while (!oRs.eof)
          {
            var room = { res_ruimte_key: oRs("res_ruimte_key").Value,
                         res_ruimte_nr : oRs("res_ruimte_nr").Value,
                         bMin          : oRs("bMin").Value,
                         bMax          : oRs("bMax").Value,
                         prepost_time  : oRs("res_disc_params_preposttime").Value,
                         begintijd     : oRs("res_ruimte_begintijd").Value || S("res_t1"),
                         eindtijd      : oRs("res_ruimte_eindtijd").Value || S("res_t2"),
                         begintijdblok : oRs("res_ruimte_begintijdblok").Value,
                         eindtijdblok  : oRs("res_ruimte_eindtijdblok").Value,
                         image         : oRs("res_ruimte_image").Value,
                         omschrijving  : oRs("res_ruimte_omschrijving").Value,
                         locatie       : oRs("alg_locatie_omschrijving").Value,
                         plaats        : oRs("alg_locatie_plaats").Value,
                         satisfaction  : oRs("satisfaction").Value,
                         prijs         : oRs("res_ruimte_prijs").Value,
                         extern_id     : oRs("res_ruimte_extern_id").Value
                        };
            if (room.begintijd < S("res_t1")) room.begintijd = S("res_t1");
            if (room.eindtijd > S("res_t2")) room.eindtijd = S("res_t2");
            if (room.begintijd > room.eindtijd) room.begintijd = room.eindtijd;
            if (params.get_deepurl)
            {
              room.deepurl = deepurl;
              if (oRs("res_ruimte_image").Value)
                  room.image_url = HTTP.urlzelfnoroot() + S("res_image_path") + oRs("res_ruimte_image").Value;
            }
            else
            {
              if (oRs("res_ruimte_image").Value)
                  room.image_url = S("res_image_path") + oRs("res_ruimte_image").Value;
            }
            all_rooms.push(room);
            oRs.MoveNext();
          }
          oRs.Close();
          __Log("done plan_rooms_info met {0} ruimtes.".format(all_rooms.length));
          return all_rooms;
        },
        //
        // Bestaande reserveringen
        //
        // Voor de API zetten we params.anonymous zodat minimale informatie wordt opgeleverd
        // TODO: Of op basis van fe-geheim?
        plan_bezet_info: function _plan_bezet_info (params)
        {
          // Pas allerlei filtering toe om te zorgen dat we niet te veel ophalen
          // Dat is slecht voor de performance en (API) misschien geheim
          var room_sql = res.get_rooms_for_planbord(params); // Deze heeft ook autorisatie in zich
          var deepurl = res.get_deepurl(params);

          // Determine all existing reservations and store them in an array 'existing_res'
          var sql = "SELECT DISTINCT rv.res_rsv_ruimte_van"
                  + "     , rv.res_rsv_ruimte_tot"
                  + "     , rv.res_rsv_ruimte_dirtlevel"
                  + "     , res_ruimte_key_1"
                  + "     , rv.res_status_fo_key"
                  + "     , rv.res_rsv_ruimte_flag"
                  + "     , rv.res_reservering_key"
                  + (params.anonymous?", res_srtactiviteit_prefix || TO_CHAR (rv.res_reservering_key) || '/' || rv.res_rsv_ruimte_volgnr resid":"")
                  + "     , res_rsv_ruimte_volgnr"
                  + "     , rv.res_rsv_ruimte_key"
                  + "     , res_ruimte_key_2"
                  + "     , rv.res_rsv_ruimte_host_key"
                  + "     , rv.res_rsv_ruimte_contact_key"
                  + "     , rv.res_rsv_ruimte_omschrijving"
                  + "     , (SELECT count(1) FROM res_v_aanwezigrsv_artikel rra"
                  + "         WHERE rra.res_rsv_ruimte_key = rv.res_rsv_ruimte_key) aantalA" // dat de CV dirty kan zijn negeren we.
                  + "     , (SELECT count(1) FROM res_v_aanwezigrsv_deel rrd"
                  + "         WHERE rrd.res_rsv_ruimte_key = rv.res_rsv_ruimte_key"
                  + "           AND rrd.bez_bezoekers_key IS NULL) aantalD" // Uitsluiten parkeerplaatsen met een bez_bezoekers_key.
                  + "  FROM res_v_aanwezigrsv_ruimte rv"
                  + "     , res_ruimte_opstelling ro"
                  + (params.anonymous?", res_activiteit ra, res_srtactiviteit rsa":"")
                  // ///////Koppelzalen meenemen want daardoor kunnen we ook bezet raken
                  + "     ,  ( SELECT DISTINCT r1.res_ruimte_key res_ruimte_key_1, r2.res_ruimte_key res_ruimte_key_2, r2.alg_ruimte_key "
                  + "            FROM res_alg_ruimte r1, res_alg_ruimte r2 "
                  + "           WHERE r1.alg_ruimte_key = r2.alg_ruimte_key"
                  + "             AND r1.res_alg_ruimte_verwijder IS NULL"
                  + "             AND r2.res_alg_ruimte_verwijder IS NULL"
                  + "        ) X"
                  + " WHERE rv.res_rsv_ruimte_van between " + params.res_van.toSQL() + " AND " + params.res_tot.endToSQL()
                  + "   AND rv.res_ruimte_opstel_key = ro.res_ruimte_opstel_key"
                  + "   AND X.res_ruimte_key_2 = ro.res_ruimte_key"
                  + (params.anonymous
                    ? " AND rv.res_activiteit_key = ra.res_activiteit_key"
                     + " AND ra.res_srtactiviteit_key = rsa.res_srtactiviteit_key"
                     : "")
                  + (params.res_ruimte_key && !params.forSelectRoom
                    ? " AND res_ruimte_key_1=" + params.res_ruimte_key
                    : "")
                  + "   AND res_ruimte_key_1 IN (SELECT res_ruimte_key FROM (" + room_sql + "))"
                  + " ORDER BY res_ruimte_key_1"
                  + "        , (res_rsv_ruimte_tot - res_rsv_ruimte_van) DESC" // langste reserveringen eerst/onderop
                  + "        , rv.res_rsv_ruimte_dirtlevel ASC"

          var existing_res = {};
          var nnres = 0;
          var nnroom = 0;
          var oRs = Oracle.Execute(sql);
          while (!oRs.eof)
          {
            nnres ++;
            var res_ruimte_key = oRs("res_ruimte_key_1").Value;
            if (!existing_res[res_ruimte_key])
            {
              // if (S("res_plan_max_bezet") > 0 && nnres > S("res_plan_max_bezet"))
              //    abort_with_warning("Dit kost veel te veel geheugen");
              // 105282 bezettingen was ongeveer 280 MB geheugen
              existing_res[res_ruimte_key] = [];
              nnroom++;
            }
            // existing_res kan bij grotere periodes heel groot worden (100.000+ records)
            // Daarom is data zwaar geoptimaliseerd: geen properties zetten die 0/null zijn
            var data = { res_van      : new Date(oRs("res_rsv_ruimte_van").Value),
                         res_tot      : new Date(oRs("res_rsv_ruimte_tot").Value),
                         fo_status    : oRs("res_status_fo_key").Value
                       };
            if (oRs("res_rsv_ruimte_flag").Value)
              data.flag_status = oRs("res_rsv_ruimte_flag").Value;
            if (oRs("res_ruimte_key_1").Value != oRs("res_ruimte_key_2").Value)
              data.na_koppel = 1;
            if (oRs("res_rsv_ruimte_dirtlevel").Value)
              data.dirtlevel = oRs("res_rsv_ruimte_dirtlevel").Value;

            if (params.anonymous && data.dirtlevel > 0)
            {
              oRs.MoveNext();
              continue; // die zijn niet nodig
            }

            if (!params.anonymous)
            {
              data.rsv_ruimte_key  = oRs("res_rsv_ruimte_key").Value;
              data.host_key = oRs("res_rsv_ruimte_host_key").Value;
              if (oRs("res_rsv_ruimte_contact_key").Value != data.host_key)
                data.contact_key = oRs("res_rsv_ruimte_contact_key").Value;
              if (oRs("aantalA").Value)
                data.aantalA = oRs("aantalA").Value;
              if (oRs("aantalD").Value)
                data.aantalD = oRs("aantalD").Value;
            }
            else if (oRs("res_rsv_ruimte_host_key").Value == params.prs_key || oRs("res_rsv_ruimte_contact_key").Value == params.prs_key)
            {
              // Tooltip via res_load_title_rsv.asp is te complex, zou een api zijn
              data.extern = { res_nr       : oRs("resid").Value,
                              omschrijving : oRs("res_rsv_ruimte_omschrijving").Value,
                              deepurl      : deepurl + "&k=" + oRs("res_rsv_ruimte_key").Value
                            }
            }
            existing_res[res_ruimte_key].push(data);
            oRs.MoveNext();
          }
          oRs.close();
          __Log("done plan_bezet_info met {0} bezettingen voor {1} ruimtes.".format(nnres, nnroom));

          return existing_res;
        },
        //
        // Bestaande res_deel reserveringen
        //
        //
        plan_deel_bezet_info: function _plan_deel_bezet_info (params)
        {
          var existing_res = {};

          var ora_date1 = params.res_van.beginToSQL();
          var ora_date2 = params.res_tot.endToSQL();
          var disc_key = params.disc_key; // Nog verplicht
          // var res_ruimte_key = params.res_ruimte_key||-1;

          // Determine all existing reserved objects and store them in an array 'existing_res'
          // Via gewone reserveringen, die hebben res_rsv_ruimte_key
          sql = "SELECT rrd.res_rsv_deel_van"
              + "     , rrd.res_rsv_deel_tot"
              + "     , rd.res_deel_key"
              + "     , rr.res_status_fo_key"
              + "     , rr.res_rsv_ruimte_flag"
              + "     , rrd.res_rsv_deel_key"
              + "     , rrd.res_rsv_deel_dirtlevel"
              + "     , rrd.res_rsv_ruimte_key"
              + "     , rr.alg_ruimte_key"
              + "     , rr.res_rsv_ruimte_host_key "
              + "     , rr.res_rsv_ruimte_contact_key "
              + "     , -1 bez_bezoekers_key"
              + "     , rrd.res_status_bo_key"
              + "     , rd.res_ins_deel_key"
              + "     , rd.res_discipline_key"
              + "     , 1 type"
              + "     , rr.res_reservering_key"
              + "  FROM res_v_aanwezigrsv_ruimte rr"
              + "     , res_v_aanwezigdeel rd"
              + "     , res_v_aanwezigrsv_deel rrd"
              + " WHERE rrd.res_rsv_deel_tot >= " + ora_date1
              + "   AND rrd.res_rsv_deel_van <= " + ora_date2
              + "   AND rrd.res_deel_key = rd.res_deel_key"
              + "   AND rrd.res_rsv_ruimte_key = rr.res_rsv_ruimte_key"
              + "   AND rd.res_discipline_key = " + disc_key
              // Gereserveerde objecten uit andere disciplines maar hetzelfde object
              // (zelfde ins_deel) als object in gekozen discipline
              // JGL: Dit is vergelijkbaar met koppelzalen bij ruimtes. De code daarvan heeft lost het
              //      anders op met na_koppel. Ooit hier herzien?
              + " UNION "
              + "SELECT DISTINCT rrd.res_rsv_deel_van"
              + "     , rrd.res_rsv_deel_tot"
              + "     , rd.res_deel_key"
              + "     , rr.res_status_fo_key"
              + "     , rr.res_rsv_ruimte_flag"
              + "     , rrd.res_rsv_deel_key"
              + "     , rrd.res_rsv_deel_dirtlevel"
              + "     , rrd.res_rsv_ruimte_key"
              + "     , rr.alg_ruimte_key"
              + "     , rr.res_rsv_ruimte_host_key "
              + "     , rr.res_rsv_ruimte_contact_key "
              + "     , -1 bez_bezoekers_key"
              + "     , rrd.res_status_bo_key"
              + "     , rd.res_ins_deel_key"
              + "     , rd.res_discipline_key"
              + "     , 2 type"
              + "     , rr.res_reservering_key"
              + "  FROM res_v_aanwezigrsv_ruimte rr"
              + ",      res_v_aanwezigdeel rd"
              + ",      res_v_aanwezigrsv_deel rrd"
              + ",      res_v_aanwezigdeel rd2"
              + " WHERE rrd.res_rsv_deel_tot >= " + ora_date1
              + "   AND rrd.res_rsv_deel_van <= " + ora_date2
              + "   AND rrd.res_deel_key = rd.res_deel_key"
              + "   AND rrd.res_rsv_ruimte_key = rr.res_rsv_ruimte_key"
              + "   AND rd.res_discipline_key != " + disc_key
              + "   AND rd.res_ins_deel_key = rd2.res_ins_deel_key"
              + "   AND rd2.res_discipline_key = " + disc_key;

          // MGE: Als S("vis_parking_key") een waarde heeft dan wordt de discipline niet getoond
          //      door toevoeging van AND aan querie in res_edit_objcat.asp "(AND dis.ins_discipline_key != " + S("vis_parking_key"))
          //      Dan is de dicipline parkeerplaatsen dus nooit aanwezig is volgende if
          //      voorwaarde dus nooit true omdat disc_key van parkeerplaaten nooit voorkomt.
          //      Lijkt me dat de volgende if dus verwijderd kan worden??
          if (disc_key == S("vis_parking_key"))
            sql = sql + " UNION ALL " //Die via afspraak/bezoekers (parkeerplaatsen)
                + "SELECT rrd.res_rsv_deel_van"
                + "     , rrd.res_rsv_deel_tot"
                + "     , rd.res_deel_key"
                + "     , 2 res_status_fo_key" // altijd definitief
                + "     , 0 res_rsv_ruimte_flag" // hier niet van toepassing
                + "     , rrd.res_rsv_deel_key"
                + "     , rrd.res_rsv_deel_dirtlevel"
                + "     , rrd.res_rsv_ruimte_key"
                + "     , -1 alg_ruimte_key"
                + "     , -1 res_rsv_ruimte_host_key "
                + "     , -1 res_rsv_ruimte_contact_key "
                + "     , rrd.bez_bezoekers_key"
                + "     , rrd.res_status_bo_key"
                + "     , rd.res_ins_deel_key"
                + "     , rd.res_discipline_key"
                + "     , 1 type"
                + "     , 0 res_reservering_key"
                + "  FROM res_v_aanwezigdeel rd"
                + "     , res_v_aanwezigrsv_deel rrd"
                + "     , bez_bezoekers b"
                + "     , bez_afspraak a"
                + " WHERE rrd.res_rsv_deel_tot >= " + ora_date1
                + "   AND rrd.res_rsv_deel_van <= " + ora_date2
                + "   AND rrd.res_deel_key = rd.res_deel_key"
                + "   AND rrd.bez_bezoekers_key = b.bez_bezoekers_key"
                + "   AND b.bez_afspraak_key = a.bez_afspraak_key"
                + "   AND rrd.res_rsv_ruimte_key IS NULL"
                + "   AND rd.res_discipline_key = " + disc_key

          sql += " ORDER BY type"
          var oRs = Oracle.Execute(sql);
          while (!oRs.eof)
          {
            var ins_d_key = oRs("res_ins_deel_key").Value;

            if (!existing_res[ins_d_key]) existing_res[ins_d_key] = new Array();

            // In het bijzondere geval als initeel meerdere res_delen van hetzelfde ins_deel worden geselecteerd kunnen meerdere regels voor een res_deel worden opgeleverd.
            var doubledeel = false;
            if (oRs("type").Value == 2) {
                for (var i = 0; i < existing_res[ins_d_key].length; i++)
                { // Controleer op dezelfde rsv_ruimte_key. Dan te maken met dezelfde deelreservering
                  //__Log ("Checking ins/res_deel " + ins_d_key+"/"+oRs("res_deel_key").Value + "("+i+"): "+ existing_res[ins_d_key][i].rsv_ruimte_key + " vs. " +oRs("res_rsv_ruimte_key").Value);
                  if (existing_res[ins_d_key][i].rsv_ruimte_key == oRs("res_rsv_ruimte_key").Value)
                  { // Er was al een res_deel van hetzelfde ins_deel aanwezig in deze lijst. Deze bevat al de juiste waarden.
                    // __Log(".. double");
                    doubledeel = true;
                    break;
                  }
                }
            }

            if (!doubledeel)
            {
              existing_res[ins_d_key].push({res_deel_key: oRs("res_deel_key").Value,
                                            res_van: new Date(oRs("res_rsv_deel_van").Value),
                                            res_tot: new Date(oRs("res_rsv_deel_tot").Value),
                                            rsv_ruimte_key: oRs("res_rsv_ruimte_key").Value,
                                            status_fo_key: oRs("res_status_fo_key").Value,
                                            dirtlevel: oRs("res_rsv_deel_dirtlevel").Value,
                                            rsv_deel_key: oRs("res_rsv_deel_key").Value,
                                            alg_ruimte_key: oRs("alg_ruimte_key").Value,
                                            bez_key: oRs("bez_bezoekers_key").Value,
                                            host_key: oRs("res_rsv_ruimte_host_key").Value,
                                            contact_key: oRs("res_rsv_ruimte_contact_key").Value,
                                            res_status: oRs("res_status_bo_key").Value,
                                            res_flag: oRs("res_rsv_ruimte_flag").Value || 0,
                                            res_key: oRs("res_reservering_key").Value
                                           });
            }

            oRs.MoveNext();
          }
          oRs.close();

          return existing_res;
        },

        // Wordt bij Share&Meet via API opgevraagd
        user_lopend_info: function _user_lopend_info (params)
        {
          var showall = false; //  TODO?
          // ....
          var sql = "SELECT rr.res_rsv_ruimte_host_key prs_perslid_key"
                  + "     , res_srtactiviteit_prefix || TO_CHAR (rr.res_reservering_key) || '/' || rr.res_rsv_ruimte_volgnr item"
                  + "     , rr.res_rsv_ruimte_key item_key"
                  + "     , 'reservering' xmlnode"
                  + "     , rr.res_rsv_ruimte_van datum"
                  + "     , COALESCE (rr.res_rsv_ruimte_omschrijving, " + lcl.xsql('ra.res_activiteit_omschrijving', 'res_activiteit_key') + ") detail"
                  + "     , ' ' status"
                  + "     , res_rsv_ruimte_satisfaction satisfaction"
                  + "     , res_rsv_ruimte_satisfaction_op satisfaction_op"
                  + "     , DECODE (res_rsv_ruimte_dirtlevel, 0, rr.res_status_fo_key, -1) status_key"
                  + "     , rr.res_status_bo_key bo_status_key"
                  //+ sqlTracking("rr.res_rsv_ruimte_key","reservering")
                  + "  FROM res_v_aanwezigrsv_ruimte rr"
                  + "     , res_activiteit ra"
                  + "     , res_srtactiviteit rsa"
                  + "     , fac_tracking tr"
                  + "     , fac_srtnotificatie str"
                  + " WHERE rr.res_activiteit_key = ra.res_activiteit_key"
                  + "   AND ra.res_srtactiviteit_key = rsa.res_srtactiviteit_key"
                  + "   AND (   (    rr.res_status_bo_key IN (4, 3, 2, 7))"
                  + "        OR (    rr.res_status_bo_key IN (5, 6) "
                  + "            AND tr.fac_tracking_datum > SYSDATE - " + S("facilities_flike_past")
                  + "            AND str.fac_srtnotificatie_code = 'RESAFM')"
                  + "        OR (    rr.res_status_bo_key NOT IN (5, 6) "
                  + "            AND rr.res_rsv_ruimte_tot > SYSDATE - " + S("facilities_flike_past") + ")"
                  + "       )"
                  + "   AND rr.res_rsv_ruimte_key = tr.fac_tracking_refkey(+)"
                  + "   AND tr.fac_srtnotificatie_key = str.fac_srtnotificatie_key(+)"
                  + (!showall
                    ? " AND (rr.res_rsv_ruimte_van BETWEEN SYSDATE - " + S("facilitiespast_res") + " AND SYSDATE + " + S("facilitiesfuture_res") + " OR rr.res_rsv_ruimte_van IS NULL)"
                    : "")
                  + " UNION "
                  + "SELECT rr.res_rsv_ruimte_contact_key prs_perslid_key"
                  + "     , res_srtactiviteit_prefix || TO_CHAR (rr.res_reservering_key) || '/' || rr.res_rsv_ruimte_volgnr item"
                  + "     , rr.res_rsv_ruimte_key"
                  + "     , 'reservering' xmlnode"
                  + "     , rr.res_rsv_ruimte_van datum"
                  + "     , COALESCE (rr.res_rsv_ruimte_omschrijving, " + lcl.xsql('ra.res_activiteit_omschrijving', 'res_activiteit_key') + ") detail"
                  + "     , ' ' status"
                  + "     , res_rsv_ruimte_satisfaction satisfaction"
                  + "     , res_rsv_ruimte_satisfaction_op satisfaction_op"
                  + "     , DECODE (res_rsv_ruimte_dirtlevel, 0, rr.res_status_fo_key, -1) status_key"
                  + "     , rr.res_status_bo_key bo_status_key"
                  //+ sqlTracking("rr.res_rsv_ruimte_key","reservering")
                  + "  FROM res_v_aanwezigrsv_ruimte rr"
                  + "     , res_activiteit ra"
                  + "     , res_srtactiviteit rsa"
                  + " WHERE rr.res_activiteit_key = ra.res_activiteit_key"
                  + "   AND ra.res_srtactiviteit_key = rsa.res_srtactiviteit_key"
                  + "   AND rr.res_status_bo_key IN (4, 3, 2, 7)"
                  + (!showall
                    ? " AND (rr.res_rsv_ruimte_van BETWEEN SYSDATE - " + S("facilitiespast_res") + " AND SYSDATE + " + S("facilitiesfuture_res") + " OR rr.res_rsv_ruimte_van IS NULL)"
                    : "");

          var sqln = "SELECT prs_perslid_key"
                   + "     , item"
                   + "     , item_key"
                   + "     , xmlnode"
                   + "     , datum"
                   + "     , detail"
                   + "     , status"
                   + "     , satisfaction"
                   + "     , satisfaction_op"
                   + "     , status_key"
//                   + "     , recentdatum"
                   + "     , bo_status_key"
                   + "  FROM (" + sql + ")"
                   + " WHERE prs_perslid_key = " + params.prs_key
//                   + module_filter
                   + " ORDER by datum";
          var oRs = Oracle.Execute(sqln);
          var user_res = Oracle.rs2hash(oRs);
          oRs.Close();

          // Bepaal de URL waar user straks op kan klikken
          if (params.mobile)
              var deepurl = HTTP.urlzelf() + "/appl/pda/reservering.asp?fac_id=" + customerId + "&rsv_ruimte_key=";
          else
              var deepurl = HTTP.urlzelf() + "/?fac_id=" + customerId + "&u=reservering&k=" ;
          for (var r in user_res)
          {
              user_res[r].deepurl = deepurl + user_res[r].item_key
          }

          return user_res;
        },
        getremotes_sql: function (autfunction)
        {
          var sql = "SELECT dis.ins_discipline_key,"
                  + "       "+ lcl.xsqla('dis.ins_discipline_omschrijving','dis.ins_discipline_key') +","
                  + "       res_disc_params_remoteurl"
                  + "  FROM res_v_aanwezigdiscipline dis, res_disc_params rdp"
                  + " WHERE dis.ins_discipline_key = rdp.res_ins_discipline_key"
                  + "   AND ins_discipline_min_level = 3" // Alleen nog Ruimte
                  + "   AND res_disc_params_remoteurl IS NOT NULL"
                  + "   AND dis.ins_discipline_key IN("
                  + "        SELECT g.ins_discipline_key FROM fac_v_webgebruiker g, fac_functie f "
                  + "         WHERE g.fac_functie_key = f.fac_functie_key "
                  + "           AND f.fac_functie_code = " + safe.quoted_sql(autfunction)
                  + "           AND g.fac_gebruiker_alg_level_write < 9"
                  + "           AND g.fac_gebruiker_prs_level_write < 9"
                  + "           AND g.prs_perslid_key = " + user_key + ")";
          return sql;
        },
        anyremotes: function () // TODO: Bij veel aanroepen gaan cachen in session?
        {
          var sql = res.getremotes_sql("WEB_RESUSE");
          var oRs = Oracle.Execute(sql);
          var any = !oRs.EOF;
          oRs.Close()
          return any;
        },
        hour_px: function (width_px, nr_days)
        {
          var nr_hours = nr_days * (S("res_t2") - S("res_t1"));
          var targethour_px = width_px / nr_hours;
          var hour_px = 96;
          if (targethour_px < 96) hour_px = 72;
          if (targethour_px < 72) hour_px = 60;
          if (targethour_px < 60) hour_px = 48;
          if (targethour_px < 48) hour_px = 36;
          if (targethour_px < 36) hour_px = 24;
          if (targethour_px < 24) hour_px = 16;
          if (targethour_px < 16) hour_px =  8;
          if (targethour_px <  8) hour_px =  4;
          return hour_px;
        },
        max_duration_r: function (act_key, disc_key, duration_hours)
        {
          var result = { err: false, message: "", maxhours: 0};
          var sql = "SELECT rad.res_activiteit_key"
                  + "     , rdp.res_disc_params_maxduur"
                  + "  FROM res_activiteitdiscipline rad"
                  + "     , res_disc_params rdp"
                  + " WHERE rad.res_discipline_key = rdp.res_ins_discipline_key"
                  + "   AND rad.res_discipline_key = " + disc_key
                  + "   AND rad.res_activiteit_key = " + act_key;
          var oRs = Oracle.Execute(sql);
          if (oRs.Eof)
          {
             result.message = L("lcl_res_bad_activity");
             result.err = true;
          }
          else
          {
            var maxHours = oRs("res_disc_params_maxduur").Value;
            // Controle op maximale tijdsduur reservering.
            if ((maxHours > 0) && (maxHours < duration_hours))
            {
              result.message = L("lcl_res_bad_maxperiod").format(maxHours);
              result.maxhours = maxHours;
              result.err = true;
            }
          }
          oRs.close();
          return result;
        },
        max_duration_cv: function (rsv_ruimte_key, duration_hours)
        {
          var result = { err: false, message: "", maxhours: 0};
          if (rsv_ruimte_key > 0)
          {
            var sql = "SELECT rdp.res_disc_params_maxduur"
                    + "  FROM res_rsv_artikel rsva"
                    + "     , res_artikel ra"
                    + "     , res_rsv_ruimte rsv"
                    + "     , res_activiteitdiscipline rad"
                    + "     , res_disc_params rdp"
                    + " WHERE rsva.res_artikel_key = ra.res_artikel_key"
                    + "   AND rsva.res_rsv_ruimte_key = rsv.res_rsv_ruimte_key"
                    + "   AND rad.res_discipline_key = rdp.res_ins_discipline_key"
                    + "   AND rad.res_discipline_key = ra.res_discipline_key"
                    + "   AND rad.res_activiteit_key = rsv.res_activiteit_key"
                    + "   AND rsv.res_rsv_ruimte_key = " + rsv_ruimte_key
                    + " UNION "
                    + "SELECT rdp.res_disc_params_maxduur"
                    + "  FROM res_rsv_deel rsvd"
                    + "     , res_deel rd"
                    + "     , res_rsv_ruimte rsv"
                    + "     , res_activiteitdiscipline rad"
                    + "     , res_disc_params rdp"
                    + " WHERE rsvd.res_deel_key = rd.res_deel_key"
                    + "   AND rsvd.res_rsv_ruimte_key = rsv.res_rsv_ruimte_key"
                    + "   AND rad.res_discipline_key = rdp.res_ins_discipline_key"
                    + "   AND rad.res_discipline_key = rd.res_discipline_key"
                    + "   AND rad.res_activiteit_key = rsv.res_activiteit_key"
                    + "   AND rsv.res_rsv_ruimte_key = " + rsv_ruimte_key
                    + "   AND rad.res_discipline_key != " + S("vis_parking_key") // Parkeerplaatsen doen we niet moeilijk over
                    + " ORDER BY 1";
            var oRs = Oracle.Execute(sql);
            var min_hours = 0;
            while (!oRs.eof)
            {
              var maxHours = oRs("res_disc_params_maxduur").Value;
              // Controle op maximale tijdsduur reservering.
              if ((maxHours > 0) && (maxHours < duration_hours))
              {
                if ((min_hours == 0) || (maxHours < min_hours))
                {
                  min_hours = maxHours;
                  result.message = L("lcl_res_bad_maxperiod").format(maxHours);
                  result.maxhours = maxHours;
                  result.err = true;
                }
              }
              oRs.MoveNext();
            }
            oRs.close();
          }
          return result;
        }
}

// Later nog: p_alg_ruimte_key zelf bepalen uit p_rsv_ruimte_key
/* Vier varianten
  A) p_res_ruimte_key gegeven van een 'CV'. We zijn een CV reservering aan het bewerken en hebben dus een alg_ruimte
     Toon alleen datgene wat gereserveerd kan worden in die ruimte
  A) p_alg_ruimte_key gegeven van een 'R'. We zijn een R reservering aan het bewerken en hebben dus een res_ruimte
     Toon alleen datgene wat gereserveerd kan worden in die res_ruimte
  C) Geen p_rsv_ruimte_key maar wel een loc_key
     Toon alleen datgene wat gereserveerd kan worden op die locatie
  D) Geen p_rsv_ruimte_key en geen loc_key
     Toon alleen datgene wat gereserveerd kan worden
 */

function get_res_deel_sql (params)
{
  params.alg_ruimte_key = params.alg_ruimte_key || -1;
  params.res_ruimte_key = params.res_ruimte_key || -1;

  if (params.res_tot)
    var res_tot = new Date(params.res_tot);
  else
    var res_tot = new Date();
  if (params.rsv_ruimte_key > 0)
  {
    sql = "SELECT res_rsv_ruimte_tot"
        + "  FROM res_rsv_ruimte"
        + " WHERE res_rsv_ruimte_key = " + params.rsv_ruimte_key;
    oRs = Oracle.Execute(sql);
    var res_tot = new Date(oRs("res_rsv_ruimte_tot").Value);
  }
  var ora_date2 = res_tot.endToSQL();

  var sql1 = "SELECT " + params.select_fields
           + "  FROM ins_deel d"
           + "     , res_v_aanwezigdeel r"
           + "     , res_disc_params rdp"
           + "     , (SELECT aob.alg_regio_key, aob.alg_district_key, aob.alg_locatie_key, aob.alg_gebouw_key, aob.alg_verdieping_key, aob.alg_ruimte_key "
           + "          FROM "
           + (params.res_ruimte_key > 0
             ? "             res_v_aanwezigalg_ruimte rrr,"
             : "")
           + "                alg_v_onrgoed_boom aob"
           + "         WHERE 1 = 1"
           + ((params.reg_key || -1) > -1
             ? "         AND aob.alg_regio_key=" + params.reg_key
             : "")
           + ((params.dist_key || -1) > -1
             ? "          AND aob.alg_district_key=" + params.dist_key
             : "")
           + ((params.loc_key || -1) > -1
             ? "          AND aob.alg_locatie_key=" + params.loc_key
             : "")
           + ((params.geb_key || -1) > -1
             ? "          AND aob.alg_gebouw_key=" + params.geb_key
             : "")
           + ((params.ver_key || -1) > -1
             ? "          AND aob.alg_verdieping_key=" + params.ver_key
             : "")
           + ((params.res_ruimte_key > 0)
             ? "          AND res_ruimte_key = " + params.res_ruimte_key + " AND rrr.alg_ruimte_key = aob.alg_ruimte_key"
             : "")
           + ((params.alg_ruimte_key > 0)
             ? "          AND aob.alg_ruimte_key = " + params.alg_ruimte_key
             : "")
           + "        ) scope"
           + "     ,  res_v_deelscope ds"
           + " WHERE r.res_ins_deel_key = d.ins_deel_key"
           + "   AND rdp.res_ins_discipline_key = r.res_discipline_key"
           + (params.sdisc
             ? " AND r.res_discipline_key = " + params.sdisc
             : "")
           + "   AND COALESCE(d.ins_alg_ruimte_type_org, d.ins_alg_ruimte_type) IN ('R','T')";

    sql1 += "    AND "
          + "    (     ds.alg_regio_key      = scope.alg_regio_key"
          + "       OR ds.alg_district_key   = scope.alg_district_key"
          + "       OR ds.alg_locatie_key    = scope.alg_locatie_key"
          + "       OR ds.alg_gebouw_key     = scope.alg_gebouw_key"
          + "       OR ds.alg_verdieping_key = scope.alg_verdieping_key"
          + "       OR ds.alg_ruimte_key     = scope.alg_ruimte_key"
          + "       OR ds.res_deel_alg_level = -1)"
          + "    AND r.res_deel_key = ds.res_deel_key"
          + "    AND (r.res_deel_vervaldatum IS NULL OR r.res_deel_vervaldatum > " + ora_date2 + ")"

  if (params.res_ruimte_key < 0 && (params.alg_ruimte_key < 0))
  { // Locatie scope of nog groter. Rechten ook meenemen
    sql1 = discxalg3d (sql1,
                       "r.RES_DISCIPLINE_KEY",
                       "scope.alg_regio_key",
                       "scope.alg_district_key",
                       "scope.alg_locatie_key",
                       "scope.alg_gebouw_key",
                       "scope.alg_verdieping_key",
                       "scope.alg_ruimte_key",
                       params.autfunction,
                       typeof params.sdisc == "number"? params.sdisc : '');
  }
  sql1 += (params.group_by || "");
  return sql1;
}

function planbordTooltipShowing(res_rsv_ruimte_key)
{
  var tonen = false;
  var disc = null;
  if (res_rsv_ruimte_key) // Hebben we niet bij een afspraak
  {
    var sql = "SELECT res_srtactiviteit_anonym, "
            + "       res_discipline_key "
            + " FROM res_srtactiviteit sa,"
            +      " res_activiteit    ra,"
            +      " res_rsv_ruimte    rrr,"
            +      " res_ruimte_opstelling rro,"
            +      " res_ruimte    rr"
            + " WHERE sa.res_srtactiviteit_key = ra.res_srtactiviteit_key"
            + "   AND ra.res_activiteit_key = rrr.res_activiteit_key"
            + "   AND rrr.res_rsv_ruimte_key = " + rsv_ruimte_key
            + "   AND rrr.res_ruimte_opstel_key = rro.res_ruimte_opstel_key(+)"
            + "   AND rro.res_ruimte_key = rr.res_ruimte_key(+)";
    var oRs = Oracle.Execute(sql);
    var disc = oRs("res_discipline_key").Value;
    tonen = (oRs("res_srtactiviteit_anonym").Value != 1);
    oRs.Close();
  }
  // FOF mag het wel zien als hij rechten heeft op de (ruimte)discipline.
  //     Bij voorzieningreserveringen mag je het met any FOF altijd zien.
  var canFOF = user.checkAutorisation("WEB_RESFOF", true, disc);
  if (canFOF)
    tonen = true;

  return tonen;
}

Date.prototype.getFloatHours = function getFloatHours()
{
  return this.getHours() + (this.getMinutes()/60);
}
// Noot: rondt af op S("res_h")!
//       levert een nieuw date-object op
Date.prototype.setFloatHours = function setFloatHours(hrs)
{
  var dd = new Date(this);
  hrs = Math.floor((hrs / S("res_h")) + 0.5) * S("res_h");
  var hh = Math.floor(hrs);
  var mm = (hrs * 60) % 60;
  dd.setHours(hh, mm, 0, 0);
  return dd;
}
// Noot: rondt af op S("res_h")!
//       levert een nieuw date-object op
Date.prototype.addFloatHours = function addFloatHours(hrs)
{
  return this.setFloatHours(this.getFloatHours() + hrs);
}

%>
