298bd2100d6c474895d582e35375cebf36f70933
[xtuple] / enyo-client / database / source / xm / javascript / item_site.sql
1 /* Delete previously misnamed record */
2 delete from xt.js where js_context='xtuple' and js_type = 'item_site';
3
4 select xt.install_js('XM','ItemSite','xtuple', $$
5   /* Copyright (c) 1999-2014 by OpenMFG LLC, d/b/a xTuple.
6      See www.xm.ple.com/CPAL for the full text of the software license. */
7
8 (function () {
9
10   if (!XM.ItemSite) { XM.ItemSite = {}; }
11
12   XM.ItemSite.isDispatchable = true;
13
14   /**
15     Return the current cost for a particular item site.
16   */
17   XM.ItemSite.cost = function (itemsiteId) {
18     if (!XT.Data.checkPrivilege('ViewCosts')) { return null; }
19     return plv8.execute('select itemcost(itemsite_id) as cost from itemsite where obj_uuid = $1;', [itemsiteId])[0].cost;
20   };
21
22   if (!XM.ItemSitePrivate) { XM.ItemSitePrivate = {}; }
23
24   /*
25     This should NEVER be set to true. XM.ItemSitePrivate.fetch can be passed
26     table and column names to drasitcally change it behaviour which could be
27     abused. It should NOT be isDispatchable.
28   **/
29   XM.ItemSitePrivate.isDispatchable = false;
30
31   /**
32     @private
33
34     This function supports the XM.ItemSiteListItem.fetch() and XM.ItemSiteRelation.fetch(),
35     but also xDruple extension XM.XdrupleCommerceProduct.xdCommerceProductFetch() call like this:
36     XM.ItemSitePrivate.fetch("XM.XdrupleCommerceProduct", "xdruple.xd_commerce_product", query, 'product_id', 'id');
37   */
38   XM.ItemSitePrivate.fetch = function (recordType, backingType, query, backingTypeJoinColumn, idColumn) {
39     query = query || {};
40     backingTypeJoinColumn = backingTypeJoinColumn || 'itemsite_item_id';
41     idColumn = idColumn || 'itemsite_id';
42
43     var data = Object.create(XT.Data),
44       nameSpace = recordType.beforeDot(),
45       type = recordType.afterDot(),
46       tableNamespace = backingType.beforeDot(),
47       table = backingType.afterDot(),
48       orderBy = query.orderBy,
49       orm = data.fetchOrm(nameSpace, type),
50       pkey = XT.Orm.primaryKey(orm),
51       nkey = XT.Orm.naturalKey(orm),
52       keyColumn = XT.Orm.primaryKey(orm, true),
53       customerId = null,
54       accountId = -1,
55       shiptoId,
56       effectiveDate = new Date(),
57       vendorId = null,
58       limit = query.rowLimit ? 'limit ' + Number(query.rowLimit) : '',
59       offset = query.rowOffset ? 'offset ' + Number(query.rowOffset) : '',
60       clause,
61       ret = {
62         nameSpace: nameSpace,
63         type: type
64       },
65       itemJoinMatches,
66       itemJoinTable,
67       joinTables = [],
68       keySearch = false,
69       extra = "",
70       qry,
71       counter = 1,
72       ids = [],
73       idParams = [],
74       etags,
75       sqlCount,
76       sql1 = 'select pt1.id ' +
77              'from ( ' +
78              'select t1.%3$I as id {groupColumns} ' +
79              'from %1$I.%2$I t1 {joins} ' +
80              /* Add dummy/blank itemalias join to satisfy unions below. */
81              'left join (' +
82              '  select * from itemalias where true = false ' +
83              ') as itemalias on t1.%4$I=itemalias_item_id ' +
84              'where {conditions} {extra}',
85       sql2 = 'select * from %1$I.%2$I where id in ({ids}) {orderBy}';
86
87     /* Handle special parameters */
88     if (query.parameters) {
89       query.parameters = query.parameters.filter(function (param) {
90         var result = false;
91
92         /* Over-ride usual search behavior */
93         if (param.keySearch) {
94           keySearch = param.value;
95           sql1 += ' and t1.%4$I in (select item_id from item where item_number ~^ ${p1} or item_upccode ~^ ${p1}) ' +
96             'union ' +
97             'select t1.%3$I as id {groupColumns} ' +
98             'from %1$I.%2$I t1 {joins} ' +
99             ' join itemalias on t1.%4$I=itemalias_item_id ' +
100             '   and itemalias_crmacct_id is null ' +
101             'where {conditions} {extra} ' +
102             ' and (itemalias_number ~^ ${p1}) ' +
103             'union ' +
104             'select t1.%3$I as id {groupColumns}  ' +
105             'from %1$I.%2$I t1 {joins} ' +
106             ' join itemalias on t1.%4$I=itemalias_item_id ' +
107             '   and itemalias_crmacct_id={accountId} ' +
108             'where {conditions} {extra} ' +
109             ' and (itemalias_number ~^ ${p1}) ';
110           return false;
111         }
112
113         switch (param.attribute)
114         {
115         case "customer":
116           customerNumber = param.value;
117           customerId = data.getId(data.fetchOrm('XM', 'CustomerProspectRelation'), param.value);
118           accountId = data.getId(data.fetchOrm('XM', 'AccountRelation'), param.value);
119           break;
120         case "shipto":
121           shiptoId = data.getId(data.fetchOrm('XM', 'CustomerShipto'), param.value);
122           break;
123         case "effectiveDate":
124           effectiveDate = param.value;
125           break;
126         case "vendor":
127           vendorId = data.getId(data.fetchOrm('XM', 'VendorRelation'), param.value);
128           break;
129         default:
130           result = true;
131         }
132         return result;
133       });
134     }
135
136     clause = data.buildClause(nameSpace, type, query.parameters, orderBy);
137
138     /* Check if public.item is already joined through clause.joins. */
139     if (clause.joins && clause.joins.length) {
140       itemJoinMatches = clause.joins.match(/(.item )(jt\d+)/g);
141
142       if (itemJoinMatches && itemJoinMatches.length) {
143         itemJoinTable = itemJoinMatches[0].match(/(jt\d+)/g);
144       }
145
146       /* Get all join table names. */
147       joinTables = clause.joins.match(/(jt\d+)/g).unique();
148     }
149
150     if (!itemJoinTable) {
151       /* public.item is not already joined. Set the default name. */
152       itemJoinTable = 'sidejoin';
153     }
154
155     /* If customer passed, restrict results to item sites allowed to be sold to that customer */
156     if (customerId) {
157       extra += XT.format(' and %1$I.item_id in (' +
158              'select item_id from item where item_sold and not item_exclusive ' +
159              'union ' +
160              'select item_id from xt.custitem where cust_id=${p2} ' +
161              '  and ${p4}::date between effective and (expires - 1) ', [itemJoinTable]);
162
163       if (shiptoId) {
164         extra += 'union ' +
165                'select item_id from xt.shiptoitem where shipto_id=${p3}::integer ' +
166                '  and ${p4}::date between effective and (expires - 1) ';
167       }
168
169       extra += ") ";
170
171       if (!clause.joins) {
172         clause.joins = '';
173       }
174
175       /* public.item is not already joined. Add it here. */
176       if (itemJoinTable === 'sidejoin') {
177         clause.joins = clause.joins + XT.format(' left join item %1$I on t1.%2$I = %1$I.item_id ', [itemJoinTable, backingTypeJoinColumn]);
178       }
179     }
180
181     /* If vendor passed, and vendor can only supply against defined item sources, then restrict results */
182     if (vendorId) {
183       extra +=  XT.format(' and %1$I.item_id in (' +
184               '  select itemsrc_item_id ' +
185               '  from itemsrc ' +
186               '  where itemsrc_active ' +
187               '    and itemsrc_vend_id=%2$I)', [itemJoinTable, vendorId]);
188
189       if (!clause.joins) {
190         clause.joins = '';
191       }
192
193       /* public.item is not already joined. Add it here. */
194       if (itemJoinTable === 'sidejoin') {
195         clause.joins = clause.joins + XT.format(' left join item %1$I on t1.%2$I = %1$I.item_id ', [itemJoinTable, backingTypeJoinColumn]);
196       }
197     }
198
199     if (query.count) {
200       /* Just get the count of rows that match the conditions */
201       sqlCount = 'select count(distinct t1.%3$I) as count from %1$I.%2$I t1 {joins} where {conditions} {extra};';
202       sqlCount = XT.format(sqlCount, [tableNamespace.decamelize(), table.decamelize(), idColumn, backingTypeJoinColumn]);
203       sqlCount = sqlCount.replace(/{conditions}/g, clause.conditions)
204                          .replace(/{extra}/g, extra)
205                          .replace('{joins}', clause.joins)
206                          .replace(/{p2}/g, clause.parameters.length + 1)
207                          .replace(/{p3}/g, clause.parameters.length + 2)
208                          .replace(/{p4}/g, clause.parameters.length + 3);
209
210       if (customerId) {
211         clause.parameters = clause.parameters.concat([customerId, shiptoId, effectiveDate]);
212       }
213
214       if (DEBUG) {
215         XT.debug('ItemSiteListItem sqlCount = ', sqlCount);
216         XT.debug('ItemSiteListItem values = ', clause.parameters);
217       }
218
219       ret.data = plv8.execute(sqlCount, clause.parameters);
220
221       return ret;
222     }
223
224     sql1 = XT.format(
225       sql1 += ') pt1 group by pt1.id {groupBy} {orderBy} %5$s %6$s;',
226       [tableNamespace, table, idColumn, backingTypeJoinColumn, limit, offset]
227     );
228
229     /* Because we query views of views, you can get inconsistent results */
230     /* when doing limit and offest queries without an order by. Add a default. */
231     if (limit && offset && (!orderBy || !orderBy.length) && !clause.orderByColumns) {
232       /* We only want this on sql1, not sql2's clause.orderBy. */
233       clause.orderByColumns = XT.format('order by t1.%1$I', [idColumn]);
234     }
235
236     /* Set columns to include in sub query unions before replacing table alias. */
237     clause.joinGroupColumns = clause.groupByColumns || '';
238
239     /* Change table reference in group by and order by to pt1. */
240     if (clause.groupByColumns && clause.groupByColumns.length) {
241       clause.groupByColumns = clause.groupByColumns.replace(/t1./g, 'pt1.');
242     }
243     if (clause.orderByColumns && clause.orderByColumns.length) {
244       clause.orderByColumns = clause.orderByColumns.replace(/t1./g, 'pt1.');
245     }
246     if (joinTables.length) {
247       for (var j=0; j < joinTables.length; j++) {
248         var regex = new RegExp(joinTables + '.', 'g');
249         clause.groupByColumns = clause.groupByColumns.replace(regex, 'pt1.');
250         clause.orderByColumns = clause.orderByColumns.replace(regex, 'pt1.');
251       }
252     }
253
254     /* Query the model */
255     sql1 = sql1.replace(/{conditions}/g, clause.conditions)
256              .replace(/{extra}/g, extra)
257              .replace(/{joins}/g, clause.joins)
258              .replace(/{groupBy}/g, clause.groupByColumns)
259              .replace(/{groupColumns}/g, clause.joinGroupColumns)
260              .replace('{orderBy}', clause.orderByColumns)
261              .replace('{accountId}', accountId)
262              .replace(/{p1}/g, clause.parameters.length + 1)
263              .replace(/{p2}/g, clause.parameters.length + (keySearch ? 2 : 1))
264              .replace(/{p3}/g, clause.parameters.length + (keySearch ? 3 : 2))
265              .replace(/{p4}/g, clause.parameters.length + (keySearch ? 4 : 3));
266
267     if (keySearch) {
268       clause.parameters.push(keySearch);
269     }
270     if (customerId) {
271       clause.parameters = clause.parameters.concat([customerId, shiptoId, effectiveDate]);
272     }
273     if (DEBUG) {
274       XT.debug('ItemSiteListItem sql1 = ', sql1.slice(0,500));
275       XT.debug(sql1.slice(500, 1000));
276       XT.debug(sql1.slice(1000, 1500));
277       XT.debug(sql1.slice(1500, 2000));
278       XT.debug(sql1.slice(2000, 2500));
279       XT.debug(sql1.slice(2500, 3000));
280       XT.debug(sql1.slice(3000, 3500));
281       XT.debug(sql1.slice(3500, 4000));
282       XT.debug(sql1.slice(4000, 4500));
283       XT.debug('ItemSiteListItem parameters = ', clause.parameters);
284     }
285     qry = plv8.execute(sql1, clause.parameters);
286
287     if (!qry.length) {
288       ret.data = [];
289       return ret;
290     }
291
292     qry.forEach(function (row) {
293       ids.push(row.id);
294       idParams.push("$" + counter);
295       counter++;
296     });
297
298     if (orm.lockable) {
299       sql_etags = "select ver_etag as etag, ver_record_id as id " +
300                   "from xt.ver " +
301                   "where ver_table_oid = ( " +
302                     "select pg_class.oid::integer as oid " +
303                     "from pg_class join pg_namespace on relnamespace = pg_namespace.oid " +
304                     /* Note: using $L for quoted literal e.g. 'contact', not an identifier. */
305                     "where nspname = %1$L and relname = %2$L " +
306                   ") " +
307                   "and ver_record_id in ({ids})";
308       sql_etags = XT.format(sql_etags, [tableNamespace, table]);
309       sql_etags = sql_etags.replace('{ids}', idParams.join());
310
311       if (DEBUG) {
312         XT.debug('fetch sql_etags = ', sql_etags);
313         XT.debug('fetch etags_values = ', JSON.stringify(ids));
314       }
315       etags = plv8.execute(sql_etags, ids) || {};
316       ret.etags = {};
317     }
318
319     sql2 = XT.format(sql2, [nameSpace.decamelize(), type.decamelize()]);
320     sql2 = sql2.replace(/{orderBy}/g, clause.orderBy)
321                .replace('{ids}', idParams.join());
322
323     if (DEBUG) {
324       XT.debug('fetch sql2 = ', sql2);
325       XT.debug('fetch values = ', JSON.stringify(ids));
326     }
327
328     ret.data = plv8.execute(sql2, ids) || [];
329
330     for (var i = 0; i < ret.data.length; i++) {
331       if (etags) {
332         /* Add etags to result in pkey->etag format. */
333         for (var j = 0; j < etags.length; j++) {
334           if (etags[j].id === ret.data[i][pkey]) {
335             ret.etags[ret.data[i][nkey]] = etags[j].etag;
336           }
337         }
338       }
339     }
340
341     data.sanitize(nameSpace, type, ret.data);
342
343     return ret;
344   };
345
346   if (!XM.ItemSiteListItem) { XM.ItemSiteListItem = {}; }
347
348   XM.ItemSiteListItem.isDispatchable = true;
349
350   /**
351     Returns item site list items using usual query means with additional special support for:
352       * Attributes `customer`,`shipto`, and `effectiveDate` for exclusive item rules.
353       * Attribute `vendor` to filter on only items with associated item sources.
354       * Cross check on `alias` and `barcode` attributes for item numbers.
355
356     @param {String} Record type. Must have `itemsite` or related view as its orm source table.
357     @param {Object} Additional query filter (Optional)
358     @returns {Array}
359   */
360   XM.ItemSiteListItem.fetch = function (query) {
361     var result = XM.ItemSitePrivate.fetch("XM.ItemSiteListItem", "public.itemsite", query);
362     return result.data;
363   };
364
365   /**
366    Wrapper for XM.ItemSitePrivate.fetch with support for REST query formatting.
367    Sample usage:
368     select xt.post('{
369       "nameSpace":"XM",
370       "type":"ItemSiteListItem",
371       "dispatch":{
372         "functionName":"restFetch",
373         "parameters":[
374           {
375             "query":[
376               {"customer":{"EQUALS":"TTOYS"}},
377               {"shipto":{"EQUALS":"1d103cb0-dac6-11e3-9c1a-0800200c9a66"}},
378               {"effectiveDate":{"EQUALS":"2014-05-01"}}
379             ]
380           }
381         ]
382       },
383       "username":"admin",
384       "encryptionKey":"hm6gnf3xsov9rudi"
385     }');
386
387    @param {Object} options: query
388    @returns Object
389   */
390   XM.ItemSiteListItem.restFetch = function (options) {
391     options = options || {};
392
393     var items = {},
394       query = {},
395       result = {};
396
397     if (options) {
398       /* Convert from rest_query to XM.Model.query structure. */
399       query = XM.Model.restQueryFormat("XM.ItemSiteListItem", options);
400
401       /* Perform the query. */
402       return XM.ItemSitePrivate.fetch("XM.ItemSiteListItem", "public.itemsite", query);
403     } else {
404       throw new handleError("Bad Request", 400);
405     }
406   };
407   XM.ItemSiteListItem.restFetch.description = "Returns ItemSiteListItems with additional special support for exclusive item rules, to filter on only items with associated item sources and Cross check on `alias` and `barcode` attributes for item numbers.";
408   XM.ItemSiteListItem.restFetch.request = {
409     "$ref": "ItemSiteListItemQuery"
410   };
411   XM.ItemSiteListItem.restFetch.parameterOrder = ["options"];
412   // For JSON-Schema deff, see:
413   // https://github.com/fge/json-schema-validator/issues/46#issuecomment-14681103
414   XM.ItemSiteListItem.restFetch.schema = {
415     ItemSiteListItemQuery: {
416       properties: {
417         attributes: {
418           title: "ItemSiteListItem Service request attributes",
419           description: "An array of attributes needed to perform a ItemSiteListItem query.",
420           type: "array",
421           items: [
422             {
423               title: "Options",
424               type: "object",
425               "$ref": "ItemSiteListItemOptions"
426             }
427           ],
428           "minItems": 1,
429           "maxItems": 1,
430           required: true
431         }
432       }
433     },
434     ItemSiteListItemOptions: {
435       properties: {
436         query: {
437           title: "query",
438           description: "The query to perform.",
439           type: "array",
440           items: [
441             {
442               title: "column",
443               type: "object"
444             }
445           ],
446           "minItems": 1
447         },
448         orderby: {
449           title: "Order By",
450           description: "The query order by.",
451           type: "array",
452           items: [
453             {
454               title: "column",
455               type: "object"
456             }
457           ]
458         },
459         rowlimit: {
460           title: "Row Limit",
461           description: "The query for paged results.",
462           type: "integer"
463         },
464         maxresults: {
465           title: "Max Results",
466           description: "The query limit for total results.",
467           type: "integer"
468         },
469         pagetoken: {
470           title: "Page Token",
471           description: "The query offset page token.",
472           type: "integer"
473         },
474         count: {
475           title: "Count",
476           description: "Set to true to return only the count of results for this query.",
477           type: "boolean"
478         }
479       }
480     }
481   };
482
483   if (!XM.ItemSiteRelation) { XM.ItemSiteRelation = {}; }
484
485   XM.ItemSiteRelation.isDispatchable = true;
486
487   /**
488     Returns item site relatinos using usual query means with additional special support for:
489       * Attributes `customer`,`shipto`, and `effectiveDate` for exclusive item rules.
490       * Attribute `vendor` to filter on only items with associated item sources.
491       * Cross check on `alias` and `barcode` attributes for item numbers.
492
493     @param {String} Record type. Must have `itemsite` or related view as its orm source table.
494     @param {Object} Additional query filter (Optional)
495     @returns {Array}
496   */
497   XM.ItemSiteRelation.fetch = function (query) {
498     var result = XM.ItemSitePrivate.fetch("XM.ItemSiteRelation", "xt.itemsiteinfo", query);
499     return result.data;
500   };
501
502 }());
503
504 $$ );