1696 lines
74 KiB
PHP
1696 lines
74 KiB
PHP
<% /*
|
||
$Revision$
|
||
$Id$
|
||
|
||
File: api2.inc
|
||
Description: Functies voor manipuleren van de modellen
|
||
Notes: Hier wordt weinig met de 'buitenwereld' gecommuniceerd,
|
||
dat gebeurt voornamelijk in api2_rest.inc
|
||
|
||
Status: Nog TODO's wegwerken
|
||
|
||
*/
|
||
%>
|
||
<!-- #include file="../Shared/save2db.inc" -->
|
||
<%
|
||
api2 = {
|
||
form2JSONdata: function _form2JSONdata(model, params, formfields) // Maak een jsondata-object voor gebruik vanuit model_xxx.inc
|
||
{
|
||
var jsondata = {id: params.filter.id};
|
||
|
||
// Form parameters van gewone velden.
|
||
for (i=0; i<formfields.length; i++)
|
||
{
|
||
// Voeg een nieuwe combinatie naam:waarde toe aan het jsondata object als er een frm of val bekend is.
|
||
var val;
|
||
if (formfields[i].frm)
|
||
{
|
||
var fld = model.fields[formfields[i].name];
|
||
var typ = model.fields[formfields[i].name].typ;
|
||
|
||
// Haal waarde uit de formparameters.
|
||
if (typ == "check" || typ == "check0")
|
||
{
|
||
if (Request.Form("has_" + formfields[i].frm).count == 0)
|
||
continue; // niet aanwezig
|
||
}
|
||
else
|
||
{
|
||
if (Request.Form(formfields[i].frm).count == 0)
|
||
continue; // niet aanwezig
|
||
}
|
||
|
||
switch (typ)
|
||
{
|
||
case "float":
|
||
case "currency":
|
||
val = getFParamFloat(formfields[i].frm, null);
|
||
break;
|
||
case "key":
|
||
case "number":
|
||
if (fld.bits)
|
||
{
|
||
val = 0;
|
||
for (var b=0; b < fld.bits.length; b++)
|
||
{
|
||
var bit = fld.bits[b];
|
||
if (bit.typ == "check")
|
||
{
|
||
for (var r=0; r < bit.radios.length; r++)
|
||
{
|
||
var radio = bit.radios[r];
|
||
var invert = radio.invert;
|
||
// LET OP: Als de bit readonly is wordt er wel een form-parameter meegegeven met waarde "on" of leeg,
|
||
// om de oude waarde te kunnen bewaren.
|
||
// Als de bit editable is er alleen de form-parameter aanwezig als het veld aangevinkt is.
|
||
if (radio.readonly)
|
||
val += ((Request.Form(radio.name)=="on")?(invert?0:radio.mask):(invert?radio.mask:0));
|
||
else
|
||
val += ((Request.Form(radio.name).count==1)?(invert?0:radio.mask):(invert?radio.mask:0));
|
||
}
|
||
}
|
||
else if (bit.typ == "radio")
|
||
{
|
||
val += parseInt(Request.Form(bit.name));
|
||
}
|
||
else if (bit.typ == "LOV")
|
||
{
|
||
val += getFParamInt(bit.name, 0);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (params.multiadd == formfields[i].frm)
|
||
val = getFParamIntArray(formfields[i].frm, null);
|
||
else
|
||
val = getFParamInt(formfields[i].frm, null);
|
||
}
|
||
break;
|
||
case "check":
|
||
case "check0":
|
||
var invert = model.fields[formfields[i].name].invert;
|
||
val = (Request.Form(formfields[i].frm).count==1)?(invert?0:1):(invert?1:0);
|
||
break;
|
||
case "date":
|
||
case "datetime":
|
||
val = getFParamDate(formfields[i].frm, null);
|
||
break;
|
||
case "memo":
|
||
case "varchar":
|
||
val = getFParam(formfields[i].frm, null);
|
||
break;
|
||
default:
|
||
{
|
||
__DoLog(model.fields[formfields[i].name].typ);
|
||
INTERN_ERROR_UNKNOWN_TYPE;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
val = formfields[i].val;
|
||
//
|
||
jsondata[formfields[i].name] = val;
|
||
}
|
||
return jsondata;
|
||
},
|
||
|
||
// Verwerk filtervelden die in de url zijn meegegeven
|
||
sqlfilter: function _sqlfilter(params, model)
|
||
{
|
||
var wheres = [];
|
||
if (params.filter)
|
||
{
|
||
for (var fld in model.fields)
|
||
{
|
||
if (fld.substring(0,1) == "_")
|
||
continue;
|
||
var field = model.fields[fld];
|
||
|
||
if ("filter" in field)
|
||
var filter = field.filter
|
||
else // default filtertypes
|
||
{
|
||
switch (field.typ)
|
||
{
|
||
case "key":
|
||
filter = "exact";
|
||
break;
|
||
case "float":
|
||
case "currency":
|
||
case "number":
|
||
filter = "range";
|
||
break;
|
||
case "check":
|
||
case "check0":
|
||
filter = "exact";
|
||
break;
|
||
case "varchar":
|
||
case "memo":
|
||
case "html":
|
||
filter = "like";
|
||
break;
|
||
case "date":
|
||
case "datetime":
|
||
filter = "range";
|
||
break;
|
||
default:
|
||
if (fld in params.filter) // Je probeert er wel op te filteren?
|
||
{
|
||
__DoLog(field);
|
||
__DoLog(params.filter);
|
||
UNKNOWN_FILTER_TYPE;
|
||
}
|
||
}
|
||
}
|
||
|
||
// We kijken of dit model-veld in de filterparameters voorkomt. Voor ranges kunnen prefixes in gebruik zijn
|
||
var filterval = null;
|
||
if (fld in params.filter || "start_" + fld in params.filter || "end_" + fld in params.filter)
|
||
{
|
||
// filterval is de meegegeven filterwaarde voor dit veld
|
||
if (fld in params.filter)
|
||
{
|
||
filterval = params.filter[fld];
|
||
if (filterval === "" || filterval === null)
|
||
continue;
|
||
if (filterval < 0 && field.typ == "key")
|
||
continue;
|
||
}
|
||
|
||
// Voor ranges komt de naam (misschien) niet letterlijk voor, maar (mogelijk) met start_ of end_ ervoor
|
||
// meerdere filters voor 1 veld dus wellicht
|
||
if (filter == "range") {
|
||
var filterval1 = params.filter["start_" + fld];
|
||
var filterval2 = params.filter["end_" + fld];
|
||
// Als er maar 1 filterwaarde is, dan gedraagt zich die als start_
|
||
if (filterval && !filterval1)
|
||
filterval1 = filterval;
|
||
if ((filterval1 === "" || filterval1 === null) && (filterval2 === "" || filterval2 === null))
|
||
continue;
|
||
}
|
||
// Nu is ook voor ranges alles weer normaal
|
||
var clause;
|
||
var operand = " = ";
|
||
switch (field.typ)
|
||
{
|
||
case "key":
|
||
if (!filterval)
|
||
continue;
|
||
if (filterval instanceof Array)
|
||
{
|
||
safe_val = filterval.join(", ");
|
||
if (typeof filter != 'function')
|
||
safe_val = "(" + safe_val + ")";
|
||
operand = " IN ";
|
||
}
|
||
else if (String(filterval).indexOf(",") != -1) // let op: bij buildings/1234.json is id al numeriek gemaakt
|
||
// NB: index=-1 als het geen array is.
|
||
{
|
||
var arr = params.filter[fld].split(",");
|
||
safe_val = safe.int_array(arr).join(",");
|
||
if (typeof filter != 'function')
|
||
safe_val = "(" + safe_val + ")";
|
||
operand = " IN ";
|
||
}
|
||
else if (String(filterval).substr(0,1) == '~'
|
||
&& field.foreign
|
||
&& typeof field.foreign == 'string'
|
||
) // Als je een foreign filter met ~ begint dan filtert hij op de tekst
|
||
{
|
||
operand = " IN ";
|
||
var foreign = foreignKeyTable(field.foreign);
|
||
if (!foreign)
|
||
MISSING_FOREIGN;
|
||
var fieldname = (foreign.name||foreign.desc);
|
||
var foreignsql = "SELECT " + foreign.key
|
||
+ " FROM " + foreign.tbl + " " + (foreign.alias||"xx")
|
||
+ " WHERE ";
|
||
if (foreign.where)
|
||
foreignsql += foreign.where + " AND ";
|
||
foreignsql += "UPPER(" + (foreign.alias||"xx") + "." + fieldname + ") LIKE " + safe.quoted_sql_wild("%"+String(filterval).substr(1) + "%");
|
||
safe_val = foreignsql;
|
||
if (typeof filter != 'function')
|
||
safe_val = "(" + safe_val + ")";
|
||
}
|
||
else
|
||
{
|
||
if (String(filterval).toLowerCase() == "self")
|
||
var safe_val = user_key;
|
||
else
|
||
var safe_val = parseInt(filterval, 10);
|
||
if (isNaN(safe_val))
|
||
{
|
||
return ["0=1"];
|
||
}
|
||
}
|
||
break;
|
||
case "float":
|
||
case "currency":
|
||
var safe_val = parseFloat(filterval);
|
||
if (filter == "range")
|
||
{
|
||
if (filterval1) {
|
||
var safe_val1 = parseFloat(filterval1);
|
||
}
|
||
if (filterval2) {
|
||
var safe_val2 = parseFloat(filterval2);
|
||
}
|
||
if (filterval1 && filterval2) {
|
||
operand = " BETWEEN ";
|
||
safe_val = safe_val1+" AND "+safe_val2;
|
||
} else if (filterval1) {
|
||
operand = " >= ";
|
||
safe_val = safe_val1;
|
||
} else if (filterval2) {
|
||
operand = " <= ";
|
||
safe_val = safe_val2;
|
||
}
|
||
}
|
||
break;
|
||
case "number":
|
||
var safe_val = parseInt(filterval, 10);
|
||
if (filterval && isNaN(safe_val))
|
||
{
|
||
return ["0=1"]; // niets gevonden
|
||
}
|
||
if (filter == "range")
|
||
{
|
||
if (filterval1) {
|
||
var safe_val1 = parseInt(filterval1, 10);
|
||
}
|
||
if (filterval2) {
|
||
var safe_val2 = parseInt(filterval2, 10);
|
||
}
|
||
if (filterval1 && filterval2) {
|
||
operand = " BETWEEN ";
|
||
safe_val = safe_val1+" AND "+safe_val2;
|
||
} else if (filterval1) {
|
||
operand = " >= ";
|
||
safe_val = safe_val1;
|
||
} else if (filterval2) {
|
||
operand = " <= ";
|
||
safe_val = safe_val2;
|
||
}
|
||
}
|
||
break;
|
||
case "check":
|
||
case "check0":
|
||
if (filterval == "on")
|
||
var safe_val = 1;
|
||
else if (filterval == "off")
|
||
var safe_val = 0;
|
||
else
|
||
var safe_val = parseInt(filterval, 10);
|
||
if (isNaN(safe_val))
|
||
{
|
||
return ["0=1"]; // niets gevonden
|
||
}
|
||
break;
|
||
case "varchar": // Als fld.match(/^fclt_d_/) dan is het een old-style rapport filterveld
|
||
case "memo":
|
||
case "html":
|
||
if (filterval instanceof Array)
|
||
{
|
||
safe_val = safe.quoted_sql_join(filterval);
|
||
if (typeof filter != 'function')
|
||
safe_val = "(" + safe_val + ")";
|
||
operand = " IN ";
|
||
}
|
||
else switch (filter)
|
||
{
|
||
case "exact":
|
||
if (field.autolike) // Doen we bij rapportages
|
||
{
|
||
filter = "like"; // Lijkt tegenstrijdig voor 'exact' maar we staan nu wel
|
||
// toe dat je zelf *, % en _ gebruikt
|
||
var safe_val = safe.quoted_sql_wild(filterval);
|
||
}
|
||
else
|
||
{
|
||
if (field.caseinsensitive)
|
||
var safe_val = safe.quoted_sql_upper(filterval);
|
||
else
|
||
var safe_val = safe.quoted_sql(filterval);
|
||
}
|
||
break;
|
||
case "like":
|
||
var safe_val = safe.quoted_sql_wild("%"+filterval+"%");
|
||
break;
|
||
case "range":
|
||
var safe_val;
|
||
if (filterval1) {
|
||
if (fld.match(/^fclt_d_/))
|
||
safe_val1 = api2.toDate(filterval1).beginToSQL();
|
||
else
|
||
safe_val1 = safe.quoted_sql(filterval1);
|
||
}
|
||
if (filterval2) {
|
||
if (fld.match(/^fclt_d_/))
|
||
safe_val2 = api2.toDate(filterval2).endToSQL();
|
||
else
|
||
safe_val2 = safe.quoted_sql(filterval2);
|
||
}
|
||
if (filterval1 && filterval2) {
|
||
operand = " BETWEEN ";
|
||
safe_val = safe_val1+" AND "+safe_val2;
|
||
} else if (filterval1) {
|
||
operand = " >= ";
|
||
safe_val = safe_val1;
|
||
} else if (filterval2) {
|
||
operand = " <= ";
|
||
safe_val = safe_val2;
|
||
}
|
||
break;
|
||
}
|
||
break;
|
||
case "date": // onderscheid date en datetime?
|
||
case "datetime":
|
||
if (filter == "exact")
|
||
{
|
||
var safe_val = api2.toDate(filterval);
|
||
safe_val = safe_val.toSQL(); // Een exacte *tijd* component ondersteunen we niet.
|
||
operand = " = ";
|
||
}
|
||
else if (filter == "range")
|
||
{
|
||
var safe_val;
|
||
if (filterval1) {
|
||
var safe_val1 = api2.toDate(filterval1);
|
||
var withtime = (field.typ == "datetime" && api2.hasTime(safe_val1));
|
||
safe_val1 = withtime?safe_val1.toSQL(true):safe_val1.beginToSQL();
|
||
}
|
||
if (filterval2) {
|
||
var safe_val2 = api2.toDate(filterval2);
|
||
var withtime = (field.typ == "datetime" && api2.hasTime(safe_val2));
|
||
safe_val2 = withtime?safe_val2.toSQL(true):safe_val2.endToSQL();
|
||
}
|
||
if (filterval1 && filterval2) {
|
||
operand = " BETWEEN ";
|
||
safe_val = safe_val1+" AND "+safe_val2;
|
||
} else if (filterval1) {
|
||
operand = " >= ";
|
||
safe_val = safe_val1;
|
||
} else if (filterval2) {
|
||
operand = " <= ";
|
||
safe_val = safe_val2;
|
||
}
|
||
}
|
||
break;
|
||
default:
|
||
__DoLog(field.typ);
|
||
UNKNOWN_FILTER_TYPE;
|
||
}
|
||
var dbs = field.dbs;
|
||
if (model.aliasprefix)
|
||
dbs = model.aliasprefix + dbs;
|
||
if (dbs.indexOf(".") < 0)
|
||
dbs = model.table + "." + field.dbs;
|
||
if (field.sql && !field.keepdbsforfilter)
|
||
dbs = field.sql; /* dit werkt bv voor cnt-XD */
|
||
|
||
if (filter == "like")
|
||
{
|
||
if (operand != " IN ")
|
||
operand = " LIKE ";
|
||
if (field.typ == "varchar" || field.typ == "memo" || field.typ == "html")
|
||
{
|
||
if (field.islcl)
|
||
dbs = 'lcl.l({0}, 1)'.format(dbs);
|
||
field.caseinsensitive = true;
|
||
}
|
||
}
|
||
if (field.caseinsensitive)
|
||
{
|
||
dbs = "UPPER(" + dbs + ")";
|
||
safe_val = safe_val.toUpperCase();
|
||
}
|
||
if (filter == "range" && fld.match(/^fclt_d_/))
|
||
{
|
||
dbs = "TO_DATE(" + dbs + ",'dd-mm-yyyy')";
|
||
}
|
||
if (filter == "exact" && (field.typ == "date" || field.typ == "datetime"))
|
||
{
|
||
dbs = "TRUNC("+dbs+")";
|
||
}
|
||
|
||
if (fld == 'id') // Bij filtering op id negeren we *al het andere*
|
||
return [dbs + operand + safe_val];
|
||
|
||
// Als filter een functie is dan de functie aanroepen.
|
||
if (typeof filter == 'function')
|
||
wheres.push(filter(safe_val));
|
||
else
|
||
wheres.push(dbs + operand + safe_val);
|
||
}
|
||
}
|
||
if (params.filter.includefilter) // Bijvoorbeeld "rooms.location,configurations.res_opstelling_key"
|
||
{
|
||
var incarr = params.filter.includefilter.split(",");
|
||
for (var i = 0; i < incarr.length; i++)
|
||
{
|
||
var inc = incarr[i].split(".")[0];
|
||
var fld = incarr[i].split(".")[1];
|
||
var incmodel = model.includes[inc].model;
|
||
var newfilter = {};
|
||
newfilter[fld] = params.filter[fld];
|
||
var wheresinc = api2.sqlfilter({ filter: newfilter }, incmodel);
|
||
if (wheresinc.length)
|
||
{
|
||
// TODO: res_alg_ruimte heeft ook nog weer een foreign op het locatiekey veld....
|
||
wheresinc.push(incmodel.table + "." + incmodel.fields[model.includes[inc].joinfield].dbs + " = " + model.table + "." + model.fields.id.dbs);
|
||
var where = "EXISTS (SELECT 1 FROM {0} WHERE {1})".format(incmodel.table, wheresinc.join(" AND "))
|
||
wheres.push(where);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return wheres;
|
||
},
|
||
|
||
// Als er voor een veldnaam een alias moet worden gebruikt, doe het dan op deze manier.
|
||
sqlfield_alias: function _sqlfield_alias(model, fld)
|
||
{
|
||
var field = model.fields[fld];
|
||
return model.aliasprefix + (field.dbs == model.primary ? field.dbs : fld);
|
||
},
|
||
|
||
// Vult bij alle varchar en memo velden de len property in als dat nog niet is gedaan
|
||
setfieldlengths: function _setfieldlengths(model)
|
||
{
|
||
var dblengths = null;
|
||
for (var fld in model.fields)
|
||
{
|
||
if (fld.substring(0,1) == "_")
|
||
continue;
|
||
var field = model.fields[fld];
|
||
if (!field.dbs || field.hidden || field.len || field.readonly)
|
||
continue;
|
||
if (field.typ != "varchar" && field.typ != "memo")
|
||
continue;
|
||
if (!dblengths)
|
||
{
|
||
dblengths = {};
|
||
var sql = "SELECT column_name, data_length"
|
||
+ " FROM user_tab_columns"
|
||
+ " WHERE table_name = " + safe.quoted_sql_upper(model.table)
|
||
+ " AND data_type = 'VARCHAR2'";
|
||
var oRs = Oracle.Execute(sql);
|
||
while (!oRs.Eof)
|
||
{
|
||
dblengths[oRs("column_name").Value] = oRs("data_length").Value;
|
||
oRs.MoveNext();
|
||
}
|
||
oRs.Close()
|
||
__Log(dblengths);
|
||
}
|
||
field.len = dblengths[field.dbs.toUpperCase()];
|
||
}
|
||
},
|
||
|
||
// Bepaal bij een GET welke velden op te halen
|
||
sqlfields: function _sqlfields(params, model)
|
||
{
|
||
model.aliasprefix = model.aliasprefix || "";
|
||
var selects = [];
|
||
if ("fntablesql" in model)
|
||
model.tablesql = model.fntablesql();
|
||
|
||
var withs = {};
|
||
var tables = [ model.tablesql || model.table ];
|
||
var wheres = [];
|
||
var orderbys = [];
|
||
|
||
var name_cnt = 0;
|
||
|
||
if (model.includes && "tracking" in model.includes && params.include && inArray("lastchange", params.include))
|
||
{
|
||
// lastchange veld altijd toevoegen.
|
||
// Onafhankelijk van het feit of de include=tracking aanstaat
|
||
var sql = "SELECT MAX(fac_tracking_datum)"
|
||
+ " FROM fac_tracking ft, "
|
||
+ " fac_srtnotificatie fs"
|
||
+ " WHERE ft.fac_srtnotificatie_key = fs.fac_srtnotificatie_key"
|
||
+ " AND fs.fac_srtnotificatie_xmlnode IN (" + safe.quoted_sql_join(model.includes["tracking"].model.xmlnodes) + ") "
|
||
+ " AND fac_tracking_refkey = " + model.table + "." + model.primary;
|
||
model.fields["lastchange"] = { dbs: "lastchange", sql: "(" + sql + ")", typ: 'datetime', filter: "range" };
|
||
}
|
||
|
||
for (var fld in model.fields)
|
||
{
|
||
if (fld.substring(0,1) == "_")
|
||
continue;
|
||
var field = model.fields[fld];
|
||
if (field.hidden)
|
||
continue;
|
||
|
||
var dbs = field.dbs;
|
||
if (field.sql)
|
||
{
|
||
if (model.aliasprefix)
|
||
dbs = model.aliasprefix + fld;
|
||
selects.push(field.sql + " AS " + dbs);
|
||
}
|
||
else if (dbs)
|
||
{
|
||
// veldnamen moeten gelijk zijn bij het ophalen. Zie ook: sql2jsonval
|
||
if (dbs.indexOf(".") < 0)
|
||
{
|
||
dbs = model.table + "." + dbs;
|
||
if (field.translate && !params.for_edit)
|
||
dbs = lcl.xsql(dbs, model.table + "." + model.fields.id.dbs, null, true); // never autolcl
|
||
if (model.aliasprefix)
|
||
dbs += " AS " + api2.sqlfield_alias(model, fld);
|
||
else
|
||
if (field.translate && !params.for_edit)
|
||
dbs += " AS " + field.dbs;
|
||
}
|
||
if (!inArray(dbs, selects))
|
||
selects.push(dbs);
|
||
}
|
||
if (field.val instanceof Function)
|
||
continue;
|
||
|
||
|
||
if (field.foreign && typeof field.foreign != 'function') // de functions komen later
|
||
{
|
||
if (typeof field.foreign == 'string')
|
||
{
|
||
var foreign = foreignKeyTable(field.foreign);
|
||
if (!foreign)
|
||
MISSING_FOREIGN;
|
||
}
|
||
else
|
||
foreign = field.foreign;
|
||
var fieldname = (foreign.name||foreign.desc);
|
||
field.foreignsql = "SELECT " + fieldname
|
||
+ " FROM " + foreign.tbl + " " + (foreign.alias||"xx")
|
||
+ " WHERE ";
|
||
if (foreign.where)
|
||
field.foreignsql += foreign.where + " AND ";
|
||
field.foreignsql += (foreign.alias||"xx") + "." + foreign.key + " = " + (field.sql||(field.dbs.indexOf(".") < 0? model.table + ".":"") + field.dbs);
|
||
}
|
||
|
||
if (field.foreignsql)
|
||
{
|
||
name_cnt ++;
|
||
field._foreignname = "foreign_" + name_cnt; // Genereer een niet al te extreem lange naam
|
||
selects.push("(" + field.foreignsql + ") " + " AS " + model.aliasprefix + field._foreignname);
|
||
}
|
||
}
|
||
if (params.include && model.includes)
|
||
{
|
||
// Dubbelen er uit halen, dat geeft lelijke foutmeldingen en kan ontstaan
|
||
// als je include=visitors in de url meegeeft <20>n visitors in de body POST
|
||
var tmp = {};
|
||
for (var i in params.include) {
|
||
tmp[params.include[i]] = 1;
|
||
}
|
||
params.include = [];
|
||
for (var i in tmp) {
|
||
params.include.push(i);
|
||
}
|
||
var inccnt = 0;
|
||
for (var i in params.include)
|
||
{
|
||
if (params.include[i] == "lastchange") // special case
|
||
continue;
|
||
if (params.include[i] in model.includes)
|
||
{
|
||
var inc = model.includes[params.include[i]];
|
||
// Geneste includes verwijderen, we doen hooguit 1 diep
|
||
if ("model" in inc) // reservablerooms/include/occupations heeft geen model
|
||
delete inc.model.includes;
|
||
|
||
if (inc.model)
|
||
{
|
||
if (inc.single_only && !params.filter.id)
|
||
{
|
||
abort_with_warning("Include '{0}' only allowed with single '{1}'".format(params.include[i], model.record_name));
|
||
}
|
||
inccnt ++;
|
||
inc.model.aliasprefix = "I" + inccnt;
|
||
|
||
// Zorg dat de parent_key niet genoemd wordt bij de children. Dat is redundant
|
||
if (inc.joinfield in inc.model.fields)
|
||
inc.model.fields[inc.joinfield].hidden = true;
|
||
|
||
// Fabriceer een limited include tabel zodat bij fac_groep niet te veel
|
||
// personen uit fac_groeprechten getoond worden
|
||
if (inc.limit)
|
||
{
|
||
withs["L_" + inc.model.table] =
|
||
"(SELECT " + inc.model.table + ".*,"
|
||
+ " ROW_NUMBER ()"
|
||
+ " OVER (PARTITION BY " + inc.model.fields[inc.joinfield].dbs
|
||
+ " ORDER BY " + inc.limit + " ASC)"
|
||
+ " AS rn"
|
||
+ " FROM " + inc.model.table + ")";
|
||
inc.model.table = "L_" + inc.model.table; // LET OP: We vervangen dus de tabel tijdelijk
|
||
wheres.push(inc.model.table + ".rn <= 6");
|
||
}
|
||
|
||
var incquery = api2.sqlfields(params, inc.model);
|
||
|
||
selects = selects.concat (incquery.selects);
|
||
if (inc.model.gparams && inc.model.gparams.GET.wheres)
|
||
{
|
||
var tablesql = "(SELECT * FROM {0} WHERE {1}) {2}".format(incquery.tables.join(", "),
|
||
inc.model.gparams.GET.wheres.join(" AND "),
|
||
inc.model.table);
|
||
tables.push(tablesql);
|
||
}
|
||
else
|
||
tables = tables.concat (incquery.tables);
|
||
wheres = wheres.concat (incquery.wheres);
|
||
|
||
if (inc.model.gparams && inc.model.gparams.GET.tables)
|
||
tables = tables.concat(inc.model.gparams.GET.tables);
|
||
|
||
|
||
// Bij includes tonen we ook verwijderde records indien gewenst.
|
||
if (inc.model.soft_delete &&
|
||
( (params.filter.has_show_deleted && params.filter.show_deleted != "on") || !params.filter.has_show_deleted)
|
||
)
|
||
wheres.push(inc.model.soft_delete + " IS NULL");
|
||
|
||
// Bij includes ook de vervallen records tonen indien gewenst.
|
||
if (inc.model.soft_expire &&
|
||
( (params.filter.has_show_expired && params.filter.show_expired != "on") || !params.filter.has_show_expired)
|
||
)
|
||
wheres.push("(" + model.soft_expire + " IS NULL OR " + model.soft_expire + " > SYSDATE)");
|
||
|
||
if (inc.joinfunction)
|
||
{
|
||
var where = inc.joinfunction(params);
|
||
if (typeof where == "string")
|
||
wheres.push (where);
|
||
else
|
||
{
|
||
tables = tables.concat (where.tables);
|
||
wheres.push (where.where);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (params.orderby)
|
||
{
|
||
for (var i = 0; i < params.orderby.length; i++)
|
||
{
|
||
var field = inc.model.fields[params.orderby[i]];
|
||
if (field)
|
||
orderbys.push(inc.model.table + "." + field.dbs);
|
||
}
|
||
}
|
||
// In orderbys worden de include primary keys verzameld om te sorteren
|
||
// We bieden daar geen garantie op maar tijdens testen is deterministisch
|
||
// gedrag wel fijn.
|
||
orderbys.push(inc.model.aliasprefix + inc.model.primary);
|
||
// simpel op joinfield
|
||
var outer = "(+)";
|
||
if ("outertoggle" in inc)
|
||
{
|
||
if (params.filter.has_scf_outer && params.filter.scf_outer == "on")
|
||
outer = "";
|
||
else
|
||
outer = inc.outertoggle.def?"":"(+)";
|
||
}
|
||
wheres.push ( model.table + "." + model.primary + "=" + inc.model.table + "." + inc.model.fields[inc.joinfield].dbs + outer);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
__Log("Unknown include '{0}' requested".format(params.include[i]));
|
||
}
|
||
}
|
||
return { withs: withs, selects: selects, tables: tables, wheres: wheres, orderbys: orderbys };
|
||
},
|
||
|
||
// TODO the_key *moet* bestaan. Andere filtervelden negeren we
|
||
// Bouw een fields object zoals save2db dat verwacht/ kent
|
||
update_fields: function _update_fields(params, model, jsondata)
|
||
{
|
||
var fields = {};
|
||
for (var fld in model.fields)
|
||
{
|
||
var field = model.fields[fld];
|
||
// De key halen we uit de url, die in de JSON negeren we
|
||
if (fld == "id")
|
||
continue;
|
||
if ("sql" in field)
|
||
continue;
|
||
if (!(fld in jsondata) && !field.fnval)
|
||
continue;
|
||
if (field.readonly)
|
||
continue;
|
||
if (field.insertonly && !params.isNew)
|
||
continue;
|
||
|
||
if (field.fnval)
|
||
var newval = field.fnval(jsondata);
|
||
else // simpel
|
||
var newval = jsondata[fld];
|
||
|
||
if (field.LOV && newval && typeof newval == "object" && "id" in newval)
|
||
{ // dereference
|
||
newval = newval.id;
|
||
}
|
||
|
||
switch (field.typ)
|
||
{
|
||
case "key": // De foreign keys action { "id": "5", "name": "afhalen" }
|
||
if (newval && typeof newval == "object")
|
||
{
|
||
if ("id" in newval)
|
||
{ // dereference
|
||
newval = newval.id;
|
||
jsondata[fld] = newval;
|
||
}
|
||
else if ("name" in newval && field.foreign && typeof field.foreign != 'function')
|
||
{
|
||
if (typeof field.foreign == 'string')
|
||
{
|
||
field.foreign = foreignKeyTable(field.foreign);
|
||
}
|
||
if (field.foreign.desc_is_unique) // Dan mag je die bij saven ook opgeven
|
||
{
|
||
field.typ = "sql";
|
||
newval = "(SELECT {0} FROM {1} WHERE {2} = {3})".format(field.foreign.key,
|
||
field.foreign.tbl,
|
||
field.foreign.desc,
|
||
safe.quoted_sql(newval.name));
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case "date":
|
||
case "datetime":
|
||
if (newval === "")
|
||
newval = null;
|
||
// LET OP: Een (new Date) gemaakt binnen een plugin is vreemd genoeg geen (instanceof Date)
|
||
// Waarschijnlijk gebruikt een wsc een ander Date object als (ASP)JScript?
|
||
if (newval !== null && typeof newval == "object" && !(newval instanceof Date))
|
||
newval = new Date(newval);
|
||
if (newval !== null && !(newval instanceof Date))
|
||
{
|
||
abort_with_warning("Invalid " + field.typ + " (" + model.record_name + "." + fld + "): " + newval);
|
||
}
|
||
break;
|
||
case "float":
|
||
case "currency":
|
||
case "number":
|
||
if (isNaN(newval))
|
||
abort_with_warning("Invalid number (" + model.record_name + "." + fld + "): " + newval);
|
||
break;
|
||
}
|
||
if (field.dbs.indexOf(".") >= 0) // complexe foreign key
|
||
continue;
|
||
|
||
// LET OP: newfield en fields zijn in het formaat wat save2db verwacht/kent
|
||
// dat lijkt wel op een standaard model.fields object maar is soms een fractie anders
|
||
// Zo heeft een save2db field geen "label" en is "track" geen boolean maar de label-tekst
|
||
// LET OP2: save2db werkt eigenlijk met een Array voor fields maar wij hebben een
|
||
// object met "name": { field.. } wat daar wel omgezet wordt.
|
||
var newfield = { dbs: field.dbs,
|
||
typ: field.typ,
|
||
track: field.track && field.label?field.label:field.track,
|
||
foreign: field.foreign,
|
||
val: newval,
|
||
len: field.len
|
||
};
|
||
fields[fld] = newfield;
|
||
}
|
||
return fields;
|
||
},
|
||
|
||
// Verwerk de POST en PUT (== insert en update)
|
||
// Merk op dat het resultaat van deze functie redelijk ongedefinieerd is
|
||
process_includes: function(params, model, jsondata, the_key)
|
||
{
|
||
if (!model.includes)
|
||
return [];
|
||
for (var incname in model.includes)
|
||
{
|
||
if (incname in jsondata) // i=="visitors"
|
||
{
|
||
var inc = model.includes[incname];
|
||
if (inc.model && inc.enable_update) // andere includes zijn nog niet bij te werken
|
||
{
|
||
// Als je bij een PUT/POST een include in de BODY zet geven we hem automagisch terug
|
||
params.include = params.include || [];
|
||
params.include.push(incname);
|
||
|
||
var incmodel = inc.model;
|
||
|
||
var existing_includes = {};
|
||
if (!params.isNew)
|
||
{
|
||
// Vul existing_includes met bestaande records in de database
|
||
if ("fntablesql" in incmodel)
|
||
incmodel.tablesql = incmodel.fntablesql();
|
||
|
||
var sql = "SELECT " + incmodel.primary
|
||
+ " FROM " + (incmodel.tablesql || incmodel.table)
|
||
+ " WHERE " + incmodel.table + "." + incmodel.fields[inc.joinfield].dbs + "=" + the_key;
|
||
var oRs = Oracle.Execute(sql);
|
||
while (!oRs.Eof)
|
||
{
|
||
existing_includes[oRs(incmodel.primary).Value] = { found: false }; // vooralsnog niet in json-data gevonden
|
||
oRs.MoveNext();
|
||
}
|
||
oRs.Close();
|
||
}
|
||
var incdata = jsondata[incname]; // Array zoals via API aangeleverd
|
||
for (var j=0; j<incdata.length; j++)
|
||
{
|
||
var inckey = incdata[j][incmodel.keyfield || "id"]; // Die kan er zijn. custom_field werkt via keyfield
|
||
if (incmodel.keyfield || !(inckey > 0) || params.isNew)
|
||
{
|
||
delete incdata[j]["id"]; // voor als je bij isNew toch keys had meegegeven
|
||
incdata[j][inc.joinfield] = the_key; // de parent_key altijd zetten
|
||
__Log("Nu ga ik een '{0}' toevoegen".format(incname));
|
||
incmodel.REST_POST(params, incdata[j], the_key);
|
||
}
|
||
else if (inckey in existing_includes)
|
||
{
|
||
__Log("Nu ga ik '{0}' {1} updaten".format(incname, inckey));
|
||
incmodel.REST_PUT(params, incdata[j], inckey, the_key);
|
||
existing_includes[inckey].found = true;
|
||
}
|
||
}
|
||
for (oldi in existing_includes)
|
||
{
|
||
if (!existing_includes[oldi].found)
|
||
{
|
||
incmodel.REST_DELETE(params, oldi);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//if (inc.func)
|
||
}
|
||
}
|
||
}
|
||
},
|
||
// Geeft de GET terug van een enkel veld
|
||
sql2jsonval: function _sql2jsonval(oRs, fld, model)
|
||
{
|
||
var field = model.fields[fld];
|
||
var sqlfieldname = (model.aliasprefix ? api2.sqlfield_alias(model, fld) : field.dbs);
|
||
if (field.val instanceof Function)
|
||
var val = field.val(oRs, field, model, sqlfieldname);
|
||
else if (field.dbs.indexOf(".") < 0)
|
||
{
|
||
var val = oRs(sqlfieldname).Value;
|
||
}
|
||
else
|
||
var val = oRs(field.dbs.split(".")[1]).Value;
|
||
|
||
if (field.typ == "check" || field.typ == "check0")
|
||
{
|
||
if (field.invert)
|
||
val = (val==1?0:1);
|
||
}
|
||
|
||
if (val === null)
|
||
return val;
|
||
if (field.typ == "date")
|
||
val = new Date(val);
|
||
if (field.typ == "datetime")
|
||
val = new Date(val);
|
||
if (field.typ == "time") // In de database ondersteunen we dit niet maar komt voor bij fac_report
|
||
val = new Date(val);
|
||
// Wat te doen met lege waarde
|
||
// action: null
|
||
// action: {key: null, name: null}
|
||
// action: {}
|
||
// of helemaal weglaten? We hebben nu de 1e optie. Dat is zelfdocumenterend
|
||
// En wat bij een leeg (include) array? Dan kun je ook nog occupations:[] krijgen
|
||
if (field.foreign || field.foreignsql)
|
||
{
|
||
if (field.foreignsql)
|
||
{
|
||
var name = oRs(model.aliasprefix + field._foreignname).Value;
|
||
if (name != null && typeof name == "date" )
|
||
name = new Date(name);
|
||
}
|
||
else
|
||
{
|
||
var name = field.foreign(val);
|
||
}
|
||
val = { id: val };
|
||
val.name = name;
|
||
}
|
||
// if (field.typ == 'check' && !field.LOV)
|
||
// field.LOV = "0;" + L("lcl_No") + ";1;" + L("lcl_Yes");
|
||
|
||
if (field.LOV)
|
||
{
|
||
var spl = api2.splitLOV(field.LOV);
|
||
val = { id: val, name: spl[val] };
|
||
}
|
||
|
||
if (field.flexmodule)
|
||
{
|
||
var flexparam = flexProps(field.flexmodule, -1, null, null,
|
||
{ getFile: val, api2name: null });
|
||
if (flexparam.files.length)
|
||
{
|
||
var file = flexparam.files[0];
|
||
val = { name: file.name,
|
||
date: file.date,
|
||
size: file.size,
|
||
content_url: file.deepurl,
|
||
digest: file.digest };
|
||
}
|
||
}
|
||
return val;
|
||
},
|
||
filterLOV: function _splitLOV(LOV, filter)
|
||
{
|
||
filter = filter.split(",");
|
||
var spl = api2.splitLOV(LOV);
|
||
var result = [];
|
||
for (var f in filter)
|
||
result.push(filter[f] + ";" + spl[filter[f]]);
|
||
return result.join(";");
|
||
},
|
||
splitLOV: function _splitLOV(LOV, nameprefix)
|
||
{
|
||
var splits = LOV.split(";");
|
||
if (splits % 2)
|
||
abort_with_warning("LOV '{0}' mismatch".format(LOV));
|
||
var result = {};
|
||
for (var i=0; i < splits.length / 2; i++)
|
||
{
|
||
var name = splits[i*2];
|
||
if (nameprefix)
|
||
name = nameprefix + name;
|
||
result[name] = splits[i*2 + 1];
|
||
}
|
||
return result;
|
||
},
|
||
splitLOV2sql: function _splitLOV(LOV)
|
||
{
|
||
var spl = api2.splitLOV(LOV);
|
||
var dual = [];
|
||
var cnt = 0;
|
||
for (var l in spl)
|
||
{
|
||
cnt ++;
|
||
dual.push("SELECT " + safe.quoted_sql(l) + ", " + safe.quoted_sql(spl[l]) + ", " + cnt + " FROM DUAL");
|
||
}
|
||
return dual.join(" UNION ALL ") + " ORDER BY 3";
|
||
},
|
||
splitLOV2select: function _splitLOV(LOV, current, params)
|
||
{
|
||
params = params || {};
|
||
var spl = api2.splitLOV(LOV);
|
||
var options = [];
|
||
for (var l in spl)
|
||
options.push("<option value='" + safe.htmlattr(l) + "'" + (l == current?" selected='1'":"") + ">" + safe.html(spl[l]) + "</option>");
|
||
return "<select" + (params.selclass?" class=" + params.selclass:"") + ">" + options.join("") + "</select>";
|
||
},
|
||
// ['xxx','yyy','zzz'] ==> "0;xxx;1;yyy;2;zzz"
|
||
makeLOV: function _makeLOV(arr)
|
||
{
|
||
var LOVarr = [];
|
||
for (var i = 0; i < arr.length; i++)
|
||
{
|
||
LOVarr.push(String(i));
|
||
LOVarr.push(arr[i]);
|
||
}
|
||
return LOVarr.join(";");
|
||
},
|
||
getTimetable: function()
|
||
{
|
||
var timetable = [];
|
||
for (var t = 0; t < 24*4-1; t++)
|
||
{ // "0;0:00;0.25;0:15;0.5;0:30;0.75;0:45;" enzovoorts
|
||
timetable.push(t / 4);
|
||
timetable.push(String(Math.floor(t / 4)) + ":" + padout(15*(t % 4)));
|
||
}
|
||
return timetable.join(";");
|
||
},
|
||
// Geef alle velden van een record terug
|
||
// ?? Alleen bij includes in gebruik ??
|
||
sql2jsonfields: function _sql2jsonfields(params, oRs, model)
|
||
{
|
||
var record = {};
|
||
for (var fld in model.fields)
|
||
{
|
||
if (fld.substring(0,1) == "_")
|
||
continue;
|
||
var field = model.fields[fld];
|
||
if (field.hidden)
|
||
continue;
|
||
|
||
var val = api2.sql2jsonval(oRs, fld, model);
|
||
//waarom was dit aanwezig?
|
||
//if (field.readonly && !val.id)
|
||
// continue;
|
||
record[fld] = val;
|
||
}
|
||
if ("post_get" in model)
|
||
model.post_get(params, record);
|
||
return record;
|
||
},
|
||
sql2json: function _sql2json(params, sql, model)
|
||
{
|
||
var prefuncdata;
|
||
var prefuncdatainitialized = false;
|
||
var prefilterfuncdata;
|
||
if (model.filter && model.filter.prefunc)
|
||
prefilterfuncdata = model.filter.prefunc(params);
|
||
|
||
var data = [];
|
||
var oRs = Oracle.Execute(sql, params.errmsg);
|
||
if (params.errmsg && oRs.friendlyMsg)
|
||
{
|
||
var record = {};
|
||
record[model.list.columns[0]] = oRs.friendlyMsg;
|
||
data.push(record);
|
||
return data;
|
||
}
|
||
|
||
var lastkey = 0;
|
||
var record = {};
|
||
// Merk op dat onze recordset meer regels kan bevatten dan je zou verwachten
|
||
// omdat de includes er bij zijn gejoind
|
||
var total_count = 0;
|
||
var limit = S("qp_maxrows"); // TODO: maximaliseren op qp_maxrows2?
|
||
if ("limit" in params.filter)
|
||
limit = params.filter.limit;
|
||
if (params.filter.nolimit || limit == -1)
|
||
limit = 99999999; // Excel/CSV alles tonen
|
||
while (!oRs.Eof)
|
||
{
|
||
if (data.length >= limit) // Alleen nog maar tellen
|
||
{
|
||
if (model.primary)
|
||
{
|
||
var key = oRs(model.primary).Value; // Oppassen met de includes
|
||
if (key != lastkey)
|
||
total_count ++;
|
||
lastkey = key;
|
||
}
|
||
else
|
||
{
|
||
total_count ++;
|
||
}
|
||
oRs.MoveNext();
|
||
continue;
|
||
}
|
||
if (model.primary)
|
||
{
|
||
var key = oRs(model.primary).Value;
|
||
if (key != lastkey)
|
||
{
|
||
if (key < lastkey && params.include && params.include.length && !params.include[0] == 'disc_params') // disc_params is toch one-on-one
|
||
{
|
||
abort_with_warning("Include '{0}' only allowed when main record is ordered by '{1}'".format(params.include[0], model.primary));
|
||
}
|
||
|
||
if (lastkey && !isEmptyObject(record)) // Record was er mogelijk uit gefilterd
|
||
{
|
||
data.push(record);
|
||
total_count ++;
|
||
if (data.length == limit)
|
||
continue; // alleen nog tellen, 'volgende' record niet meer opbouwen
|
||
}
|
||
var record = {};
|
||
}
|
||
}
|
||
|
||
// Complexe filtering die we niet voor elkaar kregen met een WHERE-clause
|
||
if (model.filter && model.filter.func)
|
||
{
|
||
if (!model.filter.func(oRs, params, prefilterfuncdata))
|
||
{
|
||
lastkey = key;
|
||
oRs.MoveNext();
|
||
continue;
|
||
}
|
||
}
|
||
|
||
var fld;
|
||
for (fld in model.fields)
|
||
{
|
||
if (fld.substring(0,1) == "_")
|
||
continue;
|
||
var field = model.fields[fld];
|
||
if (field.hidden)
|
||
continue;
|
||
if ("fields" in params.filter && !inArray(fld, params.filter.fields.split(",")))
|
||
continue;
|
||
|
||
var val = api2.sql2jsonval(oRs, fld, model)
|
||
//waarom was dit aanwezig?
|
||
//if (field.readonly && !val.id)
|
||
// continue;
|
||
record[fld] = val;
|
||
}
|
||
if (params.include && model.includes)
|
||
{
|
||
for (var i in params.include) // welke includes worden opgevraagd?
|
||
{
|
||
var incname = params.include[i];
|
||
if (incname == "lastchange") // special case
|
||
continue;
|
||
if (incname in model.includes) // Ondersteunen we deze include?
|
||
{
|
||
if (!(incname in record))
|
||
record[incname] = [];
|
||
var incmodel = model.includes[incname].model;
|
||
if (incmodel)
|
||
{ // Standaard include via model. Ons 'hoofd' record zal meerdere keren
|
||
// uit de query komen met telkens een ander 'include' record
|
||
incmodel.aliasprefix = incmodel.aliasprefix || "";
|
||
var inc_key = oRs(incmodel.aliasprefix + incmodel.primary).value;
|
||
if (inc_key == null) // Geen record door outer join
|
||
continue;
|
||
// Nu kan het zo zijn dat ons include record al eerder is toegevoegd
|
||
// Kan gebeuren bij res_ruimte als je een include van res_alg_ruimte *en* opstelling doet
|
||
var incfound = false;
|
||
for (var i = 0; i < record[incname].length && !incfound; i++)
|
||
{
|
||
if (record[incname][i].id == inc_key)
|
||
incfound = true;
|
||
}
|
||
if (!incfound)
|
||
record[incname].push(api2.sql2jsonfields(params, oRs, incmodel));
|
||
}
|
||
else if (model.includes[incname].func) // include via callback functie zoals reservablerooms/occupation
|
||
{
|
||
if (model.includes[incname].prefunc && !prefuncdatainitialized)
|
||
{
|
||
prefuncdata = model.includes[incname].prefunc(params);
|
||
prefuncdatainitialized = true;
|
||
}
|
||
var incdata = model.includes[incname].func(key, params, oRs, record, prefuncdata);
|
||
if (incdata !== null)
|
||
record[incname] = incdata; // de callback geeft de gehele include in een keer
|
||
}
|
||
}
|
||
else
|
||
__Log("Unknown include '{0}' requested".format(incname));
|
||
}
|
||
}
|
||
|
||
lastkey = key;
|
||
if (!model.primary)
|
||
{
|
||
data.push(record);
|
||
record = {};
|
||
total_count ++;
|
||
}
|
||
oRs.MoveNext();
|
||
}
|
||
if (lastkey && data.length < limit)
|
||
{
|
||
total_count ++;
|
||
data.push(record);
|
||
}
|
||
if ("merged_model" in model) // de disc_params truc
|
||
{
|
||
model.merged_model.limit = limit;
|
||
//model.offset = offset;
|
||
model.merged_model.total_count = total_count;
|
||
}
|
||
model.limit = limit;
|
||
//model.offset = offset;
|
||
model.total_count = total_count;
|
||
return data;
|
||
},
|
||
error: function (code, msg)
|
||
{
|
||
abort_with_warning(msg, code);
|
||
},
|
||
find_fieldindex_by_dbsname: function(array, value)
|
||
{
|
||
for(var i = 0; i < array.length; i++)
|
||
{
|
||
if(array[i].hasOwnProperty("dbs") && array[i]["dbs"] === value)
|
||
{
|
||
return i;
|
||
}
|
||
}
|
||
return -1;
|
||
},
|
||
// Pas op: fields is hier alleen de fields die bijgewerkt moeten worden
|
||
field_alter: function(fields, fld, newval, model)
|
||
{
|
||
if (fld in fields)
|
||
{
|
||
fields[fld].val = newval;
|
||
}
|
||
else
|
||
{
|
||
// Als veld niet aanwezig is, dan toevoegen.
|
||
// Het typ halen we uit het api-model.
|
||
var newfield = {};
|
||
newfield.dbs = model[fld].dbs;
|
||
newfield.typ = model[fld].typ;
|
||
if (model[fld].track)
|
||
newfield.fld = model[fld].label||model[fld].track;
|
||
newfield.val = newval;
|
||
__Log(newfield);
|
||
fields[fld] = newfield;
|
||
}
|
||
},
|
||
// Maak van inkomende data een standaard formaat
|
||
cleanup_data: function(model, jsondata)
|
||
{
|
||
// foreigns's leveren we op als issuetype: { id: 123, name: "de standaardmelding") }
|
||
// Zo mag je het met een PUT en POST ook opgeven maar we ondersteunen/prefereren ook rechtstreeks issuetype: 123
|
||
// Onderstaande functie converteert inkomende data naar het simpele formaat
|
||
for (var fld in model.fields)
|
||
{
|
||
if (model.fields[fld].foreign)
|
||
{
|
||
if (fld in jsondata && typeof jsondata[fld] == "object" && jsondata[fld] && "id" in jsondata[fld])
|
||
jsondata[fld] = jsondata[fld].id;
|
||
}
|
||
}
|
||
},
|
||
get_jdata_refkey: function(jsondataobject)
|
||
{
|
||
var value = null;
|
||
if (jsondataobject)
|
||
{
|
||
if (jsondataobject.id && typeof jsondataobject.id != "object")
|
||
value = jsondataobject.id;
|
||
else
|
||
value = jsondataobject;
|
||
}
|
||
return value;
|
||
},
|
||
hasTime: function (dt)
|
||
{
|
||
return dt.getHours() > 0 ||
|
||
dt.getMinutes() > 0 ||
|
||
dt.getSeconds() > 0
|
||
},
|
||
toDate: function (dt)
|
||
{
|
||
if (dt instanceof Array)
|
||
MULTI_DATE_NOT_SUPPORTED;
|
||
|
||
if (dt instanceof Date)
|
||
return dt;
|
||
// LET OP: Een (new Date) gemaakt binnen een plugin is vreemd genoeg geen (instanceof Date)
|
||
// Waarschijnlijk gebruikt een wsc een ander Date object als (ASP)JScript?
|
||
if (dt !== null && typeof dt == "object")
|
||
{
|
||
dt = new Date(dt);
|
||
if (dt instanceof Date)
|
||
return dt;
|
||
}
|
||
if (typeof dt == 'string')
|
||
{
|
||
if (dt.match(/^[0-9]+$/))
|
||
return new Date(parseInt(dt, 10));
|
||
var dt = myJSON.internal_parsedate(null, dt);
|
||
if (dt && dt instanceof Date)
|
||
return dt;
|
||
}
|
||
NOT_A_DATE;
|
||
},
|
||
generic_REST: function(model, gparams)
|
||
{
|
||
__Log("Creating generic_REST for model " + model.records_name);
|
||
gparams = gparams || {};
|
||
if (typeof model == "function") // Nieuwe stijl is het een function. Even compatible.
|
||
DIT_KLOPT_NIET;
|
||
|
||
// autfunction === false kun je het expliciet uitschakelen
|
||
if (model.autfunction !== false && !model.autfunction)
|
||
GENERIC_ONLY_WITH_AUTFUNCTION;
|
||
|
||
model.REST_GET = generic_REST_GET(model, gparams);
|
||
if (model.primary)
|
||
{
|
||
model.REST_POST = generic_REST_POST(model, gparams);
|
||
model.REST_PUT = generic_REST_PUT(model, gparams);
|
||
model.REST_DELETE = generic_REST_DELETE(model, gparams);
|
||
}
|
||
},
|
||
GET: function _GET(model, key, params) // Haal <20><>n enkel record op of null als niet gevonden
|
||
{ // Key is optioneel, dan meot je zelf in params maar goed filteren
|
||
if (typeof key == "object")
|
||
{
|
||
params = key;
|
||
key = null;
|
||
}
|
||
else
|
||
{
|
||
params = params || {};
|
||
params.filter = params.filter || {};
|
||
params.filter.id = key;
|
||
}
|
||
|
||
var xxx_array = model.REST_GET(params);
|
||
if (!xxx_array.length)
|
||
return null;
|
||
return xxx_array[0];
|
||
},
|
||
POST: function _POST(model, jsondata, parent_key) // Maak <20><>n enkel record aan
|
||
{ // Key is optioneel, dan meot je zelf in params maar goed filteren
|
||
var params = {};
|
||
return model.REST_POST(params, jsondata, parent_key);
|
||
},
|
||
merge_disc_params_model: function(deze, template_model)
|
||
{
|
||
template_model.merged_model = deze; // hebben we nodig om total_count te kunnen zetten
|
||
for (var x in template_model)
|
||
deze[x] = template_model[x];
|
||
|
||
deze.fields = {}; // Die krijgt een deepclone
|
||
for (var fld in template_model.fields)
|
||
deze.fields[fld] = template_model.fields[fld];
|
||
for (var fld in deze.disc_params.model.fields)
|
||
{
|
||
if (fld != "id")
|
||
deze.fields[fld] = deze.disc_params.model.fields[fld];
|
||
}
|
||
}
|
||
}
|
||
|
||
function generic_REST_GET(model, gparams)
|
||
{
|
||
gparams = gparams || {};
|
||
gparams.GET = gparams.GET || {};
|
||
model.gparams = gparams;
|
||
|
||
return function _generic_REST_GET(params, jsondata)
|
||
{
|
||
if (model.autfunction !== false)
|
||
user.checkAutorisation(model.autfunction); // Als je zelfs geen readrechten hebt ben je heel fout bezig
|
||
|
||
if ("disc_params" in model)
|
||
{
|
||
// We zouden net als bij de PUT etc. een rechtstreekse call op de eigen REST_GET kunnen doen
|
||
// maar dat werkt niet (zo soepeltjes) voor meerdere records
|
||
model.includes = model.includes || {};
|
||
model.includes["disc_params"] = model.disc_params;
|
||
params.include = params.include || [];
|
||
params.include.push("disc_params"); // Altijd
|
||
}
|
||
|
||
var query = api2.sqlfields(params, model);
|
||
|
||
var wheres = api2.sqlfilter(params, model);
|
||
query.wheres = query.wheres.concat(wheres);
|
||
|
||
if ("disc_params" in model) // Daar mag je ook op filteren
|
||
{
|
||
if (params.filter && "id" in params.filter)
|
||
delete params.filter.id; // anders wordt de disc_params_key onterecht gefilterd.
|
||
var wheres = api2.sqlfilter(params, model.disc_params.model);
|
||
query.wheres = query.wheres.concat(wheres);
|
||
}
|
||
|
||
if (gparams.GET.tables)
|
||
query.tables = query.tables.concat(gparams.GET.tables);
|
||
|
||
if (gparams.GET.wheres)
|
||
query.wheres = query.wheres.concat(gparams.GET.wheres);
|
||
|
||
if (model.soft_delete && params.filter.show_deleted != "on")
|
||
query.wheres.push(model.soft_delete + " IS NULL");
|
||
|
||
if (model.soft_expire && params.filter.show_expired != "on" && !params.filter.id)
|
||
query.wheres.push("(" + model.soft_expire + " IS NULL OR " + model.soft_expire + " > SYSDATE)");
|
||
|
||
var sql = "SELECT " + query.selects.join(", ")
|
||
+ " FROM " + query.tables.join(", ")
|
||
+ (query.wheres.length ? " WHERE " + query.wheres.join(" AND " ) : "");
|
||
if (query.withs)
|
||
{
|
||
for (var w in query.withs)
|
||
sql = " WITH " + w + " AS " + query.withs[w] + sql;
|
||
}
|
||
|
||
var orderbys = ((params.orderby && params.orderby.length > 0) ? [] : gparams.GET.orderbys || []);
|
||
if (params.orderby)
|
||
{
|
||
for (var i = 0; i < params.orderby.length; i++)
|
||
{
|
||
var field = model.fields[params.orderby[i]];
|
||
orderbys.push(field._foreignname || field.dbs);
|
||
}
|
||
}
|
||
|
||
if (!params.include && params.columns && params.columns.length > 1) // Sorteren volgens 'columns'
|
||
{
|
||
for (var i = 0; i < params.columns.length; i++)
|
||
{
|
||
if (params.columns[i] != "id")// Sla id-veld over bij sorteren
|
||
{
|
||
var field = model.fields[params.columns[i]];
|
||
orderbys.push(model.fields[params.columns[i]]._foreignname || model.fields[params.columns[i]].dbs);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!orderbys.length)
|
||
{
|
||
if (model.fields && "name" in model.fields)
|
||
orderbys.push(model.fields.name.dbs);
|
||
else if (model.fields && "id" in model.fields)
|
||
orderbys.push(model.fields.id.dbs);
|
||
else
|
||
orderbys.push(1); // FAC_VERSION heeft niets van dit alles
|
||
}
|
||
|
||
sql += " ORDER BY " + orderbys.join(", "); // TODO: Altijd goed met includes?
|
||
|
||
// Probeer ook nog de 1e include te sorteren. Fraaier met subframes in scaffolding
|
||
if (params.include && params.include.length && model.includes && params.include[0] in model.includes)
|
||
{
|
||
var inc0 = model.includes[params.include[0]].model;
|
||
if (inc0.fields["sequence"])
|
||
sql += ", " + inc0.table + "." + inc0.fields["sequence"].dbs;
|
||
if (inc0.fields["name"])
|
||
sql += ", " + inc0.table + "." + inc0.fields["name"].dbs;
|
||
if (inc0.fields["id"])
|
||
sql += ", " + inc0.table + "." + inc0.fields["id"].dbs;
|
||
}
|
||
|
||
var maxcnt = params.filter.showall==1?S("qp_maxrows2"):S("qp_maxrows");
|
||
if (params.include && params.include.length)
|
||
{
|
||
// geen beperking
|
||
// We proberen te voorkomen dat we onnodig veel rijen ophalen.
|
||
// Als er echter includes zijn krijgen we veel meer records dan je direct zou verwachten en
|
||
// klopt de beperking op basis van ROWNUM geheel niet. Met gepaste tegenzin bij includes
|
||
// dan maar (veel) te veel records ophalen.
|
||
}
|
||
else
|
||
{
|
||
if (!params.filter.nolimit) // Dan niet (Excel, CSV)
|
||
var sql = "SELECT * FROM (" + sql + ") WHERE ROWNUM <= " + (maxcnt + 1); // Eentje extra om overflow-melding te triggeren in resultsettable
|
||
}
|
||
|
||
if (model.fields)
|
||
var json = api2.sql2json (params, sql, model);
|
||
else
|
||
json = {};
|
||
|
||
if ("disc_params" in model)
|
||
{
|
||
// include van disc_params data niveau 'hoger' zetten
|
||
for (var i = 0; i < json.length; i ++)
|
||
{
|
||
for (var fld in json[i].disc_params[0])
|
||
{
|
||
if (fld != "id")
|
||
json[i][fld] = json[i].disc_params[0][fld];
|
||
}
|
||
delete json[i].disc_params;
|
||
}
|
||
}
|
||
return json;
|
||
}
|
||
}
|
||
|
||
function generic_REST_POST(model, gparams)
|
||
{
|
||
gparams = gparams || {};
|
||
|
||
if (!model.fields["id"].seq) // We willen per se een sequence
|
||
return false;
|
||
|
||
return function _generic_REST_POST(params, jsondata, parent_key)
|
||
{
|
||
params.isNew = true; // negeer eventuele bestaande keys
|
||
if (model.autfunction !== false)
|
||
{
|
||
var autparams = user.checkAutorisation(model.autfunction);
|
||
user.auth_required_or_abort(autparams.PRSwritelevel < 9 && autparams.ALGwritelevel < 9);
|
||
}
|
||
|
||
if ("disc_params" in model)
|
||
{
|
||
// disc_params velden er uit trekken naar eigen json object
|
||
var module = model.fields.ins_discipline_module || "";
|
||
var jsondata_dp = { };
|
||
for (var fld in jsondata)
|
||
{
|
||
if (fld in model.disc_params.model.fields && !(fld in model.fields))
|
||
{
|
||
jsondata_dp[fld] = jsondata[fld];
|
||
delete jsondata[fld];
|
||
}
|
||
}
|
||
}
|
||
for (var fld in model.fields)
|
||
{
|
||
if (!(fld in jsondata) && model.fields[fld].required)
|
||
jsondata[fld] = model.fields[fld].defaultvalue; // hopen dat die is gezet
|
||
}
|
||
|
||
var dbfields = api2.update_fields(params, model, jsondata);
|
||
|
||
dbfields["id"] = model.fields["id"]; // Die zal een seq hebben
|
||
var xxxIns = buildInsert(model.table, dbfields, { noValidateToken: true });
|
||
var the_key = xxxIns.sequences[model.fields.id.dbs];
|
||
// TODO: Generieke tracking?
|
||
|
||
var err = Oracle.Execute(xxxIns.sql, true);
|
||
if (err.friendlyMsg)
|
||
abort_with_warning(err.friendlyMsg);
|
||
|
||
var inctrack = api2.process_includes(params, model, jsondata, the_key);
|
||
|
||
if ("disc_params" in model)
|
||
{
|
||
// Nu de one-on-one tabel. De eigen sequence daarvan interesseert ons niet
|
||
jsondata_dp[model.disc_params.joinfield] = the_key;
|
||
var dbfields = api2.update_fields(params, model.disc_params.model, jsondata_dp);
|
||
var xxxIns = buildInsert(model.disc_params.model.table, dbfields, { noValidateToken: true });
|
||
|
||
var err = Oracle.Execute(xxxIns.sql, true);
|
||
if (err.friendlyMsg)
|
||
{
|
||
var sql = "DELETE FROM " + model.table + " WHERE " + model.fields.id.dbs + " = " + the_key;
|
||
Oracle.Execute(sql);
|
||
abort_with_warning(err.friendlyMsg);
|
||
}
|
||
}
|
||
|
||
return { key: the_key, warning: "" };
|
||
}
|
||
}
|
||
|
||
function generic_REST_PUT(model, gparams)
|
||
{
|
||
gparams = gparams || {};
|
||
|
||
return function _generic_REST_PUT(params, jsondata, the_key)
|
||
{
|
||
if (model.autfunction !== false)
|
||
{
|
||
var autparams = user.checkAutorisation(model.autfunction);
|
||
user.auth_required_or_abort(autparams.PRSwritelevel < 9 && autparams.ALGwritelevel < 9);
|
||
}
|
||
|
||
if ("disc_params" in model)
|
||
{
|
||
// disc_params velden er uit trekken naar eigen json object
|
||
var jsondata_dp = { };
|
||
for (var fld in jsondata)
|
||
{
|
||
if (fld in model.disc_params.model.fields && !(fld in model.fields))
|
||
{
|
||
jsondata_dp[fld] = jsondata[fld];
|
||
// delete jsondata[fld]; neen, dan gaat multi-save fout
|
||
}
|
||
}
|
||
model.disc_params.model.fields[model.disc_params.joinfield].readonly = true; // Voor de zekerheid
|
||
jsondata_dp[model.disc_params.joinfield] = the_key;
|
||
}
|
||
|
||
var dbfields = api2.update_fields(params, model, jsondata);
|
||
var wheres = [model.fields["id"].dbs + " = " + the_key];
|
||
var xxxUpd = buildTrackingUpdate(model.table, wheres.join(" AND " ), dbfields, { noValidateToken: true });
|
||
// TODO: Generieke tracking?
|
||
|
||
if (xxxUpd) // Misschien geen velden opgegeven.
|
||
{
|
||
var err = Oracle.Execute(xxxUpd.sql, true);
|
||
if (err.friendlyMsg)
|
||
abort_with_warning(err.friendlyMsg);
|
||
if (model.trackcode)
|
||
shared.trackaction(model.trackcode, the_key, xxxUpd.trackarray.join("\n"));
|
||
}
|
||
var inctrack = api2.process_includes(params, model, jsondata, the_key);
|
||
|
||
if ("disc_params" in model)
|
||
{
|
||
// Nu de one-on-one tabel
|
||
var dbfields = api2.update_fields(params, model.disc_params.model, jsondata_dp);
|
||
var wheres = [model.disc_params.model.fields[model.disc_params.joinfield].dbs + " = " + the_key];
|
||
var xxxUpd = buildTrackingUpdate(model.disc_params.model.table, wheres.join(" AND " ), dbfields, { noValidateToken: true });
|
||
if (xxxUpd) // Misschien geen velden opgegeven.
|
||
{
|
||
var err = Oracle.Execute(xxxUpd.sql, true);
|
||
if (err.friendlyMsg)
|
||
abort_with_warning(err.friendlyMsg);
|
||
}
|
||
}
|
||
|
||
return { key: the_key, warning: "" };
|
||
}
|
||
}
|
||
|
||
function generic_REST_DELETE(model, gparams)
|
||
{
|
||
gparams = gparams || {};
|
||
|
||
return function _generic_REST_DELETE(params, the_key)
|
||
{
|
||
if (model.autfunction !== false)
|
||
{
|
||
var autparams = user.checkAutorisation(model.autfunction);
|
||
user.auth_required_or_abort(autparams.PRSwritelevel < 9 && autparams.ALGwritelevel < 9);
|
||
}
|
||
|
||
if (model.soft_delete)
|
||
{
|
||
var sql = "UPDATE " + model.table
|
||
+ " SET " + model.soft_delete + " = SYSDATE"
|
||
+ " WHERE " + model.fields.id.dbs + " = " + the_key
|
||
+ " AND " + model.soft_delete + " IS NULL";
|
||
}
|
||
else
|
||
{
|
||
var sql = "DELETE " + model.table
|
||
+ " WHERE " + model.fields.id.dbs + " = " + the_key;
|
||
}
|
||
|
||
var err = Oracle.Execute(sql, true);
|
||
if (err.friendlyMsg)
|
||
abort_with_warning(err.friendlyMsg);
|
||
|
||
return { key: the_key, warning: "" };
|
||
}
|
||
}
|
||
|
||
// Algemene normalizing
|
||
function def(hash, fld, val)
|
||
{
|
||
if (!(fld in hash))
|
||
hash[fld] = val;
|
||
}
|
||
|
||
function isEmptyObject ( obj )
|
||
{
|
||
var name;
|
||
for ( name in obj ) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
%> |