<%@ language = "JavaScript" %>
<% /*
     $Revision$
     $Id$

    File:           api_opdrsoap.asp
    Description:    API voor ORD-soap berichten
                    Interpreteert een bericht zodanig dat een opdracht wordt aangemaakt
                    (incl. evt. bovenliggende melding) of aangepast.
                    Dat is: qua omschrijving (voortgang), kosten of status.
                    Als een status wordt meegegeven, dan is ook vereist dat dat een
                    geldige statusovergang is, anders wordt het hele bericht genegeerd.

    Parameters:     Een xml
    Context:
    Notes:
*/
    DOCTYPE_Disable = 1;
    THIS_FILE = "appl/api/api_opdrsoap.asp";
%>
<!-- #include file="../Shared/common.inc" -->
<!-- #include file="../Shared/xml_converter.inc" -->
<!-- #include file="../Shared/save2db.inc" -->
<!-- #include file="../Shared/kenmerk_common.inc" -->
<!-- #include file="../api/api.inc" -->
<!-- #include file="../mld/mld.inc" -->
<%
// We sturen het antwoord in UTF-8.
Session.Codepage = 65001;
Response.Charset = 'utf-8';

var API = new API_func();

var MLDremark = 0; // 0= negeer, 1=append bij autoorder, 2=overwrite bij autoorder, 5=append altijd, 6=overwrite altijd
var ORDremark = 2; //

/* API options voorbeeld:
  {
    MLDremark: 0,
    ORDremark: 2
  }
*/

API.apidata.options = API.apidata.options || {};

if ("MLDremark" in API.apidata.options)
    MLDremark = API.apidata.options["MLDremark"];

if ("ORDremark" in API.apidata.options)
    ORDremark = API.apidata.options["ORDremark"];

//user.checkAutorisation("WEB_MLDUSE"); // Dit is nog ongeacht de melding.

var tsql = "-";     // Voor tijdelijke statement.
var sql = "-";      // De uiteindelijke insert/update-sql.
var xmlReq = Server.CreateObject("MSXML2.DOMDocument.6.0");

//if (API.apidata.loglevel) __Log2File(Request, API.APIname + "_IN1");

var xmlReq = styledRequestXML(API);

var xmlResp = new ActiveXObject("MSXML2.DOMDocument.6.0");
xmlResp.appendChild(xmlResp.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\""))

// The response.
var FCLTElement = xmlResp.createElement("facilitor");
// The response header.
var now = new Date();
var nowtxt = now.getFullYear()+'-'+padout(now.getMonth()+1) +'-'+padout(now.getDate()) +' '
                       +padout(now.getHours()) +':'+padout(now.getMinutes())+':'+padout(now.getSeconds());
var elmHeader = xmlResp.createElement("header");
var headerinfo = {
//             file: String(Request.ServerVariables("SCRIPT_NAME")).toLowerCase(),
             datum: nowtxt,
             naam: user.naam(),
             custId: customerId,
             language: user.lang()
           }
for (param in headerinfo)
{
  var FCLTdata = xmlResp.createElement(param);
  FCLTdata.appendChild(xmlResp.createTextNode(headerinfo[param]));
  elmHeader.appendChild(FCLTdata);
}
FCLTElement.appendChild(elmHeader);

// Hier zou het echte werk moeten gebeuren nav. xmlReq
// en FCLTElement moeten we uitbreiden met antwoorden.
var detected ="?";

// Dit is de opdrachtenloop (mochten er ooit nog anderen gaan volgen).
var opdrachten = xmlReq.getElementsByTagName("opdracht");
var wasMLDremark = MLDremark;
var wasORDremark = ORDremark;
for (var i = 0; i < opdrachten.length; i++)
{
  MLDremark = wasMLDremark; // Resetten voor elke opdracht. Tweede opdracht hoeft namelijk geen autoorder te zijn
  ORDremark = wasORDremark;

  var resultcode = -1;   /* Zolang deze -1 is, is het nog goed. Uiteindelijk eindigt het met 0 (als goed). */
  var resulttekst = "";

  // Lees per node de (optionele) tags en waarden.
  var opdrid          = opdrachten[i].getAttribute("key"); // UPD
  var opdraction      = opdrachten[i].getAttribute("type").toLowerCase(); // NEW/UPD

  var externnr        = XMLval(opdrachten[i], "externnr"); // NEW
  var externopdrnr    = XMLval(opdrachten[i], "externopdrnr");
  var plandatum       = XMLval(opdrachten[i], "plandatum", true);
  if (S("mld_use_plandate2") & 2) // Otherwise ignore 'plandatum2'
    var plandatum2    = XMLval(opdrachten[i], "plandatum2", true);
  var halt            = XMLval(opdrachten[i], "halt") == 1;   // Onderbreken.
  var resume          = XMLval(opdrachten[i], "resume") == 1; // Hervatten.
  var voor_key        = parseInt(XMLval(opdrachten[i], "voor"), 10); // NEW
  var voor_email      = XMLval(opdrachten[i], "voor_email"); // NEW
  var stdm_key        = parseInt(XMLval(opdrachten[i], "stdmelding_key"), 10); // NEW
  var topdr_key       = parseInt(XMLval(opdrachten[i], "typeopdr_key"), 10); // NEW
  var uitv_key        = parseInt(XMLval(opdrachten[i], "bedrijf_key"), 10); // NEW
  var opdrachttekst   = XMLval(opdrachten[i], "opdrachttekst"); // NEW
  var meldingtekst    = XMLval(opdrachten[i], "meldingtekst"); // NEW
  var onderwerp       = XMLval(opdrachten[i], "onderwerp"); // NEW
  var opdr_kpn        = XMLval(opdrachten[i], "kostenplaats");
  var flag            = parseInt(XMLval(opdrachten[i], "flag"), 10);

  var txt_mut_datum   = XMLval(opdrachten[i], "mut_datum"); // UPD
  var opdrstatus      = XMLval(opdrachten[i], "status"); // UPD
  var opdropmerking   = XMLval(opdrachten[i], "opmerking"); // UPD
  var opdrnote        = XMLval(opdrachten[i], "note"); // UPD
  var opdrnoteflag    = parseInt(XMLval(opdrachten[i], "noteflag")); // UPD
  var note_key = -1; // vooralnog

  // Welke tracking gaan we registreren?
  var status_tracking_key = -1; // prio1
  var datum_tracking_key = -1;  // prio2
  var update_tracking_key = -1; // prio3

  var opdrgereed      = XMLval(opdrachten[i], "datumgereed"); // NEW/UPD
  var opdrkosten      = XMLval(opdrachten[i], "kosten"); // NEW/UPD
  var opdruren        = XMLval(opdrachten[i], "uren"); // NEW/UPD
  var opdrmateriaal   = XMLval(opdrachten[i], "materiaal"); // NEW/UPD
  var opdruurloon     = XMLval(opdrachten[i], "uurloon"); // NEW/UPD

  var opdrkenmerken   = (opdrachten[i].getElementsByTagName("kenmerk")); // NEW/UPD
  var opdrregels      = (opdrachten[i].getElementsByTagName("mld_opdr_materiaal")); // NEW/UPD/DEL Opdrachtregels.

  var kpn_key         = -1;
  var mld_key         = -1;   // Maken we aan of bepalen we uit de melding/volgnr.
  var opdrvolgnr      = -1;   // Maken we aan of bepalen we uit de melding/volgnr.
  var opdr_key        = -1;   // Maken we aan of bepalen we uit de melding/volgnr.
  var oldstatus       = -1;
  var mld_fields      = [];   // Bij te werken velden.
  var opdr_fields     = [];   // Bij te werken velden.

  if (opdrid == null)
  { // Voorkom problemen als key-attribuut ontbreekt/leeg!
    opdrid = '';
  }
  if (externnr == null)
  { // Voorkom problemen als externnr-node ontbreekt/leeg!
    externnr = '';
  }
  if (externopdrnr == null)
  { // Voorkom problemen als externopdrnr-node ontbreekt/leeg!
    externopdrnr = '';
  }
  if (voor_email == null)
  { // Voorkom problemen als het emailadres ontbreekt/leeg!
    voor_email = '';
  }

  // Zoek de voorkey op aan de hand van het emailadres
  tsql = "SELECT prs_perslid_key "
       + "  FROM prs_v_aanwezigperslid "
       + " WHERE UPPER(prs_perslid_email) = UPPER(" + safe.quoted_sql(voor_email) + ")";
  var oRs = Oracle.Execute(tsql);
  if (!oRs.eof)
  {
     voor_key = oRs("prs_perslid_key").Value;
  }
  oRs.Close();

  // Kijk of er een opdracht bestaat met dit externe opdracht nummer.
  // Ben je bang dat het nummer vaker voorkomt dan kun je deze prefixen in de xsl
  tsql = "SELECT MIN(mld_opdr_key) mld_opdr_key, COUNT(*) aantal"
         + "  FROM mld_opdr"
         + " WHERE mld_opdr_id ="+ safe.quoted_sql(externopdrnr);
  var oRs = Oracle.Execute(tsql);
  if (oRs("aantal").Value == 1)
  {
     tsql = "SELECT mld_melding_key || '/' || mld_opdr_bedrijfopdr_volgnr mld_opdr_nr"
         + "  FROM mld_opdr"
         + " WHERE mld_opdr_key ="+ oRs("mld_opdr_key").Value;
     var oRs = Oracle.Execute(tsql);
     opdrid = oRs("mld_opdr_nr").Value;
     // Als het externe opdracht nummer al bestaat weten we zeker dat het om een update gaat.
     opdraction = "update";
  }
  oRs.Close();

  if (opdraction == "insert")
  {
    // Let op: mld_melding_externnr is niet gegarandeerd uniek: meerdere externe systemen
    //         kunnen dezelfde (externe) nummering gebruiken
    // TODO: Bepalen meldingen  beperken tot alleen die meldingen die zijn aangemaakt door betreffende API-user!
    //       Of beter: WHERE ORDBOF schrijfrechten op de melding
    // Wel: verderop controleren we al wel of je canAddOpdr hebt
    tsql = "SELECT MIN(mld_melding_key) mld_melding_key, COUNT(*) aantal"
         + "  FROM mld_melding"
         + " WHERE mld_melding_externnr="+ safe.quoted_sql(externnr);
    var oRs = Oracle.Execute(tsql);
    switch (oRs("aantal").Value)
    {
      case 0: // Automatisch melding aanmaken.
              // We ondersteunen hier alleen extreem simpele meldingen (alles default).
              // Wil je meer? Dan maak je maar zelf een melding aan via de MLDAPI.
          if (stdm_key && stdm_key > 0)
          {
            tsql = "SELECT sm.mld_stdmelding_t_uitvoertijd.tijdsduur tijdsduur"
                + "      , sm.mld_stdmelding_t_uitvoertijd.eenheid eenheid"
                + "   FROM mld_stdmelding sm"
                + "  WHERE sm.mld_stdmelding_key = " + stdm_key
                + "    AND sm.mld_stdmelding_verwijder IS NULL";
            oRs = Oracle.Execute(tsql);
            if (oRs.eof)
            {
              resultcode = 3;
              resulttekst = "Undefined stdmelding " + stdm_key;
            }

            if (isNaN(voor_key))
            {
              resultcode = 3;
              resulttekst = "Undefined 'voor': " + voor_email;
            }

            // Hier heb ik een geldige stdm_key.
            if (resultcode == -1) // Nog steeds geen fouten.
            { // Bepaal kostenplaats via voor-user
              // Als die voor exact één kostenplaats is gemandateerd dan gebruiken
              // we die kostenplaats. Anders laten we de kostenplaats gewoon leeg.
              sql = "SELECT pk.prs_kostenplaats_key, prs_kostenplaats_nr"
                  + "  FROM prs_perslidkostenplaats ppk,"
                  + "       prs_kostenplaats pk"
                  + " WHERE pk.prs_kostenplaats_key = ppk.prs_kostenplaats_key"
                  + "   AND ppk.prs_perslid_key = " + voor_key
                  + "   AND prs_kostenplaats_verwijder IS NULL"
                  + "   AND prs_perslidkostenplaats_boeken = 1";
              var oRs = Oracle.Execute(sql);
              if (!oRs.Eof)
              {
                kpn_key = oRs("prs_kostenplaats_key").Value || -1; // Kan leeg zijn als voor alles gemandateerd
                kpn_nr = oRs("prs_kostenplaats_nr").Value;
                oRs.MoveNext();
                if (!oRs.Eof) // Er zijn er meerdere
                {
                    kpn_key = -1;
                    __Log("Gemandateerd voor meerdere kostenplaatsen dus genegeerd.");
                }
                else
                    __Log("Uitsluitend gemandateerd voor kostenplaats: " + kpn_nr);
              }
            }

            if (opdr_kpn && opdr_kpn != "")
            {
                tsql = "SELECT prs_kostenplaats_key"
                     + "  FROM prs_kostenplaats"
                     + " WHERE prs_kostenplaats_verwijder IS NULL"
                     + "   AND (prs_kostenplaats_begin IS NULL"
                     + "         OR prs_kostenplaats_begin < SYSDATE)"
                     + "   AND (prs_kostenplaats_eind IS NULL"
                     + "         OR prs_kostenplaats_eind > SYSDATE)"
                     + "   AND prs_kostenplaats_upper = " + safe.quoted_sql_upper(opdr_kpn); // Unique
                oRs = Oracle.Execute(tsql);
                if (!oRs.EoF)
                {
                  var opdr_kpn_key = oRs("prs_kostenplaats_key").Value;

                  tsql = "SELECT ''"
                       + "  FROM prs_perslidkostenplaats"
                       + " WHERE prs_perslidkostenplaats_boeken = 1"
                       + "   AND (prs_kostenplaats_key = " + opdr_kpn_key
                       + "         OR prs_kostenplaats_key IS NULL)"       // Mandated for all
                       + "   AND prs_perslid_key = " + voor_key;
                  oRs = Oracle.Execute(tsql);

                  if (!oRs.EoF) // persoon gemandateerd voor gegeven kostenplaats
                      kpn_key = opdr_kpn_key;
                }
            }

            if (resultcode == -1) // Nog steeds geen fouten.
            {
              var meld_oms = "Automatisch aangemaakte melding tbv. werkzaamheden geïnitieerd in extern systeem met referentie: "+ externnr;
              if (meldingtekst != null)
              {
                 meld_oms = meld_oms + "\n" + meldingtekst;
              }
              mld_key = mld.insertmelding(stdm_key,
                      {
                          kostenplaats_key: kpn_key,
                          perslid_key: voor_key,
                          fields: [{ dbs: "mld_melding_externnr",     typ: "varchar",  val: externnr },
                                   { dbs: "mld_melding_omschrijving", typ: "varchar",  val: meld_oms },
                                   { dbs: "mld_melding_onderwerp",    typ: "varchar",  val: onderwerp }
                                  ]
                      });

              // mld.insertmelding heeft al op status 2 (nieuw) gezet
              mld.setmeldingstatus(mld_key, 4);
            }
          }
          break;
      case 1:
          mld_key = oRs("mld_melding_key").Value;
          break;
      default:
          {
            // Te veel kunnen we nog niet echt aan?
          }
    }
    oRs.Close();
    if (mld_key < 0)
    { // Tja, dan wordt het lastig?
      resultcode = 7;
      resulttekst = "Melding '{0}' cannot be identified uniquely".format(externnr);
    }


    if (resultcode == -1) // Nog steeds geen fouten.
    {
      var this_melding = mld.func_enabled_melding(mld_key);
      if (!this_melding.canAddOpdr)
      {
        resultcode = 4;
        resulttekst = "Not authorized";
      }
    }
    // Hier heb ik een geldige mld_key (nieuw of bestaand).
    if (resultcode == -1) // Nog steeds geen fouten.
    { // Bepaal opdrachttype.
      tsql = "SELECT mld_typeopdr_key"
           + "  FROM mld_typeopdr"
           + " WHERE mld_typeopdr_key="+ topdr_key;
      var oRs = Oracle.Execute(tsql);
      if (oRs.eof)
      {
        resultcode = 8;
        resulttekst = "Undefined opdrachttype "+ topdr_key;
      }
      oRs.Close();
    }

    // Hier heb ik een geldige topdr_key.
    if (resultcode == -1) // Nog steeds geen fouten.
    { // Bepaal uitvoerende.
      tsql = "SELECT prs_bedrijf_key"
           + "  FROM prs_bedrijf"
           + " WHERE prs_bedrijf_key="+ uitv_key
           + "   AND prs_bedrijf_verwijder IS NULL";
      var oRs = Oracle.Execute(tsql);
      if (oRs.eof)
      {
        resultcode = 8;
        resulttekst = "Undefined uitvoerende "+ uitv_key;
      }
      oRs.Close();
    }

    // Hier heb ik een geldige uitv_key.
    if (resultcode == -1) // Nog steeds geen fouten.
    { // Alvast de nieuwe opdracht aanmaken. Rest komt later.
      var mld_info = mld.mld_melding_info(mld_key);
      var fields = [ { dbs: "mld_opdr_key",                typ: "key",      seq: "mld_s_mld_opdr_key" },
                     { dbs: "mld_opdr_module",             typ: "varchar",  val: "MLD" },
                     { dbs: "mld_melding_key",             typ: "key",      val: mld_key },
                     { dbs: "prs_kostenplaats_key",        typ: "key",      val: mld_info.kostenpl_key },
                     { dbs: "mld_uitvoerende_keys",        typ: "key",      val: uitv_key },
                     { dbs: "mld_typeopdr_key",            typ: "key",      val: topdr_key },
                     { dbs: "mld_opdr_bedrijfopdr_volgnr", typ: "sql",      val: "mld.bepaalopdrmeldingvolgnr(" + mld_key +")" },
                     { dbs: "mld_opdr_datumbegin",         typ: "datetime", val: new Date() }
                   ];

      if (opdrachttekst != null)
      {
        fields.push({ dbs: "mld_opdr_omschrijving",      typ: "varchar",  val: opdrachttekst, len: 4000 });
      }

      // Flag, ignore if invalid
      if (!isNaN(flag) && flag >= 0 && flag < S("mld_opdracht_flags"))
      {
        fields.push({ dbs: "mld_opdr_flag",      typ: "number",  val: flag });
      }

      var mldIns = buildInsert("mld_opdr", fields, { noValidateToken: true });
      var sql = mldIns.sql;
      var err = Oracle.Execute(sql, true);
      if (err.friendlyMsg)
      {
        resultcode = 9;
        resulttekst = "Database error: "+ err.friendlyMsg;
      }

      opdr_key = mldIns.sequences["mld_opdr_key"]; // Hier heb ik de geldige opdr_key (igv. insert).

      tsql = "UPDATE mld_opdr"
          + "    SET mld_opdr_id = " + safe.quoted_sql(externopdrnr)
          + "  WHERE mld_opdr_key=" + opdr_key;
      Oracle.Execute(tsql);

      mld.setopdrachtstatus (opdr_key, 5);
      mld.updatemeldingstatus (mld_key, 0);
    }
  }
  else if (opdraction == "update" || opdraction == "note")
  {   // Bestaande opdracht zoeken
    var idarr = opdrid.split('/');
    mld_key = parseInt(idarr[0], 10);
    opdrvolgnr = parseInt(idarr[1], 10);

    if (isNaN (mld_key) || isNaN (opdrvolgnr))
    {
      resultcode = 2;
      resulttekst = "Invalid opdracht id (key="+ opdrid +")";
    }
    else
    {
      tsql = "SELECT mld_opdr_key"
          + "   FROM mld_opdr"
          + "  WHERE mld_melding_key="+ mld_key
          + "    AND mld_opdr_bedrijfopdr_volgnr="+ opdrvolgnr;
      var oRs = Oracle.Execute(tsql);
      if (oRs.eof)
      {
          resultcode = 5;
          resulttekst = "Non existent opdracht";
      }
      else
      {   // Hier heb ik de geldige opdr_key (igv. update).
          opdr_key = oRs("mld_opdr_key").Value;
      }
      oRs.Close();
    }
    // als de action note is dan vullen we alleen de notitie en zijn we verder klaar.
    if (resultcode == -1 && opdraction == "note")
    {
       tsql = "INSERT INTO mld_opdr_note(mld_opdr_key, prs_perslid_key, mld_opdr_note_omschrijving)"
            + " VALUES (" + opdr_key + "," + user_key + "," + safe.quoted_sql(opdropmerking) + ")";
       Oracle.Execute(tsql);
       resultcode = 0;   // we zijn klaar.
    }
  }
  else
  {
    resultcode = 2;
    resulttekst = "Undefined operation (type=" + opdraction + ")";
  }

  if (resultcode == -1)
  {  // Hier heb ik een geldige opdr_key.
    var this_opdr = mld.func_enabled_opdracht(opdr_key);
    if (!this_opdr.canChange)
    {
      resultcode = 4;
      resulttekst = "Not authorized";
    }

    var mld_opdr = mld.mld_opdr_info(opdr_key); // Bevat alle info van de opdracht.
    var stdm_info = mld.mld_stdmeldinginfo(mld_opdr.stdm_key);

    // Initialiseer (nog eens) enkele opdrachtgegevens tbv. de response of vervolg.
    opdrid = mld_key + "/" + mld_opdr.mld_opdr_bedrijfopdr_volgnr;
    oldstatus = mld_opdr.opdr_status;
    topdr_key = mld_opdr.opdr_type;

    var autoorder = stdm_info.autoorder;
    if (autoorder)
    {
        if (MLDremark > 0)
            MLDremark |= 4; // 'Altijd' bitje zetten
        if (ORDremark > 0)
            ORDremark |= 4; // 'Altijd' bitje zetten
    }

    var mut_datum = new Date();
    if (txt_mut_datum && mld_opdr.typeopdr_afmeldmarge>0)
    {
      var mut_datum = XMLtoJsDate(txt_mut_datum);
      var maxmarge = new Date();
      maxmarge.setDate(maxmarge.getDate() - mld_opdr.typeopdr_afmeldmarge);
      if (mut_datum < maxmarge)
          mut_datum = maxmarge; // te lang geleden
    }

    // Dit zijn de wijzigingen, die voeren we alleen uit bij een geldige statuswijziging
    // of geen statuswijziging
    var canOpmChange = opdraction == "insert" ||
                        this_opdr.canOpmChange &&
                          (   S("mld_afhandeling_restrict_edit") == 0                 // Hier mag het afhandeling veld altijd aangepast worden
                           || opdrstatus != null && (oldstatus == 5 || oldstatus == 8) && // En anders alleen bij het afmelden, afwijzen of annuleren
                                (   opdrstatus == 'ORDAFM' && this_opdr.canClose      // Verifieer dat we gaan (en mogen) afmelden
                                 || opdrstatus == 'ORDCAN' && this_opdr.canCancel));  // Verifieer dat we gaan (en mogen) annuleren
    if (opdropmerking != null && canOpmChange)
    { // opmerking bij de opdracht
      var new_opm = opdropmerking;
      if (ORDremark == 5) // Append
      {
          sql = "SELECT mld_opdr_opmerking"
              + "  FROM mld_opdr"
              + " WHERE mld_opdr_key = " + opdr_key;
          oRs = Oracle.Execute(sql);
          if (oRs("mld_opdr_opmerking").Value)
              new_opm = oRs("mld_opdr_opmerking").Value + "\n" + new_opm;
          oRs.Close();
      }
      if (ORDremark == 5 || ORDremark == 6)
      {
        opdr_fields.push({ dbs: "mld_opdr_opmerking",  typ: "varchar", val: new_opm,        track: L("lcl_mld_inf_Opmerking"), len: 4000 });
      }

      // opmerking bij de melding
      var new_mld_opm = opdropmerking;
      if (MLDremark == 5) // Append
      {
          sql = "SELECT mld_melding_opmerking"
              + "  FROM mld_melding"
              + " WHERE mld_melding_key = " + mld_opdr.mld_key;
          oRs = Oracle.Execute(sql);
          if (oRs("mld_melding_opmerking").Value)
            new_mld_opm = oRs("mld_melding_opmerking").Value + "\n" + new_mld_opm;
          oRs.Close();
      }
      if (MLDremark == 5 || MLDremark == 6)
      {
        mld_fields.push({ dbs: "mld_melding_opmerking",  typ: "varchar", val: new_mld_opm,        track: L("lcl_mld_inf_Opmerking"), len: 4000 });
      }
    }

    if (opdrkosten != null && opdrkosten != 0)
    {
       if (this_opdr.iamBedrijfContact && opdrkosten > S("mld_max_kosten_extern"))
       {
          resultcode = 10;
          resulttekst = L("lcl_opdr_cost_exceed") + S("mld_max_kosten_extern");
       }
    }

    if (opdrkosten != null && (opdraction == "insert" || this_opdr.canUrenMatChange))
       opdr_fields.push({ dbs: "mld_opdr_kosten",     typ: "float",   val: opdrkosten,     track: L("lcl_total_cost") });
    if (opdruren != null && (opdraction == "insert" || this_opdr.canUrenMatChange))
       opdr_fields.push({ dbs: "mld_opdr_uren",       typ: "float",   val: opdruren,       track: L("lcl_hours") });
    if (opdrmateriaal != null && (opdraction == "insert" || (this_opdr.typeopdr_materiaal < 2 && this_opdr.canUrenMatChange)))
       opdr_fields.push({ dbs: "mld_opdr_materiaal",  typ: "float",   val: opdrmateriaal,  track: L("lcl_mld_material") });
    if (opdruurloon != null)
       opdr_fields.push({ dbs: "mld_opdr_uurloon",    typ: "float",   val: opdruurloon,    track: L("lcl_prs_person_uurloon") });

    var ins_srtdiscipline_prefix = mld.mld_prefix(mld_key);
    var formattedID = ins_srtdiscipline_prefix + opdrid;

    var orddat_tracking = "";
    if (opdrgereed != null && (opdraction == "insert" || this_opdr.canDatesChange))
    {
      var old_einddatum = mld_opdr.mld_opdr_einddatum;
      var new_einddatum = XMLtoJsDate(opdrgereed);
      if (old_einddatum == null || old_einddatum.midnight().getTime() != new_einddatum.midnight().getTime()) // Difference? (Check Date only)
      {
        opdr_fields.push({ dbs: "mld_opdr_einddatum", typ: "datetime", val: new_einddatum });
        orddat_tracking = L("lcl_ord_is_orddattrack").format(formattedID) + ": " + buildTrackText("date", old_einddatum, new_einddatum);
      }
      if (autoorder) // Ooit: net als appendRemark via json configureerbaar maken?
        mld_fields.push({ dbs: "mld_melding_einddatum",  typ: "datetime", val: new_einddatum, track: L("lcl_mld_enddate") });
    }

    // Plandatum wordt apart getracked want deze heeft zijn eigen tracking code (ORDPLD)
    var ordpld_tracking = "";
    if (plandatum != null && (opdraction == "insert" || this_opdr.canPlanDatesChange))
    {
      var new_plandatum;
      if (plandatum == "")
      {
        new_plandatum = null;
      }
      else
      {
        new_plandatum = XMLtoJsDate(plandatum);
      }
      var old_plandatum = mld_opdr.mld_opdr_plandatum;
      if ((old_plandatum != null || new_plandatum != null) &&
          (old_plandatum == null || new_plandatum == null || old_plandatum.getTime() != new_plandatum.getTime())) // Difference?
      {
        opdr_fields.push({ dbs: "mld_opdr_plandatum", typ: "datetime", val: new_plandatum });
        ordpld_tracking = L("lcl_ord_is_ordpldtrack").format(formattedID) + ": " + buildTrackText("datetime", old_plandatum, new_plandatum);
      }
    }

    // Plandatum2 wordt apart getracked want deze heeft zijn eigen tracking code (ORDPL2)
    var ordpl2_tracking = "";
    if ((S("mld_use_plandate2") & 2 && plandatum2 != null) && (opdraction == "insert" || this_opdr.canPlanDatesChange))
    {
      var new_plandatum2;
      if (plandatum2 == "")
      {
        new_plandatum2 = null;
      }
      else
      {
        new_plandatum2 = XMLtoJsDate(plandatum2);
      }
      var old_plandatum2 = mld_opdr.mld_opdr_plandatum2;
      if ((old_plandatum2 != null || new_plandatum2 != null) &&
          (old_plandatum2 == null || new_plandatum2 == null || old_plandatum2.getTime() != new_plandatum2.getTime())) // Difference?
      {
        opdr_fields.push({ dbs: "mld_opdr_plandatum2", typ: "datetime", val: new_plandatum2 });
        ordpl2_tracking = L("lcl_ord_is_ordpl2track").format(formattedID) + ": " + buildTrackText("datetime", old_plandatum2, new_plandatum2);
      }
    }

    if ((this_opdr.canHervatten && resume) || (this_opdr.canOnderbreken && halt))
    {
      status_tracking_key = mld.opdr_hltrsm(opdr_key, { halt: halt, resume: resume }); // Zorgt voor het zetten van de mld_opdr.mld_opdr_halted waarde en het tracken van "ORDHLT" of "ORDRSM".
    }
    else if (resume || halt)
    {
      resultcode = 4;
      if (mld_opdr.opdr_status != 8) // Een halt of resume is alleen mogelijk in de opdrachtstatus Geaccepteerd(8).
        resulttekst = "Not authorized for" + (resume? " ORDRSM" : " ORDHLT");
      else if (!this_opdr.canHervatten && resume && mld_opdr.halted)  // Van halted naar resumed.
        resulttekst = "Not authorized for ORDRSM";
      else if (!this_opdr.canOnderbreken && halt && !mld_opdr.halted) // Van resumed naar halted.
        resulttekst = "Not authorized for ORDHLT";
      else
      { // De opdracht was al halted of resume.
        // Als er nog geen "ORDHLT" of "ORDRSM" tracking aanwezig was, dan moet deze tracking nu wel worden toegevoegd.
        // Dit moet in de functie opdr_hltrsm worden geforceerd, anders doet de functie dat niet.
        var forcetracking = false;
        var sql = "SELECT COUNT(*) aantal"
                + "  FROM fac_tracking t"
                + "     , fac_srtnotificatie sn"
                + " WHERE t.fac_srtnotificatie_key = sn.fac_srtnotificatie_key"
                + "   AND t.fac_tracking_refkey = " + opdr_key
                + "   AND sn.fac_srtnotificatie_xmlnode = 'opdracht'"
                + "   AND sn.fac_srtnotificatie_code IN ('ORDHLT', 'ORDRSM')";
        oRs = Oracle.Execute(sql);
        if (oRs("aantal").Value == 0)
          forcetracking = true;

        if (((halt && mld_opdr.halted) || (resume && !mld_opdr.halted)) && forcetracking)
        {
          // De status staat al op halted of resume maar er is nog geen "ORDHLT" of "ORDRSM" tracking aanwezig.
          // Dan deze tracking alsnog aanmaken m.b.v. de functie opdr_hltrsm.
          // De functie zorgt ook voor het leegmaken van verplichte onderbreek kenmerken bij het hervatten.
          status_tracking_key = mld.opdr_hltrsm(opdr_key, { halt: halt, resume: resume, forcetracking: true }); // Zorgt voor het zetten van de mld_opdr.mld_opdr_halted waarde en het tracken van "ORDHLT" of "ORDRSM".
        }
        resultcode = -1; // Hij was al halted of resumed, niet erg dus.
      }
    }

    if (S("mld_use_plandate2") & 2)
    {
      var latest_pld = new_plandatum || mld_opdr.mld_opdr_plandatum;
      var latest_pl2 = new_plandatum2 || mld_opdr.mld_opdr_plandatum2;
      if (latest_pld != null && latest_pl2 != null        // We have a plan-start-date and a plan-end-date
        && (new_plandatum || new_plandatum2)              // and at least 1 of them is new
        && latest_pld.getTime() > latest_pl2.getTime())   // plan-start-date is later than plan-end-date
      {
        resultcode = 6;
        resulttekst = L("lcl_opdr_plandate") + " (" + latest_pld + ") later than " + L("lcl_opdr_plandate2") + " (" + latest_pl2 + ")";
      }
    }

    // Flag, ignore if invalid
    if (!isNaN(flag) && flag >= 0 && flag < S("mld_opdracht_flags"))
    {
      opdr_fields.push({ dbs: "mld_opdr_flag",  typ: "number",    val: flag });
    }

    // We weten nu de updates, straks voeren we deze pas uit
    // Eerst de eventuele statuswijzigingen
    var newstatus = -1;
    if (opdrstatus != null)
    { // Er is dus wel een status meegegeven
      switch (opdrstatus)
      {
        case 'ORDACP' :
            if (oldstatus == 8)
            {
                // dan gewoon negeren
            }
            else // aanpassen
            {
	            if (this_opdr.canAccept) {
	                if (oldstatus == 5) {
	                    newstatus = 8;
	                } else {
	                    resultcode = 2;
	                    resulttekst = "Invalid status change";
	                }
	            } else {
	                resultcode = 4;
	                resulttekst = "Not authorized for ORDACP";
	            }
            }
            break;
        case 'ORDCAN' :
            if (oldstatus == 1)
            {
                // dan gewoon negeren
            }
            else // aanpassen
            {
	            if (this_opdr.canCancel) {
	                if (oldstatus == 5 || oldstatus == 8) {
	                    newstatus = 1;
	                } else {
	                    resultcode = 2;
	                    resulttekst = "Invalid status change";
	                }
	            } else {
	                resultcode = 4;
	                resulttekst = "Not authorized for ORDCAN";
	            }
            }
            break;
        case 'ORDAFM' :
            if (oldstatus == 6 || oldstatus == 7)
            {
                // al afgemeld of verwerkt, dan gewoon negeren
            }
            else // aanpassen
            {
                if (this_opdr.canClose) {
                    if (oldstatus == 5 || oldstatus == 8 ) {
                        newstatus = 6;
                    } else {
                        resultcode = 2;
                        resulttekst = "Invalid status change";
                    }
                } else {
                    resultcode = 4;
                    resulttekst = "Not authorized for ORDAFM";
                }
            }
            break;
        case 'ORDAFR' :
            if (oldstatus == 9)
            {
                // dan gewoon negeren
            }
            else
            {
                if (this_opdr.canFinish) {
                    if (oldstatus == 6 ) {
                        newstatus = 9;
                    } else {
                        resultcode = 2;
                        resulttekst = "Invalid status change";
                    }
                } else {
                    resultcode = 4;
                    resulttekst = "Not authorized for ORDAFR";
                }
            }
            break;
        default:
            resultcode = 3;
            resulttekst = "Invalid status";
      }  // switch
    }

    if (resultcode == -1)
    { // Nog steeds geen fouten
      // Construct the update
      if (this_opdr.canChange)
      {
        if (opdr_fields.length > 0)
        {
          var mldUpd = buildTrackingUpdate("mld_opdr", "mld_opdr_key = " + opdr_key, opdr_fields, { noValidateToken: true });
          Oracle.Execute(mldUpd.sql);
          update_tracking_key = mld.trackopdrachtupdate(opdr_key, L("lcl_ord_is_updatedbysoap") + "\n" + mldUpd.trackarray.join("\n"));
          if (mut_datum) // Antedateren voor SLA rapportages.
          {
              var sql = "BEGIN fac.backtrackaction('ORDUPD', " + opdr_key + ", " + user_key + ", " + mut_datum.toSQL(true) +"); END;"
              Oracle.Execute(sql);
          }
          // Tracken en notificeren van de einddatum (ORDDAT) (notificeren aan de gebouwverantwoordelijke van het betreffende gebouw van de onderliggende melding).
          if (orddat_tracking != "")
          {
            datum_tracking_key = mld.trackopdreinddatumupdate(opdr_key, orddat_tracking);
          }
          // Tracken en notificeren van de geplande aanvang datum (ORDPLD) (notificeren aan interne contactpersoon).
          if (ordpld_tracking != "")
          {
            datum_tracking_key = mld.trackopdrplandatumupdate(opdr_key, ordpld_tracking);
          }
          // Tracken en notificeren van de geplande einddatum (ORDPL2) (notificeren aan interne contactpersoon).
          if (S("mld_use_plandate2") & 2 && ordpl2_tracking != "")
          {
            datum_tracking_key = mld.trackopdrplandatum2update(opdr_key, ordpl2_tracking);
          }
        }
      }

      var this_mld = mld.func_enabled_melding(mld_opdr.mld_key);
      // mag ik de melding ook aanpassen?
      if (this_mld.canClose && (autoorder || MLDremark > 0))
      {
        if (mld_fields.length>0)
        {
          var mldUpd = buildTrackingUpdate("mld_melding", "mld_melding_key = " + mld_opdr.mld_key, mld_fields, { noValidateToken: true });
          Oracle.Execute(mldUpd.sql);

          var formattedID = stdm_info.ins_srtdiscipline_prefix+mld_opdr.mld_key + " ("+ stdm_info.discipline_omschrijving + "/"+ stdm_info.stdmelding_omschrijving +")";
          mld.trackmeldingupdate(mld_opdr.mld_key, L("lcl_mld_is_updatedbysoap").format(formattedID) + "\n" + mldUpd.trackarray.join("\n"));

          if (mut_datum) // Antedateren voor SLA rapportages.
          {
              var sql = "BEGIN fac.backtrackaction('MLDUPD', " + mld_opdr.mld_key + ", " + user_key + ", " + mut_datum.toSQL(true) +"); END;"
              Oracle.Execute(sql);
          }
        }
      }

      // De kenmerken.
      update_tracking_key = coalesce(update_tracking_key, upsertKenmerk(opdrkenmerken, topdr_key, opdr_key));

      // De opdrachtregels.
      update_tracking_key = coalesce(update_tracking_key, upsertOpdrRegels(opdrregels, mld_opdr));

      if (newstatus != -1)
      {
        add_note(); // zodat we note_key hebben
        mld.setopdrachtstatus(opdr_key, newstatus, null, note_key);
        // Hier terugchecken van de status
        tsql = "SELECT mld_statusopdr_key FROM mld_opdr"
             + " WHERE mld_opdr_key="+ opdr_key;
        oRs = Oracle.Execute(tsql);
        if (oRs("mld_statusopdr_key").Value == newstatus)
        {
          resulttekst = "Status set to " + opdrstatus;

          if (newstatus == 8)
          { // Opdracht is geaccepteerd ("ORDACP").
            // Ik kan hier zelf bepalen of de actieve opdracht onderbroken moet worden (hltactive).
            // Als de opdrachten niet sequentieel uitgevoerd moeten worden, dan wordt er ook geen actieve opdracht gevonden.
            if (mld_opdr.contactpers_key < 0)
              var activeopdr = mld.getactiveopdracht(opdr_key, user_key, mld_opdr.uitvoerende_key); // Haal de actieve opdracht op.
            else
              var activeopdr = mld.getactiveopdracht(opdr_key); // Haal de actieve opdracht op.
            var activeopdr_key = activeopdr.opdr_key;
            var activeopdracht = activeopdr.opdracht;
            var hltactive = (activeopdr_key > 0);

            var onHold = ((mld_opdr.typeopdr_sequential & 4) == 4); // Staat "Onderbroken starten" aan?
            if (hltactive)
            { // Er is 1 actieve opdracht die onderbroken moet worden. Dit doen we door een "ORDHLT" tracking op die actieve opdracht te doen.
              Oracle.Execute("UPDATE mld_opdr SET mld_opdr_halted = 1 WHERE mld_opdr_key = " + activeopdr.opdr_key);
              shared.trackaction("ORDHLT", activeopdr.opdr_key);
            }
            else if (onHold)
            { // Als "Onderbroken starten" aan staat dan deze opdracht op "On Hold" zetten. Dan geen "ORDHLT" tracking genereren.
              Oracle.Execute("UPDATE mld_opdr SET mld_opdr_halted = 1 WHERE mld_opdr_key = " + opdr_key);
            }
          }

          if (mut_datum && opdrstatus) // Tracking antedateren voor SLA rapportages.
          { // 'Toevallig' loopt opdrstatus 1-op-1 met onze trackingcodes
            var sql = "BEGIN fac.backtrackaction("+safe.quoted_sql(opdrstatus)+", " + opdr_key + ", " + user_key + ", " + mut_datum.toSQL(true) +"); END;"
            Oracle.Execute(sql); // Statuswijzigingen wel even tracken
            var txt = { "ORDAFM": L("lcl_ord_is_ordafm"),
                        "ORDACP": L("lcl_ord_is_ordacp"),
                        "ORDAFR": L("lcl_ord_is_ordafr")
                      }[opdrstatus];
            update_tracking_key = coalesce(update_tracking_key, mld.trackopdrachtupdate(opdr_key, L("lcl_ord_is_ordupd") + "\n" + txt + L("lcl_trackto") + toDateTimeString(mut_datum)));
          }

          // Zijn er nog lopende opdrachten
          tsql = "SELECT COUNT ( * ) lopend FROM  mld_opdr"
               + " WHERE mld_melding_key = " + mld_key
               + " AND mld_statusopdr_key NOT IN (1, 2, 6, 7, 9)"; // (1=afgewezen, 2=niet akkoord, 6=afgemeld, 7=verwerkt en 9=afgerond)
          oRs = Oracle.Execute(tsql);

          if (oRs("lopend").Value == 0)
          { // Er zijn alleen opdrachten met status 1 (Afgewezen) of 6/9 (Afgemeld/Afgerond)
            // Check of setting bij opdrachttype automatisch sluiten ALTIJD (= 2) toestaat.

            // Ondertussen kan de status van de opdracht aangepast zijn.
            // De waarde this_mld.canClose kan daarom niet meer juist zijn omdat deze van de status afhankelijk is.
            // Daarom moet de waarde opnieuw bepaald worden (PROR#90212 / PROR#90706).
            var this_mld2 = mld.func_enabled_melding(mld_opdr.mld_key);

            tsql = "SELECT mld_typeopdr_sluitmelding FROM mld_typeopdr"
                 + " WHERE mld_typeopdr_key = " + topdr_key;
            var oRs = Oracle.Execute(tsql);

            // Afhankelijk van sluitmelding nu melding status zetten (0=nooit, 1=vragen en 2=altijd)
            // Als de laatste opdracht een cancel was dan wordt de melding niet afgemeld.
            if ((oRs("mld_typeopdr_sluitmelding").Value == 2 || oRs("mld_typeopdr_sluitmelding").Value == 1)
                 && this_mld2.canClose
                 && opdrstatus != 'ORDCAN')
            {
              mld.setmeldingstatus (mld_key, 5); // Afgemeld
              var sql = "BEGIN fac.backtrackaction('MLDAFM', " + mld_key + ", " + user_key + ", " + mut_datum.toSQL(true) +"); END;"
              Oracle.Execute(sql); // Statuswijzigingen wel even tracken
              mld.trackmeldingupdate(mld_key, L("lcl_mld_is_mldupd") + "\n" + L("lcl_mld_is_mldafm") + L("lcl_trackto") + toDateTimeString(mut_datum));
              // Handle workflow actions for completion:
              mld.nextworkflowstep(mld_key, 1);  // 1 = completed
            }
            else
              // Dan zetten we de Melding hier terug naar Geaccepteerd indien die Uitgegeven was
              // Zodat die bij de FO weer onder Inbehandeling valt.
              mld.setmeldingstatus (mld_key, 4); // Geaccepteerd
          }
        }
        else
        {
          resultcode = 1;
          resulttekst = "Status not set to "+opdrstatus+" (is "+oRs("mld_statusopdr_key").Value+")";
        }
      }
    }
  }

  if (resultcode == -1) // Nog steeds geen fouten.
  {

    var tracking_key = coalesce(coalesce(status_tracking_key, datum_tracking_key), update_tracking_key); // volgorde van belangrijkheid
    add_note(tracking_key); // voor als nog niet eerder gebeurd

    resultcode = 0; // Ik heb gedaan wat ik moest en mocht doen voor deze opdracht.
  }
  else if (resultcode > 0)
  {
    var logtxt = "api_opdrsoap fail.\nResultcode: {0}\nReturntekst: {1}\nOpdracht: {2}\nOldstatus: {3}".format(resultcode, resulttekst, opdrid, oldstatus);
    if (resulttekst.match(/^Not authorized/)) // Dit komt heel vaak voor door updates op gesloten opdrachten.
        __Log(logtxt);                      // Het vervuilt de logfile veel te veel
    else
        __DoLog(logtxt, "#00FF00");
  }

  var binfo = {
               returncode: resultcode,
               returntekst: resulttekst,
               // executed:sql,   // debug
               datum: nowtxt,
               externnr: externnr
              }

  var FCLTBody = xmlResp.createElement("opdracht");   // type=response
  FCLTBody.setAttribute('key', opdrid);
  FCLTBody.setAttribute('type', 'response');
  for (param in binfo)
  {
    var FCLTdata = xmlResp.createElement(param);
    FCLTdata.appendChild(xmlResp.createTextNode(binfo[param]));
    FCLTBody.appendChild(FCLTdata);
  }

  FCLTElement.appendChild(FCLTBody);
} // end for
// Alle opdracht-nodes zijn behandeld.

// Voeg een notitie toe en koppel hem eventueel direct aan tracking_key
function add_note(tracking_key)
{
    if (/*global*/ note_key > 0)
      return; // hadden we het al gedaan

    if (opdraction == "update" && opdrnote != null)
    {
        var fields =  [ { dbs: "mld_opdr_note_key",     typ: "key",     seq: "mld_s_mld_opdr_note_key" },
                        { dbs: "mld_opdr_key",          typ: "key",     val: opdr_key },
                        { dbs: "prs_perslid_key",       typ: "key",     val: user_key },
                        { dbs: "mld_opdr_note_flag",    typ: "number",  val: opdrnoteflag || 4 }, // default alleen uitvoerende, +1 voor FE-zichtbaar
                        { dbs: "mld_opdr_note_omschrijving", typ: "varchar", val: opdrnote, len: 4000 }
                      ];
        var noteIns = buildInsert("mld_opdr_note", fields, { noValidateToken: true });
        note_key = noteIns.sequences["mld_opdr_note_key"];
        Oracle.Execute(noteIns.sql);

        if (!(tracking_key > 0))
        { // Er is alleen een losse notitie. De opdracht is niet aangepast.
          if ((opdrnoteflag & 1) == 1) // voor FE-zichtbaar
            tracking_key = mld.trackmeldingaddnote(mld_key); // MLDNOT tracking.
          else
            tracking_key = mld.trackopdrachtaddnote(opdr_key); // ORDNOB tracking.
        }
        var sql = "UPDATE mld_opdr_note"
                + "   SET fac_tracking_key = " + tracking_key
                + " WHERE mld_opdr_note_key = " + note_key;
        Oracle.Execute(sql);
    }
}

// Als key1 geldig is heeft die de voorkeur, anders keys
function coalesce(key1, key2)
{
    if (key1 > 0)
        return key1;
    return key2;
}

function upsertKenmerk(kenmerken, topdr_key, opdr_key)
{
  //Response.Write("lengte:"+ kenmerken.length);
  var flextrack = []; // eventuele tracking
  for (var j = 0; j < kenmerken.length; j++)
  {
    // Er kan op drie manieren aangegeven worden om welk kenmerk het gaat
    var kenmerk_attr_key = parseInt(kenmerken[j].getAttribute("key"), 10);
    var kenmerk_attr_code = kenmerken[j].getAttribute("code");
    var kenmerk_naam = kenmerken[j].getAttribute("naam");
    // default srtkenmerk on name
    var srtkenmerk_key_code_omschr = "   AND sk.mld_srtkenmerk_upper = " + safe.quoted_sql_upper(kenmerk_naam);
    if (kenmerk_attr_key > 0)
      srtkenmerk_key_code_omschr = "   AND sk.mld_srtkenmerk_key = " + kenmerk_attr_key;
    else if (kenmerk_attr_code)
      srtkenmerk_key_code_omschr = "   AND  UPPER(sk.mld_srtkenmerk_code) = " + safe.quoted_sql_upper(kenmerk_attr_code);
    if (kenmerken[j].childNodes.length > 0)
       var kenmerk_waarde = kenmerken[j].childNodes[0].nodeValue;
    else
       var kenmerk_waarde = "";
    //Response.Write(kenmerk_naam);
    //Response.Write(kenmerk_waarde);
    // Kenmerk_key opzoeken
    ksql = "SELECT k.mld_kenmerk_key"
         + "     , COALESCE (" + lcl.xsql("k.mld_kenmerk_omschrijving", "k.mld_kenmerk_key")
         + "              ,  " + lcl.xsql("sk.mld_srtkenmerk_omschrijving", "sk.mld_srtkenmerk_key") + ") kenmerk_omschrijving"
         + "     , sk.mld_srtkenmerk_kenmerktype"
         + "     , sk.mld_srtkenmerk_lengte"
         + "     , sk.fac_kenmerkdomein_key"
         + "  FROM mld_kenmerk k"
         + "     , mld_srtkenmerk sk"
         + " WHERE (k.mld_typeopdr_key = " + topdr_key + " OR k.mld_kenmerk_niveau = 'P')"
         + "   AND k.mld_srtkenmerk_key = sk.mld_srtkenmerk_key"
         + "   AND k.mld_kenmerk_verwijder IS NULL"
         + srtkenmerk_key_code_omschr;
    var oRs = Oracle.Execute(ksql);

    if (!oRs.eof)
    {
      var kenmerk_key = oRs("mld_kenmerk_key").Value;
      var label = oRs("kenmerk_omschrijving").Value;
      var kenmerk_type = oRs("mld_srtkenmerk_kenmerktype").Value;
      var kenmerk_lengte = oRs("mld_srtkenmerk_lengte").Value;
      var kenmerkdomein_key = oRs("fac_kenmerkdomein_key").Value;

      // Was het flexkenmerk er al?
      ksql = "SELECT ko.mld_kenmerkopdr_key, mld_kenmerkopdr_waarde "
           + "  FROM mld_kenmerkopdr ko "
           + " WHERE ko.mld_opdr_key =  " + opdr_key
           + "   AND ko.mld_kenmerk_key = " + kenmerk_key;
      oRs.Close();
      var oRs = Oracle.Execute(ksql);

      switch (kenmerk_type)
      {
        case 'R':
        case 'r':
        case 'S':
        {
          // read reference fields
          if (kenmerk_waarde != "") {
            var kenmerk_ref = kenmerken[j].getAttribute("ref");
            if (kenmerk_ref) {
              kenmerk_waarde = getKenmerkKeyByRef(kenmerkdomein_key, kenmerk_ref, kenmerk_waarde);
            }
          }
        }
        // geen break, doorvallen naar bijwerken van de kenmerken
        case 'C':
        case 'X':
        case 'N':
        case 'V':
        { // We ondersteunen alleen nog maar karakter- en bestandsnaam-velden.
          // AEG: Maar wat maakt een nummer nu anders dan een karakter ?
          var vnew = kenmerk_waarde;
          if (kenmerk_type == 'R' || kenmerk_type == 'r' || kenmerk_type == 'S')
          {
            if (vnew)
            {
              var oRsd = Oracle.Execute("SELECT fac.getdomeinwaarde(" + kenmerkdomein_key + ", " + safe.quoted_sql(vnew) + ") domeinwaarde FROM DUAL");
              vnew = oRsd("domeinwaarde").Value;
              oRsd.Close();
            }
          }
          if (!oRs.eof)
          {
            var kenmerkopdr_key = oRs("mld_kenmerkopdr_key").Value;
            var vold = oRs("mld_kenmerkopdr_waarde").Value; // Voor tracking
            if (kenmerk_type == 'R' || kenmerk_type == 'r' || kenmerk_type == 'S')
            {
              if (vold)
              {
                var oRsd = Oracle.Execute("SELECT fac.getdomeinwaarde(" + kenmerkdomein_key + ", " + safe.quoted_sql(vold) + ") domeinwaarde FROM DUAL");
                vold = oRsd("domeinwaarde").Value;
                oRsd.Close();
              }
            }
            if (kenmerk_waarde == "") {
              // delete characteristic when empty
              ksql = "DELETE FROM mld_kenmerkopdr WHERE mld_kenmerkopdr_key = " + kenmerkopdr_key;
            } else {
              ksql = "UPDATE mld_kenmerkopdr SET mld_kenmerkopdr_waarde = " + safe.quoted_sql(kenmerk_waarde) + " WHERE mld_kenmerkopdr_key = " + kenmerkopdr_key;
            }
            Oracle.Execute(ksql);
            if (vold != vnew)
                flextrack.push(label + ": " + buildTrackText("varchar", vold, vnew, {nodiff: (kenmerk_type != "C")}));
          } else {
            if (kenmerk_waarde != "") {
              // write a new characteristic with a value only
              ksql = "INSERT INTO mld_kenmerkopdr (mld_kenmerk_key, mld_opdr_key, mld_kenmerkopdr_waarde) "
                      + "VALUES (" + kenmerk_key + ", " + opdr_key + ", " + safe.quoted_sql(kenmerk_waarde) + ")";
              Oracle.Execute(ksql);
              flextrack.push(label + ": " + buildTrackText("varchar", null, vnew, {nodiff: (kenmerk_type != "C")}));
            }
          }
        }
        break; // niet doorvallen naar bijlagen
        case "M": // Folder met bijlagen
        { //Response.Write(kenmerk_naam);
          var bijlagen = kenmerken[j].getElementsByTagName("bijlage");
          var bi;
          __Log ('bijlagen.length:' + bijlagen.length);
          for (bi = 0; bi < bijlagen.length; bi++)
          {
            var Attachment = XMLval(bijlagen[bi], "attachment");
            var Name = XMLval(bijlagen[bi], "name");
            var Size = XMLval(bijlagen[bi], "size");
                var Encoding = XMLval(bijlagen[bi], "encoding", true);
            //Response.Write("Name: " + Name + " size: " + Size);
            if (Attachment && Name && Size)
            {
              var SafeName = safe.filename(Name);
              var params = flexProps("MLD", opdr_key, String(kenmerk_key), "O");
              if (!params.isAllowedName(SafeName))
              {
                __DoLog("Unsafe SOAP file '{0}' ignored.".format(SafeName), "#FFFF00");
              }
              else
              {
                __Log("Start saving: " + params.AttachPath + SafeName);
                CreateFullPath(params.AttachPath);
                if (Encoding == 'base64')
                {
                   encodedString2File(params.AttachPath + SafeName, Attachment, "bin.base64");
                }
                else
                {
                   encodedString2File(params.AttachPath + SafeName, Attachment, "bin.hex");
                }

                var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
                var sql = "BEGIN "
                        + "  flx.setflexbijlage"
                                + "(" + safe.quoted_sql("MLD")    // MLD | RES | ....
                                + ", " + kenmerk_key
                                + ", " + opdr_key
                                + ", " + safe.quoted_sql(params.AttachSubPath)
                                + ", " + "NULL" // diskfilename
                                + ", " + safe.quoted_sql(SafeName)
                                + ", " + fso.GetFile(params.AttachPath + SafeName).Size
                                + ", " + "SYSDATE"
                                + ", " + safe.quoted_sql(oCrypto.hex_sha1_file(params.AttachPath + SafeName))
                                + ");"
                        + " END;";
                Oracle.Execute(sql);

                __Log("Done saving: " + params.AttachPath + SafeName);
                flextrack.push(L("lcl_shared_attachment_add").format(label, SafeName));
              }
            }
          }
        }
      }
    }
    oRs.Close();
  }
  // Via de GUI komt dit in hetzelfde tracking record als de vaste-velden-wijziging.
  // Hier komt het in een eigen tracking record. Daar kan ik mee leven.
  if (flextrack.length)
    return mld.trackopdrachtupdate(opdr_key, L("lcl_ord_is_updatedbysoap") + "\n" + flextrack.join("\n"));

  return -1; // else geen trackingkey
}


function isGoodCurrency(str)
{
  var anum = /^([-\+]?\d{1,6}[.,]?|[-\+]?\d{0,6}[.,]\d{1,7})$/
  return anum.test(str);
}

function upsertOpdrRegels(regels, mld_opdr)
{
  var opdr_key = mld_opdr.opdr_key;
  var mat_key_list = [];
  var mat_tracking_arr = [];
  var tracking_key = -1;
  for (var r = 0; r < regels.length; r++)
  {
    var mat_fields = []; // Bij te werken velden.
    var id = parseInt(regels[r].getAttribute("id"), 10);
    var aantal = XMLval(regels[r], "aantal", true);
    var aantalval = parseFloat(XMLval(regels[r], "aantal")? XMLval(regels[r], "aantal").replace(/,/g,".") : XMLval(regels[r], "aantal"));
    var eenheidcode = XMLval(regels[r], "eenheidcode", true);
    var eenheid = XMLval(regels[r], "eenheid", true);
    var prijs = XMLval(regels[r], "prijs", true);
    var prijsval = parseFloat(XMLval(regels[r], "prijs")? XMLval(regels[r], "prijs").replace(/,/g,".") : XMLval(regels[r], "prijs"));
    var btw = XMLval(regels[r], "btw", true);
    var btwval = parseFloat(XMLval(regels[r], "btw")? XMLval(regels[r], "btw").replace(/,/g,".") : XMLval(regels[r], "btw"));
    var matcode = XMLval(regels[r], "matcode", true);
    var omschr = XMLval(regels[r], "omschr", true);
    var info = XMLval(regels[r], "info", true);
    var groep = XMLval(regels[r], "groep", true);
    var extra1 = XMLval(regels[r], "extra1", true);
    var extra2 = XMLval(regels[r], "extra2", true);
    var extra3 = XMLval(regels[r], "extra3", true);
    var extra4 = XMLval(regels[r], "extra4", true);

    // Zijn er elementen binnen de node "mld_opdr_materiaal_id"
    //var elementaantal = regels.getElementsByTagName("mld_opdr_materiaal_id")[r].childNodes.length;

    var sql = "SELECT mld_opdr_materiaal_key"
            + "  FROM mld_opdr_materiaal"
            + " WHERE mld_opdr_key = " + opdr_key
            + "   AND mld_opdr_materiaal_id = " + id;
    var oRs = Oracle.Execute(sql);
    if (aantal && (isNaN (aantalval) || !shared.isGoodNumber(aantalval, false, false, 8, 2)))
    {
      resultcode = 10;
      resulttekst = "Order line " + id + ": Invalid number";
      break;
    }
    else if (prijs && (isNaN(prijsval) || prijsval < 0 || !isGoodCurrency(prijsval)))
    {
      resultcode = 11;
      resulttekst = "Order line " + id + ": Invalid price";
      break;
    }
    else if (btw && (isNaN(btwval) || btwval < 0))
    {
      resultcode = 12;
      resulttekst = "Order line " + id + ": Invalid btw";
      break;
    }
    else if (aantal == "")
    { // Aantal is leeg of 0. Opdrachtregel verwijderen (Delete).
      // Als het aantal niet is meegegeven dan blijft het aantal hetzelfde.
      if (!oRs.eof)
      { // Opdrachtregel is aanwezig.
        var materiaal_key = oRs("mld_opdr_materiaal_key").Value;
        var sql_del = "DELETE mld_opdr_materiaal"
                    + " WHERE mld_opdr_materiaal_key = " + materiaal_key;
        Oracle.Execute(sql_del);
      }
      else
      { // Opdrachtregel is niet aanwezig en kan dus niet verwijderd worden.
        resultcode = 13;
        resulttekst = "Order line not present. Order line can't be deleted.";
        break;
      }
    }
    else
    { // Aantal is meegegeven (Update of Insert).
      if (oRs.eof)
      { // Opdrachtregel bestaat niet (Insert).
        if (!omschr)
        {
          resultcode = 14;
          resulttekst = "Order line " + id + ": Invalid omschr. Omschr not filled";
          break;
        }
        else
        {
          mat_fields = mat_fields.concat(
                          [ { dbs: "mld_opdr_materiaal_key",         typ: "key",     seq: "mld_s_mld_opdr_materiaal_key" },
                            { dbs: "mld_opdr_key",                   typ: "key",     val: opdr_key },
                            { dbs: "mld_opdr_materiaal_id",          typ: "number",  val: id },
                            { dbs: "mld_opdr_materiaal_aantal",      typ: "float",   val: aantalval },
                            { dbs: "mld_opdr_materiaal_eenheidcode", typ: "varchar", val: eenheidcode, len: 10 },
                            { dbs: "mld_opdr_materiaal_eenheid",     typ: "varchar", val: eenheid,     len: 30 },
                            { dbs: "mld_opdr_materiaal_prijs",       typ: "float",   val: prijs? prijsval : "" },
                            { dbs: "mld_opdr_materiaal_code",        typ: "varchar", val: matcode,     len: 30 },
                            { dbs: "mld_opdr_materiaal_omschr",      typ: "varchar", val: omschr,      len: 255 },
                            { dbs: "mld_opdr_materiaal_info",        typ: "varchar", val: info,        len: 4000 },
                            { dbs: "mld_opdr_materiaal_groep",       typ: "varchar", val: groep,       len: 60 },
                            { dbs: "mld_opdr_materiaal_extra1",      typ: "varchar", val: extra1,      len: 60 },
                            { dbs: "mld_opdr_materiaal_extra2",      typ: "varchar", val: extra2,      len: 60 },
                            { dbs: "mld_opdr_materiaal_extra3",      typ: "varchar", val: extra3,      len: 4000 },
                            { dbs: "mld_opdr_materiaal_extra4",      typ: "varchar", val: extra4,      len: 4000 }
                          ]);

          if ((btw || btw == 0) && (!isNaN (btwval) && btwval >= 0))
          {
            var sql_btw = "SELECT btw.fin_btwtabelwaarde_key"
                        + "  FROM fin_btwtabelwaarde btw"
                        + " WHERE btw.fin_btwtabelwaarde_perc = " + btwval
                        + "   AND btw.fin_btwtabelwaarde_verwijder IS NULL"
                        + "   AND (btw.fin_btwtabelwaarde_verlegd IS NULL OR btw.fin_btwtabelwaarde_verlegd = 0)"
                        + " ORDER BY btw.fin_btwtabel_key";
            var oRs_btw = Oracle.Execute(sql_btw);
            if (!oRs_btw.eof)
              mat_fields.push({ dbs: "fin_btwtabelwaarde_key", typ: "key", val: oRs_btw("fin_btwtabelwaarde_key").Value, track: L("lcl_mld_opdr_btw"), foreign: "fin_btwtabelwaarde" });
            else
            {
              resultcode = 12;
              resulttekst = "Order line " + id + ": Invalid btw";
              break;
            }
            oRs_btw.Close();
          }
          else if (btw == "")
            mat_fields.push({ dbs: "fin_btwtabelwaarde_key", typ: "key", val: -1, track: L("lcl_mld_opdr_btw"), foreign: "fin_btwtabelwaarde" });

          var matIns = buildInsert("mld_opdr_materiaal", mat_fields, { noValidateToken: true });
          var mat_key = matIns.sequences["mld_opdr_materiaal_key"];
          var sql = matIns.sql;
          var err = Oracle.Execute(sql, true);
          if (err.friendlyMsg)
          {
            resultcode = 9;
            resulttekst = "Order line " + id + ": Database error: "+ err.friendlyMsg;
            break;
          }
          mat_key_list.push(mat_key);
        }
      }
      else
      { // Opdrachtregel bestaat (Update).
        // Aantal is groter dan 0. Opdrachtregel toevoegen (Update).
        // Als aantal 0 is dan is de materiaalregel al verwijderd en kom je hier niet.
        mat_fields = mat_fields.concat(
                        [ { dbs: "mld_opdr_materiaal_aantal",      typ: "float",   savewhen: aantal !== null,      track: L("lcl_mld_opdr_aantal"),      val: aantalval },
                          { dbs: "mld_opdr_materiaal_eenheidcode", typ: "varchar", savewhen: eenheidcode !== null, track: L("lcl_mld_opdr_eenheidcode"), val: eenheidcode, len: 10 },
                          { dbs: "mld_opdr_materiaal_eenheid",     typ: "varchar", savewhen: eenheid !== null,     track: L("lcl_mld_opdr_eenheid"),     val: eenheid,     len: 30 },
                          { dbs: "mld_opdr_materiaal_prijs",       typ: "float",   savewhen: prijs !== null,       track: L("lcl_mld_opdr_prijs"),       val: (prijs? prijsval : null) },
                          { dbs: "mld_opdr_materiaal_code",        typ: "varchar", savewhen: matcode !== null,     track: L("lcl_mld_opdr_code"),        val: matcode,     len: 30 },
                          { dbs: "mld_opdr_materiaal_omschr",      typ: "varchar", savewhen: omschr !== null,      track: L("lcl_mld_opdr_omschr"),      val: omschr,      len: 255 },
                          { dbs: "mld_opdr_materiaal_info",        typ: "varchar", savewhen: info !== null,        track: L("lcl_mld_opdr_info"),        val: info,        len: 4000 },
                          { dbs: "mld_opdr_materiaal_groep",       typ: "varchar", savewhen: groep !== null,       track: L("lcl_mld_opdr_groep"),       val: groep,       len: 60 },
                          { dbs: "mld_opdr_materiaal_extra1",      typ: "varchar", savewhen: extra1 !== null,      track: L("lcl_mld_opdr_extra1"),      val: extra1,      len: 60 },
                          { dbs: "mld_opdr_materiaal_extra2",      typ: "varchar", savewhen: extra2 !== null,      track: L("lcl_mld_opdr_extra2"),      val: extra2,      len: 60 },
                          { dbs: "mld_opdr_materiaal_extra3",      typ: "varchar", savewhen: extra3 !== null,      track: L("lcl_mld_opdr_extra3"),      val: extra3,      len: 4000 },
                          { dbs: "mld_opdr_materiaal_extra4",      typ: "varchar", savewhen: extra4 !== null,      track: L("lcl_mld_opdr_extra4"),      val: extra4,      len: 4000 }
                        ]);

        if ((btw || btw == 0) && (!isNaN (btwval) && btwval >= 0))
        {
          var sql_btw = "SELECT btw.fin_btwtabelwaarde_key"
                      + "  FROM fin_btwtabelwaarde btw"
                      + " WHERE btw.fin_btwtabelwaarde_perc = " + btwval
                      + "   AND btw.fin_btwtabelwaarde_verwijder IS NULL"
                      + "   AND (btw.fin_btwtabelwaarde_verlegd IS NULL OR btw.fin_btwtabelwaarde_verlegd = 0)"
                      + " ORDER BY btw.fin_btwtabel_key";
          var oRs_btw = Oracle.Execute(sql_btw);
          if (!oRs_btw.eof)
            mat_fields.push({ dbs: "fin_btwtabelwaarde_key", typ: "key", val: oRs_btw("fin_btwtabelwaarde_key").Value, track: L("lcl_mld_opdr_btw"), foreign: "fin_btwtabelwaarde" });
          else
          {
            resultcode = 12;
            resulttekst = "Order line " + id + ": Invalid btw";
            break;
          }
          oRs_btw.Close();
        }
        else if (btw == "")
          mat_fields.push({ dbs: "fin_btwtabelwaarde_key", typ: "key", val: -1, track: L("lcl_mld_opdr_btw"), foreign: "fin_btwtabelwaarde" });

        var materiaal_key = oRs("mld_opdr_materiaal_key").Value;
        mat_key_list.push(materiaal_key);
        var matUpd = buildTrackingUpdate("mld_opdr_materiaal", "mld_opdr_materiaal_key = " + materiaal_key, mat_fields, { noValidateToken: true });

        // Zijn er velden die gewijzigd moeten worden? Als matUpd null is, dan waren alle savewhen condities false en hoeft er niets geupdate te worden.
        if (matUpd && matUpd.sql && matUpd.trackarray.length) { // Als trackarray.length === 0 dan is er effectief niets aangepast (we tracken alles)
          Oracle.Execute(matUpd.sql);
          mat_tracking_arr.push(L("lcl_ord_is_upd_material").format(id) + "\n" + matUpd.trackarray.join("\n"));
        }
      }
    }
    oRs.Close();

    // 1) Nieuwe materiaalbedrag in de opdracht opslaan.
    // 2) De totaalwaarde opnieuw berekenen want het materiaalbedrag is aangepast.
    //    Dan hebben we het correctie bedrag ook nodig. Dit correctie bedrag moet hetzelfde blijven.
    var correctie = mld_opdr.corr_cost;
    var sql_total = "UPDATE mld_opdr"
                  + "   SET mld_opdr_materiaal = (SELECT SUM(mld_opdr_materiaal_prijs)"
                  + "                               FROM mld_opdr_materiaal"
                  + "                              WHERE mld_opdr_key = " + opdr_key + ")"
                  + "     , mld_opdr_kosten = ((COALESCE(mld_opdr_uren, 0) * COALESCE(mld_opdr_uurloon, 0)) +"
                  + "                          COALESCE((SELECT SUM(mld_opdr_materiaal_prijs)"
                  + "                                      FROM mld_opdr_materiaal"
                  + "                                     WHERE mld_opdr_key = " + opdr_key + "), 0)) +"
                  + "                      " + correctie
                  + " WHERE mld_opdr_key = " + opdr_key
    Oracle.Execute(sql_total);
  }

  if (mat_tracking_arr.length)
  { // Er zijn wijzigingen.
    tracking_key = mld.trackopdrachtupdate(opdr_key, L("lcl_ord_is_updatedbysoap") + "\n" + mat_tracking_arr.join("\n"));
  }

  if (mat_key_list.length) {
    var sql_del = "DELETE mld_opdr_materiaal"
                + " WHERE mld_opdr_key = " + opdr_key
                + "   AND mld_opdr_materiaal_key NOT IN (" + mat_key_list.join(",") + ")";
    Oracle.Execute(sql_del);
  }

  return tracking_key;
}

xmlResp.appendChild(FCLTElement);

if (API.apidata.errorhandling == 1 && user.errorhandling() == 1)
{
  if (resultcode > 0) // -1 en 0 zijn 'goed'
  {
    Response.Status = '400 Bad Request';
  }
}

Response.ContentType = "text/xml";
if (API.apidata.stylesheet)
{ // Niet super efficient dat we eerst naar tekstuele xml gaan maar ach...
  STR2Stream(xmlResp.xml, API.apidata.stylesheet, Response, {});
}
else
{
  Response.Write(xmlResp.xml)
}

ASPPAGE_END();
%>
