<% /*
     $Revision$
     $Id$

    File:               save2db.inc
    Description:        functions to save records to database
    Context:            Form u2 action from reserv_Change.asp form
    Note:
*/
 %>
<!-- #include file="../Shared/adovbs.inc"-->
<%

/* TODO: Kunnen/willen we dit generiek herschrijven naar bind-variabelen?
         (of recordset operatie: handig voor tracking oude waarden)
   fields is een array van bij te werken velden
     telkens een tuple met:
        dbs: veldnaam in database
        typ: key,           altijd integer number. Als -1 of "" dan wordt NULL opgeslagen
             date,          Tijd wordt er af gestript
             datetime,      Inclusief tijd
             float,         Accepteert punten en komma's
             number,
             varchar        Quotes worden escaped
             memo           Quotes worden escaped
             check          "on" wordt 1, "" wordt "null". Let op: moet aangemaakt zijn met iface.inc/CHECKBOX
             check0         "on" wordt 1, "" wordt 0. Let op: moet aangemaakt zijn met iface.inc/CHECKBOX
             time           millisecs sinds middernacht
             sql            voorgedefinieerd, wordt ongewijzigd gebruikt
        len: varchar wordt hier op afgekapt
        -- een van de volgende view (verplicht) --
        frm: Request.Form veld. Als niet meegesubmit dan ook niet opgeslagen!
        seq: naam van een sequence
        qrs: (nog niet ondersteund!) vergelijk frm maar dan voor QueryString
        val: kant en klare waarde, zelf berekend
        track: "een tekst" waaronder wijzigingen getrackt moeten worden
               of: true waardoor wel oldjsval wordt bepaald maar niet getrackt (mld_behandelaar_key)
        foreign: bij tracking nodig als typ=="key", een waarde uit onderstaande foreignKeyTable
                of: een functie, voorals we zelf de waarde naar een (andere) tekst willen vertalen
   params: vooralsnog geen
   result: een sqlstatement met alvast het woord WHERE er achter (opdat het het niet vergeten)

 */

// Zorg dat tbl ook eventueel verwijderde records bevat. Anders kan
// de tracking de oude  waarde niet naar tekst omzetten
var __foreignKeyTable; // Indirect om te voorkomen dat bij API's te vroeg lcl.xsql wordt uitgevoerd
function foreignKeyTable(field)
{
    if (!__foreignKeyTable)
    {
        __foreignKeyTable
         = {
                "PRS_PERSLID"            : { tbl: "prs_v_perslid_fullnames_all", key: "prs_perslid_key",           desc: "prs_perslid_naam_full"},
                "PRS_KOSTENPLAATS"       : { tbl: "prs_kostenplaats",            key: "prs_kostenplaats_key",      desc: S("prs_kpn_string"),           alias: "k"},
                "PRS_KOSTENPLAATSGRP"    : { tbl: "prs_kostenplaatsgrp",         key: "prs_kostenplaatsgrp_key",   desc: "prs_kostenplaatsgrp_oms"},
                "PRS_KOSTENSOORT"        : { tbl: "prs_kostensoort",             key: "prs_kostensoort_key",       desc: "prs_kostensoort_oms"},
                "PRS_KOSTENSOORTGRP"     : { tbl: "prs_kostensoortgrp",          key: "prs_kostensoortgrp_key",    desc: "prs_kostensoortgrp_oms"},
                "CNT_TERMIJN"            : { tbl: "cnt_termijn",                 key: "cnt_termijn_key",           desc: lcl.xsql("cnt_termijn_omschrijving", "cnt_termijn_key" )},
                "CNT_CONTRACT"           : { tbl: "cnt_contract",                key: "cnt_contract_key",          desc: "cnt_contract_nummer_intern" },
                "CNT_DISCIPLINE"         : { tbl: "cnt_discipline",              key: "ins_discipline_key",        desc: "ins_discipline_omschrijving" },
                "CTR_DISCIPLINE"         : { tbl: "ctr_discipline",              key: "ins_discipline_key",        desc: "ins_discipline_omschrijving" },
                "PRS_BEDRIJF"            : { tbl: "prs_bedrijf",                 key: "prs_bedrijf_key",           desc: "prs_bedrijf_naam" },
                "PRS_DIENST"             : { tbl: "prs_dienst",                  key: "prs_dienst_key",            desc: lcl.xsql('prs_dienst_omschrijving', 'prs_dienst_key') },
                "PRS_AFDELING"           : { tbl: "prs_v_afdeling",              key: "prs_afdeling_key",          desc: S("prs_dep_string"),           alias: "d"},
                "PRS_CONTACTPERSOON"     : { tbl: "prs_contactpersoon",          key: "prs_contactpersoon_key",    desc: S("prs_contactpers_string"),   alias: "cp"},
                "MLD_TYPEOPDR"           : { tbl: "mld_typeopdr",                key: "mld_typeopdr_key",          desc: lcl.xsql("mld_typeopdr_omschrijving", "mld_typeopdr_key") },
                "MLD_UITVOERENDE"        : { tbl: "mld_v_uitvoerende",           key: "mld_uitvoerende_key",       desc: "naam" },
                "MLD_STDMELDING"         : { tbl: "mld_stdmelding",              key: "mld_stdmelding_key",        desc: lcl.xsql("mld_stdmelding_omschrijving", "mld_stdmelding_key") },
                // Altijd met de prefix ervoor
                "MLD_DISCIPLINE"         : { tbl: "mld_discipline",
                                             key: "ins_discipline_key",
                                             desc: "ins_discipline_omschrijving",
                                             alias: "sdiscipline",
                                             name: "(SELECT (NVL2(ins_srtdiscipline_prefix,ins_srtdiscipline_prefix||'-','')) || ins_discipline_omschrijving"
                                                 + "   FROM ins_srtdiscipline isd"
                                                 + "  WHERE isd.ins_srtdiscipline_key = sdiscipline.ins_srtdiscipline_key) ins_discipline_omschrijving" },
              /* de prefix komt zo niet mee met de desc van opdracht: */
                "MLD_OPDR"               : { tbl: "mld_opdr",                    key: "mld_opdr_key",              desc: "COALESCE(mld_opdr_id, mld_melding_key||'/'||mld_opdr_bedrijfopdr_volgnr)", alias: "o" },
                "ALG_V_PLAATSAANDUIDING" : { tbl: "alg_v_plaatsaanduiding",      key: "alg_onroerendgoed_keys",    desc: "alg_plaatsaanduiding" },
                "MLD_V_AFLEVERADRES"     : { tbl: "mld_adres",                   key: "mld_adres_key",             desc: "mld_adres_naam" },
                "MLD_V_FACTUURADRES"     : { tbl: "mld_adres",                   key: "mld_adres_key",             desc: "mld_adres_naam" },
                "ALG_SRTRUIMTE"          : { tbl: "alg_srtruimte",               key: "alg_srtruimte_key",         desc: lcl.xsql("alg_srtruimte_omschrijving", "alg_srtruimte_key") },
                "ALG_REGIO"              : { tbl: "alg_regio",                   key: "alg_regio_key",             desc: "alg_regio_omschrijving" },
                "ALG_DISTRICT"           : { tbl: "alg_district",                key: "alg_district_key",          desc: "alg_district_omschrijving" },
                "ALG_LOCATIE"            : { tbl: "alg_locatie",                 key: "alg_locatie_key",           desc: "alg_locatie_code",    name: "alg_locatie_omschrijving" },
                "ALG_GEBOUW"             : { tbl: "alg_gebouw",                  key: "alg_gebouw_key",            desc: "alg_gebouw_code",     name: "alg_gebouw_omschrijving" },
                "ALG_VERDIEPING"         : { tbl: "alg_verdieping",              key: "alg_verdieping_key",        desc: "alg_verdieping_code", name: "alg_verdieping_code"  },
                "ALG_RUIMTE"             : { tbl: "alg_ruimte",                  key: "alg_ruimte_key",            desc: "alg_ruimte_nr" },
                "ALG_TERREINSECTOR"      : { tbl: "alg_terreinsector",           key: "alg_terreinsector_key",     desc: "alg_terreinsector_code" },
                "MLD_DIENSTNIVEAU"       : { tbl: "mld_dienstniveau",            key: "mld_dienstniveau_key",      desc: lcl.xsql("mld_dienstniveau_omschr", "mld_dienstniveau_key") },
                "RES_ACTIVITEIT"         : { tbl: "res_activiteit",              key: "res_activiteit_key",        desc: lcl.xsql("res_activiteit_omschrijving", "res_activiteit_key") },
                "RES_OPSTELLING"         : { tbl: "res_opstelling",              key: "res_opstelling_key",        desc: lcl.xsql("res_opstelling_omschrijving", "res_opstelling_key") },
                "RES_RUIMTE"             : { tbl: "res_ruimte",                  key: "res_ruimte_key",            desc: lcl.xsql("res_ruimte_nr", "res_ruimte_key") },
                "RES_DEEL"               : { tbl: "res_deel",                    key: "res_deel_key",              desc: "res_deel_omschrijving" },
                "RES_ARTIKEL"            : { tbl: "res_artikel",                 key: "res_artikel_key",           desc: "res_artikel_omschrijving" },
                "RES_RSV_RUIMTE"         : { tbl: "res_rsv_ruimte",              key: "res_rsv_ruimte_key",        desc: "res_reservering_key || '/' || res_rsv_ruimte_volgnr" },
                "BEZ_ACTIE"              : { tbl: "bez_actie",                   key: "bez_actie_key",             desc: lcl.xsql("bez_actie_omschrijving", "bez_actie_key") },
                "BEZ_AFSPRAAK"           : { tbl: "bez_afspraak",                key: "bez_afspraak_key",          desc: "bez_afspraak_datum" },
                "FIN_BTWTABELWAARDE"     : { tbl: "fin_btwtabelwaarde",          key: "fin_btwtabelwaarde_key",    desc: lcl.xsql("fin_btwtabelwaarde_oms", "fin_btwtabelwaarde_key") },
                "RES_DISCIPLINE"         : { tbl: "res_discipline",              key: "ins_discipline_key",        desc: "ins_discipline_omschrijving" },
                "INS_DEEL"               : { tbl: "ins_deel",                    key: "ins_deel_key",              desc: "ins_deel_omschrijving" },
                "INS_SRTDEEL"            : { tbl: "ins_srtdeel",                 key: "ins_srtdeel_key",           desc: "ins_srtdeel_omschrijving" },
                "INS_CONTROLEMODE"       : { tbl: "ins_controlemode",            key: "ins_controlemode_key",      desc: "ins_controlemode_oms" },
                "BES_BESTELOPDR"         : { tbl: "bes_bestelopdr",              key: "bes_bestelopdr_key",        desc: "bes_bestelopdr_id" },
                "FAC_FUNCTIE"            : { tbl: "fac_functie",                 key: "fac_functie_key",           desc: "lcl.l(fac_functie_omschrijving, 1) || ' (' || fac_functie_code || ')'" },
                "FAC_SRTNOTIFICATIE"     : { tbl: "fac_srtnotificatie",          key: "fac_srtnotificatie_key",    desc: "fac_srtnotificatie_code" },
                "FAC_PROFIEL"            : { tbl: "fac_profiel",                 key: "fac_profiel_key",           desc: "fac_profiel_omschrijving" },
                "RES_SRTACTIVITEIT"      : { tbl: "res_srtactiviteit",           key: "res_srtactiviteit_key",     desc: "res_srtactiviteit_omschrijving" }
            };
    }
    return __foreignKeyTable[field.toUpperCase()];
}

function buildUpdate(tabel, fields, params)
{
    return buildTrackingUpdate(tabel, null, fields, params).sql;
}

function shorttxt(txt, len)
{
    if  (txt.length < len)
        return txt;
    else
        return txt.substr(0, len-4) + "...";
}

// tracktxt.push(buildTrackText(tupel.field.typ, oldjsval, tupel.jsval, oRs(tupel.field.dbs).DefinedSize));
// bouw een mooie tekst die de van-naar wijziging beschrijft
// params: nodiff: geen L("lcl_trackappend") of L("lcl_trackpercent")
//         foreign: info over de te toeken foreign key
function buildTrackText(typ, oldjsval, newjsval, params)
{
    params = params || {};
    var oldtxt = oldjsval;
    var newtxt = newjsval;

    if (typeof params.foreign == "function")
    {
        oldtxt = params.foreign(oldjsval, params.oldjsvals);
        newtxt = params.foreign(newjsval);
    }
    else
    {
        switch (typ)
        {
            case "datetime":
            {
                var oldtxt = toDateTimeString(oldjsval);
                var newtxt = toDateTimeString(newjsval);
                break;
            }
            case "date":
            {
                var oldtxt = toDateString(oldjsval);
                var newtxt = toDateString(newjsval);
                break;
            }
            case "time":
            {
                var oldtxt = toTimeString(oldjsval);
                var newtxt = toTimeString(newjsval);
                break;
            }
            case "key": // hash foreign moet ook gedefinieerd zijn
            {
                if (!params.foreign)
                {
                    abort_with_warning("INTERNAL ERROR:\nForeign key for tracking " + oldjsval + " to "+newjsval+" is not defined");
                }
                else if (!foreignKeyTable(params.foreign))
                {
                    abort_with_warning("INTERNAL ERROR:\nForeign key for tracking " + params.foreign.toUpperCase()+" is not defined in foreignKeyTable");
                }
                else
                {
                    var foreign = foreignKeyTable(params.foreign);
                    var sqlf = "SELECT " + foreign.desc
                             + "  FROM " + foreign.tbl + " " + (foreign.alias||"")
                    if (foreign.where)
                        sqlf += " WHERE " + foreign.where + " AND ";
                    else
                        sqlf += " WHERE ";
                    sqlf += foreign.key + " = ";
                    if (oldjsval)
                    {
                        var oRs = Oracle.Execute(sqlf + oldjsval);
                        oldtxt = oRs(0).Value;
                        oRs.Close()
                    }
                    if (newjsval)
                    {
                        var oRs = Oracle.Execute(sqlf + newjsval);
                        newtxt = oRs(0).Value;
                        oRs.Close();
                    }
                }
                break;
            }
        }
    }
    if (oldjsval === null || oldjsval === "") oldtxt = L("lcl_trackfromempty");
    if (newjsval === null || newjsval === "") newtxt = L("lcl_tracktoempty");
    // oke, en hoe gaan we het verschil presenteren?
    var tracktxt = null;
    switch (typ)
    {
        case "memo":
        case "varchar":
        {
            if (oldjsval == null || oldjsval == "") // leeg naar iets
                var txt = oldtxt + L("lcl_trackto") + shorttxt(newjsval, 30);
            else if (newjsval == null || newjsval == "") // iets naar leeg
                var txt = shorttxt(oldjsval, 30) + L("lcl_trackto") + newtxt;
            else if (params.nodiff || (oldjsval.length<30 && newjsval.length<30)) // korte tekst: gewoon oud-->nieuw
                var txt = shorttxt(oldtxt, 30) + L("lcl_trackto") + shorttxt(newtxt, 30)
            else if (newjsval.indexOf(oldjsval) == 0) // lange tekst aangevuld
                var txt = L("lcl_trackappend") + shorttxt(newjsval.substr(oldjsval.length), 30);
            else //  lange tekst gewijzigd: percentage verschil noemen
            {
                // Helemaal naar Oracle om dit te berekenen is niet echt efficient natuurlijk
                // maar voor met name lange strings fors sneller dan een js-functie
                var sqldiff = "SELECT UTL_MATCH.EDIT_DISTANCE_SIMILARITY("+safe.quoted_sql(String(oldjsval))
                                                           +","+safe.quoted_sql(String(newjsval))+") FROM DUAL";
                try
                {
                    var oRs = Oracle.Execute(sqldiff);
                    var diff = 100 - oRs(0).Value;
                    oRs.Close();
                }
                catch(e)
                {   // 10.2.0.4? Crasht sporadisch bij extreem lange teksten UWVA#19894
                    __DoLog("Bad UTL_MATCH.EDIT_DISTANCE_SIMILARITY call #19894");
                    var diff = "??"; // beter dan niet kunnen opslaan
                }
                var txt = String(diff) + L("lcl_trackpercent");
            }
            tracktxt = txt;
            break;
        }
        default:
            tracktxt = oldtxt + L("lcl_trackto") + newtxt;
    }
    return tracktxt;
}


var known_adotypes =
    {
       key:      ['NUMBER'],
       date:     ['DATE'],
       datetime: ['DATE'],
       number:   ['NUMBER'],
       float:    ['NUMBER'],
       memo:     ['VARCHAR2'],
       varchar:  ['VARCHAR2'],
       check:    ['NUMBER'],
       check0:   ['NUMBER'],
       time:     ['DATE'],
       sql:      []
    };

// automatische debugging tool als logging aan staat
// controleert of field-lijst klopt met echte veldtypes
function checkFieldTypes(tabel, fields)
{
    var i, j;
    var oRs = Oracle.RealConnection.Execute("SELECT column_name || CASE"
                                          + "                      WHEN attr_name IS NOT NULL"
                                          + "                      THEN '.' || attr_name"
                                          + "                      ELSE ''"
                                          + "                      END  column_name"
                                          + "     , COALESCE(attr_type_name, data_type) data_type"
                                          + "     , data_scale"
                                          + "     , attr_name"
                                          + "  FROM user_tab_columns utc"
                                          + "     , user_type_attrs uta"
                                          + " WHERE table_name = " + safe.quoted_sql(tabel.toUpperCase())
                                          + "   AND utc.data_type = uta.type_name(+)");
    if (oRs.eof) // dan kunnen we niets
    {
        oRs.Close();
        return;
    }

    for (i=0; i < fields.length; i++)
    {
        var field = fields[i];
        var val = field.val;
        var dbs = field.dbs;
        var dbfield = null;
        while (!oRs.eof)
        {
            if (oRs("column_name").value == dbs.toUpperCase())
            {
                dbfield = { name: oRs("column_name").value,
                            Type: oRs("data_type").value,
                            NumericScale: oRs("data_scale").value
                          };
            }
            //else
            //__Log(oRs.Fields(j).Name.toUpperCase() + " is het niet?");
            oRs.MoveNext();
        }
        if (!dbfield)
        {
            __Log(tabel + "." + dbs + " niet gevonden in recordset?");
            continue;
        }

        var ok_types = known_adotypes[field.typ];
        var found = false;
        for (j in ok_types)
        {
            if (ok_types[j] == dbfield.Type)
                found = true;
        }
        if (!found && field.typ != "sql")
        {
            __SafeLog("<span style='background-color: #00FFFF;'>" + dbs + " is opgegeven als type '" + field.typ + "' maar is: " + dbfield.Type + "</span>");
        }
        else
        {
            if (field.typ == "number" && dbfield.NumericScale > 0)
            {
                __SafeLog("<span style='background-color: #00FFFF;'>"
                            + dbs + " is gedefinieerd als  'number' maar heeft " + dbfield.NumericScale
                            + " decimalen dus 'float' ligt meer voor de hand</span>");
            }
        }
        oRs.MoveFirst(); // voor de volgende
    }
    oRs.Close();
}

// where is bijvoorbeeld 'MLD_MELDING_KEY=1234'
// params bevat eventueel
//      dialect_key: 134
function buildTrackingUpdate(tabel, where, xfields, params)
{
    params = params || {};

    if (!params.noValidateToken) // Uit API's
        protectRequest.validateToken(); // Alle _edit_ schermen horen IFACE.FORM_END aan het einde te hebben

    if (xfields instanceof Array)
        var fields = xfields;
    else // Uit API2
    {
        var fields = [];
        for (var fld in xfields)
            fields.push(xfields[fld]);
    }

    if (Session("logging")&1)
    {
        checkFieldTypes(tabel, fields)
    }
    var result = _buildFields(fields, params);
    var tupels = result.tupels;
    // we hebben nooit sequences bij updates?
    var sql = "UPDATE "+tabel+" xx SET ";
    var first=true;
    var i;
    var trackfields = [];
    for (i=0; i < tupels.length; i++)
    {
        var tupel = tupels[i];
        if (!first) sql+=", ";
        first = false;
        sql += "xx." + tupel.field.dbs + "=" + tupel.sql;
        if (where && tupel.field.track)
            trackfields.push(tupel.field.dbs);
    }
    var trackarray = [];
    var oldjsvals = [];
    if (trackfields.length)
    {
        var sqlt = "SELECT xx." + trackfields.join(", xx.") + " FROM " + tabel + " xx WHERE " + where;
        var oRs = Oracle.Execute(sqlt);
        for (i in tupels)
        {
            var tupel = tupels[i];
            if (tupel.field.track)
            {
                switch (tupel.field.typ)
                {
                    case "datetime":
                    case "date":
                    case "time":
                        // Waarde kan ook null zijn en moet dat dan ook blijven.
                        var oldjsval = (oRs(tupel.field.dbs).Value!=null? new Date(oRs(tupel.field.dbs).Value) : null);
                        break;
                    case "sql":
                        ERROR_CANNOT_TRACK_TYP_SQL;
                    default:
                        var oldjsval = oRs(tupel.field.dbs).Value;
                }
                var different = false;
                switch (tupel.field.typ)
                {
                    case "datetime":
                    case "date":
                    case "time":
                        // Beide waarden kunnen null zijn.
                        var different = (oldjsval !=null || tupel.jsval != null)
                                        && ((oldjsval == null && tupel.jsval != null)
                                            || (oldjsval != null && tupel.jsval == null)
                                            || (oldjsval.getTime() != tupel.jsval.getTime()))

                        break
                    case "memo":
                    case "varchar":
                        var different = (oldjsval||"") != (tupel.jsval||"");// bij Oracle tekst is null gelijk aan ""
                        break
                    default:
                        var different = oldjsval != tupel.jsval;
                }
                // Waarden converteren naar gebruikersvriendelijke teksten
                if (typeof tupel.field.track == "string" && different)
                {
                    var lbl = tupel.field.track;
//                    if (params.dialect_key)
//                        lbl = lcl.x(tupel.field.track, params.dialect_key);
                    trackarray.push(lbl + ": " + buildTrackText(tupel.field.typ, oldjsval, tupel.jsval, { foreign: tupel.field.foreign, oldjsvals: oldjsvals } ));
                }
                oldjsvals[tupel.field.dbs] = oldjsval;
            }
        }
        oRs.Close();
    }

    sql += " WHERE " + (where||"");

    return { sql: sql, trackarray: trackarray, oldjsvals: oldjsvals };
}

function buildInsert(tabel, xfields, params)
{
    params = params || {};

    if (!params.noValidateToken) // Uit API's
        protectRequest.validateToken(); // Alle _edit_ schermen horen IFACE.FORM_END aan het einde te hebben

    if (xfields instanceof Array)
        var fields = xfields;
    else // Uit API2
    {
        var fields = [];
        for (var fld in xfields)
            fields.push(xfields[fld]);
    }

    var result = _buildFields(fields, params);
    var tupels = result.tupels;
    var first = true;
    var cols = "",vals="";
    var i;
    var obj = [];

    for (i=0; i < tupels.length; i++)
    {
        if (tupels[i].field.obj)
        { // Object typen hier verzamelen en later toevoegen aan cols en vals.
          var objfield = tupels[i].field.dbs.split(".")                 // objfield = kolom_object.object_field
          var obj_identifier = tupels[i].field.obj + "." + objfield[0]; // obj_identifier = object_type.object_field
          if (!obj[obj_identifier])
            obj[obj_identifier] = [];
          obj[obj_identifier].push(objfield[1] + "=>" + tupels[i].sql);
        }
        else
        {
          if (!first) { cols += ", "; vals += ", " };
          first = false;
          cols += tupels[i].field.dbs;
          vals += tupels[i].sql;
        }
    }

    // De verzamelde object type nog toevoegen aan cols en vals:  OBJECT_TYPE(field1 => val1, field2 => val2).
    for (var obj_i in obj)
    {
      if (cols != "") { cols += ", "; vals += ", " };
      var obj_identifier = obj_i.split(".");
      cols += obj_identifier[1];
      vals += obj_identifier[0] + "(" + obj[obj_i] + ")";
    }

    var sql = "INSERT INTO "+tabel+"("+cols+") VALUES ("+vals+")";
    return { sql: sql, sequences: result.sequences};
}

// Bouw het field-array om naar { tupels: tupels, sequences: sequences }
// met tupels { dbs: veldnaam, jsval: js-value, sql: sql-value }
//  en sequences[veldnaam] = seq-value
// alleen velden die in de Form-collection voorkwamen worden
// gebruikt
// params wordt nog niet gebruikt.
function _buildFields(fields, params)
{
    params = params || {};
    var tupels = [];
    var sequences = [];
    var i;
    for (i=0; i < fields.length; i++)
    {
        var field = fields[i];
        var val = field.val;
        if (typeof field.savewhen != "boolean")
        {
            if (typeof field.savewhen == "string")
                field.savewhen = Request.Form(field.savewhen).count > 0;
            else if (field.frm)
            {
                if (field.typ=="check" || field.typ=="check0") // Zie iface.inc/CHECKBOX: we hebben een hidden field has_xxxx toegevoegd
                    field.savewhen = Request.Form("has_" + field.frm).count==1;
                else
                    field.savewhen = Request.Form(field.frm).count > 0;
            }
            else
                field.savewhen = true;
        }

        if (!field.savewhen)
            continue; // helemaal niet opnemen

        if (field.frm)
        {
            if (field.typ=="check" || field.typ=="check0")
            {
                var val = (Request.Form(field.frm).count==1);
            }
            else
            {
                var val = Request.Form(field.frm)(1);
            }
        }
        if (field.seq)
        {
            var sql="select "+field.seq+".nextval from dual";
            var oRs = Oracle.Execute( sql );
            var seq_key = oRs(0).value;
            oRs.Close();
            sequences[field.dbs] = seq_key;
            var val = seq_key;
        }

        var tupel = { field: field }; // 'teruggeven'
        switch (field.typ)
        {
            case "sql":
            {
                tupel.sql = val;
                break;
            }
            case "memo":
            case "varchar":
            {
                if (val == "" || val === null)
                    tupel.sql = " NULL"
                else
                {
                    var val2 = String(val);
                    val2 = val2.replace(/^\s+/,""); // trim spaties aan begin
                    val2 = val2.replace(/\s+$/,""); // trim spaties aan einde
                    tupel.jsval = val2;
                    tupel.sql = safe.quoted_sql(val2, field.len);
                }
                break;
            }
            case "key":
            {   // Pas op: *zeer* sporadisch komt een key==0 voor!
                if (val === "" || val === null || parseInt(val, 10)==-1)
                    tupel.sql = " NULL"
                else
                {
                    tupel.sql = parseInt(val, 10);
                    if (isNaN(tupel.sql))
                        abort_with_warning("Unexpected error: " + field.dbs + " is not a key");
                }
                break;
            }
            case "bool":
            {
                if (val == "" || val === null || val == "0")
                    tupel.sql = " NULL"
                else
                    tupel.sql = " 1"
                break;
            }
            case "number":
            {
                if (val === "" || val === null)
                    tupel.sql = " NULL"
                else
                {   tupel.sql = parseInt(val, 10);
                    if (isNaN(tupel.sql))
                        abort_with_warning("Unexpected error: " + field.dbs + " is not a number");
                }
                break;
            }
            case "float":
            {
                if (val === "" || val === null)
                    tupel.sql = " NULL"
                else
                {
                    if (typeof val == "string")
                        val = val.replace(/,/g,".");
                    tupel.sql = parseFloat(val);
                    if (isNaN(tupel.sql))
                        abort_with_warning("Unexpected error: " + field.dbs + " is not a float");
                }
                break;
            }
            case "check":
            case "check0":
            {
                if (val) // ((val && !field.inverse) || (!val && field.inverse))
                    tupel.sql = " 1"
                else
                    tupel.sql = (field.typ == "check"? " NULL" : 0)
                break;
            }
            case "date":
            case "datetime":
            {
                if (val == "" || val === null)
                    tupel.sql = " NULL"
                else
                {
                    tupel.jsval = (val instanceof Date)?val:new Date(parseInt(val, 10));
                    tupel.sql = tupel.jsval.toSQL(field.typ=="datetime");
                }
                break;
            }
            case "time": // Laat datum-deel ongemoeid!!
            {
                if (val == "" || val === null)
                    tupel.sql = " TRUNC("+field.dbs+")";
                else
                {
                    tupel.jsval = (val instanceof Date)?val:new Date(parseInt(val, 10));
                    var tm = tupel.jsval.getHours()*60+tupel.jsval.getMinutes();
                    tupel.sql = " TRUNC("+field.dbs+") + " + tm + "/24/60";
                }
                break;
            }
            default: ERROR_UNSUPPORTED_FIELDTYP;
        }
        if (typeof tupel.jsval == "undefined")
        {
            if (tupel.sql == " NULL")
                tupel.jsval = null;
            else
                tupel.jsval = tupel.sql; // default;
        }
        tupels.push(tupel);
    }
    return { tupels: tupels, sequences: sequences };
}
%>