<%
 /*
    $Revision$
    $Id$

    File:               res.inc
    Description:        res ondersteunende functies
    Context:
    Note:
 */

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 pResult = new HookResult();
           var sql;
           // delete parkeerplaatsen gebeurt wel met behulp van CASCADE (bez_bezoekers)

           // delete bezoekers
           var sql = "SELECT bez_bezoekers_key"
                   + "  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 + ")";
           var oRs = Oracle.Execute(sql);
           while (!oRs.eof)
           {
              var bez_key = oRs("bez_bezoekers_key").Value;
              if (!custfunc.bez_predelete(bez_key, pResult))
              {
                abort_with_warning(pResult.errmsg);
              }
              sql = "DELETE FROM bez_bezoekers"
                  + " WHERE bez_bezoekers_key = " + bez_key;
              Oracle.Execute(sql);
              if (!custfunc.bez_postdelete(bez_key, pResult))
              {
                abort_with_warning(pResult.errmsg);
              }
              oRs.MoveNext();
           }
           oRs.Close();

           // 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 (resdeel_key, actie)
        {
          sql = "SELECT " + lcl.xsqla('rd.res_deel_omschrijving', 'rd.res_deel_key')
              + ", res_ins_deel_key, res_prs_perslid_key"
              + "  FROM res_deel rd"
              + " WHERE rd.res_deel_key = " + resdeel_key;
          var oRs = Oracle.Execute(sql);
          var obj_oms = oRs("res_deel_omschrijving").Value;
          var rdtype = oRs("res_prs_perslid_key").Value == null ? "deel" : "perslid";
          oRs.Close();
          var track = "";
          switch (actie)
          {
            case "delete": track = (rdtype == "deel" ? L("lcl_res_insdelete") : L("lcl_res_prsdelete")).format(obj_oms);
                           break;
            case "insert": track = (rdtype == "deel" ? L("lcl_res_insinsert") : L("lcl_res_prsinsert")).format(obj_oms);
                           break;
          }
          return track;
        },

        trackartikel:
        function (artikel_key, actie, params)
        {
          sql = "SELECT " + lcl.xsqla("a.res_artikel_omschrijving", "a.res_artikel_key")
              + "     , a.res_discipline_key"
              + "  FROM res_artikel a"
              + " WHERE a.res_artikel_key = " + artikel_key;
          var oRs = Oracle.Execute(sql);
          var art_oms = oRs("res_artikel_omschrijving").Value;
          var discipline_key = oRs("res_discipline_key").Value;
          oRs.Close();
          var track = "";

          var sql = "SELECT COALESCE(ks.prs_kostensoort_btw, 0) inclBTW"
                  + "  FROM prs_kostensoort ks"
                  + "     , res_discipline rd"
                  + " WHERE ks.prs_kostensoort_key = rd.prs_kostensoort_key"
                  + "   AND rd.ins_discipline_key = " + discipline_key;
          var oRs = Oracle.Execute(sql);
          var inclBTW = (!oRs.eof? oRs("inclBTW").Value : 0);
          oRs.Close();

          switch (actie)
          {
            case "delete": track = L("lcl_res_artdelete").format(art_oms);
                           break;
            case "insert": track = L("lcl_res_artinsert").format(art_oms);
                           break;
            case "update": track = L("lcl_res_artupdate").format(art_oms)
                                 + (params.old_number != params.new_number
                                   ? "\n" + L("lcl_count") + ": "
                                     + buildTrackText("number", params.old_number, params.new_number, { nodiff: true })
                                   : "")
                                 + (params.old_price != params.new_price
                                   ? "\n" + L("lcl_r_price5") + " " + (inclBTW? L("lcl_shared_inclBTW") : L("lcl_shared_exclBTW")) + ": "
                                     + buildTrackText("float", safe.curr(params.old_price), safe.curr(params.new_price), { nodiff: true })
                                   : "")
                                 + (params.old_date.getTime() != params.new_date.getTime()
                                   ? "\n" + L("lcl_time") + ": "
                                     + buildTrackText("datetime", params.old_date, params.new_date, { nodiff: true })
                                   : "")
                                 + (  (params.old_afhaal && params.new_afhaal && params.old_afhaal.getTime() != params.new_afhaal.getTime())
                                   || (params.old_afhaal && !params.new_afhaal)
                                   || (params.new_afhaal && !params.old_afhaal)
                                   ? "\n" + L("lcl_res_ophalen") + ": "
                                     + buildTrackText("datetime", params.old_afhaal, params.new_afhaal, { nodiff: true })
                                   : "")
                                 + (params.old_omschr != params.new_omschr
                                   ? "\n" + params.old_omschr + " --> " + params.new_omschr
                                   : "")
                                   ;
                           break;
          }
          return track;
        },

        // Levert de budgedhouder (kostenplaatsverantwoordelijke) op van de catering bestelling van een (deel)reservering.
        getApprover: function (rsv_ruimte_key)
        {
          var sql = "SELECT k.prs_perslid_key approver_key"
                  + "  FROM res_rsv_ruimte rr"
                  + "     , prs_kostenplaats k"
                  + " WHERE rr.prs_kostenplaats_key = k.prs_kostenplaats_key"
                  + "   AND rr.res_rsv_ruimte_key = " + rsv_ruimte_key
          var oRs = Oracle.Execute(sql);
          var approver_key = (!oRs.eof? oRs("approver_key").Value : -1);
          oRs.Close();
          return approver_key;
        },

        // Levert de te tonen berichtstring "De reservering wordt ter fiattering aangeboden aan XXXXX" op.
        // Kopie in RES_PAC
        sendNeedApprovalNotification: function (rsv_ruimte_key, approver_key)
        {
          if (approver_key == -1)
            return false;

          sql = "SELECT rr.res_ruimte_opstel_key"
              + "     , rr.res_reservering_key"
              + "     , rr.alg_ruimte_key"
              + "  FROM res_rsv_ruimte rr"
              + " WHERE rr.res_rsv_ruimte_key = " + rsv_ruimte_key
          var oRs = Oracle.Execute(sql);
          var res_ruimte_opstel_key = oRs("res_ruimte_opstel_key").Value || -1;
          var res_reservering_key = oRs("res_reservering_key").Value;
          var alg_ruimte_key = oRs("alg_ruimte_key").Value || -1;
          oRs.Close();

          var afzender;
          // Bepaal afzender. Hier is het voldoende om alleen naar de locatie te kijken.
          if (res_ruimte_opstel_key > 0)
          {
            sql = "SELECT MAX(l.alg_locatie_email) afzender"
                + "  FROM res_ruimte_opstelling ro"
                + "     , res_ruimte rr"
                + "     , res_alg_ruimte rag"
                + "     , alg_ruimte r"
                + "     , alg_verdieping v"
                + "     , alg_gebouw g"
                + "     , alg_locatie l"
                + " WHERE l.alg_locatie_key = g.alg_locatie_key"
                + "   AND g.alg_gebouw_key = v.alg_gebouw_key"
                + "   AND r.alg_verdieping_key = v.alg_verdieping_key"
                + "   AND r.alg_ruimte_key = rag.alg_ruimte_key"
                + "   AND rag.res_ruimte_key = rr.res_ruimte_key"
                + "   AND rr.res_ruimte_key = ro.res_ruimte_key"
                + "   AND ro.res_ruimte_opstel_key = " + res_ruimte_opstel_key;
            var oRs = Oracle.Execute(sql);
            var afzender = oRs("afzender").Value;
            oRs.Close();
          }
          else if (alg_ruimte_key > 0)
          {
            // Locatieafzender
            sql = "SELECT MAX(l.alg_locatie_email) afzender"
                + "  FROM alg_ruimte r"
                + "     , alg_verdieping v"
                + "     , alg_gebouw g"
                + "     , alg_locatie l"
                + " WHERE l.alg_locatie_key = g.alg_locatie_key"
                + "   AND g.alg_gebouw_key = v.alg_gebouw_key"
                + "   AND r.alg_verdieping_key = v.alg_verdieping_key"
                + "   AND r.alg_ruimte_key = " + alg_ruimte_key;
            oRs = Oracle.Execute(sql);
            var afzender = oRs("afzender").Value;
            oRs.Close();
          }
          //else initiële waarde
          //  afzender = null;

          // notify approver by message & mail as configured in fac_srtnotificatie with MLDAP1
          // putnotificationsrtprio doet geen vertaling van ##OPDR_KEY## (notify_tracking zou dat wel doen)
          // dus doen we het hier zelf maar.
          var omssql = "SELECT fac_srtnotificatie_oms"
                     + "  FROM fac_srtnotificatie"
                     + " WHERE fac_srtnotificatie_code = 'RESFIA'";
          var oRs = Oracle.Execute(omssql);
          var oms = L(oRs("fac_srtnotificatie_oms").Value, { autolcl: true });
          oRs.Close();

          Oracle.Execute("BEGIN fac.putnotificationsrtprio(NULL"
                         + ", " + approver_key
                         + ", 'RESFIA'"
                         + ", " + res_reservering_key
                         + ", res.sprintf(" + safe.quoted_sql(oms) + ", " + rsv_ruimte_key + ")"
                         + ",  NULL, NULL, NULL, " + rsv_ruimte_key + ", 2"
                         + ", " + (afzender?  safe.quoted_sql(afzender) : "NULL") + "); END;");

          var sql = "SELECT prs_perslid_naam_full FROM prs_v_perslid_fullnames_all WHERE prs_perslid_key = " + approver_key;
          var oRs = Oracle.Execute(sql);
          var prsname = oRs("prs_perslid_naam_full").Value;
          oRs.Close();
          return L("lcl_res_regApprov") + prsname;
        },

        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();
          },

        // Geeft de tijdszone terug van een (R of CV) reservering obv res_rsv_ruimte_key
        getRsvTimezone: function _getRsvTimezone(res_rsv_ruimte_key) {
          var sql = "SELECT rrr.alg_ruimte_key, rro.res_ruimte_key"
                  + "  FROM res_rsv_ruimte  rrr"
                  + "       LEFT JOIN res_ruimte_opstelling rro"
                  + "           ON rrr.res_ruimte_opstel_key = rro.res_ruimte_opstel_key"
                  + " WHERE res_rsv_ruimte_key = " + res_rsv_ruimte_key;
          var oRs = Oracle.Execute(sql);
          var alg_ruimte_key = oRs("alg_ruimte_key").Value;
          var res_ruimte_key = oRs("res_ruimte_key").Value;
          if (res_ruimte_key != null) {
            return res.getTimezoneByRuimte(res_ruimte_key, "RES");
          } else if (alg_ruimte_key != null) {
            return res.getTimezoneByRuimte(alg_ruimte_key, "ALG");
          } else {
            return S("fac_server_timezone");
          }
        },

        getTimezoneByRuimte: function _getTimezoneByRuimte(ruimte_key, mode) {
          // mode IN ["ALG", "RES"]
          mode = mode || "RES";
          var sql = "SELECT MIN (l.alg_locatie_timezone) alg_tz"
                  + "  FROM alg_locatie     l"
                  + "     , alg_gebouw      g"
                  + "     , alg_verdieping  v"
                  + "     , alg_ruimte      r"
                  + (mode == "RES"
                    ? "     , res_alg_ruimte  rar"
                    + "     , res_ruimte      rr"
                    : "")
                  + " WHERE     g.alg_locatie_key = l.alg_locatie_key"
                  + "       AND v.alg_gebouw_key = g.alg_gebouw_key"
                  + "       AND r.alg_verdieping_key = v.alg_verdieping_key"
                  + (mode == "RES"
                    ? "       AND rar.alg_ruimte_key = r.alg_ruimte_key"
                    + "       AND rr.res_ruimte_key = rar.res_ruimte_key"
                    + "       AND rr.res_ruimte_key = " + ruimte_key
                    : "       AND r.alg_ruimte_key = " + ruimte_key);
          var oRs = Oracle.Execute(sql);
          var alg_tz = oRs("alg_tz").Value || S("fac_server_timezone");
          oRs.Close();
          return alg_tz;
        },

        getTimezoneBy: function _getTimezoneBy(alg_key, level) {
          // level ? ["R", "V", "G", "L"]
          var sql;
          switch (level) {
            case "L":
              sql = "SELECT l.alg_locatie_timezone"
                  + "  FROM alg_locatie l"
                  + " WHERE l.alg_locatie_key = " + alg_key;
              break;

            case "G":
              sql = "SELECT l.alg_locatie_timezone"
                  + "  FROM alg_locatie l, alg_gebouw g"
                  + " WHERE g.alg_gebouw_key = " + alg_key
                  + "   AND g.alg_locatie_key = l.alg_locatie_key";
              break;

            case "V":
              sql = "SELECT l.alg_locatie_timezone"
                  + "  FROM alg_locatie l, alg_gebouw g, alg_verdieping v"
                  + " WHERE v.alg_verdieping_key = " + alg_key
                  + "   AND v.alg_gebouw_key = g.alg_gebouw_key"
                  + "   AND g.alg_locatie_key = l.alg_locatie_key";
              break;

            case "R":
              return res.getTimezoneByRuimte(alg_key, "ALG");

            default:
              throw new Error("Invalid level: " + level);
          }

          var oRs = Oracle.Execute(sql);
          var alg_tz = !oRs.EOF && oRs("alg_locatie_timezone").Value
                    || S("fac_server_timezone");
          oRs.Close();
          return alg_tz;
        },

        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')
                  + "     , rr.res_ruimte_minbezoekers"
                  + "     , rr.res_ruimte_info_url"
                  + "     , rr.res_ruimte_image"
                  + "     , ac.res_activiteit_aantalverplicht"
                  + "     , ac.res_activiteit_notfrontend"
                  + "     , rdp.res_disc_params_kosten"
                  + "     , rd.ins_discipline_kpnverplicht"
                  + "     , COALESCE(rr.res_ruimte_begintijd, " + S("res_t1") + ") begintijd"
                  + "     , COALESCE(rr.res_ruimte_eindtijd, " + S("res_t2") + ") eindtijd"
                  + "     , rr.res_ruimte_begintijdblok"
                  + "     , rr.res_ruimte_eindtijdblok"
                  + "     , rr.res_discipline_key"
                  + "     , rr.res_status_fo_key"
                  + "     , rr.res_ruimte_startdatum"
                  + "     , rr.res_ruimte_vervaldatum"
                  + "     , rr.res_ruimte_extern_id"
                  + "     , rr.res_ruimte_min_duur"
                  + "     , rr.res_ruimte_limiet"
                  + "  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.minbezoekers = oRs("res_ruimte_minbezoekers").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.startdatum = oRs("res_ruimte_startdatum").Value != null ? new Date(oRs("res_ruimte_startdatum").Value) : null;
            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;
            result.limiet = oRs("res_ruimte_limiet").Value;
          }
          oRs.Close();

          sql = "SELECT res_ruimte_opstel_key"
              + "     , res_ruimte_opstel_tijd"
              + "     , 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,
                       opstel_tijd: oRs("res_ruimte_opstel_tijd").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 && oRs("res_ruimte_opstel_verwijder").Value == null)
              result.default_opstel_key = oRs("res_ruimte_opstel_key").Value;
            oRs.MoveNext();
          }
          oRs.close();

          result.vrije_dagen = {id: "Default"};
          var vrije_dagen_alt = [];
          var sql = "SELECT ag.mld_vrije_dagen_id"
                  + "     , mv.mld_vrije_dagen_datum"
                  + "  FROM res_alg_ruimte rr"
                  + "     , alg_ruimte ar"
                  + "     , alg_verdieping av"
                  + "     , alg_gebouw ag"
                  + "     , mld_vrije_dagen mv"
                  + " WHERE rr.alg_ruimte_key = ar.alg_ruimte_key"
                  + "   AND ar.alg_verdieping_key = av.alg_verdieping_key"
                  + "   AND av.alg_gebouw_key = ag.alg_gebouw_key"
                  + "   AND ag.mld_vrije_dagen_id = mv.mld_vrije_dagen_id"
                  + "   AND rr.res_ruimte_key = " + res_ruimte_key;
          var oRs = Oracle.Execute(sql);
          while (!oRs.eof)
          {
            result.vrije_dagen.id = oRs("mld_vrije_dagen_id").Value;
            vrije_dagen_alt.push((new Date(oRs("mld_vrije_dagen_datum").Value)).getTime());
            oRs.MoveNext();
          }
          oRs.Close();
          if (vrije_dagen_alt.length && result.vrije_dagen.id != "Default")
            result.vrije_dagen.alt = vrije_dagen_alt;

          return result;
        },

        res_getruimteprijs: function _res_getruimteprijs(res_ruimte_key, params)
        {
          var ruimte_prijs = 0;
          var sql = "SELECT res.getresruimteprijs("    + res_ruimte_key
                                                 + "," + params.rsv_van_datum.toSQL(true)
                                                 + "," + params.rsv_tot_datum.toSQL(true)
                                                 + "," + params.rsv_extern
                                                 + ") prijs"
                  + "  FROM DUAL";
          var oRs = Oracle.Execute(sql);
             ruimte_prijs = oRs("prijs").Value;
          oRs.Close();
          return ruimte_prijs;
        },

        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"
                  + ", rrr.res_rsv_ruimte_externnr"
                  + ", " + 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      = new Date(oRs("res_rsv_ruimte_van").Value);
          result.ruimte_tot      = new Date(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.extern_nr       = oRs("res_rsv_ruimte_externnr").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.act_effectief_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, ismjob); (Moment modus dus geen mjob)
            var ora_date1 = nextDate.beginToSQL();
            var sql = "SELECT fac.nextcyclusdate (" + ora_date1 + ", 0, 3, 1, " + params.bits + ", 1, 0) 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;
        },

        rsv_opstelling_fit: function _rsv_opstelling_fit(params)
        {
          var res_ruimte_key = params.res_ruimte_key;
          var res_opstel_key = params.res_opstel_key;

          var ora_rsv_from = new Date(params.rsv_van).toSQL(true);
          var ora_rsv_to   = new Date(params.rsv_tot).toSQL(true);

          var sql = "SELECT COUNT (1) overlap"
                  + "  FROM res_rsv_ruimte rr, res_ruimte_opstelling ro, res_ruimte_opstelling ro2"
                  + " WHERE rr.res_ruimte_opstel_key = ro.res_ruimte_opstel_key"
                  + "   AND ro2.res_ruimte_opstel_key = " + res_opstel_key
                  + "   AND ro.res_ruimte_key = " + res_ruimte_key
                  + "   AND rr.res_ruimte_opstel_key <> " + res_opstel_key
                  + "   AND rr.res_rsv_ruimte_dirtlevel < 256"
                  + "   AND rr.res_rsv_ruimte_verwijder IS NULL"
                  /* Alleen overlap als deze veroorzaakt worden door de opsteltijd, dus; */
                  + "   AND ((rr.res_rsv_ruimte_van >= "    + ora_rsv_to
                  + "        AND rr.res_rsv_ruimte_van < "  + ora_rsv_to   + " + GREATEST (ro.res_ruimte_opstel_tijd, ro2.res_ruimte_opstel_tijd) / 24)"
                  + "     OR (rr.res_rsv_ruimte_tot > "     + ora_rsv_from + " - GREATEST (ro.res_ruimte_opstel_tijd, ro2.res_ruimte_opstel_tijd) / 24"
                  + "        AND rr.res_rsv_ruimte_tot <= " + ora_rsv_from + "))";
          var oRs = Oracle.Execute(sql);
          var result = { overlap: (oRs("overlap").Value > 0 ? 1 : 0) };
          oRs.Close();

          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;
        },

        res_deel_flex_bijlagen: function (p_key, disc_key)
        {
          var bijlagen_aantal = 0;
          var bijlagen_oms = "";
          var bijlagen_lijst = [];

          var sql_k = "SELECT k.res_kenmerk_key"
                    + "     , COALESCE (" + lcl.xsql("k.res_kenmerk_omschrijving", "k.res_kenmerk_key")
                    + "     ,           " + lcl.xsql("sk.res_srtkenmerk_omschrijving", "sk.res_srtkenmerk_key") + ") omschrijving"
                    + "  FROM res_kenmerk k"
                    + "     , res_srtkenmerk sk"
                    + " WHERE k.res_srtkenmerk_key = sk.res_srtkenmerk_key"
                    + "   AND k.res_kenmerk_verwijder IS NULL"
                    + "   AND sk.res_srtkenmerk_verwijder IS NULL"
                    + "   AND sk.res_srtkenmerk_kenmerktype = 'M'"
                    + "   AND (k.res_artikel_key = " + p_key
                    + "     OR k.res_discipline_key = " + disc_key + ")"
                    + "   AND BITAND(k.res_kenmerk_rolcode, 3) > 0";
          var oRs_k = Oracle.Execute(sql_k);
          while (!oRs_k.eof)
          {
            var kenmerk_key = oRs_k("res_kenmerk_key").Value;
            var bijlagen_oms = oRs_k("omschrijving").Value;
            var bestanden = flexProps("RES", p_key, kenmerk_key, null, {getFiles: true});
            var km_aantal = bestanden.files.length;
            for (var i=0; i<km_aantal; i++)
            {
              bijlagen_lijst.push(bestanden.files[i].name);
            }
            bijlagen_aantal += km_aantal;

            oRs_k.MoveNext();
          }
          oRs_k.Close();

          return ( bijlagen_aantal > 0
                 ?  "<span title='" +  safe.htmlattr(bijlagen_lijst.join("\n"))  + "'>"
                    + safe.html(bijlagen_oms) + ": " + bijlagen_aantal
                    + "</span>"
                 : ""
                 );
        },

        // Als een datum voor (discipline_expire(disc_key) ligt dan is hij 'te vroeg'
        discipline_expire: function _discipline_expire(discipline_key, mode, timezone)
        {
          timezone = timezone || S("fac_server_timezone");
          // 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;
          // beginDate moet worden;
          // Als grens wijzigingshorizon (tijd) nog niet is verstreken; nu
          // Als grens wijzigingshorizon (dag) nog niet is verstreken; Eerstvolgende maandag
          // Als grens wijzigingshorizon (tijd) is verstreken en res_disc_params_expire_wkdag is null; begin morgen
          // Als grens wijzigingshorizon (dag) is verstreken (al dan niet icm grens wijzigingshorizon (tijd)); De eerstvolgende maandag na volgende week
          var beginDate = convertToLocalTime(new Date(), timezone);
          var beginDateSql = beginDate.toSQL(true, true);
          if (mode != 1) { // Hou rekening met een evt. 'grens wijzigingshorizon'
            var sql = "SELECT rdp.res_disc_params_expire_tijd"
                    + "     , rdp.res_disc_params_expire_wkdag"
                    + "     , rd.ins_discipline_min_level"
                    + "  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);
            var exp_wkday = oRs("res_disc_params_expire_wkdag").Value;
            var exp_time  = oRs("res_disc_params_expire_tijd").Value;
            var restype = oRs("ins_discipline_min_level").Value == 3 ? "R" : "CV";
            oRs.Close();
            var vandaag = beginDate.getDay() + 1; // Oracle en Facilitor hanteren Zo=1 t/m Za=7, JS hanteert Zo=0 t/m Za=6, vandaar +1
            if (exp_wkday !== null) {
              beginDate = new Date(beginDate.setDate(beginDate.getDate() + 2 /* Ma=2 */ + 7 - vandaag)); // Verschuif minstens naar de eerstvolgende maandag
              if (vandaag > exp_wkday ||
                  vandaag == exp_wkday && exp_time !== null && beginDate.getFloatHours() > exp_time) {
                beginDate = new Date(beginDate.setDate(beginDate.getDate() + 7)); // Als de grens verstreken is, dan nog een week verder
              }
              beginDate = beginDate.setFloatHours((restype == "CV" && S("res_cat_t1") >= 0) ? S("res_cat_t1") : S("res_t1")); // Begin vd dag
              return beginDate; // Klaar, want hier luisteren we niet naar de relatieve wijzigingshorizon (dagen)
            } else if (exp_time !== null && beginDate.getFloatHours() > exp_time) {
              beginDate = new Date(beginDate.setDate(beginDate.getDate() + 1)); // Verschuif naar morgen
              beginDateSql = beginDate.beginToSQL();
            }
          }

          var sql = "SELECT fac.DatumTijdPlusUitvoerTijd(" + beginDateSql
                  + "                                  , " + (mode == 1
                                                             ? "res_disc_params_cancel_dagen"
                                                             : "res_disc_params_expire_dagen")
                  + "                                  , 'DAGEN'"
                  + "                                  ," + ((restype == "CV" && S("res_cat_t1") >= 0) ? S("res_cat_t1") : S("res_t1"))
                  + "                                  ," + ((restype == "CV" && S("res_cat_t2") >= 0) ? S("res_cat_t2") : S("res_t2"))
                  + "                                  , " + (mode || 1) + ") datum"
                  + "     , " + (mode == 1
                                ? "res_disc_params_cancel_dagen"
                                : "res_disc_params_expire_dagen") + " dagen"
                  + "     , 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);
          var dagen = oRs("dagen").Value;
          if (dagen < 0) {
            oRs.Close();
            return new Date().midnight();
          }
          var omschrijving = oRs("ins_discipline_omschrijving").Value; // handig voor logging

          var exp_date = new Date(oRs("datum").Value);
          if (exp_time >= new Date().getFloatHours()) { // grens-tijdstip nog niet vertreken.
            if (dagen == 0) { // we blijven vandaag
              exp_date = exp_date.setFloatHours(exp_time); // (exp_time rol B)
            } else {
              exp_date = exp_date.setFloatHours((restype == "CV" && S("res_cat_t1") >= 0) ? S("res_cat_t1") : S("res_t1"));
            }
          }
          if (exp_date < new Date)
              exp_date = new Date;
          oRs.Close();
          exp_date = convertToServerTime(exp_date, timezone);
          __Log("discipline_expire " + omschrijving + " (key: " + discipline_key + "): " + toDateTimeString(exp_date) + ", mode: " + mode + (mode == 1? " (cancel)" : " (expire)"));
          return exp_date;
        },

        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";

            RenameFlexFolders(flexsql, "RES", "R", res_key);
          }
        },

        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.mld_opdr_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"
                  + "     , r.res_rsv_ruimte_externnr"  // Ruimte-event-id
                  + "     , r.res_rsv_ruimte_externnr2" // Organisator-event-id
                  + "     , r.res_rsv_ruimte_externsyncdate" // sync (export) datum
                  + "     , DECODE(p.prs_perslid_oslogin, '_MSGRAPH_FALLBACK_ROOMS', 1, '_MSGRAPH_FALLBACK_UNKNOWN', 1, 0) fallback_host" // Deze zijn niet in Facilitor wijzigbaar
                  + "     , ra.res_activiteit_notfrontend"
                  + "     , ra.res_activiteit_meteindtijd"
                  + "     , ra.res_activiteit_ismaster"
                  + "     , ra.res_activiteit_master_key"
                  + "  FROM res_rsv_ruimte r,"
                  + "       res_activiteit ra,"
                  + "       prs_perslid p"
               	+ " WHERE res_rsv_ruimte_key = " + rsv_ruimte_key
               	+ "   AND r.res_activiteit_key = ra.res_activiteit_key"
               	+ "   AND r.res_rsv_ruimte_host_key = p.prs_perslid_key(+)";
          var roRs = Oracle.Execute(sql);
          if (roRs.EOF) // vanuit API2 niet bestaande id
            return null;

          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 mld_opdr_key = roRs("mld_opdr_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 isMaster = roRs("res_activiteit_ismaster").Value;
          var isChild = roRs("res_activiteit_master_key").Value > 0;
          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;
          var rsv_ruimte_externnr = roRs("res_rsv_ruimte_externnr").Value;
          var rsv_ruimte_externnr2 = roRs("res_rsv_ruimte_externnr2").Value;
          var fallback_host = roRs("fallback_host").Value == 1;
          var rsv_ruimte_externsyncdate = roRs("res_rsv_ruimte_externsyncdate").Value;

          // 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
                                                true); // 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 = rsv_ruimte_externnr != null;
          var fe_edit_option_only = S("res_fe_edit_option_only");
          if (alg_ruimte_key == null && mld_opdr_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"
                    + "     , rdp.res_disc_params_edit_option"
                    + "     , 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"
                    + "        , rdp.res_disc_params_edit_option";
            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;
            fe_edit_option_only = oRs("res_disc_params_edit_option").Value;
            alg_ruimte_key = oRs("alg_ruimte_key").Value;
            res_ruimte_extern = 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.hasCV = hasCV;
          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.res_ruimte_extern = res_ruimte_extern;

          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 (   !user.isCollega(rsv_ruimte_contact_key)
                && !user.isCollega(rsv_ruimte_host_key))
            {
              if (mld_opdr_key !== null) {
                sql = "SELECT res_prs_perslid_key"
                    + "  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;
                oRs = Oracle.Execute(sql);
                var userIsReservedResource = false;
                while (!oRs.EoF && !userIsReservedResource) {
                  if (user_key == oRs("res_prs_perslid_key").Value) {
                    userIsReservedResource = true;
                  }
                  oRs.MoveNext();
                }
                rresult.canReadAny = userIsReservedResource;
              } else {
                rresult.canReadAny = false;// JGL: Kan dit nog legaal gebeuren?
              }
            }
          }

          rresult.canReadFO = rresult.canRead("WEB_RESFOF");
          rresult.canReadBO = rresult.canRead("WEB_RESBOF");
          rresult.canWriteExtern = ((S("msgraph_sync_level") & 6) == 2 && // We hebben alleen schrijfrechten op de ruimte-resource (niet op de gebruikers)
                                      ((rsv_ruimte_externnr != null &&    // Deprecated;
                                        rsv_ruimte_externnr2 != null &&   // vanaf 2023.1 Gold B wordt externnr2 niet meer gebruikt bij deze instelling
                                        rsv_ruimte_externnr.split("|")[0] == rsv_ruimte_externnr2)) // De ruimte is de organisator, dus dan kunnen we schrijven
                                    || (rsv_ruimte_externnr == null &&
                                        rsv_ruimte_externnr2 == null)     // (Nog) niet gesynced
                                    || (rsv_ruimte_externnr != null &&
                                        rsv_ruimte_externnr2 == null &&
                                        rsv_ruimte_externsyncdate != null)) // Deze reservering is vanuit Facilitor ge-initieerd (vanuit Outlook is de externsyncdate niet gevuld)
                                  || (S("msgraph_sync_level") & 4);         // We hebben schrijfrechten op alle gebruikers
          rresult.canWriteExtern = rresult.canWriteExtern && !fallback_host;  // Deze mag niet in Facilitor gewijzigd worden.

          // -- CONTROLE SCHRIJFRECHTEN --
          // Je moet tenminste schrijfrechten hebben
          rresult.couldCreate = rresult.canReadAny &&
                                (rresult.canWrite("WEB_RESFOF") ||
                                 rresult.canWrite("WEB_RESBOF") ||
                                 (!notfrontend && rresult.canWrite("WEB_RESUSE") &&
                                  (user.isCollega(rsv_ruimte_contact_key) ||
                                   user.isCollega(rsv_ruimte_host_key)))) &&
                                canWritePresentPrk;

          rresult.canChange = rresult.couldCreate  &&
                               (!res_goedgekeurd || (res_goedgekeurd && haveRESAFRrights));

          // canChange maar met notfrontend, zodat FE wel werkplek-reserveringen van zichzelf
          // kan verwijderen, of wijzigen via de 'Reserveer Werkplek'-module
          // FE kan deze reserveringen niet via de reguliere UI aanmaken, alleen via
          // de 'Reserveer Werkplek'-module en canChangeWP wordt ook alleen daar gebruikt
          // canChangeWP werkt ook automagisch voor sub-reserveringen!
          rresult.canChangeWP = rresult.canReadAny &&
                                  (rresult.canWrite("WEB_RESUSE") &&
                                  (user.isCollega(rsv_ruimte_contact_key) ||
                                   user.isCollega(rsv_ruimte_host_key))) &&
                               (!res_goedgekeurd || (res_goedgekeurd && haveRESAFRrights));

          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 || isChild) {
            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;
          rresult.canChangeWP = rresult.canChangeWP && canWrite_ruimte;

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

          // 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, 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 || rresult.canChangeWP) &&
                               canWrite_ruimte &&
                               !openstaandemld &&
                               (!res_ruimte_extern || haveRESMSUrights || rresult.canWriteExtern);

          rresult.canChangeFEOnly = false;
          // (als alleen) Frontend-rechten-->we controleren nog strenger
          if (   (rresult.canChange || rresult.canChangeWP)
              && !rresult.canWrite("WEB_RESFOF")
              && !rresult.canWrite("WEB_RESBOF")
              )
          { // frontend controles
            rresult.canChangeFEOnly = true;
            if (status_fo_key != 1 && (fe_edit_option_only == 1))
            {
              rresult.canChange = false;
              rresult.canChangeWP = false;
              rresult.canChangeCV = false;
              rresult.readoReason = L("lcl_res_fe_edit_option_only");
            }
            if ((rresult.canChange || rresult.canChangeWP) && rsv_ruimte_van < rresult.earliest_expire_change)
            {
              rresult.canChange = false;
              rresult.canChangeWP = false;
              rresult.readoReason = L("lcl_res_reado_late"); // Ruimte of sommige voorzieningen mogen niet meer gewijzigd worden
            }
            rresult.canDelete = (rresult.canChange || rresult.canChangeWP) && !openstaandemld && (!res_ruimte_extern || rresult.canWriteExtern); // 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"
                && !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.canChangeWP = 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 (!isChild && (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() )
              {
                // Voor ruimtereserveringen is wijzigbaar afhankelijk van de instelling bij de catalogus en status_fo_key=1 (optie).
                if (!(fe_edit_option_only == 1 && status_fo_key != 1))
                {
                  rresult.canChangeFEExtended = true;
                }
              }
            }
          }

          if (isChild) { // Dan mag dit allemaal niet meer
            rresult.couldCreate = false;
            rresult.canChange = false;
            rresult.canChangeWP = false;
            if (rresult.canChangeFEOnly) {
                rresult.canChangeCV = false;
            }
          }

          // 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.canChangeWP = 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;
            oRs.Close();

            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();
          }

          // Let op: CV -reservering heeft geen bo-status in res_rsv_ruimte
          sql = "SELECT rr.res_rsv_ruimte_cat_appr"
              + "     , COUNT(ra.res_rsv_artikel_approved) cnt_appr"
              + "     , k.prs_perslid_key budgethouder"
              + "  FROM res_rsv_ruimte rr"
              + "     , res_rsv_artikel ra"
              + "     , prs_kostenplaats k"
              + "     , prs_perslid p"
              + " WHERE rr.res_rsv_ruimte_key = ra.res_rsv_ruimte_key"
              + "   AND rr.prs_kostenplaats_key = k.prs_kostenplaats_key"
              + "   AND k.prs_perslid_key = p.prs_perslid_key"
              + "   AND ra.res_rsv_ruimte_key = " + rsv_ruimte_key
              + "   AND ra.res_rsv_artikel_approved = 0"
              + "   AND COALESCE(rr.res_status_bo_key, ra.res_status_bo_key) = 2"
              + "GROUP BY rr.res_rsv_ruimte_cat_appr"
              + "       , k.prs_perslid_key";
          oRs = Oracle.Execute(sql);
          // Moeten en kunnen de artikelen gefiatteerd worden?
          // 1) Dan moeten er artikelregels gevonden zijn die nog niet gefiatteerd zijn (res_rsv_artikel_approved = 0).
          // 2) De BO status (res_status_bo_key) moet Nieuw(2) zijn (COALESCE(res_rsv_ruimte.res_status_bo_key, res_rsv_artikel.res_status_bo_key) = 2).
          //    Als de BO status al Afgemeld(5) is of Verwerkt(6) dan is de reservering al voorbij en heeft fiattering geen zin meer.
          //    1) en 2) zijn waar als (!oRs.eof).
          // 3) Dan moet gelden dat de artikelen in de catalogus gefiatteerd dienen te worden (res_rsv_ruimte_cat_appr == 1)
          //    Tijdens het opslaan wordt gekeken of er catalogussen zijn met een totaal bestelbedrag die boven de drempelwaarde ligt.
          //    Afhankelijk daarvan wordt de waarde res_rsv_ruimte_cat_appr op 0=Nee of 1=Ja gezet (Wel of niet fiatteren).
          // 4) Ben ik de budgethouder van de kostenplaats van de reservering?
          rresult.canFiatteren = !oRs.eof &&                                                               // 1) Er zijn niet gefiatteerde artikelregels en 2) BO status is Nieuw(2).
                                 oRs("res_rsv_ruimte_cat_appr").Value == 1 &&                              // 3) De artikelregels zijn nog niet gefiatteerd en er dient gefiatteerd te worden.
                                 (oRs("budgethouder").Value && user.isCollega(oRs("budgethouder").Value)); // 4) Er is een budgethouder voor de kostenplaats en dat ben ik of een collega/vervanger.
          rresult.canAfwijzen = rresult.canFiatteren;
          oRs.Close();

          // Als een collega/vervanger of de user al gefiatteerd heeft mag hij/zij ook de reservering inzien.
          sql = "SELECT ra.res_rsv_ruimte_key"
              + "  FROM res_rsv_ruimte rr"
              + "     , res_rsv_artikel ra"
              + "     , prs_kostenplaats k"
              + " WHERE rr.res_rsv_ruimte_key = ra.res_rsv_ruimte_key"
              + "   AND rr.prs_kostenplaats_key = k.prs_kostenplaats_key"
              + "   AND rr.res_rsv_ruimte_cat_appr = 1"
              + "   AND ra.res_rsv_artikel_approved = 1"
              + "   AND ra.res_rsv_ruimte_key = " + rsv_ruimte_key
              + "   AND k.prs_perslid_key IN (" + user.collegakeylist() + ")";
          oRs = Oracle.Execute(sql);
          hasFiat = !oRs.eof;
          oRs.Close();

          rresult.canReadAnyBudget = rresult.canFiatteren || hasFiat;
          rresult.canReadAny |= rresult.canReadAnyBudget;

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

          if (rresult.canDelete && isMaster)
          {
            var sql = "SELECT COUNT(*) aantal"
                    + "  FROM res_rsv_ruimte"
                    + " WHERE res_rsv_ruimte_verwijder IS NULL"
                    + "   AND res_rsv_ruimte_parent_key = " + rsv_ruimte_key;
            var oRs = Oracle.Execute(sql);
            if (oRs("aantal").Value > 0)
                rresult.canDelete = false;
            oRs.Close()
          }

          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))
                                      );

          // Mag ik een negatief aantal CV reserveren
          rresult.canReturnCV = rresult.canChangeCV
                                   && (rresult.canWrite("WEB_RESFOF") || rresult.canWrite("WEB_RESBOF"));

          // 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 < 5) || // ruimte is af te melden
              (rresult.min_bo && rresult.min_bo < 5)) // 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 || [];
          params.bld_key_arr = params.bld_key_arr || [];
          params.ver_key_arr = params.ver_key_arr || [];
          params.rui_key_arr = params.rui_key_arr || [];
          params.act_key_arr = params.act_key_arr || [];
          var sql_loc_from = "";
          var sql_loc_where = "";
          if (params.rui > 0 || params.rui_key_arr.length)
            sql_loc_from = ", res_alg_ruimte ra";
          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_key_arr.length)
            sql_loc_where = " AND ra.res_ruimte_key = rg.res_ruimte_key"
                          + " AND ra.alg_ruimte_key IN (" + params.rui_key_arr.join(", ") + ")";
          else if (params.ver_key_arr.length)
            sql_loc_where = " AND rg.alg_verdieping_key IN (" + params.ver_key_arr.join(", ") + ")";
          else if (params.bld_key_arr.length)
            sql_loc_where = " AND rg.alg_gebouw_key IN (" + params.bld_key_arr.join(", ") + ")";
          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();
          var ora_date2 = params.res_tot.endToSQL();
          // RES_RUIMTE informatie
          // Deze query bepaalt het aantal regels in het planbord
          var sql = "SELECT DISTINCT "
                  + ("sort_on_count" in params && params.sort_on_count ? "("
                  + "     SELECT "
                  + "         count(*) "
                  + "     FROM "
                  + "         RES_V_RSV_RUIMTE_2_ALG_RUIMTE r2a, "
                  + "         RES_V_AANWEZIGRSV_RUIMTE arr "
                  + "     WHERE "
                  + "         arr.res_rsv_ruimte_contact_key = " + user_key
                  + "         and r2a.res_rsv_ruimte_key = arr.res_rsv_ruimte_key "
                  + "         and r2a.res_ruimte_key = fr.res_ruimte_key "
                  + "         and arr.res_rsv_ruimte_van BETWEEN SYSDATE - " + S("res_lookhistory_days") + " AND SYSDATE "
                  + " ) aantal, "
                    : "")
                  + "     fr.res_ruimte_key"
                  + "     , " + lcl.xsqla('rg.res_ruimte_nr', 'rg.res_ruimte_key')
                  + "     , rg.res_ruimte_volgnummer"
                  + "     , min_opstel_tijd"
                  + "     , bmin"
                  + "     , bmax"
                  + "     , fr.res_ruimte_minbezoekers"
                  + "     , l.alg_locatie_key"
                  + "     , alg_locatie_omschrijving"
                  + "     , alg_locatie_plaats"
                  + "     , alg_locatie_code"
                  + "     , alg_locatie_volgnr"
                  + "     , " + 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"
                  + "     , rdp.res_disc_params_preposttime"
                  + "     , fr.res_ruimte_prijs"
                  + "     ," + (params.with_satisfaction?sati_sql:"0") + " satisfaction"
                  + "     , rg.res_discipline_key"
                  + "     , fr.res_ruimte_extern_id"
                  + "     , fr.res_ruimte_extern_meeting"
                  + "     , rd.ins_discipline_image"
                  + "  FROM alg_locatie l"
                  + "     , alg_gebouw g"
                  + "     , alg_srtgebouw sg"
                  + "     , opstel_bez"
                  + "     , res_ruimte fr"
                  + "     , res_v_res_ruimte_gegevens rg"
                  + "     , res_discipline rd"
                  + "     , res_disc_params rdp"
                  + 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 rd.ins_discipline_key = rg.res_discipline_key "
                  + "   AND rdp.res_ins_discipline_key = rg.res_discipline_key "
                  + "   AND (fr.res_ruimte_startdatum  IS NULL OR fr.res_ruimte_startdatum <= " + ora_date2 + ")"
                  + "   AND (fr.res_ruimte_vervaldatum IS NULL OR fr.res_ruimte_vervaldatum > " + ora_date1 + ")"
                  + "   AND COALESCE(l.alg_locatie_timezone, " + safe.quoted_sql(S("fac_server_timezone")) + ") = " + safe.quoted_sql(S("fac_server_timezone"))
                  + sql_loc_where
                  + (params.max_rooms ? " AND ROWNUM <= " + params.max_rooms : "");

          // 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_tijd) min_opstel_tijd"
              + "           , 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
                   + " AND res_ruimte_minbezoekers <= " + params.vis
                 : "")
               + ((params.activiteit_key || params.act_key_arr.length > 0)
                 ? " AND EXISTS (SELECT res_activiteit_key FROM res_activiteitdiscipline rad"
                   + "            WHERE rad.res_discipline_key = rg.res_discipline_key"
                   + (params.activiteit_key
                     ? "            AND rad.res_activiteit_key = " + params.activiteit_key
                     : "            AND rad.res_activiteit_key IN (" + params.act_key_arr.join(",") + ")")
                   + ")"
                 : "")
               + ((!params.activiteit_key && params.act_key_arr.length == 0 && 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 "; // sortering significant, matchen met de visuele groepering in het planbord (plaats? locatiecode? locatieomschrijving?, gebouw?)
               if ("sort" in params && !!params.sort)
               {
                 sql += (params.sort == "room_up" ? " alg_locatie_omschrijving, res_ruimte_nr " : "")
                      + (params.sort == "room_down" ? " alg_locatie_omschrijving, res_ruimte_nr DESC " : "")
                      + (params.sort == "cap_up" ? " alg_locatie_omschrijving, bmin " : "")
                      + (params.sort == "cap_down" ? " alg_locatie_omschrijving, bmin DESC " : "");
               }
               else
               {
                 sql += ("sort_on_count" in params && params.sort_on_count ? " aantal DESC, " : "")
                     + " alg_locatie_volgnr, alg_locatie_omschrijving, 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"));
          oRs.Close();

          // 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 + ")"
          if (params.maxrec)
          {
            sql += " WHERE ROWNUM <= " + params.maxrec;
          }

          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,
                         opsteltijd    : oRs("min_opstel_tijd").Value,
                         bMin          : oRs("bMin").Value,
                         bMax          : oRs("bMax").Value,
                         minbezoekers  : oRs("res_ruimte_minbezoekers").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,
                         catimage      : oRs("ins_discipline_image").Value,
                         omschrijving  : oRs("res_ruimte_omschrijving").Value,
                         locatie       : oRs("alg_locatie_omschrijving").Value,
                         loc_key       : oRs("alg_locatie_key").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,
                         res_ruimte_extern_meeting: oRs("res_ruimte_extern_meeting").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_status_bo_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"
                  + "     , ra.res_activiteit_key"
                  + "     , ra.res_activiteit_color"
                  + "     , ra.res_activiteit_posttime"
                  + "     , (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.
                  + "     , (SELECT count(1) FROM mld_melding mld_res"
                  + "         WHERE mld_res.res_rsv_ruimte_key = rv.res_rsv_ruimte_key) aantalM"
                  + "     , rsv_prepost.res_disc_params_preposttime"
                  + "     , ro.res_ruimte_opstel_key"
                  + "     , ro.res_ruimte_opstel_tijd"
                  + "     , rsv_prepost.res_ruimte_extern_id"
                  + "     , rv.res_rsv_ruimte_externnr"
                  + "     , rv.res_rsv_ruimte_externnr2"
                  + "     , rv.res_rsv_ruimte_externsyncdate"
                  + "     , " + lcl.xsqla("ra.res_activiteit_omschrijving", "ra.res_activiteit_key")
                  + "     , res_rsv_ruimte_bezoekers"
                  + "  FROM res_v_aanwezigrsv_ruimte rv"
                  + "     , res_ruimte_opstelling ro"
                  + "     , res_activiteit ra"
                  + (params.anonymous?", 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"
                  + "     , (SELECT res_ruimte_key, res_ruimte_extern_id, res_disc_params_preposttime"
                  + "          FROM res_disc_params, res_ruimte"
                  + "         WHERE res_disc_params.res_ins_discipline_key = res_ruimte.res_discipline_key) rsv_prepost"
                  + " 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"
                  + "   AND rv.res_activiteit_key = ra.res_activiteit_key"
                  + (params.anonymous
                    ? " 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 + "))"
                  + "   AND rsv_prepost.res_ruimte_key = res_ruimte_key_2"
                  + " 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,
                         bo_status    : oRs("res_status_bo_key").Value
                       };
            if (oRs("res_ruimte_opstel_key").Value)
              data.res_opstel_key = oRs("res_ruimte_opstel_key").Value;
            if (oRs("res_ruimte_opstel_tijd").Value)
              data.res_opstel_tijd = oRs("res_ruimte_opstel_tijd").Value;
            if (oRs("res_disc_params_preposttime").Value)
              data.rsv_preposttime = oRs("res_disc_params_preposttime").Value;
            data.act_key = oRs("res_activiteit_key").Value;
            if (oRs("res_activiteit_posttime").Value)
              data.act_post_time = oRs("res_activiteit_posttime").Value;
            if (oRs("res_activiteit_color").Value)
              data.act_color = oRs("res_activiteit_color").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;
            var externnr  = oRs("res_rsv_ruimte_externnr").Value;
            var externnr2 = oRs("res_rsv_ruimte_externnr2").Value;
            var externsyncdate = oRs("res_rsv_ruimte_externsyncdate").Value;
            var canWriteExtern = oRs("res_ruimte_extern_id").Value != null // Deze mogen nooit
                                 && ((S("msgraph_sync_level") & 6) == 2   // We hebben alleen schrijfrechten op de ruimte-resource (niet op de gebruikers)
                                      && (   (externnr == null && externnr2 == null)  // (Nog) niet gesynced
                                          || (externnr != null && externnr2 == null   // Deze reservering is vanuit Facilitor ge-initieerd
                                              && externsyncdate != null)) // Vanuit Outlook is de externsyncdate niet gevuld
                                      || (S("msgraph_sync_level") & 4));  // We hebben schrijfrechten op alle gebruikers
            if (externnr) { // Is dit geboekt vanuit een externe API die wij niet mogen wijzigen in Facilitor?
              data.extern_api = true;
              data.extern_readonly = !canWriteExtern;
            }

            data.activiteit = oRs("res_activiteit_omschrijving").Value;
            data.res_key = oRs("res_reservering_key").Value;
            data.res_volgnr = oRs("res_rsv_ruimte_volgnr").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("res_rsv_ruimte_bezoekers").Value)
                data.bezoekers = oRs("res_rsv_ruimte_bezoekers").Value;

              if (oRs("aantalA").Value)
                data.aantalA = oRs("aantalA").Value;
              if (oRs("aantalD").Value)
                data.aantalD = oRs("aantalD").Value;
              if (oRs("aantalM").Value)
                data.aantalM = oRs("aantalM").Value;
              data.intern = { omschrijving: oRs("res_rsv_ruimte_omschrijving").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_prs_perslid_key"
              + "     , rd.res_discipline_key"
              + "     , 1 type_res"
              + "     , rr.res_reservering_key"
              + "     , rr.res_rsv_ruimte_volgnr"
              + "     , rr.res_rsv_ruimte_omschrijving"
              + "     , res_rsv_ruimte_bezoekers"
              + "     , rr.res_ruimte_opstel_key"
              + "     , rr.mld_opdr_key"
              + "     , p1.prs_perslid_naam_friendly contact"
              + "     , p2.prs_perslid_naam_friendly host"
              + "     , " + lcl.xsqla("ra.res_activiteit_omschrijving", "ra.res_activiteit_key")
              + "  FROM res_v_aanwezigrsv_ruimte rr"
              + "     , res_v_aanwezigdeel rd"
              + "     , res_v_aanwezigrsv_deel rrd"
              + "     , res_activiteit ra"
              + "     , prs_v_perslid_fullnames_all p1"
              + "     , prs_v_perslid_fullnames_all p2"
              + " 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 rr.res_activiteit_key = ra.res_activiteit_key"
              + "   AND rr.res_rsv_ruimte_contact_key = p1.prs_perslid_key(+)"
              + "   AND rr.res_rsv_ruimte_host_key = p2.prs_perslid_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_prs_perslid_key"
              + "     , rd.res_discipline_key"
              + "     , 2 type_res"
              + "     , rr.res_reservering_key"
              + "     , rr.res_rsv_ruimte_volgnr"
              + "     , rr.res_rsv_ruimte_omschrijving"
              + "     , res_rsv_ruimte_bezoekers"
              + "     , rr.res_ruimte_opstel_key"
              + "     , rr.mld_opdr_key"
              + "     , p1.prs_perslid_naam_friendly contact"
              + "     , p2.prs_perslid_naam_friendly host"
              + "     , " + lcl.xsqla("ra.res_activiteit_omschrijving", "ra.res_activiteit_key")
              + "  FROM res_v_aanwezigrsv_ruimte rr"
              + "     , res_v_aanwezigdeel rd"
              + "     , res_v_aanwezigrsv_deel rrd"
              + "     , res_v_aanwezigdeel rd2"
              + "     , res_activiteit ra"
              + "     , prs_v_perslid_fullnames_all p1"
              + "     , prs_v_perslid_fullnames_all p2"
              + " 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 rr.res_activiteit_key = ra.res_activiteit_key"
              + "   AND rr.res_rsv_ruimte_contact_key = p1.prs_perslid_key(+)"
              + "   AND rr.res_rsv_ruimte_host_key = p2.prs_perslid_key(+)"
              + "   AND rd.res_ins_deel_key = rd2.res_ins_deel_key"
              + "   AND rd.res_discipline_key != " + disc_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_prs_perslid_key"
                + "     , rd.res_discipline_key"
                + "     , 1 type_res"
                + "     , 0 res_reservering_key"
                + "     , 0 res_rsv_ruimte_volgnr"
                + "     , NULL res_rsv_ruimte_omschrijving"
                + "     , NULL res_rsv_ruimte_bezoekers"
                + "     , NULL res_ruimte_opstel_key"
                + "     , NULL mld_opdr_key"
                + "     , NULL contact"
                + "     , NULL host"
                + "     , NULL res_activiteit_omschrijving"
                + "  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_res"
          var oRs = Oracle.Execute(sql);
          while (!oRs.eof)
          {
            // Bekijk het onderliggende fysieke ding of persoon
            var ins_d_key = oRs("res_ins_deel_key").Value ? oRs("res_ins_deel_key").Value :oRs("res_prs_perslid_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_res").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,
                                            res_volgnr: oRs("res_rsv_ruimte_volgnr").Value,
                                            ruimte_omschrijving: oRs("res_rsv_ruimte_omschrijving").Value || "",
                                            ruimte_bezoekers: oRs("res_rsv_ruimte_bezoekers").Value,
                                            ruimte_opstel_key: oRs("res_ruimte_opstel_key").Value,
                                            mld_opdr_key: oRs("mld_opdr_key").Value,
                                            contact: oRs("contact").Value || "",
                                            host: oRs("host").Value || "",
                                            activiteit: oRs("res_activiteit_omschrijving").Value || ""
                                           });
            }

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

          return existing_res;
        },

        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;
        },

        set_bloktijdenopdatum: function (p_bloktijden, p_resdate, p_geb_key)
        {
          if (p_geb_key)
          {
            var sql = "SELECT g.alg_gebouw_beginuur"
                    + "     , g.alg_gebouw_einduur"
                    + "     , CASE"
                    + "       WHEN g.alg_gebouw_beginuur IS NOT NULL"
                    + "       THEN g.alg_gebouw_beginuur * 60"
                    + "       ELSE -1"
                    + "       END mins_beginuur_gebouw"
                    + "     , CASE"
                    + "       WHEN g.alg_gebouw_einduur IS NOT NULL"
                    + "       THEN g.alg_gebouw_einduur * 60"
                    + "       ELSE -1"
                    + "       END mins_einduur_gebouw"
                    + "  FROM alg_gebouw g"
                    + " WHERE g.alg_gebouw_key = " + p_geb_key;
            var oRs = Oracle.Execute(sql);
            var beginuur_gebouw = oRs("alg_gebouw_beginuur").Value;
            var einduur_gebouw = oRs("alg_gebouw_einduur").Value;
            var mins_beginuur_gebouw = oRs("mins_beginuur_gebouw").Value;
            var mins_einduur_gebouw  = oRs("mins_einduur_gebouw").Value;
            oRs.close();
          }
          // algemene p_bloktijden verbijzonderen naar de gegeven datum
          for (var i = 0; i < p_bloktijden.length; i++)
          {
            var bi = p_bloktijden[i];
            var bisplit = bi.begin.split(":"); // JGL: Dit voelt als raar reverse-enigineeren maar zo staat het in de bloktijden JSON
            bi.startdatum = new Date(p_resdate);
            var mins = parseInt(bisplit[0], 10) * 60 + parseInt(bisplit[1], 10);
            if (p_geb_key && mins_beginuur_gebouw >= 0 && mins < mins_beginuur_gebouw)
            {
              mins = mins_beginuur_gebouw;
              bi.begin = (Math.floor(mins / 60) < 10? "0" : "") + Math.floor(mins / 60) + ":" + ((mins % 60) < 10? "0" : "") + (mins % 60);
            }
            bi.startdatum.setHours(0, mins, 0, 0);

            var bisplit = bi.end.split(":");
            bi.einddatum = new Date(p_resdate);
            var mins = parseInt(bisplit[0], 10) * 60 + parseInt(bisplit[1], 10);
            if (p_geb_key && mins_einduur_gebouw >= 0 && mins > mins_einduur_gebouw)
            {
              mins = mins_einduur_gebouw;
              bi.end = (Math.floor(mins / 60) < 10? "0" : "") + Math.floor(mins / 60) + ":" + ((mins % 60) < 10? "0" : "") + (mins % 60);
            }
            bi.einddatum.setHours(0, mins, 0, 0);

            // Als de einddatum voor de startdatum valt of gelijk is, dan kan dat blok verwijder worden.
            if (bi.einddatum <= bi.startdatum)
            {
              p_bloktijden.splice(i, 1);
              i--;
            }
          }

          return p_bloktijden;
        },

        getBloktijden: function(p_act_key, p_res_disc_key, p_resdate, p_geb_key)
        {
            var bloktijden = ((p_act_key && p_act_key > -1) ? res.getBloktijdenAct(p_act_key, p_resdate, p_geb_key) : []);
            if (bloktijden.length == 0) {
                bloktijden = res.getBloktijdenDisc(p_res_disc_key, p_resdate, p_geb_key);
            }
            return bloktijden;
        },

        // levert de reserveringstijd-intervallen die behoren tot de catalogus p_res_disc_key
        // het resultaat wordt vertaald naar tijden op specifieke datum p_resdate zodat je makkelijk
        // kunt vergelijken of een reservering overlapt met deze interval e.d.
        // Als een gebouw key is meegegeven, dan moet het begin- en eindtijdstip wel binnen de openingstijden van het gebouw zijn.
        getBloktijdenDisc: function (p_res_disc_key, p_resdate, p_geb_key)
        {
            var bloktijden = [];
            var sql = "SELECT " + lcl.xsqla("rd.res_disc_params_intervals", "rd.res_ins_discipline_key")
                    + "  FROM res_disc_params rd"
                    + " WHERE rd.res_ins_discipline_key = " + p_res_disc_key
            var oRs = Oracle.Execute(sql);
            if (oRs("res_disc_params_intervals").Value)
              bloktijden = JSON.parse(oRs("res_disc_params_intervals").Value);
            oRs.close();

            bloktijden = res.set_bloktijdenopdatum(bloktijden, p_resdate, p_geb_key);

            return bloktijden;
        },

        // levert de reserveringstijd-intervallen die behoren tot de activiteit p_act_key
        // het resultaat wordt vertaald naar tijden op specifieke datum p_resdate zodat je makkelijk
        // kunt vergelijken of een reservering overlapt met deze interval e.d.
        // Als een gebouw key is meegegeven, dan moet het begin- en eindtijdstip wel binnen de openingstijden van het gebouw zijn.
        getBloktijdenAct: function (p_act_key, p_resdate, p_geb_key)
        {
            var bloktijden = [];
            var sql = "SELECT " + lcl.xsqla("res_activiteit_intervals", "res_activiteit_key")
                    + "     , res_activiteit_master_key"
                    + "     , res_activiteit_duur"
                    + "   FROM res_activiteit "
                    + "  WHERE res_activiteit_key = " + p_act_key;
            var oRs = Oracle.Execute(sql);
            var master_act_key = oRs("res_activiteit_master_key").Value;
            var res_activiteit_duur = oRs("res_activiteit_duur").Value;
            if (oRs("res_activiteit_intervals").Value)
            {
              bloktijden = JSON.parse(oRs("res_activiteit_intervals").Value);
            }
            oRs.close();

            if (!bloktijden.length && master_act_key > 0) // misschien een child onder een master reservering
            {
                var slotsize = res_activiteit_duur * 60; // In minuten
                if (slotsize > 0)
                {
                    // zoek op p_resdate beschikbare reservering(en) die als master van
                    // p_act_key kunnen optreden
                    // TODO: ook scope van object-->ruimte er bij betrekken!
                    //       Het achterliggende ins_deel moet geregistreerd zijn in de
                    //       (eerste) alg_ruimte van de res_ruimte
                    var sql = "SELECT res_rsv_ruimte_key"
                            + "     , res_rsv_ruimte_van"
                            + "     , res_rsv_ruimte_tot"
                            + "  FROM res_rsv_ruimte rrr"
                            + " WHERE TRUNC(res_rsv_ruimte_van) = " + p_resdate.beginToSQL(true, true)
                            + "   AND res_rsv_ruimte_verwijder IS NULL"
                            + "   AND res_activiteit_key = " + master_act_key
                            + " ORDER BY res_rsv_ruimte_van";
                    var oRs = Oracle.Execute(sql);
                    while (!oRs.EOF)
                    {
                        var masterbegin = new Date(oRs("res_rsv_ruimte_van").Value);
                        var mastereind = new Date(oRs("res_rsv_ruimte_tot").Value);
                        // Bouw een bloktijden object
                        var blokstart = new Date(masterbegin); // begin eerstvolgende blok
                        var blokeind = new Date(masterbegin); // einde eerstvolgende blok
                        blokeind.setMinutes(blokeind.getMinutes() + slotsize);
                        while (blokeind <= mastereind)
                        {
                            bloktijden.push({ "name": toTimeString(blokstart) + "-" + toTimeString(blokeind),
                                              "begin": toTimeString(blokstart),
                                              "end": toTimeString(blokeind),
                                              "rsv_ruimte_master_key": oRs("res_rsv_ruimte_key").Value
                                            });
                            blokstart.setMinutes(blokstart.getMinutes() + slotsize);
                            blokeind.setMinutes(blokeind.getMinutes() + slotsize);
                        }
                        oRs.MoveNext();
                    }
                    oRs.Close();
                }
            }
            bloktijden = res.set_bloktijdenopdatum(bloktijden, p_resdate, p_geb_key)

            return bloktijden;
        },
        // Probeer gegeven een alg_ruimte_key een res_ruimte te vinden welke die alg_ruimte reserveert
        // Heeft bij koppelzalen een voorkeur voor de enkelvoudige zaal (mits ook gedefinieerd)
        alg_ruimte2res_ruimte: function _alg_ruimte2res_ruimte(alg_ruimte_key)
        {
            var res_ruimte_key = -1;
            // Dit kan nog uitgebreid worden zodat het meerdere "enkele_ruimten" in verschillende catalogi kan handelen
            // voor nu MAX() om AiAi uit te voorkomen
            var sql = "SELECT MAX(ra.res_ruimte_key) res_ruimte_key, COUNT(*)"
                    + "  FROM res_alg_ruimte ra"
                    + " WHERE ra.res_alg_ruimte_verwijder IS NULL"
                    + "   AND ra.alg_ruimte_key=" + alg_ruimte_key
                    + " ORDER BY COUNT(*)"; // eenvoudigste samenstelling eerst
            var oRs = Oracle.Execute(sql);
            if (!oRs.eof)
            {
                res_ruimte_key = oRs("res_ruimte_key").value || -1;
            }
            oRs.Close();
            return res_ruimte_key;
        },
        photoinfo: function _photoinfo(pres_deel_key, pins_deel_key, pprs_perslid_key)
        {
            if (pprs_perslid_key) {
                var resPerslid = new Perslid(pprs_perslid_key);
                var props = resPerslid.photoinfo();
                return {
                    photomap: props.RelativePath,
                    photopath: props.photopath,
                    photopaththumb: props.photopaththumb
                }
            }
            var props = flexProps("RESPHD", pres_deel_key, null, null, { getFiles: true });
            if (props.files.length) {
              return {
                photopath: props.files[0].deepurl,
                photopaththumb: props.RelativePath + "thumb/" + props.files[0].name
              }
            }
            props = flexProps("INSPHD", pins_deel_key, null, null, { getFiles: true });
            if (props.files.length) {
              return {
                photopath: props.files[0].deepurl,
                photopaththumb: props.RelativePath + "thumb/" + props.files[0].name
              }
            }
            return {};
        },
        getRuimteXY_sql: function()
        {
          var sql = "SELECT z_iad.ins_alg_ruimte_key"
                  + "     , z_rad.res_discipline_key"
                  + "     , AVG(z_iad.ins_deel_dwgx) ins_ruimte_x"
                  + "     , AVG(z_iad.ins_deel_dwgy) ins_ruimte_y"
                  + "  FROM res_v_aanwezigdeel z_rad"
                  + "     , ins_v_aanwezigdeel z_iad"
                  + " WHERE z_rad.res_ins_deel_key = z_iad.ins_deel_key"
                  + " GROUP BY z_iad.ins_alg_ruimte_key"
                  + "        , z_rad.res_discipline_key";
          return sql;
        },
        getNearestWP_sql: function(rsv_ruimte_key, params)
        {
          var params = params || {};
          if (!params.rsv)
          { // Eerst gegevens over de werkplekreservering ophalen met behulp van de rsv_ruimte_key als die niet gegeven zijn.
            var sql = "SELECT ar.alg_ruimte_key"
                    + "     , ar.alg_verdieping_key"
                    + "     , rsv_r.res_rsv_ruimte_van"
                    + "     , rsv_r.res_rsv_ruimte_tot"
                    + "     , rd.res_discipline_key"
                    + "     , insd.ins_deel_DWGX"
                    + "     , insd.ins_deel_DWGY"
                    + "     , z.ins_ruimte_x"
                    + "     , z.ins_ruimte_y"
                    + "  FROM res_rsv_ruimte        rsv_r"
                    + "     , res_rsv_deel          rsv_d"
                    + "     , res_v_aanwezigdeel    rd"
                    + "     , alg_v_aanwezigruimte  ar"
                    + "     , res_deel              resd"
                    + "     , ins_deel              insd"
                    + "     , (" + res.getRuimteXY_sql() + ") z"
                    + " WHERE rsv_r.res_rsv_ruimte_key = rsv_d.res_rsv_ruimte_key"
                    + "   AND rsv_r.alg_ruimte_key = ar.alg_ruimte_key"
                    + "   AND rsv_d.res_deel_key = rd.res_deel_key"
                    + "   AND rsv_d.res_deel_key = resd.res_deel_key"
                    + "   AND resd.res_ins_deel_key = insd.ins_deel_key"
                    + "   AND insd.ins_alg_ruimte_key = z.ins_alg_ruimte_key"
                    + "   AND rd.res_discipline_key = z.res_discipline_key"
                    + "   AND rsv_r.res_rsv_ruimte_key = " + rsv_ruimte_key;
            var oRs = Oracle.Execute(sql);
            params = { rsv: { extra_wp_nr: 0
                            , rsv_ruimte_key: rsv_ruimte_key
                            , discipline_key: oRs("res_discipline_key").Value
                            , verdieping_key: oRs("alg_verdieping_key").Value
                            , ruimte_key: oRs("alg_ruimte_key").Value
                            , van: new Date(oRs("res_rsv_ruimte_van").Value)
                            , tot: new Date(oRs("res_rsv_ruimte_tot").Value)
                            , x: oRs("ins_ruimte_x").Value
                            , y: oRs("ins_ruimte_y").Value
                            }
                      , wp: { x: oRs("ins_deel_DWGX").Value
                            , y: oRs("ins_deel_DWGY").Value
                            }
                     }
            oRs.Close();
          }
__Log(params);
          var sql = "SELECT res_wp.*"
                  + "  FROM (SELECT rd.res_deel_key"
                          + "     , ar.alg_ruimte_nr ||' - '|| rd.res_deel_omschrijving  res_deel_omschrijving"
                          + "     , " + ( params.wp && params.wp.x && params.wp.y
                                        ? "SQRT(POWER((id.ins_deel_DWGX - " + params.wp.x + "),2) + POWER((id.ins_deel_DWGY - " + params.wp.y + "),2))"
                                        : "1"
                                        ) + " afstand_werkplek"
                          + "     , " + ( params.wp && params.wp.x && params.wp.y
                                        ? "SQRT(POWER((z.ins_ruimte_x - " + params.rsv.x + "),2) + POWER((z.ins_ruimte_y - " + params.rsv.y + "),2))"
                                        : "CASE WHEN ar.alg_ruimte_key = " + params.rsv.ruimte_key + " THEN 0 ELSE 1 END"
                                        ) + " afstand_ruimte"
                          + "  FROM res_v_aanwezigdeel    rd"
                          + "     , ins_v_aanwezigdeel    id"
                          + "     , alg_v_aanwezigruimte  ar"
                          + "     , (" + res.getRuimteXY_sql() + ") z"
                          + " WHERE id.ins_alg_ruimte_key = ar.alg_ruimte_key"
                          + "   AND rd.res_ins_deel_key = id.ins_deel_key"
                          + "   AND (   rd.res_deel_vervaldatum IS NULL"
                          + "        OR rd.res_deel_vervaldatum > " + params.rsv.van.toSQL()
                          + "       )"
                          + ( params.rsv.ruimte_key > 0
                            ? " AND ar.alg_verdieping_key = (SELECT a.alg_verdieping_key FROM alg_ruimte a WHERE a.alg_ruimte_key = " + params.rsv.ruimte_key + ")"
                            : " AND ar.alg_verdieping_key = " + params.rsv.verdieping_key
                            )
                          + "   AND rd.res_discipline_key = " + params.rsv.discipline_key
                          + "   AND NOT EXISTS" // Ze mogen geen overlap met deze reservering hebben
                                + " (SELECT 1"
                                + "    FROM res_v_aanwezigrsv_deel rsv_d"
                                + "   WHERE rsv_d.res_deel_key = rd.res_deel_key"
                                + "     AND rsv_d.res_rsv_deel_van < " + params.rsv.tot.toSQL(true)
                                + "     AND rsv_d.res_rsv_deel_tot > " + params.rsv.van.toSQL(true)
                                + " )"
                          + "   AND z.ins_alg_ruimte_key = id.ins_alg_ruimte_key"
                          + "   AND z.res_discipline_key = rd.res_discipline_key"
                  + "       ) res_wp"
                  + " ORDER BY res_wp.afstand_ruimte"
                  + "        , res_wp.afstand_werkplek"
                  + "        , res_wp.res_deel_omschrijving";
__Log(sql)
//abort_with_warning("STOP");
          return sql;
        }
}

// 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.mld_opdr_key = params.mld_opdr_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);
    oRs.Close();
  }
  var ora_date2 = res_tot.endToSQL();

  // Reserveren van persoonen: res_v_aanwezigdeel.res_prs_perslid_key is dan gevuld.
  var sql1p = "SELECT " + (params.select_fields_p ? params.select_fields_p : params.select_fields)
            + "  FROM prs_perslid p"
            + "     , res_v_aanwezigdeel r"
            + "     , res_disc_params rdp"
            + "     , res_v_aanwezigdiscipline dis"
            + (((params.extra_from || "") != "")
              ? "     ,  " + params.extra_from
              : "")
            + " WHERE r.res_prs_perslid_key = p.prs_perslid_key"
            + "   AND rdp.res_ins_discipline_key = r.res_discipline_key"
            + "   AND rdp.res_ins_discipline_key = dis.ins_discipline_key"
            + "   AND dis.ins_discipline_min_level = 4"
            + (params.sdisc
              ? " AND r.res_discipline_key = " + params.sdisc
              : "");

    if (params.mld_opdr_key > 0)
    { // params.mld_opdr_key > 0:
      // Bepaal de uitvoerder van de opdrachtnemer en de aanvrager van de opdrachtgever.
      var sql = "SELECT o.mld_uitvoerende_keys"
              + "     , m.prs_perslid_key"
              + "     , m.mld_alg_locatie_key"
              + "     , g.alg_gebouw_key"
              + "  FROM mld_opdr o"
              + "     , mld_melding m"
              + "     , alg_v_allonrgoed_gegevens g"
              + " WHERE o.mld_melding_key = m.mld_melding_key"
              + "   AND m.mld_alg_onroerendgoed_keys = g.alg_onroerendgoed_keys(+)"
              + "   AND o.mld_opdr_key = " + params.mld_opdr_key
      oRs = Oracle.Execute(sql);
      var uitv_key = oRs("mld_uitvoerende_keys").Value;
      var aanvrager_key = oRs("prs_perslid_key").Value;
      var mld_loc_key = oRs("mld_alg_locatie_key").Value || -1;
      var mld_bld_key = oRs("alg_gebouw_key").Value || -1;
      oRs.Close();

      // Controleren of de opdrachtnemer/vakman (uitvoerder van de opdracht) de dienst verleend aan de opdrachtgever (bedrijf van de aanvrager van de melding).
      // De volgende opdrachtnemers/vakmannen zijn beschikbaar als uitvoerder:
      // 1) De opdrachtnemers/vakmannen waarbij geen diensten zijn opgegeven.
      // 2) De opdrachtnemers/vakmannen die de benodigde dienst leveren voor de betreffende opdrachtgever/aanvrager.
      //    a) Voor een bepaalde opdrachtgever (prs_bedrijf_voor_key > 0).
      //    b) Voor alle opdrachtgevers (prs_bedrijf_voor_key IS NULL).
      sql1p += (uitv_key > 0 && aanvrager_key > 0
               ? "    AND (NOT EXISTS (SELECT dl.prs_bedrijfdienstlocatie_key"
                 + "                     FROM prs_bedrijfdienstlocatie dl"
                 + "                    WHERE dl.prs_perslid_key = r.res_prs_perslid_key)"
                 + "    OR EXISTS (SELECT dl.prs_bedrijfdienstlocatie_key"
                 + "                 FROM prs_bedrijfdienstlocatie dl"
                 + "                WHERE dl.prs_perslid_key = r.res_prs_perslid_key"
                 + "                  AND dl.prs_dienst_key IN (SELECT prs_dienst_key"
                 + "                                              FROM prs_bedrijfdienstlocatie"
                 + "                                             WHERE prs_bedrijf_key = " + uitv_key
                 + (mld_loc_key > 0
                   ? "                                            AND (alg_locatie_key = " + mld_loc_key + " OR alg_locatie_key IS NULL)"
                   : "")
                 + (mld_bld_key > 0
                   ? "                                            AND (alg_gebouw_key = " + mld_bld_key + " OR alg_gebouw_key IS NULL)"
                   : "")
                 + "                                          )"
                 + "                  AND (dl.prs_bedrijf_voor_key = (SELECT ab.prs_bedrijf_key"
                 + "                                                   FROM prs_perslid p"
                 + "                                                      , prs_v_afdeling_boom ab"
                 + "                                                  WHERE p.prs_afdeling_key = ab.prs_afdeling_key"
                 + "                                                    AND p.prs_perslid_key = " + aanvrager_key + ")"
                 + "                    OR dl.prs_bedrijf_voor_key IS NULL))"
                 + "      )"
               : "");
    }

    sql1p += "    AND (r.res_deel_vervaldatum IS NULL OR r.res_deel_vervaldatum > " + ora_date2 + ")"
           + (params.group_by_p || "");

    // TODO: catalogus controle
    if (0&& params.res_ruimte_key < 0 && (params.alg_ruimte_key < 0))
    { // Locatie scope of nog groter. Rechten ook meenemen
      sql1p = discxalg3d (sql1p,
                          "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 : '');
    }

    var sqlScope = "";
    if (!(params.mld_opdr_key > 0))
    {
        sqlScope = "     , (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 || -1) > -1
                ? "             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_arr && params.loc_key_arr.length)
                ? "          AND aob.alg_locatie_key IN (" + params.loc_key_arr.join(", ") + ")"
                : (params.loc_key || -1) > -1
                  ? "          AND aob.alg_locatie_key=" + params.loc_key
                  : "")
              + ((params.geb_key_arr && params.geb_key_arr.length)
                ? "          AND aob.alg_gebouw_key IN (" + params.geb_key_arr.join(", ") + ")"
                : (params.geb_key || -1) > -1
                  ? "          AND aob.alg_gebouw_key=" + params.geb_key
                  : "")
              + ((params.ver_key_arr && params.ver_key_arr.length)
                ? "          AND aob.alg_verdieping_key IN (" + params.ver_key_arr.join(", ") + ")"
                : (params.ver_key || -1) > -1
                  ? "          AND aob.alg_verdieping_key=" + params.ver_key
                  : "")
              + ((params.res_ruimte_key || -1) > -1
                ? "          AND res_ruimte_key = " + params.res_ruimte_key + " AND rrr.alg_ruimte_key = aob.alg_ruimte_key"
                : "")
              + ((params.alg_ruimte_key || -1) > -1
                ? "          AND aob.alg_ruimte_key = " + params.alg_ruimte_key
                : "")
              + "        ) scope";
    }

    var sql1d = "SELECT {0}"
              + "  FROM ins_deel d"
              + "     , res_v_aanwezigdeel r"
              + "     , res_disc_params rdp"
              + "     , res_v_aanwezigdiscipline dis"
              + sqlScope
              + "{1}"
              + "     ,  res_v_deelscope ds"
              + (((params.extra_from || "") != "")
                ? "     ,  " + params.extra_from
                : "")
              + " WHERE r.res_ins_deel_key = d.ins_deel_key"
              + "   AND rdp.res_ins_discipline_key = r.res_discipline_key"
              + "   AND rdp.res_ins_discipline_key = dis.ins_discipline_key"
              + "   AND dis.ins_discipline_min_level IN (1,5)"
              + (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')";

      sql1d +="    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.mld_opdr_key > 0))
      {
        sql1d += "    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)";

        if (params.res_ruimte_key < 0 && (params.alg_ruimte_key < 0))
        { // Locatie scope of nog groter. Rechten ook meenemen
          sql1d = discxalg3d (sql1d,
                              "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 : '');
        }
      }
      else
        sql1d += "    AND ds.res_deel_alg_level = -1";

    // separate bulk-items from non bulk-items
    if (params.withbulk)
    {
      // temp table for count of bulk-items on unique description
      var sqlBulkCount = ", ("
                        + "     SELECT distinct"
                        + "         oms.res_deel_omschrijving,"
                        + "         COUNT(oms.res_deel_omschrijving) total,"
                        + "         oms.res_deel_key,"
                        + "         oms.res_ins_deel_key ins_deel_key"
                        + "     FROM"
                        + "         ("
                        + "             SELECT"
                        + "                 distinct res_deel_omschrijving, res_deel_key, res_ins_deel_key"
                        + "             FROM"
                        + "                 res_v_aanwezigdeel rx"
                        + "             WHERE"
                        + "                 res_deel_bulkreserveerbaar = 1"
                        + "                 AND res_deel_key = (SELECT min(res_deel_key) FROM res_v_aanwezigdeel ry WHERE rx.res_deel_omschrijving = ry.res_deel_omschrijving)"
                        + "                 AND res_deel_key IN ( " + sql1d.format("r.res_deel_key", "") + " )"
                        + "         ) oms,"
                        + "         res_v_aanwezigdeel r1"
                        + "     WHERE"
                        + "         oms.res_deel_omschrijving = r1.res_deel_omschrijving"
                        + "               AND r1.res_deel_key NOT IN ("
                        + "                                            SELECT distinct rrd.res_deel_key"
                        + "                                              FROM res_rsv_ruimte rrr, res_rsv_deel rrd, res_deel rd"
                        + "                                             WHERE     (   rrr.RES_RSV_RUIMTE_VAN BETWEEN " + res_van.toSQL(true)
                        + "                                                                                      AND " + res_tot.toSQL(true) + ""
                        + "                                                        OR rrr.RES_RSV_RUIMTE_TOT BETWEEN " + res_van.toSQL(true) + ""
                        + "                                                                                      AND " + res_tot.toSQL(true) + ")"
                        + "                                                   AND rrr.res_rsv_ruimte_key <> " + rsv_ruimte_key
                        + "                                                   AND rrd.res_rsv_ruimte_key = rrr.res_rsv_ruimte_key"
                        + "                                                   AND rrd.res_deel_key = rd.res_deel_key"
                        + "                                                   AND rd.res_deel_bulkreserveerbaar = 1"
                        + "                                          )"
                        + "     GROUP BY"
                        + "         oms.res_deel_omschrijving,"
                        + "         oms.res_deel_key,"
                        + "         oms.res_ins_deel_key"
                        + "   ) cnt";
      var sqlSelectedCount = ", ("
                        + "  SELECT DISTINCT oms.res_deel_omschrijving,"
                        + "                    COUNT (oms.res_deel_omschrijving) selected,"
                        + "                    oms.res_deel_key,"
                        + "                    oms.res_ins_deel_key ins_deel_key"
                        + "      FROM (SELECT DISTINCT"
                        + "                   rx.res_deel_omschrijving, rx.res_deel_key, rx.res_ins_deel_key"
                        + "              FROM res_deel rx, res_rsv_deel rrd"
                        + "             WHERE     rx.res_deel_bulkreserveerbaar = 1"
                        + "               AND rx.res_deel_key = rrd.res_deel_key"
                        + "               AND rrd.res_rsv_ruimte_key = " + params.rsv_ruimte_key + ") oms,"
                        + "           res_v_aanwezigdeel r1, res_rsv_deel rrd1"
                        + "     WHERE oms.res_deel_omschrijving = r1.res_deel_omschrijving"
                        + "               AND r1.res_deel_key = rrd1.res_deel_key"
                        + "               AND rrd1.res_rsv_ruimte_key = " + params.rsv_ruimte_key
                        + "     GROUP BY oms.res_deel_omschrijving,"
                        + "           oms.res_deel_key,"
                        + "           oms.res_ins_deel_key"
                        + "   ) sel";
      // bulk-item part of the union all sql
      var sql2 = " UNION ALL " + sql1d.format(params.select_bulkfields, sqlBulkCount + sqlSelectedCount)
                + " AND r.res_deel_bulkreserveerbaar = 1"
                + " AND r.res_deel_omschrijving = cnt.res_deel_omschrijving "
                + " AND r.res_deel_omschrijving = sel.res_deel_omschrijving (+) ";
      // complete sql with bulk-items separated from non bulk-items
      sql1d = sql1d.format((params.select_fields_d ? params.select_fields_d : params.select_fields), "") + " AND r.res_deel_bulkreserveerbaar = 0"
            + (params.group_by_d || params.group_by || "")
            + sql2;
    }
    else
    {
      sql1d = sql1d.format((params.select_fields_d ? params.select_fields_d : params.select_fields), "");
    }
  sql1d += (params.withbulk ? params.bulk_group_by : (params.group_by_d || params.group_by)) || "";

  var sql1;
  if (params.deprecated) // Planbord v1 ondersteunt personen reserveringen niet
    sql1 = sql1d;
  else
    sql1 = "({0} UNION {1})".format(sql1p ,sql1d);

  if (params.maxrec)
  {
    sql1 = "SELECT * FROM (" + sql1 + ") WHERE ROWNUM <= " + params.maxrec;
  }

  return sql1;
}

function getScopedResDeelSql(params)
{

  params = params || {};
  params.reg_key  = params.reg_key  || -1;
  params.dist_key = params.dist_key || -1;
  params.loc_key  = params.loc_key  || -1;
  params.geb_key  = params.geb_key  || -1;
  params.ver_key  = params.ver_key  || -1;
  params.rui_key  = params.rui_key  || -1;

  var alg_level;
  if      (params.rui_key  > 0) alg_level = 5;
  else if (params.ver_key  > 0) alg_level = 4;
  else if (params.geb_key  > 0) alg_level = 3;
  else if (params.loc_key  > 0) alg_level = 2;
  else if (params.dist_key > 0) alg_level = 1;
  else if (params.reg_key  > 0) alg_level = 0;
  else                          alg_level = -1;

  var res_tot = params.res_tot ? new Date(params.res_tot) : new Date();
  var ora_date2 = res_tot.endToSQL();

  // res_deel_alg scoping
  var alg_filter;
  switch (alg_level)
  {
    case 0: alg_filter = "alg_regio_key      = " + params.reg_key;  break;
    case 1: alg_filter = "alg_district_key   = " + params.dist_key; break;
    case 2: alg_filter = "alg_locatie_key    = " + params.loc_key;  break;
    case 3: alg_filter = "alg_gebouw_key     = " + params.geb_key;  break;
    case 4: alg_filter = "alg_verdieping_key = " + params.ver_key;  break;
    case 5: alg_filter = "alg_ruimte_key     = " + params.rui_key;  break;
  }

  // res_deel without alg scope
  var res_d = "SELECT ds.* "
            + "  FROM res_v_deelscope ds "
            + " WHERE ds.res_deel_alg_level = -1 ";

  // res_deel scoped for alg parents
  var res_d_arr = [];
  if (alg_level != -1)
  {
    switch (alg_level)
    { // note: no break -> multi switch
      case 5: res_d_arr.push("ds.alg_ruimte_key     = aob.alg_ruimte_key");
      case 4: res_d_arr.push("ds.alg_verdieping_key = aob.alg_verdieping_key");
      case 3: res_d_arr.push("ds.alg_gebouw_key     = aob.alg_gebouw_key");
      case 2: res_d_arr.push("ds.alg_locatie_key    = aob.alg_locatie_key");
      case 1: res_d_arr.push("ds.alg_district_key   = aob.alg_district_key");
      case 0: res_d_arr.push("ds.alg_regio_key      = aob.alg_regio_key");
    }
    res_d += "UNION ALL "
           + "SELECT ds.* "
           + "  FROM res_v_deelscope ds "
           + "     , (SELECT * "
           + "          FROM alg_v_onrgoed_boom aob "
           + "         WHERE aob." + alg_filter
           + "           AND ROWNUM = 1) aob "
           + " WHERE (" + res_d_arr.join(" OR ") + ")"
           + "   AND ds.res_deel_alg_level <= " + alg_level;
  }

  // res_deel scoped for underlying alg level
  if (alg_level > -1 && alg_level < 5) // no underlying alg_scope after 5
  {

    res_d_arr = [];
    var temp_sql = "ds.res_deel_alg_level = {0} AND EXISTS (SELECT '' FROM alg_v_onrgoed_boom aob WHERE {1} AND aob." + alg_filter + ")";
    switch (alg_level)
    { // note: no break -> multi switch
      case 0: res_d_arr.push(temp_sql.format("1", "ds.alg_district_key   = aob.alg_district_key"));
      case 1: res_d_arr.push(temp_sql.format("2", "ds.alg_locatie_key    = aob.alg_locatie_key"));
      case 2: res_d_arr.push(temp_sql.format("3", "ds.alg_gebouw_key     = aob.alg_gebouw_key"));
      case 3: res_d_arr.push(temp_sql.format("4", "ds.alg_verdieping_key = aob.alg_verdieping_key"));
      case 4: res_d_arr.push(temp_sql.format("5", "ds.alg_ruimte_key     = aob.alg_ruimte_key"));
    }

    res_d += " UNION ALL "
           + "SELECT ds.* "
           + "  FROM res_v_deelscope ds "
           + " WHERE ds.res_deel_alg_level > " + alg_level
           + "   AND (" + res_d_arr.join(" OR ") + ")";
  }
  // end res_d (res_deel_alg scoping)

  var sql_a = "SELECT a.res_artikel_vervaldatum  res_vervaldatum"
            + "     , 'C' res_cvtype"
            + "     , a.res_artikel_key"
            + "     , " + lcl.xsql("a.res_artikel_omschrijving", "a.res_artikel_key") + " res_omschrijving"
            + "     , " + lcl.xsql("a.res_artikel_opmerking",    "a.res_artikel_key") + " res_opmerking"
            + "     , NULL res_deel_key"
            + "     , NULL ins_deel_key"
            + "     , resd.alg_ruimte_key ins_alg_ruimte_key"
            + "     , resd.alg_locatie_key ins_alg_locatie_key"
            + "     , a.res_artikel_image  res_image"
            + "     , rdp.res_disc_params_preposttime"
            + "     , resd.res_discipline_key"
            + "     , resd.res_alg_level"
            + "     , resd.alg_regio_key"
            + "     , resd.alg_district_key"
            + "     , resd.alg_locatie_key"
            + "     , resd.alg_gebouw_key"
            + "     , resd.alg_verdieping_key"
            + "     , resd.alg_ruimte_key"
            + "  FROM res_v_aanwezigartikel  a"
            + "     , res_disc_params rdp"
            + "     , (SELECT ra.res_artikel_key"
            + "             , ra.res_discipline_key"
            + "             , DECODE(o.alg_onrgoed_niveau, 'L',2, 'G',3, 'V',4, 'R',5, -1) res_alg_level"
            + "             , NULL alg_regio_key"
            + "             , NULL alg_district_key"
            + "             , b.alg_locatie_key"
            + "             , b.alg_gebouw_key"
            + "             , b.alg_verdieping_key"
            + "             , b.alg_ruimte_key"
            + "          FROM res_v_aanwezigartikel ra"
            + "             , res_srtartikel_onrgoed o"
            + "             , alg_v_allonroerendgoed b"
            + "         WHERE ra.res_discipline_key = o.res_discipline_key"
            + "           AND b.alg_onroerendgoed_keys	= o.alg_onrgoed_key"
            + "           AND b.alg_type = o.alg_onrgoed_niveau"
            + "        ) resd"
            + " WHERE a.res_discipline_key = resd.res_discipline_key"
            + "   AND rdp.res_ins_discipline_key = a.res_discipline_key";

  var sql_d = "SELECT a.res_deel_vervaldatum  res_vervaldatum"
            + "     , 'V' res_cvtype"
            + "     , NULL res_artikel_key"
            + "     , " + lcl.xsql("a.res_deel_omschrijving", "a.res_deel_key") + " res_omschrijving"
            + "     , " + lcl.xsql("a.res_deel_opmerking", "a.res_deel_key") + " res_opmerking"
            + "     , resd.res_deel_key"
            + "     , d.ins_deel_key"
            + "     , COALESCE(d.ins_alg_ruimte_key_org, d.ins_alg_ruimte_key) ins_alg_ruimte_key"
            + "     , d.ins_alg_locatie_key"
            + "     , a.res_deel_image  res_image"
            + "     , rdp.res_disc_params_preposttime"
            + "     , a.res_discipline_key"
            + "     , resd.res_deel_alg_level res_alg_level"
            + "     , resd.alg_regio_key"
            + "     , resd.alg_district_key"
            + "     , resd.alg_locatie_key"
            + "     , resd.alg_gebouw_key"
            + "     , resd.alg_verdieping_key"
            + "     , resd.alg_ruimte_key"
            + "  FROM ins_deel d"
            + "     , res_v_aanwezigdeel a"
            + "     , res_disc_params rdp"
            + "     , (" + res_d + ") resd"
            + " WHERE a.res_ins_deel_key = d.ins_deel_key"
            + "   AND a.res_deel_key = resd.res_deel_key"
            + "   AND rdp.res_ins_discipline_key = a.res_discipline_key"
            + "   AND COALESCE(d.ins_alg_ruimte_type_org, d.ins_alg_ruimte_type) IN ('R','T')";

  var sql = "SELECT " + params.select_fields
          + "  FROM (" + sql_a + " UNION ALL " + sql_d + ") r"
          + ( ((params.extra_from || "") != "")
            ? "     ,  " + params.extra_from
            : "")
          + " WHERE (r.res_vervaldatum IS NULL OR r.res_vervaldatum > " + ora_date2 + ")"
          + (params.sdisc
            ? " AND r.res_discipline_key = " + params.sdisc
            : "");

  sql = discxalg3d (sql,
                     "r.res_discipline_key",
                     "r.alg_regio_key",
                     "r.alg_district_key",
                     "r.alg_locatie_key",
                     "r.alg_gebouw_key",
                     "r.alg_verdieping_key",
                     "r.alg_ruimte_key",
                     params.autfunction,
                     typeof params.sdisc == "number"? params.sdisc : '',
                     "",  // forwrite
                     null,  // minlevel
                     params.sql_group_by);
  return sql;
}

function planbordTooltipShowing(res_rsv_ruimte_key)
{
  var tonen = 0;
  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 = " + res_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;
    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 = 0;

  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);
}

// grid Objects
var tijdbalken_kop;
var tijdbalken_voet;
function Tijdbalken(res_van, nr_days, p_res_t1, p_res_t2, fnStep, row)
{
  var _res_van = res_van;
  var _nr_days = nr_days;
  var _p_res_t1 = p_res_t1;
  var _p_res_t2 = p_res_t2;
  var _fnStep = fnStep;
  var _row = row || 1;
  var _start_column = 3;
  var _end_column;
  var _tijdbalk_arr = [];
  this.render = function()
  {
      var index;
      var is_first_date;
      var is_last_date;
      var res_date = new Date(_res_van);
      var html = '';
      var start_column = _start_column;
      for (index = 0; index < _nr_days; index++)
      {
          is_first_date = index == 0;
          is_last_date = index == (_nr_days - 1);
          _tijdbalk_arr[index] = new Tijdbalk(res_date, _p_res_t1, _p_res_t2, is_first_date, is_last_date, _fnStep, _row, _start_column, _nr_days);
          html += _tijdbalk_arr[index].render(_nr_days);
          _end_column = _tijdbalk_arr[index].end_column();
          _start_column = _end_column + 1;
          res_date.setDate(res_date.getDate() + 1);
      }
      return html;
  }
  // array of time-lines
  this.tijdbalk_arr = function()
  {
      return _tijdbalk_arr;
  }
  // first column of the time-line total
  this.start_column = function()
  {
      return _start_column;
  }
  // last column of the time-line total
  this.end_column = function()
  {
      return _end_column;
  }
  // first time of the time-line total
  this.start_time = function()
  {
      return _p_res_t1;
  }
  // last column of the time-line total
  this.end_time = function()
  {
      return _p_res_t2;
  }
}
// Object for time-line of 1 day
function Tijdbalk(res_date, p_res_t1, p_res_t2, is_first_date, is_last_date, fnStep, row, start_column, nr_days)
{
  var that = this;
  var _fnStep = fnStep;
  var _p_res_t1 = p_res_t1;
  var _p_res_t2 = p_res_t2;
  var _is_first_date = is_first_date || false;
  var _is_last_date = is_last_date || false;
  var _res_date = new Date(res_date);
  var _row = row;
  var _start_column = start_column;
  var _end_column;
  var _nr_days = nr_days;
  var _unit_length = (_nr_days >= 3 ? 1 : (S("res_h") || 0.25));
  var _units_hour = (1 / _unit_length);
  // units between to_time - from_time
  var units = function()
  {
      return (Math.floor((_p_res_t2 - _p_res_t1) * _units_hour));
  }
  var dateFmt = function(nr_days)
  {
      var dateString = toDateString(_res_date);
      var date_split_arr = dateString.split(" ");
      var date_dmy_arr = date_split_arr[1].split("-");
      var d = String((date_dmy_arr[0] >= 10 ? date_dmy_arr[0] : "0" + Number(date_dmy_arr[0])));
      var m = String((date_dmy_arr[1] >= 10 ? date_dmy_arr[1] : "0" + Number(date_dmy_arr[1])));
      if (nr_days > 12)
      {
        return d;
      }
      else if (nr_days > 7)
      {
        return date_split_arr[0] + " " + d;
      }
      else if (nr_days > 3)
      {
        return date_split_arr[0] + " " + d + "-" + m;
      }
      else
      {
        return dateString;
      }
  }
  // array with starttime for each unit
  var starttime_arr = function()
  {
      var tb = {};
      // for serverside
      if (typeof that != "undefined")
      {
        tb._units = units();
        tb._p_res_t1 = _p_res_t1;
        tb._unit_length = _unit_length;
      }
      // for clientside
      else
      {
        tb = tijdbalken[0];
      }
      var arr = [];
      for (index = 0; index < tb._units; index++)
      {
          arr.push(tb._p_res_t1 + ( index * tb._unit_length ));
      }
      return arr;
  }
  // render the html
  this.render = function(nr_days)
  {
      var _times = function()
      {
        var index;
        var h;
        var m;
        var html = '';
        var times_count = 0;
        var hour_class;
        _end_column = _start_column;
        for (index = _p_res_t1; index <= _p_res_t2; index++)
        {
          _end_column = _start_column + (times_count * _units_hour);

          h = Math.floor(index);
          if (h < 10) h = "0" + h;
          m = Math.floor((index - h)*60);
          if (m < 10) m = "0" + m;
          hour_class = [" hour-1", " hour-2", " hour-3", " hour-4"][(index - _p_res_t1) % 4];
          // only show time when hour and index come together
          if (_end_column % 1 == 0)
          {
              html += '<div class="plantimegrid' + times_count + hour_class + '" style="grid-column: ' + _end_column + '/span 2; grid-row: ' + (_row + 1) + ';">' + h + ':' + m + '</div>';
          }
          times_count++;
        }
        _end_column = _start_column + units();
        html = '<div class="plantimegrid first" style="grid-column: ' + (_start_column + 1) + '; grid-row: ' + (_row + 1) + ';"></div>'
             + '<div class="plantimegrid last" style="grid-column: ' + (_end_column + 1) + '; grid-row: ' + (_row + 1) + ';"></div>'
             +html
        return html;
      }

      return '<div class="plandategrid" style="grid-column: ' + (_start_column + 1) + '/' + (_start_column + 1 + units()) + '; grid-row: ' + _row + ';">'
           + ((_is_first_date && _fnStep) ?
                  '<span title="' + L('lcl_res_plan_tt_eerder')
                + '" class="details" id="resplanprev" onclick="' + _fnStep + '(-1, -1)">'
                + I('fa-arrow-left')
                + '</span> '
                + '<span title="' + L('lcl_res_plan_tt_meer_eerder')
                + '" class="details" id="resplanmoreprev" onclick="' + _fnStep + '(-1, 0)">'
                + I('fa-plus-circle')
                + '</span> '
              :
                  '')
            + dateFmt(nr_days)
            + ((_is_last_date && _fnStep) ?
                  ' <span title="' + L('lcl_res_plan_tt_meer_later')
                + '" class="details" id="resplanmorenext" onclick="' + _fnStep + '(0, +1)">'
                + I('fa-plus-circle')
                + '</span>'
                + '<span title="' + L('lcl_res_plan_tt_later')
                + '" class="details" id="resplannext" onclick="' + _fnStep + '(+1, +1)">'
                + I('fa-arrow-right')
                + '</span>'
              :
                '')
            + '</div>'
            + _times();
  }
  // closure for measure of px per unit (for clientside execution)
  function unit_px()
  {
      var tb = tijdbalken[0];
      return ($("div.plantimegrid.last").length > 0 ? ($("div.plantimegrid.last").first().position().left - $("div.plantimegrid.first").first().position().left) / (tb._units) : 0);
  }
  // closure for measure of px per hour (for clientside execution)
  function hour_px()
  {
      var tb = tijdbalken[0];
      return tb._units_hour * tb._unit_px();
  }
  // length of unit
  this.unit_length = function()
  {
      return _unit_length;
  }
  // render the client-side array
  this.render_object = function()
  {
      return '{'
           + '_res_date: ' + Date.parse(_res_date)
           + ', _p_res_t1:' + _p_res_t1
           + ', _p_res_t2:' + _p_res_t2
           + ', _start_column:' + (_start_column + 1)
           + ', _end_column:' + _end_column
           + ', _unit_length:' + _unit_length
           + ', _units_hour:' + _units_hour
           + ', _units:' + units()
           + ', _unit_px: ' + unit_px
           + ', _hour_px: ' + hour_px
           + ', _starttime_arr: [' + starttime_arr() + ']'
           + ', _gridtimecolumns: ' + this.gridtimecolumns
           + ', _gridtimeposition: ' + this.gridtimeposition
           + ', _gridareaposition: ' + this.gridareaposition
           + '}';
  }
  // first column of the time-line of this day
  this.start_column = function()
  {
      return _start_column;
  }
  // last column of the time-line of this day
  this.end_column = function()
  {
      return _end_column;
  }
  // object with column from - to + diff on time-from - time-to block
  this.gridtimecolumns = function(starttime, endtime)
  {
      var tb = {};
      // for serverside
      if (typeof that != "undefined")
      {
        tb._starttime_arr = starttime_arr();
        tb._start_column = _start_column + 1;
      }
      // for clientside
      else
      {
        tb = tijdbalken[0];
      }
      var index;
      var indexstart;
      var indexend;
      var arr = tb._starttime_arr;
      var result = {};
      for (index = 0; index < arr.length; index++)
      {
          if (typeof indexstart == "undefined" || arr[index] <= starttime)
          {
              indexstart = tb._start_column + index;
          }
          if (typeof indexend == "undefined" || arr[index] < endtime)
          {
              indexend = tb._start_column + index;
          }
      }
      var indexdiff = indexend - indexstart;
      result.indexstart = indexstart;
      result.indexend = indexend;
      result.indexdiff = indexdiff;
      return result;
  }
  // style for time-from - time-to block
  this.gridtimeposition = function(starttime, endtime, gridtimecolumns)
  {
      var tb = {};
      // for serverside
      if (typeof that != "undefined")
      {
        tb._gridtimecolumns = this.gridtimecolumns;
      }
      // for clientside
      else
      {
        tb = tijdbalken[0];
      }
      var columns = gridtimecolumns || tb._gridtimecolumns(starttime, endtime);
      return 'grid-column: ' + columns.indexstart + (columns.indexdiff > 0 ? "/span " + (columns.indexdiff + 1) : "") + ";";
  }
  // style for grid-area block
  this.gridareaposition = function(row, starttime, endtime, gridtimecolumns)
  {
      var tb = {};
      // for serverside
      if (typeof that != "undefined")
      {
        tb._gridtimecolumns = this.gridtimecolumns;
      }
      // for clientside
      else
      {
        tb = tijdbalken[0];
      }
      var columns = gridtimecolumns || tb._gridtimecolumns(starttime, endtime);
      return row + "/" + columns.indexstart + "/auto" + (columns.indexdiff > 0 ? "/span " + (columns.indexdiff + 1) : "/auto");
  }
}

%>
