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