<% /*
    $Revision$
    $Id$

    File:           api2.inc
    Description:    Functies voor manipuleren van de modellen
    Notes:          Hier wordt weinig met de 'buitenwereld' gecommuniceerd,
                    dat gebeurt voornamelijk in api2_rest.inc

    Status:         Nog TODO's wegwerken

*/
%>
<!-- #include file="../Shared/save2db.inc" -->
<%
api2 = {
    form2JSONdata: function _form2JSONdata(model, params, formfields) // Maak een jsondata-object voor gebruik vanuit model_xxx.inc
    {
        var jsondata ={id: params.filter.id};

        // Form parameters van gewone velden.
        for (i=0; i<formfields.length; i++)
        {
            // Voeg een nieuwe combinatie naam:waarde toe aan het jsondata object als er een frm of val bekend is.
            var val;
            if (formfields[i].frm)
            {
                var fld = model.fields[formfields[i].name];
                var typ = model.fields[formfields[i].name].typ;

                // Haal waarde uit de formparameters.
                if (typ == "check" || typ == "check0")
                {
                    if (Request.Form("has_" + formfields[i].frm).count == 0)
                        continue; // niet aanwezig
                }
                else
                {
                    if (Request.Form(formfields[i].frm).count == 0)
                        continue; // niet aanwezig
                }

                switch (typ)
                {
                    case "float":
                    case "currency":
                        val = getFParamFloat(formfields[i].frm, null);
                        break;
                    case "key":
                    case "number":
                        if (fld.bits)
                        {
                            val = 0;
                            for (var b=0; b < fld.bits.length; b++)
                            {
                                var bit = fld.bits[b];
                                if (bit.typ == "check")
                                {
                                    for (var r=0; r < bit.radios.length; r++)
                                    {
                                        var radio = bit.radios[r];
                                        var invert = radio.invert;

                                        val += ((Request.Form(radio.name).count==1)?(invert?0:radio.mask):(invert?radio.mask:0));
                                    }
                                }
                                else if (bit.typ == "radio")
                                {
                                    val += parseInt(Request.Form(bit.name));
                                }
                                else if (bit.typ == "LOV")
                                {
                                    val += getFParamInt(bit.name, 0);
                                }
                            }
                        }
                        else
                        {
                            val = getFParamInt(formfields[i].frm, null);
                        }
                        break;
                    case "check":
                    case "check0":
                        var invert = model.fields[formfields[i].name].invert;
                        val = (Request.Form(formfields[i].frm).count==1)?(invert?0:1):(invert?1:0);
                        break;
                    case "date":
                    case "datetime":
                        val = getFParamDate(formfields[i].frm, null);
                        break;
                    case "memo":
                    case "varchar":
                        val = getFParam(formfields[i].frm, null);
                        break;
                    default:
                    {
                        __DoLog(model.fields[formfields[i].name].typ);
                        INTERN_ERROR_UNKNOWN_TYPE;
                    }
                }
             }
            else
                val = formfields[i].val;
            //
            jsondata[formfields[i].name] = val;
        }
        return jsondata;
    },

    // Verwerk filtervelden die in de url zijn meegegeven
    sqlfilter: function _sqlfilter(params, model)
    {
        var wheres = [];
        if (params.filter)
        {
            for (var fld in model.fields)
            {
                if (fld.substring(0,1) == "_")
                    continue;
                var field = model.fields[fld];
                if ("filter" in field)
                    var filter = field.filter
                else // default filtertypes
                {
                    switch (field.typ)
                    {
                        case "key":
                            filter = "exact";
                            break;
                        case "float":
                        case "currency":
                        case "number":
                            filter = "range";
                            break;
                        case "check":
                        case "check0":
                            filter = "exact";
                            break;
                        case "varchar":
                        case "memo":
                            filter = "like";
                            break;
                        case "date":
                        case "datetime":
                            filter = "range";
                            break;
                        default:
                            if (fld in params.filter) // Je probeert er wel op te filteren?
                            {
                                __DoLog(field);
                                __DoLog(params.filter);
                                UNKNOWN_FILTER_TYPE;
                            }
                    }
                }

                // We kijken of dit model-veld in de filterparameters voorkomt. Voor ranges kunnen prefixes in gebruik zijn
                var filterval = null;
                if (fld in params.filter || "start_" + fld in params.filter || "end_" + fld in params.filter)
                {
                    // filterval is de meegegeven filterwaarde voor dit veld
                    if (fld in params.filter)
                    {
                        filterval = params.filter[fld];
                        if (filterval === "" || filterval === null)
                            continue;
                        if (filterval < 0 && field.typ == "key")
                            continue;
                    }

                    // Voor ranges komt de naam (misschien) niet letterlijk voor, maar (mogelijk) met start_ of end_ ervoor
                    // meerdere filters voor 1 veld dus wellicht
                    if (filter == "range") {
                        var filterval1 = params.filter["start_" + fld];
                        var filterval2 = params.filter["end_" + fld];
                        // Als er maar 1 filterwaarde is, dan gedraagt zich die als start_
                        if (filterval && !filterval1)
                            filterval1 = filterval;
                        if ((filterval1 === "" || filterval1 === null) && (filterval2 === "" || filterval2 === null))
                            continue;
                    }
                    // Nu is ook voor ranges alles weer normaal
                    var clause;
                    var operand = " = ";
                    switch (field.typ)
                    {
                        case "key":
                            if (!filterval)
                                continue;
                            if (filterval instanceof Array)
                            {
                                safe_val = filterval.join(",");
                                if (typeof filter != 'function')
                                  safe_val = "(" + safe_val + ")";
                                operand = " IN ";
                            }
                            else if (String(filterval).indexOf(",") != -1) // let op: bij buildings/1234.json is id al numeriek gemaakt
                                                                           // NB: index=-1 als het geen array is.
                            {
                                var arr = params.filter[fld].split(",");
                                safe_val = safe.int_array(arr).join(",");
                                if (typeof filter != 'function')
                                  safe_val = "(" + safe_val + ")";
                                operand = " IN ";
                            }
                            else if (String(filterval).substr(0,1) == '~'
                                            && field.foreign
                                            && typeof field.foreign == 'string'
                                        ) // Als je een foreign filter met ~ begint dan filtert hij op de tekst
                            {
                                operand = " IN ";
                                var foreign = foreignKeyTable(field.foreign);
                                if (!foreign)
                                    MISSING_FOREIGN;
                                var fieldname = (foreign.name||foreign.desc);
                                var foreignsql = "SELECT " + foreign.key
                                                 + "  FROM " + foreign.tbl + " " + (foreign.alias||"xx")
                                                 + " WHERE ";
                                if (foreign.where)
                                    foreignsql += foreign.where + " AND ";
                                foreignsql += "UPPER(" + (foreign.alias||"xx") + "." + fieldname + ") LIKE " + safe.quoted_sql_wild("%"+String(filterval).substr(1) + "%");
                                safe_val = foreignsql;
                                if (typeof filter != 'function')
                                  safe_val = "(" + safe_val + ")";
                            }
                            else
                            {
                                var safe_val = parseInt(filterval, 10);
                                if (isNaN(safe_val))
                                {
                                    return ["0=1"];
                                }
                            }
                            break;
                        case "float":
                        case "currency":
                            var safe_val = parseFloat(filterval);
                            if (filter == "range")
                            {
                                if (filterval1) {
                                    var safe_val1 = parseFloat(filterval1);
                                }
                                if (filterval2) {
                                    var safe_val2 = parseFloat(filterval2);
                                }
                                if (filterval1 && filterval2) {
                                    operand = " BETWEEN ";
                                    safe_val = safe_val1+" AND "+safe_val2;
                                } else if (filterval1) {
                                    operand = " >= ";
                                    safe_val = safe_val1;
                                } else if (filterval2) {
                                    operand = " <= ";
                                    safe_val = safe_val2;
                                }
                            }
                            break;
                        case "number":
                            var safe_val = parseInt(filterval, 10);
                            if (filterval && isNaN(safe_val))
                            {
                                return ["0=1"]; // niets gevonden
                            }
                            if (filter == "range")
                            {
                                if (filterval1) {
                                    var safe_val1 = parseInt(filterval1, 10);
                                }
                                if (filterval2) {
                                    var safe_val2 = parseInt(filterval2, 10);
                                }
                                if (filterval1 && filterval2) {
                                    operand = " BETWEEN ";
                                    safe_val = safe_val1+" AND "+safe_val2;
                                } else if (filterval1) {
                                    operand = " >= ";
                                    safe_val = safe_val1;
                                } else if (filterval2) {
                                    operand = " <= ";
                                    safe_val = safe_val2;
                                }
                            }
                            break;
                        case "check":
                        case "check0":
                            var safe_val = parseInt(filterval, 10);
                            if (isNaN(safe_val))
                            {
                                return ["0=1"]; // niets gevonden
                            }
                            break;
                        case "varchar": // Als fld.match(/^fclt_d_/) dan is het een old-style rapport filterveld
                        case "memo":
                            if (filterval instanceof Array)
                            {
                                safe_val = safe.quoted_sql_join(filterval);
                                if (typeof filter != 'function')
                                  safe_val = "(" + safe_val + ")";
                                operand = " IN ";
                            }
                            else switch (filter)
                            {
                                case "exact":
                                    if (field.autolike) // Doen we bij rapportages
                                    {
                                        filter = "like"; // Lijkt tegenstrijdig voor 'exact' maar we staan nu wel
                                                         // toe dat je zelf *, % en _ gebruikt
                                        var safe_val = safe.quoted_sql_wild(filterval);
                                    }
                                    else
                                        var safe_val = safe.quoted_sql(filterval);
                                    break;
                                case "like":
                                    var safe_val = safe.quoted_sql_wild("%"+filterval+"%");
                                    break;
                                case "range":
                                    var safe_val;
                                    if (filterval1) {
                                        if (fld.match(/^fclt_d_/))
                                            safe_val1 = api2.toDate(filterval1).beginToSQL();
                                        else
                                            safe_val1 = safe.quoted_sql(filterval1);
                                    }
                                    if (filterval2) {
                                        if (fld.match(/^fclt_d_/))
                                            safe_val2 = api2.toDate(filterval2).endToSQL();
                                        else
                                            safe_val2 = safe.quoted_sql(filterval2);
                                    }
                                    if (filterval1 && filterval2) {
                                        operand = " BETWEEN ";
                                        safe_val = safe_val1+" AND "+safe_val2;
                                    } else if (filterval1) {
                                        operand = " >= ";
                                        safe_val = safe_val1;
                                    } else if (filterval2) {
                                        operand = " <= ";
                                        safe_val = safe_val2;
                                    }
                                    break;
                            }
                            break;
                        case "date":   // onderscheid date en datetime?
                        case "datetime":
                            if (filter == "exact")
                            {
                                var safe_val = api2.toDate(filterval);
                                safe_val = safe_val.toSQL(); // Een exacte *tijd* component ondersteunen we niet.
                                operand = " = ";
                            }
                            else if (filter == "range")
                            {
                                var safe_val;
                                if (filterval1) {
                                    var safe_val1 = api2.toDate(filterval1);
                                    var withtime = (field.typ == "datetime" && api2.hasTime(safe_val1));
                                    safe_val1 = withtime?safe_val1.toSQL(true):safe_val1.beginToSQL();
                                }
                                if (filterval2) {
                                    var safe_val2 = api2.toDate(filterval2);
                                    var withtime = (field.typ == "datetime" && api2.hasTime(safe_val2));
                                    safe_val2 = withtime?safe_val2.toSQL(true):safe_val2.endToSQL();
                                }
                                if (filterval1 && filterval2) {
                                    operand = " BETWEEN ";
                                    safe_val = safe_val1+" AND "+safe_val2;
                                } else if (filterval1) {
                                    operand = " >= ";
                                    safe_val = safe_val1;
                                } else if (filterval2) {
                                    operand = " <= ";
                                    safe_val = safe_val2;
                                }
                            }
                            break;
                        default:
                            __DoLog(field.typ);
                            UNKNOWN_FILTER_TYPE;
                    }
                    var dbs = field.dbs;
                    if (model.aliasprefix)
                        dbs = model.aliasprefix + dbs;
                    if (dbs.indexOf(".") < 0)
                        dbs = model.table + "." + field.dbs;
                    if (field.sql && !field.keepdbsforfilter)
                        dbs = field.sql;  /* dit werkt bv voor cnt-XD */

                    if (filter == "like")
                    {
                      if (operand != " IN ")
                        operand = " LIKE ";
                      if (field.typ == "varchar" || field.typ == "memo")
                      {
                        dbs = "UPPER("+dbs+")";
                        safe_val = safe_val.toUpperCase();
                      }
                    }
                    if (filter == "range" && fld.match(/^fclt_d_/))
                    {
                        dbs = "TO_DATE(" + dbs + ",'dd-mm-yyyy')";
                    }
                    if (filter == "exact" && (field.typ == "date" || field.typ == "datetime"))
                    {
                        dbs = "TRUNC("+dbs+")";
                    }

                    if (fld == 'id') // Bij filtering op id negeren we *al het andere*
                        return [dbs + operand + safe_val];

                    // Als filter een functie is dan de functie aanroepen.
                    if (typeof filter == 'function')
                      wheres.push(filter(safe_val));
                    else
                      wheres.push(dbs + operand + safe_val);
                }
            }
            if (params.filter.includefilter) // Bijvoorbeeld "rooms.location,configurations.res_opstelling_key"
            {
                var incarr = params.filter.includefilter.split(",");
                for (var i = 0; i < incarr.length; i++)
                {
                    var inc = incarr[i].split(".")[0];
                    var fld = incarr[i].split(".")[1];
                    var incmodel = model.includes[inc].model;
                    var newfilter = {};
                    newfilter[fld] = params.filter[fld];
                    var wheresinc = api2.sqlfilter({ filter: newfilter }, incmodel);
                    if (wheresinc.length)
                    {
                        // TODO: res_alg_ruimte heeft ook nog weer een foreign op het locatiekey veld....
                        wheresinc.push(incmodel.table + "." + incmodel.fields[model.includes[inc].joinfield].dbs + " = " + model.table + "." + model.fields.id.dbs);
                        var where = "EXISTS (SELECT 1 FROM {0} WHERE {1})".format(incmodel.table, wheresinc.join(" AND "))
                        wheres.push(where);
                    }
                }
            }
        }
        return wheres;
    },

    // Als er voor een veldnaam een alias moet worden gebruikt, doe het dan op deze manier.
    sqlfield_alias: function _sqlfield_alias(model, fld)
    {
        var field = model.fields[fld];
        return model.aliasprefix + (field.dbs == model.primary ? field.dbs : fld);
    },

    // Vult bij alle varchar en memo velden de len property in als dat nog niet is gedaan
    setfieldlengths: function _setfieldlengths(model)
    {
        var dblengths = null;
        for (var fld in model.fields)
        {
            if (fld.substring(0,1) == "_")
                continue;
            var field = model.fields[fld];
            if (!field.dbs || field.hidden || field.len || field.readonly)
                continue;
            if (field.typ != "varchar" && field.typ != "memo")
                continue;
            if (!dblengths)
            {
                dblengths = {};
                var sql = "SELECT column_name, data_length"
                        + "  FROM user_tab_columns"
                        + " WHERE table_name = " + safe.quoted_sql_upper(model.table)
                        + "   AND data_type = 'VARCHAR2'";
                var oRs = Oracle.Execute(sql);
                while (!oRs.Eof)
                {
                    dblengths[oRs("column_name").Value] = oRs("data_length").Value;
                    oRs.MoveNext();
                }
                oRs.Close()
                __Log(dblengths);
            }
            field.len = dblengths[field.dbs.toUpperCase()];
        }
    },

    // Bepaal bij een GET welke velden op te halen
    sqlfields: function _sqlfields(params, model)
    {
        model.aliasprefix = model.aliasprefix || "";
        var selects = [];
        if ("fntablesql" in model)
            model.tablesql = model.fntablesql();

        var withs = {};
        var tables = [ model.tablesql || model.table ];
        var wheres = [];
        var orderbys = [];

        var name_cnt = 0;

        if (model.includes && "tracking" in model.includes && params.include && inArray("lastchange", params.include))
        {
            // lastchange veld altijd  toevoegen.
            // Onafhankelijk van het feit of de include=tracking aanstaat
            var sql = "SELECT MAX(fac_tracking_datum)"
                    + "  FROM fac_tracking ft, "
                    + "       fac_srtnotificatie fs"
                    + " WHERE ft.fac_srtnotificatie_key = fs.fac_srtnotificatie_key"
                    + "   AND fs.fac_srtnotificatie_xmlnode IN (" + safe.quoted_sql_join(model.includes["tracking"].model.xmlnodes) + ") "
                    + "   AND fac_tracking_refkey = " + model.table + "." + model.primary;
            model.fields["lastchange"] = { dbs: "lastchange", sql: "(" + sql + ")", typ: 'datetime', filter: "range" };
        }

        for (var fld in model.fields)
        {
            if (fld.substring(0,1) == "_")
                continue;
            var field = model.fields[fld];
            if (field.hidden)
                continue;

            var dbs = field.dbs;
            if (field.sql)
            {
                if (model.aliasprefix)
                    dbs = model.aliasprefix + fld;
                selects.push(field.sql + " AS " + dbs);
            }
            else if (dbs)
            {
                // veldnamen moeten gelijk zijn bij het ophalen. Zie ook: sql2jsonval
                if (dbs.indexOf(".") < 0)
                {
                    dbs = model.table + "." + dbs;
                    if (field.translate && !params.for_edit)
                        dbs = lcl.xsql(dbs, model.table + "." + model.fields.id.dbs, null, true); // never autolcl
                    if (model.aliasprefix)
                        dbs += " AS " + api2.sqlfield_alias(model, fld);
                    else
                        if (field.translate && !params.for_edit)
                            dbs += " AS " + field.dbs;
                }
                if (!inArray(dbs, selects))
                    selects.push(dbs);
            }
            if (field.val instanceof Function)
                continue;


            if (field.foreign && typeof field.foreign != 'function') // de functions komen later
            {
                if (typeof field.foreign == 'string')
                {
                  var foreign = foreignKeyTable(field.foreign);
                  if (!foreign)
                      MISSING_FOREIGN;
                }
                else
                  foreign = field.foreign;
                var fieldname = (foreign.name||foreign.desc);
                field.foreignsql = "SELECT " + fieldname
                                 + "  FROM " + foreign.tbl + " " + (foreign.alias||"xx")
                                 + " WHERE ";
                if (foreign.where)
                    field.foreignsql += foreign.where + " AND ";
                field.foreignsql += (foreign.alias||"xx") + "." + foreign.key + " = " + (field.sql||(field.dbs.indexOf(".") < 0? model.table + ".":"") + field.dbs);
            }

            if (field.foreignsql)
            {
                name_cnt ++;
                field._foreignname = "foreign_" + name_cnt; // Genereer een niet al te extreem lange naam
                selects.push("(" + field.foreignsql + ") " + " AS " + model.aliasprefix + field._foreignname);
            }
        }
        if (params.include && model.includes)
        {
            // Dubbelen er uit halen, dat geeft lelijke foutmeldingen en kan ontstaan
            // als je include=visitors in de url meegeeft én visitors in de body POST
            var tmp = {};
            for (var i in params.include) {
                tmp[params.include[i]] = 1;
            }
            params.include = [];
            for (var i in tmp) {
                params.include.push(i);
            }
            var inccnt = 0;
            for (var i in params.include)
            {
                if (params.include[i] == "lastchange") // special case
                    continue;
                if (params.include[i] in model.includes)
                {
                    var inc = model.includes[params.include[i]];
                    // Geneste includes verwijderen, we doen hooguit 1 diep
                    if ("model" in inc) // reservablerooms/include/occupations heeft geen model
                        delete inc.model.includes;

                    if (inc.model)
                    {
                        if (inc.single_only && !params.filter.id)
                        {
                            abort_with_warning("Include '{0}' only allowed with single '{1}'".format(params.include[i], model.record_name));
                        }
                        inccnt ++;
                        inc.model.aliasprefix = "I" + inccnt;

                        // Zorg dat de parent_key niet genoemd wordt bij de children. Dat is redundant
                        if (inc.joinfield in inc.model.fields)
                            inc.model.fields[inc.joinfield].hidden = true;

                        // Fabriceer een limited include tabel zodat bij fac_groep niet te veel
                        // personen uit fac_groeprechten getoond worden
                        if (inc.limit)
                        {
                            withs["L_" + inc.model.table] =
                                        "(SELECT " + inc.model.table + ".*,"
                                      + "        ROW_NUMBER ()"
                                      + "             OVER (PARTITION BY " + inc.model.fields[inc.joinfield].dbs
                                      + "                   ORDER BY " + inc.limit + " ASC)"
                                      + "             AS rn"
                                      + "     FROM " + inc.model.table + ")";
                            inc.model.table = "L_" + inc.model.table; // LET OP: We vervangen dus de tabel tijdelijk
                            wheres.push(inc.model.table + ".rn <= 6");
                        }

                        var incquery = api2.sqlfields(params, inc.model);

                        selects = selects.concat (incquery.selects);
                        tables = tables.concat (incquery.tables);
                        wheres = wheres.concat (incquery.wheres);

                        if (inc.model.gparams && inc.model.gparams.GET.tables)
                            tables = tables.concat(inc.model.gparams.GET.tables);

                        if (inc.model.gparams && inc.model.gparams.GET.wheres)
                            wheres = wheres.concat(inc.model.gparams.GET.wheres);

                        // Bij includes tonen we ook verwijderde records indien gewenst.
                        if (inc.model.soft_delete &&
                            ( (params.filter.has_show_deleted && params.filter.show_deleted != "on") || !params.filter.has_show_deleted)
                           )
                            wheres.push(inc.model.soft_delete + " IS NULL");

                        // Bij includes ook de vervallen records tonen indien gewenst.
                        if (inc.model.soft_expire &&
                            ( (params.filter.has_show_expired && params.filter.show_expired != "on") || !params.filter.has_show_expired)
                           )
                            wheres.push("(" + model.soft_expire + " IS NULL OR " + model.soft_expire + " > SYSDATE)");

                        if (inc.joinfunction)
                        {
                            var where = inc.joinfunction(params);
                            if (typeof where == "string")
                                wheres.push (where);
                            else
                            {
                                tables = tables.concat (where.tables);
                                wheres.push (where.where);
                            }
                        }
                        else
                        {
                            if (params.orderby)
                            {
                                for (var i = 0; i < params.orderby.length; i++)
                                {
                                    var field = inc.model.fields[params.orderby[i]];
                                    if (field)
                                        orderbys.push(inc.model.table + "." + field.dbs);
                                }
                            }
                            // In orderbys worden de include primary keys verzameld om te sorteren
                            // We bieden daar geen garantie op maar tijdens testen is deterministisch
                            // gedrag wel fijn.
                            orderbys.push(inc.model.aliasprefix + inc.model.primary);
                            // simpel op joinfield
                            var outer = "(+)";
                            if ("outertoggle" in inc)
                            {
                                if (params.filter.has_scf_outer && params.filter.scf_outer == "on")
                                    outer = "";
                                else
                                    outer = inc.outertoggle.def?"":"(+)";
                            }
                            wheres.push ( model.table + "." + model.primary + "=" + inc.model.table + "." + inc.model.fields[inc.joinfield].dbs + outer);
                        }
                    }
                }
                else
                    __Log("Unknown include '{0}' requested".format(params.include[i]));
            }
        }
        return { withs: withs, selects: selects, tables: tables, wheres: wheres, orderbys: orderbys };
    },

    // TODO the_key *moet* bestaan. Andere filtervelden negeren we
    // Bouw een fields object zoals save2db dat verwacht/ kent
    update_fields: function _update_fields(params, model, jsondata)
    {
        var fields = {};
        for (var fld in model.fields)
        {
            var field = model.fields[fld];
            // De key halen we uit de url, die in de JSON negeren we
            if (fld == "id")
                continue;
            if ("sql" in field)
                continue;
            if (!(fld in jsondata) && !field.fnval)
                continue;
            if (field.readonly)
                continue;

            if (field.fnval)
                var newval = field.fnval(jsondata);
            else // simpel
                var newval = jsondata[fld];

            if (field.LOV && newval && typeof newval == "object" && "id" in newval)
            {   // dereference
                newval = newval.id;
            }

            switch (field.typ)
            {
                case "key": // De foreign keys action { "id": "5", "name": "afhalen" }
                    if (newval && typeof newval == "object" && "id" in newval)
                    {   // dereference
                        newval = newval.id;
                        jsondata[fld] = newval;
                    }
                    break;
                case "date":
                case "datetime":
                    if (newval === "")
                        newval = null;
                    // LET OP: Een (new Date) gemaakt binnen een plugin is vreemd genoeg geen (instanceof Date)
                    //         Waarschijnlijk gebruikt een wsc een ander Date object als (ASP)JScript?
                    if (newval !== null && typeof newval == "object" && !(newval instanceof Date))
                        newval = new Date(newval);
                    if (newval !== null && !(newval instanceof Date))
                    {
                        abort_with_warning("Invalid " + field.typ + " (" + model.record_name + "." + fld + "): " + newval);
                    }
                    break;
                case "float":
                case "currency":
                case "number":
                    if (isNaN(newval))
                        abort_with_warning("Invalid number (" + model.record_name + "." + fld + "): " + newval);
                    break;
            }
            if (field.dbs.indexOf(".") >= 0) // complexe foreign key
                continue;

            // LET OP: newfield en fields zijn in het formaat wat save2db verwacht/kent
            // dat lijkt wel op een standaard model.fields object maar is soms een fractie anders
            // Zo heeft een save2db field geen "label" en is "track" geen boolean maar de label-tekst
            // LET OP2: save2db werkt eigenlijk met een Array voor fields maar wij hebben een
            //          object met "name": { field.. } wat daar wel omgezet wordt.
            var newfield = { dbs: field.dbs,
                             typ: field.typ,
                             track: field.track && field.label?field.label:field.track,
                             foreign: field.foreign,
                             val: newval,
                             len: field.len
                           };
            fields[fld] = newfield;
        }
        return fields;
    },

    // Verwerk de POST en PUT (== insert en update)
    // Merk op dat het resultaat van deze functie redelijk ongedefinieerd is
    process_includes: function(params, model, jsondata, the_key)
    {
        if (!model.includes)
            return [];
        for (var incname in model.includes)
        {
            if (incname in jsondata) // i=="visitors"
            {
                var inc = model.includes[incname];
                if (inc.model && inc.enable_update) // andere includes zijn nog niet bij te werken
                {
                    // Als je bij een PUT/POST een include in de BODY zet geven we hem automagisch terug
                    params.include = params.include || [];
                    params.include.push(incname);

                    var incmodel = inc.model;

                    // Vul existing_includes met bestaande records in de database
                    var sql = "SELECT " + incmodel.primary
                            + "  FROM " + incmodel.table
                            + " WHERE " + incmodel.table + "." + incmodel.fields[inc.joinfield].dbs + "=" + the_key;
                    var existing_includes = {};
                    var oRs = Oracle.Execute(sql);
                    while (!oRs.Eof)
                    {
                        existing_includes[oRs(incmodel.primary).Value] = { found: false }; // vooralsnog niet in json-data gevonden
                        oRs.MoveNext();
                    }
                    oRs.Close();
                    var incdata = jsondata[incname]; // Array zoals via API aangeleverd
                    for (var j=0; j<incdata.length; j++)
                    {
                        var inckey = incdata[j]["id"]; // Die moet er zijn
                        if (!inckey || inckey < 0 || params.isNew)
                        {
                            __Log("Nu ga ik een '{0}' toevoegen".format(incname));
                            incmodel.REST_POST(params, incdata[j], the_key);
                        }
                        else if (inckey in existing_includes)
                        {
                            __Log("Nu ga ik '{0}' {1} updaten".format(incname, inckey));
                            incmodel.REST_PUT(params, incdata[j], inckey);
                            existing_includes[inckey].found = true;
                        }
                    }
                    for (oldi in existing_includes)
                    {
                        if (!existing_includes[oldi].found)
                        {
                            incmodel.REST_DELETE(params, oldi);
                        }
                    }
                }
                else
                {
                    //if (inc.func)
                }
            }
        }
    },
    // Geeft de GET terug van een enkel veld
    sql2jsonval: function _sql2jsonval(oRs, fld, model)
    {
        var field = model.fields[fld];
        var sqlfieldname = (model.aliasprefix ? api2.sqlfield_alias(model, fld) : field.dbs);
        if (field.val instanceof Function)
            var val = field.val(oRs, field, model, sqlfieldname);
        else if (field.dbs.indexOf(".") < 0)
        {
            var val = oRs(sqlfieldname).Value;
        }
        else
            var val = oRs(field.dbs.split(".")[1]).Value;

        if (field.typ == "check" || field.typ == "check0")
        {
            if (field.invert)
                val = (val==1?0:1);
        }

        if (val === null)
            return val;
        if (field.typ == "date")
            val = new Date(val);
        if (field.typ == "datetime")
            val = new Date(val);
        if (field.typ == "time") // In de database ondersteunen we dit niet maar komt voor bij fac_report
            val = new Date(val);
        // Wat te doen met lege waarde
        //  action: null
        //  action: {key: null, name: null}
        //  action: {}
        //  of helemaal weglaten? We hebben nu de 1e optie. Dat is zelfdocumenterend
        // En wat bij een leeg (include) array? Dan kun je ook nog occupations:[] krijgen
        if (field.foreign || field.foreignsql)
        {
            if (field.foreignsql)
            {
                var name = oRs(model.aliasprefix + field._foreignname).Value;
                if (name != null && typeof name == "date" )
                    name = new Date(name);
            }
            else
            {
                var name = field.foreign(val);
            }
            val = { id: val };
            val.name = name;
        }
//        if (field.typ == 'check' && !field.LOV)
//            field.LOV = "0;" + L("lcl_No") + ";1;" + L("lcl_Yes");

        if (field.LOV)
        {
            var spl = api2.splitLOV(field.LOV);
            val = { id: val, name: spl[val] };
        }

        if (field.flexmodule)
        {
            var flexparam = flexProps(field.flexmodule, -1, null, null,
                                            { getFile: val, api2name: null });
            if (flexparam.files.length)
            {
                var file = flexparam.files[0];
                val = { name: file.name,
                        date: file.date,
                        size: file.size,
                        content_url: file.deepurl,
                        digest: file.digest };
            }
        }
        return val;
    },
    filterLOV: function _splitLOV(LOV, filter)
    {
        filter = filter.split(",");
        var spl = api2.splitLOV(LOV);
        var result = [];
        for (var f in filter)
            result.push(filter[f] + ";" + spl[filter[f]]);
        return result.join(";");
    },
    splitLOV: function _splitLOV(LOV)
    {
        var splits = LOV.split(";");
        if (splits % 2)
            abort_with_warning("LOV '{0}' mismatch".format(LOV));
        var result = {};
        for (var i=0; i < splits.length / 2; i++)
            result[splits[i*2]] = splits[i*2 + 1];
        return result;
    },
    splitLOV2sql: function _splitLOV(LOV)
    {
        var spl = api2.splitLOV(LOV);
        var dual = [];
        var cnt = 0;
        for (var l in spl)
        {
            cnt ++;
            dual.push("SELECT " + safe.quoted_sql(l) + ", " + safe.quoted_sql(spl[l]) + ", " + cnt + " FROM DUAL");
        }
        return dual.join(" UNION ALL ") + " ORDER BY 3";
    },
    splitLOV2select: function _splitLOV(LOV, current, params)
    {
        params = params || {};
        var spl = api2.splitLOV(LOV);
        var options = [];
        for (var l in spl)
            options.push("<option value='" + safe.htmlattr(l) + "'" + (l == current?" selected='1'":"") + ">" + safe.html(spl[l]) + "</option>");
        return "<select" + (params.selclass?" class=" + params.selclass:"") + ">" + options.join("") + "</select>";
    },
    // ['xxx','yyy','zzz'] ==> "0;xxx;1;yyy;2;zzz"
    makeLOV: function _makeLOV(arr)
    {
        var LOVarr = [];
        for (var i = 0; i < arr.length; i++)
        {
            LOVarr.push(String(i));
            LOVarr.push(arr[i]);
        }
        return LOVarr.join(";");
    },
    getTimetable: function()
        {
            var timetable = [];
            for (var t = 0; t < 24*4-1; t++)
            {   //  "0;0:00;0.25;0:15;0.5;0:30;0.75;0:45;" enzovoorts
                timetable.push(t / 4);
                timetable.push(String(Math.floor(t / 4)) + ":" + padout(15*(t % 4)));
            }
            return timetable.join(";");
        },
    // Geef alle velden van een record terug
    sql2jsonfields: function _sql2jsonfields(oRs, model)
    {
        var record = {};
        for (var fld in model.fields)
        {
            if (fld.substring(0,1) == "_")
                continue;
            var field = model.fields[fld];
            if (field.hidden)
                continue;

            var val = api2.sql2jsonval(oRs, fld, model);
            //waarom was dit aanwezig?
            //if (field.readonly && !val.id)
            //    continue;
            record[fld] = val;
        }
        if ("post_get" in model)
            model.post_get(record);
        return record;
    },
    sql2json: function _sql2json(params, sql, model)
    {
        var prefuncdata;
        var prefuncdatainitialized = false;
        var prefilterfuncdata;
        if (model.filter && model.filter.prefunc)
            prefilterfuncdata = model.filter.prefunc(params);

        var data = [];
        var oRs = Oracle.Execute(sql, params.errmsg);
        if (params.errmsg && oRs.friendlyMsg)
        {
                var record = {};
                record[model.list.columns[0]] = oRs.friendlyMsg;
                data.push(record);
                return data;
        }

        var lastkey = 0;
        var record = {};
        // Merk op dat onze recordset meer regels kan bevatten dan je zou verwachten
        // omdat de includes er bij zijn gejoind
        var total_count = 0;
        var limit = S("qp_maxrows"); // TODO: maximaliseren op qp_maxrows2?
        if ("limit" in params.filter)
            limit = params.filter.limit;
        if (params.filter.nolimit || limit == -1)
            limit = 99999999; // Excel/CSV alles tonen
        while (!oRs.Eof)
        {
            if (data.length >= limit) // Alleen nog maar tellen
            {
                if (model.primary)
                {
                    var key = oRs(model.primary).Value; // Oppassen met de includes
                    if (key != lastkey)
                        total_count ++;
                    lastkey = key;
                }
                else
                {
                    total_count ++;
                }
                oRs.MoveNext();
                continue;
            }
            if (model.primary)
            {
                var key = oRs(model.primary).Value;
                if (key != lastkey)
                {
                    if (key < lastkey && params.include && params.include.length && !params.include[0] == 'disc_params') // disc_params is toch one-on-one
                    {
                        abort_with_warning("Include '{0}' only allowed when main record is ordered by '{1}'".format(params.include[0], model.primary));
                    }

                    if (lastkey && !isEmptyObject(record)) // Record was er mogelijk uit gefilterd
                    {
                        data.push(record);
                        total_count ++;
                        if (data.length == limit)
                            continue; // alleen nog tellen, 'volgende' record niet meer opbouwen
                    }
                    var record = {};
                }
            }

            // Complexe filtering die we niet voor elkaar kregen met een WHERE-clause
            if (model.filter && model.filter.func)
            {
                if (!model.filter.func(oRs, params, prefilterfuncdata))
                {
                    lastkey = key;
                    oRs.MoveNext();
                    continue;
                }
            }

            var fld;
            for (fld in model.fields)
            {
                if (fld.substring(0,1) == "_")
                    continue;
                var field = model.fields[fld];
                if (field.hidden)
                    continue;

                var val = api2.sql2jsonval(oRs, fld, model)
                //waarom was dit aanwezig?
                //if (field.readonly && !val.id)
                //    continue;
                record[fld] = val;
            }
            if (params.include && model.includes)
            {
                for (var i in params.include) // welke includes worden opgevraagd?
                {
                    var incname = params.include[i];
                    if (incname == "lastchange") // special case
                        continue;
                    if (incname in model.includes) // Ondersteunen we deze include?
                    {
                        if (!(incname in record))
                            record[incname] = [];
                        var incmodel = model.includes[incname].model;
                        if (incmodel)
                        {   // Standaard include via model. Ons 'hoofd' record zal meerdere keren
                            // uit de query komen met telkens een ander 'include' record
                            incmodel.aliasprefix = incmodel.aliasprefix || "";
                            if (oRs(incmodel.aliasprefix + incmodel.primary).value == null) // Geen record door outer join
                                continue;
                            record[incname].push(api2.sql2jsonfields(oRs, incmodel));
                        }
                        else if (model.includes[incname].func) // include via callback functie zoals reservablerooms/occupation
                        {
                            if (model.includes[incname].prefunc && !prefuncdatainitialized)
                            {
                                prefuncdata = model.includes[incname].prefunc(params);
                                prefuncdatainitialized = true;
                            }
                            var incdata = model.includes[incname].func(key, params, oRs, record, prefuncdata);
                            if (incdata !== null)
                                record[incname] = incdata; // de callback geeft de gehele include in een keer
                        }
                    }
                    else
                        __Log("Unknown include '{0}' requested".format(incname));
                }
            }

            lastkey = key;
            if (!model.primary)
            {
                data.push(record);
                record = {};
                total_count ++;
            }
            oRs.MoveNext();
        }
        if (lastkey && data.length < limit)
        {
            total_count ++;
            data.push(record);
        }
        if ("merged_model" in model) // de disc_params truc
        {
            model.merged_model.limit = limit;
            //model.offset = offset;
            model.merged_model.total_count = total_count;
        }
        model.limit = limit;
        //model.offset = offset;
        model.total_count = total_count;
        return data;
    },
    error: function (code, msg)
    {
        abort_with_warning(msg, code)
    },
    find_fieldindex_by_dbsname: function(array, value)
    {
        for(var i = 0; i < array.length; i++)
        {
            if(array[i].hasOwnProperty("dbs") && array[i]["dbs"] === value)
            {
                return i;
            }
        }
        return -1;
    },
    // Pas op: fields is hier alleen de fields die bijgewerkt moeten worden
    field_alter: function(fields, fld, newval, model)
    {
        if (fld in fields)
        {
            fields[fld].val = newval;
        }
        else
        {
            // Als veld niet aanwezig is, dan toevoegen.
            // Het typ halen we uit het api-model.
            var newfield = {};
            newfield.dbs = model[fld].dbs;
            newfield.typ = model[fld].typ;
            if (model[fld].track)
                newfield.fld = model[fld].label||model[fld].track;
            newfield.val = newval;
            __Log(newfield);
            fields[fld] = newfield;
        }
    },
    // Maak van inkomende data een standaard formaat
    cleanup_data: function(model, jsondata)
    {
        // foreigns's leveren we op als issuetype: { id: 123, name: "de standaardmelding") }
        // Zo mag je het met een PUT en POST ook opgeven maar we ondersteunen/prefereren ook rechtstreeks issuetype: 123
        // Onderstaande functie converteert inkomende data naar het simpele formaat
        for (var fld in model.fields)
        {
            if (model.fields[fld].foreign)
            {
                if (fld in jsondata && typeof jsondata[fld] == "object" && jsondata[fld] && "id" in jsondata[fld])
                    jsondata[fld] = jsondata[fld].id;
            }
        }
    },
    get_jdata_refkey: function(jsondataobject)
    {
        var value = null;
        if (jsondataobject)
        {
            if (jsondataobject.id && typeof jsondataobject.id != "object")
                value = jsondataobject.id;
            else
                value = jsondataobject;
        }
        return value;
    },
    hasTime: function (dt)
    {
        return dt.getHours() > 0 ||
               dt.getMinutes() > 0 ||
               dt.getSeconds() > 0
    },
    toDate: function (dt)
    {
        if (dt instanceof Array)
            MULTI_DATE_NOT_SUPPORTED;

        if (dt instanceof Date)
            return dt;
        // LET OP: Een (new Date) gemaakt binnen een plugin is vreemd genoeg geen (instanceof Date)
        //         Waarschijnlijk gebruikt een wsc een ander Date object als (ASP)JScript?
        if (dt !== null && typeof dt == "object")
        {
            dt = new Date(dt);
            if (dt instanceof Date)
                return dt;
        }
        if (typeof dt == 'string')
        {
            if (dt.match(/^[0-9]+$/))
                return new Date(parseInt(dt, 10));
            var dt = myJSON.internal_parsedate(null, dt);
            if (dt && dt instanceof Date)
                return dt;
        }
        NOT_A_DATE;
    },
    generic_REST: function(model, gparams)
    {
        __Log("Creating generic_REST for model " + model.records_name);
        gparams = gparams || {};
        if (typeof model == "function") // Nieuwe stijl is het een function. Even compatible.
            DIT_KLOPT_NIET;

        // autfunction === false kun je het expliciet uitschakelen
        if (model.autfunction !== false && !model.autfunction)
            GENERIC_ONLY_WITH_AUTFUNCTION;

        model.REST_GET = generic_REST_GET(model, gparams);
        model.REST_POST = generic_REST_POST(model, gparams);
        model.REST_PUT = generic_REST_PUT(model, gparams);
        model.REST_DELETE = generic_REST_DELETE(model, gparams);
    },
    merge_disc_params_model: function(deze, template_model)
    {
        template_model.merged_model = deze; // hebben we nodig om total_count te kunnen zetten
        for (var x in template_model)
            deze[x] = template_model[x];

        deze.fields = {}; // Die krijgt een deepclone
        for (var fld in template_model.fields)
            deze.fields[fld] = template_model.fields[fld];
        for (var fld in deze.disc_params.model.fields)
        {
            if (fld != "id")
                deze.fields[fld] = deze.disc_params.model.fields[fld];
        }
    }
}

function generic_REST_GET(model, gparams)
{
    gparams = gparams || {};
    gparams.GET = gparams.GET || {};
    model.gparams = gparams;

    return function _generic_REST_GET(params, jsondata)
        {
            if (model.autfunction !== false)
                user.checkAutorisation(model.autfunction); // Als je zelfs geen readrechten hebt ben je heel fout bezig

            if ("disc_params" in model)
            {
                // We zouden net als bij de PUT etc. een rechtstreekse call op de eigen REST_GET kunnen doen
                // maar dat werkt niet (zo soepeltjes) voor meerdere records
                model.includes = model.includes || {};
                model.includes["disc_params"] = model.disc_params;
                params.include = params.include || [];
                params.include.push("disc_params"); // Altijd
            }

            var query = api2.sqlfields(params, model);

            var wheres = api2.sqlfilter(params, model);
            query.wheres = query.wheres.concat(wheres);

            if (gparams.GET.tables)
                query.tables = query.tables.concat(gparams.GET.tables);

            if (gparams.GET.wheres)
                query.wheres = query.wheres.concat(gparams.GET.wheres);

            if (model.soft_delete && params.filter.show_deleted != "on")
                query.wheres.push(model.soft_delete + " IS NULL");

            if (model.soft_expire && params.filter.show_expired != "on" && !params.filter.id)
                query.wheres.push("(" + model.soft_expire + " IS NULL OR " + model.soft_expire + " > SYSDATE)");

            var sql = "SELECT " + query.selects.join(", ")
                    + "  FROM " + query.tables.join(", ")
                    + (query.wheres.length ? " WHERE " + query.wheres.join(" AND " ) : "");
            if (query.withs)
            {
                for (var w in query.withs)
                    sql = " WITH " + w + " AS " + query.withs[w] + sql;
            }

            var orderbys = ((params.orderby && params.orderby.length > 0) ? [] : gparams.GET.orderbys || []);
            if (params.orderby)
            {
                for (var i = 0; i < params.orderby.length; i++)
                {
                    var field = model.fields[params.orderby[i]];
                    orderbys.push(field._foreignname || field.dbs);
                }
            }

            if (!params.include && params.columns && params.columns.length > 1) // Sorteren volgens 'columns'
            {
                for (var i = 0; i < params.columns.length; i++)
                {
                    if (params.columns[i] != "id")// Sla id-veld over bij sorteren
                    {
                        var field = model.fields[params.columns[i]];
                        orderbys.push(model.fields[params.columns[i]]._foreignname || model.fields[params.columns[i]].dbs);
                    }
                }
            }

            if (!orderbys.length)
                orderbys.push(model.fields.name?model.fields.name.dbs:model.fields.id.dbs);

            sql += " ORDER BY " + orderbys.join(", "); // TODO: Altijd goed met includes?

            // Probeer ook nog de 1e include te sorteren. Fraaier met subframes in scaffolding
            if (params.include && params.include.length && model.includes)
            {
                var inc0 = model.includes[params.include[0]].model;
                if (inc0.fields["sequence"])
                    sql += ", " + inc0.table + "." + inc0.fields["sequence"].dbs;
                if (inc0.fields["name"])
                    sql += ", " + inc0.table + "." + inc0.fields["name"].dbs;
                if (inc0.fields["id"])
                    sql += ", " + inc0.table + "." + inc0.fields["id"].dbs;
            }

            var maxcnt = params.filter.showall==1?S("qp_maxrows2"):S("qp_maxrows");
            if (params.include && params.include.length)
            {
                // geen beperking
                // We proberen te voorkomen dat we onnodig veel rijen ophalen.
                // Als er echter includes zijn krijgen we veel meer records dan je direct zou verwachten en
                // klopt de beperking op basis van ROWNUM geheel niet. Met gepaste tegenzin bij includes
                // dan maar (veel) te veel records ophalen.
            }
            else
            {
                if (!params.filter.nolimit) // Dan niet (Excel, CSV)
                   var sql = "SELECT * FROM (" + sql + ") WHERE ROWNUM <= " + (maxcnt + 1); // Eentje extra om overflow-melding te triggeren in resultsettable
            }

            var json = api2.sql2json (params, sql, model);

            if ("disc_params" in model)
            {
                // include van disc_params data niveau 'hoger' zetten
                for (var i = 0; i < json.length; i ++)
                {
                    for (var fld in json[i].disc_params[0])
                    {
                        if (fld != "id")
                            json[i][fld] = json[i].disc_params[0][fld];
                    }
                    delete json[i].disc_params;
                }
            }
            return json;
        }
}

function generic_REST_POST(model, gparams)
{
    gparams = gparams || {};

    if (!model.fields["id"].seq) // We willen per se een sequence
        return false;

    return function _generic_REST_POST(params, jsondata)
        {
            if (model.autfunction !== false)
                var autparams = user.checkAutorisation(model.autfunction);
            user.auth_required_or_abort(autparams.PRSwritelevel < 9 && autparams.ALGwritelevel < 9);

            if ("disc_params" in model)
            {
                // disc_params velden er uit trekken naar eigen json object
                var module = model.fields.ins_discipline_module || "";
                var jsondata_dp = { };
                for (var fld in jsondata)
                {
                    if (fld in this.disc_params.model.fields && !(fld in model.fields))
                    {
                        jsondata_dp[fld] = jsondata[fld];
                        delete jsondata[fld];
                    }
                }
            }
            for (var fld in model.fields)
            {
                if (!(fld in jsondata) && model.fields[fld].required)
                    jsondata[fld] = model.fields[fld].defaultvalue; // hopen dat die is gezet
            }

            var dbfields = api2.update_fields(params, model, jsondata);

            dbfields["id"] = model.fields["id"]; // Die zal een seq hebben
            var xxxIns = buildInsert(model.table, dbfields, { noValidateToken: true });
            var the_key = xxxIns.sequences[model.fields.id.dbs];
            // TODO: Generieke tracking?
            // TODO: de includes?

            var err = Oracle.Execute(xxxIns.sql, true);
            if (err.friendlyMsg)
                abort_with_warning(err.friendlyMsg);

            if ("disc_params" in model)
            {
                // Nu de one-on-one tabel. De eigen sequence daarvan interesseert ons niet
                jsondata_dp[model.disc_params.joinfield] = the_key;
                var dbfields = api2.update_fields(params, model.disc_params.model, jsondata_dp);
                var xxxIns = buildInsert(model.disc_params.model.table, dbfields, { noValidateToken: true });

                var err = Oracle.Execute(xxxIns.sql, true);
                if (err.friendlyMsg)
                {
                    var sql = "DELETE FROM " + this.table + " WHERE " + this.fields.id.dbs + " = " + the_key;
                    Oracle.Execute(sql);
                    abort_with_warning(err.friendlyMsg);
                }
            }

            return { key: the_key, warning: "" };
        }
}

function generic_REST_PUT(model, gparams)
{
    gparams = gparams || {};

    return function _generic_REST_PUT(params, jsondata, the_key)
        {
            if (model.autfunction !== false)
                var autparams = user.checkAutorisation(model.autfunction);
            user.auth_required_or_abort(autparams.PRSwritelevel < 9 && autparams.ALGwritelevel < 9);

            if ("disc_params" in model)
            {
                // disc_params velden er uit trekken naar eigen json object
                var jsondata_dp = { };
                for (var fld in jsondata)
                {
                    if (fld in this.disc_params.model.fields && !(fld in model.fields))
                    {
                        jsondata_dp[fld] = jsondata[fld];
                        delete jsondata[fld];
                    }
                }
                jsondata_dp[this.disc_params.joinfield] = the_key;
            }

            var dbfields = api2.update_fields(params, model, jsondata);
            var wheres = [model.fields["id"].dbs + "  = " + the_key];
            var xxxUpd = buildTrackingUpdate(model.table, wheres.join(" AND " ), dbfields, { noValidateToken: true });
            // TODO: Generieke tracking?
            // TODO: de includes?

            var err = Oracle.Execute(xxxUpd.sql, true);
            if (err.friendlyMsg)
                abort_with_warning(err.friendlyMsg);

            if ("disc_params" in model)
            {
                // Nu de one-on-one tabel
                var dbfields = api2.update_fields(params, this.disc_params.model, jsondata_dp);
                var wheres = [model.disc_params.model.fields[model.disc_params.joinfield].dbs + "  = " + the_key];
                var xxxUpd = buildTrackingUpdate(model.disc_params.model.table, wheres.join(" AND " ), dbfields, { noValidateToken: true });
                var err = Oracle.Execute(xxxUpd.sql, true);
                if (err.friendlyMsg)
                    abort_with_warning(err.friendlyMsg);
            }

            return { key: the_key, warning: "" };
        }
}

function generic_REST_DELETE(model, gparams)
{
    gparams = gparams || {};

    return function _generic_REST_DELETE(params, the_key)
        {
            if (model.autfunction !== false)
                var autparams = user.checkAutorisation(model.autfunction);
            user.auth_required_or_abort(autparams.PRSwritelevel < 9 && autparams.ALGwritelevel < 9);

            if (model.soft_delete)
            {
                var sql = "UPDATE " + model.table
                        + "   SET " + model.soft_delete + " = SYSDATE"
                        + " WHERE " + model.fields.id.dbs + "  = " + the_key
                        +  "  AND " + model.soft_delete + " IS NULL";
            }
            else
            {
                var sql = "DELETE " + model.table
                        + " WHERE " + model.fields.id.dbs + "  = " + the_key;
            }

            var err = Oracle.Execute(sql, true);
            if (err.friendlyMsg)
                abort_with_warning(err.friendlyMsg);

            return { key: the_key, warning: "" };
        }
}

// Algemene normalizing
function def(hash, fld, val)
{
    if (!(fld in hash))
        hash[fld] = val;
}

function isEmptyObject ( obj )
{
	var name;
	for ( name in obj ) {
		return false;
	}
	return true;
}
%>