<% /*
    $Revision$
    $Id$

    File:           model_orders.inc

    Description:    Opdrachten model
    Parameters:
    Context:

    Notes:          Ongeveer

                    TODO: disc3d conditie voor de geldende autfunction toevoegen
                    TODO: het bijhalen van de prefix is op deze manier veel te duur
*/

%>
<!-- #include file="../Shared/discxalg3d.inc"-->
<!-- #include file="../shared/status.inc" -->
<!-- #include file="../shared/budget.inc" -->
<!-- #include file="../shared/kpl_ksrt_validate.inc" -->
<!-- #include file="../mld/mld.inc" -->
<!-- #include file="model_notes.inc"-->
<!-- #include file="./model_custom_fields.inc"-->
<!-- #include file="./model_tracking.inc"-->
<%

function model_orders(opdr_key, params)
{
    params = params || {};
    this.module =       "MLD";
    this.table =        "mld_opdr";
    this.primary =      "mld_opdr_key";
    this.records_name = "orders";
    this.record_name =  "order";
    this.records_title = L("lcl_orders");
    this.record_title =  L("lcl_opdr_info");

    if (opdr_key > 0) // Dialect zetten voordat alle L("") in fields zijn uitgevoerd
    {
        var sql = "SELECT mld_typeopdr_key"
                + "  FROM mld_opdr"
                + " WHERE mld_opdr_key = " + opdr_key;
        var oRs = Oracle.Execute(sql);
        if (!oRs.Eof) // Voorkom error, we moeten eindigen met 404
            lcl.set_dialect(oRs("mld_typeopdr_key").Value, "MLD_TYPEOPDR_KEY");
        oRs.Close();
    }

    // Dit zijn voorlopige namen!
    this.fields =
            {"id"             : { dbs: "mld_opdr_key",                                 typ: "key",                                                                               filter: "exact" },
             "name"           : { dbs: "mld_opdr_id",    /* moet alias worden */       typ: "varchar",                                                                           filter: "exact",
                                  sql: "COALESCE(mld_opdr_id, ins_srtdiscipline.ins_srtdiscipline_prefix||mld_opdr.mld_melding_key||'/'||mld_opdr_bedrijfopdr_volgnr)" },
             "issue"          : { dbs: "mld_melding_key",
                                  typ: "key",
                                  foreignsql: "SELECT ins_srtdiscipline.ins_srtdiscipline_prefix||mld_opdr.mld_melding_key FROM DUAL",
                                  label: L("lcl_complain"),             filter: "exact" },
             "contact"        : { dbs: "prs_perslid_key",                              typ: "key",      foreign: "prs_perslid",            label: L("lcl_mld_behandelaar"),      track: true, filter: "exact" },
             "supplier"       : { dbs: "mld_uitvoerende_keys",                         typ: "key",      foreign: "mld_uitvoerende",        label: L("lcl_ord_company_uit"),      track: true, filter: "exact" },
             "suppliercontact": { dbs: "prs_contactpersoon_key",                       typ: "key",      foreign: "prs_contactpersoon",     label: L("lcl_contact_pers"),         track: true, filter: "exact" },
             "ordertype"      : { dbs: "mld_typeopdr_key",                             typ: "key",      foreign: "mld_typeopdr",           label: L("lcl_mld_typeopdr"),         track: true },
             "description"    : { dbs: "mld_opdr_omschrijving",                        typ: "varchar",                                     label: L("lcl_mld_opdr_description"), track: true, filter: "like" },
             "remark"         : { dbs: "mld_opdr_opmerking",                           typ: "varchar",                                     label: L("lcl_mld_inf_Opmerking"),    track: true, filter: "like", len: 4000 },
             "account"        : { dbs: "prs_kostenplaats_key",                         typ: "key",      foreign: "prs_kostenplaats",       label: L("lcl_mld_inf_Kostenplaats"), track: true, filter: "exact" },
             "hours"          : { dbs: "mld_opdr_uren",                                typ: "float",                                       label: L("lcl_mld_hours"),            track: true },
             "hourrate"       : { dbs: "mld_opdr_uurloon",                             typ: "float",    "iscurrency": true,                label: L("lcl_hour_rate"),            track: true },
             "materialcost"   : { dbs: "mld_opdr_materiaal",                           typ: "float",    "iscurrency": true,                label: L("lcl_mater_cost"),           track: true },
             "costs"          : { dbs: "mld_opdr_kosten",                              typ: "float",    "iscurrency": true,                label: L("lcl_total_cost"),           track: true },
             "status"         : { dbs: "mld_statusopdr_key",                           typ: "key",      foreign: status.getopdrstatustext, label: L("lcl_status"),               track: true, filter: "exact" },
             "ordernr"        : { dbs: "mld_opdr_ordernr",                             typ: "varchar",                                     label: L("lcl_mld_inf_Ordernr"),      track: true },
             "orderdate"      : { dbs: "mld_opdr_datumbegin",                          typ: "datetime",                                    label: L("lcl_orderdate"),            track: true, filter:"range" },
             "enddate"        : { dbs: "mld_opdr_einddatum",                           typ: "datetime",                                    label: L("lcl_opdr_enddate"),         track: true, filter:"range" },
             "plandate"       : { dbs: "mld_opdr_plandatum",                           typ: "datetime",                                    label: L("lcl_opdr_plandate"),        track: true, filter:"range" },
             "location"       : { dbs: "alg_v_allonroerendgoed.alg_locatie_key",       typ: "key",      foreign: "alg_locatie",            label: L("lcl_location"),             track: true, filter: "exact" },
             "building"       : { dbs: "alg_v_allonroerendgoed.alg_gebouw_key",        typ: "key",      foreign: "alg_gebouw",             label: L("lcl_building"),             track: true, filter: "exact" },
             "floor"          : { dbs: "alg_v_allonroerendgoed.alg_verdieping_key",    typ: "key",      foreign: "alg_verdieping",         label: L("lcl_floor"),                track: true, filter: "exact" },
             "room"           : { dbs: "alg_v_allonroerendgoed.alg_ruimte_key",        typ: "key",      foreign: "alg_ruimte",             label: L("lcl_room"),                 track: true, filter: "exact" },
             "terrain"        : { dbs: "alg_v_allonroerendgoed.alg_terreinsector_key", typ: "key",      foreign: "alg_terreinsector",      label: L("lcl_terra"),                track: true, filter: "exact" },
             "contract"       : { dbs: "cnt_contract_key",                             typ: "key",      foreign: "cnt_contract",           label: L("lcl_contract"),             track: true, filter: "exact" }
            },
//    list: { columns: ["id", "name"], canGroup: true },
    this.includes = {
                "notes": {
                           model: new model_notes("MLD"),
                           joinfield: "mld_opdr_key"
                         },
                "custom_fields" : { model: new model_custom_fields(this, "MLD", { pNiveau: "O", readman: true, readuse: true }),
                                    joinfield: "flexparentkey"
                                  },
                "tracking": {
                                  model: new model_tracking(['opdracht']),
                                  joinfield: "fac_tracking_refkey"
                            }
              };

    this.flexprops = { }; // So scaffolding_save knows to save properties

    this.REST_GET = function _GET(params)
        {
            var scope = params.filter.scope || "fe";
            var autfunction = { fe : "WEB_MLDORD", fo : "WEB_ORDBOF", bo : "WEB_ORDBOF", mi : "WEB_MLDBAC" } [scope];
            params.authparams = user.checkAutorisation(autfunction, null, null, true); // pessimistisch

            // TODO: Add authorization
            var query = api2.sqlfields(params, this );

            if (scope == "fe")
            {
                query.wheres.push("mld_uitvoerende_keys=" + user_key);  // Is dit juiste interpretatie van fe?
            }

            if (!params.filter.id)
            {
                // TODO Kiezen voor een redelijk defaultfilter
            }

            /* we need the prefix for the id, costly! Scheelt 1100ms!! */
            query.tables.push("mld_melding");
            query.wheres.push("mld_opdr.mld_melding_key = mld_melding.mld_melding_key");
            query.tables.push("mld_stdmelding");
            query.wheres.push("mld_melding.mld_stdmelding_key = mld_stdmelding.mld_stdmelding_key");
            query.tables.push("ins_tab_discipline");
            query.wheres.push("mld_stdmelding.mld_ins_discipline_key = ins_tab_discipline.ins_discipline_key");
            query.tables.push("ins_srtdiscipline");
            query.wheres.push("ins_tab_discipline.ins_srtdiscipline_key = ins_srtdiscipline.ins_srtdiscipline_key");

            // Opmerkelijk: we pakken niet de plaatsvelden van de opdracht (zijn nog niet (altijd) gevuld maar van de melding
            query.tables.push("alg_v_allonroerendgoed");
            query.wheres.push("mld_melding.mld_alg_onroerendgoed_keys = alg_v_allonroerendgoed.alg_onroerendgoed_keys(+)");


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

            var sql = "SELECT " + query.selects.join(", ")
                    + "  FROM " + query.tables.join(", ")
                    + " WHERE " + query.wheres.join(" AND " )
                    + " ORDER BY mld_opdr_key"; // Order by nodig voor includes
            if (query.orderbys.length)
                sql += ", " + query.orderbys.join(", ");

            // TODO: disc3d conditie voor de geldende autfunction toevoegen

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

            return json;
        };

    this._pre_analyze_fields = function (params, jsondata) /* analyseer inkomende jsondata voor POST */
    {
        params.filter = params.filter || {};
        // Basis validatie rechten.
        var scope = params.filter.scope || "fe";
        var autfunction = { fe : "WEB_MLDORD", fo : "WEB_ORDBOF", bo : "WEB_ORDBOF", mi : "WEB_MLDBAC" } [scope];
        params.authparams = user.checkAutorisation(autfunction, null, null, true); // pessimistisch
        //
        params.auth = {};     // Zet hier de autorisaties voor verschillende autorisatiefuncties.
        params.mld_opdr = {}; // Zet hier de gegevens uit mld.mld_opdr_info() in.
        params.mld_info = {}; // Zet hier de gegevens uit mld.mld_melding_info() in.
        //
        params.filter.autoorder = (parseInt(params.filter.autoorder)==1 ? 1 : 0)
        params.filter.copyorder_key = (params.filter.copyorder_key && parseInt(params.filter.copyorder_key)!="NaN" ? parseInt(params.filter.copyorder_key) : -1);
        params.filter.volgnr = (params.filter.volgnr && parseInt(params.filter.volgnr) != "NaN" ? parseInt(params.filter.volgnr) : 1);
        params.filter.t_opdr = (params.filter.t_opdr && parseInt(params.filter.t_opdr) != "NaN" ? parseInt(params.filter.t_opdr) : 0);
        params.filter.has_fiat = (params.filter.has_fiat && parseInt(params.filter.has_fiat) != "NaN" ? parseInt(params.filter.has_fiat) == 1 : false);
        params.filter.fiat = (params.filter.fiat && parseInt(params.filter.fiat) != "NaN" ? parseInt(params.filter.fiat) == 1 : false);
        //
        params.mld_opdr.act_key = (params.filter.act_key && parseInt(params.filter.act_key) != "NaN" ? parseInt(params.filter.act_key) : -1);
        params.mld_opdr.StatusTV = false;
        params.mld_opdr.uitvoerende = (jsondata.supplier ? api2.get_jdata_refkey(jsondata.supplier) : -1);
        params.mld_opdr.tot_kosten  = (jsondata.costs && parseFloat(jsondata.costs) != "NaN" ? parseFloat(jsondata.costs) : -1);
    };

    this._analyze_fields = function (fields, params, jsondata) /* analyseer inkomende data, common voor PUT en POST */
    {
        var autoorder = params.isNew && (params.filter.autoorder==1);
        var uitvoerende = params.mld_opdr.uitvoerende;

        if (!params.isNew)
        {   // Bestaande opdracht
            var mld_opdr = mld.mld_opdr_info(jsondata.id);
            // Alleen de gegevens die je nodig hebt voor een bestaande opdracht meenemen uit mld_opdr.
            params.mld_opdr.old_uitvoerende = mld_opdr.uitvoerende_key;
            //
            var mld_info = mld.mld_melding_info(mld_opdr.mld_key);
        }
        else
        {   // Nieuwe opdracht
            var mld_info = mld.mld_melding_info(params.mld_opdr.mld_key);
            if (autoorder)
            {   // dan pakken we per definitie de default. We eisen dat die is ingevuld.
                var sql = "SELECT mld_typeopdr_key"
                        + "  FROM mld_stdmelding"
                        + " WHERE mld_stdmelding_key = " + mld_info.stdm;
                var oRs = Oracle.Execute(sql);
                params.mld_opdr.opdr_type = oRs("mld_typeopdr_key").Value || 0;
                oRs.Close();
            }
            else
            {
                params.mld_opdr.opdr_type = (jsondata.ordertype ? jsondata.ordertype : -1);
            }
            if (params.mld_opdr.opdr_type <= 0)
                abort_with_warning("Unknown ordertype");

            var params_new_opdr = { uitvoerende_key: params.mld_opdr.uitvoerende,
                                    act_key: params.mld_opdr.act_key
                                  };
            var mld_opdr = mld.mld_opdr_info_new(params.mld_opdr.mld_key, params.mld_opdr.opdr_type, params_new_opdr);
            // Alleen de gegevens die je nodig hebt voor een nieuwe opdracht meenemen uit mld_opdr.
            params.mld_opdr.old_uitvoerende = -1;
            params.mld_opdr.disc_params_pgb = mld_opdr.disc_params_pgb;
            params.mld_opdr.dis_key         = mld_opdr.dis_key;
            //
            params.mld_info.einddatum     = mld_info.einddatum;
            params.mld_info.mld_omschr    = mld_info.mld_omschr;
            params.mld_info.bestellimiet  = mld_info.bestellimiet;
            params.mld_info.stdm          = mld_info.stdm;
        }
        params.mld_opdr.mld_key         = mld_opdr.mld_key;
        params.mld_opdr.opdr_type       = mld_opdr.opdr_type;
        params.mld_opdr.StatusTV        = mld_opdr.opdr_status == 6;
        params.mld_opdr.opdr_status     = mld_opdr.opdr_status;
        params.mld_opdr.opdrkosten_verplicht      = mld_opdr.opdrkosten_verplicht;
        params.mld_opdr.typeopdr_kosten_verplicht = mld_opdr.typeopdr_kosten_verplicht;
        //
        params.mld_info.mld_kk       = mld_info.mld_kk;
        params.mld_info.kostenpl_key = mld_info.kostenpl_key;
        params.mld_info.melder_key   = mld_info.melder_key;
        params.mld_info.disc         = mld_info.disc;

        lcl.set_dialect(params.mld_opdr.opdr_type, "mld_typeopdr_key"); // TODO: Dit is nu al wel gebeurt?
        // Bepaal matchtype van opdracht
        var sql = "SELECT mld_typeopdr_matchtype"
                + "     , mld_typeopdr_slamode"
                + "     , mld_typeopdr_isofferte"
                + "     , mld_typeopdr_gvs"
                + "     , mld_typeopdr_kosten"
                + "  FROM mld_typeopdr"
                + " WHERE mld_typeopdr_key = " + params.mld_opdr.opdr_type;
        oRs = Oracle.Execute(sql);
        params.mld_opdr.mld_typeopdr_matchtype = oRs("mld_typeopdr_matchtype").Value;
        params.mld_opdr.mld_typeopdr_slamode   = oRs("mld_typeopdr_slamode").Value;
        params.mld_opdr.mld_typeopdr_isofferte = oRs("mld_typeopdr_isofferte").Value;
        params.mld_opdr.mld_typeopdr_gvs       = oRs("mld_typeopdr_gvs").Value || 0;
        var needKostenplaats = oRs("mld_typeopdr_kosten").Value == 1;
        oRs.Close();

        // Voor automatische opdrachten controleren of uitvoerende bepaald kan worden (niet voor offertes en kopiëren).
        if (params.isNew && uitvoerende < 0 && params.filter.copyorder_key < 0 && params.mld_opdr.mld_typeopdr_isofferte == 0)
        {
            var locatiekey = mld_info.loc_key;
            if (locatiekey < 0 && mld_info.deladdress)
            {   // Als we geen locatiekey hebben maar wel een afleveradres, kunnen we mogelijk ook een locatiekey bepalen.
                var sql = "SELECT alg_locatie_key"
                        + "  FROM mld_adres"
                        + " WHERE mld_adres_key = " + mld_info.deladdress;
                var oRs = Oracle.Execute(sql);
                if (!oRs.eof && oRs("alg_locatie_key").Value)
                    locatiekey = oRs("alg_locatie_key").Value;
                oRs.Close();
            }
            var uitvoerende = mld.dienst_controle(mld_info.stdm, locatiekey, mld_info.bld_key, mld_info.room_key, mld_info.perslid_key_voor);
            if (uitvoerende < 0)
                abort_with_warning(L("lcl_orders_no_contractor"));
            params.mld_opdr.uitvoerende = uitvoerende;
        }

        params.auth.ORDBOF = user.checkAutorisation("WEB_ORDBOF", true);
        params.auth.ORDBO2 = user.checkAutorisation("WEB_ORDBO2", true);
        params.auth.MLDORD = user.checkAutorisation("WEB_MLDORD", true);
        params.auth.ORDFIN = user.checkAutorisation("WEB_ORDFIN", true); // User mag gegevens aanpassen, zelfs als al TV (FSN#1791/FSN#11381) (mldfin)
        params.auth.hasBOread = params.auth.ORDBOF || params.auth.ORDBO2 || params.auth.MLDORD; // Als je write rechten hebt heb je zeker ook read rechten.

        var mld_opdr = params.mld_opdr; // Hierboven voor zowel PUT als POST bepaald.

        // Zijn de totale kosten verplicht en ingevuld? Dit wordt ook gecontroleeed bij de invoer. Ook bij de save controleren.
        // 0 = niet verplicht, 1 = save + 2 = afmelden, 4 = afronden.
        var isRequired = (mld_opdr.typeopdr_kosten_verplicht & 1 || (mld_opdr.typeopdr_kosten_verplicht == 0 && mld_opdr.opdrkosten_verplicht & 1)) ||
                        ((mld_opdr.typeopdr_kosten_verplicht & 2 || (mld_opdr.typeopdr_kosten_verplicht == 0 && mld_opdr.opdrkosten_verplicht & 2)) &&
                         (mld_opdr.opdr_status == 5 || mld_opdr.opdr_status == 8)) ||
                        ((mld_opdr.typeopdr_kosten_verplicht & 4 || (mld_opdr.typeopdr_kosten_verplicht == 0 && mld_opdr.opdrkosten_verplicht & 4)) &&
                          mld_opdr.opdr_status == 6);

        user.auth_required_or_abort((autoorder || params.auth.ORDBOF || params.auth.ORDBO2 || params.auth.MLDORD) && !(isRequired && params.mld_opdr.tot_kosten <= 0));

        // (default) kostenplaats bepaling
        var kostenplaats_key = (jsondata.account ? api2.get_jdata_refkey(jsondata.account) : -1);
        if (kostenplaats_key < 0 && autoorder)
        {   // Kijk of kostenplaats bij de opdracht verplicht is (Kijken bij opdrachttype naar "Kosten meerekenen", mld_typeopdr_kosten == 1)
            if (needKostenplaats)
            {
                if (mld_info.kostenpl_key < 0)
                {   // Kostenplaats was bij de melding niet verplicht en niet ingevuld. Kostenplaats is bij opdracht verplicht
                    // We kunnen de opdracht niet automatisch aanmaken.
                    // Alert geven en terug. Beter dan error in de IIS log.
                    abort_with_warning(L("lcl_orders_no_account"));
                }
                else
                {
                    kostenplaats_key = mld_info.kostenpl_key;
                }
            }
        }
        params.mld_opdr.kostenplaats_key = kostenplaats_key;

        // Combinatievalidatie
        if (kostenplaats_key > 0)
        {
            if (!kpl_ksrt_validate(kostenplaats_key, mld_info.stdm_ksrt_key, mld_info.disc))
                abort_with_warning(L("lcl_mld_kpks_validatie"));
        }

        // Uurloon
        params.mld_opdr.uurloon = (jsondata.hourrate ? jsondata.hourrate : -1);
        if (params.mld_opdr.uurloon < 0)
        {
            var sql = "SELECT b.prs_bedrijf_uurloon"
                    + "  FROM prs_bedrijf b"
                    + " WHERE b.prs_bedrijf_key = " + uitvoerende;
            var oRs = Oracle.Execute(sql);
            if (!oRs.eof) // Had in theorie ook een persoon kunnen zijn?
            {
                params.mld_opdr.uurloon = oRs("prs_bedrijf_uurloon").Value;
            }
            oRs.Close();
        }

        // Startdatum: indien niet meegegeven, neem dan sysdate of (autoorder:) melding datum
        var startdate = (jsondata.orderdate ? jsondata.orderdate : (autoorder ? mld_info.melddatum : new Date));
        var startwerkdag = parseFloat(S("fac_t_startofworkday"));
        var startwerkdag_uur = Math.floor(startwerkdag);
        var startwerkdag_min = (startwerkdag - Math.floor(startwerkdag)) * 60;
        var startdatebegin = new Date(startdate.getFullYear(), startdate.getMonth(), startdate.getDate(), startwerkdag_uur, startwerkdag_min);
        //
        params.mld_opdr.startdate = startdate;
        params.mld_opdr.startdatebegin = startdatebegin;
    };

    this._validate_fields = function (fields, params, jsondata) /* valideer fields, alle constraints die niet door de database worden afgevangen */
    {
    };

    this.REST_PUT = function (params, jsondata, the_key) /* update order */
        {
            var opdr_key = the_key;
            if (opdr_key == -1)
                abort_with_warning("PUT needs record key in url.");
            if (!jsondata.id) jsondata.id = opdr_key;       // huidige key altijd in jsondata
            //
            this._pre_analyze_fields(params, jsondata);
            params.mld_opdr.opdr_key = opdr_key;
            var dbfields = api2.update_fields(params, this, jsondata); // Build updater
            if ("status" in dbfields)
                dbfields["status"].savewhen = false; // Die gaan we handmatig doen

            this._analyze_fields(dbfields, params, jsondata);
            this._validate_fields(dbfields, params, jsondata);

            var upd_params = {};
            upd_params.opdrkeyArray = [];
            upd_params.uitvkeyArray = [params.mld_opdr.uitvoerende];
            upd_params.initopdrkey = opdr_key;
            upd_params.refiat = false;
            upd_params.reapprove = false;
            upd_params.ter_fiattering = false;
            upd_params.approver_key = -1;
            upd_params.requestApproval = false;
            var mldUpd = this._wijzig_opdracht(opdr_key, dbfields, params, jsondata, upd_params);
            this._materials(params);
            this._set_autoorder_properties(params);

            var flextrack = this._save_flexproperties(opdr_key, params, jsondata);

            if (mldUpd.trackarray.length || flextrack.length)
                mld.trackopdrachtupdate(opdr_key, L("lcl_ord_is_ordupd") + "\n" + mldUpd.trackarray.concat(flextrack).join("\n"));

            // Bij update van uitvoerende en als status 5 is, dan uitvoerende tracken (ORDMLI (intern) of ORDMLE (extern)
            // Tekstueel is dit (ook) wel wel vermeld i trackopdrachtupdate maar de ORDMLI of MLE notificatie moet nog
            if (params.mld_opdr.uitvoerende > -1 && params.mld_opdr.old_uitvoerende != params.mld_opdr.uitvoerende && params.mld_opdr.opdr_status == 5)
                mld.trackuitvoerende(opdr_key, params.mld_opdr.uitvoerende);

            // De tracking van de wijziging is geweest. Nu kan de statusverandering voor de hergoedkeuring en/of herfiattering ook uitgevoerd worden.
            var status = -1;
            if ("status" in jsondata)
                status = jsondata["status"];

            if (upd_params.reapprove)
            {   // Zetten van de status van de gewijzigde opdracht op "Ter goedkeuring" en afhandelen van de tracking (status + uitvoerende tracking) en daarmee notificatie.
                status = 10; // Ter goedkeuring.
            }
            else if (upd_params.refiat)
            {   // Zetten van de status van de gewijzigde opdracht op "Ter fiattering" en afhandelen van de tracking (status + uitvoerende tracking) en daarmee notificatie.
                status = 3; // Ter fiattering.
            }

            if (status > 0)
                mld.setopdrachtstatus(opdr_key, status, params.mld_opdr.uitvoerende);

            this._notification(params.mld_opdr.mld_key, opdr_key, params, upd_params);
            return { key: opdr_key };
        };

    this.REST_POST = function (params, jsondata, parent_key) /* new order */
        {
            params.isNew = true;
            var mld_key = parent_key;
            if (mld_key == -1)
                abort_with_warning("POST needs parent key in url.");
            //
            this._pre_analyze_fields(params, jsondata);
            params.mld_opdr.mld_key = mld_key;
            var dbfields = api2.update_fields(params, this, jsondata); // Build updater

            this._analyze_fields(dbfields, params, jsondata);
            this._validate_fields(dbfields, params, jsondata);

            var new_params = {};
            new_params.requestApproval = false;
            new_params.voluntaryApproval = params.filter.has_fiat && params.filter.fiat;   // Vrijwillig fiatteren.
            var mldNew = this._nieuwe_opdracht(mld_key, dbfields, params, jsondata, new_params);
            var opdr_key = mldNew.sequences["mld_opdr_key"];

            this._materials(params);
            this._set_autoorder_properties(params);
            var flextrack = this._save_flexproperties(opdr_key, params, jsondata);

            this._notification(mld_key, opdr_key, params, new_params);
            return { key: opdr_key };
        };

    this.REST_DELETE = function (params, the_key) /* delete order */
        {
            // Opdrachten zijn niet te verwijderen.
        };

    this._nieuwe_opdracht = function (mld_key, dbfields, params, jsondata, new_params)
        {   // nieuwe opdracht
            // Persoonlijk Gebonden Budget (PGB) en/of Afdeling Gebonden Budget (AGB) controle
            // mld_disc_info.disc_params_pgb: 0 = Geen van beiden
            //                                1 = Alleen pgb
            //                                2 = Alleen agb
            //                                3 = Beiden, pgb en agb
            var sysdate = new Date();
            var startdate        = params.mld_opdr.startdate;
            var startdatebegin   = params.mld_opdr.startdatebegin;
            var tot_kosten       = params.mld_opdr.tot_kosten;
            var disc_params_pgb  = params.mld_opdr.disc_params_pgb;
            var disc_key         = params.mld_opdr.dis_key;
            var kostenplaats_key = params.mld_opdr.kostenplaats_key;
            var mld_typeopdr_gvs = params.mld_opdr.mld_typeopdr_gvs;
            var sla_mode         = params.mld_opdr.mld_typeopdr_slamode;
            var uitvoerende      = params.mld_opdr.uitvoerende;
            var act_key          = params.mld_opdr.act_key;
            var uurloon          = params.mld_opdr.uurloon;
            var mld_info         = params.mld_info;

            var autoorder        = params.isNew && (params.filter.autoorder==1);
            var t_opdr           = params.filter.t_opdr;
            var copyorder_key    = params.filter.copyorder_key;


            var voluntaryApproval = new_params.voluntaryApproval;

            var approver_key = -1;        // Die gaan we bepalen. Als het niet anders wordt, wordt er niet gefiatteerd
            var requestApproval = false;  // Fiatteren of Goedkeuren.
            var for_approval = false;


            var kp_fiat = true;
            // Combinatievalidatie
            if (kostenplaats_key > 0)
            {
                // Per kostenplaats kan de fiattering in en uitgeschakeld worden (default is aan).
                var sql = "SELECT prs_kostenplaats_fiat"
                        + "  FROM prs_kostenplaats k"
                        + " WHERE prs_kostenplaats_key = " + kostenplaats_key;
                var oRs = Oracle.Execute(sql);
                kp_fiat = oRs("prs_kostenplaats_fiat").Value == 1;
            }

            if (disc_params_pgb > 0)
            { // Persoonlijk Gebonden Budget (PGB) en Afdeling Gebonden Budget (AGB) controle
              // Fiatteringsproces moet gevolgd worden op de naam van de user
              var exceeds_pgb = mld.exceeds_pgb(tot_kosten, user_key, disc_key)
              var exceeds_agb = mld.exceeds_agb(tot_kosten, kostenplaats_key, disc_key, -1)
              if (exceeds_pgb || exceeds_agb)
              {
                  // Voor melding opdrachten heb je altijd BO rechten nodig.
                  // Een opdracht kan dus nooit van een FE komen. Aan de setting "mld_pgb_fe_fiat" (zoals bes_pgb_fe_fiat) heb je hier dan niets.
                  if (S("mld_pgb_fiattering") == 1)
                      // Algemene fiatterings proces noodzakelijk naast PGB fiatering.
                      for_approval = true;
                  else
                  {   // Fiatteringsproces moet gevolgd worden op de naam van de user.
                      var strState = (exceeds_pgb
                                     ? L("lcl_mld_exceeds_pgb") + safe.curr(mld.get_pgb_exceed_amount(tot_kosten, user_key, disc_key, opdr_key))
                                     : "")
                                   + (exceeds_pgb && exceeds_agb
                                     ? "\n" // Nieuwe regel.
                                     : "")
                                   + (exceeds_agb
                                     ? L("lcl_mld_exceeds_agb") + safe.curr(mld.get_agb_exceed_amount(tot_kosten, kostenplaats_key, disc_key, opdr_key))
                                     : "")
                                   + "\n" + L("lcl_mld_adjust_order");
                      abort_with_warning(strState);
                  }
              }

              // Als het boven mijn profiel is, of ik -als ik budgethouder ben/zou zijn- niet zelf mag goedkeuren
              // Gaan we kijken wie dan wel.
              // Kan ik zelf fiatteren?: true:  totaalbedrag <= can_selfapprove
              //                         false  totaalbedrag > can_selfapprove
              if (voluntaryApproval || // Vrijwillig. Wel even checken of er een fiatteur gevonden kan worden.
                  for_approval ||
                  (S("mld_use_order_approval") &&
                   tot_kosten > 0 &&
                   (tot_kosten > S("can_selfapprove"))) // Ik kan niet zelf fiatteren of kosten overschrijden het profiel
                 )
              {
                // Wie moet op verzoek goedkeuren voor deze kostenplaats, terwijl het bedrag wellicht onbekend is?
                // Eerste fiatteur (pkey) mag niet fiatteren want het zit boven zijn profiel limiet.
                // Alleen de melder/aanmaker van de melding mag niet zelf goedkeuren boven het bedrag can_selfapprove.
                approver_key = prs.getKpVerantwoordelijke(kostenplaats_key, tot_kosten, user_key);
                for_approval = true;
              }
            }
            else
            { // Standaard fiatteringscontrole
              //
              // ========================== Fiatteringscontrole ==========================
              //

              var approver_key = -1;  // Die gaan we bepalen. Als het niet anders wordt, wordt er niet gefiatteerd
              // Welk bedrag eventueel te controleren?
              // Tegen wiens profiellimiet eventueel te controleren? Als de kostenplaats bij de opdracht
              // overruled is dan wordt altijd tegen het profiel van de BO-medewerker getest.
              var mld_opdr = {kp_key: kostenplaats_key,
                              opdr_key: -1,
                              t_cost: tot_kosten,
                              opdr_handfiat: voluntaryApproval};
              var approver = mld.getFirstApprover(mld_info, mld_opdr);
              approver_key = approver.approver_key;
              for_approval = approver.for_approval;
            }

            if (for_approval && kp_fiat)
            {
              if (approver_key == -1)
                abort_with_warning(L("lcl_bes_exceed_limit"));
              else
                requestApproval = true;
              // Er is nu een altijd een eerste approver_key gevonden (met of zonder voldoende fiatteringsrechten. Anders al een abort.
              // approver_key is oke of indien -1 dan is er geen approval meer nodig (is al geabort hierboven anders)
            }

            // Zetten van de status van de melding naar geaccepteerd en afhandelen van de tracking.
            mld.setmeldingstatus(mld_key, 4); // TODO: De trigger MLD_T_MLD_OPDR_B_IU' controleert nog op MLD_STATUS?

            // Deze velden staan niet in het model, maar moeten wel opgeslagen worden.
            var volgsql = "mld.bepaalopdrmeldingvolgnr(" + mld_key +")";
            dbfields["xx1"] = { dbs: "mld_opdr_bedrijfopdr_volgnr", typ: "sql",     val: volgsql };
            dbfields["xx2"] = { dbs: "mld_opdr_handfiat",           typ: "check",   val: (voluntaryApproval? 1 : 0) };
            dbfields["xx3"] = { dbs: "mld_opdr_module",             typ: "varchar", val: "MLD" };
            dbfields["xx4"] = { dbs: "mld_melding_key",             typ: "key",     val: mld_key };
            //
            // Velden die in het model staan, maar een defaultwaarde moeten krijgen.
            uurloon = (uurloon > 0 ? uurloon : 0) // I.v.m. de bon moet het uurloon i.i.g. een waarde hebben zodat niet NaN wordt weergegeven.
            api2.field_alter(dbfields, "hourrate", uurloon, this.fields);
            //
            // Vanuit het model_orders zullen de volgende velden worden opgeslagen (indien waarden via jsondata meegegeven)
            // mld_uitvoerende_keys
            // prs_contactpersoon_key
            // prs_kostenplaats_key
            // mld_opdr_materiaal
            // mld_opdr_kosten
            // mld_opdr_ordernr
            // cnt_contract_key
            // mld_opdr_plandatum
            //
            // Deze velden niet opslaan.
            delete dbfields["status"]; // status wordt later gedaan

            // Startdatum is vandaag: starttijd is huidige tijd.
            // Startdatum is niet vandaag: 1) Uitvoertijd is in uren => starttijd is begin werkdag (bijv. 7:00).
            //                             2) Uitvoertijd is in dagen => starttijd is 0:00.
            var startdate_is_today = (startdate.midnight().getTime() == sysdate.midnight().getTime());

            var bedrijf_uitvoertijd = mld.getbedrijfuitvoertijd(uitvoerende); // vaak nodig
            var melding_uitvoertijd = mld.getmeldinguitvoertijd(mld_key);
            var opdracht_uitvoertijd = (sla_mode == 2 ? melding_uitvoertijd : bedrijf_uivoertijd);

            if (startdate_is_today)
                startdate = sysdate;
            else
            {   // Bepaal of starttijd in uren of in dagen is.
                // Bepaal de uitvoertijd van de uitvoerder. In uren als < 1 anders in dagen.
                // Als uitvoertijd 0 of null is dan in uren (einddatum opdracht wordt gelijk genomen aan einddatum melding).
                if (opdracht_uitvoertijd == null || opdracht_uitvoertijd.eenheid == 'U')
                    var uitvoertijdInUren = false;
                else
                    var uitvoertijdInUren = true;

                startdate = (uitvoertijdInUren? startdatebegin : startdate.midnight());  //.getTime());
            }

            api2.field_alter(dbfields, "orderdate", startdate, this.fields);

            if (autoorder)
            {
                if (opdracht_uitvoertijd)
                    var einddatum = mld.geteinddatumopdracht(mld_key, opdracht_uitvoertijd, startdate, sla_mode);
                else
                    var einddatum = mld_info.einddatum;

                var oms = mld_info.mld_onderwerp || "";
                oms += mld_info.mld_onderwerp && mld_info.mld_omschr? "\n" : "";
                oms += mld_info.mld_omschr || "";
                if (S("mld_ord_flexsummary") == 1)
                    oms += mld.getFlexSummary(mld_key);
                api2.field_alter(dbfields, "enddate", einddatum, this.fields);
                api2.field_alter(dbfields, "ordertype",   t_opdr,    this.fields);

                // Ben ik zelf een geldige behandelaar? Dan invullen, anders leeg laten
                mresult = user.func_enabled2("MLD", { ins_discipline_key: mld_info.disc });
                if (mresult.canWrite("WEB_MLDBOF") || mresult.canWrite("WEB_MLDBO3"))
                    api2.field_alter(dbfields, "contact", user_key, this.fields);

                api2.field_alter(dbfields, "description", oms, this.fields);
                api2.field_alter(dbfields, "hours", 0, this.fields);
                delete dbfields["mld_opdr_opmerking"];
            }
            else
            {
                // waarden die in dbfields staan zijn goed.
            }

            if (act_key > 0)
                dbfields["act"] = { dbs: "fac_activiteit_key", typ: "key",  val: act_key };

            dbfields["id"] = { dbs: "mld_opdr_key", typ: "key", seq: "mld_s_mld_opdr_key" };
            var mldIns = buildInsert("mld_opdr", dbfields, { noValidateToken: true });
            var opdr_key = mldIns.sequences["mld_opdr_key"];
            sql = mldIns.sql;
            var err = Oracle.Execute(sql, true);
            if (err.friendlyMsg)
                abort_with_warning(err.friendlyMsg);

            var status = 5; // (Uitgegeven)
            if (tot_kosten > mld_info.bestellimiet || tot_kosten > mld_typeopdr_gvs)
            {
                status = 10; // Ter goedkeuring.
                requestApproval = true;
            }
            else if (requestApproval)
                status = 3; // Ter fiattering.
            // requestApproval heeft nu zijn definitieve waarde, en approver_key is geldig gedefinieerd.

            // Status en tracking altijd met de functie setopdrachtstatus
            // Zetten van de status van een nieuwe opdracht op nieuw en afhandelen van de tracking (status + uitvoerende tracking) en daarmee notificatie.
            mld.setopdrachtstatus(opdr_key, status, uitvoerende);
            // Refresh meldingstatus zonodig als gevolg van een nieuw uitgegeven opdracht
            mld.updatemeldingstatus(mld_key, 0);

            // Nu de diverse flexfiles van de melding naar de opdracht kopieren
            // voor zover het kenmerksoort overeenstemt
            var first_opdr_key = opdr_key;
            if (copyorder_key > 0) // kopieren bijlagen van de originele opdracht of eerste offerte
            {
              var copy_from = (copyorder_key > 0 ? copyorder_key : first_opdr_key);
              __Log("Copy flexfiles from: " + copy_from);
              var flexsql = "SELECT k_old.mld_kenmerk_key k_old"
                          + ", k_new.mld_kenmerk_key k_new"
                          + "  FROM mld_srtkenmerk t"
                          + "     , mld_kenmerk k_old"
                          + "     , mld_kenmerk k_new"
                          + "     , mld_opdr o_old"
                          + "     , mld_typeopdr s_new"
                          + " WHERE o_old.mld_opdr_key = " + copy_from
                          + "   AND s_new.mld_typeopdr_key = " + t_opdr
                          + "   AND t.mld_srtkenmerk_kenmerktype IN ('F', 'E', 'M')"
                          + "   AND k_old.mld_typeopdr_key = o_old.mld_typeopdr_key"
                          + "   AND k_old.mld_kenmerk_niveau = 'O'"
                          + "   AND k_new.mld_typeopdr_key = s_new.mld_typeopdr_key"
                          + "   AND k_new.mld_kenmerk_niveau = 'O'"
                          + "   AND k_old.mld_kenmerk_verwijder IS NULL"
                          + "   AND k_new.mld_kenmerk_verwijder IS NULL"
                          + "   AND k_old.mld_srtkenmerk_key = t.mld_srtkenmerk_key"
                          + "   AND k_new.mld_srtkenmerk_key = t.mld_srtkenmerk_key"
                          + "   AND t.mld_srtkenmerk_verwijder IS NULL"
                          + "   AND k_old.mld_kenmerk_groep = k_new.mld_kenmerk_groep";
              CopyFlexFolders("MLD/" + subfolderKey("O", copy_from), "MLD/" + subfolderKey("O", opdr_key), flexsql);
            }
            else // kopieren van de bijbehorende melding
            {
              var flexsql = "SELECT k_old.mld_kenmerk_key k_old"
                          + "     , k_new.mld_kenmerk_key k_new"
                          + "  FROM mld_srtkenmerk t"
                          + "     , mld_kenmerk k_old"
                          + "     , mld_kenmerk k_new"
                          + "     , mld_stdmelding s_old"
                          + "     , mld_typeopdr s_new"
                          + "     , mld_discipline d_old"
                          + " WHERE s_old.mld_stdmelding_key = " + mld_info.stdm
                          + "   AND s_new.mld_typeopdr_key = " + t_opdr
                          + "   AND s_old.mld_ins_discipline_key = d_old.ins_discipline_key"
                          + "   AND t.mld_srtkenmerk_kenmerktype IN ('F', 'E', 'M')"
                          + "   AND (   (    k_old.mld_stdmelding_key = s_old.mld_stdmelding_key"
                          + "            AND k_old.mld_kenmerk_niveau = 'S'"
                          + "           )"
                          + "        OR (    k_old.mld_stdmelding_key = s_old.mld_ins_discipline_key"
                          + "            AND k_old.mld_kenmerk_niveau = 'D'"
                          + "           )"
                          + "        OR (    k_old.mld_stdmelding_key = d_old.ins_srtdiscipline_key "
                          + "            AND k_old.mld_kenmerk_niveau = 'T' "
                          + "           ) "
                          + "       )"
                          + "   AND k_new.mld_typeopdr_key = s_new.mld_typeopdr_key"
                          + "   AND k_new.mld_kenmerk_niveau = 'O'"
                          + "   AND k_old.mld_kenmerk_verwijder IS NULL"
                          + "   AND k_new.mld_kenmerk_verwijder IS NULL"
                          + "   AND k_old.mld_srtkenmerk_key = t.mld_srtkenmerk_key"
                          + "   AND k_new.mld_srtkenmerk_key = t.mld_srtkenmerk_key"
                          + "   AND t.mld_srtkenmerk_verwijder IS NULL"
                          + "   AND k_old.mld_kenmerk_groep = k_new.mld_kenmerk_groep";
              CopyFlexFolders("MLD/" + subfolderKey("M", mld_key), "MLD/" + subfolderKey("O", opdr_key), flexsql);
            }
            return mldIns;

        }; // Nieuwe opdracht

    this._wijzig_opdracht = function (opdr_key, dbfields, params, jsondata, upd_params)
        {   // Bestaande opdracht
            // Hardness controle schrijfrechten
            var this_opdr = mld.func_enabled_opdracht(opdr_key); // wat mag ik zoal op deze opdracht
            user.auth_required_or_abort(this_opdr.canChange);

            var sysdate          = new Date();
            var startdate        = params.mld_opdr.startdate;
            var startdatebegin   = params.mld_opdr.startdatebegin;
            var tot_kosten       = params.mld_opdr.tot_kosten;
            var kostenplaats_key = params.mld_opdr.kostenplaats_key;
            var StatusTV         = params.mld_opdr.StatusTV;
            var opdr_status      = params.mld_opdr.opdr_status;
            var mld_typeopdr_gvs = params.mld_opdr.mld_typeopdr_gvs;
            var uitvoerende      = params.mld_opdr.uitvoerende;
            var old_uitvoerende  = params.mld_opdr.old_uitvoerende;
            var mld_info         = params.mld_info;
            var reapprove        = upd_params.reapprove;
            var approver_key     = upd_params.approver_key;
            var refiat           = upd_params.refiat;


            if (this_opdr.iamBedrijfContact &&
                this_opdr.canUrenMatChange &&
                tot_kosten > S("mld_max_kosten_extern"))
            {
                abort_with_warning(L("lcl_opdr_cost_exceed") + S("mld_max_kosten_extern"));
            }
            upd_params.opdrkeyArray[upd_params.opdrkeyArray.length] = opdr_key;

            if (!StatusTV || params.auth.ORDFIN || this_opdr.canFinish || this_opdr.canChange)
            {
                // PF: Mag ik dit een vage conditie vinden? Beter lijkt me de verschillende .canXXX specifiek te matchen?
                // Oorspronkelijke gegevens opdracht ophalen.
                var opdr_info = mld.mld_opdr_info(opdr_key);

                // Hergoedkeuring (Ter goedkeuring) wordt (pas) vereist als het opdrachtbedrag hoger is dan al is goedgekeurd en boven de GVS-grens uitkomt.
                // Herfiattering (Ter fiattering) wordt (pas) vereist indien het bedrag boven het mandaat van de oorspronkelijke (laatste) fiatteur uitkomt.
                // Hergoedkeuring en herfiattering kan alleen als het totaalbedrag van de opdracht is verhoogd en alleen in de statussen 5, 8, 6 en 9 (de uitvoeringsfase en de afhandelingsfase).
                // Hergoedkeuring en herfiattering kan leiden tot een tijdelijke terugkeer naar de voorfase status 3 of 10.
                if ((tot_kosten > opdr_info.t_cost) &&
                    (opdr_status == 5 || opdr_status == 6 || opdr_status == 8 || opdr_status == 9))
                {   // Hergoedkeuring (Ter goedkeuring) wordt (pas) vereist als het opdrachtbedrag hoger is dan al is goedgekeurd en boven de GVS-grens uitkomt.
                    reapprove = (tot_kosten > opdr_info.approved && tot_kosten > mld_typeopdr_gvs);
                    // Herfiattering (Ter fiattering) wordt (pas) vereist indien het bedrag boven het mandaat van de oorspronkelijke (laatste) fiatteur uitkomt.
                    // Bepaal het mandaat van de oorspronkelijke (laatste) fiatteur.
                    // Ook als er de vorige keer geen kosten waren dan moet pas worden geherfiatteerd als (tot_kosten > mandaat),
                    // waarbij de mandaat van de Melder/aanvrager van melding of de user is.
                    var pkey = -1;
                    if (tot_kosten > opdr_info.t_cost && tot_kosten > 0)
                    {   // De totale kosten zijn verhoogd.
                        // Bepaling oorspronkelijke (laatste) fiatteur, als die er was.
                        sql = "SELECT pf.prs_perslid_key"
                            + "  FROM fac_tracking tr"
                            + "     , fac_srtnotificatie str"
                            + "     , prs_v_perslid_fullnames_all pf"
                            + " WHERE tr.fac_srtnotificatie_key = str.fac_srtnotificatie_key"
                            + "   AND tr.prs_perslid_key = pf.prs_perslid_key(+)"
                            + "   AND str.fac_srtnotificatie_code = 'ORDFOK'"
                            + "   AND tr.fac_tracking_refkey = " + opdr_key
                            + "   AND str.fac_srtnotificatie_xmlnode IN ('opdracht')"
                            + " ORDER BY fac_tracking_datum DESC"
                            + "        , fac_tracking_key DESC"
                        var oRs = Oracle.Execute(sql);
                        if (!oRs.eof)
                        {
                          pkey = oRs("prs_perslid_key").Value;
                        }
                        else
                        {   // Dan waren de opdracht kosten onder het mandaat van mijzelf of de BO-medewerker.
                            // Als de kostenplaats bij de opdracht overruled is dan wordt altijd tegen het profiel van de BO-medewerker getest.
                            if (mld_info.mld_kk == 1 && mld_info.kostenpl_key == kostenplaats_key)
                                pkey = mld_info.melder_key;  // Melder/aanvrager van melding
                            else
                                pkey = user_key; //getFParamInt("behandelaar"); // BO medewerker? dat is gewoon de user
                        }

                        if (S("mld_use_order_approval") && (tot_kosten > S("can_selfapprove") || budget.exceeds_profiel(tot_kosten, pkey, mld_info.disc)))
                        {   // Er moet (opnieuw) gefiatteerd worden. Is er wel iemand met voldoende mandaat? Indien er niemand gevonden kan worden mag de wijziging niet doorgaan.
                            // Het mandaat is al te hoog voor de melder/aanvrager van de melding.
                            // Alleen de melder/aanmaker van de melding mag niet zelf goedkeuren boven het bedrag can_selfapprove.
                            approver_key = prs.getKpVerantwoordelijke(kostenplaats_key, tot_kosten, mld_info.melder_key);
                            if (approver_key == -1)
                                abort_with_warning(L("lcl_bes_exceed_limit"));
                            refiat = true;
                        }
                    }
                }
                upd_params.reapprove    = reapprove;
                upd_params.approver_key = approver_key;
                upd_params.refiat       = refiat;
                // Opdracht direct electronisch versturen of door putorders laten oppikken (te verzenden veld zetten) bij
                // wijzigen opdrachten als de uitvoerder aangepast is en
                // in de statussen Ter Goedkeurnig(10), Ter Fiattering(3), Gefiatteerd(4), Uitgegeven(5), Geaccepteerd(8).

                var checkTeVerzenden = old_uitvoerende != uitvoerende &&
                             (opdr_status == 10 || opdr_status == 3 || opdr_status == 4 || opdr_status == 5 || opdr_status == 8);
                upd_params.checkteverzenden = checkTeVerzenden;
                if (checkTeVerzenden)
                {
                    dbfields["mustsend"] = { dbs: "mld_opdr_teverzenden",   typ: "check",   val: "" };
                }

                if (!params.auth.ORDBOF)
                {
                    // verwijder het veld mld_uitvoerende_keys
                    delete dbfields["supplier"];
                }

                if (!this_opdr.canKostChange || S("mld_ord_show_kostenplaats") == 2)
                {   // Bij bestaande melding kostenplaats opslaan als kostenplaats wijzigbaar en zichtbaar was,
                    // Als dit niet is: het veld verwidjeren
                    delete dbfields["account"];
                }

                if (!StatusTV) // Alleen als status geen TV is zijn de kalenders niet readonly. authparamsORDFIN en kostentechnisch afmelden (ORDAFR) hebben geen invloed.
                {
                    var ddfrom = "orderdate" in dbfields?dbfields["orderdate"].val:null;
                    var ddto   = "enddate" in dbfields?dbfields["enddate"].val:null;
                    if (ddfrom && ddto) // (zijn ze soms toch nog readonly?)
                    {
                        var ddsql = "SELECT MIN (mld_opdr_uren_datum) ddmin, MAX (mld_opdr_uren_datum) ddmax"
                                  + "  FROM mld_opdr_uren"
                                  + " WHERE mld_opdr_key = " + opdr_key;
                        var oRs = Oracle.Execute(ddsql);
                        if (oRs("ddmin").Value != null && oRs("ddmax").Value != null)
                        {
                            var ddmin = new Date(oRs("ddmin").Value);
                            var ddmax = new Date(oRs("ddmax").Value);

                            // Vallen de geschreven uren (ddmin...ddmax) BINNEN DE WEEK van de looptijd?
                            // Daarom ddfrom (begindatum) naar begin van de week (maandag) en ddto (einddatum) naar eind van de week (zondag) verschuiven.
                            var ddfrom_day = new Date(ddfrom).getDay();
                            if (ddfrom_day == 0) // Zondag.
                                ddfrom = new Date(ddfrom - 6);
                            else // Maandag(1)...Zaterdag(5).
                                ddfrom.setDate(ddfrom.getDate() - (ddfrom_day - 1));

                            var ddto_day = new Date(ddto).getDay();
                            if (ddto_day > 0) // Maandag(1)...Zaterdag(5).
                                ddto.setDate(ddto.getDate() + (7 - ddto_day));

                            if (ddmin < ddfrom.midnight() || ddmax > ddto.midnight())
                                abort_with_warning(L("lcl_mlduren_booked_from") + toDateString(ddmin) + L("lcl_mlduren_booked_until") + toDateString(ddmax));
                        }
                    }

                    // Startdatum bestaande opdracht
                    sql = "SELECT mld_opdr_datumbegin"
                        + "  FROM mld_opdr"
                        + " WHERE mld_opdr_key = " + opdr_key;
                    oRs = Oracle.Execute(sql);
                    var oldstartdate = new Date(oRs("mld_opdr_datumbegin").Value);

                    // Alleen als datum veranderd is moet de starttijd worden aangepast.
                    if (startdate.midnight().getTime() != oldstartdate.midnight().getTime())
                    {   // Startdatum is aangepast.
                        // Als de startdatum is aangepast in registratiedatum, dan wel weer de registratietijd (registratiedatum) pakken.
                        // In alle andere gevallen begin van de werkdag pakken.
                        sql = "SELECT fac.gettrackingdate('ORDNEW', " + opdr_key + ") registratiedatum FROM DUAL"
                        oRs = Oracle.Execute(sql);
                        var registratiedatum = new Date(oRs("registratiedatum").Value);

                        var startdatum_is_registratiedatum = (startdate.midnight().getTime() == registratiedatum.midnight().getTime());
                        startdate = (startdatum_is_registratiedatum? registratiedatum : startdatebegin);
                    }
                    // else startdatum niet aanpassen.
                    oRs.Close();
                    // Waarde van "mld_opdr_datumbegin" aanpassen.
                    api2.field_alter(dbfields, "orderdate", startdate, this.fields);

                    // Als uitvoertijd in uren is er een tijdsveld aanwezig geweest en dan wordt de tijd automatisch opgeslagen.
                    // : staat al in dbfields.
                }
                else
                {
                    delete dbfields["orderdate"];
                    delete dbfields["enddate"];
                }

                if (reapprove || refiat)
                {   // Zet de refiat waarden. Het hergoedkeurings- en/of herfiatteringstraject wordt opnieuw ingegaan. // Tracking hiervan is niet nodig.
                    if (opdr_info.hours > 0)
                        dbfields["hoursrefiat"] = { dbs: "mld_opdr_uren_refiat",       typ: "float",  "iscurrency": true, val: opdr_info.hours };
                    if (opdr_info.h_rate > 0)
                        dbfields["raterefiat"] = { dbs: "mld_opdr_uurloon_refiat",    typ: "float", "iscurrency": true,  val: opdr_info.h_rate };
                    if (opdr_info.m_cost > 0)
                        dbfields["matrerialfiat"] = { dbs: "mld_opdr_materiaal_refiat",  typ: "float", "iscurrency": true,  val: opdr_info.m_cost };
                    if (opdr_info.t_cost > 0)
                        dbfields["costrefiat"] = { dbs: "mld_opdr_kosten_refiat",     typ: "float", "iscurrency": true,  val: opdr_info.t_cost };

                    // Ook voor status 5 wordt refiatstatus gezet zodat bekend is dat het om een herfiattering gaat. De fiatteringsvoorwaarde is dan iets anders.
                    dbfields["statusrefiat"] = { dbs: "mld_statusopdr_key_refiat",  typ: "number", val: opdr_info.opdr_status };
                    // Goedkeuring opnieuw ingaan kan alleen als het approved bedrag leeg is.
                    dbfields["approvedrefiat"] = { dbs: "mld_opdr_approved_refiat",   typ: "float", "iscurrency": true,  val: opdr_info.approved };
                    dbfields["approved"] = { dbs: "mld_opdr_approved",          typ: "float", "iscurrency": true,  val: null };
                }

                // Met FSN#20132 kan bij bestaande opdrachten via deze weg niet meer het opdrachttype wijzigen
                // Documentfolders hernoemen (RenameFlexFolders) is dus niet meer nodig

                var mldUpd = buildTrackingUpdate("mld_opdr", "mld_opdr_key = " + opdr_key, dbfields, { noValidateToken: true });

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

               // tracking doen we gelijk met de flexkenmerken
                // Opdrachtstatus aanpassen i.v.m. hergoedkeuring en/of herfiattering moet na de tracking, dus ook later.
            }
            else
            {
                mldUpd = { trackarray: [] };
            }
            return mldUpd;
        }; // Bestaande opdracht

    this._set_autoorder_properties = function(params)
        {
            // Bij het automatisch aanmaken van een opdracht bij een melding is alleen de melding_key doorgegeven en dus niet de melding kenmerken.
            // Deze kenmerken zijn dus ook niet opgeslagen.
            // De melding kenmerken die overeenkomen met de opdracht kenmerken ophalen en opslaan voor de nieuwe opdracht.
            var autoorder = params.isNew && (params.filter.autoorder==1);
            if (autoorder)
            {
              var sql_kenmerkwaarde = "COALESCE((SELECT v.mld_kenmerkmelding_waarde"
                                    + "            FROM mld_kenmerkmelding v"
                                    + "               , mld_kenmerk vk"
                                    + "               , mld_srtkenmerk vt"
                                    + "           WHERE v.mld_melding_key = " + params.mld_opdr.mld_key
                                    + "             AND vk.mld_srtkenmerk_key = vt.mld_srtkenmerk_key"
                                    + "             AND vk.mld_srtkenmerk_key = k.mld_srtkenmerk_key"
                                    + "             AND vk.mld_kenmerk_groep = k.mld_kenmerk_groep"
                                    + "             AND v.mld_kenmerk_key = vk.mld_kenmerk_key"
                                    + "             AND v.mld_kenmerkmelding_verwijder IS NULL),"
                                    + "         k.mld_kenmerk_default)";

              sql_insert = "INSERT INTO mld_kenmerkopdr (mld_opdr_key"
                         + "                           , mld_kenmerk_key"
                         + "                           , mld_kenmerkopdr_waarde)"
                         + "                      SELECT " + params.mld_opdr.opdr_key
                         + "                           , k.mld_kenmerk_key kenmerk_key"
                         + "                           , " + sql_kenmerkwaarde + " kenmerk_waarde"
                         + "                        FROM mld_srtkenmerk t"
                         + "                           , mld_kenmerk k"
                         + "                           , mld_typeopdr s"
                         + "                       WHERE s.mld_typeopdr_key = " + params.mld_opdr.opdr_type
                         + "                         AND k.mld_typeopdr_key = s.mld_typeopdr_key"
                         + "                         AND k.mld_kenmerk_niveau = 'O'"
                         + "                         AND k.mld_kenmerk_verwijder IS NULL"
                         + "                         AND k.mld_srtkenmerk_key = t.mld_srtkenmerk_key"
                         + "                         AND t.mld_srtkenmerk_verwijder IS NULL"
                         + "                         AND t.mld_srtkenmerk_kenmerktype <> 'M'"
                         + "                         AND (k.mld_kenmerk_volgnummer <= 100"
                         + "                              OR k.mld_kenmerk_volgnummer >= 900)" // Alleen bij of na afmelden mogen afmeld-kenmerken tussen de 100..900
                         + "                         AND " + sql_kenmerkwaarde + " IS NOT NULL";
              Oracle.Execute(sql_insert);
            }
        };

    this._materials = function(params)
        {
            // Als het matchtype 5 is en er zijn materialen geselecteerd, dan deze materialen opslaan
            var opdr_key = params.mld_opdr.opdr_key;
            if (params.mld_opdr.mld_typeopdr_matchtype == 5)
            {
                var matexiststrArr    = getFParamIntArray("matexiststr", []);
                var matopdrkeystrArr  = getFParamIntArray("matopdrkeystr", []);
                var matkeystrArr      = getFParamIntArray("matkeystr", []);
                var matamountstrArr   = getFParamArray("matamountstr", []);
                var matpricestrArr    = getFParamArray("matpricestr", []);
                var matdescriptstrArr = getFParamArray("matdescriptstr", [], true); // nosplit
                if (!params.isNew)
                {   // Verwijder niet geselecteerde materialen
                    sql = "DELETE FROM mld_opdr_materiaal"
                        + " WHERE fac_usrdata_key IS NOT NULL"
                        + "   AND fac_usrdata_key NOT IN (" + (matkeystrArr.length > 0? matkeystrArr.join(",") : "-1") + ")"
                        + "   AND mld_opdr_key = " + opdr_key;
                    oRs = Oracle.Execute(sql);
                    // Verwijderde vrije materialen
                    sql = "DELETE FROM mld_opdr_materiaal"
                        + " WHERE fac_usrdata_key IS NULL"
                        + "   AND mld_opdr_materiaal_key NOT IN (" + (matopdrkeystrArr.length > 0? matopdrkeystrArr.join(",") : "-1") + ")"
                        + "   AND mld_opdr_key = " + opdr_key;
                    oRs = Oracle.Execute(sql);
                }

                for (var i = 0; i < matexiststrArr.length; i++)
                {
                    sql = "SELECT mld_opdr_materiaal_key"
                        + "  FROM mld_opdr_materiaal"
                        + " WHERE mld_opdr_key = " + opdr_key;
                    sql += (matexiststrArr[i] == 1
                           ? " AND fac_usrdata_key = " + matkeystrArr[i]
                           : " AND mld_opdr_materiaal_key = " + (matopdrkeystrArr[i] ? matopdrkeystrArr[i] : -1)
                           );

                    oRs = Oracle.Execute(sql);
                    if (!oRs.eof)
                    {
                        sql = "UPDATE mld_opdr_materiaal"
                            + "   SET mld_opdr_materiaal_aantal = " + parseFloat(matamountstrArr[i])
                            + "     , mld_opdr_materiaal_prijs = " + parseFloat(matpricestrArr[i])
                            + (matexiststrArr[i] == 1 ? "" : ", mld_opdr_materiaal_omschr = " + safe.quoted_sql(matdescriptstrArr[i]))
                            + " WHERE mld_opdr_materiaal_key = " + oRs("mld_opdr_materiaal_key").Value;
                        var err = Oracle.Execute(sql, true);
                        if (err.friendlyMsg)
                            abort_with_warning(err.friendlyMsg);
                    }
                    else
                    {
                        sql = "INSERT INTO mld_opdr_materiaal"
                            + "     ( mld_opdr_key"
                            + (matexiststrArr[i] == 1 ? " , fac_usrdata_key" : "")
                            + "     , mld_opdr_materiaal_aantal"
                            + "     , mld_opdr_materiaal_prijs"
                            + (matexiststrArr[i] == 1 ? "" : " , mld_opdr_materiaal_omschr")
                            + "     )"
                            + " VALUES ( " + opdr_key
                            + (matexiststrArr[i] == 1 ? ", " + matkeystrArr[i] : "")
                            + "        , " + parseFloat(matamountstrArr[i])
                            + "        , " + parseFloat(matpricestrArr[i])
                            + (matexiststrArr[i] == 1 ? "" : ", " + safe.quoted_sql(matdescriptstrArr[i]))
                            + "        )";
                        var err = Oracle.Execute(sql, true);
                        if (err.friendlyMsg)
                            abort_with_warning(err.friendlyMsg);
                    }
                }
            }
        };  // _materials

    this._save_flexproperties = function(opdr_key, params, jsondata)
        {   // Uses:
            //  params: - isNew
            //
            // readonly had voornamelijk betrekking op de vaste velden.
            // met mldman o.i.d. kan zijn dat alles readonly is behalve bepaalde flexkenmerken
            // Wijzigbare flexkenmerken altijd opslaan
            var currentKenmerkenSQL = " SELECT k.mld_kenmerk_key, mld_kenmerkopdr_waarde"
                                    + "   FROM mld_kenmerkopdr ko"
                                    + ",       mld_kenmerk k"
                                    + "  WHERE k.mld_kenmerk_key = ko.mld_kenmerk_key"
                                    + "    AND mld_kenmerkopdr_verwijder IS NULL"
                                    + "    AND mld_opdr_key = " + opdr_key;

            var allKenmerkenSQL     = "SELECT " + lcl.xsql('t.mld_srtkenmerk_omschrijving', 't.mld_srtkenmerk_key') +" kenmerk_omschrijving"
                                    + ",      m.mld_srtkenmerk_key srtkenmerk_key"
                                    + ",      fac_kenmerkdomein_key"
                                    + "  FROM mld_kenmerk m, "
                                    + "       mld_srtkenmerk t"
                                    + " WHERE m.mld_srtkenmerk_key = t.mld_srtkenmerk_key"
                                    + "   AND m.mld_kenmerk_key = "; /* wordt in saveFlexkenmerken uitgebreid */

            var flextrack =
                saveFlexKenmerken(opdr_key, { kenmerkTable: "mld_kenmerkopdr",
                                              kenmerkParentKey : "mld_opdr_key",
                                              kenmerkWaarde: "mld_kenmerkopdr_waarde",
                                              kenmerkKey: "mld_kenmerk_key",
                                              currentKenmerkenSQL: currentKenmerkenSQL,
                                              allKenmerkenSQL: allKenmerkenSQL,
                                              flexdata: jsondata.custom_fields,
                                              isNew: params.isNew,
                                              flexPath: "MLD/O",
                                              tracking: true,
                                              module: "MLD"
                                            }
                                 );
            return flextrack;
        };  // _save_flexproperties

    this._notification = function(mld_key, opdr_key, params, upd_params)
        {   // Uses:
            //  params:     - isNew
            //              - filter.autoorder
            //              - filter.copyorder_key
            //              - filter.volgnr
            //              - auth.hasBOread
            //              - mld_opdr.act_key
            //              - mld_opdr.mld_typeopdr_isofferte
            //              - mld_opdr.opdr_status
            //              - mld_opdr.opdr_type
            //              - mld_opdr.uitvoerende
            //  upd_params: - approver_key
            //              - checkteverzenden
            //              - initopdrkey
            //              - opdrkeyArray[]   // TODO: is dit nog wel een array? slects 1 opdracht per keer
            //              - requestApproval
            //              - uitvkeyArray[]   // TODO: is dit nog wel een array? slechts 1 uitvoerende/opdracht per keer
            //
            var autoorder = params.isNew && (params.filter.autoorder==1);
            var doneParams = { opdr_key: opdr_key, mld_key: mld_key, autoprint: false, asksend: false, success: true };

            // Opdracht direct electronisch versturen of door putorders laten oppikken (te verzenden veld zetten) bij:
            // 1) nieuwe opdrachten.
            // 2) wijzigen opdrachten als de uitvoerder aangepast is en in de statussen Ter Goedkeurnig(10), Ter Fiattering(3), Gefiatteerd(4), Uitgegeven(5), Geaccepteerd(8).
            // Nooit als er een activiteits key is meegegeven omdat het dan eigenlijk geen opdracht is.
            __Log("checkTeVerzenden = " + upd_params.checkteverzenden);
            if (params.isNew || (!params.isNew && upd_params.checkteverzenden) && params.mld_opdr.act_key <= 0)
            {
                if (upd_params.initopdrkey < 0 && params.filter.copyorder_key < 0 && params.mld_opdr.mld_typeopdr_isofferte == 1)
                {   // Meerdere offerte opdrachten aanmaken
                    for (var u = 0; u < upd_params.uitvkeyArray.length; u++)
                    {
                        // putorders.sendMLDorders doen we hier niet, altijd scheduled via putorders
                        sql = "UPDATE mld_opdr SET mld_opdr_teverzenden = 1 WHERE mld_opdr_key = " + upd_params.opdrkeyArray[u];
                        Oracle.Execute(sql);
                    }
                }
                else
                {   // In geval van een voor-de-uitvoerder-nieuwe
                    var sql = "SELECT prs_bedrijfadres_url"
                            + "  FROM prs_bedrijfadres"
                            + " WHERE prs_bedrijfadres_type = 'O'"
                            + "   AND (mld_typeopdr_key IS NULL OR mld_typeopdr_key = " + params.mld_opdr.opdr_type + ")"
                            + "   AND prs_bedrijf_key = " + params.mld_opdr.uitvoerende;
                    var oRs = Oracle.Execute(sql);
                    // Als (1) externe uitvoerder met (2) prs_bedrijfadres_url type O dan vragen
                    // of de opdracht elektrisch verstuurd moet worden. Zo ja, dan
                    if (!oRs.eof)
                    {
                        if (autoorder || S("mld_opdr_confirm_for_send") != 1) // meldracht
                        {   // Code uit order_mail.asp komen we anders niet.
                            var puo_result = putorders.sendMLDorders(opdr_key);
                            if (!puo_result)
                            {   // mld_opdr_teverzenden is op 1 gezet door sendMLDorders, putorders schedule probeert wel weer
                                doneParams.warning = L("lcl_puo_order_failfirst");
                            }
                        }
                        else if (S("mld_opdr_confirm_for_send") == 1)
                        {   // Gaat straks vragen: "Zal ik electronisch versturen?"
                            // Let op: zelfs gevraagd als nog gefiatteerd moet worden
                            // Putorders negeert hem nog zolang hij ter fiattering staat!
                            doneParams.asksend = true;
                        }
                    }
                    else
                    {   // Als via setting mogelijkheid/workaround enabled om zelfs als er geen mldorder_adres
                        // is gedefinieerd toch een opdracht per e-mail te kunnen versturen, dan vragen naar
                        // welk adres en met welk subject. Eea. natuurlijk alleen als opdracht geen fiattering
                        // vereist of inmiddels gefiatteerd is.
                        // Daarnaast ook geen email versturen bij interne uitvoerders.

                        // Is het een interne uitvoerder?
                        var interne_uitvoerder = false;
                        var sql_i = "SELECT prs_perslid_key"
                                  + "  FROM prs_perslid"
                                  + " WHERE prs_perslid_key = " + params.mld_opdr.uitvoerende;
                        var oRs_i = Oracle.Execute(sql_i);
                        if (!oRs_i.eof)
                            interne_uitvoerder = true;
                        oRs_i.Close();

                        if (S("mld_opdr_ask_mail_if_unknown") && !upd_params.requestApproval && !interne_uitvoerder)
                        {
                            var subjectMail = L("lcl_mld_opdr_description") + " " + mld_key + "/" + params.filter.volgnr;
                            var urlMail = "../shared/queuemail.asp?pcode=ORDMAI&defemail_key=-1&key=" + opdr_key
                                        + "&pshowcompany=1"
                                        + "&subject=" + Server.URLencode(subjectMail)
                                        + "&mailfrom=" + (params.auth.hasBOread? 1 : 0);
                            var url = protectQS.create(urlMail);
                            // Net als autoprint en asksend de parameter queuemail met doneParams mee geven zodat
                            // bij het sluiten ../shared/queuemail.asp wordt aangeroepen in CloseDetail.
                            // Parameter queuemail wordt in de functie closeDetail() opgepakt
                            doneParams.queuemail = url;
                        }
                    }
                    oRs.Close();
                }
            }

            if (upd_params.initopdrkey > 0 || params.filter.copyorder_key > 0 || params.mld_opdr.mld_typeopdr_isofferte == 0)
            {
                var info_txt = "";
                if (upd_params.requestApproval)
                {
                    if (params.mld_opdr.opdr_status == 3) // Ter Fiattering
                        var info_txt = mld.sendNeedApprovalNotification(opdr_key, upd_params.approver_key);
                    else // status == 10
                        var info_txt = L("lcl_mld_goedkeuring"); // we weten niet wie, iedereen met WEB_ORDGOE
                    doneParams.warning = info_txt;
                }
                else // Straks bon tonen voor intern en extern order
                {
                    if ((params.isNew && S("mld_order_autoprint") !=3) || S("mld_order_autoprint") == 1) // nieuwe opdracht of setting
                    {
                        if (params.mld_opdr.act_key <= 0 && params.mld_opdr.uitvoerende > 0)
                        {   // RWSN#15010: Bij het opslaan van een nieuwe opdracht GEEN bon tonen/printen als
                            // de uitvoerende een INTERNE uitvoerende is (S("mld_order_autoprint") == 2).
                            sql = "SELECT mu.intern"
                                + "  FROM mld_v_uitvoerende mu"
                                + " WHERE mu.mld_uitvoerende_key = " +  params.mld_opdr.uitvoerende;
                            var oRs = Oracle.Execute(sql);
                            var intern = oRs("intern").Value == 1;
                            oRs.Close();

                            if ((S("mld_order_autoprint") == 2 && !intern) || (S("mld_order_autoprint") != 2))
                            {   // Als S("mld_order_autoprint") == 2 dan geen bon bij prs_perslid (interne uitvoerende)
                                // parameter autoprint wordt in de functie closeDetail() opgepakt
                                doneParams.autoprint = true;
                            }
                        }
                    }
                }
            }
        };  //_notification

    if (opdr_key > 0)
    {
        params.filter = params.filter || {};
        params.filter.id = opdr_key;
        if (! ("include" in params) )
            params.include = { include: ["custom_fields"]};

        var xxx_array = this.REST_GET(params);
        if (!xxx_array.length)
            shared.record_not_found();
       this.data = xxx_array[0];
    }
}
%>