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