Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge pull request #28 from xtuple/master

Catch up.
  • Loading branch information...
commit 95bcfbc61c98098842db3e64b64fec027419754e 2 parents d926568 + f7626b5
John Rogelstad authored March 22, 2013

Showing 64 changed files with 4,251 additions and 2,045 deletions. Show diff stats Hide diff stats

  1. 3  enyo-client/application/source/en/strings.js
  2. 16  enyo-client/application/source/ext/datasource.js
  3. 3  enyo-client/application/source/models/account.js
  4. 6  enyo-client/application/source/models/address.js
  5. 3  enyo-client/application/source/models/contact.js
  6. 4  enyo-client/application/source/models/currency.js
  7. 4  enyo-client/application/source/models/customer.js
  8. 4  enyo-client/application/source/models/incident.js
  9. 4  enyo-client/application/source/models/item.js
  10. 88  enyo-client/application/source/models/quote.js
  11. 1  enyo-client/application/source/models/to_do.js
  12. 3  enyo-client/application/source/models/url.js
  13. 2  enyo-client/application/source/models/user_account.js
  14. 2  enyo-client/application/source/views/workspace.js
  15. 83  enyo-client/application/source/widgets/parameter.js
  16. 2  enyo-client/application/source/widgets/picker.js
  17. 7  enyo-client/database/orm/models/customer.json
  18. 7  enyo-client/database/orm/models/quote.json
  19. 2  enyo-client/database/source/xm/javascript/item_site.sql
  20. 22  enyo-client/database/source/xt/views/customer_prospect.sql
  21. 67  enyo-client/extensions/source/crm/client/models/customer.js
  22. 1  enyo-client/extensions/source/crm/client/models/package.js
  23. 7  enyo-client/extensions/source/crm/client/views/workspace.js
  24. 61  enyo-client/extensions/source/crm/database/orm/ext/crm.json
  25. 530  enyo-client/extensions/source/crm/database/orm/ext/customer.json
  26. 2  lib/backbone-x/lib/Backbone-relational
  27. 2  lib/backbone-x/lib/backbone
  28. 1  lib/backbone-x/source/alarm.js
  29. 5  lib/backbone-x/source/characteristic.js
  30. 14  lib/backbone-x/source/collection.js
  31. 4  lib/backbone-x/source/document.js
  32. 1,340  lib/backbone-x/source/model.js
  33. 1,506  lib/backbone-x/source/model_mixin.js
  34. 2  lib/backbone-x/source/package.js
  35. 50  lib/backbone-x/source/simple_model.js
  36. 4  lib/enyo-x/source/views/documents_box.js
  37. 2  lib/enyo-x/source/views/list.js
  38. 14  lib/enyo-x/source/views/list_relations_box.js
  39. 4  lib/enyo-x/source/views/navigator.js
  40. 11  lib/enyo-x/source/views/workspace.js
  41. 299  node-datasource/database/orm/models/oauth2.json
  42. 6  node-datasource/database/source/init_global.sql
  43. 19  node-datasource/database/source/xt/tables/oa2client.sql
  44. 9  node-datasource/database/source/xt/tables/oa2clientredirs.sql
  45. 24  node-datasource/database/source/xt/tables/oa2token.sql
  46. 21  node-datasource/lib/ext/datasource.js
  47. 117  node-datasource/lib/ext/models.js
  48. 8  node-datasource/lib/ext/session.js
  49. 84  node-datasource/main.js
  50. 71  node-datasource/oauth2/db/accesstokens.js
  51. 101  node-datasource/oauth2/db/authorizationcodes.js
  52. 95  node-datasource/oauth2/db/clients.js
  53. 946  node-datasource/oauth2/db/connect-xt-pg.js
  54. 306  node-datasource/oauth2/oauth2.js
  55. 27  node-datasource/oauth2/passport.js
  56. 2  node-datasource/oauth2/user.js
  57. 31  node-datasource/oauth2/utils.js
  58. 4  node-datasource/package.json
  59. 102  node-datasource/routes/auth.js
  60. 31  node-datasource/routes/data.js
  61. 92  node-datasource/test/vows/models/customer_prospect_relation.js
  62. 4  node-datasource/test/vows/models/incident.js
  63. 4  node-datasource/views/dialog.ejs
  64. 0  orm/source/xt/functions/add_column.sql b/lib/orm/source/xt/functions/add_column.sql
3  enyo-client/application/source/en/strings.js
@@ -177,6 +177,7 @@ var lang = XT.stringsFor("en_US", {
177 177
   "_error": "Error",
178 178
   "_errorColor": "Error Color",
179 179
   "_eventRecipient": "Event Recipient",
  180
+  "_excludeProspects": "Exclude Prospects",
180 181
   "_expenses": "Expenses",
181 182
   "_expiredColor": "Expired Color",
182 183
   "_expires": "Expires",
@@ -413,8 +414,10 @@ var lang = XT.stringsFor("en_US", {
413 414
   "_shippingNotes": "Shipping Notes",
414 415
   "_site": "Site",
415 416
   "_siteCode": "Site Code",
  417
+  "_showClosed": "Show Closed",
416 418
   "_showCompleted": "Show Complete",
417 419
   "_showCompletedOnly": "Show Complete Only",
  420
+  "_showExpired": "Show Expired",
418 421
   "_showInactive": "Show Inactive",
419 422
   "_sold": "Sold",
420 423
   "_source": "Source",
16  enyo-client/application/source/ext/datasource.js
@@ -19,7 +19,7 @@ white:true*/
19 19
     @param {Object} query
20 20
     @param {Object} options
21 21
     */
22  
-    fetch: function (options) {
  22
+    fetch: function (collection, options) {
23 23
       options = options ? _.clone(options) : {};
24 24
       var that = this,
25 25
         payload = {},
@@ -42,7 +42,7 @@ white:true*/
42 42
           // currently dealing with two different protocols for response formatting
43 43
           dataHash = response.data.rows ? JSON.parse(response.data.rows[0].fetch) : response.data;
44 44
           if (options && options.success) {
45  
-            options.success.call(that, dataHash);
  45
+            options.success.call(that, collection, dataHash, options);
46 46
           }
47 47
         };
48 48
 
@@ -96,7 +96,7 @@ white:true*/
96 96
     @param {Number} id
97 97
     @param {Object} options
98 98
     */
99  
-    retrieveRecord: function (recordType, id, options) {
  99
+    retrieveRecord: function (model, options) {
100 100
       var that = this,
101 101
         payload = {},
102 102
         complete = function (response) {
@@ -126,13 +126,13 @@ white:true*/
126 126
 
127 127
           // Handle success
128 128
           if (options && options.success) {
129  
-            options.success.call(that, dataHash);
  129
+            options.success.call(that, model, dataHash, options);
130 130
           }
131 131
         };
132 132
 
133 133
       payload.requestType = 'retrieveRecord';
134  
-      payload.recordType = recordType;
135  
-      payload.id = id;
  134
+      payload.recordType = model.recordType;
  135
+      payload.id = options.id || model.id;
136 136
       payload.databaseType = options.databaseType;
137 137
       payload.options = { context: options.context };
138 138
 
@@ -169,7 +169,7 @@ white:true*/
169 169
           dataHash = response.data.rows ? JSON.parse(response.data.rows[0].commit_record) : response.data;
170 170
           //dataHash = JSON.parse(response.data.rows[0].commit_record);
171 171
           if (options && options.success) {
172  
-            options.success.call(that, dataHash);
  172
+            options.success.call(that, model, dataHash, options);
173 173
           }
174 174
         };
175 175
 
@@ -222,7 +222,7 @@ white:true*/
222 222
           dataHash = response.data.rows ? JSON.parse(response.data.rows[0].dispatch) : response.data;
223 223
           //dataHash = JSON.parse(response.data.rows[0].dispatch);
224 224
           if (options && options.success) {
225  
-            options.success.call(that, dataHash);
  225
+            options.success.call(that, dataHash, options);
226 226
           }
227 227
         };
228 228
 
3  enyo-client/application/source/models/account.js
@@ -81,10 +81,11 @@ white:true*/
81 81
     // METHODS
82 82
     //
83 83
 
84  
-    validateEdit: function (attributes) {
  84
+    validate: function (attributes) {
85 85
       if (attributes.parent && attributes.parent.id === this.id) {
86 86
         return XT.Error.clone('xt2006');
87 87
       }
  88
+      return XM.Document.prototype.validate.apply(this, arguments);
88 89
     }
89 90
 
90 91
   });
6  enyo-client/application/source/models/address.js
@@ -65,7 +65,7 @@ white:true*/
65 65
       return this;
66 66
     },
67 67
 
68  
-    validateEdit: function (attributes, options) {
  68
+    validate: function (attributes, options) {
69 69
       var settings = XT.session.getSettings(),
70 70
         strict = settings.get('StrictAddressCountry'),
71 71
         country = attributes.country,
@@ -80,6 +80,7 @@ white:true*/
80 80
           return XT.Error.clone('xt2008');
81 81
         }
82 82
       }
  83
+      return XM.Document.prototype.validate.apply(this, arguments);
83 84
     }
84 85
 
85 86
   };
@@ -284,7 +285,7 @@ white:true*/
284 285
     // METHODS
285 286
     //
286 287
 
287  
-    validateEdit: function (attributes) {
  288
+    validate: function (attributes) {
288 289
       var params = {};
289 290
 
290 291
       if (attributes.abbreviation &&
@@ -300,6 +301,7 @@ white:true*/
300 301
         params.length = "3";
301 302
         return XT.Error.clone('xt1006', { params: params });
302 303
       }
  304
+      return XM.Document.prototype.validate.apply(this, arguments);
303 305
     }
304 306
 
305 307
   });
3  enyo-client/application/source/models/contact.js
@@ -67,10 +67,11 @@ white:true*/
67 67
       return name.join(' ');
68 68
     },
69 69
 
70  
-    validateSave: function (attributes, options) {
  70
+    validate: function (attributes, options) {
71 71
       if (!attributes.firstName && !attributes.lastName) {
72 72
         return XT.Error.clone('xt2004');
73 73
       }
  74
+      return XM.Document.prototype.validate.apply(this, arguments);
74 75
     }
75 76
 
76 77
   });
4  enyo-client/application/source/models/currency.js
@@ -41,7 +41,6 @@ white:true*/
41 41
     abbreviationDidChange: function (model, value, options) {
42 42
       var that = this,
43 43
         checkOptions = {};
44  
-      if (this.isNotReady()) { return; }
45 44
 
46 45
       checkOptions.success = function (resp) {
47 46
         var err, params = {};
@@ -244,7 +243,7 @@ white:true*/
244 243
       return this.get('abbreviation') + ' - ' + this.get('symbol');
245 244
     },
246 245
 
247  
-    validateEdit: function (attributes) {
  246
+    validate: function (attributes) {
248 247
       var params = {};
249 248
       if (attributes.abbreviation &&
250 249
           attributes.abbreviation.length !== 3) {
@@ -252,6 +251,7 @@ white:true*/
252 251
         params.length = "3";
253 252
         return XT.Error.clone('xt1006', { params: params });
254 253
       }
  254
+      return XM.Document.prototype.validate.apply(this, arguments);
255 255
     }
256 256
 
257 257
   });
4  enyo-client/application/source/models/customer.js
@@ -276,7 +276,7 @@ white:true*/
276 276
     
277 277
     salesRepDidChange: function () {
278 278
       var salesRep = this.get('salesRep');
279  
-      if (!salesRep || this.isNotReady()) { return; }
  279
+      if (!salesRep) { return; }
280 280
       this.set('commission', salesRep.get('commission'));
281 281
     }
282 282
 
@@ -484,7 +484,7 @@ white:true*/
484 484
 
485 485
     salesRepDidChange: function () {
486 486
       var salesRep = this.get('salesRep');
487  
-      if (!salesRep || this.isNotReady()) { return; }
  487
+      if (!salesRep) { return; }
488 488
       this.set('commission', salesRep.get('commission'));
489 489
     }
490 490
 
4  enyo-client/application/source/models/incident.js
@@ -182,15 +182,15 @@ white:true*/
182 182
     },
183 183
 
184 184
     assignedToDidChange: function (model, value, options) {
185  
-      if (this.isNotReady()) { return; }
186 185
       if (value) { this.set('status', XM.Incident.ASSIGNED); }
187 186
     },
188 187
 
189  
-    validateSave: function () {
  188
+    validate: function () {
190 189
       var K = XM.Incident;
191 190
       if (this.get('status') === K.ASSIGNED && !this.get('assignedTo')) {
192 191
         return XT.Error.clone('xt2001');
193 192
       }
  193
+      return XM.Document.prototype.validate.apply(this, arguments);
194 194
     },
195 195
 
196 196
     save: function (key, value, options) {
4  enyo-client/application/source/models/item.js
@@ -212,7 +212,6 @@ white:true*/
212 212
     },
213 213
 
214 214
     inventoryUnitDidChange: function (model, value, options) {
215  
-      if (this.isNotReady()) { return; }
216 215
       if (value) { this.set('priceUnit', value); }
217 216
     },
218 217
 
@@ -234,12 +233,13 @@ white:true*/
234 233
       }
235 234
     },
236 235
 
237  
-    validateSave: function () {
  236
+    validate: function () {
238 237
       var isSold = this.get('isSold'),
239 238
         productCategory = this.get('productCategory');
240 239
       if (isSold && (productCategory.id || -1) === -1) {
241 240
         return XT.Error.clone('xt2005');
242 241
       }
  242
+      return XM.Document.prototype.validate.apply(this, arguments);
243 243
     }
244 244
 
245 245
   });
88  enyo-client/application/source/models/quote.js
@@ -5,6 +5,27 @@ white:true*/
5 5
 
6 6
 (function () {
7 7
   "use strict";
  8
+  
  9
+  /**
  10
+    @namespace
  11
+
  12
+    A mixin shared by project models that share common project status
  13
+    functionality.
  14
+  */
  15
+  XM.QuoteStatus = {
  16
+    /** @scope XM.ProjectStatus */
  17
+
  18
+    /**
  19
+    Returns project status as a localized string.
  20
+
  21
+    @returns {String}
  22
+    */
  23
+    getProjectStatusString: function () {
  24
+      var K = XM.Quote,
  25
+        status = this.get("status");
  26
+      return status === K.OPEN_STATUS ? "_open".loc() : "_closed".loc();
  27
+    }
  28
+  };
8 29
 
9 30
   /**
10 31
     @class
@@ -172,8 +193,6 @@ white:true*/
172 193
         siteClass = [],
173 194
         i;
174 195
 
175  
-      if (this.isNotReady()) { return this; }
176  
-
177 196
       if (customer && currency && docDate && lineItems.length) {
178 197
         // Collect data needed for freight
179 198
         for (i = 0; i < lineItems.length; i++) {
@@ -272,8 +291,6 @@ white:true*/
272 291
         dispOptions = {},
273 292
         params;
274 293
 
275  
-      if (this.isNotReady()) { return this; }
276  
-
277 294
       if (effective && currency && amount) {
278 295
         params = [taxZoneId, taxTypeId, effective, currency.id, amount];
279 296
         dispOptions.success = function (resp) {
@@ -295,8 +312,6 @@ white:true*/
295 312
       var lineItems = this.get("lineItems").models,
296 313
         scheduleDate;
297 314
 
298  
-      if (this.isNotReady()) { return this; }
299  
-
300 315
       if (lineItems.length) {
301 316
         _.each(lineItems, function (line) {
302 317
           var lineSchedDate = line.get("scheduleDate");
@@ -321,8 +336,6 @@ white:true*/
321 336
     calculateTotals: function (calcFreight) {
322 337
       var calculateFreight = this.get("calculateFreight");
323 338
 
324  
-      if (this.isNotReady()) { return this; }
325  
-
326 339
       if (calculateFreight && calcFreight !== false) {
327 340
         this.calculateFreight();
328 341
       } else {
@@ -395,8 +408,6 @@ white:true*/
395 408
         this.setReadOnly(this.shiptoAttrArray[i], isFreeFormShipto);
396 409
       }
397 410
 
398  
-      if (this.isNotReady()) { return; }
399  
-
400 411
       // Set customer default data
401 412
       if (customer) {
402 413
         billtoAttrs = {
@@ -492,23 +503,11 @@ white:true*/
492 503
     },
493 504
 
494 505
     /**
495  
-    Returns quote status as a localized string.
496  
-
497  
-    @returns {String}
498  
-    */
499  
-    getQuoteStatusString: function () {
500  
-      var K = this.getClass(),
501  
-        status = this.get("status");
502  
-      return status === K.OPEN_STATUS ? "_open".loc() : "_closed".loc();
503  
-    },
504  
-
505  
-    /**
506 506
       If the user changed the freight determine whether they want the automatic calculation
507 507
       turned on or off as a result of their change. This function will trigger a `notify` call
508 508
       asking the question, which must be answered via the attached callback to complete the process.
509 509
     */
510 510
     freightDidChange: function () {
511  
-      if (this.isNotReady()) { return; }
512 511
       var calculateFreight = this.get("calculateFreight"),
513 512
         freight = this.get("freight"),
514 513
         that = this,
@@ -579,7 +578,7 @@ white:true*/
579 578
         options = {},
580 579
         that = this;
581 580
 
582  
-      if (this.isNotReady || !lineItems.length) { return; }
  581
+      if (!lineItems.length) { return; }
583 582
 
584 583
       options.type = XM.Model.QUESTION;
585 584
 
@@ -650,7 +649,7 @@ white:true*/
650 649
         shiptoAddress = shiptoContact ? shiptoContact.get("address") : false,
651 650
         shiptoAttrs;
652 651
 
653  
-      if (this.isNotReady() || !shipto) { return; }
  652
+      if (!shipto) { return; }
654 653
 
655 654
       shiptoAttrs = {
656 655
         shiptoName: shipto.get("name"),
@@ -692,12 +691,10 @@ white:true*/
692 691
 
693 692
     shiptoAddressDidChange: function () {
694 693
       // If the address was manually changed, then clear shipto
695  
-      if (this.isNotReady()) { return; }
696 694
       this.unset("shipto");
697 695
     },
698 696
 
699 697
     siteDidChange: function () {
700  
-      if (this.isNotReady()) { return; }
701 698
       var fob = this.getValue("site.fob") || "";
702 699
       this.set("fob", fob);
703 700
     },
@@ -710,7 +707,7 @@ white:true*/
710 707
       }
711 708
     },
712 709
 
713  
-    validateSave: function () {
  710
+    validate: function () {
714 711
       var customer = this.get("customer"),
715 712
         shipto = this.get("shipto"),
716 713
         total = this.get("total"),
@@ -729,6 +726,8 @@ white:true*/
729 726
       if (!lineItems.length) {
730 727
         return XT.Error.clone('xt2012');
731 728
       }
  729
+      
  730
+      return XM.Document.prototype.validate.apply(this, arguments);
732 731
     },
733 732
 
734 733
     // ..........................................................
@@ -818,6 +817,9 @@ white:true*/
818 817
     }
819 818
 
820 819
   });
  820
+  
  821
+  // Add in quote status mixin
  822
+  XM.Quote = XM.Quote.extend(XM.QuoteStatus);
821 823
 
822 824
   // ..........................................................
823 825
   // CLASS METHODS
@@ -975,8 +977,6 @@ white:true*/
975 977
         price = this.get("price"),
976 978
         options = {};
977 979
 
978  
-      if (this.isNotReady()) { return; }
979  
-
980 980
       options.success = function (basePrice) {
981 981
         var K = that.getClass(),
982 982
           priceMode = that.get("priceMode"),
@@ -1044,7 +1044,7 @@ white:true*/
1044 1044
         currency = parent ? parent.get("currency") :false;
1045 1045
 
1046 1046
       // If no parent, don't bother
1047  
-      if (!parent || this.isNotReady()) { return; }
  1047
+      if (!parent) { return; }
1048 1048
 
1049 1049
       // Make sure we have necessary values
1050 1050
       if (canUpdate && customer && currency &&
@@ -1083,8 +1083,6 @@ white:true*/
1083 1083
         that = this,
1084 1084
         options = {};
1085 1085
 
1086  
-      if (this.isNotReady()) { return; }
1087  
-
1088 1086
       if (price) {
1089 1087
         if (standardCost) {
1090 1088
           options.success = function (value) {
@@ -1112,7 +1110,7 @@ white:true*/
1112 1110
         params;
1113 1111
 
1114 1112
       // If no parent, don't bother
1115  
-      if (!parent || this.isNotReady()) { return; }
  1113
+      if (!parent) { return; }
1116 1114
 
1117 1115
       recordType = parent.recordType;
1118 1116
       taxZoneId = parent.getValue("taxZone.id");
@@ -1156,8 +1154,6 @@ white:true*/
1156 1154
         discounted,
1157 1155
         price;
1158 1156
 
1159  
-      if (this.isNotReady()) { return; }
1160  
-
1161 1157
       if (!customerPrice) {
1162 1158
         this.unset("discount");
1163 1159
       } else if (this._updatePrice) {
@@ -1199,8 +1195,6 @@ white:true*/
1199 1195
         item.sellingUnits(unitOptions);
1200 1196
       }
1201 1197
 
1202  
-      if (this.isNotReady()) { return; }
1203  
-
1204 1198
       // Reset values
1205 1199
       this.unset("quantityUnit");
1206 1200
       this.unset("priceUnit");
@@ -1286,8 +1280,6 @@ white:true*/
1286 1280
        lineNumber = this.get("lineNumber"),
1287 1281
        scheduleDate;
1288 1282
 
1289  
-      if (this.isNotReady()) { return; }
1290  
-
1291 1283
       // Set next line number
1292 1284
       if (parent && !lineNumber) {
1293 1285
         this.set("lineNumber", parent.get("lineItems").length);
@@ -1325,8 +1317,8 @@ white:true*/
1325 1317
         inventoryUnit = item ? this.getValue("inventoryUnit") : false,
1326 1318
         that = this,
1327 1319
         options = {};
1328  
-        
1329  
-      if (!inventoryUnit || !quantityUnit || !priceUnit || this.isNotReady()) { return; }
  1320
+    
  1321
+      if (!inventoryUnit || !quantityUnit || !priceUnit) { return; }
1330 1322
       
1331 1323
       if (inventoryUnit.id === priceUnit.id) {
1332 1324
         this.set("priceUnitRatio", 1);
@@ -1353,7 +1345,7 @@ white:true*/
1353 1345
         isFractionalCache,
1354 1346
         ratioCache;
1355 1347
 
1356  
-      if (!inventoryUnit || !quantityUnit || this.isNotReady()) { return; }
  1348
+      if (!inventoryUnit || !quantityUnit) { return; }
1357 1349
 
1358 1350
       if (quantityUnit.id === item.get("inventoryUnit").id) {
1359 1351
         this.set("quantityUnitRatio", 1);
@@ -1427,8 +1419,6 @@ white:true*/
1427 1419
         that = this,
1428 1420
         options = {};
1429 1421
 
1430  
-      if (this.isNotReady()) { return; }
1431  
-
1432 1422
       if (customer && item && scheduleDate) {
1433 1423
         options.success = function (canPurchase) {
1434 1424
           if (!canPurchase) {
@@ -1455,7 +1445,7 @@ white:true*/
1455 1445
       }
1456 1446
     },
1457 1447
 
1458  
-    validateSave: function () {
  1448
+    validate: function () {
1459 1449
       var quantity = this.get("quantity");
1460 1450
 
1461 1451
       // Check quantity
@@ -1467,7 +1457,8 @@ white:true*/
1467 1457
       if (!this._unitIsFractional && Math.round(quantity) !== quantity) {
1468 1458
         return XT.Error.clone('xt2014');
1469 1459
       }
1470  
-
  1460
+      
  1461
+      return XM.Document.prototype.validate.apply(this, arguments);
1471 1462
     },
1472 1463
 
1473 1464
     /** @private
@@ -1746,12 +1737,15 @@ white:true*/
1746 1737
     @returns {String}
1747 1738
     */
1748 1739
     getQuoteStatusString: function () {
1749  
-      var K = this.getClass(),
  1740
+      var K = XM.Quote,
1750 1741
         status = this.get("status");
1751 1742
       return status === K.OPEN_STATUS ? "_open".loc() : "_closed".loc();
1752 1743
     }
1753 1744
 
1754 1745
   });
  1746
+  
  1747
+  // Add in quote status mixin
  1748
+  XM.QuoteListItem = XM.QuoteListItem.extend(XM.QuoteStatus);
1755 1749
 
1756 1750
   /**
1757 1751
     @class
1  enyo-client/application/source/models/to_do.js
@@ -106,7 +106,6 @@ white:true*/
106 106
         completeDate = this.get('completeDate'),
107 107
         K = XM.ToDo,
108 108
         attrStatus = K.NEITHER;
109  
-      if (this.isNotReady()) { return; }
110 109
 
111 110
       // Set the `status` attribute with appropriate value
112 111
       if (completeDate) {
3  enyo-client/application/source/models/url.js
@@ -28,10 +28,11 @@ white:true*/
28 28
       path: 'http:'
29 29
     },
30 30
     
31  
-    validateSave: function (attributes, options) {
  31
+    validate: function (attributes, options) {
32 32
       if (!this.validateUrl(attributes.path)) {
33 33
         return XT.Error.clone('xt2009');
34 34
       }
  35
+      return XM.Document.prototype.validate.apply(this, arguments);
35 36
     },
36 37
     
37 38
     validateUrl: function (value) {
2  enyo-client/application/source/models/user_account.js
@@ -173,8 +173,6 @@ white:true*/
173 173
         return;
174 174
       }
175 175
 
176  
-      if (this.isNotReady()) { return; }
177  
-
178 176
       // Check for conflicts
179 177
       if (this.checkConflicts && value && this.isDirty() && !this._number) {
180 178
         options.success = function (resp) {
2  enyo-client/application/source/views/workspace.js
@@ -1524,9 +1524,9 @@ trailing:true white:true*/
1524 1524
           {kind: "onyx.GroupboxHeader", content: "_overview".loc()},
1525 1525
           {kind: "XV.ScrollableGroupbox", name: "mainGroup", fit: true,
1526 1526
             classes: "in-panel", components: [
1527  
-            {kind: "XV.CheckboxWidget", attr: "isActive"},
1528 1527
             {kind: "XV.InputWidget", attr: "name"},
1529 1528
             {kind: "XV.InputWidget", attr: "description"},
  1529
+            {kind: "XV.CheckboxWidget", attr: "isActive"},
1530 1530
             {kind: "XV.PriorityPicker", attr: "priority"},
1531 1531
             {kind: "onyx.GroupboxHeader", content: "_schedule".loc()},
1532 1532
             {kind: "XV.DateWidget", attr: "dueDate"},
83  enyo-client/application/source/widgets/parameter.js
@@ -532,34 +532,61 @@ trailing:true white:true*/
532 532
     kind: "XV.ParameterWidget",
533 533
     components: [
534 534
       {kind: "onyx.GroupboxHeader", content: "_quote".loc()},
535  
-      // {name: "showInactive", label: "_showInactive".loc(), attr: "isActive", defaultKind: "XV.CheckboxWidget",
536  
-      //   getParameter: function () {
537  
-      //     var param;
538  
-      //     if (!this.getValue()) {
539  
-      //       param = {
540  
-      //         attribute: this.getAttr(),
541  
-      //         operator: '=',
542  
-      //         value: true
543  
-      //       };
544  
-      //     }
545  
-      //     return param;
546  
-      //   }
547  
-      // },
548  
-      // {name: "name", label: "_name".loc(), attr: "name"},
549  
-      // {name: "description", label: "_description".loc(), attr: "description"},
550  
-      // {kind: "onyx.GroupboxHeader", content: "_relationships".loc()},
551  
-      // {name: "account", label: "_account".loc(), attr: "account", defaultKind: "XV.AccountWidget"},
552  
-      // {name: "contact", label: "_contact".loc(), attr: "contact", defaultKind: "XV.ContactWidget"},
553  
-      // {kind: "onyx.GroupboxHeader", content: "_userAccounts".loc()},
554  
-      // {name: "owner", label: "_owner".loc(), attr: "owner", defaultKind: "XV.UserAccountWidget"},
555  
-      // {name: "assignedTo", label: "_assignedTo".loc(), attr: "assignedTo", defaultKind: "XV.UserAccountWidget"},
556  
-      // {kind: "onyx.GroupboxHeader", content: "_dueDate".loc()},
557  
-      // {name: "fromDate", label: "_fromDate".loc(), attr: "dueDate", operator: ">=",
558  
-      //   filterLabel: "_from".loc() + " " + "_dueDate".loc() + " " + "_date".loc(),
559  
-      //   defaultKind: "XV.DateWidget"},
560  
-      // {name: "toDate", label: "_toDate".loc(), attr: "dueDate", operator: "<=",
561  
-      //   filterLabel: "_to".loc() + " " + "_dueDate".loc() + " " + "_date".loc(),
562  
-      //   defaultKind: "XV.DateWidget"}
  535
+      {name: "showExpired", label: "_showExpired".loc(), attr: "expireDate", defaultKind: "XV.CheckboxWidget",
  536
+        getParameter: function () {
  537
+          var param;
  538
+          if (!this.getValue()) {
  539
+            param = {
  540
+              attribute: this.getAttr(),
  541
+              operator: '>=',
  542
+              value: new Date()
  543
+            };
  544
+          }
  545
+          return param;
  546
+        }
  547
+      },
  548
+      {name: "showClosed", label: "_showClosed".loc(), attr: "status", defaultKind: "XV.CheckboxWidget",
  549
+        getParameter: function () {
  550
+          var param;
  551
+          if (!this.getValue()) {
  552
+            param = {
  553
+              attribute: this.getAttr(),
  554
+              operator: '!=',
  555
+              value: 'C'
  556
+            };
  557
+          }
  558
+          return param;
  559
+        }
  560
+      },
  561
+      {name: "excludeProspects", label: "_excludeProspects".loc(), attr: "customer.status", defaultKind: "XV.CheckboxWidget",
  562
+        getParameter: function () {
  563
+          var param;
  564
+          if (this.getValue()) {
  565
+            param = {
  566
+              attribute: this.getAttr(),
  567
+              operator: '!=',
  568
+              value: "P"
  569
+            };
  570
+          }
  571
+          return param;
  572
+        }
  573
+      },
  574
+      {name: "number", label: "_number".loc(), attr: "number"},
  575
+      {name: "salesRep", attr: "salesRep", label: "_salesRep".loc(), defaultKind: "XV.SalesRepPicker"},
  576
+      {kind: "onyx.GroupboxHeader", content: "_customer".loc()},
  577
+      {name: "customer", attr: "customer", label: "_customer".loc(), defaultKind: "XV.CustomerProspectWidget"},
  578
+      {name: "customerType", attr: "customer.customerType", label: "_customerType".loc(), defaultKind: "XV.CustomerTypePicker"},
  579
+      {name: "customerPurchaseOrderNumber", attr: "customerPurchaseOrderNumber",
  580
+        label: "_custPO".loc()},
  581
+      {kind: "onyx.GroupboxHeader", content: "_quoteDate".loc()},
  582
+      {name: "createdFromDate", label: "_fromDate".loc(),
  583
+        filterLabel: "_quoteDate".loc() + " " + "_fromDate".loc(),
  584
+        attr: "quoteDate", operator: ">=",
  585
+        defaultKind: "XV.DateWidget"},
  586
+      {name: "createdToDate", label: "_toDate".loc(),
  587
+        filterLabel: "_quoteDate".loc() + " " + "_toDate".loc(),
  588
+        attr: "quoteDate", operator: "<=",
  589
+        defaultKind: "XV.DateWidget"}
563 590
     ]
564 591
   });
565 592
 
2  enyo-client/application/source/widgets/picker.js
@@ -264,7 +264,7 @@ regexp:true, undef:true, trailing:true, white:true */
264 264
     kind: "XV.PickerWidget",
265 265
     collection: "XM.projectStatuses"
266 266
   });
267  
-
  267
+  
268 268
   // ..........................................................
269 269
   // SALES REP
270 270
   //
7  enyo-client/database/orm/models/customer.json
@@ -1818,6 +1818,13 @@
1818 1818
           "type": "String",
1819 1819
           "column": "status"
1820 1820
         }
  1821
+      },
  1822
+      {
  1823
+        "name": "customerType",
  1824
+        "attr": {
  1825
+          "type": "Number",
  1826
+          "column": "type"
  1827
+        }
1821 1828
       }
1822 1829
     ],
1823 1830
     "extensions": [],
7  enyo-client/database/orm/models/quote.json
@@ -1334,6 +1334,13 @@
1334 1334
          }
1335 1335
       },
1336 1336
       {
  1337
+        "name": "salesRep",
  1338
+        "attr": {
  1339
+          "type": "Number",
  1340
+          "column": "quhead_salesrep_id"
  1341
+         }
  1342
+      },
  1343
+      {
1337 1344
         "name": "shiptoName",
1338 1345
         "attr": {
1339 1346
           "type": "String",
2  enyo-client/database/source/xm/javascript/item_site.sql
@@ -35,7 +35,7 @@ select xt.install_js('XM','item_site','xtuple', $$
35 35
     if(!XT.Data.checkPrivileges(nameSpace, type)) { return []; };
36 36
 
37 37
     /* Restrict results to items that are associated with the customer and/or shipto */ 
38  
-    var custItemSql = 'select * from custitem($1, $2, $3);';
  38
+    var custItemSql = 'select * from custitem($1, $2::integer, $3::date);';
39 39
     var allowedArray = plv8.execute(custItemSql, [customerId, shiptoId, effectiveDate]);
40 40
     var allowedIds = [];
41 41
     for(var i = 0; i < allowedArray.length; i++) {
22  enyo-client/database/source/xt/views/customer_prospect.sql
... ...
@@ -1,20 +1,20 @@
1 1
 drop view if exists xt.customer_prospect cascade;
2 2
 
3 3
 create or replace view xt.customer_prospect as
4  
-  
5  
-  select cust_id as id, cust_active as active, cust_number as number, cust_name as name, 
6  
-    cust_cntct_id as contact, cust_ffshipto, cust_ffbillto, cust_curr_id, cust_terms_id, 
7  
-    cust_creditstatus, cust_salesrep_id as salesrep_id, cust_commprcnt, cust_discntprcnt, 
8  
-    cust_taxzone_id as taxzone_id, cust_shipchrg_id, cust_comments as comments, 
9  
-    cust_preferred_warehous_id as site, shipto_id as default_shipto_id, cust_shipvia as cust_shipvia, 
  4
+
  5
+  select cust_id as id, cust_active as active, cust_number as number, cust_name as name, cust_custtype_id as type,
  6
+    cust_cntct_id as contact, cust_ffshipto, cust_ffbillto, cust_curr_id, cust_terms_id,
  7
+    cust_creditstatus, cust_salesrep_id as salesrep_id, cust_commprcnt, cust_discntprcnt,
  8
+    cust_taxzone_id as taxzone_id, cust_shipchrg_id, cust_comments as comments,
  9
+    cust_preferred_warehous_id as site, shipto_id as default_shipto_id, cust_shipvia as cust_shipvia,
10 10
     'C' as status
11 11
   from custinfo left join shiptoinfo on cust_id = shipto_cust_id and shipto_default
12 12
   union
13 13
   select prospect_id as id, prospect_active as active, prospect_number as number,
14  
-    prospect_name as name, prospect_cntct_id as contact, null as cust_ffshipto, 
15  
-    null as cust_ffbillto, null as cust_curr_id, null as cust_terms_id, 
16  
-    null as cust_creditstatus, prospect_salesrep_id as salesrep_id, null as cust_commprcnt, 
17  
-    null as cust_discntprcnt, null as taxzone_id, null as cust_shipchrg_id, 
18  
-    prospect_comments as comments, prospect_warehous_id as site, null as default_shipto_id, 
  14
+    prospect_name as name, null as type, prospect_cntct_id as contact, null as cust_ffshipto,
  15
+    null as cust_ffbillto, null as cust_curr_id, null as cust_terms_id,
  16
+    null as cust_creditstatus, prospect_salesrep_id as salesrep_id, null as cust_commprcnt,
  17
+    null as cust_discntprcnt, null as taxzone_id, null as cust_shipchrg_id,
  18
+    prospect_comments as comments, prospect_warehous_id as site, null as default_shipto_id,
19 19
     null as cust_shipvia, 'P' as status
20 20
   from prospect;
67  enyo-client/extensions/source/crm/client/models/customer.js
... ...
@@ -0,0 +1,67 @@
  1
+/*jshint indent:2, curly:true eqeqeq:true, immed:true, latedef:true,
  2
+newcap:true, noarg:true, regexp:true, undef:true, strict:true, trailing:true
  3
+white:true*/
  4
+/*global XT:true, XM:true, Backbone:true, _:true, console:true */
  5
+
  6
+(function () {
  7
+  "use strict";
  8
+
  9
+  XT.extensions.crm.initCustomerModels = function () {
  10
+    /**
  11
+      @class
  12
+
  13
+      @extends XM.Model
  14
+    */
  15
+    XM.CustomerContactRelation = XM.Info.extend(
  16
+      /** @scope XM.CustomerContactRelation.prototype */ {
  17
+
  18
+      recordType: 'XM.CustomerContactRelation',
  19
+      
  20
+      editableModel: 'XM.Contact'
  21
+
  22
+    });
  23
+
  24
+    /**
  25
+      @class
  26
+
  27
+      @extends XM.Model
  28
+    */
  29
+    XM.CustomerToDoRelation = XM.Info.extend(
  30
+      /** @scope XM.CustomerToDoRelation.prototype */ {
  31
+
  32
+      recordType: 'XM.CustomerToDoRelation',
  33
+      
  34
+      editableModel: 'XM.ToDo'
  35
+
  36
+    });
  37
+
  38
+    /**
  39
+      @class
  40
+
  41
+      @extends XM.Model
  42
+    */
  43
+    XM.CustomerIncidentRelation = XM.Info.extend(
  44
+      /** @scope XM.CustomerIncidentRelation.prototype */ {
  45
+
  46
+      recordType: 'XM.CustomerIncidentRelation',
  47
+      
  48
+      editableModel: 'XM.Incident'
  49
+
  50
+    });
  51
+
  52
+    /**
  53
+      @class
  54
+
  55
+      @extends XM.Model
  56
+    */
  57
+    XM.CustomerOpportunityRelation = XM.Info.extend(
  58
+      /** @scope XM.CustomerOpportunityRelation.prototype */ {
  59
+
  60
+      recordType: 'XM.CustomerOpportunityRelation',
  61
+      
  62
+      editableModel: 'XM.Opportunity'
  63
+
  64
+    });
  65
+  };
  66
+
  67
+}());
1  enyo-client/extensions/source/crm/client/models/package.js
@@ -3,6 +3,7 @@ depends(
3 3
   "configure.js",
4 4
   "account.js",
5 5
   "contact.js",
  6
+  "customer.js",
6 7
   "incident.js",
7 8
   "opportunity.js",
8 9
   "to_do.js",
7  enyo-client/extensions/source/crm/client/views/workspace.js
@@ -104,6 +104,8 @@ trailing:true white:true*/
104 104
     //
105 105
   
106 106
     extensions = [
  107
+      {kind: "XV.AccountContactsBox", container: "panels",
  108
+        attr: "contactRelations", parentAttr: "account"},
107 109
       {kind: "XV.AccountToDosBox", container: "panels",
108 110
         attr: "toDoRelations", parentAttr: "account"},
109 111
       {kind: "XV.AccountOpportunitiesBox", container: "panels",
@@ -113,6 +115,11 @@ trailing:true white:true*/
113 115
     ];
114 116
 
115 117
     XV.appendExtension("XV.CustomerWorkspace", extensions);
  118
+    
  119
+    XV.registerModelWorkspace("XM.CustomerContactRelation", "XV.ContactWorkspace");
  120
+    XV.registerModelWorkspace("XM.CustomerIncidentRelation", "XV.IncidentWorkspace");
  121
+    XV.registerModelWorkspace("XM.CustomerOpportunityRelation", "XV.OpportunityWorkspace");
  122
+    XV.registerModelWorkspace("XM.CustomerToDoRelation", "XV.ToDoWorkspace");
116 123
 
117 124
     // ..........................................................
118 125
     // INCIDENT
61  enyo-client/extensions/source/crm/database/orm/ext/crm.json
@@ -156,67 +156,6 @@
156 156
   {
157 157
     "context": "crm",
158 158
     "nameSpace": "XM",
159  
-    "type": "Customer",
160  
-    "table": "crmacct",
161  
-    "isExtension": true,
162  
-    "isChild": true,
163  
-    "comment": "Extended by Crm",
164  
-    "relations": [
165  
-      {
166  
-        "column": "crmacct_cust_id",
167  
-        "inverse": "id"
168  
-      }
169  
-    ],
170  
-    "properties": [
171  
-     {
172  
-        "name": "account",
173  
-        "attr": {
174  
-         "type": "Number",
175  
-         "column": "crmacct_id"
176  
-        }
177  
-     },
178  
-     {
179  
-        "name": "contactRelations",
180  
-        "toMany": {
181  
-          "isNested": true,
182  
-          "type": "ContactRelation",
183  
-          "column": "crmacct_id",
184  
-          "inverse": "account"
185  
-        }
186  
-      },
187  
-      {
188  
-        "name": "incidentRelations",
189  
-        "toMany": {
190  
-          "isNested": true,
191  
-          "type": "IncidentRelation",
192  
-          "column": "crmacct_id",
193  
-          "inverse": "account"
194  
-        }
195  
-      },
196  
-      {
197  
-        "name": "opportunityRelations",
198  
-        "toMany": {
199  
-          "isNested": true,
200  
-          "type": "OpportunityRelation",
201  
-          "column": "crmacct_id",
202  
-          "inverse": "account"
203  
-        }
204  
-      },
205  
-      {
206  
-        "name": "toDoRelations",
207  
-        "toMany": {
208  
-          "isNested": true,
209  
-          "type": "ToDoRelation",
210  
-          "column": "crmacct_id",
211  
-          "inverse": "account"
212  
-        }
213  
-      }
214  
-    ],
215  
-    "isSystem": true
216  
-  },
217  
-  {
218  
-    "context": "crm",
219  
-    "nameSpace": "XM",
220 159
     "type": "ToDo",
221 160
     "table": "todoitem",
222 161
     "isExtension": true,
530  enyo-client/extensions/source/crm/database/orm/ext/customer.json
... ...
@@ -0,0 +1,530 @@
  1
+[
  2
+  {
  3
+    "context": "crm",
  4
+    "nameSpace": "XM",
  5
+    "type": "Customer",
  6
+    "table": "crmacct",
  7
+    "isExtension": true,
  8
+    "isChild": true,
  9
+    "comment": "Extended by Crm",
  10
+    "relations": [
  11
+      {
  12
+        "column": "crmacct_cust_id",
  13
+        "inverse": "id"
  14
+      }
  15
+    ],
  16
+    "properties": [
  17
+     {
  18
+        "name": "account",
  19
+        "attr": {
  20
+         "type": "Number",
  21
+         "column": "crmacct_id"
  22
+        }
  23
+     },
  24
+     {
  25
+        "name": "contactRelations",
  26
+        "toMany": {
  27
+          "isNested": true,
  28
+          "type": "CustomerContactRelation",
  29
+          "column": "crmacct_id",
  30
+          "inverse": "account"
  31
+        }
  32
+      },
  33
+      {
  34
+        "name": "incidentRelations",
  35
+        "toMany": {
  36
+          "isNested": true,
  37
+          "type": "CustomerIncidentRelation",
  38
+          "column": "crmacct_id",
  39
+          "inverse": "account"
  40
+        }
  41
+      },
  42
+      {
  43
+        "name": "opportunityRelations",
  44
+        "toMany": {
  45
+          "isNested": true,
  46
+          "type": "CustomerOpportunityRelation",
  47
+          "column": "crmacct_id",
  48
+          "inverse": "account"
  49
+        }
  50
+      },
  51
+      {
  52
+        "name": "toDoRelations",
  53
+        "toMany": {
  54
+          "isNested": true,
  55
+          "type": "CustomerToDoRelation",
  56
+          "column": "crmacct_id",
  57
+          "inverse": "account"
  58
+        }
  59
+      }
  60
+    ],
  61
+    "isSystem": true
  62
+  },
  63
+  {
  64
+    "context": "xtuple",
  65
+    "nameSpace": "XM",
  66
+    "type": "CustomerContactRelation",
  67
+    "table": "cntct",
  68
+    "comment": "Customer Contact Relation Map",
  69
+    "privileges": {
  70
+      "all": {
  71
+        "create": false,
  72
+        "read": "ViewAllContacts MaintainAllContacts",
  73
+        "update": false,
  74
+        "delete": false
  75
+      },
  76
+      "personal": {
  77
+        "create": false,
  78
+        "read": true,
  79
+        "update": false,
  80
+        "delete": false,
  81
+        "properties": [
  82
+          "owner.username"
  83
+        ]
  84
+      }
  85
+    },
  86
+    "properties": [
  87
+      {
  88
+        "name": "id",
  89
+        "attr": {
  90
+          "type": "Number",
  91
+          "column": "cntct_id",
  92
+          "isPrimaryKey": true
  93
+        }
  94
+      },
  95
+      {
  96
+        "name": "isActive",
  97
+        "attr": {
  98
+          "type": "Boolean",
  99
+          "column": "cntct_active"
  100
+        }
  101
+      },
  102
+      {
  103