<% /*
    $Revision$
    $Id$

    File:           model_invoices.inc

    Description:    Factuur model.
    Parameters:
    Context:

    Notes:          TODO: definitieve naamgeving verifieren
                    TODO: autorisaties voor scope=fo|bo

                    We had a discussion. The references (order, contract, purchaseorder) are now present by their foreign nodes.
                    We ourselves want the creditor of the invoice in the result, it is logically a property of an invoice
                    which we technically do not store with the invoice. Formally the creditor should be obtained by a subsequent
                    call to the corresponing API. Alternatively we could add those 3 optional nodes with an include option
                    which is merely designed for 1:n relations, not n:1 like this one.
                    We do not want to slide down from our purest REST forms (yet) so we made the compromise of these 2 readonly fields.
                    Let's see how it holds.
*/
%>
<!-- #include file="../Shared/discxalg3d.inc"-->
<!-- #include file="../fin/fin.inc" -->
<!-- #include file="./model_invoicelines.inc"-->
<!-- #include file="./model_custom_fields.inc"-->
<!-- #include file="./model_fac_tracking.inc"-->
<!-- #include file="./model_fin_kenmerk.inc"-->

<%
function model_invoices(fin_key, params)
{
    params = params || {};
    this.module = "FIN";
    this.table = "fin_factuur";
    this.primary = "fin_factuur_key";
    this.records_name = "invoices";
    this.record_name = "invoice";
    this.soft_delete = "fin_factuur_verwijder";

              // Het interne factuurnr is de id, het externe nummer is name
    this.fields =
            {"id"              : { dbs: "fin_factuur_key",            typ: "key" },
             "invoicedate"     : { dbs: "fin_factuur_datum",          typ: "datetime",                               track: true, label: L("lcl_fin_findate"),           filter: "range" },
/*readonly*/ "creditor"        : { dbs: "prs_bedrijf_key",            typ: "key",     foreign: "prs_bedrijf",        track: true, label: L("lcl_ord_company_uit"),
                                   sql: "COALESCE(mld_opdr.mld_uitvoerende_keys, bes_bestelopdr.prs_bedrijf_key, cnt_contract.cnt_prs_bedrijf_key)" },
/*readonly*/ "referencetype"   : { dbs: "dummy",                      typ: "varchar",
                                   sql: "DECODE(fin_factuur.mld_opdr_key, NULL, DECODE(fin_factuur.bes_bestelopdr_key, NULL, DECODE(fin_factuur.cnt_contract_key, NULL, '?', 'C'), 'B'), 'O')" },
/*readonly*/ "accountholder"   : { dbs: "prs_perslid_key",            typ: "key",     foreign: "prs_perslid",        track: true, label: L("lcl_prs_budgethouder"),
                                   sql: "fin.getfiatteur(fin_factuur.fin_factuur_key)" },
             "name"            : { dbs: "fin_factuur_nr",             typ: "varchar",                                track: true, label: L("lcl_fin_invoice_nr_extern") },
             "order"           : { dbs: "mld_opdr_key",               typ: "key",     foreign: "mld_opdr"},
             "contract"        : { dbs: "cnt_contract_key",           typ: "key",     foreign: "cnt_contract"},
             "purchaseorder"   : { dbs: "bes_bestelopdr_key",         typ: "key",     foreign: "bes_bestelopdr"},
             "total"           : { dbs: "fin_factuur_totaal",         typ: "float",   iscurrency: true,              track: true, label: L("lcl_fin_totaal_bedrag") },
             "vat"             : { dbs: "fin_factuur_totaal_btw",     typ: "float",   iscurrency: true,              track: true, label: L("lcl_fin_totaal_bedrag_btw") },
             "blockedaccount"  : { dbs: "fin_factuur_gbedrag",        typ: "float",   iscurrency: true,              track: true, label: L("lcl_fin_gbedrag") },
             "status"          : { dbs: "fin_factuur_statuses_key",   typ: "key",     foreign: fin.getfinstatustext, track: true, label: L("lcl_fin_fin_status")},
             "accountingperiod": { dbs: "fin_factuur_boekmaand",      typ: "varchar",                                track: true, label: L("lcl_fin_divide_period"),                     multiedit: true },
             "debiteur_nr"     : { dbs: "fin_factuur_debiteur_nr",    typ: "varchar",                                track: true, label: L("lcl_fin_debtor_nr"),         filter: "like", multiedit: true },
             "costtype"        : { dbs: "prs_kostensoort_key",        typ: "key",     foreign: "prs_kostensoort",    track: true, label: L("lcl_shared_charge_type"),                    multiedit: true },
             "contact"         : { dbs: "prs_perslid_key_user",       typ: "key",     foreign: "prs_perslid",        track: true, label: L("lcl_mld_name"),                              multiedit: true },
             "validater"       : { dbs: "prs_perslid_key_goedkeur",   typ: "number",  foreign: "prs_perslid",        track: true, label: L("lcl_fin_goedkeurder") },
             "source"          : { dbs: "fin_factuur_bron",           typ: "number",                                 track: true, label: L("lcl_fin_invoice_source") },
             "advice"          : { dbs: "fin_factuur_advies",         typ: "date",                                   track: true, label: L("lcl_fin_adviesdatum") },
             "flag"            : { dbs: "fin_factuur_flag",           typ: "number",                                 track: true, label: L("lcl_fin_flags") },
             "contractplace"   : { dbs: "cnt_contract_plaats_key",    typ: "number",                                 track: true, label: L("lcl_fin_location_scope") },
             "remark"          : { dbs: "fin_factuur_opmerking",      typ: "varchar",                                track: true, label: L("lcl_fin_remark"),            filter: "like" },
             "remarkreject"    : { dbs: "fin_factuur_opmerking_afw",  typ: "varchar",                                track: true, label: L("lcl_fin_remark_reject"),     filter: "like" },
             "externnr"        : { dbs: "fin_factuur_externnr",       typ: "varchar",                                             label: L("extern_nr"),                 readonly: !(user.has("WEB_FACTAB") || user.has("WEB_FACXNR")) },
             "externsyncdate"  : { dbs: "fin_factuur_externsyncdate", typ: "datetime",                                            label: L("extern_syncdate"),           readonly: !(user.has("WEB_FACTAB") || user.has("WEB_FACXNR")) },
             "contractplace"   : { dbs: "bes_bestellevr_key",         typ: "number",                                 track: true, label: L("lcl_bes_bestellevr_pakbon") }
            };

    this.includes = {
        "invoicelines": {
            model: new model_invoicelines(),
            joinfield: "invoice",
            enable_update: true
        },
        "custom_fields" : {
            model: new model_custom_fields(this, new model_fin_kenmerk("F", { internal: true }), { pNiveau: "F", readman: true, readuse: true }),
            joinfield: "flexparentkey",
            enable_update: true
        },
        "tracking": {
            model: new model_tracking(["factuur"]),
            joinfield: "trackingrefkey"
        }
    };

    function _pre_analyze_fields(params, jsondata) /* analyseer inkomende jsondata, common voor PUT en POST */
    {
        // De factuurbedragen worden berekend uit de factuurregels. Eventuele waarden die hier gezet zijn negeren.
        if (params.method != "POST")
        {
          delete jsondata.total;
          delete jsondata.vat;
        }

        if (jsondata.status)
        { // Er is een status meegegeven.
          jsondata.status = typeof jsondata.status == "object"? jsondata.status.id : jsondata.status;
        }
        // Status van 3 (foute import) moet bij saven op 2 worden gezet.
        // Status 6 (Akkoord) moet bij saven op 2 (Ingevoerd) worden gezet. Factuur dient indien nodig weer gefiatteerd te worden.
        if (jsondata.status == 3 || jsondata.status == 6)
        {
          jsondata.status = 2;
        }
    };

    function _analyze_fields(dbfields, params, jsondata) /* analyseer inkomende data, common voor PUT en POST */
    {
        if (jsondata.purchaseorder_name && !dbfields.purchaseorder)
        {
            var sql = "SELECT bes_bestelopdr_key purchaseorder_key"
                    + "  FROM bes_bestelopdr"
                    + " WHERE bes_bestelopdr_id = "+ safe.quoted_sql(jsondata.purchaseorder_name)
                    + " ORDER BY bes_bestelopdr_key DESC";
            var oRs = Oracle.Execute(sql);
            if (!oRs.eof)
            {
                dbfields["purchaseorder"] = { "dbs": "bes_bestelopdr_key"
                                            , "typ": "key"
                                            , "foreign": "bes_bestelopdr"
                                            , "val" : oRs("purchaseorder_key").Value
                                            };
                jsondata.purchaseorder = oRs("purchaseorder_key").Value;
            }
            oRs.Close();
        }
    };

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

    this.REST_GET = function _GET(params)
    {
        var this_fin = fin.func_enabled_factuur(params.filter.id || -1);
        user.auth_required_or_abort(this_fin.canReadAny);

        var query = api2.sqlfields(params, this );
        query.wheres.push("fin_factuur_verwijder IS NULL");

        if (/* self */ 0)
        {
            query.wheres.push("prs_perslid_key_user=" + user_key);
        }
        else
        {
            // TODO: disc3d conditie voor de geldendende autfunction toevoegen
        }

        query.tables.push("mld_opdr");
        query.wheres.push("fin_factuur.mld_opdr_key = mld_opdr.mld_opdr_key(+)");
        query.tables.push("bes_bestelopdr");
        query.wheres.push("fin_factuur.bes_bestelopdr_key = bes_bestelopdr.bes_bestelopdr_key(+)");
        query.tables.push("cnt_contract");
        query.wheres.push("fin_factuur.cnt_contract_key = cnt_contract.cnt_contract_key(+)");

        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 fin_factuur_key";
        if (query.orderbys.length)
            sql += ", " + query.orderbys.join(", ");

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

        return json;
    };

    this.REST_PUT = function (params, jsondata, the_key) /* update invoice */
    {
        var fin_key = the_key;
        var this_fin = fin.func_enabled_factuur(fin_key);

        user.auth_required_or_abort(this_fin.canChange);  // Geen wijzigingen toestaan bij onvoldoende rechten.

        _pre_analyze_fields(params, jsondata);

        //if ("filter" in params && "mode" in params.filter)
        //  this_fin.canFlexChange = this_fin.canReadAny && params.filter.mode == "attachment"; // Deze mag altijd
        // Geen wijzigingen toestaan bij onvoldoende rechten.
        user.auth_required_or_abort(this_fin.canChange ||
                                    (this_fin.canReject && jsondata.status == 1) ||                              // Afwijzen.
                                    ((this_fin.canUnReject || this_fin.canUnapprove) && jsondata.status == 2) || // On-Afwijzen, On-Fiatteren of On-Goedkeuren.
                                    (this_fin.canAccept && jsondata.status == 5) ||                              // Fiatteren.
                                    (this_fin.canGoedkeur && jsondata.status == 6))                              // Goedkeuren.


        if (jsondata.status == 1 && this_mld.canReject)
        { // Afwijzen.
          var fields = [ { dbs: "fin_factuur_opmerking_afw", typ: "varchar", val: jsondata.remark, track: L("lcl_fin_remark_reject"), len: 4000} ];

          var finUpd = buildTrackingUpdate("fin_factuur", "fin_factuur_key = " + fin_key, fields);
          oRs = Oracle.Execute(finUpd.sql);
          if (finUpd.trackarray.length)
          {
            fin.trackfactuurupdate(fin_key, L("lcl_fin_is_finupdtrack").format(fin_key) + "\n" + finUpd.trackarray.join("\n"));
          }

          // Status naar Afgewezen(1).
          var sql = "UPDATE fin_factuur"
                  + " SET fin_factuur_statuses_key = 1"
                  + " WHERE fin_factuur_key = " + fin_key
                  + " AND (mld_opdr_key IS NOT NULL OR cnt_contract_key IS NOT NULL OR bes_bestelopdr_key IS NOT NULL)";
          Oracle.Execute(sql);
          shared.trackaction(this_fin.canAccept? "FINFNO" : "FINGNO", fin_key);

          var pResult = new HookResult();
          // Klantspecifieke check functie (hookfunction) voor de invoer.
          if (!custfunc.fin_postsave(fin_key, pResult))
          {
             abort_with_warning(pResult.errmsg);
          }
        }
        else if (jsondata.status == 2 && (this_fin.canUnReject || this_fin.canUnapprove))
        { // On-Afwijzen, On-Fiatteren of On-Goedkeuren.
          // Huidige status opvragen.
          sql = "SELECT fin_factuur_statuses_key"
              + "  FROM fin_factuur"
              + " WHERE fin_factuur_key = " + fin_key;
          oRs = Oracle.Execute(sql);
          var fin_status_old = oRs("fin_factuur_statuses_key").Value;
          oRs.Close();

          var fields = [ { dbs: "fin_factuur_opmerking_afw", typ: "varchar", val: jsondata.remark, track: L("lcl_fin_remark_reject"), len: 4000} ];

          var finUpd = buildTrackingUpdate("fin_factuur", "fin_factuur_key = " + fin_key, fields);
          oRs = Oracle.Execute(finUpd.sql);
          if (finUpd.trackarray.length)
          {
            fin.trackfactuurupdate(fin_key, L("lcl_fin_is_finupdtrack").format(fin_key) + "\n" + finUpd.trackarray.join("\n"));
          }

          // Status terugzetten naar Ingevoerd(2).
          sql = "UPDATE fin_factuur"
              + " SET fin_factuur_statuses_key = 2"
              + " WHERE fin_factuur_key = " + fin_key
              + " AND (mld_opdr_key IS NOT NULL OR cnt_contract_key IS NOT NULL OR bes_bestelopdr_key IS NOT NULL)";
          Oracle.Execute(sql);

          shared.trackaction((fin_status_old == 5? "FINGUN" : "FINFUN"), fin_key); // Vanuit status Afgewezen(1) en Akkoord(6) FINFUN en vanuit status Ter Goedkeuring(5) FINGUN.
        }
        else if (jsondata.status == 5 && this_fin.canAccept)
        { // Fiatteren.
          var fin_enable_goedkeuren = S("fin_enable_goedkeuren");
          var fin_status_new = fin_enable_goedkeuren != 0
                               ? "CASE WHEN prs_perslid_key_goedkeur IS NOT NULL THEN 5 ELSE 6 END"
                               : "6";
          sql = "UPDATE fin_factuur"
              + " SET fin_factuur_statuses_key = " + fin_status_new
              + " WHERE fin_factuur_key = " +  fin_key
              + " AND (mld_opdr_key IS NOT NULL OR cnt_contract_key IS NOT NULL OR bes_bestelopdr_key IS NOT NULL)";
          Oracle.Execute(sql);

          shared.trackaction("FINFOK", fin_key);

          var sql = "SELECT prs_perslid_key_goedkeur"
                  + " FROM fin_factuur f"
                  + " WHERE fin_factuur_key = " + fin_key;
          oRs = Oracle.Execute(sql);
          if (fin_enable_goedkeuren != 0 && oRs("prs_perslid_key_goedkeur").Value > 0)
          { // Factuur is naar status Ter goedkeuring(5) gegaan.
            // Notificatie aan de goedkeurder.
            fin.sendNeedApprovalNotification(fin_key, oRs("prs_perslid_key_goedkeur").Value);
          }
          //else // Factuur is naar status Akkoord(6) gegaan.
          oRs.Close();
        }
        else if (jsondata.status == 6 && this_fin.canGoedkeur)
        { // Fiatteren.
          var sql = "UPDATE fin_factuur"
                  + " SET fin_factuur_statuses_key = 6"
                  + " WHERE fin_factuur_key = " + fin_key
                  + " AND (mld_opdr_key IS NOT NULL OR cnt_contract_key IS NOT NULL OR bes_bestelopdr_key IS NOT NULL)";
          Oracle.Execute(sql);

          shared.trackaction("FINGOE", fin_key);

          var pResult = new HookResult();
          // Klantspecifieke check functie (hookfunction) voor de invoer
          if (!custfunc.fin_postsave(fin_key, pResult))
          {
             abort_with_warning(pResult.errmsg);
          }
        }
        else
        {
          var dbfields = api2.update_fields(params, this, jsondata); // Build updater
          _analyze_fields(dbfields, params, jsondata);
          _validate_fields(dbfields, params, jsondata);

          var wheres = [" fin_factuur_key = " + fin_key];
          var finUpd = buildTrackingUpdate("fin_factuur", wheres.join(" AND " ), dbfields, { noValidateToken: true });
          var err = Oracle.Execute(finUpd.sql, true);
          if (err.friendlyMsg)
              abort_with_warning(err.friendlyMsg);

          var fintrack = api2.process_includes(params, this, jsondata, the_key);

          // update nog tracken
          if (fin_key > 0)
          {
            fin.trackfactuurupdate(fin_key, L("lcl_fin_is_finupdtrack").format(fin_key) + (finUpd.trackarray.length > 0? "\n" : "") + finUpd.trackarray.join("\n"));
          };
        }

        return { key: fin_key };
    };

    this.REST_POST = function (params, jsondata) /* new invoice */
    {
        var this_fin = fin.func_enabled_factuur(-1);
        user.auth_required_or_abort(this_fin.canChange);  // Geen wijzigingen toestaan bij onvoldoende rechten.

        params.isNew = true; // negeer eventuele bestaande keys

        _pre_analyze_fields(params, jsondata);
        var dbfields = api2.update_fields(params, this, jsondata); // Build updater
        _analyze_fields(dbfields, params, jsondata);
        _validate_fields(dbfields, params, jsondata);

        dbfields["id"] = { dbs: "fin_factuur_key",         typ: "key",   seq: "fin_s_fin_factuur_key" };
        var finIns = buildInsert("fin_factuur", dbfields, { noValidateToken: true} );
        var factuur_key = finIns.sequences["fin_factuur_key"];

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

        // factuurregels toevoegen
        var fintrack = api2.process_includes(params, this, jsondata, factuur_key);

        shared.trackaction("FINNEW", factuur_key);

        // factuurmatching
        var sql_fm = "BEGIN fin.autoapprovefactuur(" + factuur_key + "); END;";
        Oracle.Execute(sql_fm);

        return { key: factuur_key };
    };

    this.REST_DELETE = function (params, the_key) /* delete invoice */
    {
        var factuur_key = the_key;

        var this_fin = fin.func_enabled_factuur(factuur_key);
        user.auth_required_or_abort(this_fin.canDelete);
        if (this_fin.canDelete) // Dubbel op: bij canDelete=false komt hij hier niet meer.
        {
            // Verwijderdatum van de factuur zetten en niet fysiek verwijderen
            var sql = "UPDATE fin_factuur"
                    + "   SET fin_factuur_verwijder = SYSDATE"
                    + " WHERE fin_factuur_verwijder IS NULL"
                    + "   AND fin_factuur_key = " + factuur_key;
            oRs = Oracle.Execute(sql);
        }
    };

    if (fin_key > 0)
    {
        params.filter = params.filter || {};
        params.filter.id = fin_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];
    }
}
%>