merge 4.6.0-beta into master
authorGil Moskowitz <gmoskowitz@xtuple.com>
Mon, 28 Jul 2014 20:45:23 +0000 (16:45 -0400)
committerGil Moskowitz <gmoskowitz@xtuple.com>
Mon, 28 Jul 2014 20:45:23 +0000 (16:45 -0400)
114 files changed:
.travis.yml
RELEASE.md
enyo-client/application/source/en/strings.js
enyo-client/application/source/ext/datasource.js
enyo-client/application/source/models/incident.js
enyo-client/application/source/views/workspace.js
enyo-client/application/source/widgets/characteristics.js
enyo-client/application/source/widgets/checkbox.js
enyo-client/application/source/widgets/combobox.js
enyo-client/application/source/widgets/money.js
enyo-client/application/source/widgets/number.js
enyo-client/application/source/widgets/parameter.js
enyo-client/application/source/widgets/relation.js
enyo-client/database/orm/models/sys.json
enyo-client/database/source/update_version.sql
enyo-client/extensions/source/crm/client/en/strings.js
enyo-client/extensions/source/crm/client/postbooks.js
enyo-client/extensions/source/crm/client/widgets/chart.js
enyo-client/extensions/source/oauth2/client/models/oauth2.js
enyo-client/extensions/source/oauth2/client/views/workspace.js
enyo-client/extensions/source/purchasing/client/en/strings.js
enyo-client/extensions/source/purchasing/client/widgets/relation.js
enyo-client/extensions/source/sales/client/en/strings.js
enyo-client/extensions/source/sales/client/postbooks.js
enyo-client/extensions/source/sales/client/widgets/chart.js
foundation-database/manifest.js
foundation-database/public/functions/balanceitemsite.sql
foundation-database/public/functions/bankreconciliation.sql [new file with mode: 0644]
foundation-database/public/functions/itemcost.sql
foundation-database/public/functions/itemipsprice.sql
foundation-database/public/functions/postbankreconciliation.sql
foundation-database/public/functions/postcashreceipt.sql
foundation-database/public/functions/postcheck.sql
foundation-database/public/functions/releasepr.sql [new file with mode: 0644]
foundation-database/public/functions/reopenbankreconciliation.sql
foundation-database/public/patches/fixflcol.sql [new file with mode: 0644]
foundation-database/public/tables/checkhead.sql [new file with mode: 0644]
foundation-database/public/tables/metasql/itemPricingSchedule-detail.mql
foundation-database/public/tables/metasql/orderActivityByProject-detail.mql
foundation-database/public/tables/metasql/pricelist-detail.mql
foundation-database/public/tables/metasql/salesOrderItems-list.mql
foundation-database/public/tables/metric.sql
foundation-database/public/views/docinfo.sql
lib/backbone-x/source/model_mixin.js
lib/enyo-x/source/less/address.less
lib/enyo-x/source/less/characteristics.less [deleted file]
lib/enyo-x/source/less/grid.less
lib/enyo-x/source/less/list.less
lib/enyo-x/source/less/picker.less
lib/enyo-x/source/less/pullout.less
lib/enyo-x/source/less/relations.less
lib/enyo-x/source/less/screen.less
lib/enyo-x/source/less/widgets.less [deleted file]
lib/enyo-x/source/less/workspace.less
lib/enyo-x/source/stylesheets/screen.css
lib/enyo-x/source/views/grid_box.js
lib/enyo-x/source/views/list_relations_editor_box.js
lib/enyo-x/source/views/navigator.js
lib/enyo-x/source/views/search.js
lib/enyo-x/source/widgets/characteristics.js
lib/enyo-x/source/widgets/checkbox.js
lib/enyo-x/source/widgets/combobox.js
lib/enyo-x/source/widgets/date.js
lib/enyo-x/source/widgets/file_input.js
lib/enyo-x/source/widgets/input.js
lib/enyo-x/source/widgets/number.js
lib/enyo-x/source/widgets/number_spinner.js
lib/enyo-x/source/widgets/parameter.js
lib/enyo-x/source/widgets/picker.js
lib/enyo-x/source/widgets/relation.js
lib/enyo-x/source/widgets/text_area.js
lib/enyo-x/source/widgets/toggle_button.js
node-datasource/lib/ext/models.js
node-datasource/main.js
node-datasource/oauth2/oauth2.js
node-datasource/routes/analysis.js [deleted file]
node-datasource/routes/install_extension.js
node-datasource/routes/routes.js
node-datasource/views/login/assets/grabbutton.png [deleted file]
node-datasource/views/login/assets/item-hilite.png [deleted file]
node-datasource/views/login/assets/login-blue-scr.png [deleted file]
node-datasource/views/login/assets/login-logo.png [deleted file]
node-datasource/views/login/assets/menu-icon-bookmark.png [deleted file]
node-datasource/views/login/assets/menu-icon-export.png [deleted file]
node-datasource/views/login/assets/menu-icon-gear.png [deleted file]
node-datasource/views/login/assets/menu-icon-help.png [deleted file]
node-datasource/views/login/assets/menu-icon-lock.png [deleted file]
node-datasource/views/login/assets/menu-icon-new.png [deleted file]
node-datasource/views/login/assets/menu-icon-refresh.png [deleted file]
node-datasource/views/login/assets/menu-icon-relation.png [deleted file]
node-datasource/views/login/assets/menu-icon-search.png [deleted file]
node-datasource/views/login/assets/relation-icon-search.png [deleted file]
node-datasource/views/login/assets/search-input-search.png [deleted file]
node-datasource/views/login/assets/setup-icon.png [deleted file]
node-datasource/views/login/assets/sliding-shadow.png [deleted file]
node-datasource/views/login/assets/sort-icon.png [deleted file]
package.json
scripts/0 [new file with mode: 0644]
scripts/install_bi.sh [deleted file]
scripts/lib/build_all.js
scripts/lib/build_database.js
scripts/lib/build_database_util.js
scripts/start_bi.sh [deleted file]
scripts/stop_bi.sh [deleted file]
scripts/xml/distribution_install.xml
scripts/xml/distribution_package.xml
scripts/xml/postbooks_package.xml
scripts/xml/xtmfg_install.xml
scripts/xml/xtmfg_package.xml
test/database/bankrec.js [new file with mode: 0644]
test/database/gotchas.js
test/extensions/all/grid_box.js
test/extensions/core/date.js
test/specs/incident.js

index da8ebf3..7d0d3a4 100644 (file)
@@ -15,3 +15,7 @@ script:
   - "npm run-script test-datasource"
   - "npm run-script test"
   - "npm run-script jshint"
+
+  # test an upgrade from 4.4.0
+  - "wget http://sourceforge.net/projects/postbooks/files/03%20PostBooks-databases/4.4.0/postbooks_demo-4.4.0.backup"
+  - "./scripts/build_app.js -d upgrade_test -i -b ./postbooks_demo-4.4.0.backup"
index be4f8fc..e7039d6 100644 (file)
@@ -1,3 +1,138 @@
+4.6.0-beta (2014/07/21)
+=======================
+
+Features and bugfixes
+---------------------
+
+- Fixed
+  issue #[18401](http://www.xtuple.org/xtincident/view/bugs/18401)
+  _Relation autocompleter menu gets in the way_
+- Fixed
+  issue #[18409](http://www.xtuple.org/xtincident/view/bugs/18409)
+  _blank screen after login, selecting DB_
+- Fixed
+  issue #[18505](http://www.xtuple.org/xtincident/view/bugs/18505)
+  _Advanced search panel is blank on iPad_
+- Fixed
+  issue #[18521](http://www.xtuple.org/xtincident/view/bugs/18521)
+  _Automatically-added comments have "admin" as the name_
+- Fixed
+  issue #[18635](http://www.xtuple.org/xtincident/view/bugs/18635)
+  _Creating a user account in the mobile client does not create a CRM Account_
+- Implemented
+  issue #[18637](http://www.xtuple.org/xtincident/view/bugs/18637)
+  _Extend "Personal" privilege methodology to any property that can be tied back to a UserAccount ID_
+- Fixed
+  issue #[18640](http://www.xtuple.org/xtincident/view/bugs/18640)
+  _error in CRM Acct Merge_
+- Fixed
+  issue #[18692](http://www.xtuple.org/xtincident/view/bugs/18692)
+  _List Relation Box should be disabled for new records_
+- Fixed
+  issue #[18695](http://www.xtuple.org/xtincident/view/bugs/18695)
+  _Comments don't work on iPad_
+- Fixed
+  issue #[18698](http://www.xtuple.org/xtincident/view/bugs/18698)
+  _iPad sorts strings as numbers_
+- Fixed
+  issue #[18699](http://www.xtuple.org/xtincident/view/bugs/18699)
+  _iPad does not scroll smoothly_
+- Fixed
+  issue #[18707](http://www.xtuple.org/xtincident/view/bugs/18707)
+  _Can't switch databases on iPad_
+- Fixed
+  issue #[18730](http://www.xtuple.org/xtincident/view/bugs/18730)
+  _Task number read only status is erratic_
+- Fixed
+  issue #[18731](http://www.xtuple.org/xtincident/view/bugs/18731)
+  _Address placeholders are not translated_
+- Fixed
+  issue #[18732](http://www.xtuple.org/xtincident/view/bugs/18732)
+  _All postgres users are showing up in user list_
+- Fixed
+  issue #[18769](http://www.xtuple.org/xtincident/view/bugs/18769)
+  _Cookies are not being deleted in any test-cases_
+- Completed
+  issue #[18770](http://www.xtuple.org/xtincident/view/bugs/18770)
+  _Integrate node-router into node-datasource_
+- Fixed
+  issue #[19199](http://www.xtuple.org/xtincident/view/bugs/19199)
+  _Security on reset password._
+- Implemented
+  issue #[20689](http://www.xtuple.org/xtincident/view/bugs/20689)
+  _Support for Barcode_
+- Implemented
+  issue #[20962](http://www.xtuple.org/xtincident/view/bugs/20962)
+  _Add support to enter Receipt transactions_
+- Implemented
+  issue #[21038](http://www.xtuple.org/xtincident/view/bugs/21038)
+  _Complete Issue to Shipping_
+- Fixed
+  issue #[21476](http://www.xtuple.org/xtincident/view/bugs/21476)
+  _*Unable to select back 'External' option as the Gateway for Credit cards in System Configuration screen_
+- Implemented
+  issue #[21584](http://www.xtuple.org/xtincident/view/bugs/21584)
+  _Organize pentaho properties in config.js_
+- Fixed
+  issue #[21686](http://www.xtuple.org/xtincident/view/bugs/21686)
+  _*Toolbar overlaps the Export icon making it unable to select in mobile devices_
+- Fixed
+  issue #[21889](http://www.xtuple.org/xtincident/view/bugs/21889)
+  _Mobile Web Client List View Items Overlap_
+- Fixed
+  issue #[21909](http://www.xtuple.org/xtincident/view/bugs/21909)
+  _ErpBI configuration not production ready_
+- Fixed
+  issue #[21978](http://www.xtuple.org/xtincident/view/bugs/21978)
+  _Item auto-populates on Sales Order when it should not_
+- Implemented
+  issue #[22067](http://www.xtuple.org/xtincident/view/bugs/22067)
+  _Implement returns(credit memo)_
+- Implemented
+  issue #[22072](http://www.xtuple.org/xtincident/view/bugs/22072)
+  _Add support for Transfer Order_
+- Implemented
+  issue #[22129](http://www.xtuple.org/xtincident/view/bugs/22129)
+  _Support system printers for reports_
+- Fixed
+  issue #[22151](http://www.xtuple.org/xtincident/view/bugs/22151)
+  _Selecting ITEM-GROUP produces internal server error_
+- Fixed
+  issue #[22152](http://www.xtuple.org/xtincident/view/bugs/22152)
+  _Site is required in PostBooks_
+- Fixed
+  issue #[22172](http://www.xtuple.org/xtincident/view/bugs/22172)
+  _Item Group does not appear in Postbooks_
+- Implemented
+  issue #[22175](http://www.xtuple.org/xtincident/view/bugs/22175)
+  _Add the ability to issue to shipping and ship from Sales Order_
+- Fixed
+  issue #[22214](http://www.xtuple.org/xtincident/view/bugs/22214)
+  _Inventory gear options not showing on small devices_
+- Fixed
+  issue #[22224](http://www.xtuple.org/xtincident/view/bugs/22224)
+  _*Selecting 'Issue to Shipping' displays a JS console error and does not displays any response_
+- No Change Required
+  issue #[22227](http://www.xtuple.org/xtincident/view/bugs/22227)
+  _*Selecting to open an existing shipment hangs the application_
+- Fixed
+  issue #[22298](http://www.xtuple.org/xtincident/view/bugs/22298)
+  _*Selecting to add an address to a record displays an irrelevant dialog_
+- Fixed
+  issue #[22355](http://www.xtuple.org/xtincident/view/bugs/22355)
+  _Toolbar height changed_
+- Fixed
+  issue #[22368](http://www.xtuple.org/xtincident/view/bugs/22368)
+  _*Date format is displayed incorrectly in Transfer order list and Issue to shipping screens_
+- Fixed
+  issue #[22407](http://www.xtuple.org/xtincident/view/bugs/22407)
+  _*Selecting 'Issue to Shipping' without saving the Sales order and then selecting 'Save', doesn't populate the Sales order number_
+- Fixed
+  issue #[22408](http://www.xtuple.org/xtincident/view/bugs/22408)
+  _*Selecting Express Checkout without saving the Sales order and then selecting 'No' in the confirmation dialog displays a console_
+
+
+
 4.5.2 (2014/07/15)
 ==================
 
index a4b4b08..4e86c70 100644 (file)
@@ -15,7 +15,6 @@
     // Models
     // ********
 
-    "_analysis": "Analysis",
     "_all": "All",
     "_annualy": "Annually",
     "_asset": "Asset",
     "_subtotal": "Subtotal",
     "_suffix": "Suffix",
     "_summary": "Summary",
+    "_success!": "Success!",
     "_successors": "Successors",
     "_symbol": "Symbol",
     "_system": "System",
     "_deleteLine?": "Are you sure you want to delete this line?",
     "_exitPageWarning": "You are about to leave the xTuple application.",
     "_installExtensionWarning": "Extensions are very powerful and potentially have full access to your " +
-      "data. You should only install an extension from a source you trust.",
+      "data. You should only install an extension from a source you trust. ",
     "_insufficientPrivileges": "You have insufficient privileges to perform this action.",
     "_manualFreight": "Manually editing the freight will disable automatic freight recalculations.",
     "_mustSave": "You must save your changes before proceeding.",
index 8a216b1..554e05f 100644 (file)
@@ -206,7 +206,7 @@ white:true*/
                   XM.jsonpatch.apply(attrs, data.patches);
                   cModel.etag = data.etag;
 
-                  // This is a hack to work around Backbone messing with 
+                  // This is a hack to work around Backbone messing with
                   // attributes when we don't want it to. Parse function
                   // on model handles the other side of this
                   options.fixAttributes = cModel.attributes;
@@ -259,7 +259,8 @@ white:true*/
       // handle error
       if (inResponse.isError) {
         if (inSender.error) {
-          params.error = inResponse.message;
+          // inResponse.message sometimes gets lost in the vagaries of socket-io
+          params.error = inResponse.message || inResponse.errorMessage;
           error = XT.Error.clone('xt1001', { params: params });
           inSender.error.call(this, error);
         }
index df7bc72..77d5e7f 100644 (file)
@@ -161,7 +161,7 @@ white:true*/
     },
 
     assignedToDidChange: function (model, value, options) {
-      if (value) {
+      if (value && this.get("status") !== XM.Incident.RESOLVED && this.get("status") !== XM.Incident.CLOSED) {
         this.set('status', XM.Incident.ASSIGNED);
       }
     },
index 98860f5..f301ee0 100644 (file)
@@ -463,7 +463,7 @@ strict: false*/
             },
             {
               success: function (message) {
-                that.doNotify({message: message});
+                that.doNotify({message: message && message.loc()});
               },
               error: function (error) {
                 that.doNotify({message: error.message ? error.message() : error});
index b051ef7..c5a0143 100644 (file)
@@ -90,7 +90,7 @@ white:true, strict:false*/
     name: "XV.OrderCharacteristicItem",
     kind: "XV.CharacteristicItem",
     components: [
-      {kind: "XV.ComboboxWidget", name: "combobox", attr: "value", style: "width: 300px"}
+      {kind: "XV.ComboboxWidget", name: "combobox", attr: "value"}
     ],
     disabledChanged: function (oldValue) {
       this.$.combobox.setDisabled(this.disabled);
index 71db45f..cf31b74 100644 (file)
@@ -47,7 +47,7 @@ regexp:true, undef:true, trailing:true, white:true */
       options = options || {};
       var isRelation = this.isRelation(),
         that = this,
-        color = "black",
+        colorClass = "",
         enabled = false,
         input = this.$.input.getValue(),
         openWorkspace,
@@ -64,10 +64,10 @@ regexp:true, undef:true, trailing:true, white:true */
 
       // Turn on label link if applicable
       if (this.getValue() && isRelation) {
-        color = "blue";
+        colorClass = "hyperlink";
         enabled = true;
       }
-      this.$.label.setStyle("color: " + color);
+      this.$.label.addClass(colorClass);
       this.setLinkEnabled(enabled);
       this.setDisabled(enabled);
 
index 9cb0add..58b2c66 100644 (file)
@@ -70,11 +70,9 @@ regexp:true, undef:true, trailing:true, white:true */
       this.inherited(arguments);
       this.createComponent({
         name: "comboboxNote",
-        container: this.$.fittableColumns,
+        container: this.$.container,
         classes: "xv-combobox-note"
       });
-      this.$.input.applyStyle("padding-top", "8px");
-      this.$.input.applyStyle("padding-left", "8px");
     },
     /**
       Populate the note field
index 86053f5..f29fce8 100644 (file)
@@ -33,29 +33,26 @@ regexp:true, undef:true, trailing:true, white:true */
     },
     maxlength: 12,
     components: [
-      {kind: "FittableColumns", components: [
+      {controlClasses: "enyo-inline", components: [
         {name: "label", content: "", classes: "xv-label"},
-        {kind: "onyx.InputDecorator", classes: "xv-input-decorator",
-          components: [
+        {kind: "onyx.InputDecorator", components: [
           {name: "input", kind: "onyx.Input",
             onchange: "inputChanged", onkeydown: "keyDown"}
         ]},
-        {name: "picker", kind: "XV.CurrencyPicker", showLabel: false}
+        {name: "picker", kind: "XV.CurrencyPicker", classes: "xv-currency-picker", showLabel: false}
       ]},
-      {kind: "FittableColumns", name: "basePanel", showing: false,
-        components: [
-        {name: "spacer", content: "", classes: "xv-label"},
-        {kind: "onyx.InputDecorator", classes: "xv-input-decorator",
-          components: [
-          {name: "baseAmountLabel", classes: "xv-money-label"}
+      {controlClasses: "enyo-inline", name: "basePanel", showing: false, components: [
+        {classes: "xv-label"},
+        {kind: "onyx.InputDecorator", components: [
+          {name: "baseAmountLabel", classes: "xv-label"}
         ]},
-        {kind: "onyx.InputDecorator", classes: "xv-input-decorator, xv-currency-label",
-          components: [
-          {name: "baseCurrencyLabel"}
+        {classes: "xv-currency-picker", components: [
+          {kind: 'onyx.InputDecorator', components: [
+            {name: 'baseCurrencyLabel'}
+          ]}
         ]}
       ]}
     ],
-
     /**
       Set the base price into the base amount label
     */
index b8007c7..475a804 100644 (file)
@@ -8,12 +8,6 @@ regexp:true, undef:true, trailing:true, white:true */
   // COST
   //
 
-  enyo.kind({
-    name: "XV.Cost",
-    kind: "XV.Number",
-    scale: XT.COST_SCALE
-  });
-
   enyo.kind({
     name: "XV.CostWidget",
     kind: "XV.NumberWidget",
@@ -24,12 +18,6 @@ regexp:true, undef:true, trailing:true, white:true */
   // EXTENDED PRICE
   //
 
-  enyo.kind({
-    name: "XV.ExtendedPrice",
-    kind: "XV.Number",
-    scale: XT.EXTENDED_PRICE_SCALE
-  });
-
   enyo.kind({
     name: "XV.ExtendedPriceWidget",
     kind: "XV.NumberWidget",
@@ -40,13 +28,6 @@ regexp:true, undef:true, trailing:true, white:true */
   // HOURS
   //
 
-  enyo.kind({
-    name: "XV.Hours",
-    kind: "XV.Number",
-    maxlength: 12,
-    scale: XT.HOURS_SCALE
-  });
-
   enyo.kind({
     name: "XV.HoursWidget",
     kind: "XV.NumberWidget",
@@ -58,28 +39,6 @@ regexp:true, undef:true, trailing:true, white:true */
   // PERCENT
   //
 
-  enyo.kind({
-    name: "XV.Percent",
-    kind: "XV.Number",
-    scale: XT.PERCENT_SCALE,
-    validate: function (value) {
-      // this takes the string from the input field and parses it (including understanding commas, which isNaN cannot)
-      // if it cannot parse the value, it returns NaN
-      value = Globalize.parseFloat(value);
-      // use isNaN here because parseFloat could return NaN
-      // if you pass NaN into _.isNumber, it will misleadingly return true
-      // only bad string and null/undefined cases do we want to fail validation
-      return !isNaN(value) ? value / 100 : false;
-    },
-    valueChanged: function (value) {
-      // use isNaN here because this value may be a number string and _isNaN requires
-      // a separate falsy check.
-      // In this case, it is ok for 0 to fall to the true case, just not null or a bad string
-      value = !isNaN(value) ? value * 100 : value;
-      XV.Number.prototype.valueChanged.call(this, value);
-    }
-  });
-
   enyo.kind({
     name: "XV.PercentWidget",
     kind: "XV.NumberWidget",
@@ -106,12 +65,6 @@ regexp:true, undef:true, trailing:true, white:true */
   // PURCHASE PRICE
   //
 
-  enyo.kind({
-    name: "XV.PurchasePrice",
-    kind: "XV.Number",
-    scale: XT.PURCHASE_PRICE_SCALE
-  });
-
   enyo.kind({
     name: "XV.PurchasePriceWidget",
     kind: "XV.NumberWidget",
@@ -122,13 +75,6 @@ regexp:true, undef:true, trailing:true, white:true */
   // QUANTITY
   //
 
-  enyo.kind({
-    name: "XV.Quantity",
-    kind: "XV.Number",
-    maxlength: 12,
-    scale: XT.QTY_SCALE
-  });
-
   enyo.kind({
     name: "XV.QuantityWidget",
     kind: "XV.NumberWidget",
@@ -140,12 +86,6 @@ regexp:true, undef:true, trailing:true, white:true */
   // QUANTITY PER
   //
 
-  enyo.kind({
-    name: "XV.QuantityPer",
-    kind: "XV.Number",
-    scale: XT.QTY_PER_SCALE
-  });
-
   enyo.kind({
     name: "XV.QuantityPerWidget",
     kind: "XV.NumberWidget",
@@ -156,12 +96,6 @@ regexp:true, undef:true, trailing:true, white:true */
   // SALES PRICE
   //
 
-  enyo.kind({
-    name: "XV.SalesPrice",
-    kind: "XV.Number",
-    scale: XT.SALES_PRICE_SCALE
-  });
-
   enyo.kind({
     name: "XV.SalesPriceWidget",
     kind: "XV.NumberWidget",
@@ -172,12 +106,6 @@ regexp:true, undef:true, trailing:true, white:true */
   // UNIT RATIO
   //
 
-  enyo.kind({
-    name: "XV.UnitRatio",
-    kind: "XV.Number",
-    scale: XT.UNIT_RATIO_SCALE
-  });
-
   enyo.kind({
     name: "XV.UnitRatioWidget",
     kind: "XV.NumberWidget",
@@ -188,12 +116,6 @@ regexp:true, undef:true, trailing:true, white:true */
   // WEIGHT
   //
 
-  enyo.kind({
-    name: "XV.Weight",
-    kind: "XV.Number",
-    scale: XT.WEIGHT_SCALE
-  });
-
   enyo.kind({
     name: "XV.WeightWidget",
     kind: "XV.NumberWidget",
index 5ffc5b4..48ca27a 100644 (file)
@@ -175,7 +175,8 @@ trailing:true, white:true, strict:false*/
       // see if toggle is on and update params
       _.each(keys, function (key) {
         _.each(actTypes[key], function (obj) {
-          if (that.$[_namify(obj)].getValue()) {
+          // the pluralize function in _namify is imperfect
+          if (that.$[_namify(obj)] && that.$[_namify(obj)].getValue()) {
             value.push(obj.type);
           }
         });
index a2e4a73..0ca32d7 100644 (file)
@@ -23,98 +23,49 @@ regexp:true, undef:true, trailing:true, white:true, strict:false */
     name: "XV.ContactWidget",
     kind: "XV.RelationWidget",
     label: "_contact".loc(),
-    collection: "XM.ContactRelationCollection",
-    list: "XV.ContactList",
     keyAttribute: "name",
     nameAttribute: "jobTitle",
     descripAttribute: "phone",
-    classes: "xv-relationwidget",
+    collection: "XM.ContactRelationCollection",
+    list: "XV.ContactList",
     published: {
       showAddress: false
     },
-    components: [
-      {kind: "FittableColumns", components: [
-        {name: "label", content: "", fit: true, classes: "xv-flexible-label"},
-        {kind: "onyx.InputDecorator", name: "decorator",
-          classes: "xv-input-decorator", components: [
-          {name: "input", kind: "onyx.Input", classes: "xv-subinput",
-            onkeyup: "keyUp", onkeydown: "keyDown", onblur: "receiveBlur",
-            onfocus: "receiveFocus"
-          },
-          {kind: "onyx.MenuDecorator", onSelect: "itemSelected", components: [
-            {kind: "onyx.IconButton", classes: "icon-folder-open-alt"},
-            {name: "popupMenu", floating: true, kind: "onyx.Menu",
-              components: [
-              {kind: "XV.MenuItem", name: "searchItem", content: "_search".loc()},
-              {kind: "XV.MenuItem", name: "openItem", content: "_open".loc(),
-                disabled: true},
-              {kind: "XV.MenuItem", name: "newItem", content: "_new".loc(),
-                disabled: true}
-            ]}
-          ]},
-          {name: "completer", kind: "XV.Completer", onSelect: "itemSelected"}
-        ]}
+    descriptionComponents: [
+      {name: "jobTitleRow", controlClasses: "enyo-inline", showing: false, components: [
+        {classes: 'xv-description', name: "name"}
+      ]},
+      {name: "phoneRow", controlClasses: "enyo-inline", showing: false, components: [
+        {classes: "xv-description hyperlink", target: '_blank', name: "description"}
+      ]},
+      {name: "alternateRow", controlClasses: "enyo-inline", showing: false, components: [
+        {classes: "xv-description hyperlink", target: "_blank", name: "alternate"}
       ]},
-      {kind: "FittableColumns", components: [
-        {name: "labels", classes: "xv-relationwidget-column left",
-          components: [
-          {name: "jobTitleLabel", content: "_jobTitle".loc() + ":",
-            classes: "xv-relationwidget-description label",
-            showing: false},
-          {name: "phoneLabel", content: "_phone".loc() + ":",
-            classes: "xv-relationwidget-description label",
-            showing: false},
-          {name: "alternateLabel", content: "_alternate".loc() + ":",
-            classes: "xv-relationwidget-description label",
-            showing: false},
-          {name: "faxLabel", content: "_fax".loc() + ":",
-            classes: "xv-relationwidget-description label",
-            showing: false},
-          {name: "primaryEmailLabel", content: "_email".loc() + ":",
-            classes: "xv-relationwidget-description label",
-            showing: false},
-          {name: "webAddressLabel", content: "_web".loc() + ":",
-            classes: "xv-relationwidget-description label",
-            showing: false},
-          {name: "addressLabel", content: "_address".loc() + ":",
-            classes: "xv-relationwidget-description label",
-            showing: false}
-        ]},
-        {name: "data", fit: true, components: [
-          {name: "name", classes: "xv-relationwidget-description hasLabel"},
-          {name: "description", ontap: "callPhone",
-            classes: "xv-relationwidget-description hasLabel hyperlink"},
-          {name: "alternate", classes: "xv-relationwidget-description hasLabel"},
-          {name: "fax", classes: "xv-relationwidget-description hasLabel"},
-          {name: "primaryEmail", ontap: "sendMail",
-            classes: "xv-relationwidget-description hasLabel hyperlink"},
-          {name: "webAddress", ontap: "openWindow",
-            classes: "xv-relationwidget-description hasLabel hyperlink"},
-          {name: "address", classes: "xv-relationwidget-description hasLabel",
-            allowHtml: true}
-        ]}
+      {name: "faxRow", controlClasses: "enyo-inline", showing: false, components: [
+        {classes: "xv-description hyperlink", target: "_blank", name: "fax"}
+      ]},
+      {name: "emailRow", controlClasses: "enyo-inline", showing: false, components: [
+        {classes: 'xv-description hyperlink', target: "_blank", name: "email"}
+      ]},
+      {name: "webAddressRow", controlClasses: "enyo-inline", showing: false, components: [
+        {classes: 'xv-description hyperlink', target: "_blank", name: "webAddress"}
+      ]},
+      {name: "addressRow", controlClasses: "enyo-inline", showing: false, components: [
+        {classes: "xv-description", name: "address", allowHtml: true}
       ]}
     ],
-    disabledChanged: function () {
-      this.inherited(arguments);
-      var disabled = this.getDisabled();
-      if (this.$.phone) {
-        this.$.jobTitle.addRemoveClass("disabled", disabled);
-        this.$.phone.addRemoveClass("disabled", disabled);
-        this.$.alternate.addRemoveClass("disabled", disabled);
-        this.$.fax.addRemoveClass("disabled", disabled);
-        this.$.primaryEmail.addRemoveClass("disabled", disabled);
-        this.$.webAddress.addRemoveClass("disabled", disabled);
-      }
-    },
     setValue: function (value, options) {
       this.inherited(arguments);
+
       if (value && !value.get) {
         // the value of the widget is still being fetched asyncronously.
         // when the value is fetched, this function will be run again,
         // so for now we can just stop here.
         return;
       }
+
+      // The rows are here because sometimes the values needs labels
+      // to go with the values.
       var jobTitle = value ? value.get("jobTitle") : "",
         phone = value ? value.get("phone") : "",
         alternate = value ? value.get("alternate") : "",
@@ -123,23 +74,32 @@ regexp:true, undef:true, trailing:true, white:true, strict:false */
         webAddress = value ? value.get("webAddress") : "",
         address = value ? XM.Address.format(value.get("address")) : "",
         showAddress = this.getShowAddress();
-      this.$.jobTitleLabel.setShowing(jobTitle);
-      this.$.phoneLabel.setShowing(phone);
-      this.$.alternate.setShowing(alternate);
+
+      this.$.jobTitleRow.setShowing(!!jobTitle);
+      this.$.name.setContent(jobTitle);
+
+      this.$.phoneRow.setShowing(!!phone);
+      this.$.description.setContent(phone);
+      this.$.description.setAttribute('href', 'tel://' + phone);
+
+      this.$.alternateRow.setShowing(!!alternate);
       this.$.alternate.setContent(alternate);
-      this.$.alternateLabel.setShowing(alternate);
-      this.$.fax.setShowing(fax);
+      this.$.alternate.setAttribute('href', 'tel://' + alternate);
+
+      this.$.faxRow.setShowing(!!fax);
       this.$.fax.setContent(fax);
-      this.$.faxLabel.setShowing(fax);
-      this.$.primaryEmail.setShowing(primaryEmail);
-      this.$.primaryEmail.setContent(primaryEmail);
-      this.$.primaryEmailLabel.setShowing(primaryEmail);
-      this.$.webAddress.setShowing(webAddress);
+      this.$.fax.setAttribute('href', 'tel://' + fax);
+
+      this.$.emailRow.setShowing(!!primaryEmail);
+      this.$.email.setContent(primaryEmail);
+      this.$.email.setAttribute('href', 'mailto:' + primaryEmail);
+
+      this.$.webAddressRow.setShowing(!!webAddress);
       this.$.webAddress.setContent(webAddress);
-      this.$.webAddressLabel.setShowing(webAddress);
-      this.$.address.setShowing(address && showAddress);
-      this.$.addressLabel.setShowing(address && showAddress);
-      if (showAddress) { this.$.address.setContent(address); }
+      this.$.webAddress.setAttribute('href', '//' + alternate);
+
+      this.$.addressRow.setShowing(address && showAddress);
+      this.$.address.setContent(address);
     },
     openWindow: function () {
       var address = this.value ? this.value.get("webAddress") : null;
index 22e20d7..69b2d2c 100644 (file)
           "column": "grp_descrip"
         }
       },
+      {
+        "name": "grantedPrivileges",
+        "toMany": {
+          "type": "UserAccountRolePrivilegeAssignment",
+          "column": "grp_id",
+          "inverse": "userAccountRole",
+          "isNested": true
+        }
+      },
       {
         "name": "grantedExtensions",
         "toMany": {
     ],
     "isSystem": true
   },
+  {
+    "context": "xtuple",
+    "nameSpace": "SYS",
+    "type": "UserAccountRolePrivilegeAssignment",
+    "table": "grppriv",
+    "idSequenceName": "grppriv_grppriv_id_seq",
+    "comment": "User Account Role Privilege Assignment Map",
+    "privileges": {
+      "all": {
+        "create": true,
+        "read": true,
+        "update": false,
+        "delete": true
+      }
+    },
+    "properties": [
+      {
+        "name": "id",
+        "attr": {
+          "type": "Number",
+          "column": "grppriv_id",
+          "isPrimaryKey": true
+        }
+      },
+      {
+        "name": "uuid",
+        "attr": {
+          "type": "String",
+          "column": "obj_uuid",
+          "isNaturalKey": true
+        }
+      },
+      {
+        "name": "userAccountRole",
+        "attr": {
+          "type": "Number",
+          "column": "grppriv_grp_id"
+        }
+      },
+      {
+        "name": "privilege",
+        "toOne": {
+          "type": "Privilege",
+          "column": "grppriv_priv_id"
+        }
+      }
+    ],
+    "isNestedOnly": true,
+    "isSystem": true
+  },
   {
     "context": "xtuple",
     "nameSpace": "SYS",
index 53906e2..80427bc 100644 (file)
@@ -1 +1 @@
-UPDATE pkghead SET pkghead_version = '4.5.2' WHERE pkghead_name = 'xt';
+UPDATE pkghead SET pkghead_version = '4.6.0-beta' WHERE pkghead_name = 'xt';
index f204ccf..af3ceee 100644 (file)
@@ -15,7 +15,6 @@ strict:true, trailing:true, white:true */
     "_incidentStatusColors": "Incident Status Colors",
     "_opportunitiesNext30Days": "Opportunities Next 30 Days",
     "_maintainEmailProfiles": "Maintain Email Profiles",
-    "_staleAnalysisWarning": "Free trial demo analysis data will not be updated from your live changes.",
     "_viewEmailProfiles": "View Email Profiles"
   });
 
index 78d5771..2a57a3c 100644 (file)
@@ -81,11 +81,6 @@ trailing:true, white:true*/
       XT.app.$.postbooks.insertModule(dashboardModule, 0);
     }
 
-    isBiAvailable = XT.session.config.biAvailable && XT.session.privileges.get("ViewSalesHistory");
-    if (isBiAvailable) {
-      module.panels.push({name: "analysisPage", kind: "analysisFrame"});
-    }
-
     XT.app.$.postbooks.insertModule(module, 0);
 
     relevantPrivileges = [
@@ -151,54 +146,5 @@ trailing:true, white:true*/
     ];
     XT.session.addRelevantPrivileges(module.name, relevantPrivileges);
 
-    /**
-      This iFrame is to show the Analysis tool.
-      On creation, it uses the analysis route to generate a signed,
-      encoded JWT which it sends to Pentaho to get the report.
-    */
-    enyo.kind({
-      name: "analysisFrame",
-      label: "_analysis".loc(),
-      tag: "iframe",
-      style: "border: none;",
-      attributes: {src: ""},
-      events: {
-        onMessage: ""
-      },
-      published: {
-        source: ""
-      },
-
-      create: function () {
-        this.inherited(arguments);
-        if (XT.session.config.freeDemo) {
-          this.doMessage({message: "_staleAnalysisWarning".loc()});
-        }
-        // generate the web token and render
-        // the iFrame
-        var url, ajax = new enyo.Ajax({
-          url: XT.getOrganizationPath() + "/analysis",
-          handleAs: "text"
-        });
-        ajax.response(this, function (inSender, inResponse) {
-          this.setSource(inResponse);
-        });
-        // uh oh. HTTP error
-        ajax.error(this, function (inSender, inResponse) {
-          // TODO: trigger some kind of error here
-          console.log("There was a problem generating the iFrame");
-        });
-        // param for the report name
-        ajax.go({reportUrl: "content/saiku-ui/index.html?biplugin=true"});
-      },
-      /**
-        When the published source value is set, this sets the src
-        attribute on the iFrame.
-      */
-      sourceChanged: function () {
-        this.inherited(arguments);
-        this.setAttributes({src: this.getSource()});
-      }
-    });
   };
 }());
index 016dae7..4b2a940 100644 (file)
@@ -51,11 +51,11 @@ trailing:true, white:true*/
         parameters: [{
           attribute: "targetClose",
           operator: ">=",
-          value: XT.date.applyTimezoneOffset(XV.Date.prototype.textToDate("0"), true)
+          value: XT.date.applyTimezoneOffset(XV.DateWidget.prototype.textToDate("0"), true)
         }, {
           attribute: "targetClose",
           operator: "<=",
-          value: XT.date.applyTimezoneOffset(XV.Date.prototype.textToDate("+30"), true)
+          value: XT.date.applyTimezoneOffset(XV.DateWidget.prototype.textToDate("+30"), true)
         }]
       },
       totalField: "amount"
index 20f968a..5afc2df 100644 (file)
@@ -34,6 +34,7 @@ white:true*/
       bindEvents: function () {
         XM.Model.prototype.bindEvents.apply(this, arguments);
         this.on('statusChange', this.statusDidChange);
+        this.on('change:clientType', this.clientTypeDidChange);
       },
 
       // clientType must not be editable once first saved.
@@ -51,6 +52,10 @@ white:true*/
         }
       },
 
+      clientTypeDidChange: function () {
+        this.set("delegatedAccess", this.get("clientType") === 'jwt bearer');
+      },
+
       save: function (key, value, options) {
         // Handle both `"key", value` and `{key: value}` -style arguments.
         if (_.isObject(key) || _.isEmpty(key)) {
index 9e18f0d..fe60f97 100644 (file)
@@ -32,7 +32,8 @@ white:true*/
               {kind: "XV.DateWidget", attr: "issued"},
               {kind: "XV.InputWidget", attr: "organization"},
               {kind: "XV.CheckboxWidget", name: "delegatedAccess", attr: "delegatedAccess"},
-              {kind: "XV.InputWidget", name: "clientX509PubCert", attr: "clientX509PubCert", label: "_x509PubCert".loc()},
+              {kind: "onyx.GroupboxHeader", content: "_x509PubCert".loc()},
+              {kind: "XV.TextArea", name: "clientX509PubCert", attr: "clientX509PubCert"},
               {kind: "onyx.GroupboxHeader", content: "_fullListUrl".loc()},
               {kind: "XV.TextArea", name: "fullListUrl", classes: "xv-short-textarea", disabled: true},
               {kind: "onyx.GroupboxHeader", content: "_singleResourceUrl".loc()},
@@ -45,7 +46,7 @@ white:true*/
               {kind: "XV.TextArea", name: "tokenRevocationURI", classes: "xv-short-textarea", disabled: true}
             ]}
           ]},
-          {kind: "XV.Oauth2clientRedirectBox", name: "redirectBox", attr: "redirectURIs" }
+          {kind: "XV.Oauth2clientRedirectBox", name: "redirectBox", attr: "redirectURIs", showing: false}
         ]}
       ],
       create: function () {
@@ -61,15 +62,15 @@ white:true*/
       attributesChanged: function (model, options) {
         this.inherited(arguments);
 
-        this.$.delegatedAccess.setShowing(model.get("clientType") === 'jwt bearer');
-        this.$.clientX509PubCert.setShowing(model.get("clientType") === 'jwt bearer');
-        // Enyo messes this one up for some reason, so use CSS
-        if (model.get("clientType") === 'web server') {
-          this.$.redirectBox.applyStyle("visibility", "showing");
-        } else {
-          this.$.redirectBox.applyStyle("visibility", "hidden");
-        }
+        var serviceAccount = model.get("clientType") === 'jwt bearer',
+          webServer = model.get("clientType") === 'web server';
 
+        // Delegated Access is only meaningful for Service Accounts
+        this.$.delegatedAccess.setShowing(serviceAccount);
+        this.$.clientX509PubCert.setShowing(serviceAccount);
+        this.$.redirectBox.setShowing(webServer);
+        // There is some rendering issue with this box that this fixes w/o css
+        this.$.redirectBox.render();
       }
     });
 
index fa285e8..0868e86 100644 (file)
@@ -57,6 +57,7 @@ strict:true, trailing:true, white:true */
     "_UseEarliestAvailDateOnPOItem": "Use Earliest Date",
     "_vendorItem": "Vendor Item",
     "_vendorItemNumber": "VendorItemNumber",
+    "_vendors": "Vendors",
     "_vendorUnit": "Vendor Unit",
     "_viewPurchaseOrders": "View Purchase Orders",
     "_vouchered": "Vouchered",
index 780df0d..dca6a6b 100644 (file)
@@ -31,71 +31,32 @@ white:true, strict:false*/
         showDetail: true,
         vendorItemNumber: null,
       },
-      components: [
-        {kind: "FittableColumns", components: [
-          {name: "label", content: "", fit: true, classes: "xv-flexible-label"},
-          {kind: "onyx.InputDecorator", name: "decorator",
-            classes: "xv-input-decorator", components: [
-            {name: "input", kind: "onyx.Input", classes: "xv-subinput",
-              onkeyup: "keyUp", onkeydown: "keyDown", onblur: "receiveBlur",
-              onfocus: "receiveFocus"
-            },
-            {kind: "onyx.MenuDecorator", onSelect: "itemSelected", components: [
-              {kind: "onyx.IconButton", classes: "icon-folder-open-alt"},
-              {name: "popupMenu", floating: true, kind: "onyx.Menu",
-                components: [
-                {kind: "XV.MenuItem", name: "searchItem", content: "_search".loc()},
-                {kind: "XV.MenuItem", name: "openItem", content: "_open".loc(),
-                  disabled: true},
-                {kind: "XV.MenuItem", name: "newItem", content: "_new".loc(),
-                  disabled: true}
-              ]}
-            ]},
-            {name: "completer", kind: "XV.Completer", onSelect: "itemSelected"}
-          ]}
-        ]},
-        {kind: "FittableColumns", name: "detailColumns", components: [
-          {name: "labels", classes: "xv-relationwidget-column left",
-            components: [
-            {name: "contractLabel", content: "_contract".loc() + ":",
-              classes: "xv-relationwidget-description label",
-              showing: false},
-            {name: "minimumQtyLabel", content: "_minimumOrderQuantity".loc() + ":",
-              classes: "xv-relationwidget-description label",
-              showing: false},
-            {name: "multipleQtyLabel", content: "_multipleOrderQuantity".loc() + ":",
-              classes: "xv-relationwidget-description label",
-              showing: false},
-            {name: "earliestDateLabel", content: "_earliestDate".loc() + ":",
-              classes: "xv-relationwidget-description label",
-              showing: false}
+      descriptionComponents: [
+        {controlClasses: 'enyo-inline', components: [
+          {name: "nameRow", controlClasses: "enyo-inline", components: [
+            {name: "name", classes: "xv-description"}
+          ]},
+          {name: "contractRow", controlClasses: "enyo-inline", components: [
+            {classes: 'xv-label', content: "_contract".loc() + ":"},
+            {name: "description", classes: "xv-description"}
+          ]},
+          {name: "minimumQtyRow", controlClasses: "enyo-inline", components: [
+            {classes: 'xv-label', content: "_minimumOrderQuantity".loc() + ":"},
+            {name: "minimumQty", classes: "xv-description"}
           ]},
-          {name: "data", fit: true, components: [
-            {name: "name", classes: "xv-relationwidget-description hasLabel",
-              showing: false},
-            {name: "description", classes: "xv-relationwidget-description hasLabel",
-              showing: false},
-            {name: "minimumQty", classes: "xv-relationwidget-description hasLabel",
-              showing: false},
-            {name: "multipleQty", classes: "xv-relationwidget-description hasLabel",
-              showing: false},
-            {name: "earliestDate", classes: "xv-relationwidget-description hasLabel",
-              showing: false}
+          {name: "multipleQtyRow", controlClasses: "enyo-inline", components: [
+            {classes: 'xv-label', content: "_multipleOrderQuantity".loc() + ":"},
+            {name: "multipleQty", classes: "xv-description"}
+          ]},
+          {name: "earliestDateRow", controlClasses: "enyo-inline", components: [
+            {classes: 'xv-label', content: "_earliestDate".loc() + ":"},
+            {name: "earliestDate", classes: "xv-description"}
           ]}
         ]}
       ],
       create: function () {
         this.inherited(arguments);
-        if (!this.getShowDetail()) {
-          this.$.detailColumns.setStyle("display: none");
-        }
-      },
-      disabledChanged: function () {
-        this.inherited(arguments);
-        var disabled = this.getDisabled();
-        this.$.minimumQty.addRemoveClass("disabled", disabled);
-        this.$.multipleQty.addRemoveClass("disabled", disabled);
-        this.$.earliestDate.addRemoveClass("disabled", disabled);
+        this.$.descriptionContainer.setShowing(this.getShowDetail());
       },
       /**
         Can accept a two property object with an item source and vendor item number
@@ -176,9 +137,10 @@ white:true, strict:false*/
 
         // Handle menu actions
         that.$.openItem.setShowing(true);
-        that.$.newItem.setShowing(true);
         that.$.openItem.setDisabled(true);
+        that.$.newItem.setShowing(true);
         that.$.newItem.setDisabled(_couldNotCreate.apply(this) || this.disabled);
+
         if (Model) { setPrivileges(); }
 
         if (this.getShowDetail()) {
@@ -187,15 +149,17 @@ white:true, strict:false*/
             multipleQty = value ? value.get("multipleOrderQuantity") : 0,
             earliestDate = value ? XT.date.applyTimezoneOffset(value.get("earliestDate"), true) : null,
             scale = XT.QTY_SCALE;
-          this.$.contractLabel.setShowing(contract);
-          this.$.minimumQtyLabel.setShowing(minimumQty);
-          this.$.minimumQty.setShowing(minimumQty);
+
+          this.$.contractRow.setShowing(!!contract);
+          this.$.description.setContent(contract);
+
+          this.$.minimumQtyRow.setShowing(!!minimumQty);
           this.$.minimumQty.setContent(Globalize.format(minimumQty, "n" + scale));
-          this.$.multipleQtyLabel.setShowing(multipleQty);
-          this.$.multipleQty.setShowing(multipleQty);
+
+          this.$.multipleQtyRow.setShowing(!!multipleQty);
           this.$.multipleQty.setContent(Globalize.format(multipleQty, "n" + scale));
-          this.$.earliestDateLabel.setShowing(earliestDate);
-          this.$.earliestDate.setShowing(earliestDate);
+
+          this.$.earliestDateRow.setShowing(!!earliestDate);
           this.$.earliestDate.setContent(Globalize.format(earliestDate, "d"));
         }
       },
index bf6ef81..4a7506f 100644 (file)
@@ -65,7 +65,6 @@ strict:true, trailing:true, white:true */
     "_shipTo": "Ship To",
     "_showQuotesAfterConverted": "Show Quotes after Conversion to SO",
     "_showSaveAndAddbutton": "Show 'Save and Add to Packing List' Button on Sales Order",
-    "_staleAnalysisWarning": "Free trial demo analysis data will not be updated from your live changes.",
     "_termsType": "Terms Type",
     "_thisWeek": "This Week",
     "_thisMonth": "This Month",
index 583fe69..97250d3 100644 (file)
@@ -98,11 +98,6 @@ trailing:true, white:true*/
       }
     }
 
-    isBiAvailable = XT.session.config.biAvailable && XT.session.privileges.get("ViewSalesHistory");
-    if (isBiAvailable) {
-      module.panels.push({name: "salesAnalysisPage", kind: "analysisFrame"});
-    }
-
     XT.app.$.postbooks.insertModule(module, 0);
 
     relevantPrivileges = [
@@ -175,54 +170,5 @@ trailing:true, white:true*/
     ];
     XT.session.addRelevantPrivileges(module.name, relevantPrivileges);
 
-    /**
-      This iFrame is to show the Sales Analysis report from Pentaho.
-      On creation, it uses the analysis route to generate a signed,
-      encoded JWT which it sends to Pentaho to get the report.
-    */
-    enyo.kind({
-      name: "analysisFrame",
-      label: "_analysis".loc(),
-      tag: "iframe",
-      style: "border: none;",
-      attributes: {src: ""},
-      events: {
-        onMessage: ""
-      },
-      published: {
-        source: ""
-      },
-
-      create: function () {
-        this.inherited(arguments);
-        if (XT.session.config.freeDemo) {
-          this.doMessage({message: "_staleAnalysisWarning".loc()});
-        }
-        // generate the web token and render
-        // the iFrame
-        var url, ajax = new enyo.Ajax({
-          url: XT.getOrganizationPath() + "/analysis",
-          handleAs: "text"
-        });
-        ajax.response(this, function (inSender, inResponse) {
-          this.setSource(inResponse);
-        });
-        // uh oh. HTTP error
-        ajax.error(this, function (inSender, inResponse) {
-          // TODO: trigger some kind of error here
-          console.log("There was a problem generating the iFrame");
-        });
-        // param for the report name
-        ajax.go({reportUrl: "content/saiku-ui/index.html?biplugin=true"});
-      },
-      /**
-        When the published source value is set, this sets the src
-        attribute on the iFrame.
-      */
-      sourceChanged: function () {
-        this.inherited(arguments);
-        this.setAttributes({src: this.getSource()});
-      }
-    });
   };
 }());
index 6180e9e..ed2035d 100644 (file)
@@ -19,7 +19,7 @@ trailing:true, white:true*/
       parameters: [{
         attribute: "shipDate",
         operator: ">=",
-        value: XT.date.applyTimezoneOffset(XV.Date.prototype.textToDate("-30"), true)
+        value: XT.date.applyTimezoneOffset(XV.DateWidget.prototype.textToDate("-30"), true)
       }]
     },
     dateField: "shipDate",
@@ -40,11 +40,11 @@ trailing:true, white:true*/
       parameters: [{
         attribute: "orderDate",
         operator: ">=",
-        value: XT.date.applyTimezoneOffset(XV.Date.prototype.textToDate("0"), true)
+        value: XT.date.applyTimezoneOffset(XV.DateWidget.prototype.textToDate("0"), true)
       }, {
         attribute: "orderDate",
         operator: "<=",
-        value: XT.date.applyTimezoneOffset(XV.Date.prototype.textToDate("+30"), true)
+        value: XT.date.applyTimezoneOffset(XV.DateWidget.prototype.textToDate("+30"), true)
       }]
     },
     dateField: "orderDate",
index b4ef264..a4a67c4 100644 (file)
@@ -12,6 +12,8 @@
     "../lib/orm/source/xt/functions/add_primary_key.sql",
     "../lib/orm/source/xt/functions/create_table.sql",
 
+    "public/patches/fixflcol.sql",
+
     "public/indexes/apopentax.sql",
     "public/indexes/aropentax.sql",
     "public/indexes/asohisttax.sql",
@@ -81,6 +83,7 @@
     "public/functions/averagesalesprice.sql",
     "public/functions/avgcost.sql",
     "public/functions/balanceitemsite.sql",
+    "public/functions/bankreconciliation.sql",
     "public/functions/basecurrid.sql",
     "public/functions/bomcontains.sql",
     "public/functions/bomhistsequence.sql",
     "public/functions/releaseinvcnumber.sql",
     "public/functions/releasenumber.sql",
     "public/functions/releaseponumber.sql",
+    "public/functions/releasepr.sql",
     "public/functions/releaseprnumber.sql",
     "public/functions/releasepurchaseorder.sql",
     "public/functions/releasequnumber.sql",
     "public/tables/cashrcpt.sql",
     "public/tables/ccpay.sql",
     "public/tables/ccbank.sql",
+    "public/tables/checkhead.sql",
     "public/tables/metric.sql",
     "public/tables/payco.sql",
     "public/tables/priv.sql",
index 8538760..3ae61d0 100644 (file)
@@ -35,10 +35,8 @@ BEGIN
   FROM itemsite
   WHERE (itemsite_id=pItemsiteid);
 
---  Post the invtrans records associated with the itemlocdist records
-  PERFORM postInvhist(itemlocdist_invhist_id)
-     FROM itemlocdist
-    WHERE(itemlocdist_series=_itemlocseries);
+--  Post the itemloc series which will postIntoTrialBalance and postInvHist
+  PERFORM postItemlocSeries(_itemlocseries);
 
 --  Kill the resultant distribution records
   DELETE FROM itemlocdist
diff --git a/foundation-database/public/functions/bankreconciliation.sql b/foundation-database/public/functions/bankreconciliation.sql
new file mode 100644 (file)
index 0000000..b670282
--- /dev/null
@@ -0,0 +1,361 @@
+CREATE OR REPLACE FUNCTION bankReconciliation(pBankrecid INTEGER, pTask TEXT) RETURNS INTEGER AS $$
+-- Copyright (c) 1999-2014 by OpenMFG LLC, d/b/a xTuple. 
+-- See www.xtuple.com/CPAL for the full text of the software license.
+-- posting and reopening bank reconciliations are nearly identical.
+-- the main differences revolve around what cleanup is done before starting.
+-- other than that, posting and reopening touch the same g/l accounts but with
+-- debits and credits reversed.
+DECLARE
+  _accntid      INTEGER;
+  _bankrecid    INTEGER;
+  _gltransid    INTEGER;
+  _post         BOOLEAN;
+  _r            RECORD;
+  _result       INTEGER;
+  _sequence     INTEGER;
+  _sign         INTEGER := 1;
+  _tax          RECORD;
+
+BEGIN
+
+  CASE lower(pTask)
+    WHEN 'post'   THEN _post = TRUE;
+    WHEN 'reopen' THEN _post = FALSE;
+    ELSE RAISE EXCEPTION
+          'bankReconciliation got an invalid task %1 [xtuple: bankReconciliation, -2, %2]',
+          pTask, pTask;
+  END CASE;
+
+  -- Check the accnt information to make sure it is valid
+  SELECT accnt_id INTO _accntid
+    FROM bankrec
+    JOIN bankaccnt ON (bankrec_bankaccnt_id=bankaccnt_id)
+    JOIN accnt     ON (bankaccnt_accnt_id=accnt_id)
+   WHERE (bankrec_id=pBankrecid);
+  IF ( NOT FOUND ) THEN
+    RAISE EXCEPTION 'bankReconciliation %1 %2 did not find the bank''s G/L account [xtuple: bankReconciliation, -1, %3, %4]',
+                    pTask, pBankrecid, pTask, pBankrecid;
+  END IF;
+
+  IF _post THEN
+    DELETE FROM bankrecitem
+     WHERE ( (NOT bankrecitem_cleared)
+       AND   (bankrecitem_bankrec_id=pBankrecid) );
+
+    -- Post any cleared bankadj items and convert the bankrecitem
+    FOR _r IN SELECT bankrecitem_id, bankrecitem_source_id
+                FROM bankrecitem, bankadj
+               WHERE ( (bankrecitem_source = 'AD')
+                 AND   (bankrecitem_source_id=bankadj_id)
+                 AND   (bankrecitem_cleared)
+                 AND   (NOT bankadj_posted)
+                 AND   (bankrecitem_bankrec_id=pBankrecid) ) LOOP
+
+      _sequence := postBankAdjustment(_r.bankrecitem_source_id);
+
+      IF (_sequence < 0) THEN
+        RAISE EXCEPTION 'postBankAdjustment %1 %2 failed during bankReconciliation [xtuple: postBankAdjustment, -10, %3, %4, %5]',
+                         pTask, pBankrecid, pTask, pBankrecid, _sequence;
+      END IF;
+
+      SELECT gltrans_id INTO _gltransid
+        FROM gltrans
+       WHERE ( (gltrans_sequence=_sequence)
+         AND   (gltrans_accnt_id=_accntid) );
+      IF ( NOT FOUND ) THEN
+        RAISE EXCEPTION 'bankReconciliation %1 %2 did not find exactly one gltrans record for %3 [xtuple: bankReconciliation, -11, %4, %5, %6]',
+                        pTask, pBankrecid, _sequence, pTask, pBankrecid, _sequence;
+      END IF;
+
+      UPDATE bankrecitem
+         SET bankrecitem_source = 'GL',
+             bankrecitem_source_id=_gltransid
+       WHERE (bankrecitem_id=_r.bankrecitem_id);
+
+    END LOOP;
+
+  ELSE -- NOT _post, therefore must be reopen
+    _sign := -1;
+    SELECT bankrec_id INTO _bankrecid
+      FROM bankrec
+     WHERE (NOT bankrec_posted);
+    IF (FOUND) THEN
+      -- Delete any bankrecitem records for unposted periods
+      DELETE FROM bankrecitem
+       WHERE (bankrecitem_bankrec_id=_bankrecid);
+      -- Delete any bankrec records for unposted period
+      DELETE FROM bankrec
+       WHERE (bankrec_id=_bankrecid);
+    END IF;
+  END IF;
+
+  IF (fetchMetricBool('CashBasedTax')) THEN
+    -- Cash based tax distributions
+    -- GL Transactions
+    SELECT fetchGLSequence() INTO _sequence;
+    FOR _r IN SELECT *
+              FROM bankrecitem
+             WHERE ( (bankrecitem_cleared)
+               AND   (bankrecitem_bankrec_id=pBankrecid) ) LOOP
+      -- first, debit the tax liability clearing account
+      -- and credit the tax liability distribution account
+      -- for each tax code
+      FOR _tax IN SELECT docnumber, custname, distdate, source, doctype,
+                         tax_sales_accnt_id, tax_dist_accnt_id,
+                         ROUND(currToBase(currid, ROUND(SUM(taxhist_tax),2), taxhist_docdate) * percentpaid, 2) AS taxbasevalue
+                  FROM (
+                        -- Cash receipt, gltrans
+                        SELECT aropen_docnumber AS docnumber, cust_name AS custname,
+                               aropen_curr_id AS currid, gltrans_date AS distdate,
+                               (cashrcptitem_amount / aropen_amount) AS percentpaid,
+                               gltrans_source AS source, gltrans_doctype AS doctype,
+                               tax_sales_accnt_id, tax_dist_accnt_id,
+                               taxhist_tax, taxhist_docdate
+                        FROM gltrans JOIN cashrcpt  ON ((gltrans_source='A/R')
+                                                    AND (gltrans_doctype='CR')
+                                                    AND (gltrans_misc_id=cashrcpt_id))
+                                     JOIN cashrcptitem ON (cashrcptitem_cashrcpt_id=cashrcpt_id)
+                                     JOIN aropen ON (aropen_id=cashrcptitem_aropen_id)
+                                     JOIN custinfo ON (cust_id=aropen_cust_id)
+                                     JOIN cohist ON (cohist_invcnumber=aropen_docnumber AND cohist_doctype=aropen_doctype)
+                                     JOIN cohisttax ON (taxhist_parent_id=cohist_id)
+                                     JOIN tax ON (tax_id=taxhist_tax_id)
+                        WHERE (gltrans_id=_r.bankrecitem_source_id)
+                        -- Cash receipt, sltrans
+                        UNION
+                        SELECT aropen_docnumber AS docnumber, cust_name AS custname,
+                               aropen_curr_id AS currid, sltrans_date AS distdate,
+                               (cashrcptitem_amount / aropen_amount) AS percentpaid,
+                               sltrans_source AS source, sltrans_doctype AS doctype,
+                               tax_sales_accnt_id, tax_dist_accnt_id,
+                               taxhist_tax, taxhist_docdate
+                        FROM sltrans JOIN cashrcpt  ON ((sltrans_source='A/R')
+                                                    AND (sltrans_doctype='CR')
+                                                    AND (sltrans_misc_id=cashrcpt_id))
+                                     JOIN cashrcptitem ON (cashrcptitem_cashrcpt_id=cashrcpt_id)
+                                     JOIN aropen ON (aropen_id=cashrcptitem_aropen_id)
+                                     JOIN custinfo ON (cust_id=aropen_cust_id)
+                                     JOIN cohist ON (cohist_invcnumber=aropen_docnumber AND cohist_doctype=aropen_doctype)
+                                     JOIN cohisttax ON (taxhist_parent_id=cohist_id)
+                                     JOIN tax ON (tax_id=taxhist_tax_id)
+                        WHERE (sltrans_id=_r.bankrecitem_source_id)
+                        -- Cash payment, gltrans
+                        UNION
+                        SELECT apopen_docnumber AS docnumber, vend_name AS vendname,
+                               apopen_curr_id AS currid, gltrans_date AS distdate,
+                               (vohead_amount / apopen_amount) AS percentpaid,
+                               gltrans_source AS source, gltrans_doctype AS doctype,
+                               tax_sales_accnt_id, tax_dist_accnt_id,
+                               taxhist_tax, taxhist_docdate
+                        FROM gltrans JOIN checkhead ON ((gltrans_source='A/P')
+                                                    AND (gltrans_doctype='CK')
+                                                    AND (gltrans_misc_id=checkhead_id))
+                                     JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
+                                     JOIN apopen ON (apopen_id=checkitem_apopen_id)
+                                     JOIN vohead ON (vohead_number=apopen_docnumber)
+                                     JOIN vendinfo ON (vend_id=apopen_vend_id)
+                                     JOIN voheadtax ON (taxhist_parent_id=vohead_id)
+                                     JOIN tax ON (tax_id=taxhist_tax_id)
+                        WHERE (gltrans_id=_r.bankrecitem_source_id)
+                        UNION
+                        SELECT apopen_docnumber AS docnumber, vend_name AS vendname,
+                               apopen_curr_id AS currid, gltrans_date AS distdate,
+                               (vohead_amount / apopen_amount) AS percentpaid,
+                               gltrans_source AS source, gltrans_doctype AS doctype,
+                               tax_sales_accnt_id, tax_dist_accnt_id,
+                               taxhist_tax, taxhist_docdate
+                        FROM gltrans JOIN checkhead ON ((gltrans_source='A/P')
+                                                    AND (gltrans_doctype='CK')
+                                                    AND (gltrans_misc_id=checkhead_id))
+                                     JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
+                                     JOIN apopen ON (apopen_id=checkitem_apopen_id)
+                                     JOIN vohead ON (vohead_number=apopen_docnumber)
+                                     JOIN vendinfo ON (vend_id=apopen_vend_id)
+                                     JOIN voitem ON (voitem_vohead_id=vohead_id)
+                                     JOIN voitemtax ON (taxhist_parent_id=voitem_id)
+                                     JOIN tax ON (tax_id=taxhist_tax_id)
+                        WHERE (gltrans_id=_r.bankrecitem_source_id)
+                        -- Cash payment, sltrans
+                        UNION
+                        SELECT apopen_docnumber AS docnumber, vend_name AS vendname,
+                               apopen_curr_id AS currid, sltrans_date AS distdate,
+                               (vohead_amount / apopen_amount) AS percentpaid,
+                               sltrans_source AS source, sltrans_doctype AS doctype,
+                               tax_sales_accnt_id, tax_dist_accnt_id,
+                               taxhist_tax, taxhist_docdate
+                        FROM sltrans JOIN checkhead ON ((sltrans_source='A/P')
+                                                    AND (sltrans_doctype='CK')
+                                                    AND (sltrans_misc_id=checkhead_id))
+                                     JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
+                                     JOIN apopen ON (apopen_id=checkitem_apopen_id)
+                                     JOIN vohead ON (vohead_number=apopen_docnumber)
+                                     JOIN vendinfo ON (vend_id=apopen_vend_id)
+                                     JOIN voheadtax ON (taxhist_parent_id=vohead_id)
+                                     JOIN tax ON (tax_id=taxhist_tax_id)
+                        WHERE (sltrans_id=_r.bankrecitem_source_id)
+                        UNION
+                        SELECT apopen_docnumber AS docnumber, vend_name AS vendname,
+                               apopen_curr_id AS currid, sltrans_date AS distdate,
+                               (vohead_amount / apopen_amount) AS percentpaid,
+                               sltrans_source AS source, sltrans_doctype AS doctype,
+                               tax_sales_accnt_id, tax_dist_accnt_id,
+                               taxhist_tax, taxhist_docdate
+                        FROM sltrans JOIN checkhead ON ((sltrans_source='A/P')
+                                                    AND (sltrans_doctype='CK')
+                                                    AND (sltrans_misc_id=checkhead_id))
+                                     JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
+                                     JOIN apopen ON (apopen_id=checkitem_apopen_id)
+                                     JOIN vohead ON (vohead_number=apopen_docnumber)
+                                     JOIN vendinfo ON (vend_id=apopen_vend_id)
+                                     JOIN voitem ON (voitem_vohead_id=vohead_id)
+                                     JOIN voitemtax ON (taxhist_parent_id=voitem_id)
+                                     JOIN tax ON (tax_id=taxhist_tax_id)
+                        WHERE (sltrans_id=_r.bankrecitem_source_id)
+                       ) AS data
+                  GROUP BY docnumber, custname, currid, distdate, percentpaid,
+                           source, doctype,
+                           tax_sales_accnt_id, tax_dist_accnt_id, taxhist_docdate
+      LOOP
+        SELECT insertIntoGLSeries( _sequence, _tax.source, _tax.doctype, _tax.docnumber,
+                                   _tax.tax_dist_accnt_id, 
+                                   _tax.taxbasevalue * _sign,
+                                   COALESCE(_r.bankrecitem_effdate, _tax.distdate), _tax.custname ) INTO _result;
+        IF (_result < 0) THEN
+          RAISE EXCEPTION 'insertIntoGLSeries failed, result=%', _result;
+        END IF;
+        SELECT insertIntoGLSeries( _sequence, _tax.source, _tax.doctype, _tax.docnumber,
+                                   _tax.tax_sales_accnt_id, 
+                                   (_tax.taxbasevalue * -1.0 * _sign),
+                                   COALESCE(_r.bankrecitem_effdate, _tax.distdate), _tax.custname ) INTO _result;
+        IF (_result < 0) THEN
+          RAISE EXCEPTION 'insertIntoGLSeries failed, result=%', _result;
+        END IF;
+      END LOOP;
+
+      -- second, create a taxpay row for each taxhist
+      FOR _tax IN SELECT taxhist_id, applyid, distdate,
+                         ROUND(taxhist_tax * percentpaid, 2) AS taxpaid
+                  FROM (
+                        -- Cash receipt, gltrans
+                        SELECT taxhist_id, aropen_id AS applyid, gltrans_date AS distdate, taxhist_tax,
+                               (cashrcptitem_amount / aropen_amount) AS percentpaid
+                          FROM gltrans JOIN cashrcpt  ON ((gltrans_source='A/R')
+                                                      AND (gltrans_doctype='CR')
+                                                      AND (gltrans_misc_id=cashrcpt_id))
+                                       JOIN cashrcptitem ON (cashrcptitem_cashrcpt_id=cashrcpt_id)
+                                       JOIN aropen ON (aropen_id=cashrcptitem_aropen_id)
+                                       JOIN cohist ON (cohist_invcnumber=aropen_docnumber AND cohist_doctype=aropen_doctype)
+                                       JOIN cohisttax ON (taxhist_parent_id=cohist_id)
+                          WHERE (gltrans_id=_r.bankrecitem_source_id)
+                        -- Cash receipt, sltrans
+                        UNION
+                        SELECT taxhist_id, aropen_id AS applyid, sltrans_date AS distdate, taxhist_tax,
+                               (cashrcptitem_amount / aropen_amount) AS percentpaid
+                          FROM sltrans JOIN cashrcpt  ON ((sltrans_source='A/R')
+                                                      AND (sltrans_doctype='CR')
+                                                      AND (sltrans_misc_id=cashrcpt_id))
+                                       JOIN cashrcptitem ON (cashrcptitem_cashrcpt_id=cashrcpt_id)
+                                       JOIN aropen ON (aropen_id=cashrcptitem_aropen_id)
+                                       JOIN cohist ON (cohist_invcnumber=aropen_docnumber AND cohist_doctype=aropen_doctype)
+                                       JOIN cohisttax ON (taxhist_parent_id=cohist_id)
+                          WHERE (sltrans_id=_r.bankrecitem_source_id)
+                        -- Cash payment, gltrans
+                        UNION
+                        SELECT taxhist_id, apopen_id AS applyid, gltrans_date AS distdate, taxhist_tax,
+                               (checkitem_amount / apopen_amount) AS percentpaid
+                          FROM gltrans JOIN checkhead  ON ((gltrans_source='A/P')
+                                                       AND (gltrans_doctype='CK')
+                                                       AND (gltrans_misc_id=checkhead_id))
+                                       JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
+                                       JOIN apopen ON (apopen_id=checkitem_apopen_id)
+                                       JOIN vohead ON (vohead_number=apopen_docnumber)
+                                       JOIN voheadtax ON (taxhist_parent_id=vohead_id)
+                          WHERE (gltrans_id=_r.bankrecitem_source_id)
+                        UNION
+                        SELECT taxhist_id, apopen_id AS applyid, gltrans_date AS distdate, taxhist_tax,
+                               (checkitem_amount / apopen_amount) AS percentpaid
+                          FROM gltrans JOIN checkhead  ON ((gltrans_source='A/P')
+                                                       AND (gltrans_doctype='CK')
+                                                       AND (gltrans_misc_id=checkhead_id))
+                                       JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
+                                       JOIN apopen ON (apopen_id=checkitem_apopen_id)
+                                       JOIN vohead ON (vohead_number=apopen_docnumber)
+                                       JOIN voitem ON (voitem_vohead_id=vohead_id)
+                                       JOIN voitemtax ON (taxhist_parent_id=voitem_id)
+                          WHERE (gltrans_id=_r.bankrecitem_source_id)
+                        -- Cash payment, sltrans
+                        UNION
+                        SELECT taxhist_id, apopen_id AS applyid, sltrans_date AS distdate, taxhist_tax,
+                               (checkitem_amount / apopen_amount) AS percentpaid
+                          FROM sltrans JOIN checkhead  ON ((sltrans_source='A/P')
+                                                       AND (sltrans_doctype='CK')
+                                                       AND (sltrans_misc_id=checkhead_id))
+                                       JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
+                                       JOIN apopen ON (apopen_id=checkitem_apopen_id)
+                                       JOIN vohead ON (vohead_number=apopen_docnumber)
+                                       JOIN voheadtax ON (taxhist_parent_id=vohead_id)
+                          WHERE (sltrans_id=_r.bankrecitem_source_id)
+                        UNION
+                        SELECT taxhist_id, apopen_id AS applyid, sltrans_date AS distdate, taxhist_tax,
+                               (checkitem_amount / apopen_amount) AS percentpaid
+                          FROM sltrans JOIN checkhead  ON ((sltrans_source='A/P')
+                                                       AND (sltrans_doctype='CK')
+                                                       AND (sltrans_misc_id=checkhead_id))
+                                       JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
+                                       JOIN apopen ON (apopen_id=checkitem_apopen_id)
+                                       JOIN vohead ON (vohead_number=apopen_docnumber)
+                                       JOIN voitem ON (voitem_vohead_id=vohead_id)
+                                       JOIN voitemtax ON (taxhist_parent_id=voitem_id)
+                          WHERE (sltrans_id=_r.bankrecitem_source_id)
+                       ) AS data
+      LOOP
+        IF _post THEN
+          INSERT INTO taxpay
+          ( taxpay_taxhist_id, taxpay_apply_id, taxpay_distdate, taxpay_tax )
+          VALUES
+          ( _tax.taxhist_id, _tax.applyid, COALESCE(_r.bankrecitem_effdate, _tax.distdate), _tax.taxpaid );
+        ELSE
+          DELETE FROM taxpay
+          WHERE ((taxpay_taxhist_id=_tax.taxhist_id)
+             AND (taxpay_apply_id=_tax.applyid)
+             AND (taxpay_distdate=COALESCE(_r.bankrecitem_effdate, _tax.distdate))
+             AND (taxpay_tax=_tax.taxpaid));
+        END IF;
+      END LOOP;
+
+    END LOOP;
+
+    SELECT postGLSeries(_sequence, fetchJournalNumber('GL-MISC')) INTO _result;
+    IF (_result < 0) THEN
+      RAISE EXCEPTION 'postGLSeries failed, result=%', _result;
+    END IF;
+
+  END IF;
+
+  UPDATE gltrans
+     SET gltrans_rec = _post
+   WHERE ( (gltrans_id IN (SELECT bankrecitem_source_id
+                             FROM bankrecitem
+                            WHERE ((bankrecitem_source = 'GL')
+                              AND  (bankrecitem_cleared)
+                              AND  (bankrecitem_bankrec_id=pBankrecid) ) ) )
+     AND   (gltrans_accnt_id=_accntid) ) ;
+
+  UPDATE sltrans
+     SET sltrans_rec = _post
+   WHERE ( (sltrans_id IN (SELECT bankrecitem_source_id
+                             FROM bankrecitem
+                            WHERE ((bankrecitem_source = 'SL')
+                              AND  (bankrecitem_cleared)
+                              AND  (bankrecitem_bankrec_id=pBankrecid) ) ) )
+     AND   (sltrans_accnt_id=_accntid) ) ;
+
+  UPDATE bankrec SET 
+    bankrec_posted = _post,
+    bankrec_postdate = CASE _post WHEN TRUE THEN now() ELSE NULL END
+   WHERE (bankrec_id=pBankrecid);
+
+  RETURN pBankrecid;
+END;
+$$ LANGUAGE 'plpgsql';
+
index 03cef38..ab7931a 100644 (file)
@@ -18,7 +18,7 @@ DECLARE
   _cost NUMERIC := 0.0;
 BEGIN
   -- cache item info
-  SELECT * INTO _r
+  SELECT *, itemInvPriceRat(item_id) AS itempriceinvrat INTO _r
   FROM itemsite, item
   WHERE (itemsite_item_id=pItemid)
     AND (itemsite_warehous_id=pSiteid)
@@ -33,7 +33,7 @@ BEGIN
       AND (bomitem_rev_id=getActiveRevid('BOM', _r.item_id))
       AND (pEffective BETWEEN bomitem_effective AND (bomitem_expires - 1));
   ELSEIF (fetchMetricBool('WholesalePriceCosting')) THEN
-    _cost := _r.item_listcost;
+    _cost := _r.item_listcost / _r.itempriceinvrat;
   ELSE
     SELECT itemcost(_r.itemsite_id) INTO _cost;
   END IF;
index f7ef3bc..93bfe05 100644 (file)
@@ -18,7 +18,7 @@ DECLARE
   _item RECORD;
   _cust RECORD;
   _shipto RECORD;
-  _iteminvpricerat NUMERIC := 1.0;
+  _itempricepricerat NUMERIC := 1.0;
   _listprice NUMERIC := 0.0;
   _qty NUMERIC;
   _asof DATE;
@@ -38,7 +38,7 @@ BEGIN
   _asof := COALESCE(pAsOf, CURRENT_DATE);
 
 --  Cache Item, Customer and Shipto
-  SELECT item.*, itemCost(itemsite_id) AS invcost INTO _item
+  SELECT item.*, (itemCost(itemsite_id) / itemuomtouomratio(item_id, item_inv_uom_id, item_price_uom_id)) AS invcost INTO _item
   FROM item LEFT OUTER JOIN itemsite ON (itemsite_item_id=item_id AND itemsite_warehous_id=pSiteid)
   WHERE (item_id=pItemid);
 
@@ -52,7 +52,7 @@ BEGIN
 
 -- Get a value here so we do not have to call the function several times
   SELECT itemuomtouomratio(pItemid, pPriceUOM, _item.item_price_uom_id) AS ratio
-    INTO _iteminvpricerat;
+    INTO _itempricepricerat;
 
 -- First get a sales price if any so we when we find other prices
 -- we can determine if we want that price or this price.
@@ -64,15 +64,15 @@ BEGIN
          CASE WHEN (ipsitem_type = 'N') THEN
                (ipsitem_price * itemuomtouomratio(_item.item_id, pPriceUOM, ipsitem_price_uom_id))
               WHEN (ipsitem_type = 'D') THEN
-               noNeg(_item.item_listprice - (_item.item_listprice * ipsitem_discntprcnt) - ipsitem_fixedamtdiscount) * _iteminvpricerat
+               noNeg(_item.item_listprice - (_item.item_listprice * ipsitem_discntprcnt) - ipsitem_fixedamtdiscount) * _itempricepricerat
               WHEN ((ipsitem_type = 'M') AND _long30markups AND _wholesalepricecosting) THEN
-               (_item.item_listcost / (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _iteminvpricerat
+               (_item.item_listcost / (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _itempricepricerat
               WHEN ((ipsitem_type = 'M') AND _long30markups) THEN
-               (_item.invcost / (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _iteminvpricerat
+               (_item.invcost / (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _itempricepricerat
               WHEN (ipsitem_type = 'M' AND _wholesalepricecosting) THEN
-               (_item.item_listcost + (_item.item_listcost * ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _iteminvpricerat
+               (_item.item_listcost + (_item.item_listcost * ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _itempricepricerat
               WHEN (ipsitem_type = 'M') THEN
-               (_item.invcost + (_item.invcost * ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _iteminvpricerat
+               (_item.invcost + (_item.invcost * ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _itempricepricerat
               ELSE 0.00
          END AS ipsprice_price,
          CASE WHEN (ipsitem_item_id=_item.item_id) THEN itemuomtouom(ipsitem_item_id, ipsitem_qty_uom_id, NULL, ipsitem_qtybreak)
@@ -115,15 +115,15 @@ BEGIN
            CASE WHEN (ipsitem_type = 'N') THEN
                  (ipsitem_price * itemuomtouomratio(_item.item_id, pPriceUOM, ipsitem_price_uom_id))
                 WHEN (ipsitem_type = 'D') THEN
-                 noNeg(_item.item_listprice - (_item.item_listprice * ipsitem_discntprcnt) - ipsitem_fixedamtdiscount) * _iteminvpricerat
+                 noNeg(_item.item_listprice - (_item.item_listprice * ipsitem_discntprcnt) - ipsitem_fixedamtdiscount) * _itempricepricerat
                 WHEN ((ipsitem_type = 'M') AND _long30markups AND _wholesalepricecosting) THEN
-                 (_item.item_listcost / (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _iteminvpricerat
+                 (_item.item_listcost / (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _itempricepricerat
                 WHEN ((ipsitem_type = 'M') AND _long30markups) THEN
-                 (_item.invcost / (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _iteminvpricerat
+                 (_item.invcost / (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _itempricepricerat
                 WHEN (ipsitem_type = 'M' AND _wholesalepricecosting) THEN
-                 (_item.item_listcost + (_item.item_listcost * ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _iteminvpricerat
+                 (_item.item_listcost + (_item.item_listcost * ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _itempricepricerat
                 WHEN (ipsitem_type = 'M') THEN
-                 (_item.invcost + (_item.invcost * ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _iteminvpricerat
+                 (_item.invcost + (_item.invcost * ipsitem_discntprcnt) + ipsitem_fixedamtdiscount) * _itempricepricerat
                 ELSE 0.00
            END AS protoprice,
            CASE WHEN (ipsitem_item_id=_item.item_id) THEN itemuomtouom(ipsitem_item_id, ipsitem_qty_uom_id, NULL, ipsitem_qtybreak)
index 31856f0..d57867a 100644 (file)
@@ -1,326 +1,8 @@
-
-CREATE OR REPLACE FUNCTION postBankReconciliation(INTEGER) RETURNS INTEGER AS $$
+CREATE OR REPLACE FUNCTION postBankReconciliation(pBankrecid INTEGER) RETURNS INTEGER AS $$
 -- Copyright (c) 1999-2014 by OpenMFG LLC, d/b/a xTuple. 
 -- See www.xtuple.com/CPAL for the full text of the software license.
-DECLARE
-  pBankrecid ALIAS FOR $1;
-  _accntid INTEGER;
-  _sequence INTEGER;
-  _gltransid INTEGER;
-  _result INTEGER;
-  _r RECORD;
-  _tax RECORD;
-
 BEGIN
-
--- Check the accnt information to make sure it is valid
-  SELECT accnt_id INTO _accntid
-    FROM bankrec, bankaccnt, accnt
-   WHERE ( (bankaccnt_accnt_id=accnt_id)
-     AND   (bankrec_bankaccnt_id=bankaccnt_id)
-     AND   (bankrec_id=pBankrecid) );
-  IF ( NOT FOUND ) THEN
-    RETURN -1;
-  END IF;
-
--- Delete any bankrecitem records that are not marked as cleared for cleanliness
-  DELETE FROM bankrecitem
-   WHERE ( (NOT bankrecitem_cleared)
-     AND   (bankrecitem_bankrec_id=pBankrecid) );
-
--- Post any bankadj items that were marked as cleared and convert the bankrecitem
-  FOR _r IN SELECT bankrecitem_id, bankrecitem_source_id
-              FROM bankrecitem, bankadj
-             WHERE ( (bankrecitem_source = 'AD')
-               AND   (bankrecitem_source_id=bankadj_id)
-               AND   (bankrecitem_cleared)
-               AND   (NOT bankadj_posted)
-               AND   (bankrecitem_bankrec_id=pBankrecid) ) LOOP
-
-    SELECT postBankAdjustment(_r.bankrecitem_source_id) INTO _sequence;
-
-    IF (_sequence < 0) THEN
-      RETURN -10;
-    END IF;
-
-    SELECT gltrans_id INTO _gltransid
-      FROM gltrans
-     WHERE ( (gltrans_sequence=_sequence)
-       AND   (gltrans_accnt_id=_accntid) );
-    IF ( NOT FOUND ) THEN
-      RETURN -11;
-    END IF;
-
-    UPDATE bankrecitem
-       SET bankrecitem_source = 'GL',
-           bankrecitem_source_id=_gltransid
-     WHERE (bankrecitem_id=_r.bankrecitem_id);
-
-  END LOOP;
-
-  IF (fetchMetricBool('CashBasedTax')) THEN
-    -- Cash based tax distributions
-    -- GL Transactions
-    SELECT fetchGLSequence() INTO _sequence;
-    FOR _r IN SELECT *
-              FROM bankrecitem
-             WHERE ( (bankrecitem_cleared)
-               AND   (bankrecitem_bankrec_id=pBankrecid) ) LOOP
-      -- first, debit the tax liability clearing account
-      -- and credit the tax liability distribution account
-      -- for each tax code
-      FOR _tax IN SELECT docnumber, custname, distdate, source, doctype,
-                         tax_sales_accnt_id, tax_dist_accnt_id,
-                         ROUND(currToBase(currid, ROUND(SUM(taxhist_tax),2), taxhist_docdate) * percentpaid, 2) AS taxbasevalue
-                  FROM (
-                        -- Cash receipt, gltrans
-                        SELECT aropen_docnumber AS docnumber, cust_name AS custname,
-                               aropen_curr_id AS currid, gltrans_date AS distdate,
-                               (cashrcptitem_amount / aropen_amount) AS percentpaid,
-                               gltrans_source AS source, gltrans_doctype AS doctype,
-                               tax_sales_accnt_id, tax_dist_accnt_id,
-                               taxhist_tax, taxhist_docdate
-                        FROM gltrans JOIN cashrcpt  ON ((gltrans_source='A/R')
-                                                    AND (gltrans_doctype='CR')
-                                                    AND (gltrans_misc_id=cashrcpt_id))
-                                     JOIN cashrcptitem ON (cashrcptitem_cashrcpt_id=cashrcpt_id)
-                                     JOIN aropen ON (aropen_id=cashrcptitem_aropen_id)
-                                     JOIN custinfo ON (cust_id=aropen_cust_id)
-                                     JOIN cohist ON (cohist_invcnumber=aropen_docnumber AND cohist_doctype=aropen_doctype)
-                                     JOIN cohisttax ON (taxhist_parent_id=cohist_id)
-                                     JOIN tax ON (tax_id=taxhist_tax_id)
-                        WHERE (gltrans_id=_r.bankrecitem_source_id)
-                        -- Cash receipt, sltrans
-                        UNION
-                        SELECT aropen_docnumber AS docnumber, cust_name AS custname,
-                               aropen_curr_id AS currid, sltrans_date AS distdate,
-                               (cashrcptitem_amount / aropen_amount) AS percentpaid,
-                               sltrans_source AS source, sltrans_doctype AS doctype,
-                               tax_sales_accnt_id, tax_dist_accnt_id,
-                               taxhist_tax, taxhist_docdate
-                        FROM sltrans JOIN cashrcpt  ON ((sltrans_source='A/R')
-                                                    AND (sltrans_doctype='CR')
-                                                    AND (sltrans_misc_id=cashrcpt_id))
-                                     JOIN cashrcptitem ON (cashrcptitem_cashrcpt_id=cashrcpt_id)
-                                     JOIN aropen ON (aropen_id=cashrcptitem_aropen_id)
-                                     JOIN custinfo ON (cust_id=aropen_cust_id)
-                                     JOIN cohist ON (cohist_invcnumber=aropen_docnumber AND cohist_doctype=aropen_doctype)
-                                     JOIN cohisttax ON (taxhist_parent_id=cohist_id)
-                                     JOIN tax ON (tax_id=taxhist_tax_id)
-                        WHERE (sltrans_id=_r.bankrecitem_source_id)
-                        -- Cash payment, gltrans
-                        UNION
-                        SELECT apopen_docnumber AS docnumber, vend_name AS vendname,
-                               apopen_curr_id AS currid, gltrans_date AS distdate,
-                               (vohead_amount / apopen_amount) AS percentpaid,
-                               gltrans_source AS source, gltrans_doctype AS doctype,
-                               tax_sales_accnt_id, tax_dist_accnt_id,
-                               taxhist_tax, taxhist_docdate
-                        FROM gltrans JOIN checkhead ON ((gltrans_source='A/P')
-                                                    AND (gltrans_doctype='CK')
-                                                    AND (gltrans_misc_id=checkhead_id))
-                                     JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                     JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                     JOIN vohead ON (vohead_number=apopen_docnumber)
-                                     JOIN vendinfo ON (vend_id=apopen_vend_id)
-                                     JOIN voheadtax ON (taxhist_parent_id=vohead_id)
-                                     JOIN tax ON (tax_id=taxhist_tax_id)
-                        WHERE (gltrans_id=_r.bankrecitem_source_id)
-                        UNION
-                        SELECT apopen_docnumber AS docnumber, vend_name AS vendname,
-                               apopen_curr_id AS currid, gltrans_date AS distdate,
-                               (vohead_amount / apopen_amount) AS percentpaid,
-                               gltrans_source AS source, gltrans_doctype AS doctype,
-                               tax_sales_accnt_id, tax_dist_accnt_id,
-                               taxhist_tax, taxhist_docdate
-                        FROM gltrans JOIN checkhead ON ((gltrans_source='A/P')
-                                                    AND (gltrans_doctype='CK')
-                                                    AND (gltrans_misc_id=checkhead_id))
-                                     JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                     JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                     JOIN vohead ON (vohead_number=apopen_docnumber)
-                                     JOIN vendinfo ON (vend_id=apopen_vend_id)
-                                     JOIN voitem ON (voitem_vohead_id=vohead_id)
-                                     JOIN voitemtax ON (taxhist_parent_id=voitem_id)
-                                     JOIN tax ON (tax_id=taxhist_tax_id)
-                        WHERE (gltrans_id=_r.bankrecitem_source_id)
-                        -- Cash payment, sltrans
-                        UNION
-                        SELECT apopen_docnumber AS docnumber, vend_name AS vendname,
-                               apopen_curr_id AS currid, sltrans_date AS distdate,
-                               (vohead_amount / apopen_amount) AS percentpaid,
-                               sltrans_source AS source, sltrans_doctype AS doctype,
-                               tax_sales_accnt_id, tax_dist_accnt_id,
-                               taxhist_tax, taxhist_docdate
-                        FROM sltrans JOIN checkhead ON ((sltrans_source='A/P')
-                                                    AND (sltrans_doctype='CK')
-                                                    AND (sltrans_misc_id=checkhead_id))
-                                     JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                     JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                     JOIN vohead ON (vohead_number=apopen_docnumber)
-                                     JOIN vendinfo ON (vend_id=apopen_vend_id)
-                                     JOIN voheadtax ON (taxhist_parent_id=vohead_id)
-                                     JOIN tax ON (tax_id=taxhist_tax_id)
-                        WHERE (sltrans_id=_r.bankrecitem_source_id)
-                        UNION
-                        SELECT apopen_docnumber AS docnumber, vend_name AS vendname,
-                               apopen_curr_id AS currid, sltrans_date AS distdate,
-                               (vohead_amount / apopen_amount) AS percentpaid,
-                               sltrans_source AS source, sltrans_doctype AS doctype,
-                               tax_sales_accnt_id, tax_dist_accnt_id,
-                               taxhist_tax, taxhist_docdate
-                        FROM sltrans JOIN checkhead ON ((sltrans_source='A/P')
-                                                    AND (sltrans_doctype='CK')
-                                                    AND (sltrans_misc_id=checkhead_id))
-                                     JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                     JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                     JOIN vohead ON (vohead_number=apopen_docnumber)
-                                     JOIN vendinfo ON (vend_id=apopen_vend_id)
-                                     JOIN voitem ON (voitem_vohead_id=vohead_id)
-                                     JOIN voitemtax ON (taxhist_parent_id=voitem_id)
-                                     JOIN tax ON (tax_id=taxhist_tax_id)
-                        WHERE (sltrans_id=_r.bankrecitem_source_id)
-                       ) AS data
-                  GROUP BY docnumber, custname, currid, distdate, percentpaid,
-                           source, doctype,
-                           tax_sales_accnt_id, tax_dist_accnt_id, taxhist_docdate
-      LOOP
-        SELECT insertIntoGLSeries( _sequence, _tax.source, _tax.doctype, _tax.docnumber,
-                                   _tax.tax_dist_accnt_id, 
-                                   _tax.taxbasevalue,
-                                   COALESCE(_r.bankrecitem_effdate, _tax.distdate), _tax.custname ) INTO _result;
-        IF (_result < 0) THEN
-          RAISE EXCEPTION 'insertIntoGLSeries failed, result=%', _result;
-        END IF;
-        SELECT insertIntoGLSeries( _sequence, _tax.source, _tax.doctype, _tax.docnumber,
-                                   _tax.tax_sales_accnt_id, 
-                                   (_tax.taxbasevalue * -1.0),
-                                   COALESCE(_r.bankrecitem_effdate, _tax.distdate), _tax.custname ) INTO _result;
-        IF (_result < 0) THEN
-          RAISE EXCEPTION 'insertIntoGLSeries failed, result=%', _result;
-        END IF;
-      END LOOP;
-
-      -- second, create a taxpay row for each taxhist
-      FOR _tax IN SELECT taxhist_id, applyid, distdate,
-                         ROUND(taxhist_tax * percentpaid, 2) AS taxpaid
-                  FROM (
-                        -- Cash receipt, gltrans
-                        SELECT taxhist_id, aropen_id AS applyid, gltrans_date AS distdate, taxhist_tax,
-                               (cashrcptitem_amount / aropen_amount) AS percentpaid
-                          FROM gltrans JOIN cashrcpt  ON ((gltrans_source='A/R')
-                                                      AND (gltrans_doctype='CR')
-                                                      AND (gltrans_misc_id=cashrcpt_id))
-                                       JOIN cashrcptitem ON (cashrcptitem_cashrcpt_id=cashrcpt_id)
-                                       JOIN aropen ON (aropen_id=cashrcptitem_aropen_id)
-                                       JOIN cohist ON (cohist_invcnumber=aropen_docnumber AND cohist_doctype=aropen_doctype)
-                                       JOIN cohisttax ON (taxhist_parent_id=cohist_id)
-                          WHERE (gltrans_id=_r.bankrecitem_source_id)
-                        -- Cash receipt, sltrans
-                        UNION
-                        SELECT taxhist_id, aropen_id AS applyid, sltrans_date AS distdate, taxhist_tax,
-                               (cashrcptitem_amount / aropen_amount) AS percentpaid
-                          FROM sltrans JOIN cashrcpt  ON ((sltrans_source='A/R')
-                                                      AND (sltrans_doctype='CR')
-                                                      AND (sltrans_misc_id=cashrcpt_id))
-                                       JOIN cashrcptitem ON (cashrcptitem_cashrcpt_id=cashrcpt_id)
-                                       JOIN aropen ON (aropen_id=cashrcptitem_aropen_id)
-                                       JOIN cohist ON (cohist_invcnumber=aropen_docnumber AND cohist_doctype=aropen_doctype)
-                                       JOIN cohisttax ON (taxhist_parent_id=cohist_id)
-                          WHERE (sltrans_id=_r.bankrecitem_source_id)
-                        -- Cash payment, gltrans
-                        UNION
-                        SELECT taxhist_id, apopen_id AS applyid, gltrans_date AS distdate, taxhist_tax,
-                               (checkitem_amount / apopen_amount) AS percentpaid
-                          FROM gltrans JOIN checkhead  ON ((gltrans_source='A/P')
-                                                       AND (gltrans_doctype='CK')
-                                                       AND (gltrans_misc_id=checkhead_id))
-                                       JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                       JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                       JOIN vohead ON (vohead_number=apopen_docnumber)
-                                       JOIN voheadtax ON (taxhist_parent_id=vohead_id)
-                          WHERE (gltrans_id=_r.bankrecitem_source_id)
-                        UNION
-                        SELECT taxhist_id, apopen_id AS applyid, gltrans_date AS distdate, taxhist_tax,
-                               (checkitem_amount / apopen_amount) AS percentpaid
-                          FROM gltrans JOIN checkhead  ON ((gltrans_source='A/P')
-                                                       AND (gltrans_doctype='CK')
-                                                       AND (gltrans_misc_id=checkhead_id))
-                                       JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                       JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                       JOIN vohead ON (vohead_number=apopen_docnumber)
-                                       JOIN voitem ON (voitem_vohead_id=vohead_id)
-                                       JOIN voitemtax ON (taxhist_parent_id=voitem_id)
-                          WHERE (gltrans_id=_r.bankrecitem_source_id)
-                        -- Cash payment, sltrans
-                        UNION
-                        SELECT taxhist_id, apopen_id AS applyid, sltrans_date AS distdate, taxhist_tax,
-                               (checkitem_amount / apopen_amount) AS percentpaid
-                          FROM sltrans JOIN checkhead  ON ((sltrans_source='A/P')
-                                                       AND (sltrans_doctype='CK')
-                                                       AND (sltrans_misc_id=checkhead_id))
-                                       JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                       JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                       JOIN vohead ON (vohead_number=apopen_docnumber)
-                                       JOIN voheadtax ON (taxhist_parent_id=vohead_id)
-                          WHERE (sltrans_id=_r.bankrecitem_source_id)
-                        UNION
-                        SELECT taxhist_id, apopen_id AS applyid, sltrans_date AS distdate, taxhist_tax,
-                               (checkitem_amount / apopen_amount) AS percentpaid
-                          FROM sltrans JOIN checkhead  ON ((sltrans_source='A/P')
-                                                       AND (sltrans_doctype='CK')
-                                                       AND (sltrans_misc_id=checkhead_id))
-                                       JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                       JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                       JOIN vohead ON (vohead_number=apopen_docnumber)
-                                       JOIN voitem ON (voitem_vohead_id=vohead_id)
-                                       JOIN voitemtax ON (taxhist_parent_id=voitem_id)
-                          WHERE (sltrans_id=_r.bankrecitem_source_id)
-                       ) AS data
-      LOOP
-        INSERT INTO taxpay
-        ( taxpay_taxhist_id, taxpay_apply_id, taxpay_distdate, taxpay_tax )
-        VALUES
-        ( _tax.taxhist_id, _tax.applyid, COALESCE(_r.bankrecitem_effdate, _tax.distdate), _tax.taxpaid );
-      END LOOP;
-
-    END LOOP;
-
-    SELECT postGLSeries(_sequence, fetchJournalNumber('GL-MISC')) INTO _result;
-    IF (_result < 0) THEN
-      RAISE EXCEPTION 'postGLSeries failed, result=%', _result;
-    END IF;
-
-  END IF;
-
-
--- Mark all the gltrans items that have been cleared as reconciled.
-  UPDATE gltrans
-     SET gltrans_rec = TRUE
-   WHERE ( (gltrans_id IN (SELECT bankrecitem_source_id
-                             FROM bankrecitem
-                            WHERE ((bankrecitem_source = 'GL')
-                              AND  (bankrecitem_cleared)
-                              AND  (bankrecitem_bankrec_id=pBankrecid) ) ) )
-     AND   (gltrans_accnt_id=_accntid) ) ;
-
--- Mark all the sltrans items that have been cleared as reconciled.
-  UPDATE sltrans
-     SET sltrans_rec = TRUE
-   WHERE ( (sltrans_id IN (SELECT bankrecitem_source_id
-                             FROM bankrecitem
-                            WHERE ((bankrecitem_source = 'SL')
-                              AND  (bankrecitem_cleared)
-                              AND  (bankrecitem_bankrec_id=pBankrecid) ) ) )
-     AND   (sltrans_accnt_id=_accntid) ) ;
-
--- Mark the bankrec record as posted
-  UPDATE bankrec SET 
-    bankrec_posted = TRUE,
-    bankrec_postdate = now()
-   WHERE (bankrec_id=pBankrecid);
-
-  RETURN pBankrecid;
+  RETURN bankReconciliation(pBankrecid, 'post');
 END;
 $$ LANGUAGE 'plpgsql';
 
index 5fe1b70..b6f20b2 100644 (file)
@@ -46,17 +46,14 @@ BEGIN
     END IF;
   END IF;
 
-  SELECT cashrcpt_cust_id, (cust_number||'-'||cust_name) AS custnote,
-         cashrcpt_fundstype, cashrcpt_number, cashrcpt_docnumber,
-         cashrcpt_distdate, cashrcpt_amount, cashrcpt_discount,
+  SELECT cashrcpt.*,
+         (cust_number||'-'||cust_name) AS custnote,
          (cashrcpt_amount / cashrcpt_curr_rate) AS cashrcpt_amount_base,
-        (cashrcpt_discount / cashrcpt_curr_rate) AS cashrcpt_discount_base,
-         cashrcpt_notes,
+         (cashrcpt_discount / cashrcpt_curr_rate) AS cashrcpt_discount_base,
          cashrcpt_bankaccnt_id AS bankaccnt_id,
          accnt_id AS prepaid_accnt_id,
-         cashrcpt_usecustdeposit,
-         COALESCE(cashrcpt_applydate, cashrcpt_distdate) AS applydate,
-         cashrcpt_curr_id, cashrcpt_curr_rate, cashrcpt_posted, cashrcpt_void INTO _p
+         COALESCE(cashrcpt_applydate, cashrcpt_distdate) AS applydate
+       INTO _p
   FROM cashrcpt LEFT OUTER JOIN custinfo ON (cashrcpt_cust_id=cust_id)
                 LEFT OUTER JOIN accnt ON (accnt_id=findPrepaidAccount(cashrcpt_cust_id))
   WHERE ( (findPrepaidAccount(cashrcpt_cust_id)=0 OR accnt_id > 0) -- G/L interface might be disabled
@@ -337,6 +334,23 @@ BEGIN
                      _p.cashrcpt_distdate,
                      _p.custnote, pCashrcptid );
 
+  -- Post any gain/loss from the alternate currency exchange rate
+  IF (COALESCE(_p.cashrcpt_alt_curr_rate, 0.0) <> 0.0) THEN
+    _exchGain := ROUND((_p.cashrcpt_curr_rate - _p.cashrcpt_alt_curr_rate) * _p.cashrcpt_amount_base, 2);
+
+    IF (_exchGain <> 0) THEN
+      PERFORM insertIntoGLSeries( _sequence, 'A/R', 'CR',
+                          (_p.cashrcpt_fundstype || '-' || _p.cashrcpt_docnumber),
+                          _debitAccntid, (_exchGain * -1.0),
+                          _p.cashrcpt_distdate, _p.custnote, pCashrcptid );      
+                          
+      PERFORM insertIntoGLSeries( _sequence, 'A/R', 'CR',
+                          (_p.cashrcpt_fundstype || '-' || _p.cashrcpt_docnumber),
+                          getGainLossAccntId(_debitAccntid), _exchGain,
+                          _p.cashrcpt_distdate, _p.custnote, pCashrcptid );      
+    END IF;
+  END IF;
+
   PERFORM postGLSeries(_sequence, pJournalNumber);
 
   -- convert the cashrcptitem records to applications against the cm/cd if we are _predist
index 9a077d1..7765875 100644 (file)
@@ -270,6 +270,23 @@ BEGIN
                              round(_p.checkhead_amount_base, 2),
                               _p.checkhead_checkdate, _gltransNote, pcheckid );
 
+  -- Post any gain/loss from the alternate currency exchange rate
+  IF (COALESCE(_p.checkhead_alt_curr_rate, 0.0) <> 0.0) THEN
+    _exchGain := ROUND((_p.checkhead_curr_rate - _p.checkhead_alt_curr_rate) * _p.checkhead_amount_base, 2);
+
+    IF (_exchGain <> 0) THEN
+      PERFORM insertIntoGLSeries( _sequence, _t.checkrecip_gltrans_source, 'CK',
+                          CAST(_p.checkhead_number AS TEXT),
+                          _p.bankaccntid, (_exchGain * -1.0),
+                          _p.checkhead_checkdate, _gltransNote, pcheckid );      
+                          
+      PERFORM insertIntoGLSeries( _sequence, _t.checkrecip_gltrans_source, 'CK',
+                          CAST(_p.checkhead_number AS TEXT),
+                          getGainLossAccntId(_p.bankaccntid), _exchGain,
+                          _p.checkhead_checkdate, _gltransNote, pcheckid );      
+    END IF;
+  END IF;
+
   PERFORM postGLSeries(_sequence, _journalNumber);
 
   UPDATE checkhead
diff --git a/foundation-database/public/functions/releasepr.sql b/foundation-database/public/functions/releasepr.sql
new file mode 100644 (file)
index 0000000..f004415
--- /dev/null
@@ -0,0 +1,195 @@
+
+CREATE OR REPLACE FUNCTION releasePR(pPrId INTEGER) RETURNS INTEGER AS $$
+-- Copyright (c) 1999-2014 by OpenMFG LLC, d/b/a xTuple. 
+-- See www.xtuple.com/CPAL for the full text of the software license.
+DECLARE
+  _pr RECORD;
+  _w RECORD;
+  _i RECORD;
+  _rows INTEGER := 0;
+  _itemsrcid INTEGER := -1;
+  _poheadid INTEGER := -1;
+  _poitemid INTEGER := -1;
+  _taxtypeid INTEGER := -1;
+  _polinenumber INTEGER;
+  _ponumber NUMERIC;
+  _price NUMERIC;
+
+BEGIN
+
+  -- Cache information
+  SELECT *,
+         CASE WHEN(pr_order_type='W') THEN pr_order_id
+              ELSE -1
+         END AS parentwo,
+         CASE WHEN(pr_order_type='S') THEN pr_order_id
+              ELSE -1
+         END AS parentso
+  INTO _pr
+  FROM pr LEFT OUTER JOIN itemsite ON (pr_itemsite_id = itemsite_id)
+          LEFT OUTER JOIN item ON (item_id = itemsite_item_id)
+          LEFT OUTER JOIN prj ON (prj_id = pr_prj_id)
+  WHERE (pr_id = pPrId);
+  IF (NOT FOUND) THEN
+    RETURN -1;
+  END IF;
+
+  SELECT * INTO _w
+  FROM itemsite JOIN whsinfo ON (warehous_id = itemsite_warehous_id)
+                LEFT OUTER JOIN addr ON (warehous_addr_id = addr_id)
+                LEFT OUTER JOIN cntct ON (warehous_cntct_id = cntct_id)
+  WHERE (itemsite_id = _pr.itemsite_id);
+
+  -- Must either be a single itemsrc or a default itemsrc
+  SELECT itemsrc_id INTO _itemsrcid
+  FROM itemsrc
+  WHERE (itemsrc_item_id = _pr.item_id)
+    AND (_pr.pr_duedate BETWEEN COALESCE(itemsrc_effective, startOfTime()) AND COALESCE(itemsrc_expires, endOfTime()))
+    AND (itemsrc_default);
+  IF (NOT FOUND) THEN
+    SELECT MAX(itemsrc_id), count(*) INTO _itemsrcid, _rows
+    FROM itemsrc
+    WHERE (itemsrc_item_id = _pr.item_id)
+      AND (_pr.pr_duedate BETWEEN COALESCE(itemsrc_effective, startOfTime()) AND COALESCE(itemsrc_expires, endOfTime()))
+    GROUP BY itemsrc_item_id;
+    IF (NOT FOUND) THEN
+      RETURN -2;
+    END IF;
+    IF (_rows > 1) THEN
+      RETURN -2;
+    END IF;
+  END IF;
+    
+  SELECT * INTO _i
+  FROM itemsrc JOIN vendinfo ON (itemsrc_vend_id = vend_id)
+               LEFT OUTER JOIN cntct ON (vend_cntct1_id = cntct_id)
+               LEFT OUTER JOIN addr ON (vend_addr_id = addr_id)
+  WHERE (itemsrc_id = _itemsrcid);
+
+  RAISE NOTICE 'releasepr selected itemsrc_id = % for pr = %', _itemsrcid, _pr.pr_id;
+
+  -- Find matching unreleased PO
+  SELECT COALESCE(pohead_id, -1) INTO _poheadid
+  FROM pohead
+  WHERE ( (pohead_status = 'U')
+    AND (pohead_vend_id = _i.itemsrc_vend_id)
+    AND (COALESCE(pohead_shiptoaddress1, '') = COALESCE(_w.addr_line1, ''))
+    AND (COALESCE(pohead_shiptoaddress2, '') = COALESCE(_w.addr_line2, ''))
+    AND (COALESCE(pohead_shiptoaddress3, '') = COALESCE(_w.addr_line3, ''))
+    AND (COALESCE(pohead_shiptocity, '') = COALESCE(_w.addr_city, ''))
+    AND (COALESCE(pohead_shiptostate, '') = COALESCE(_w.addr_state, ''))
+    AND (COALESCE(pohead_shiptozipcode, '') = COALESCE(_w.addr_postalcode, ''))
+    AND (COALESCE(pohead_shiptocountry, '') = COALESCE(_w.addr_country, '')) );
+
+  IF (NOT FOUND) THEN
+    -- Create new PO
+    SELECT NEXTVAL('pohead_pohead_id_seq') INTO _poheadid;
+    SELECT fetchPoNumber() INTO _ponumber;
+
+    INSERT INTO pohead
+      ( pohead_id, pohead_number, pohead_status, pohead_dropship,
+        pohead_agent_username, pohead_vend_id, pohead_taxzone_id,
+        pohead_orderdate, pohead_curr_id, pohead_cohead_id,
+        pohead_warehous_id, pohead_shipvia,
+        pohead_terms_id, pohead_shipto_cntct_id,
+        pohead_shipto_cntct_honorific, pohead_shipto_cntct_first_name,
+        pohead_shipto_cntct_middle, pohead_shipto_cntct_last_name,
+        pohead_shipto_cntct_suffix, pohead_shipto_cntct_phone,
+        pohead_shipto_cntct_title, pohead_shipto_cntct_fax, 
+        pohead_shipto_cntct_email, pohead_shiptoaddress_id,
+        pohead_shiptoaddress1,
+        pohead_shiptoaddress2,
+        pohead_shiptoaddress3,
+        pohead_shiptocity, 
+        pohead_shiptostate, pohead_shiptozipcode,
+        pohead_shiptocountry, pohead_vend_cntct_id,
+        pohead_vend_cntct_honorific, pohead_vend_cntct_first_name,
+        pohead_vend_cntct_middle, pohead_vend_cntct_last_name,
+        pohead_vend_cntct_suffix, pohead_vend_cntct_phone,
+        pohead_vend_cntct_title, pohead_vend_cntct_fax,
+        pohead_vend_cntct_email, pohead_vendaddress1,
+        pohead_vendaddress2, pohead_vendaddress3,
+        pohead_vendcity, pohead_vendstate,
+        pohead_vendzipcode, pohead_vendcountry )
+    VALUES
+      ( _poheadid, _ponumber, 'U', FALSE,
+        getEffectiveXtUser(), _i.itemsrc_vend_id, _i.vend_taxzone_id,
+        CURRENT_DATE, COALESCE(_i.vend_curr_id, basecurrid()), NULL,
+        COALESCE(_pr.itemsite_warehous_id, -1), COALESCE(_i.vend_shipvia, TEXT('')),
+        COALESCE(_i.vend_terms_id, -1), _w.cntct_id,
+        _w.cntct_honorific, _w.cntct_first_name,
+        _w.cntct_middle, _w.cntct_last_name,
+        _w.cntct_suffix, _w.cntct_phone,
+        _w.cntct_title, _w.cntct_fax,
+        _w.cntct_email, _w.addr_id,
+        COALESCE(_w.addr_line1, ''),
+        COALESCE(_w.addr_line2, ''),
+        COALESCE(_w.addr_line3, ''),
+        COALESCE(_w.addr_city, ''),
+        COALESCE(_w.addr_state, ''), COALESCE(_w.addr_postalcode, ''),
+        COALESCE(_w.addr_country, ''), _i.cntct_id,
+        COALESCE(_i.cntct_honorific, TEXT('')), COALESCE(_i.cntct_first_name, TEXT('')),
+        COALESCE(_i.cntct_middle, TEXT('')), COALESCE(_i.cntct_last_name, TEXT('')),
+        COALESCE(_i.cntct_suffix, TEXT('')), COALESCE(_i.cntct_phone, TEXT('')),
+        COALESCE(_i.cntct_title, TEXT('')), COALESCE(_i.cntct_fax, TEXT('')),
+        COALESCE(_i.cntct_email, TEXT('')), COALESCE(_i.addr_line1, TEXT('')),
+        COALESCE(_i.addr_line2, TEXT('')), COALESCE(_i.addr_line3, TEXT('')),
+        COALESCE(_i.addr_city, TEXT('')), COALESCE(_i.addr_state, TEXT('')),
+        COALESCE(_i.addr_postalcode, TEXT('')), COALESCE(_i.addr_country, TEXT('')) );
+  END IF;
+
+  SELECT NEXTVAL('poitem_poitem_id_seq') INTO _poitemid;
+
+  SELECT (COALESCE(MAX(poitem_linenumber), 0) + 1) INTO _polinenumber
+  FROM poitem
+  WHERE (poitem_pohead_id = _poheadid);
+
+  SELECT COALESCE(itemtax_taxtype_id, -1) INTO _taxtypeid
+  FROM itemtax
+  WHERE (itemtax_item_id = _i.itemsrc_item_id);
+
+  SELECT itemsrcPrice(_i.itemsrc_id,
+                      COALESCE(_pr.itemsite_warehous_id, -1),
+                      FALSE,
+                      (_pr.pr_qtyreq / COALESCE(_i.itemsrc_invvendoruomratio, 1.00)),
+                      COALESCE(_i.vend_curr_id, baseCurrId()),
+                      CURRENT_DATE) INTO _price;
+
+  -- Create PO Item
+  INSERT INTO poitem
+    ( poitem_id, poitem_status, poitem_pohead_id, poitem_linenumber, 
+      poitem_duedate, poitem_itemsite_id,
+      poitem_vend_item_descrip, poitem_vend_uom,
+      poitem_invvenduomratio, poitem_qty_ordered, 
+      poitem_unitprice, poitem_vend_item_number, 
+      poitem_itemsrc_id, poitem_order_id, poitem_order_type, poitem_prj_id, poitem_stdcost, 
+      poitem_manuf_name, poitem_manuf_item_number, 
+      poitem_manuf_item_descrip, poitem_taxtype_id, poitem_comments )
+  VALUES
+    ( _poitemid, 'U', _poheadid, _polinenumber,
+      _pr.pr_duedate, _pr.itemsite_id,
+      COALESCE(_i.itemsrc_vend_item_descrip, TEXT('')), COALESCE(_i.itemsrc_vend_uom, TEXT('')),
+      COALESCE(_i.itemsrc_invvendoruomratio, 1.00), (_pr.pr_qtyreq / COALESCE(_i.itemsrc_invvendoruomratio, 1.00)),
+      _price, COALESCE(_i.itemsrc_vend_item_number, TEXT('')),
+      _i.itemsrc_id, _pr.pr_order_id, _pr.pr_order_type, _pr.prj_id, stdcost(_i.itemsrc_item_id),
+      COALESCE(_i.itemsrc_manuf_name, TEXT('')), COALESCE(_i.itemsrc_manuf_item_number, TEXT('')),
+      COALESCE(_i.itemsrc_manuf_item_descrip, TEXT('')), _taxtypeid,
+      COALESCE(_pr.pr_releasenote, TEXT('')));
+
+  -- Copy characteristics from the pr to the poitem
+  INSERT INTO charass
+    ( charass_target_type, charass_target_id, charass_char_id,
+      charass_value, charass_default, charass_price )
+  SELECT 'PI', _poitemid, charass_char_id,
+         charass_value, charass_default, charass_price
+  FROM charass
+  WHERE ( (charass_target_type='PR')
+    AND   (charass_target_id=pPrId) );
+
+  -- Delete the PR
+  PERFORM deletePr(pPrId);
+
+  RETURN _poitemid;
+
+END;
+$$ LANGUAGE 'plpgsql' VOLATILE;
index 7ee2ad6..2887dcd 100644 (file)
@@ -1,304 +1,8 @@
-
-CREATE OR REPLACE FUNCTION reopenBankReconciliation(INTEGER) RETURNS INTEGER AS $$
+CREATE OR REPLACE FUNCTION reopenBankReconciliation(pBankrecid INTEGER) RETURNS INTEGER AS $$
 -- Copyright (c) 1999-2014 by OpenMFG LLC, d/b/a xTuple. 
 -- See www.xtuple.com/CPAL for the full text of the software license.
-DECLARE
-  pBankrecid ALIAS FOR $1;
-  _bankrecid INTEGER;
-  _accntid INTEGER;
-  _sequence INTEGER;
-  _gltransid INTEGER;
-  _result INTEGER;
-  _r RECORD;
-  _tax RECORD;
-
 BEGIN
-
--- Check the accnt information to make sure it is valid
-  SELECT accnt_id INTO _accntid
-    FROM bankrec, bankaccnt, accnt
-   WHERE ( (bankaccnt_accnt_id=accnt_id)
-     AND   (bankrec_bankaccnt_id=bankaccnt_id)
-     AND   (bankrec_id=pBankrecid) );
-  IF ( NOT FOUND ) THEN
-    RETURN -1;
-  END IF;
-
-  SELECT bankrec_id INTO _bankrecid
-    FROM bankrec
-   WHERE (NOT bankrec_posted);
-  IF (FOUND) THEN
-  -- Delete any bankrecitem records for unposted periods
-    DELETE FROM bankrecitem
-     WHERE (bankrecitem_bankrec_id=_bankrecid);
-  -- Delete any bankrec records for unposted period
-    DELETE FROM bankrec
-     WHERE (bankrec_id=_bankrecid);
-  END IF;
-
-  IF (fetchMetricBool('CashBasedTax')) THEN
-    -- Cash based tax distributions
-    -- GL Transactions
-    SELECT fetchGLSequence() INTO _sequence;
-    FOR _r IN SELECT *
-              FROM bankrecitem
-             WHERE ( (bankrecitem_cleared)
-               AND   (bankrecitem_bankrec_id=pBankrecid) ) LOOP
-      -- first, debit the tax liability clearing account
-      -- and credit the tax liability distribution account
-      -- for each tax code
-      FOR _tax IN SELECT docnumber, custname, distdate, source, doctype,
-                         tax_sales_accnt_id, tax_dist_accnt_id,
-                         ROUND(currToBase(currid, ROUND(SUM(taxhist_tax),2), taxhist_docdate) * percentpaid, 2) AS taxbasevalue
-                  FROM (
-                        -- Cash receipt, gltrans
-                        SELECT aropen_docnumber AS docnumber, cust_name AS custname,
-                               aropen_curr_id AS currid, gltrans_date AS distdate,
-                               (cashrcptitem_amount / aropen_amount) AS percentpaid,
-                               gltrans_source AS source, gltrans_doctype AS doctype,
-                               tax_sales_accnt_id, tax_dist_accnt_id,
-                               taxhist_tax, taxhist_docdate
-                        FROM gltrans JOIN cashrcpt  ON ((gltrans_source='A/R')
-                                                    AND (gltrans_doctype='CR')
-                                                    AND (gltrans_misc_id=cashrcpt_id))
-                                     JOIN cashrcptitem ON (cashrcptitem_cashrcpt_id=cashrcpt_id)
-                                     JOIN aropen ON (aropen_id=cashrcptitem_aropen_id)
-                                     JOIN custinfo ON (cust_id=aropen_cust_id)
-                                     JOIN cohist ON (cohist_invcnumber=aropen_docnumber AND cohist_doctype=aropen_doctype)
-                                     JOIN cohisttax ON (taxhist_parent_id=cohist_id)
-                                     JOIN tax ON (tax_id=taxhist_tax_id)
-                        WHERE (gltrans_id=_r.bankrecitem_source_id)
-                        -- Cash receipt, sltrans
-                        UNION
-                        SELECT aropen_docnumber AS docnumber, cust_name AS custname,
-                               aropen_curr_id AS currid, sltrans_date AS distdate,
-                               (cashrcptitem_amount / aropen_amount) AS percentpaid,
-                               sltrans_source AS source, sltrans_doctype AS doctype,
-                               tax_sales_accnt_id, tax_dist_accnt_id,
-                               taxhist_tax, taxhist_docdate
-                        FROM sltrans JOIN cashrcpt  ON ((sltrans_source='A/R')
-                                                    AND (sltrans_doctype='CR')
-                                                    AND (sltrans_misc_id=cashrcpt_id))
-                                     JOIN cashrcptitem ON (cashrcptitem_cashrcpt_id=cashrcpt_id)
-                                     JOIN aropen ON (aropen_id=cashrcptitem_aropen_id)
-                                     JOIN custinfo ON (cust_id=aropen_cust_id)
-                                     JOIN cohist ON (cohist_invcnumber=aropen_docnumber AND cohist_doctype=aropen_doctype)
-                                     JOIN cohisttax ON (taxhist_parent_id=cohist_id)
-                                     JOIN tax ON (tax_id=taxhist_tax_id)
-                        WHERE (sltrans_id=_r.bankrecitem_source_id)
-                        -- Cash payment, gltrans
-                        UNION
-                        SELECT apopen_docnumber AS docnumber, vend_name AS vendname,
-                               apopen_curr_id AS currid, gltrans_date AS distdate,
-                               (vohead_amount / apopen_amount) AS percentpaid,
-                               gltrans_source AS source, gltrans_doctype AS doctype,
-                               tax_sales_accnt_id, tax_dist_accnt_id,
-                               taxhist_tax, taxhist_docdate
-                        FROM gltrans JOIN checkhead ON ((gltrans_source='A/P')
-                                                    AND (gltrans_doctype='CK')
-                                                    AND (gltrans_misc_id=checkhead_id))
-                                     JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                     JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                     JOIN vohead ON (vohead_number=apopen_docnumber)
-                                     JOIN vendinfo ON (vend_id=apopen_vend_id)
-                                     JOIN voheadtax ON (taxhist_parent_id=vohead_id)
-                                     JOIN tax ON (tax_id=taxhist_tax_id)
-                        WHERE (gltrans_id=_r.bankrecitem_source_id)
-                        UNION
-                        SELECT apopen_docnumber AS docnumber, vend_name AS vendname,
-                               apopen_curr_id AS currid, gltrans_date AS distdate,
-                               (vohead_amount / apopen_amount) AS percentpaid,
-                               gltrans_source AS source, gltrans_doctype AS doctype,
-                               tax_sales_accnt_id, tax_dist_accnt_id,
-                               taxhist_tax, taxhist_docdate
-                        FROM gltrans JOIN checkhead ON ((gltrans_source='A/P')
-                                                    AND (gltrans_doctype='CK')
-                                                    AND (gltrans_misc_id=checkhead_id))
-                                     JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                     JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                     JOIN vohead ON (vohead_number=apopen_docnumber)
-                                     JOIN vendinfo ON (vend_id=apopen_vend_id)
-                                     JOIN voitem ON (voitem_vohead_id=vohead_id)
-                                     JOIN voitemtax ON (taxhist_parent_id=voitem_id)
-                                     JOIN tax ON (tax_id=taxhist_tax_id)
-                        WHERE (gltrans_id=_r.bankrecitem_source_id)
-                        -- Cash payment, sltrans
-                        UNION
-                        SELECT apopen_docnumber AS docnumber, vend_name AS vendname,
-                               apopen_curr_id AS currid, sltrans_date AS distdate,
-                               (vohead_amount / apopen_amount) AS percentpaid,
-                               sltrans_source AS source, sltrans_doctype AS doctype,
-                               tax_sales_accnt_id, tax_dist_accnt_id,
-                               taxhist_tax, taxhist_docdate
-                        FROM sltrans JOIN checkhead ON ((sltrans_source='A/P')
-                                                    AND (sltrans_doctype='CK')
-                                                    AND (sltrans_misc_id=checkhead_id))
-                                     JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                     JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                     JOIN vohead ON (vohead_number=apopen_docnumber)
-                                     JOIN vendinfo ON (vend_id=apopen_vend_id)
-                                     JOIN voheadtax ON (taxhist_parent_id=vohead_id)
-                                     JOIN tax ON (tax_id=taxhist_tax_id)
-                        WHERE (sltrans_id=_r.bankrecitem_source_id)
-                        UNION
-                        SELECT apopen_docnumber AS docnumber, vend_name AS vendname,
-                               apopen_curr_id AS currid, sltrans_date AS distdate,
-                               (vohead_amount / apopen_amount) AS percentpaid,
-                               sltrans_source AS source, sltrans_doctype AS doctype,
-                               tax_sales_accnt_id, tax_dist_accnt_id,
-                               taxhist_tax, taxhist_docdate
-                        FROM sltrans JOIN checkhead ON ((sltrans_source='A/P')
-                                                    AND (sltrans_doctype='CK')
-                                                    AND (sltrans_misc_id=checkhead_id))
-                                     JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                     JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                     JOIN vohead ON (vohead_number=apopen_docnumber)
-                                     JOIN vendinfo ON (vend_id=apopen_vend_id)
-                                     JOIN voitem ON (voitem_vohead_id=vohead_id)
-                                     JOIN voitemtax ON (taxhist_parent_id=voitem_id)
-                                     JOIN tax ON (tax_id=taxhist_tax_id)
-                        WHERE (sltrans_id=_r.bankrecitem_source_id)
-                       ) AS data
-                  GROUP BY docnumber, custname, currid, distdate, percentpaid,
-                           source, doctype,
-                           tax_sales_accnt_id, tax_dist_accnt_id, taxhist_docdate
-      LOOP
-        SELECT insertIntoGLSeries( _sequence, _tax.source, _tax.doctype, _tax.docnumber,
-                                   _tax.tax_dist_accnt_id, 
-                                   (_tax.taxbasevalue * -1.0),
-                                   COALESCE(_r.bankrecitem_effdate, _tax.distdate), _tax.custname ) INTO _result;
-        IF (_result < 0) THEN
-          RAISE EXCEPTION 'insertIntoGLSeries failed, result=%', _result;
-        END IF;
-        SELECT insertIntoGLSeries( _sequence, _tax.source, _tax.doctype, _tax.docnumber,
-                                   _tax.tax_sales_accnt_id, 
-                                   _tax.taxbasevalue,
-                                   COALESCE(_r.bankrecitem_effdate, _tax.distdate), _tax.custname ) INTO _result;
-        IF (_result < 0) THEN
-          RAISE EXCEPTION 'insertIntoGLSeries failed, result=%', _result;
-        END IF;
-      END LOOP;
-
-      -- second, create a taxpay row for each taxhist
-      FOR _tax IN SELECT taxhist_id, applyid, distdate,
-                         ROUND(taxhist_tax * percentpaid, 2) AS taxpaid
-                  FROM (
-                        -- Cash receipt, gltrans
-                        SELECT taxhist_id, aropen_id AS applyid, gltrans_date AS distdate, taxhist_tax,
-                               (cashrcptitem_amount / aropen_amount) AS percentpaid
-                          FROM gltrans JOIN cashrcpt  ON ((gltrans_source='A/R')
-                                                      AND (gltrans_doctype='CR')
-                                                      AND (gltrans_misc_id=cashrcpt_id))
-                                       JOIN cashrcptitem ON (cashrcptitem_cashrcpt_id=cashrcpt_id)
-                                       JOIN aropen ON (aropen_id=cashrcptitem_aropen_id)
-                                       JOIN cohist ON (cohist_invcnumber=aropen_docnumber AND cohist_doctype=aropen_doctype)
-                                       JOIN cohisttax ON (taxhist_parent_id=cohist_id)
-                          WHERE (gltrans_id=_r.bankrecitem_source_id)
-                        -- Cash receipt, sltrans
-                        UNION
-                        SELECT taxhist_id, aropen_id AS applyid, sltrans_date AS distdate, taxhist_tax,
-                               (cashrcptitem_amount / aropen_amount) AS percentpaid
-                          FROM sltrans JOIN cashrcpt  ON ((sltrans_source='A/R')
-                                                      AND (sltrans_doctype='CR')
-                                                      AND (sltrans_misc_id=cashrcpt_id))
-                                       JOIN cashrcptitem ON (cashrcptitem_cashrcpt_id=cashrcpt_id)
-                                       JOIN aropen ON (aropen_id=cashrcptitem_aropen_id)
-                                       JOIN cohist ON (cohist_invcnumber=aropen_docnumber AND cohist_doctype=aropen_doctype)
-                                       JOIN cohisttax ON (taxhist_parent_id=cohist_id)
-                          WHERE (sltrans_id=_r.bankrecitem_source_id)
-                        -- Cash payment, gltrans
-                        UNION
-                        SELECT taxhist_id, apopen_id AS applyid, gltrans_date AS distdate, taxhist_tax,
-                               (checkitem_amount / apopen_amount) AS percentpaid
-                          FROM gltrans JOIN checkhead  ON ((gltrans_source='A/P')
-                                                       AND (gltrans_doctype='CK')
-                                                       AND (gltrans_misc_id=checkhead_id))
-                                       JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                       JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                       JOIN vohead ON (vohead_number=apopen_docnumber)
-                                       JOIN voheadtax ON (taxhist_parent_id=vohead_id)
-                          WHERE (gltrans_id=_r.bankrecitem_source_id)
-                        UNION
-                        SELECT taxhist_id, apopen_id AS applyid, gltrans_date AS distdate, taxhist_tax,
-                               (checkitem_amount / apopen_amount) AS percentpaid
-                          FROM gltrans JOIN checkhead  ON ((gltrans_source='A/P')
-                                                       AND (gltrans_doctype='CK')
-                                                       AND (gltrans_misc_id=checkhead_id))
-                                       JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                       JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                       JOIN vohead ON (vohead_number=apopen_docnumber)
-                                       JOIN voitem ON (voitem_vohead_id=vohead_id)
-                                       JOIN voitemtax ON (taxhist_parent_id=voitem_id)
-                          WHERE (gltrans_id=_r.bankrecitem_source_id)
-                        -- Cash payment, sltrans
-                        UNION
-                        SELECT taxhist_id, apopen_id AS applyid, sltrans_date AS distdate, taxhist_tax,
-                               (checkitem_amount / apopen_amount) AS percentpaid
-                          FROM sltrans JOIN checkhead  ON ((sltrans_source='A/P')
-                                                       AND (sltrans_doctype='CK')
-                                                       AND (sltrans_misc_id=checkhead_id))
-                                       JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                       JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                       JOIN vohead ON (vohead_number=apopen_docnumber)
-                                       JOIN voheadtax ON (taxhist_parent_id=vohead_id)
-                          WHERE (sltrans_id=_r.bankrecitem_source_id)
-                        UNION
-                        SELECT taxhist_id, apopen_id AS applyid, sltrans_date AS distdate, taxhist_tax,
-                               (checkitem_amount / apopen_amount) AS percentpaid
-                          FROM sltrans JOIN checkhead  ON ((sltrans_source='A/P')
-                                                       AND (sltrans_doctype='CK')
-                                                       AND (sltrans_misc_id=checkhead_id))
-                                       JOIN checkitem ON (checkitem_checkhead_id=checkhead_id)
-                                       JOIN apopen ON (apopen_id=checkitem_apopen_id)
-                                       JOIN vohead ON (vohead_number=apopen_docnumber)
-                                       JOIN voitem ON (voitem_vohead_id=vohead_id)
-                                       JOIN voitemtax ON (taxhist_parent_id=voitem_id)
-                          WHERE (sltrans_id=_r.bankrecitem_source_id)
-                       ) AS data
-      LOOP
-        DELETE FROM taxpay
-        WHERE (taxpay_taxhist_id=_tax.taxhist_id)
-          AND (taxpay_apply_id=_tax.applyid)
-          AND (taxpay_distdate=COALESCE(_r.bankrecitem_effdate, _tax.distdate))
-          AND (taxpay_tax=_tax.taxpaid);
-      END LOOP;
-
-    END LOOP;
-
-    SELECT postGLSeries(_sequence, fetchJournalNumber('GL-MISC')) INTO _result;
-    IF (_result < 0) THEN
-      RAISE EXCEPTION 'postGLSeries failed, result=%', _result;
-    END IF;
-
-  END IF;
-
--- Mark all the gltrans items that have been cleared as unreconciled.
-  UPDATE gltrans
-     SET gltrans_rec = FALSE
-   WHERE ( (gltrans_id IN (SELECT bankrecitem_source_id
-                             FROM bankrecitem
-                            WHERE ((bankrecitem_source = 'GL')
-                              AND  (bankrecitem_cleared)
-                              AND  (bankrecitem_bankrec_id=pBankrecid) ) ) )
-     AND   (gltrans_accnt_id=_accntid) ) ;
-
--- Mark all the sltrans items that have been cleared as unreconciled.
-  UPDATE sltrans
-     SET sltrans_rec = FALSE
-   WHERE ( (sltrans_id IN (SELECT bankrecitem_source_id
-                             FROM bankrecitem
-                            WHERE ((bankrecitem_source = 'SL')
-                              AND  (bankrecitem_cleared)
-                              AND  (bankrecitem_bankrec_id=pBankrecid) ) ) )
-     AND   (sltrans_accnt_id=_accntid) ) ;
-
--- Mark the bankrec record as unposted
-  UPDATE bankrec SET 
-    bankrec_posted = FALSE,
-    bankrec_postdate = NULL
-   WHERE (bankrec_id=pBankrecid);
-
-  RETURN pBankrecid;
+  RETURN bankReconciliation(pBankrecid, 'reopen');
 END;
 $$ LANGUAGE 'plpgsql';
 
diff --git a/foundation-database/public/patches/fixflcol.sql b/foundation-database/public/patches/fixflcol.sql
new file mode 100644 (file)
index 0000000..aa526c6
--- /dev/null
@@ -0,0 +1,183 @@
+-- 4.4.1 and 4.5.0 fix - synchronize flcol_report_id
+do $$
+begin
+if fetchMetricText('ServerVersion') < '4.6.0' then
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReport'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id=285
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReportMonth'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id in (375, 335)
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReportMonthBudget'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id in (376, 336)
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReportMonthDbCr'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id=387
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReportMonthPriorMonth'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id in (377, 337)
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReportMonthPriorQuarter'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id in (386, 346)
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReportMonthPriorYear'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id in (378, 338)
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReportMonthQuarter'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id in (379, 339)
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReportMonthYear'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id in (374, 334)
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReportQuarter'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id in (380, 340)
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReportQuarterBudget'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id in (381, 341)
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReportQuarterPriorQuarter'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id in (382, 342)
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReportYear'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id in (383, 343)
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReportYearBudget'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id in (384, 344)
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialReportYearPriorYear'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id in (385, 345)
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+update flcol set flcol_report_id = (select report_id from report
+                                    where report_name = 'FinancialTrend'
+                                    order by report_grade desc
+                                    limit 1)
+where flcol_report_id=388
+and flcol_id in (
+  select flcol_id
+  from flcol left join report on flcol_report_id = report_id
+  where report_id is null)
+;
+
+end if;
+end$$;
\ No newline at end of file
diff --git a/foundation-database/public/tables/checkhead.sql b/foundation-database/public/tables/checkhead.sql
new file mode 100644 (file)
index 0000000..20793d4
--- /dev/null
@@ -0,0 +1 @@
+select xt.add_column('checkhead','checkhead_alt_curr_rate', 'NUMERIC', NULL, 'public');
\ No newline at end of file
index 377f256..0ed0c04 100644 (file)
@@ -22,11 +22,14 @@ SELECT ipsitem_id AS id, 1 AS altid,
                   WHEN ((ipsitem_type = 'M') AND fetchMetricBool('Long30Markups') AND fetchMetricBool('WholesalePriceCosting')) THEN
                    (item_listcost / (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount)
                   WHEN ((ipsitem_type = 'M') AND fetchMetricBool('Long30Markups')) THEN
-                   (itemCost(itemsite_id) / (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount)
+                   (itemCost(itemsite_id) / itemuomtouomratio(item_id, ipsitem_qty_uom_id, ipsitem_price_uom_id) /
+                   (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount)
                   WHEN (ipsitem_type = 'M' AND fetchMetricBool('WholesalePriceCosting')) THEN
                    (item_listcost + (item_listcost * ipsitem_discntprcnt) + ipsitem_fixedamtdiscount)
                   WHEN (ipsitem_type = 'M') THEN
-                   (itemCost(itemsite_id) + (itemCost(itemsite_id) * ipsitem_discntprcnt) + ipsitem_fixedamtdiscount)
+                   (itemCost(itemsite_id) / itemuomtouomratio(item_id, ipsitem_qty_uom_id, ipsitem_price_uom_id) +
+                    (itemCost(itemsite_id) / itemuomtouomratio(item_id, ipsitem_qty_uom_id, ipsitem_price_uom_id) * ipsitem_discntprcnt) +
+                    ipsitem_fixedamtdiscount)
                   ELSE 0.0
              END) AS netPrice,
        CASE WHEN (ipsitem_type='N') THEN <? value("nominal") ?>
index 4d4ce29..ed6d5f1 100644 (file)
@@ -130,7 +130,7 @@ SELECT prjtask_id AS id,
   LEFT OUTER JOIN addr ON (cntct_addr_id=addr_id)
 
  WHERE (prjtask_prj_id = <? value("prj_id") ?> )
- GROUP BY custinfo.cust_name, prjtask.prjtask_id, addr.addr_city, addr.addr_state, cntct_name
+ GROUP BY prjtask_prj_id, custinfo.cust_name, prjtask.prjtask_id, addr.addr_city, addr.addr_state, cntct_name, prjtask.prjtask_number, prjtask.prjtask_status, prjtask_username, prjtask.prjtask_owner_username, prjtask_name, prjtask_descrip, prjtask_due_date, prjtask_assigned_date, prjtask_start_date, prjtask_completed_date, prjtask_hours_budget, prjtask_hours_actual, prjtask_exp_budget, prjtask_exp_actual
 
 <? if exists("showIn") ?>
 UNION ALL
index 2859c48..b679d8e 100644 (file)
@@ -51,8 +51,12 @@ FROM (
                  (ipsitem_price * itemuomtouomratio(<? value('item_id') ?>, NULL, ipsitem_price_uom_id)) * iteminvpricerat(ipsitem_item_id)
                 WHEN (ipsitem_type = 'D') THEN
                  noNeg(<? value('item_listprice') ?> - (<? value('item_listprice') ?> * ipsitem_discntprcnt) - ipsitem_fixedamtdiscount)
+                WHEN ((ipsitem_type = 'M') AND fetchMetricBool('Long30Markups') AND fetchMetricBool('WholesalePriceCosting')) THEN
+                 (<? value('item_listcost') ?> / (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount)
                 WHEN ((ipsitem_type = 'M') AND fetchMetricBool('Long30Markups')) THEN
                  (<? value('item_unitcost') ?> / (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount)
+                WHEN (ipsitem_type = 'M' AND fetchMetricBool('WholesalePriceCosting')) THEN
+                 (<? value('item_listcost') ?> + (<? value('item_listcost') ?> * ipsitem_discntprcnt) + ipsitem_fixedamtdiscount)
                 WHEN (ipsitem_type = 'M') THEN
                  (<? value('item_unitcost') ?> + (<? value('item_unitcost') ?> * ipsitem_discntprcnt) + ipsitem_fixedamtdiscount)
                 ELSE 0.00
@@ -101,11 +105,16 @@ FROM (
                 ELSE ipsitem_qtybreak
            END AS invqty,
            ipsitem_qtybreak AS qtybreak,
-           CASE WHEN ipsitem_type = 'N' THEN (ipsitem_price * itemuomtouomratio(<? value('item_id') ?>, NULL, ipsitem_price_uom_id)) *
-                                              iteminvpricerat(ipsitem_item_id)
-                WHEN ipsitem_type = 'D' THEN noNeg(<? value('item_listprice') ?> - (<? value('item_listprice') ?> * ipsitem_discntprcnt) - ipsitem_fixedamtdiscount)
+           CASE WHEN ipsitem_type = 'N' THEN
+                 (ipsitem_price * itemuomtouomratio(<? value('item_id') ?>, NULL, ipsitem_price_uom_id)) * iteminvpricerat(ipsitem_item_id)
+                WHEN ipsitem_type = 'D' THEN
+                 noNeg(<? value('item_listprice') ?> - (<? value('item_listprice') ?> * ipsitem_discntprcnt) - ipsitem_fixedamtdiscount)
+                WHEN ((ipsitem_type = 'M') AND fetchMetricBool('Long30Markups') AND fetchMetricBool('WholesalePriceCosting')) THEN
+                 (<? value('item_listcost') ?> / (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount)
                 WHEN ((ipsitem_type = 'M') AND fetchMetricBool('Long30Markups')) THEN
                  (<? value('item_unitcost') ?> / (1.0 - ipsitem_discntprcnt) + ipsitem_fixedamtdiscount)
+                WHEN (ipsitem_type = 'M' AND fetchMetricBool('WholesalePriceCosting')) THEN
+                 (<? value('item_listcost') ?> + (<? value('item_listcost') ?> * ipsitem_discntprcnt) + ipsitem_fixedamtdiscount)
                 WHEN (ipsitem_type = 'M') THEN
                  (<? value('item_unitcost') ?> + (<? value('item_unitcost') ?> * ipsitem_discntprcnt) + ipsitem_fixedamtdiscount)
                 ELSE 0.00
index 3f06697..abd4a32 100644 (file)
@@ -101,8 +101,7 @@ SELECT coitem_id,
        END AS discountfromcust,
        coitem_unitcost,
        CASE WHEN (coitem_price = 0.0) THEN 0.0
-            ELSE ROUND((coitem_qtyord * coitem_qty_invuomratio) *
-                 ((coitem_price / coitem_price_invuomratio) - (coitem_unitcost / coitem_price_invuomratio)),2)
+            ELSE ROUND(coitem_qtyord * coitem_qty_invuomratio * (coitem_price - coitem_unitcost) / coitem_price_invuomratio,2)
        END AS margin,
        CASE WHEN (coitem_price = 0.0) THEN 0.0
             ELSE ((coitem_price - coitem_unitcost) / coitem_price)
index 20684d8..8d597fd 100644 (file)
@@ -1,6 +1 @@
--- Proof of concept
-insert into metric (metric_name, metric_value)
-select 'UnifiedBuild', 'true'
-where not exists (select c.metric_id from metric c where c.metric_name = 'UnifiedBuild');
-
-SELECT setMetric('ServerVersion', '4.5.2');
+SELECT setMetric('ServerVersion', '4.6.0-beta');
index e873cc8..3e0d663 100644 (file)
@@ -364,6 +364,37 @@ CREATE VIEW docinfo AS
         WHERE ((docass_source_type='S')
          AND (docass_source_id=cohead_id)
          AND (cust_id=cohead_cust_id))
+------------ INVOICE -----------
+ UNION ALL
+ SELECT docass_id AS id,
+        invchead_invcnumber AS target_number,
+        docass_target_type AS target_type,
+        docass_target_id AS target_id,
+        docass_source_type AS source_type,
+        docass_source_id AS source_id,
+        cust_name AS name, firstline(invchead_notes) AS description,
+        docass_purpose AS purpose
+        FROM docass, invchead, custinfo
+        WHERE ((docass_target_type='INV')
+         AND (docass_target_id=invchead_id)
+         AND (cust_id=invchead_cust_id))
+ UNION ALL
+ SELECT docass_id AS id,
+        invchead_invcnumber AS target_number,
+        docass_source_type AS target_type,
+        docass_source_id AS target_id,
+        docass_target_type AS source_type,
+        docass_target_id AS source_id,
+        cust_name AS name, firstline(invchead_notes) AS description,
+        CASE 
+          WHEN docass_purpose = 'A' THEN 'C'
+          WHEN docass_purpose = 'C' THEN 'A'
+          ELSE docass_purpose
+        END AS purpose
+        FROM docass, invchead, custinfo
+        WHERE ((docass_source_type='INV')
+         AND (docass_source_id=invchead_id)
+         AND (cust_id=invchead_cust_id))
  ------------ PURCHASE ORDER -----------
  UNION ALL
  SELECT docass_id AS id,
index b877b73..dd91679 100644 (file)
@@ -5,6 +5,8 @@
 (function () {
   'use strict';
 
+  Object.observe = undefined;
+
   /**
     Abstract check for attribute level privilege access.
 
index 30b4140..38a3766 100644 (file)
@@ -10,9 +10,6 @@
       font-style: italic;
       color: @blue-gray;
     }
-    &.hyperlink {
-      color: @blue;
-    }
     &.disabled {
       color: @dim-gray;
     }
@@ -68,4 +65,4 @@
   overflow: auto;
   color: @black;
   background-color: @white;
-}
\ No newline at end of file
+}
diff --git a/lib/enyo-x/source/less/characteristics.less b/lib/enyo-x/source/less/characteristics.less
deleted file mode 100644 (file)
index 38eef4d..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
-  Characteristics
-*/
-
-.xv-characteristic-picker {
-  border: none;
-  .onyx-picker-decorator .onyx-button {
-    width: 130px;
-  }
-}
-
-.xv-characteristic-item {
-  border-bottom: 1px solid @smoke;
-  .onyx-input-decorator > input {
-    width: 115px;
-    margin-top: 10px;
-    margin-bottom: 10px;
-  }
-  > .xv-input {
-    border: none;
-  }
-}
-
-.xv-characteristic-buttons {
-  margin: 8px;
-}
-
-.xv-characteristic-button {
-  margin-left: 4px;
-  color: @slate-blue;
-  font-size: 24px;
-}
index 4453ce6..7b5cccc 100644 (file)
@@ -1,6 +1,12 @@
 /*
   Styles relating to the grid box
 */
+@widgetFontSize: 13px;
+@gridIconWidth: 4px;
+@gridIconPadding: 2px;
+@gridFieldDecoratorWidth: 100%;
+@gridFieldWidth: @gridFieldDecoratorWidth - (2 * @decoratorPadding);
+@iconGridFieldWidth: @gridFieldWidth - @gridIconWidth - (2 * @gridIconPadding);
 
 
 /* Entire box including the grid and the summary panel */
   }
 
   .xv-grid-attr {
-    // This limits the text to three lines
-    overflow: hidden;
-    display: -webkit-box;
-    -webkit-line-clamp: 3;
-    -webkit-box-orient: vertical;
+    .xv-limit-description
 
     &.bold {
     font-weight: bold;
       border: 1px solid @bright-orange;
       .xv-grid-column {
         .onyx-input-decorator {
-          .tightened-input-decorator;
+          .tightened-input-decorator(100%);
         }
         .onyx-picker-decorator {
           padding-top: 6px;
             height: 26px;
             padding-top: 3px;
             width: 100%;
-            font-size: 13px;
+            font-size: @widgetFontSize;
           }
         }
         .xv-input {
+          padding-top: 6px;
+          width: @gridFieldDecoratorWidth;
           border: none;
+
         }
-        .xv-picker-label {
+        .xv-label, .xv-flexible-label, .xv-picker-label {
           display: none;
         }
-        .xv-datewidget {
-          margin-right: 10px;
-          padding-top: 0;
-          .onyx-input-decorator {
-            padding: 0;
-            width: 100%;
-            input {
-              width: 85%;
-              font-size: 13px;
-            }
+        .xv-relationwidget {
+          .xv-description {
+            margin: 0;
+            margin-top: 5px;
+          }
+          &.xv-private-item-site-widget {
+            border-bottom: 0;
           }
-        }
-        .xv-numberwidget {
-          padding-top: 6px;
           .onyx-input-decorator {
             .tightened-input-decorator;
           }
         }
-        .xv-combobox {
-          padding-left: 0;
-          input {
-            padding-top: 3px;
-            width: 80px;
-            font-size: 13px;
-          }
-        }
-        .xv-moneywidget {
-          padding-bottom: 0;
+        .xv-datewidget {
           .onyx-input-decorator {
             .tightened-input-decorator;
           }
         }
-        .xv-relationwidget {
+        .xv-combobox {
           .onyx-input-decorator {
-            width: 100%;
-            padding-top: 6px;
-          }
-          .onyx-input {
-            width: 85%;
-            padding-right: 4px;
+            .tightened-input-decorator;
           }
         }
         .xv-useraccount-widget {
-          // Hack: we shouldn't have to force this.
-          .xv-subinput {
+          .xv-input {
             width: 80px;
             height: 16px;
           }
         }
-        .xv-subinput {
-          width: 100%;
-        }
-        .xv-input {
-          padding-top: 6px;
-          width: 100%;
-          border: none;
-        }
-        .xv-label, .xv-flexible-label, .xv-relationwidget-secondarydescription {
-          display: none;
-        }
-        .xv-relationwidget-description {
-          margin: 0;
-          margin-top: 5px;
-        }
-        .xv-private-item-site-widget {
-          border-bottom: 0;
-        }
       }
     }
   }
   }
 }
 
-.tightened-input-decorator (@width: 100%) {
+.tightened-input-decorator (@inputWidth: @iconGridFieldWidth) {
   padding: 0;
-  width: @width;
+  width: @gridFieldDecoratorWidth;
   margin: 0;
   input {
-    width: (@width);
-    font-size: 13px;
+    width: @inputWidth;
+    font-size: @widgetFontSize;
   }
 }
index e5bb712..7c103ee 100644 (file)
     color: @dim-gray;
   }
   &.hyperlink {
-    color: blue;
+    color: @slate-blue;
+    cursor: pointer;
   }
   &.disabled {
     color: @dim-gray;
index 044c9ac..7e92c6b 100644 (file)
   }
 }
 
-.xv-combobox {
-  .onyx-input-decorator {
-    input {
-      width: 145px;
-    }
-  }
-  .icon-sort {
-    color: @slate-blue;
-    vertical-align: middle;
-  }
-}
 .xv-combobox-note {
   padding: 14px 3px 8px 3px;
   text-align: left;
index 5d53c54..6b1b0af 100644 (file)
     width: 100%;
     margin: 0 4px 6px 0;
 
+    .xv-input, .xv-pickerwidget {
+      .onyx-input-decorator {
+        border: none;
+      }
+    }
+
     .xv-buttons {
       text-align: center;
     }
index d6a480f..1810893 100644 (file)
@@ -2,62 +2,30 @@
   Styles relating to RelationWidgets
 */
 
- .xv-relationwidget-completer {
 .xv-relationwidget-completer {
    left: -200px;
    top: 15px;
- }
 }
 
- .xv-completer-sidecar {
 .xv-completer-sidecar {
    color: @blue-gray;
- }
-
- .xv-relationwidget-column {
-   &.left {
-     padding-right: 18px;
-     color: @black;
-     width: 140px;
-   }
- }
+  }
 
- .xv-relationwidget-icon {
 .xv-relationwidget-icon {
    top: 1px;
    left: 8px;
    height: 30px;
    position: relative;
- }
-
- .xv-relationwidget-description {
-   overflow: hidden;
-   // This gives them 3 lines of description
-   display: -webkit-box;
-   -webkit-line-clamp: 3;
-   -webkit-box-orient: vertical;
-   max-width: 250px;
-   margin: 5px 5px 5px 80px;
-   &.disabled {
-     color: @dim-gray;
-   }
-   &.label {
-     text-indent: 0;
-     text-align: right;
-
-   }
-   &.hasLabel {
-     text-indent: 0;
-   }
-   &.hyperlink {
-     color: blue;
   }
- }
 
- .xv-private-item-site-widget {
 .xv-private-item-site-widget {
    border-bottom-color: rgb(170, 170, 170);
    border-bottom-width: 1px;
    border-bottom-style: solid;
- }
 }
 
- /* RelationsEditorBox */
- .xv-relations-editor-box {
 /* RelationsEditorBox */
 .xv-relations-editor-box {
    .xv-groupbox {
      margin-right: 0;
      padding: 0;
    &.panel {
      width: 350px;
    }
- }
 }
 
- /* ListRelationsBox */
- .xv-list-relations-box {
 /* ListRelationsBox */
 .xv-list-relations-box {
    &.panel {
      width: 625px;
    }
- }
 }
 
- .xv-short-relations-box {
 .xv-short-relations-box {
    &.panel {
      width: 500px;
    }
- }
 }
index 46a974f..ffbd2a8 100644 (file)
@@ -76,6 +76,24 @@ body {
   cursor: default;
 }
 
+a, .hyperlink {
+  color: @slate-blue;
+  cursor: pointer;
+}
+
+.xv-invisible-wrapper {
+  padding: 0;
+  border: none;
+}
+
+  // This gives them 4 lines of description
+.xv-limit-description {
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-line-clamp: 4;
+  -webkit-box-orient: vertical;
+}
+
 .xv-app-panel {
   /* Setting this as min-width overrides enyo-narrow 100% */
   width: @defaultPanelWidth;
@@ -250,9 +268,10 @@ body {
   }
 }
 
-.icon-folder-open-alt, .icon-calendar, .icon-sort {
+.icon-folder-open-alt, .icon-calendar, .icon-sort, .icon-angle-up, .icon-angle-down {
   color: @slate-blue;
   vertical-align: middle;
+  text-align: center;
 }
 
 .xv-short-textarea {
@@ -343,11 +362,13 @@ body {
   }
 
   .xv-toolbar-label, .xv-search {
-    color: @white;
     width: @searchLength;
     margin: 0;
     margin-left: 6px;
   }
+  .xv-search {
+    color: @med-gray;
+  }
 }
 
 // local imports
@@ -362,8 +383,6 @@ body {
 @import "dashboard.less";
 @import "list.less";
 @import "pullout.less";
-@import "widgets.less";
-@import "characteristics.less";
 @import "relations.less";
 @import "address.less";
 @import "search.less";
diff --git a/lib/enyo-x/source/less/widgets.less b/lib/enyo-x/source/less/widgets.less
deleted file mode 100644 (file)
index 025e556..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
-  Styles relating to misc widgets
-*/
-
-.onyx-input-decorator {
-  border: 0;
-  input {
-    width: 150px;
-  }
-}
-
-.xv-relationwidget, .xv-datewidget {
-  input {
-    width: 135px;
-  }
-}
-
-.xv-numberwidget {
-  input {
-    text-align: right;
-    width: 150px;
-  }
-}
-
-.xv-moneywidget {
-  padding-top: 6px;
-  padding-bottom: 2px;
-
-  .onyx-input-decorator {
-    font-family: @groupboxFont;
-    margin-right: 5px;
-
-    input, .xv-money-label {
-      width: 85px;
-      text-align: right;
-    }
-    &.xv-currency-label {
-      width: 65px;
-    }
-  }
-  .xv-picker-button {
-    width: 65px;
-    padding: 10px 4px;
-  }
-}
-
-.spinner {
-  // this is a label fix since the spinner is so tall
-  .enyo-fittable-columns-layout > * {
-    vertical-align: middle;
-  }
-  .buttons {
-    display: block;
-    padding: 0;
-    margin: 1px 0 1px 10px;
-    width: 20px;
-    height: 20px;
-    background: transparent;
-    color: @slate-blue;
-    font-size: 20px;
-    border: none;
-  }
-  .slider {
-    margin: 15px 10px;
-  }
-  &.xv-numberwidget {
-    input {
-      width: 120px;
-    }
-  }
-}
-
-.xv-dependency-picker {
-  .onyx-picker-decorator {
-    .onyx-button {
-      width: 280px;
-      margin-left: 6px;
-    }
-  }
-}
-
-.xv-dependency-button {
-  color: @slate-blue;
-  font-size: 24px;
-  border: none;
-  background: transparent;
-}
index 90a3739..c00b2ff 100644 (file)
 
       // fix labels with widget refactor
       .xv-label, .xv-decorated-label, .xv-flexible-label {
-        width: @labelWidth !important;
+        width: @labelWidth;
         max-width: 100%;
-        padding: 0 8px 0 6px;
         text-align: right;
+        padding: 0 8px 0 6px;
         vertical-align: middle;
+        &.disabled {
+          color: @dim-gray;
+        }
       }
+
       &.xv-assignment-box {
         .xv-flexible-label, .xv-label {
           width: 200px !important;
       }
 
       .xv-input {
-        border-bottom: 1px solid @smoke;
-        margin: 0px;
+        padding: 4px 0;
+        border-bottom: 1px solid fade(@smoke, 50%);
+
+        .enyo-tool-decorator {
+          max-width: @fieldDecoratorWidth;
+          text-align: left;
+
+          .onyx-icon {
+            display: inline-block;
+            width: @iconWidth;
+            cursor: pointer;
+          }
+        }
+
+        &.xv-textarea {
+          padding: 0;
+          margin: @decoratorPadding;
+          .enyo-tool-decorator {
+            // reset max-width on text area
+            max-width: @defaultPanelWidth;
+          }
+        }
+
+        input {
+          width: @fieldWidth;
+        }
+
+        .xv-icon-decorator {
+          input {
+            width: @iconFieldWidth;
+          }
+          .onyx-icon {
+            padding: @iconPadding;
+          }
+        }
+
+        .onyx-picker-decorator {
+          padding: 0;  // @overrides the input-decorator padding above ^^
+        }
+      }
+
+      /**
+        Styles relating to workspace widgets
+      */
+      .xv-relationwidget {
+        .xv-description {
+          .xv-limit-description;
+
+          max-width: 250px;
+          margin: 5px 5px 5px 80px;
+          font-size: 0.9em;
+
+          &.disabled {
+            color: @dim-gray;
+          }
+        }
+      }
+
+      .xv-spinnerwidget {
+        .xv-icon-decorator {
+          .onyx-icon {
+            display: block;
+            border: none;
+            font-size: 20px;
+            width: 20px;
+            padding: 0 4px 0 4px;
+          }
+          input {
+            width: @iconFieldWidth - @iconPadding;
+          }
+        }
+        .slider {
+          margin: 15px 10px;
+        }
+      }
+
+      .xv-numberwidget {
+        input {
+          text-align: right;
+        }
+      }
+
+      .xv-moneywidget {
+        .enyo-tool-decorator {
+          // reset min/max-width
+          min-width: 90px;
+          max-width: 90px;
+          input {
+            width: 90px;
+          }
+        }
+      }
+
+      .xv-currency-picker {
+        display: inline-block;
+        .enyo-tool-decorator {
+          // reset min/max-width
+          min-width: 65px;
+          max-width: 65px;
+          padding: 10px 4px;
+          .xv-button-text {
+            width: 45px;
+          }
+        }
+      }
+
+      .xv-characteristics-widget {
+        .xv-characteristic-item {
+          border-bottom: 1px solid @smoke;
+          .xv-input {
+            border: none;
+            input {
+              width: 135px;
+            }
+          }
+        }
+        .xv-characteristic-button {
+          text-align: center;
+        }
       }
 
       .enyo-fittable-columns-layout > * {
     }
 
     .onyx-input-decorator {
+      border: 0;
+      input {
+        width: 150px;
+      }
       &.onyx-disabled {
         .disabled;
       }
     }
 
+    // Dependency picker in workflow
+    .xv-dependency-picker {
+      .onyx-picker-decorator {
+        .onyx-button {
+          width: 280px;
+          margin-left: 6px;
+        }
+      }
+    }
+
+    .xv-dependency-button {
+      color: @slate-blue;
+      font-size: 24px;
+      border: none;
+      background: transparent;
+    }
+
     .xv-totals-panel {
       .onyx-input-decorator > input, * {
         font-size: @totalsFontSize;
index 8f3a404..0edfcd6 100755 (executable)
@@ -1523,6 +1523,21 @@ body {
   opacity: 0.8;
   cursor: default;
 }
+a,
+.hyperlink {
+  color: #357ec7;
+  cursor: pointer;
+}
+.xv-invisible-wrapper {
+  padding: 0;
+  border: none;
+}
+.xv-limit-description {
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-line-clamp: 4;
+  -webkit-box-orient: vertical;
+}
 .xv-app-panel {
   /* Setting this as min-width overrides enyo-narrow 100% */
   width: 320px;
@@ -1627,6 +1642,9 @@ body {
   background: #f8f8f8;
   text-overflow: ellipsis;
   border: 1px solid #d7d7d7;
+  /**
+        Styles relating to workspace widgets
+      */
   color: #070707;
   padding: 7px;
 }
@@ -1651,19 +1669,110 @@ body {
 .xv-popup.xv-groupbox-popup .xv-label,
 .xv-popup.xv-groupbox-popup .xv-decorated-label,
 .xv-popup.xv-groupbox-popup .xv-flexible-label {
-  width: 120px !important;
+  width: 120px;
   max-width: 100%;
-  padding: 0 8px 0 6px;
   text-align: right;
+  padding: 0 8px 0 6px;
   vertical-align: middle;
 }
+.xv-popup.xv-groupbox-popup .xv-label.disabled,
+.xv-popup.xv-groupbox-popup .xv-decorated-label.disabled,
+.xv-popup.xv-groupbox-popup .xv-flexible-label.disabled {
+  color: #777777;
+}
 .xv-popup.xv-groupbox-popup.xv-assignment-box .xv-flexible-label,
 .xv-popup.xv-groupbox-popup.xv-assignment-box .xv-label {
   width: 200px !important;
 }
 .xv-popup.xv-groupbox-popup .xv-input {
+  padding: 4px 0;
+  border-bottom: 1px solid rgba(215, 215, 215, 0.5);
+}
+.xv-popup.xv-groupbox-popup .xv-input .enyo-tool-decorator {
+  max-width: 180px;
+  text-align: left;
+}
+.xv-popup.xv-groupbox-popup .xv-input .enyo-tool-decorator .onyx-icon {
+  display: inline-block;
+  width: 16px;
+  cursor: pointer;
+}
+.xv-popup.xv-groupbox-popup .xv-input.xv-textarea {
+  padding: 0;
+  margin: 8px;
+}
+.xv-popup.xv-groupbox-popup .xv-input.xv-textarea .enyo-tool-decorator {
+  max-width: 320px;
+}
+.xv-popup.xv-groupbox-popup .xv-input input {
+  width: 164px;
+}
+.xv-popup.xv-groupbox-popup .xv-input .xv-icon-decorator input {
+  width: 140px;
+}
+.xv-popup.xv-groupbox-popup .xv-input .xv-icon-decorator .onyx-icon {
+  padding: 4px;
+}
+.xv-popup.xv-groupbox-popup .xv-input .onyx-picker-decorator {
+  padding: 0;
+}
+.xv-popup.xv-groupbox-popup .xv-relationwidget .xv-description {
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-line-clamp: 4;
+  -webkit-box-orient: vertical;
+  max-width: 250px;
+  margin: 5px 5px 5px 80px;
+  font-size: 0.9em;
+}
+.xv-popup.xv-groupbox-popup .xv-relationwidget .xv-description.disabled {
+  color: #777777;
+}
+.xv-popup.xv-groupbox-popup .xv-spinnerwidget .xv-icon-decorator .onyx-icon {
+  display: block;
+  border: none;
+  font-size: 20px;
+  width: 20px;
+  padding: 0 4px 0 4px;
+}
+.xv-popup.xv-groupbox-popup .xv-spinnerwidget .xv-icon-decorator input {
+  width: 136px;
+}
+.xv-popup.xv-groupbox-popup .xv-spinnerwidget .slider {
+  margin: 15px 10px;
+}
+.xv-popup.xv-groupbox-popup .xv-numberwidget input {
+  text-align: right;
+}
+.xv-popup.xv-groupbox-popup .xv-moneywidget .enyo-tool-decorator {
+  min-width: 90px;
+  max-width: 90px;
+}
+.xv-popup.xv-groupbox-popup .xv-moneywidget .enyo-tool-decorator input {
+  width: 90px;
+}
+.xv-popup.xv-groupbox-popup .xv-currency-picker {
+  display: inline-block;
+}
+.xv-popup.xv-groupbox-popup .xv-currency-picker .enyo-tool-decorator {
+  min-width: 65px;
+  max-width: 65px;
+  padding: 10px 4px;
+}
+.xv-popup.xv-groupbox-popup .xv-currency-picker .enyo-tool-decorator .xv-button-text {
+  width: 45px;
+}
+.xv-popup.xv-groupbox-popup .xv-characteristics-widget .xv-characteristic-item {
   border-bottom: 1px solid #d7d7d7;
-  margin: 0px;
+}
+.xv-popup.xv-groupbox-popup .xv-characteristics-widget .xv-characteristic-item .xv-input {
+  border: none;
+}
+.xv-popup.xv-groupbox-popup .xv-characteristics-widget .xv-characteristic-item .xv-input input {
+  width: 135px;
+}
+.xv-popup.xv-groupbox-popup .xv-characteristics-widget .xv-characteristic-button {
+  text-align: center;
 }
 .xv-popup.xv-groupbox-popup .enyo-fittable-columns-layout > * {
   vertical-align: middle;
@@ -1724,9 +1833,12 @@ body {
 }
 .icon-folder-open-alt,
 .icon-calendar,
-.icon-sort {
+.icon-sort,
+.icon-angle-up,
+.icon-angle-down {
   color: #357ec7;
   vertical-align: middle;
+  text-align: center;
 }
 .xv-short-textarea .xv-textarea-input {
   min-height: 0;
@@ -1800,11 +1912,13 @@ body {
 }
 .onyx-toolbar .xv-toolbar-label,
 .onyx-toolbar .xv-search {
-  color: #fdfdfd;
   width: 185px;
   margin: 0;
   margin-left: 6px;
 }
+.onyx-toolbar .xv-search {
+  color: #939393;
+}
 .xv-navigator .xv-header {
   background-color: #fdfdfd;
   padding: 6px 12px;
@@ -1947,6 +2061,9 @@ body {
   background: #f8f8f8;
   text-overflow: ellipsis;
   border: 1px solid #d7d7d7;
+  /**
+        Styles relating to workspace widgets
+      */
 }
 .xv-workspace-container .xv-workspace .xv-workspace-panel .onyx-groupbox-header {
   padding: 6px 10px;
@@ -1969,27 +2086,134 @@ body {
 .xv-workspace-container .xv-workspace .xv-workspace-panel .xv-label,
 .xv-workspace-container .xv-workspace .xv-workspace-panel .xv-decorated-label,
 .xv-workspace-container .xv-workspace .xv-workspace-panel .xv-flexible-label {
-  width: 120px !important;
+  width: 120px;
   max-width: 100%;
-  padding: 0 8px 0 6px;
   text-align: right;
+  padding: 0 8px 0 6px;
   vertical-align: middle;
 }
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-label.disabled,
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-decorated-label.disabled,
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-flexible-label.disabled {
+  color: #777777;
+}
 .xv-workspace-container .xv-workspace .xv-workspace-panel.xv-assignment-box .xv-flexible-label,
 .xv-workspace-container .xv-workspace .xv-workspace-panel.xv-assignment-box .xv-label {
   width: 200px !important;
 }
 .xv-workspace-container .xv-workspace .xv-workspace-panel .xv-input {
+  padding: 4px 0;
+  border-bottom: 1px solid rgba(215, 215, 215, 0.5);
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-input .enyo-tool-decorator {
+  max-width: 180px;
+  text-align: left;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-input .enyo-tool-decorator .onyx-icon {
+  display: inline-block;
+  width: 16px;
+  cursor: pointer;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-input.xv-textarea {
+  padding: 0;
+  margin: 8px;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-input.xv-textarea .enyo-tool-decorator {
+  max-width: 320px;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-input input {
+  width: 164px;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-input .xv-icon-decorator input {
+  width: 140px;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-input .xv-icon-decorator .onyx-icon {
+  padding: 4px;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-input .onyx-picker-decorator {
+  padding: 0;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-relationwidget .xv-description {
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-line-clamp: 4;
+  -webkit-box-orient: vertical;
+  max-width: 250px;
+  margin: 5px 5px 5px 80px;
+  font-size: 0.9em;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-relationwidget .xv-description.disabled {
+  color: #777777;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-spinnerwidget .xv-icon-decorator .onyx-icon {
+  display: block;
+  border: none;
+  font-size: 20px;
+  width: 20px;
+  padding: 0 4px 0 4px;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-spinnerwidget .xv-icon-decorator input {
+  width: 136px;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-spinnerwidget .slider {
+  margin: 15px 10px;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-numberwidget input {
+  text-align: right;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-moneywidget .enyo-tool-decorator {
+  min-width: 90px;
+  max-width: 90px;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-moneywidget .enyo-tool-decorator input {
+  width: 90px;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-currency-picker {
+  display: inline-block;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-currency-picker .enyo-tool-decorator {
+  min-width: 65px;
+  max-width: 65px;
+  padding: 10px 4px;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-currency-picker .enyo-tool-decorator .xv-button-text {
+  width: 45px;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-characteristics-widget .xv-characteristic-item {
   border-bottom: 1px solid #d7d7d7;
-  margin: 0px;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-characteristics-widget .xv-characteristic-item .xv-input {
+  border: none;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-characteristics-widget .xv-characteristic-item .xv-input input {
+  width: 135px;
+}
+.xv-workspace-container .xv-workspace .xv-workspace-panel .xv-characteristics-widget .xv-characteristic-button {
+  text-align: center;
 }
 .xv-workspace-container .xv-workspace .xv-workspace-panel .enyo-fittable-columns-layout > * {
   vertical-align: middle;
 }
+.xv-workspace-container .xv-workspace .onyx-input-decorator {
+  border: 0;
+}
+.xv-workspace-container .xv-workspace .onyx-input-decorator input {
+  width: 150px;
+}
 .xv-workspace-container .xv-workspace .onyx-input-decorator.onyx-disabled {
   opacity: 0.8;
   cursor: default;
 }
+.xv-workspace-container .xv-workspace .xv-dependency-picker .onyx-picker-decorator .onyx-button {
+  width: 280px;
+  margin-left: 6px;
+}
+.xv-workspace-container .xv-workspace .xv-dependency-button {
+  color: #357ec7;
+  font-size: 24px;
+  border: none;
+  background: transparent;
+}
 .xv-workspace-container .xv-workspace .xv-totals-panel .onyx-input-decorator > input,
 .xv-workspace-container .xv-workspace .xv-totals-panel * {
   font-size: 14px;
@@ -2014,7 +2238,7 @@ body {
         */
 }
 .xv-list .xv-model-decorator > .xv-list-item .xv-list-column.xv-list-attr.xm-attribute-id {
-  color: blue;
+  color: #357ec7;
   font-weight: bold;
   cursor: pointer;
 }
@@ -2072,13 +2296,6 @@ body {
 .xv-picker-label.disabled {
   color: #777777;
 }
-.xv-combobox .onyx-input-decorator input {
-  width: 145px;
-}
-.xv-combobox .icon-sort {
-  color: #357ec7;
-  vertical-align: middle;
-}
 .xv-combobox-note {
   padding: 14px 3px 8px 3px;
   text-align: left;
@@ -2111,13 +2328,7 @@ body {
 .xv-grid-box .xv-scroller {
   background: #f8f8f8;
 }
-.xv-grid-box .xv-grid-attr {
-  overflow: hidden;
-  display: -webkit-box;
-  -webkit-line-clamp: 3;
-  -webkit-box-orient: vertical;
-}
-.xv-grid-box .xv-grid-attr.bold {
+.xv-limit-description .xv-grid-box .xv-grid-attr.bold {
   font-weight: bold;
 }
 .xv-grid-box .xv-grid-attr.error {
@@ -2244,87 +2455,53 @@ body {
   font-size: 13px;
 }
 .xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-input {
+  padding-top: 6px;
+  width: 100%;
   border: none;
 }
+.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-label,
+.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-flexible-label,
 .xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-picker-label {
   display: none;
 }
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-datewidget {
-  margin-right: 10px;
-  padding-top: 0;
+.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-relationwidget .xv-description {
+  margin: 0;
+  margin-top: 5px;
 }
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-datewidget .onyx-input-decorator {
+.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-relationwidget.xv-private-item-site-widget {
+  border-bottom: 0;
+}
+.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-relationwidget .onyx-input-decorator {
   padding: 0;
   width: 100%;
+  margin: 0;
 }
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-datewidget .onyx-input-decorator input {
-  width: 85%;
+.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-relationwidget .onyx-input-decorator input {
+  width: 76%;
   font-size: 13px;
 }
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-numberwidget {
-  padding-top: 6px;
-}
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-numberwidget .onyx-input-decorator {
+.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-datewidget .onyx-input-decorator {
   padding: 0;
   width: 100%;
   margin: 0;
 }
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-numberwidget .onyx-input-decorator input {
-  width: 100%;
-  font-size: 13px;
-}
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-combobox {
-  padding-left: 0;
-}
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-combobox input {
-  padding-top: 3px;
-  width: 80px;
+.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-datewidget .onyx-input-decorator input {
+  width: 76%;
   font-size: 13px;
 }
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-moneywidget {
-  padding-bottom: 0;
-}
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-moneywidget .onyx-input-decorator {
+.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-combobox .onyx-input-decorator {
   padding: 0;
   width: 100%;
   margin: 0;
 }
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-moneywidget .onyx-input-decorator input {
-  width: 100%;
+.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-combobox .onyx-input-decorator input {
+  width: 76%;
   font-size: 13px;
 }
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-relationwidget .onyx-input-decorator {
-  width: 100%;
-  padding-top: 6px;
-}
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-relationwidget .onyx-input {
-  width: 85%;
-  padding-right: 4px;
-}
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-useraccount-widget .xv-subinput {
+.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-useraccount-widget .xv-input {
   width: 80px;
   height: 16px;
 }
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-subinput {
-  width: 100%;
-}
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-input {
-  padding-top: 6px;
-  width: 100%;
-  border: none;
-}
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-label,
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-flexible-label,
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-relationwidget-secondarydescription {
-  display: none;
-}
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-relationwidget-description {
-  margin: 0;
-  margin-top: 5px;
-}
-.xv-grid-box .xv-grid-row.selected .xv-grid-column .xv-private-item-site-widget {
-  border-bottom: 0;
-}
 .xv-grid-box.xv-groupbox .xv-sales-summary-panel {
   border: none;
   margin-top: 0;
@@ -2753,7 +2930,8 @@ body {
   color: #777777;
 }
 .xv-list-attr.hyperlink {
-  color: blue;
+  color: #357ec7;
+  cursor: pointer;
 }
 .xv-list-attr.disabled {
   color: #777777;
@@ -2830,6 +3008,9 @@ body {
   background: #f8f8f8;
   text-overflow: ellipsis;
   border: 1px solid #d7d7d7;
+  /**
+        Styles relating to workspace widgets
+      */
   width: 100%;
   margin: 0 4px 6px 0;
 }
@@ -2854,139 +3035,139 @@ body {
 .xv-pullout .xv-parameter-panel .xv-label,
 .xv-pullout .xv-parameter-panel .xv-decorated-label,
 .xv-pullout .xv-parameter-panel .xv-flexible-label {
-  width: 120px !important;
+  width: 120px;
   max-width: 100%;
-  padding: 0 8px 0 6px;
   text-align: right;
+  padding: 0 8px 0 6px;
   vertical-align: middle;
 }
+.xv-pullout .xv-parameter-panel .xv-label.disabled,
+.xv-pullout .xv-parameter-panel .xv-decorated-label.disabled,
+.xv-pullout .xv-parameter-panel .xv-flexible-label.disabled {
+  color: #777777;
+}
 .xv-pullout .xv-parameter-panel.xv-assignment-box .xv-flexible-label,
 .xv-pullout .xv-parameter-panel.xv-assignment-box .xv-label {
   width: 200px !important;
 }
 .xv-pullout .xv-parameter-panel .xv-input {
-  border-bottom: 1px solid #d7d7d7;
-  margin: 0px;
+  padding: 4px 0;
+  border-bottom: 1px solid rgba(215, 215, 215, 0.5);
 }
-.xv-pullout .xv-parameter-panel .enyo-fittable-columns-layout > * {
-  vertical-align: middle;
+.xv-pullout .xv-parameter-panel .xv-input .enyo-tool-decorator {
+  max-width: 180px;
+  text-align: left;
 }
-.xv-pullout .xv-parameter-panel .xv-buttons {
-  text-align: center;
+.xv-pullout .xv-parameter-panel .xv-input .enyo-tool-decorator .onyx-icon {
+  display: inline-block;
+  width: 16px;
+  cursor: pointer;
 }
-.xv-pullout .xv-parameter-panel .xv-filter-form .xv-list {
-  height: 150px;
+.xv-pullout .xv-parameter-panel .xv-input.xv-textarea {
+  padding: 0;
+  margin: 8px;
 }
-.xv-pullout .help-panel {
-  border: none;
-  height: 100%;
-  width: 100%;
+.xv-pullout .xv-parameter-panel .xv-input.xv-textarea .enyo-tool-decorator {
+  max-width: 320px;
 }
-.xv-pullout .history-panel .xv-list-item {
-  padding: 12px 10px;
-  color: #0e0e0e;
-  background-color: #fdfdfd;
-  border-bottom: 1px solid #d7d7d7;
+.xv-pullout .xv-parameter-panel .xv-input input {
+  width: 164px;
 }
-.xv-pullout .history-panel .xv-list-item.onyx-selected {
-  background-color: #357ec7;
-  color: #fdfdfd;
-  border-bottom: 2px solid #d8d8d8;
+.xv-pullout .xv-parameter-panel .xv-input .xv-icon-decorator input {
+  width: 140px;
 }
-/**
-  Styles relating to misc widgets
-*/
-.onyx-input-decorator {
-  border: 0;
+.xv-pullout .xv-parameter-panel .xv-input .xv-icon-decorator .onyx-icon {
+  padding: 4px;
 }
-.onyx-input-decorator input {
-  width: 150px;
+.xv-pullout .xv-parameter-panel .xv-input .onyx-picker-decorator {
+  padding: 0;
 }
-.xv-relationwidget input,
-.xv-datewidget input {
-  width: 135px;
+.xv-pullout .xv-parameter-panel .xv-relationwidget .xv-description {
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-line-clamp: 4;
+  -webkit-box-orient: vertical;
+  max-width: 250px;
+  margin: 5px 5px 5px 80px;
+  font-size: 0.9em;
 }
-.xv-numberwidget input {
-  text-align: right;
-  width: 150px;
+.xv-pullout .xv-parameter-panel .xv-relationwidget .xv-description.disabled {
+  color: #777777;
 }
-.xv-moneywidget {
-  padding-top: 6px;
-  padding-bottom: 2px;
+.xv-pullout .xv-parameter-panel .xv-spinnerwidget .xv-icon-decorator .onyx-icon {
+  display: block;
+  border: none;
+  font-size: 20px;
+  width: 20px;
+  padding: 0 4px 0 4px;
 }
-.xv-moneywidget .onyx-input-decorator {
-  font-family: "Lucida Sans Unicode", "Lucida Grande", arial, sans-serif;
-  margin-right: 5px;
+.xv-pullout .xv-parameter-panel .xv-spinnerwidget .xv-icon-decorator input {
+  width: 136px;
 }
-.xv-moneywidget .onyx-input-decorator input,
-.xv-moneywidget .onyx-input-decorator .xv-money-label {
-  width: 85px;
+.xv-pullout .xv-parameter-panel .xv-spinnerwidget .slider {
+  margin: 15px 10px;
+}
+.xv-pullout .xv-parameter-panel .xv-numberwidget input {
   text-align: right;
 }
-.xv-moneywidget .onyx-input-decorator.xv-currency-label {
-  width: 65px;
+.xv-pullout .xv-parameter-panel .xv-moneywidget .enyo-tool-decorator {
+  min-width: 90px;
+  max-width: 90px;
 }
-.xv-moneywidget .xv-picker-button {
-  width: 65px;
+.xv-pullout .xv-parameter-panel .xv-moneywidget .enyo-tool-decorator input {
+  width: 90px;
+}
+.xv-pullout .xv-parameter-panel .xv-currency-picker {
+  display: inline-block;
+}
+.xv-pullout .xv-parameter-panel .xv-currency-picker .enyo-tool-decorator {
+  min-width: 65px;
+  max-width: 65px;
   padding: 10px 4px;
 }
-.spinner .enyo-fittable-columns-layout > * {
-  vertical-align: middle;
+.xv-pullout .xv-parameter-panel .xv-currency-picker .enyo-tool-decorator .xv-button-text {
+  width: 45px;
 }
-.spinner .buttons {
-  display: block;
-  padding: 0;
-  margin: 1px 0 1px 10px;
-  width: 20px;
-  height: 20px;
-  background: transparent;
-  color: #357ec7;
-  font-size: 20px;
-  border: none;
+.xv-pullout .xv-parameter-panel .xv-characteristics-widget .xv-characteristic-item {
+  border-bottom: 1px solid #d7d7d7;
 }
-.spinner .slider {
-  margin: 15px 10px;
+.xv-pullout .xv-parameter-panel .xv-characteristics-widget .xv-characteristic-item .xv-input {
+  border: none;
 }
-.spinner.xv-numberwidget input {
-  width: 120px;
+.xv-pullout .xv-parameter-panel .xv-characteristics-widget .xv-characteristic-item .xv-input input {
+  width: 135px;
 }
-.xv-dependency-picker .onyx-picker-decorator .onyx-button {
-  width: 280px;
-  margin-left: 6px;
+.xv-pullout .xv-parameter-panel .xv-characteristics-widget .xv-characteristic-button {
+  text-align: center;
 }
-.xv-dependency-button {
-  color: #357ec7;
-  font-size: 24px;
-  border: none;
-  background: transparent;
+.xv-pullout .xv-parameter-panel .enyo-fittable-columns-layout > * {
+  vertical-align: middle;
 }
-/**
-  Characteristics
-*/
-.xv-characteristic-picker {
+.xv-pullout .xv-parameter-panel .xv-input .onyx-input-decorator,
+.xv-pullout .xv-parameter-panel .xv-pickerwidget .onyx-input-decorator {
   border: none;
 }
-.xv-characteristic-picker .onyx-picker-decorator .onyx-button {
-  width: 130px;
-}
-.xv-characteristic-item {
-  border-bottom: 1px solid #d7d7d7;
+.xv-pullout .xv-parameter-panel .xv-buttons {
+  text-align: center;
 }
-.xv-characteristic-item .onyx-input-decorator > input {
-  width: 115px;
-  margin-top: 10px;
-  margin-bottom: 10px;
+.xv-pullout .xv-parameter-panel .xv-filter-form .xv-list {
+  height: 150px;
 }
-.xv-characteristic-item > .xv-input {
+.xv-pullout .help-panel {
   border: none;
+  height: 100%;
+  width: 100%;
 }
-.xv-characteristic-buttons {
-  margin: 8px;
+.xv-pullout .history-panel .xv-list-item {
+  padding: 12px 10px;
+  color: #0e0e0e;
+  background-color: #fdfdfd;
+  border-bottom: 1px solid #d7d7d7;
 }
-.xv-characteristic-button {
-  margin-left: 4px;
-  color: #357ec7;
-  font-size: 24px;
+.xv-pullout .history-panel .xv-list-item.onyx-selected {
+  background-color: #357ec7;
+  color: #fdfdfd;
+  border-bottom: 2px solid #d8d8d8;
 }
 /**
   Styles relating to RelationWidgets
@@ -2998,38 +3179,12 @@ body {
 .xv-completer-sidecar {
   color: #93a1a1;
 }
-.xv-relationwidget-column.left {
-  padding-right: 18px;
-  color: #070707;
-  width: 140px;
-}
 .xv-relationwidget-icon {
   top: 1px;
   left: 8px;
   height: 30px;
   position: relative;
 }
-.xv-relationwidget-description {
-  overflow: hidden;
-  display: -webkit-box;
-  -webkit-line-clamp: 3;
-  -webkit-box-orient: vertical;
-  max-width: 250px;
-  margin: 5px 5px 5px 80px;
-}
-.xv-relationwidget-description.disabled {
-  color: #777777;
-}
-.xv-relationwidget-description.label {
-  text-indent: 0;
-  text-align: right;
-}
-.xv-relationwidget-description.hasLabel {
-  text-indent: 0;
-}
-.xv-relationwidget-description.hyperlink {
-  color: blue;
-}
 .xv-private-item-site-widget {
   border-bottom-color: #aaaaaa;
   border-bottom-width: 1px;
@@ -3064,9 +3219,6 @@ body {
   font-style: italic;
   color: #93a1a1;
 }
-.xv-addresswidget .xv-addresswidget-viewer.hyperlink {
-  color: #0000ff;
-}
 .xv-addresswidget .xv-addresswidget-viewer.disabled {
   color: #777777;
 }
@@ -3138,6 +3290,9 @@ body {
   background: #f8f8f8;
   text-overflow: ellipsis;
   border: 1px solid #d7d7d7;
+  /**
+        Styles relating to workspace widgets
+      */
   width: 100%;
   margin: 0 4px 6px 0;
 }
@@ -3162,23 +3317,118 @@ body {
 .xv-search .xv-search-container .xv-label,
 .xv-search .xv-search-container .xv-decorated-label,
 .xv-search .xv-search-container .xv-flexible-label {
-  width: 120px !important;
+  width: 120px;
   max-width: 100%;
-  padding: 0 8px 0 6px;
   text-align: right;
+  padding: 0 8px 0 6px;
   vertical-align: middle;
 }
+.xv-search .xv-search-container .xv-label.disabled,
+.xv-search .xv-search-container .xv-decorated-label.disabled,
+.xv-search .xv-search-container .xv-flexible-label.disabled {
+  color: #777777;
+}
 .xv-search .xv-search-container.xv-assignment-box .xv-flexible-label,
 .xv-search .xv-search-container.xv-assignment-box .xv-label {
   width: 200px !important;
 }
 .xv-search .xv-search-container .xv-input {
+  padding: 4px 0;
+  border-bottom: 1px solid rgba(215, 215, 215, 0.5);
+}
+.xv-search .xv-search-container .xv-input .enyo-tool-decorator {
+  max-width: 180px;
+  text-align: left;
+}
+.xv-search .xv-search-container .xv-input .enyo-tool-decorator .onyx-icon {
+  display: inline-block;
+  width: 16px;
+  cursor: pointer;
+}
+.xv-search .xv-search-container .xv-input.xv-textarea {
+  padding: 0;
+  margin: 8px;
+}
+.xv-search .xv-search-container .xv-input.xv-textarea .enyo-tool-decorator {
+  max-width: 320px;
+}
+.xv-search .xv-search-container .xv-input input {
+  width: 164px;
+}
+.xv-search .xv-search-container .xv-input .xv-icon-decorator input {
+  width: 140px;
+}
+.xv-search .xv-search-container .xv-input .xv-icon-decorator .onyx-icon {
+  padding: 4px;
+}
+.xv-search .xv-search-container .xv-input .onyx-picker-decorator {
+  padding: 0;
+}
+.xv-search .xv-search-container .xv-relationwidget .xv-description {
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-line-clamp: 4;
+  -webkit-box-orient: vertical;
+  max-width: 250px;
+  margin: 5px 5px 5px 80px;
+  font-size: 0.9em;
+}
+.xv-search .xv-search-container .xv-relationwidget .xv-description.disabled {
+  color: #777777;
+}
+.xv-search .xv-search-container .xv-spinnerwidget .xv-icon-decorator .onyx-icon {
+  display: block;
+  border: none;
+  font-size: 20px;
+  width: 20px;
+  padding: 0 4px 0 4px;
+}
+.xv-search .xv-search-container .xv-spinnerwidget .xv-icon-decorator input {
+  width: 136px;
+}
+.xv-search .xv-search-container .xv-spinnerwidget .slider {
+  margin: 15px 10px;
+}
+.xv-search .xv-search-container .xv-numberwidget input {
+  text-align: right;
+}
+.xv-search .xv-search-container .xv-moneywidget .enyo-tool-decorator {
+  min-width: 90px;
+  max-width: 90px;
+}
+.xv-search .xv-search-container .xv-moneywidget .enyo-tool-decorator input {
+  width: 90px;
+}
+.xv-search .xv-search-container .xv-currency-picker {
+  display: inline-block;
+}
+.xv-search .xv-search-container .xv-currency-picker .enyo-tool-decorator {
+  min-width: 65px;
+  max-width: 65px;
+  padding: 10px 4px;
+}
+.xv-search .xv-search-container .xv-currency-picker .enyo-tool-decorator .xv-button-text {
+  width: 45px;
+}
+.xv-search .xv-search-container .xv-characteristics-widget .xv-characteristic-item {
   border-bottom: 1px solid #d7d7d7;
-  margin: 0px;
+}
+.xv-search .xv-search-container .xv-characteristics-widget .xv-characteristic-item .xv-input {
+  border: none;
+}
+.xv-search .xv-search-container .xv-characteristics-widget .xv-characteristic-item .xv-input input {
+  width: 135px;
+}
+.xv-search .xv-search-container .xv-characteristics-widget .xv-characteristic-button {
+  text-align: center;
 }
 .xv-search .xv-search-container .enyo-fittable-columns-layout > * {
   vertical-align: middle;
 }
+.xv-search .xv-search-container .xv-input .onyx-input-decorator,
+.xv-search .xv-search-container .xv-pickerwidget .onyx-input-decorator {
+  border: none;
+}
 .xv-search .xv-search-container .xv-buttons {
   text-align: center;
 }
index 7f264d7..17898fe 100644 (file)
@@ -372,6 +372,10 @@ trailing:true, white:true, strict: false*/
       this.inherited(arguments);
       this.createComponents(this.buildComponents());
     },
+    allRowsSaved: function () {
+      return _.every(this.getValue().models,
+                     function (model) { return model.isReadyClean(); });
+    },
     buttonTapped: function (inSender, inEvent) {
       var editor = this.$.editableGridRow,
         model,
@@ -535,8 +539,11 @@ trailing:true, white:true, strict: false*/
       this.doExportAttr({ attr: gridbox.attr });
     },
     refreshLists: function () {
+      var collection = this.getValue();
       this.$.aboveGridList.refresh();
       this.$.belowGridList.refresh();
+      this.$.exportButton.setDisabled(! this.allRowsSaved() ||
+                                      ! collection.length);
     },
     reset: function () {
       this.setEditableIndex(null);
@@ -624,6 +631,9 @@ trailing:true, white:true, strict: false*/
       if (this.$.summaryPanel) {
         this.$.summaryPanel.setValue(model);
       }
+
+      this.$.exportButton.setDisabled(! this.allRowsSaved() ||
+                                      ! collection.length);
     }
   });
 }());
index ec885ff..f04a7a7 100644 (file)
@@ -98,7 +98,10 @@ trailing:true, white:true, strict: false*/
     @todo Document the controlValueChanged method.
     */
     controlValueChanged: function () {
-      this.$.list.refresh();
+      // this is getting called before the list is created
+      if (this.$.list) {
+        this.$.list.refresh();
+      }
       return true;
     },
     /**
index 5b93c94..52eaa55 100644 (file)
@@ -78,8 +78,8 @@ trailing:true*/
                icon: "time",
                ontap: "showHistory", content: "_history".loc()},
             {kind: "font.TextIcon", name: "searchIconButton",
-               icon: "search",
-               ontap: "showParameters", content: "_search".loc(), showing: false},
+               icon: "search", ontap: "showParameters",
+               content: "_search".loc(), showing: false},
             {kind: "font.TextIcon", name: "helpIconButton",
                icon: "question",
                ontap: "showHelp", content: "_help".loc()}
@@ -137,13 +137,12 @@ trailing:true*/
             {kind: "font.TextIcon", icon: "share", content: "_export".loc()},
             {kind: "onyx.Menu", name: "exportMenu", floating: true}
           ]},
+          {kind: "XV.SortPopup", name: "sortPopup", showing: false},
           {name: "search", kind: "onyx.InputDecorator",
-            showing: false, components: [
-            {name: 'searchInput', kind: "onyx.Input", classes: "xv-search",
+            showing: false, classes: "xv-search", components: [
+            {name: 'searchInput', kind: "onyx.Input",
               placeholder: "_search".loc(), onchange: "inputChanged"},
-            {kind: "Image", src: "/assets/search-input-search.png",
-              name: "searchJump", ontap: "jump"},
-            {kind: "XV.SortPopup", name: "sortPopup", showing: false}
+            {tag: "i", classes: "icon-search", name: "searchJump", ontap: "jump"}
           ]}
         ]},
         {name: "messageHeader"},
index c132e21..fbd7e88 100644 (file)
@@ -60,11 +60,11 @@ trailing:true, white:true*/
           {name: "spacer", classes: "spacer", fit: true},
           {kind: "font.TextIcon", name: "refreshButton",
               icon: "rotate-right", content: "_refresh".loc(), ontap: "requery"},
-          {name: "search", kind: "onyx.InputDecorator", components: [
-            {name: 'searchInput', kind: "onyx.Input", classes: "xv-search",
+          {name: "search", kind: "onyx.InputDecorator",
+            classes: "xv-search", components: [
+            {name: 'searchInput', kind: "onyx.Input",
               placeholder: "_search".loc(), onchange: "requery"},
-            {kind: "Image", src: "/assets/search-input-search.png",
-              name: "searchJump", ontap: "jump"}
+            {tag: "i", classes: "icon-search", name: "searchJump", ontap: "jump"}
           ]}
         ]},
         {name: "messageHeader", content: "", classes: ""},
index 84be7c4..fd2ae2c 100644 (file)
@@ -19,7 +19,6 @@ white:true*/
     /** @lends XV.CharacteristicPicker# */{
     name: "XV.CharacteristicPicker",
     kind: "XV.PickerWidget",
-    classes: "xv-characteristic-picker",
     collection: "XM.characteristics",
     noneText: "_delete".loc(),
     noneClasses: "xv-negative",
@@ -147,7 +146,7 @@ white:true*/
    */
   enyo.kind(/** @lends XV.CharacteristicItem# */{
     name: "XV.CharacteristicItem",
-    kind: "FittableColumns",
+    kind: "enyo.Control",
     classes: "xv-characteristic-item",
     published: {
       value: null,
@@ -157,13 +156,15 @@ white:true*/
       onValueChange: "controlValueChanged"
     },
     components: [
-      {kind: "XV.CharacteristicPicker", attr: "characteristic",
-        showLabel: false},
-      {kind: "XV.InputWidget", attr: "value", showLabel: false},
-      {kind: "XV.DateWidget", attr: "value", showLabel: false,
-        showing: false},
-      {kind: "XV.OptionsPicker", attr: "value", showLabel: false,
-        showing: false}
+      {controlClasses: 'enyo-inline', components: [
+        {kind: "XV.CharacteristicPicker", attr: "characteristic",
+          showLabel: false},
+        {kind: "XV.InputWidget", attr: "value", showLabel: false},
+        {kind: "XV.DateWidget", attr: "value", showLabel: false,
+          showing: false},
+        {kind: "XV.OptionsPicker", attr: "value", showLabel: false,
+          showing: false}
+      ]}
     ],
     disabledChanged: function (oldValue) {
       this.$.characteristicPicker.setDisabled(this.disabled);
@@ -175,8 +176,8 @@ white:true*/
      @todo Document the controlValueChanged method.
      */
     controlValueChanged: function (inSender, inEvent) {
-      var attr = inSender.getAttr(),
-        value = inSender.getValue(),
+      var attr = inEvent.originator.getAttr(),
+        value = inEvent.originator.getValue(),
         attributes = {},
         model = this.getValue(),
         characteristic,
@@ -266,18 +267,17 @@ white:true*/
       // note: which is now being kept track of in the model, and maybe should
       // only be kept track of in the model.
       which: null,
-      disabled: false
+      disabled: false,
+      showLabel: false,
     },
     components: [
       {kind: "onyx.GroupboxHeader", content: "_characteristics".loc()},
       {kind: "Repeater", count: 0, onSetupItem: "setupItem", components: [
         {kind: "XV.CharacteristicItem"}
       ]},
-      {kind: "FittableColumns", classes: "xv-characteristic-buttons",
-        components: [
+      {controlClasses: 'enyo-inline', classes: "xv-buttons", components: [
         {kind: "onyx.Button", name: "newButton",
-          classes: "icon-plus xv-characteristic-button",
-          onclick: "newItem"}
+          classes: "icon-plus xv-characteristic-button", onclick: "newItem"}
       ]}
     ],
     disabledChanged: function () {
@@ -383,7 +383,7 @@ white:true*/
       this.value.comparator = this.sort;
       this.value.sort();
       this.lengthChanged();
-    }
+    },
   });
 
 }());
index 27418ce..7b4cfed 100644 (file)
@@ -4,52 +4,6 @@ regexp:true, undef:true, trailing:true, white:true */
 
 (function () {
 
-  /**
-    @name XV.Checkbox
-    @class An input control that shows or hides a checkmark when clicked.</br >
-    To implement a checkbox, see {@link XV.CheckboxWidget}.<br />
-    Derived from <a href="http://enyojs.com/api/#onyx.Checkbox">onyx.Checkbox</a>.
-    @extends onyx.Checkbox
-   */
-  enyo.kind(
-    /** @lends XV.Checkbox# */{
-    name: "XV.Checkbox",
-    kind: "onyx.Checkbox",
-    published: {
-      attr: null
-    },
-    events: {
-      onValueChange: ""
-    },
-    handlers: {
-      onchange: "changed"
-    },
-    /**
-    @todo Document the clear method.
-    */
-    clear: function (options) {
-      this.setValue(false, options);
-    },
-    /**
-    @todo Document the setValue method.
-    */
-    setValue: function (value, options) {
-      options = options || {};
-      this._silent = options.silent;
-      this.inherited(arguments);
-      this._silent = false;
-    },
-    /**
-    @todo Document the changed method.
-    */
-    changed: function (inSender, inEvent) {
-      if (!this._silent) {
-        inEvent.value = this.getValue();
-        this.doValueChange(inEvent);
-      }
-    }
-  });
-
   /**
     @name XV.CheckboxWidget
     @class An input control consisting of fittable columns:
@@ -57,19 +11,16 @@ regexp:true, undef:true, trailing:true, white:true */
     Use to implement a styled checkbox
       which is made up of a checkbox input control and its label.
     @extends XV.Input
+    @lends XV.CheckboxWidget
    */
-  enyo.kind(/** @lends XV.CheckboxWidget# */{
+  enyo.kind({
     name: "XV.CheckboxWidget",
-    kind: "XV.Input",
-    classes: "xv-input xv-checkboxwidget",
-    published: {
-      label: ""
-    },
+    kind: "XV.InputWidget",
+    classes: "xv-checkboxwidget",
     components: [
-      {kind: "FittableColumns", components: [
-        {name: "label", content: "", classes: "xv-label"},
-        {kind: "onyx.InputDecorator", classes: "xv-input-decorator",
-          components: [
+      {controlClasses: 'enyo-inline', components: [
+        {name: "label", classes: "xv-label"},
+        {kind: "onyx.InputDecorator", components: [
           {name: "input", kind: "onyx.Checkbox", onchange: "inputChanged"}
         ]}
       ]}
@@ -80,32 +31,12 @@ regexp:true, undef:true, trailing:true, white:true */
     clear: function (options) {
       this.setValue(false, options);
     },
-    /**
-    @todo Document the create method.
-    */
-    create: function () {
-      this.inherited(arguments);
-      this.labelChanged();
-    },
-    disabledChanged: function () {
-      this.inherited(arguments);
-      this.$.label.addRemoveClass("disabled", this.getDisabled());
-    },
-    /**
-    @todo Document the inputChanged method.
-    */
+
     inputChanged: function (inSender, inEvent) {
       var input = this.$.input.getValue();
       this.setValue(input);
     },
     /**
-    @todo Document the labelChanged method.
-    */
-    labelChanged: function () {
-      var label = (this.getLabel() || ("_" + this.attr || "").loc()) + ":";
-      this.$.label.setContent(label);
-    },
-    /**
     @todo Document the valueChanged method.
     */
     valueChanged: function (value) {
@@ -128,7 +59,7 @@ regexp:true, undef:true, trailing:true, white:true */
    */
   enyo.kind(/** @lends XV.StickyCheckboxWidget# */{
     name: "XV.StickyCheckboxWidget",
-    classes: "xv-input xv-checkboxwidget",
+    kind: "XV.CheckboxWidget",
     published: {
       label: "",
       disabled: false,
@@ -138,10 +69,9 @@ regexp:true, undef:true, trailing:true, white:true */
       onValueChange: ""
     },
     components: [
-      {kind: "FittableColumns", components: [
-        {name: "label", content: "", classes: "xv-label"},
-        {kind: "onyx.InputDecorator", classes: "xv-input-decorator",
-          components: [
+      {controlClasses: 'enyo-inline', components: [
+        {name: "label", classes: "xv-label"},
+        {kind: "onyx.InputDecorator", components: [
           {name: "input", kind: "onyx.Checkbox", onchange: "inputChanged"}
         ]}
       ]}
index fc8edae..a20fd81 100644 (file)
@@ -14,14 +14,13 @@ regexp:true, undef:true, trailing:true, white:true, browser:true */
   enyo.kind(
     /** @lends XV.CheckboxWidget# */{
     name: "XV.ComboboxWidget",
-    kind: "XV.Input",
+    kind: "XV.InputWidget",
     published: {
+      attr: null,
       collection: "",
       filter: null,
       disabled: false,
       keyAttribute: "name",
-      label: "",
-      showLabel: true,
       tabStop: true
     },
     classes: "xv-combobox xv-input",
@@ -29,9 +28,9 @@ regexp:true, undef:true, trailing:true, white:true, browser:true */
       onValueChange: "controlValueChanged"
     },
     components: [
-      {kind: "FittableColumns", name: "fittableColumns", components: [
-        {name: "label", content: "", fit: true, classes: "xv-flexible-label"},
-        {kind: "onyx.InputDecorator", tag: "div", classes: "input-decorator", components: [
+      {controlClasses: 'enyo-inline', name: "container", components: [
+        {name: "label", classes: "xv-label"},
+        {kind: "onyx.InputDecorator", tag: "div", classes: "xv-icon-decorator", components: [
           {name: "input", kind: "onyx.Input", onkeyup: "keyUp", onkeydown: "keyDown",
             onblur: "receiveBlur", onchange: "inputChanged"},
           {kind: "onyx.IconButton", name: "iconButton", ontap: "toggleCompleter",
@@ -47,8 +46,6 @@ regexp:true, undef:true, trailing:true, white:true, browser:true */
       this.inherited(arguments);
       this.collectionChanged();
       this.filterChanged();
-      this.labelChanged();
-      this.showLabelChanged();
       this.tabStopChanged();
     },
     /**
@@ -117,7 +114,6 @@ regexp:true, undef:true, trailing:true, white:true, browser:true */
     setDisabled: function (isDisabled) {
       this.inherited(arguments);
       this.$.iconButton.setDisabled(isDisabled);
-      this.$.label.addRemoveClass("disabled", isDisabled);
     },
     /**
     @todo Document the keyDown method.
@@ -171,21 +167,11 @@ regexp:true, undef:true, trailing:true, white:true, browser:true */
       return models;
     },
     /**
-    @todo Document the labelChanged method.
-    */
-    labelChanged: function () {
-      var label = (this.getLabel() || ("_" + this.attr || "").loc()) + ":";
-      this.$.label.setContent(label);
-    },
-    /**
     @todo Document the receiveBlur method.
     */
     receiveBlur: function (inSender, inEvent) {
       this.autocomplete();
     },
-    showLabelChanged: function () {
-      this.$.label.setShowing(this.getShowLabel());
-    },
     tabStopChanged: function () {
       this.$.input.setAttribute("tabIndex", this.getTabStop() ? null: -1);
     },
index 13f35e7..74ec7c6 100644 (file)
@@ -5,7 +5,7 @@ regexp:true, undef:true, trailing:true, white:true */
 (function () {
 
   /**
-    @name XV.Date
+    @name XV.DateWidget
        @class An input control used to specify a date.<br />
        Reformats and sets a date entered either as a date type or a string.
        If a string is not a recognizable date, sets the input to null.<br />
@@ -13,13 +13,59 @@ regexp:true, undef:true, trailing:true, white:true */
        @extends XV.Input
    */
   enyo.kind(
-    /** @lends XV.Date# */{
-    name: "XV.Date",
-    kind: "XV.Input",
+    /** @lends XV.DateWidget# */{
+    name: "XV.DateWidget",
+    kind: "XV.InputWidget",
+    classes: "xv-input xv-datewidget",
     published: {
+      attr: null,
       nullValue: null,
       nullText: ""
     },
+    components: [
+      {controlClasses: 'enyo-inline', components: [
+        {name: "label", classes: "xv-label"},
+        {kind: "onyx.InputDecorator", name: "decorator", tag: "div",
+          classes: "xv-icon-decorator", components: [
+          {name: "input", kind: "onyx.Input", onchange: "inputChanged",
+            onkeydown: "keyDown"},
+          {name: "icon", kind: "onyx.Icon", ontap: "iconTapped",
+            classes: "icon-calendar"}
+        ]},
+        {name: "datePickPopup", kind: "onyx.Popup", maxHeight: 400, floating: true,
+            centered: true, modal: true, components: [
+          {kind: "GTS.DatePicker", name: "datePick", style: "min-width:400px;",
+            onChange: "datePicked"}
+        ]}
+      ]}
+    ],
+
+    /**
+     This function handles a date chosen via the
+     datepicker versus text entered into the input field.
+     */
+    datePicked: function (inSender, inEvent) {
+      var date = inEvent, options = {};
+      // mimic the human-typed behavior
+      this.applyTimezoneOffset(date);
+
+      options.silent = true;
+      this.setValue(date, options);
+      this.$.datePickPopup.hide();
+      this.$.input.focus();
+    },
+    disabledChanged: function () {
+      this.inherited(arguments);
+      this.$.label.addRemoveClass("disabled", this.getDisabled());
+    },
+    /**
+      This function handles the click of the calendar icon
+      that opens the datepicker.
+    */
+    iconTapped: function (inSender, inEvent) {
+      this.$.datePickPopup.show();
+      this.$.datePick.render();
+    },
     /**
      Returns the value in the input field of the widget.
      */
@@ -46,7 +92,7 @@ regexp:true, undef:true, trailing:true, white:true */
       } else {
         value = nullValue;
       }
-      XV.Input.prototype.setValue.call(this, value, options);
+      XV.InputWidget.prototype.setValue.call(this, value, options);
     },
     /**
      This function takes the value entered into a DateWidget and returns
@@ -153,107 +199,7 @@ regexp:true, undef:true, trailing:true, white:true */
       } else {
         value = "";
       }
-      return XV.Input.prototype.valueChanged.call(this, value);
-    }
-  });
 
-  /**
-    @name XV.DateWidget
-    @class An input control consisting of fittable columns.<br />
-    Use to implement a styled input field for entering a date
-    including associated popup menu for selecting a date.<br />
-    Creates an HTML input element.
-    @extends XV.Date
-   */
-  enyo.kind(/** @lends XV.DateWidget# */{
-    name: "XV.DateWidget",
-    kind: "XV.Date",
-    classes: "xv-input xv-datewidget",
-    published: {
-      label: "",
-      showLabel: true
-    },
-    components: [
-      {kind: "FittableColumns", components: [
-        {name: "label", content: "", fit: true, classes: "xv-flexible-label"},
-        // setting the tag to "div" on the decorator
-        // so that clicks of button don't get redirected back
-        // to the input field (default behavior)
-        {kind: "onyx.InputDecorator", name: "decorator", tag: "div",
-          classes: "xv-input-decorator", components: [
-          {name: "input", kind: "onyx.Input", onchange: "inputChanged",
-            classes: "xv-subinput", onkeydown: "keyDown"},
-          {name: "icon", kind: "onyx.Icon", ontap: "iconTapped",
-            classes: "icon-calendar"}
-        ]},
-        {name: "datePickPopup", kind: "onyx.Popup", maxHeight: 400, floating: true,
-            centered: true, modal: true, components: [
-          // TODO: get rid of this inline style
-          {kind: "GTS.DatePicker", name: "datePick", style: "min-width:400px;",
-            onChange: "datePicked"}
-        ]}
-      ]}
-    ],
-    /**
-     @todo Document create method.
-     */
-    create: function () {
-      this.inherited(arguments);
-      this.labelChanged();
-      this.showLabelChanged();
-    },
-
-    /**
-     This function handles a date chosen via the
-     datepicker versus text entered into the input field.
-     */
-    datePicked: function (inSender, inEvent) {
-      var date = inEvent, options = {};
-      // mimic the human-typed behavior
-      this.applyTimezoneOffset(date);
-
-      options.silent = true;
-      this.setValue(date, options);
-      this.$.datePickPopup.hide();
-      this.$.input.focus();
-    },
-    disabledChanged: function () {
-      this.inherited(arguments);
-      this.$.label.addRemoveClass("disabled", this.getDisabled());
-    },
-    /**
-      This function handles the click of the calendar icon
-      that opens the datepicker.
-    */
-    iconTapped: function (inSender, inEvent) {
-      this.$.datePickPopup.show();
-      this.$.datePick.render();
-    },
-    /**
-     Sets the label of the Date Widget with text specified in the kind.
-     */
-    labelChanged: function () {
-      var label = (this.getLabel() || ("_" + this.attr || "").loc()) + ":";
-      this.$.label.setContent(label);
-    },
-    /**
-     Sets the visibility of the label of the date widget based on
-     the value specified in the kind.
-     */
-    showLabelChanged: function () {
-      if (this.getShowLabel()) {
-        this.$.label.show();
-      } else {
-        this.$.label.hide();
-      }
-    },
-    /**
-     Set the date value into the input field and the date picker and
-     sets the value to the model.
-     */
-    valueChanged: function (value) {
-      var dateValue = value;
-      value = XV.Date.prototype.valueChanged.call(this, value);
       if (!this.$.input.value && this.$.input.attributes.value) {
         // XXX workaround for incident 19171. Something deep into enyo's
         // setters are causing the attributes value to be updated when
@@ -262,8 +208,11 @@ regexp:true, undef:true, trailing:true, white:true */
         // two-step of turning a inputted value into an actual date.
         this.$.input.attributes.value = "";
       }
-      this.$.datePick.setValue(value ? dateValue : new Date());
+
+      this.$.datePick.setValue(value ? value : new Date());
       this.$.datePick.render();
+
+      return XV.InputWidget.prototype.valueChanged.call(this, value);
     }
   });
 
index a67f0e2..d785be8 100644 (file)
@@ -14,7 +14,9 @@ regexp:true, undef:true, trailing:true, white:true */
   enyo.kind(
     /** @lends XV.FileInput# */{
     name: "XV.FileInput",
-    kind: "XV.Input",
+    kind: "XV.InputWidget",
+    showLabel: false,
+    type: "file",
     events: {
       onValueChange: "",
       onNotify: ""
@@ -23,7 +25,8 @@ regexp:true, undef:true, trailing:true, white:true */
       onValueChange: "valueChange"
     },
     components: [
-      {name: "input", tag: "input type=file", kind: "onyx.Input",  classes: "xv-subinput", onchange: "inputChanged"},
+      {name: "label", fit: true, classes: "xv-label"},
+      {name: "input", kind: "onyx.Input", onchange: "inputChanged"},
       {name: "scrim", kind: "onyx.Scrim", showing: false, floating: true}
     ],
 
index c388028..9641d60 100644 (file)
@@ -5,7 +5,7 @@ regexp:true, undef:true, trailing:true, white:true */
 (function () {
 
   /**
-    @name XV.Input
+    @name XV.InputWidget
     @class Maintains a consistent API to be used by workspaces.<br />
     The superkind from which other xTuple input objects are derived.<br />
     To create an input field for strings, see {@link XV.InputWidget}.<br />
@@ -17,15 +17,18 @@ regexp:true, undef:true, trailing:true, white:true */
     To create a togglebutton, see {@link XV.TogglebuttonWidget}.<br />
    */
   enyo.kind(
-    /** @lends XV.Input# */{
-    name: "XV.Input",
+    /** @lends XV.InputWidget# */{
+    name: "XV.InputWidget",
+    classes: "xv-input",
     published: {
       attr: null,
       value: null,
       disabled: false,
       placeholder: null,
       type: null,
-      maxlength: null
+      maxlength: null,
+      label: "",
+      showLabel: true
     },
     events: {
       "onValueChange": ""
@@ -34,32 +37,37 @@ regexp:true, undef:true, trailing:true, white:true */
       onblur: "receiveBlur"
     },
     components: [
-      {name: "input", kind: "onyx.Input", classes: "xv-subinput", onchange: "inputChanged", onkeydown: "keyDown"}
+      {controlClasses: 'enyo-inline', components: [
+        {name: "label", classes: "xv-label"},
+        {kind: "onyx.InputDecorator", components: [
+          {name: "input", kind: "onyx.Input", onchange: "inputChanged", onkeydown: "keyDown"}
+        ]}
+      ]}
     ],
-    /**
-      Sets the value of the input to an empty string
-    */
-    clear: function (options) {
-      this.setValue("", options);
-    },
-    /**
-    @todo Document the create method.
-    */
     create: function () {
       this.inherited(arguments);
       this.placeholderChanged();
       this.disabledChanged();
       this.typeChanged();
       this.maxlengthChanged();
+      this.labelChanged();
+      this.showLabelChanged();
+    },
+    /**
+      Sets the value of the input to an empty string
+    */
+    clear: function (options) {
+      this.setValue("", options);
     },
     /**
      The disabledChanged method, and many below it, are here to deal with
-     the fact that XV.Input doesvnot inherit from onyx.input or enyo.input,
+     the fact that XV.Input does not inherit from onyx.input or enyo.input,
      and so insofar as it needs to support their APIs, we
      have to redeclare the methods and pass through the data.
     */
     disabledChanged: function () {
       this.$.input.setDisabled(this.getDisabled());
+      this.$.label.addRemoveClass("disabled", this.getDisabled());
     },
     focus: function () {
       this.$.input.focus();
@@ -100,11 +108,12 @@ regexp:true, undef:true, trailing:true, white:true */
       }
     },
     /**
-    @todo Document the placeholderChanged method.
+      Sets the placeholder on the input field.
     */
     placeholderChanged: function () {
-      var placeholder = this.getPlaceholder();
-      this.$.input.setPlaceholder(placeholder);
+      if (_.isFunction(this.$.input.setPlaceholder)) {
+        this.$.input.setPlaceholder(this.placeholder);
+      }
     },
     /**
       Webkit browsers do not always emit the proper change event,
@@ -153,65 +162,17 @@ regexp:true, undef:true, trailing:true, white:true */
       return value;
     },
     /**
-      Pass through attributes intended for onyx input inside.
-    */
-    setInputStyle: function (style) {
-      this.$.input.setStyle(style);
-    },
-    /**
-      Pass through attributes intended for onyx input inside.
+      Sets the type attribute for input field.
     */
     typeChanged: function () {
       this.$.input.setType(this.getType());
     },
 
-    maxlengthChanged: function () {
-      this.$.input.setAttribute("maxlength", this.maxlength);
-    }
-  });
-
-  /**
-    @name XV.InputWidget
-    @class An input control consisting of fittable columns:
-      a styled label and an onyx.Input placed inside an onyx.InputDecorator,
-      which provides styling.<br />
-    Any controls in the InputDecorator appear to be inside an area styled as an input.<br />
-    Use to implement a styled input field for strings.<br />
-    Creates an HTML input element.
-    @extends XV.Input
-   */
-  enyo.kind(/** @lends XV.InputWidget# */{
-    name: "XV.InputWidget",
-    kind: "XV.Input",
-    classes: "xv-input",
-    published: {
-      label: "",
-      showLabel: true
-    },
-    components: [
-      {kind: "FittableColumns", components: [
-        {name: "label", content: "", fit: true, classes: "xv-flexible-label"},
-        {kind: "onyx.InputDecorator", classes: "xv-input-decorator",
-          components: [
-          {name: "input", kind: "onyx.Input", classes: "xv-subinput",
-            onchange: "inputChanged", onkeydown: "keyDown"}
-        ]}
-      ]}
-    ],
     /**
-    @todo Document the create method.
+      Set the maxlength attribute on the input field.
     */
-    create: function () {
-      this.inherited(arguments);
-      this.labelChanged();
-      this.placeholderChanged();
-      this.showLabelChanged();
-    },
-
-    disabledChanged: function () {
-      this.inherited(arguments);
-      var disabled = this.getDisabled();
-      this.$.label.addRemoveClass("disabled", disabled);
+    maxlengthChanged: function () {
+      this.$.input.setAttribute("maxlength", this.maxlength);
     },
 
     /**
@@ -223,14 +184,11 @@ regexp:true, undef:true, trailing:true, white:true */
     },
 
     /**
-    @todo Document the showLabelChanged method.
+      Sets visibility of the widget label.
     */
     showLabelChanged: function () {
-      if (this.getShowLabel()) {
-        this.$.label.show();
-      } else {
-        this.$.label.hide();
-      }
+      this.$.label.setShowing(this.getShowLabel());
     }
   });
+
 }());
index 37e00be..a1a793f 100644 (file)
@@ -12,11 +12,16 @@ regexp:true, undef:true, trailing:true, white:true */
    */
   enyo.kind(
     /** @lends XV.Number# */{
-    name: "XV.Number",
-    kind: "XV.Input",
+    name: "XV.NumberWidget",
+    kind: "XV.InputWidget",
+    classes: "xv-numberwidget xv-input",
     published: {
+      attr: null,
       scale: 0,
-      formatting: true
+      formatting: true,
+      type: "number",
+      label: "",
+      showLabel: true
     },
     /**
     @todo Document the setValue method.
@@ -25,7 +30,7 @@ regexp:true, undef:true, trailing:true, white:true */
       // use isNaN here because this value could be a number String, 0 value, or null
       // only want to set value as null in cases of bad strings and null/undefined
       value = value !== null && !isNaN(value) ? XT.math.round(value, this.getScale()) : null;
-      XV.Input.prototype.setValue.call(this, value, options);
+      this.inherited(arguments);
     },
     /**
       Determines whether the user input is numeric.
@@ -56,63 +61,7 @@ regexp:true, undef:true, trailing:true, white:true */
       } else {
         value = "";
       }
-      return XV.Input.prototype.valueChanged.call(this, value);
-    }
-  });
-
-  /**
-    @name XV.NumberWidget
-    @class An input control consisting of fittable columns: label, decorator, and input field.<br />
-    Use to implement an input field for strings that represents a number, such as prices.  This widget
-    includes a style to right-justify the text entered into the input field.
-    @extends XV.Number
-   */
-  enyo.kind(/** @lends XV.NumberWidget# */{
-    name: "XV.NumberWidget",
-    kind: "XV.Number",
-    classes: "xv-numberwidget xv-input",
-    published: {
-      label: "",
-      showLabel: true,
-      placeholder: ""
-    },
-    components: [
-      {kind: "FittableColumns", components: [
-        {name: "label", content: "", fit: true, classes: "xv-flexible-label"},
-        {kind: "onyx.InputDecorator", classes: "xv-input-decorator",
-          components: [
-          {name: "input", kind: "onyx.Input", onchange: "inputChanged", onkeydown: "keyDown", type: "number"}
-        ]}
-      ]}
-    ],
-    /**
-     @todo Document the create method.
-     */
-    create: function () {
       this.inherited(arguments);
-      this.labelChanged();
-      this.showLabelChanged();
-    },
-    disabledChanged: function () {
-      this.inherited(arguments);
-      this.$.label.addRemoveClass("disabled", this.getDisabled());
-    },
-    /**
-     @todo Document the labelChanged method.
-     */
-    labelChanged: function () {
-      var label = (this.getLabel() || ("_" + this.attr + "").loc()) + ":";
-      this.$.label.setContent(label);
-    },
-    /**
-     @todo Document the showLabelChanged method.
-     */
-    showLabelChanged: function () {
-      if (this.getShowLabel()) {
-        this.$.label.show();
-      } else {
-        this.$.label.hide();
-      }
     }
   });
 
index f1a88df..c5af7e0 100644 (file)
@@ -19,28 +19,23 @@ regexp:true, undef:true, trailing:true, white:true */
       showSlider: false,
       maxValue: 100,
       label: "",
-      showLabel: true
+      showLabel: true,
+      type: "text"
     },
-    classes: "spinner",
+    classes: "xv-spinnerwidget",
     components: [
-      {kind: "FittableColumns", components: [
-        {kind: "FittableRows", components: [
-          {name: "label", classes: "xv-label"}
-        ]},
-        {kind: "FittableRows", fit: true, components: [
+      {controlClasses: 'enyo-inline', components: [
+        {name: "label", classes: "xv-label"},
+        {controlClasses: 'enyo-inline', components: [
           {kind: "onyx.Slider", name: "slider", onChange: "sliderChanged", classes: "slider"},
-          {kind: "onyx.InputDecorator", tag: "div", classes: "input-decorator", components: [
-            {name: "input", kind: "onyx.Input", classes: "xv-subinput",
+          {kind: "onyx.InputDecorator", tag: "div", classes: "xv-icon-decorator", components: [
+            {name: "input", kind: "onyx.Input",
               onchange: "inputChanged", onkeydown: "keyDown"},
-            {kind: "FittableRows", components: [
-              {kind: "onyx.Button", classes: "buttons", ontap: "increase",
-                attributes: {tabIndex: "-1"}, components: [
-                {tag: "i", classes: "icon-angle-up"}
-              ]},
-              {kind: "onyx.Button", classes: "buttons", ontap: "decrease",
-                attributes: {tabIndex: "-1"}, components: [
-                {tag: "i", classes: "icon-angle-down"}
-              ]}
+            {components: [
+              {kind: "onyx.IconButton", ontap: "increase",
+                attributes: {tabIndex: "-1"}, classes: "icon-angle-up"},
+              {kind: "onyx.IconButton", ontap: "decrease",
+                attributes: {tabIndex: "-1"}, classes: "icon-angle-down"}
             ]}
           ]}
         ]}
@@ -48,9 +43,7 @@ regexp:true, undef:true, trailing:true, white:true */
     ],
     create: function () {
       this.inherited(arguments);
-      this.labelChanged();
       this.showSliderChanged();
-      this.showLabelChanged();
     },
     /**
       Decreases the value of the input field by an increment of
index 720976d..5efc2e0 100644 (file)
@@ -31,7 +31,7 @@ white:true*/
       onValueChange: "parameterChanged"
     },
     components: [
-      {name: "input", classes: "xv-parameter-item-input"}
+      {name: "input"}
     ],
     defaultKind: "XV.InputWidget",
     /**
@@ -42,7 +42,7 @@ white:true*/
       this.labelChanged();
       if (!this.getOperator() && this.defaultKind === "XV.InputWidget") {
         this.setOperator("MATCHES");
-      } else if (this.$.input instanceof XV.Picker) {
+      } else if (this.$.input instanceof XV.PickerWidget) {
         this.$.input.setNoneText("_any".loc());
       }
     },
index 05944d2..0473cd3 100644 (file)
@@ -19,9 +19,9 @@ regexp:true, undef:true, trailing:true, white:true, strict:false */
    */
   enyo.kind(
     /** @lends XV.PickerWidget# */{
-    name: "XV.Picker",
+    name: "XV.PickerWidget",
     kind: "enyo.Control",
-    classes: "xv-input",
+    classes: "xv-pickerwidget",
     events: /** @lends XV.PickerWidget# */{
       /**
         @property {Object} inEvent The payload that's attached to bubbled-up events
@@ -41,16 +41,22 @@ regexp:true, undef:true, trailing:true, white:true, strict:false */
       noneClasses: "",
       showNone: true,
       prerender: true,
-      defaultValue: null
+      defaultValue: null,
+      showLabel: true,
+      label: ""
     },
     handlers: {
       onSelect: "itemSelected"
     },
     components: [
-      {kind: "onyx.PickerDecorator",
-        components: [
-        {kind: "XV.PickerButton", name: "pickerButton", content: "_none".loc(), onkeyup: "keyUp"},
-        {name: "picker", kind: "onyx.Picker"}
+      {controlClasses: 'enyo-inline', components: [
+        {name: "label", classes: "xv-label"},
+        {kind: "onyx.InputDecorator", name: "inputWrapper", components: [
+          {kind: "onyx.PickerDecorator", components: [
+            {kind: "XV.PickerButton", name: "pickerButton", content: "_none".loc(), onkeyup: "keyUp"},
+            {name: "picker", kind: "onyx.Picker"}
+          ]}
+        ]}
       ]}
     ],
     /**
@@ -155,6 +161,9 @@ regexp:true, undef:true, trailing:true, white:true, strict:false */
       if (defaultValue) {
         this.setValue(defaultValue, {silent: true});
       }
+
+      this.labelChanged();
+      this.showLabelChanged();
     },
     destroy: function () {
       if (this._collection && this._collection.off) {
@@ -401,44 +410,6 @@ regexp:true, undef:true, trailing:true, white:true, strict:false */
           }
         }
       }
-    }
-  });
-
-  /**
-    @name XV.PickerWidget
-    @class A picker control that implements a dropdown list of items which can be selected.<br />
-    Unlike the {@link XV.RelationWidget}, the collection is stored local to the widget.<br />
-    The superkind of {@link XV.CharacteristicPicker}.<br />
-    Derived from <a href="http://enyojs.com/api/#enyo.Control">enyo.Control</a>.
-    @extends enyo.Control
-   */
-  enyo.kind(/** @lends XV.PickerWidget# */{
-    name: "XV.PickerWidget",
-    kind: "XV.Picker",
-    published: {
-      label: "",
-      showLabel: true
-    },
-    components: [
-      {kind: "FittableColumns", components: [
-        {name: "label", content: "", classes: "xv-label"},
-        {kind: "onyx.InputDecorator", name: "inputWrapper", classes: "xv-input-decorator",
-          components: [
-          {kind: "onyx.PickerDecorator",
-            components: [
-            {kind: "XV.PickerButton", name: "pickerButton", content: "_none".loc(), onkeyup: "keyUp"},
-            {name: "picker", kind: "onyx.Picker"}
-          ]}
-        ]}
-      ]}
-    ],
-    /**
-     @todo Document the create method.
-     */
-    create: function () {
-      this.inherited(arguments);
-      this.labelChanged();
-      this.showLabelChanged();
     },
     /**
      @todo Document the labelChanged method.
index 455bd44..3783b06 100644 (file)
@@ -14,7 +14,8 @@ regexp:true, undef:true, trailing:true, white:true, strict:false */
   enyo.kind(
     /** @lends XV.RelationWidget# */{
     name: "XV.RelationWidget",
-    kind: enyo.Control,
+    // TODO: needs to inherit from InputWidget
+    kind: "enyo.Control",
     classes: "xv-input xv-relationwidget",
     published: {
       attr: null,
@@ -22,7 +23,7 @@ regexp:true, undef:true, trailing:true, white:true, strict:false */
       placeholder: "",
       value: null,
       list: "",
-      collection: "",
+      collection: null,
       disabled: false,
       /**
         This can be a string or an array. If an array, all attributes
@@ -48,31 +49,30 @@ regexp:true, undef:true, trailing:true, white:true, strict:false */
       onSelect: "itemSelected"
     },
     components: [
-      {kind: "FittableColumns", components: [
-        {name: "label", content: "", fit: true, classes: "xv-flexible-label"},
-        // TODO: Put the InputDecorator and description in a FittableRows
-        {kind: "onyx.InputDecorator", name: "decorator",
-          classes: "xv-input-decorator", components: [
-          {name: 'input', kind: "onyx.Input", classes: "xv-subinput",
+      {controlClasses: 'enyo-inline', components: [
+        {name: "label", classes: "xv-label"},
+        {kind: "onyx.InputDecorator", name: "decorator", classes: "xv-icon-decorator", components: [
+          {name: "input", kind: "onyx.Input",
             onkeyup: "keyUp", onkeydown: "keyDown", onblur: "receiveBlur",
-            onfocus: "receiveFocus", fit: true
-          },
+            onfocus: "receiveFocus"},
           {kind: "onyx.MenuDecorator", components: [
             {kind: "onyx.IconButton", classes: "icon-folder-open-alt"},
-            {name: 'popupMenu', floating: true, kind: "onyx.Menu",
-              components: [
-              {kind: "XV.MenuItem", name: 'searchItem', content: "_search".loc()},
-              {kind: "XV.MenuItem", name: 'openItem', content: "_open".loc(),
-                disabled: true},
-              {kind: "XV.MenuItem", name: 'newItem', content: "_new".loc(),
-                disabled: true}
+            {kind: "onyx.Menu", name: 'popupMenu', floating: true, components: [
+              {kind: "XV.MenuItem", name: "searchItem", content: "_search".loc()},
+              {kind: "XV.MenuItem", name: "openItem", content: "_open".loc(), disabled: true},
+              {kind: "XV.MenuItem", name: "newItem", content: "_new".loc(), disabled: true}
             ]}
           ]},
           {name: "completer", kind: "XV.Completer"}
         ]}
       ]},
-      {name: "name", classes: "xv-relationwidget-description"},
-      {name: "description", classes: "xv-relationwidget-description xv-relationwidget-secondarydescription"}
+      {name: 'descriptionContainer', classes: 'xv-invisible-wrapper'}
+    ],
+    descriptionComponents: [
+      {controlClasses: 'enyo-inline', components: [
+        {name: "name", classes: "xv-description"},
+        {name: "description", classes: "xv-description"}
+      ]}
     ],
     /**
       Add a parameter to the query object on the widget. Parameter conventions should
@@ -144,6 +144,11 @@ regexp:true, undef:true, trailing:true, white:true, strict:false */
       this.listChanged();
       this.labelChanged();
       this.disabledChanged();
+
+      // create description components
+      _.each(this.descriptionComponents, function (component) {
+        this.$.descriptionContainer.createComponent(component, {owner: this});
+      }, this);
     },
     /**
      @todo Document the disabledChanged method.
@@ -153,9 +158,12 @@ regexp:true, undef:true, trailing:true, white:true, strict:false */
       this.$.input.setDisabled(disabled);
       this.$.searchItem.setDisabled(disabled);
       this.$.newItem.setDisabled(_couldNotCreate.apply(this) || disabled);
-      this.$.name.addRemoveClass("disabled", disabled);
-      this.$.description.addRemoveClass("disabled", disabled);
       this.$.label.addRemoveClass("disabled", disabled);
+
+      // _.each(this.$.descriptionContainer.$, function (component) {
+      //   component.addRemoveClass("disabled", disabled);
+      // }, this);
+      this.$.descriptionContainer.addRemoveClass('disabled', disabled);
     },
     /**
       Query the database.
@@ -540,12 +548,11 @@ regexp:true, undef:true, trailing:true, white:true, strict:false */
         additionalValue = value.getValue(additional) || "";
       }
       this.$.input.setValue(keyValue);
+
       this.$.name.setShowing(nameValue);
       this.$.name.setContent(nameValue);
       this.$.description.setShowing(descripValue);
       this.$.description.setContent(descripValue);
-      //this.$.additionalInfo.setShowing(additionalValue);
-      //this.$.additionalInfo.setContent(additionalValue);
 
       // Only notify if selection actually changed
       if (newId !== oldId && !options.silent) { this.doValueChange(inEvent); }
index e4d25dd..6b664c7 100644 (file)
@@ -11,17 +11,18 @@ regexp:true, undef:true, trailing:true, white:true */
     Creates an HTML textarea element.<br />
     For example, used as a component of {@link XV.CommentBoxItem}.
     @extends XV.Input
-   */
+  */
   enyo.kind(
     /** @lends XV.TextArea# */{
     name: "XV.TextArea",
-    kind: "XV.Input",
+    kind: "XV.InputWidget",
     classes: "xv-textarea",
     published: {
       attr: null,
-      placeholder: ""
+      showLabel: false
     },
     components: [
+      {name: "label", classes: "xv-label"},
       {name: "input", kind: "onyx.TextArea", classes: "xv-textarea-input",
         onchange: "inputChanged", onkeydown: "keyDown"}
     ]
index e094c27..230b90e 100644 (file)
@@ -5,30 +5,32 @@ regexp:true, undef:true, trailing:true, white:true */
 (function () {
 
   /**
-    @name XV.ToggleButton
-    @class A control that looks like a switch with labels for two states.<br />
-    Derived from <a href="http://enyojs.com/api/#onyx.ToggleButton">onyx.ToggleButton</a>.
-    @extends onyx.ToggleButton
+    @name XV.ToggleButtonWidget
+    @class An input control consisting of fittable columns:
+     label, decorator, and toggle button.<br />
+    Use to implement a toggle button, a switch with labels for two states.<br />
+    Creates an HTML input element.
+    @extends XV.Input
    */
-  enyo.kind(
-    /** @lends XV.ToggleButton# */{
-    name: "XV.ToggleButton",
-    kind: "onyx.ToggleButton",
-    published: {
-      attr: null
-    },
+  enyo.kind({
+    name: "XV.ToggleButtonWidget",
+    kind: "XV.InputWidget",
+    classes: "xv-inputwidget xv-checkboxwidget",
     events: {
       onValueChange: ""
     },
     handlers: {
       onChange: "changed"
     },
-    /**
-     @todo Document the clear method.
-     */
-    clear: function (options) {
-      this.setValue(false, options);
-    },
+    components: [
+      {controlClasses: 'enyo-inline', components: [
+        {name: "label", content: "", classes: "xv-label"},
+        {kind: "onyx.InputDecorator", classes: "xv-input-decorator",
+          components: [
+          {name: "input", kind: "onyx.ToggleButton", onChange: "inputChanged"}
+        ]}
+      ]}
+    ],
     /**
      @todo Document the setValue method.
      */
@@ -46,33 +48,7 @@ regexp:true, undef:true, trailing:true, white:true */
         inEvent.value = this.getValue();
         this.doValueChange(inEvent);
       }
-    }
-  });
-
-  /**
-    @name XV.ToggleButtonWidget
-    @class An input control consisting of fittable columns:
-     label, decorator, and toggle button.<br />
-    Use to implement a toggle button, a switch with labels for two states.<br />
-    Creates an HTML input element.
-    @extends XV.Input
-   */
-  enyo.kind(/** @lends XV.ToggleButtonWidget# */{
-    name: "XV.ToggleButtonWidget",
-    kind: "XV.Input",
-    classes: "xv-inputwidget xv-checkboxwidget",
-    published: {
-      label: ""
     },
-    components: [
-      {kind: "FittableColumns", components: [
-        {name: "label", content: "", classes: "xv-label"},
-        {kind: "onyx.InputDecorator", classes: "xv-input-decorator",
-          components: [
-          {name: "input", kind: "onyx.ToggleButton", onChange: "inputChanged"}
-        ]}
-      ]}
-    ],
     /**
      @todo Document the clear method.
      */
@@ -89,10 +65,6 @@ regexp:true, undef:true, trailing:true, white:true */
       var options = {silent: true};
       this.valueChanged(this.getValue(), options);
     },
-    disabledChanged: function () {
-      this.inherited(arguments);
-      this.$.label.addRemoveClass("disabled", this.getDisabled());
-    },
     /**
      @todo Document the inputChanged method.
      */
@@ -100,20 +72,6 @@ regexp:true, undef:true, trailing:true, white:true */
       var input = this.$.input.getValue();
       this.setValue(input);
     },
-    /**
-     @todo Document the labelChanged method.
-     */
-    labelChanged: function () {
-      var label = (this.getLabel() || ("_" + this.attr || "").loc()) + ":";
-      this.$.label.setContent(label);
-    },
-    /**
-      Not applicable in the context of a toggle button,
-      even though it is available to input widgets generally.
-     */
-    placeholderChanged: function () {
-      // Not applicable
-    },
     /**
      @todo Document the valueChanged method.
      */
index 83ac49a..b9d95c5 100644 (file)
@@ -1,11 +1,13 @@
-/*jshint indent:2, curly:true, eqeqeq:true, immed:true, latedef:true,
+/*jshint node:true, indent:2, curly:true, eqeqeq:true, immed:true, latedef:true,
 newcap:true, noarg:true, regexp:true, undef:true, strict:true, trailing:true,
 white:true*/
-/*global SYS:true, XM:true, Backbone:true, _:true */
+/*global SYS:true, XM:true, Backbone:true, _:true, X: true */
 
 (function () {
   "use strict";
 
+  var async = require("async");
+
   /**
     @class
 
@@ -106,8 +108,55 @@ white:true*/
   SYS.User = XM.SimpleModel.extend({
     /** @scope SYS.User.prototype */
 
-    recordType: 'SYS.User'
-
+    recordType: 'SYS.User',
+
+    /**
+      Checks for a user privilege. Also checks all the roles that the user is a part of.
+      Necessarily async because not all the relevant data is nested.
+      Not portable to the client because of the backbone-relational-lessness
+      of the models.
+      `callback(err, result)` where result is truthy iff the user has the privilege
+    */
+    checkPrivilege: function (privName, database, callback) {
+      var privCheck = _.find(this.get("grantedPrivileges"), function (model) {
+        return model.privilege === privName;
+      });
+      if (privCheck) {
+        callback(); // the user has this privilege!
+        return;
+      }
+      // this gets a little dicey: check all the user's roles for the priv, which
+      // requires async.map
+      var roles = _.map(this.get("grantedUserAccountRoles"), function (grantedRole) {
+        return grantedRole.userAccountRole;
+      });
+      var checkRole = function (roleName, next) {
+        var role = new SYS.UserAccountRole();
+        role.fetch({
+          id: roleName,
+          username: X.options.databaseServer.user,
+          database: database,
+          success: function (roleModel, results) {
+            var rolePriv = _.find(roleModel.get("grantedPrivileges"), function (grantedPriv) {
+              return grantedPriv.privilege === privName;
+            });
+            next(null, rolePriv);
+          }
+        });
+      };
+      async.map(roles, checkRole, function (err, results) {
+        // if any of the roles give the priv, then the user has the priv
+        var result = _.reduce(results, function (memo, priv) {
+          return priv || memo;
+        }, false);
+        console.log(result);
+        if (err || !result) {
+          callback({message: "_insufficientPrivileges"});
+          return;
+        }
+        callback(); // success!
+      });
+    }
   });
 
   /**
index 2c3d554..25801b5 100755 (executable)
@@ -104,7 +104,7 @@ var app;
     };
     return dirMap[extension.location];
   };
-  var useClientDir = function (path, dir) {
+  var useClientDir = X.useClientDir = function (path, dir) {
     path = path.indexOf("npm") === 0 ? "/" + path : path;
     _.each(X.options.datasource.databases, function (orgValue, orgKey, orgList) {
       app.use("/" + orgValue + path, express.static(dir, { maxAge: 86400000 }));
@@ -422,7 +422,6 @@ app.get('/:org/logout', routes.logout);
 app.get('/:org/app', routes.app);
 app.get('/:org/debug', routes.debug);
 
-app.get('/:org/analysis', routes.analysis);
 app.all('/:org/credit-card', routes.creditCard);
 app.all('/:org/change-password', routes.changePassword);
 app.all('/:org/client/build/client-code', routes.clientCode);
index 9c5f0ef..d42b742 100644 (file)
@@ -274,8 +274,7 @@ server.exchange(oauth2orize.exchange.refreshToken(function (client, refreshToken
 // signature parts and a done callback. If these values are valid, the
 // application issues an access token on behalf of the user in the JWT `prn`
 // property.
-
-server.exchange('assertion', jwtBearer(function (client, header, claimSet, signature, done) {
+var jwtExchange = function (client, header, claimSet, signature, done) {
   "use strict";
 
   var data = header + "." + claimSet,
@@ -297,10 +296,12 @@ server.exchange('assertion', jwtBearer(function (client, header, claimSet, signa
         expires = new Date(today.getTime() + (60 * 60 * 1000)), // One hour from now.
         token = new SYS.Oauth2token();
 
+
     // Verify JWT was formed correctly.
     if (!decodedHeader || !decodedHeader.alg || !decodedHeader.typ) {
       return done(new Error("Invalid JWT header."));
     }
+
     if (!decodedClaimSet || decodedClaimSet.length < 5 || !decodedClaimSet.iss ||
       !decodedClaimSet.scope || !decodedClaimSet.aud || !decodedClaimSet.exp ||
       !decodedClaimSet.iat) {
@@ -417,16 +418,19 @@ server.exchange('assertion', jwtBearer(function (client, header, claimSet, signa
         token.initialize(null, {isNew: true, database: scopes[0]});
       });
     } else {
-      // No prn, throw error for now.
-      return done(new Error("Invalid JWT. No delegate user."));
-
-      // TODO - Handle public scopes with no delegatedAccess users if we ever need to.
+      // Either there is no prn, OR client.delegatedAccess is not enabled.
+      // TODO: Right now, if you create a service account and uncheck the "delegatedAccess"
+      //   field, then you will see this error. We need to handle public scopes with no
+      //   delegated users here.
+      return done(new Error("Invalid JWT. No delegated user or delegated access is not enabled for this client."));
     }
   } else {
     return done(new Error("Invalid JWT. Signature verification failed"));
   }
-}));
-
+};
+// Support both known grant types.
+server.exchange('assertion', jwtBearer(jwtExchange));
+server.exchange('urn:ietf:params:oauth:grant-type:jwt-bearer', jwtBearer(jwtExchange));
 
 // TODO - We need a token revoke endpoint some day.
 //https://developers.google.com/accounts/docs/OAuth2WebServer#tokenrevoke
diff --git a/node-datasource/routes/analysis.js b/node-datasource/routes/analysis.js
deleted file mode 100644 (file)
index db04345..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/*jshint node:true, indent:2, curly:false, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
-regexp:true, undef:true, strict:true, trailing:true, white:true */
-/*global X:true, _:true, SYS:true */
-
-(function () {
-  "use strict";
-
-  var utils = require('../oauth2/utils');
-
-  /**
-    Generates a JSON Web Token (JWT) to be appended to the
-    BI Server URL so that it may authenticate the current user.
-  */
-  exports.analysis = function (req, res) {
-    var jwt,
-      privKey = "",
-      claimSet = {},
-      header = {},
-      reportUrl = req.query.reportUrl,
-      username = req.session.passport.user.username,
-      biServerHost = X.options.biServer.bihost || "localhost",
-      biServerPortHttps = X.options.biServer.httpsport || "8443",
-      biServerUrl = "https://" + biServerHost + ":" + biServerPortHttps + "/pentaho/",
-      today = new Date(),
-      expires = new Date(today.getTime() + (10 * 60 * 1000)), // 10 minutes from now
-      datasource = "https://" + req.headers.host + "/",
-      database = req.session.passport.user.organization,
-      scope = datasource + database + "/auth/" + database,
-      audience = datasource + database + "/oauth/token",
-      superuser = X.options.databaseServer.user,
-      tenant = X.options.biServer.tenantname || "default",
-      biKeyFile = X.options.biServer.restkeyfile || "";
-
-    // get private key from path in config
-    privKey = X.fs.readFileSync(biKeyFile);
-
-    // create header for JWT
-    header = {
-      "alg": "RS256",
-      "type": "JWT"
-    };
-
-    // create claimSet for JWT
-    claimSet = {
-      "prn": username, // username
-      "scope": scope,
-      "aud": audience,
-      "org": database,
-      "superuser": superuser, // database user
-      "datasource": datasource, // rest api url
-      "exp": Math.round(expires.getTime() / 1000), // expiration date in millis
-      "iat": Math.round(today.getTime() / 1000),  // created date in millis
-      "tenant": tenant || "default" // unique tenant id
-    };
-
-    // encode and sign JWT with private key
-    jwt = encodeJWT(JSON.stringify(header), JSON.stringify(claimSet), privKey);
-    // send newly formed BI url back to the client
-    res.send(biServerUrl + reportUrl + "&assertion=" + jwt.jwt);
-  };
-
-  var encodeJWT = function (header, claimSet, key) {
-    var encodeHeader,
-      encodeClaimSet,
-      signer,
-      signature,
-      data,
-      jwt;
-
-    if (!key) {
-      X.log("No private key");
-    }
-
-    // if there is a problem encoding/signing the JWT, then return invalid
-    try {
-      encodeHeader = utils.base64urlEncode(JSON.stringify(JSON.parse(header)));
-      encodeClaimSet = utils.base64urlEncode(JSON.stringify(JSON.parse(claimSet)));
-      data = encodeHeader + "." + encodeClaimSet;
-
-      signer = X.crypto.createSign("RSA-SHA256");
-      signer.update(data);
-      signature = utils.base64urlEscape(signer.sign(key, "base64"));
-      jwt = {
-        jwt: data + "." + signature
-      };
-
-    } catch (error) {
-      jwt = {
-        jwt: "invalid"
-      };
-      X.log("Invalid JWT");
-    }
-
-    return jwt;
-  };
-
-}());
index 5dafe61..4f67983 100644 (file)
@@ -27,19 +27,11 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
           id: username,
           username: X.options.databaseServer.user,
           database: database,
-          success: function (model, results) {
-            // TODO: also check role-granted privileges
-            var privCheck = _.find(model.get("grantedPrivileges"), function (model) {
-              return model.privilege === "InstallExtension";
-            });
-            if (!privCheck) {
-              callback({message: "_insufficientPrivileges"});
-              return;
-            }
-            callback(); // success!
+          success: function (userModel, results) {
+            userModel.checkPrivilege("InstallExtension", database, callback);
           },
           error: function () {
-            callback({message: "_restoreError"});
+            callback({message: "_privilegeCheckError"});
           }
         });
       },
@@ -59,6 +51,13 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
           database: database,
           extension: path.join(__dirname, "../../node_modules", extensionName)
         }, callback);
+      },
+      // make the client-side assets immediately available to the webserver
+      // without the need for a datasource restart
+      useClientDir = function (callback) {
+        X.useClientDir("npm/" + extensionName + "/client",
+          path.join(__dirname, "../../node_modules", extensionName, "client"));
+        callback();
       };
 
     async.series([
@@ -66,16 +65,17 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
       validateUser,
       npmLoad,
       npmInstall,
-      buildExtension
+      buildExtension,
+      useClientDir
     ], function (err, results) {
       if (err) {
-        console.log(err);
         err.isError = true;
+        err.errorMessage = err.message;
         res.send(err);
         return;
       }
       console.log("all done");
-      res.send({data: "_success"});
+      res.send({data: "_success!"});
     });
   };
 }());
index b1c88a9..91cee16 100644 (file)
@@ -25,7 +25,6 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
     // is a function that returns another function, and express allows routes to
     // be defined in such a way as to chain these types of functions together in an array.
     ensureLogin = require('connect-ensure-login').ensureLoggedIn(logoutPath),
-    analysis = require('./analysis'),
     app = require('./app'),
     auth = require('./auth'),
     authorizeNet = require('./authorize-net'),
@@ -98,7 +97,6 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
   exports.installExtension = [ensureLogin, installExtension.installExtension];
   exports.locale = [ensureLogin, locale.locale];
   exports.redirect = redirector.redirect;
-  exports.analysis = [ensureLogin, analysis.analysis];
   exports.resetPassword = [ensureLogin, changePassword.resetPassword];
   exports.revokeOauthToken = [ensureLogin, revokeOauthToken.revokeToken];
   exports.vcfExport = [ensureLogin, vcfExport.vcfExport];
diff --git a/node-datasource/views/login/assets/grabbutton.png b/node-datasource/views/login/assets/grabbutton.png
deleted file mode 100644 (file)
index 1dc205a..0000000
Binary files a/node-datasource/views/login/assets/grabbutton.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/item-hilite.png b/node-datasource/views/login/assets/item-hilite.png
deleted file mode 100644 (file)
index 7e9c07d..0000000
Binary files a/node-datasource/views/login/assets/item-hilite.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/login-blue-scr.png b/node-datasource/views/login/assets/login-blue-scr.png
deleted file mode 100644 (file)
index 33ff19c..0000000
Binary files a/node-datasource/views/login/assets/login-blue-scr.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/login-logo.png b/node-datasource/views/login/assets/login-logo.png
deleted file mode 100644 (file)
index 02e187b..0000000
Binary files a/node-datasource/views/login/assets/login-logo.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/menu-icon-bookmark.png b/node-datasource/views/login/assets/menu-icon-bookmark.png
deleted file mode 100644 (file)
index 4c26d8f..0000000
Binary files a/node-datasource/views/login/assets/menu-icon-bookmark.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/menu-icon-export.png b/node-datasource/views/login/assets/menu-icon-export.png
deleted file mode 100644 (file)
index d309e06..0000000
Binary files a/node-datasource/views/login/assets/menu-icon-export.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/menu-icon-gear.png b/node-datasource/views/login/assets/menu-icon-gear.png
deleted file mode 100644 (file)
index b4027b2..0000000
Binary files a/node-datasource/views/login/assets/menu-icon-gear.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/menu-icon-help.png b/node-datasource/views/login/assets/menu-icon-help.png
deleted file mode 100644 (file)
index e8c0fbe..0000000
Binary files a/node-datasource/views/login/assets/menu-icon-help.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/menu-icon-lock.png b/node-datasource/views/login/assets/menu-icon-lock.png
deleted file mode 100644 (file)
index 5f7b79e..0000000
Binary files a/node-datasource/views/login/assets/menu-icon-lock.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/menu-icon-new.png b/node-datasource/views/login/assets/menu-icon-new.png
deleted file mode 100644 (file)
index 8725795..0000000
Binary files a/node-datasource/views/login/assets/menu-icon-new.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/menu-icon-refresh.png b/node-datasource/views/login/assets/menu-icon-refresh.png
deleted file mode 100644 (file)
index 2575608..0000000
Binary files a/node-datasource/views/login/assets/menu-icon-refresh.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/menu-icon-relation.png b/node-datasource/views/login/assets/menu-icon-relation.png
deleted file mode 100644 (file)
index 4823db2..0000000
Binary files a/node-datasource/views/login/assets/menu-icon-relation.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/menu-icon-search.png b/node-datasource/views/login/assets/menu-icon-search.png
deleted file mode 100644 (file)
index 4823db2..0000000
Binary files a/node-datasource/views/login/assets/menu-icon-search.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/relation-icon-search.png b/node-datasource/views/login/assets/relation-icon-search.png
deleted file mode 100644 (file)
index d409fd9..0000000
Binary files a/node-datasource/views/login/assets/relation-icon-search.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/search-input-search.png b/node-datasource/views/login/assets/search-input-search.png
deleted file mode 100644 (file)
index 36bf1b5..0000000
Binary files a/node-datasource/views/login/assets/search-input-search.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/setup-icon.png b/node-datasource/views/login/assets/setup-icon.png
deleted file mode 100644 (file)
index 721ad6e..0000000
Binary files a/node-datasource/views/login/assets/setup-icon.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/sliding-shadow.png b/node-datasource/views/login/assets/sliding-shadow.png
deleted file mode 100644 (file)
index 7b23803..0000000
Binary files a/node-datasource/views/login/assets/sliding-shadow.png and /dev/null differ
diff --git a/node-datasource/views/login/assets/sort-icon.png b/node-datasource/views/login/assets/sort-icon.png
deleted file mode 100644 (file)
index f45e253..0000000
Binary files a/node-datasource/views/login/assets/sort-icon.png and /dev/null differ
index 1ad7be3..8c37496 100644 (file)
@@ -2,7 +2,7 @@
   "author": "xTuple <dev@xtuple.com>",
   "name": "xtuple",
   "description": "xTuple Enterprise Resource Planning Mobile-Web client",
-  "version": "4.5.2",
+  "version": "4.6.0-beta",
   "repository": {
     "type": "git",
     "url": "https://github.com/xtuple/xtuple.git"
diff --git a/scripts/0 b/scripts/0
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/scripts/install_bi.sh b/scripts/install_bi.sh
deleted file mode 100755 (executable)
index ebb9291..0000000
+++ /dev/null
@@ -1,430 +0,0 @@
-#!/bin/sh
-RUN_DIR=$(pwd)
-LOG_FILE=$RUN_DIR/install_bi.log
-mv $LOG_FILE $LOG_FILE.old
-log() {
-       echo $@
-       echo $@ >> $LOG_FILE
-}
-
-varlog() {
-       log $(eval "echo $1 = \$$1")
-}
-
-cdir() {
-       cd $1
-       log "Changing directory to $1"
-}
-
-RUNALL=true
-BI_DIR=$RUN_DIR/../../bi
-PRIVATE_DIR=$RUN_DIR/../../private-extensions
-XT_DIR=$RUN_DIR/..
-export BISERVER_HOME=$RUN_DIR/../../ErpBI
-DATABASE=dev
-DATABASEHOST=localhost
-DATABASEUSER=admin
-DATABASEPASSWORD=admin
-DATABASEPORT=5432
-DATABASELOADPORT=5432
-DATABASESSL=
-TENANT=default
-CITIES=democities.txt
-COMMONNAME=$(hostname)
-CREATE=Y
-INCREMENTAL=N
-
-while getopts ":ieblmuxyd:U:g:P:t:n:j:z:h:p:c:o:r:k:" opt; do
-  case $opt in
-    e)
-      # Install ErpBI and configure
-      RUNALL=
-      DOWNLOAD=true
-      ;;
-    b)
-      # Build BI solution and install
-      RUNALL=
-      RUN=true
-      ;;
-    l)
-      # Extract and load analytic data into erpbi database
-      RUNALL=
-      LOAD=true
-      ;;
-    m)
-      # Prep the Mobile Client to connect to BI Server
-      RUNALL=
-      PREP=true
-      ;;
-    d)
-      # Set database name (for extract)
-      DATABASE=$OPTARG
-      ;;
-    p)
-      # Set database port for extract)
-      DATABASEPORT=$OPTARG
-      ;;
-    h)
-      # Set database host name (for extract)
-      DATABASEHOST=$OPTARG
-      ;;
-    U)
-      # Set database user name (for extract)
-      DATABASEUSER=$OPTARG
-      ;;
-    P)
-      # Set database user password (for extract)
-      DATABASEPASSWORD=$OPTARG
-      ;;
-    o)
-      # Set database port (for load)
-      DATABASELOADPORT=$OPTARG
-      ;;         
-    y)
-      # Set database ssl connect option (for extract)
-      DATABASESSL="?ssl=true\&sslfactory=org.postgresql.ssl.NonValidatingFactory"
-      ;;
-    k)
-      # Server key file for SSL
-      SSLKEY=$OPTARG
-      ;;
-    r)
-      # Server certificate file for SSL
-      SSLCERT=$OPTARG
-      ;;         
-    t)
-      # Set tenant name
-      TENANT=$OPTARG
-      ;;
-    g)
-      # Geographic data file
-      CITIES=$OPTARG
-      ;;         
-    n)
-      # Common name for self signed SSL certificate
-      COMMONNAME=$OPTARG
-      ;;
-    c)
-      # Path for config file
-      CONFIGPATH=$OPTARG
-      ;;
-    u)
-      # Incremental updates
-      INCREMENTAL=Y
-      ;;
-    j)
-      # Java home
-      export JAVA_HOME=$OPTARG
-      ;;
-    x)
-      # Do not create erpbi database schema
-      CREATE=N
-      ;;
-    z)
-      # ErpBI.zip path
-      export ERPBIPATH=$OPTARG
-      ;;  
-    \?)
-      log "Invalid option: -"$OPTARG
-      exit 1
-      ;;
-    :)
-      log "Option -"$OPTARG" requires an argument."
-      exit 1
-      ;;
-  esac
-done
-
-if [ $RUNALL ]
-then
-       DOWNLOAD=true
-       RUN=true
-       LOAD=true
-       PREP=true
-fi
-
-if  [ "$INCREMENTAL" = "Y" ]
-then
-       CREATE=N
-fi
-
-if  ! test -d $BI_DIR ;
-then
-       log ""
-       log "#############################################################"
-    log "Sorry bi folder not found.  You must clone xtuple/bi"
-       log "#############################################################"
-       log ""
-    exit 1
-fi
-
-if  [ $ERPBIPATH ]
-then
-       if   ! test -f $ERPBIPATH  
-       then
-               log ""
-               log "####################################################################################"
-               log "Sorry can't find ErpBI.zip at "$ERPBIPATH
-               log "####################################################################################"
-               log ""
-               exit 1
-       fi
-fi
-
-if  [ $CONFIGPATH ]
-then
-       if   ! test -f $CONFIGPATH  
-       then
-               log ""
-               log "####################################################################################"
-               log "Sorry can't find config file at "$CONFIGPATH
-               log "####################################################################################"
-               log ""
-               exit 1
-       fi
-else
-       CONFIGPATH=config.js
-fi
-
-if   ! test -d $PRIVATE_DIR && $RUNALL   
-then
-       log ""
-       log "####################################################################################"
-    log "Sorry private-extensions folder not found.  You must clone xtuple/private-extensions"
-       log "####################################################################################"
-       log ""
-    exit 1
-fi
-
-if  [ $SSLKEY ]
-then
-       if  ! [ $SSLCERT ]
-       then
-               log ""
-               log "####################################################################################"
-               log "Sorry if SSL Key is specified, SSL certificate must be specified."
-               log "####################################################################################"
-               log ""
-               exit 1
-       fi
-fi
-
-if  [ $SSLCERT ]
-then
-       if  ! [ $SSLKEY ]
-       then
-               log ""
-               log "####################################################################################"
-               log "Sorry if SSL certificate is specified, the SSL key must be specified."
-               log "####################################################################################"
-               log ""
-               exit 1
-       fi
-fi
-
-install_packages () {
-       log ""
-       log "######################################################"
-       log "Install prereqs."
-       log "######################################################"
-       log ""
-       apt-get install -qy git openjdk-6-jdk maven2
-       export JAVA_HOME=$(readlink -f /usr/bin/javac | sed "s:bin/javac::")    
-       if  ! test -e $JAVA_HOME/bin/javac ;
-       then
-               log ""
-               log "#############################################################"
-               log "Sorry can not find javac.  Set Java Home with the -j argument"
-               log "#############################################################"
-               log ""
-               exit 1
-       fi
-}
-
-download_files () {
-       log ""
-       log "######################################################"
-       log "Download ErpBI, set permissions and generate keystore "
-       log "and truststore for SSL with self signed cert using    "
-       log "common name "$COMMONNAME
-       log "######################################################"
-       log ""
-
-       rm -R ../../ErpBI       
-       if  [ $ERPBIPATH ]
-       then
-               log ""
-               log "######################################################"
-               log "Unzipping "$ERPBIPATH
-               log "######################################################"
-               log ""
-               unzip -q $ERPBIPATH -d ../..
-       else
-               rm ../../ErpBI.zip
-               wget http://sourceforge.net/projects/erpbi/files/candidate-release/ErpBI.zip/download -O ../../ErpBI.zip
-               log ""
-               log "######################################################"
-               log "Unzipping ErpBI.zip"
-               log "######################################################"
-               log ""
-               unzip -q ../../ErpBI.zip -d ../..
-       fi              
-
-       cdir $BISERVER_HOME/biserver-ce/
-       chmod 755 -R . 2>&1 | tee -a $LOG_FILE
-       
-       if  [ $SSLKEY ]
-       then
-               cp $SSLKEY $BISERVER_HOME/biserver-ce/ssl-keys
-               cp $SSLCERT $BISERVER_HOME/biserver-ce/ssl-keys
-               cdir $BISERVER_HOME/biserver-ce/ssl-keys
-               rm cacerts.jks
-               rm keystore_server.jks
-               rm server.cer
-               openssl pkcs12 -export -out server.pkcs12 -in $SSLCERT -inkey $SSLKEY -passout pass:changeit
-               keytool -importkeystore -srcstorepass changeit -deststorepass changeit -srckeystore server.pkcs12 -srcstoretype PKCS12 -destkeystore keystore_server.jks -deststoretype JKS 
-               keytool -export -alias 1 -file $SSLCERT -storepass changeit -keystore keystore_server.jks
-               keytool -import -alias 1 -v -trustcacerts -file $SSLCERT -keypass changeit -storepass changeit -keystore cacerts.jks -noprompt  
-       else
-               cdir $BISERVER_HOME/biserver-ce/ssl-keys
-               rm cacerts.jks
-               rm keystore_server.jks
-               rm server.cer
-               keytool -genkey -alias tomcat -keyalg RSA -keypass changeit -storepass changeit -keystore keystore_server.jks -dname "cn="$COMMONNAME", ou=xTuple, o=xTuple, c=US"
-               keytool -export -alias tomcat -file server.cer -storepass changeit -keystore keystore_server.jks
-               keytool -import -alias tomcat -v -trustcacerts -file server.cer -keypass changeit -storepass changeit -keystore cacerts.jks -noprompt   
-       fi
-       
-       cdir $BISERVER_HOME/biserver-ce/tomcat/conf/Catalina/localhost
-       mv pentaho.xml pentaho.xml.sample
-       cat pentaho.xml.sample | \
-       sed s/org.h2.Driver/org.postgresql.Driver/ | \
-       sed s#jdbc:h2:../../../h2database/demomfg#jdbc:postgresql://localhost:5432/erpbi# \
-       > pentaho.xml  2>&1 | tee -a $LOG_FILE
-}
-
-run_scripts() {
-       log ""
-       log "######################################################"
-       log "Build BI solution and move to ErpBI at:               "
-       log $BISERVER_HOME
-       log "######################################################"
-       log ""
-       cdir $BI_DIR/olap-schema
-       mvn install 2>&1 | tee -a $LOG_FILE
-       java -jar Saxon-HE-9.4.jar -s:src/erpi-tenant-xtuple.xml -xsl:style.xsl -o:target/erpi-schema.xml
-       mvn process-resources 2>&1 | tee -a $LOG_FILE
-
-       cdir ../pentaho-extensions/oauthsso
-       mvn clean 2>&1 | tee -a $LOG_FILE
-       mvn install 2>&1 | tee -a $LOG_FILE
-       mvn process-resources 2>&1 | tee -a $LOG_FILE
-
-       cdir ../dynschema
-       mvn install 2>&1 | tee -a $LOG_FILE
-       mvn process-resources 2>&1 | tee -a $LOG_FILE
-       
-       cdir ../utils
-       mvn install 2>&1 | tee -a $LOG_FILE
-       mvn process-resources 2>&1 | tee -a $LOG_FILE
-
-       cdir ../../etl
-       mvn install 2>&1 | tee -a $LOG_FILE
-       mvn process-resources 2>&1 | tee -a $LOG_FILE
-}
-
-load_pentaho() {
-       log ""
-       log "######################################################"
-       log "Extract data from "$DATABASE" on host "$DATABASEHOST 
-       log "and load data into erpbi with tenant name " $TENANT
-       log "######################################################"
-       log ""
-       createdb -U postgres -O admin erpbi 2>&1 | tee -a $LOG_FILE
-       cdir $BISERVER_HOME/data-integration
-       export KETTLE_HOME=properties/psg-linux
-       
-       mv $KETTLE_HOME/.kettle/kettle.properties $KETTLE_HOME/.kettle/kettle.properties.sample  2>&1 | tee -a $LOG_FILE
-       cat $KETTLE_HOME/.kettle/kettle.properties.sample | \
-       sed s'#erpi.source.url=.*#erpi.source.url=jdbc\:postgresql\://'$DATABASEHOST'\:'$DATABASEPORT'/'$DATABASE$DATABASESSL'#' | \
-       sed s'#erpi.source.user=.*#erpi.source.user='$DATABASEUSER'#' | \
-       sed s'#erpi.source.password.*#erpi.source.password='$DATABASEPASSWORD'#' | \
-       sed s'#erpi.datamart.port.*#erpi.datamart.port='$DATABASELOADPORT'#' | \
-       sed s'#erpi.datamart.url=.*#erpi.datamart.url=jdbc\:postgresql\://localhost\:'$DATABASELOADPORT'/erpbi#' | \
-       sed s'#erpi.cities.file.*#erpi.cities.file='$CITIES'#' | \
-       sed s'#erpi.tenant.id=.*#erpi.tenant.id='$TENANT'.'$DATABASE'#' | \
-       sed s'#erpi.datamart.create=.*#erpi.datamart.create='$CREATE'#' | \
-       sed s'#erpi.incremental=.*#erpi.incremental='$INCREMENTAL'#' \
-       > $KETTLE_HOME/.kettle/kettle.properties  2>&1 | tee -a $LOG_FILE
-       
-       sh kitchenkh.sh -file=../ErpBI/ETL/JOBS/Load.kjb -level=Basic
-}
-
-prep_mobile() {
-       log ""
-       log "######################################################"
-       log "Prepare mobile app to use the BI Server. Create keys  "
-    log "for REST api used by single sign on.  Update config.js"
-    log "with BI Server URL https://"$COMMONNAME":8443"
-       log "######################################################"
-       log ""
-       if [ ! -d $XT_DIR/node-datasource/lib/rest-keys ]
-    then
-      mkdir $XT_DIR/node-datasource/lib/rest-keys
-    fi
-       cdir $XT_DIR/node-datasource/lib/rest-keys
-       openssl genrsa -out server.key 1024 2>&1 | tee -a $LOG_FILE
-       openssl rsa -in server.key -pubout > server.pub 2>&1 | tee -a $LOG_FILE
-       
-       #
-       # Would be better to get multiline sed working to put commonname in:
-       # biserver: {
-       #    hostname: myname
-       #
-       # Something similar to:
-       #       sed 'N;s#biServer: {\n        hostname:.*#biServer: {\n        hostname: \"'$COMMONNAME'\",#' \
-       
-       cdir $XT_DIR/node-datasource
-       mv $CONFIGPATH $CONFIGPATH'.old' 2>&1 | tee -a $LOG_FILE
-       cat $CONFIGPATH'.old' | \
-       sed 's#restkeyfile: .*#restkeyfile: \"./lib/rest-keys/server.key\",#' | \
-       sed 's#tenantname: .*#tenantname: \"'$TENANT'",#' | \
-       sed 's#bihost: .*#bihost: \"'$COMMONNAME'\",#' \
-       > $CONFIGPATH
-}
-
-install_packages
-
-if [ $? -ne 0 ]
-then
-       log "bad."
-fi
-
-if [ $DOWNLOAD ]
-then
-       download_files
-fi
-
-if [ $RUN ]
-then
-       run_scripts
-fi
-
-if [ $LOAD ]
-then
-       load_pentaho
-fi
-
-if [ $PREP ]
-then
-       prep_mobile
-fi
-
-log ""
-log "######################################################"
-log "                FINISED! READ ME                      "
-log "If you use the self signed certificate created by this"
-log "script you will need to accept the certificate in your"
-log "browser.  Connect to https://"$COMMONNAME":8443"
-log "######################################################"
-log ""
index 24b40e3..611107a 100644 (file)
@@ -36,79 +36,42 @@ var _ = require('underscore'),
     var buildSpecs = {},
       databases = [],
       extension,
-      //
-      // Looks in a database to see which extensions are registered, and
-      // tacks onto that list the core directories.
-      //
       getRegisteredExtensions = function (database, callback) {
-        var result,
-          credsClone = JSON.parse(JSON.stringify(creds)),
-          existsSql = "select relname from pg_class where relname = 'ext'",
-          preInstallSql = "select xt.js_init();update xt.ext set ext_location = '/core-extensions' " +
-            "where ext_name = 'oauth2' and ext_location = '/xtuple-extensions';",
-          extSql = preInstallSql + "SELECT * FROM xt.ext ORDER BY ext_load_order",
-          defaultExtensions = [
-            { ext_location: '/core-extensions', ext_name: 'crm' },
-            { ext_location: '/core-extensions', ext_name: 'project' },
-            { ext_location: '/core-extensions', ext_name: 'sales' },
-            { ext_location: '/core-extensions', ext_name: 'billing' },
-            { ext_location: '/core-extensions', ext_name: 'purchasing' },
-            { ext_location: '/core-extensions', ext_name: 'oauth2' }
-          ],
-          adaptExtensions = function (err, res) {
-            if (err) {
-              callback(err);
-              return;
-            }
-
-            var paths = _.map(_.compact(res.rows), function (row) {
-              var location = row.ext_location,
-                name = row.ext_name,
-                extPath;
-
-              if (location === '/core-extensions') {
-                extPath = path.join(__dirname, "/../../enyo-client/extensions/source/", name);
-              } else if (location === '/xtuple-extensions') {
-                extPath = path.join(__dirname, "../../../xtuple-extensions/source", name);
-              } else if (location === '/private-extensions') {
-                extPath = path.join(__dirname, "../../../private-extensions/source", name);
-              } else if (location === 'npm') {
-                extPath = path.join(__dirname, "../../node_modules", name);
-              }
-              return extPath;
-            });
-
-            paths.unshift(path.join(__dirname, "../../enyo-client")); // core path
-            paths.unshift(path.join(__dirname, "../../lib/orm")); // lib path
-            paths.unshift(path.join(__dirname, "../../foundation-database")); // foundation path
-            callback(null, {
-              extensions: _.compact(paths),
-              database: database,
-              keepSql: options.keepSql,
-              populateData: options.populateData,
-              wipeViews: options.wipeViews,
-              clientOnly: options.clientOnly,
-              databaseOnly: options.databaseOnly
-            });
-          };
-
+        var credsClone = JSON.parse(JSON.stringify(creds));
         credsClone.database = database;
-        dataSource.query(existsSql, credsClone, function (err, res) {
-          if (err) {
-            callback(err);
-            return;
-          }
-          if (res.rowCount === 0) {
-            // xt.ext doesn't exist, because this is probably a brand-new DB.
-            // No problem! Give them the core extensions.
-            adaptExtensions(null, { rows: defaultExtensions });
-          } else {
-            dataSource.query(extSql, credsClone, adaptExtensions);
-          }
+        buildDatabaseUtil.inspectDatabaseExtensions(credsClone, function (err, paths) {
+          callback(null, {
+            extensions: paths,
+            database: database,
+            keepSql: options.keepSql,
+            populateData: options.populateData,
+            wipeViews: options.wipeViews,
+            clientOnly: options.clientOnly,
+            databaseOnly: options.databaseOnly
+          });
         });
       },
       buildAll = function (specs, creds, buildAllCallback) {
         async.series([
+          function (done) {
+            // step 0: init the database, if requested
+
+            if (specs.length === 1 &&
+                specs[0].initialize &&
+                (specs[0].backup || specs[0].source)) {
+
+              // The user wants to initialize the database first (i.e. Step 0)
+              // Do that, then call this function again
+              buildDatabaseUtil.initDatabase(specs[0], creds, function (err, res) {
+                specs[0].wasInitialized = true;
+                done(err, res);
+              });
+              return;
+            } else {
+              done();
+            }
+
+          },
           function (done) {
             // step 1: npm install extension if necessary
             // an alternate approach would be only npm install these
@@ -118,7 +81,7 @@ var _ = require('underscore'),
               return _.flatten(memo);
             }, []);
             var npmExtensions = _.filter(allExtensions, function (extName) {
-              return extName.indexOf("node_modules") >= 0;
+              return extName && extName.indexOf("node_modules") >= 0;
             });
             if (npmExtensions.length === 0) {
               done();
@@ -165,13 +128,10 @@ var _ = require('underscore'),
       },
       config;
 
-    // the config path is not relative if it starts with a slash
-    if (options.config && options.config.substring(0, 1) === '/') {
-      config = require(options.config);
-    } else if (options.config) {
-      config = require(path.join(process.cwd(), options.config));
+    if (options.config) {
+      config = require(path.resolve(process.cwd(), options.config));
     } else {
-      config = require(path.join(__dirname, "../../node-datasource/config.js"));
+      config = require(path.resolve(__dirname, "../../node-datasource/config.js"));
     }
     creds = config.databaseServer;
     creds.encryptionKeyFile = config.datasource.encryptionKeyFile;
@@ -193,6 +153,9 @@ var _ = require('underscore'),
     } else if (options.backup && options.source) {
       callback("You can build from backup or from source but not both.");
 
+    } else if (options.backup && options.extension) {
+      callback("When you're building from a backup you get whatever extensions the backup merits.");
+
     } else if (options.initialize &&
         (options.backup || options.source) &&
         options.database &&
@@ -203,16 +166,17 @@ var _ = require('underscore'),
 
       buildSpecs.database = options.database;
       if (options.backup) {
-        // the backup path is not relative if it starts with a slash
-        buildSpecs.backup = options.backup.substring(0, 1) === '/' ?
-          options.backup :
-          path.join(process.cwd(), options.backup);
+        buildSpecs.backup = path.resolve(process.cwd(), options.backup);
+        buildSpecs.extensions = false;
+        // we'll determine the extensions by looking at the db after restore
       }
       if (options.source) {
-        // the source path is not relative if it starts with a slash
-        buildSpecs.source = options.source.substring(0, 1) === '/' ?
-          options.source :
-          path.join(process.cwd(), options.source);
+        buildSpecs.source = path.resolve(process.cwd(), options.source);
+        // if we initialize with the foundation, that means we want
+        // an unmobilized build
+        buildSpecs.extensions = options.extension ?
+          [options.extension] :
+          buildDatabaseUtil.defaultExtensions;
       }
       buildSpecs.initialize = true;
       buildSpecs.keepSql = options.keepSql;
@@ -220,19 +184,6 @@ var _ = require('underscore'),
       buildSpecs.wipeViews = options.wipeViews;
       buildSpecs.clientOnly = options.clientOnly;
       buildSpecs.databaseOnly = options.databaseOnly;
-      // if we initialize with the foundation, that means we want
-      // an unmobilized build
-      buildSpecs.extensions = options.extension ? [options.extension] : [
-        path.join(__dirname, '../../foundation-database'),
-        path.join(__dirname, '../../lib/orm'),
-        path.join(__dirname, '../../enyo-client'),
-        path.join(__dirname, '../../enyo-client/extensions/source/crm'),
-        path.join(__dirname, '../../enyo-client/extensions/source/project'),
-        path.join(__dirname, '../../enyo-client/extensions/source/sales'),
-        path.join(__dirname, '../../enyo-client/extensions/source/billing'),
-        path.join(__dirname, '../../enyo-client/extensions/source/purchasing'),
-        path.join(__dirname, '../../enyo-client/extensions/source/oauth2')
-      ];
       buildAll([buildSpecs], creds, callback);
 
     } else if (options.initialize || options.backup || options.source) {
@@ -244,10 +195,7 @@ var _ = require('underscore'),
       // the user has specified an extension to build or unregister
       // extensions are assumed to be specified relative to the cwd
       buildSpecs = _.map(databases, function (database) {
-        // the extension is not relative if it starts with a slash
-        var extension = options.extension.substring(0, 1) === '/' ?
-          options.extension :
-          path.join(process.cwd(), options.extension);
+        var extension = path.resolve(process.cwd(), options.extension);
         return {
           database: database,
           frozen: options.frozen,
index d86e089..aadd5d0 100644 (file)
@@ -44,29 +44,6 @@ var  async = require('async'),
         host: 'localhost' }
   */
   var buildDatabase = exports.buildDatabase = function (specs, creds, masterCallback) {
-    if (specs.length === 1 &&
-        specs[0].initialize &&
-        (specs[0].backup || specs[0].source)) {
-
-      // The user wants to initialize the database first (i.e. Step 0)
-      // Do that, then call this function again
-      buildDatabaseUtil.initDatabase(specs[0], creds, function (err, res) {
-        if (err) {
-          winston.error("Init database error: ", err);
-          masterCallback(err);
-          return;
-        }
-        // recurse to do the build step. Of course we don't want to initialize a second
-        // time, so destroy those flags.
-        specs[0].initialize = false;
-        specs[0].wasInitialized = true;
-        specs[0].backup = undefined;
-        specs[0].source = undefined;
-        buildDatabase(specs, creds, masterCallback);
-      });
-      return;
-    }
-
     //
     // The function to generate all the scripts for a database
     //
@@ -82,7 +59,6 @@ var  async = require('async'),
           extensionCallback(null, "");
           return;
         }
-        //console.log("Installing extension", databaseName, extension);
         // deal with directory structure quirks
         var baseName = path.basename(extension),
           isFoundation = extension.indexOf("foundation-database") >= 0,
index e2da5e7..c8f5ca5 100644 (file)
@@ -14,8 +14,17 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
     dataSource = require('../../node-datasource/lib/ext/datasource').dataSource,
     winston = require('winston');
 
-
-
+  var defaultExtensions = [
+    path.join(__dirname, '../../foundation-database'),
+    path.join(__dirname, '../../lib/orm'),
+    path.join(__dirname, '../../enyo-client'),
+    path.join(__dirname, '../../enyo-client/extensions/source/crm'),
+    path.join(__dirname, '../../enyo-client/extensions/source/project'),
+    path.join(__dirname, '../../enyo-client/extensions/source/sales'),
+    path.join(__dirname, '../../enyo-client/extensions/source/billing'),
+    path.join(__dirname, '../../enyo-client/extensions/source/purchasing'),
+    path.join(__dirname, '../../enyo-client/extensions/source/oauth2')
+  ];
 
   var convertFromMetasql = function (content, filename, defaultSchema) {
     var lines = content.split("\n"),
@@ -376,6 +385,71 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
     });
   };
 
+  var pathFromExtension = function (name, location) {
+    if (location === '/core-extensions') {
+      return path.join(__dirname, "/../../enyo-client/extensions/source/", name);
+    } else if (location === '/xtuple-extensions') {
+      return path.join(__dirname, "../../../xtuple-extensions/source", name);
+    } else if (location === '/private-extensions') {
+      return path.join(__dirname, "../../../private-extensions/source", name);
+    } else if (location === 'npm') {
+      return path.join(__dirname, "../../node_modules", name);
+    }
+  };
+
+  //
+  // Looks in a database to see which extensions are registered, and
+  // tacks onto that list the core directories.
+  //
+  var inspectMobilizedDatabase = function (creds, done) {
+    var extSql = "SELECT * FROM xt.ext ORDER BY ext_load_order";
+    dataSource.query(extSql, creds, function (err, res) {
+      if (err) {
+        return done(err);
+      }
+
+      var paths = _.map(_.compact(res.rows), function (row) {
+        return pathFromExtension(row.ext_name, row.ext_location);
+      });
+
+      paths.unshift(path.join(__dirname, "../../enyo-client")); // core path
+      paths.unshift(path.join(__dirname, "../../lib/orm")); // lib path
+      paths.unshift(path.join(__dirname, "../../foundation-database")); // foundation path
+      done(null, _.compact(paths));
+    });
+  };
+
+  var inspectUnmobilizedDatabase = function (creds, done) {
+    var extSql = "select * from public.pkghead where pkghead_name in ('xtmfg', 'xwd');",
+      editionMap = {
+        xtmfg: ["inventory", "manufacturing"],
+        xwd: ["inventory", "distribution"]
+      };
+    dataSource.query(extSql, creds, function (err, res) {
+      if (err) {
+        return done(err);
+      }
+      var extensions = _.unique(_.flatten(_.map(res.rows, function (row) {
+        return _.map(editionMap[row.pkghead_name], function (ext) {
+          return path.join(__dirname, "../../../private-extensions/source", ext);
+        });
+      })));
+      done(err, defaultExtensions.concat(extensions));
+    });
+  };
+
+  var inspectDatabaseExtensions = function (creds, done) {
+    var isMobilizedSql = "select * from information_schema.tables where table_schema = 'xt' and table_name = 'ext';";
+
+    dataSource.query(isMobilizedSql, creds, function (err, res) {
+      if (res.rowCount === 0) {
+        inspectUnmobilizedDatabase(creds, done);
+      } else {
+        inspectMobilizedDatabase(creds, done);
+      }
+    });
+  };
+
   //
   // Step 0 (optional, triggered by flags), wipe out the database
   // and load it from scratch using pg_restore something.backup unless
@@ -416,7 +490,7 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
           if (err) {
             console.log("ignoring restore db error", err);
           }
-          callback(null, res);
+          done(null, res);
         });
       },
       finish = function (err, results) {
@@ -437,7 +511,15 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
       async.series([
         dropDatabase,
         createDatabase,
-        restoreBackup
+        restoreBackup,
+        function (done) {
+          credsClone.database = databaseName;
+          inspectDatabaseExtensions(credsClone, function (err, paths) {
+            // in the case of a build-from-backup, we ignore any user desires and dictate the extensions
+            spec.extensions = paths;
+            done();
+          });
+        }
       ], finish);
     }
   };
@@ -524,6 +606,8 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
     async.each(specs, unregisterEach, masterCallback);
   };
 
+  exports.defaultExtensions = defaultExtensions;
+  exports.inspectDatabaseExtensions = inspectDatabaseExtensions;
   exports.explodeManifest = explodeManifest;
   exports.initDatabase = initDatabase;
   exports.sendToDatabase = sendToDatabase;
diff --git a/scripts/start_bi.sh b/scripts/start_bi.sh
deleted file mode 100755 (executable)
index 491a3f4..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/sh
-LOG_FILE='start_bi.log'
-log() {
-       echo $@
-       echo $@ >> $LOG_FILE
-}
-cdir() {
-       cd $1
-       log "Changing directory to $1"
-}
-log ""
-log "######################################################"
-log "######################################################"
-log "Start BI server listening on port 8843                "
-log "######################################################"
-log "######################################################"
-log ""
-cdir ../../ErpBI/biserver-ce/
-./start-pentaho.sh
-
-log ""
-log "######################################################"
-log "######################################################"
-log "Refresh repository and clear OLAP Cache.             "
-log "######################################################"
-log "######################################################"
-log ""
-
-sleep 10
-
-wget -O tempresponse.txt "http://localhost:8080/pentaho/Publish?publish=now&class=org.pentaho.platform.engine.services.solution.SolutionPublisher&userid=admin&password=Car54WhereRU"
-rm tempresponse.txt
-wget -O tempresponse.txt "http://localhost:8080/pentaho/ViewAction?solution=admin&path=&action=clear_mondrian_schema_cache.xaction&userid=admin&password=Car54WhereRU"
-rm tempresponse.txt
\ No newline at end of file
diff --git a/scripts/stop_bi.sh b/scripts/stop_bi.sh
deleted file mode 100755 (executable)
index 857603b..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-LOG_FILE='stop_bi.log'
-log() {
-       echo $@
-       echo $@ >> $LOG_FILE
-}
-cdir() {
-       cd $1
-       log "Changing directory to $1"
-}
-log ""
-log "######################################################"
-log "######################################################"
-log "Stop BI server listening on port 8843                "
-log "######################################################"
-log "######################################################"
-log ""
-cdir ../../ErpBI/biserver-ce/
-./stop-pentaho.sh
-
index 4853c2b..f41f001 100644 (file)
@@ -1,5 +1,5 @@
-<package id        = "distribution-install-452"
-         version   = "4.5.2"
+<package id        = "distribution-install-460-beta"
+         version   = "4.6.0Beta"
          developer = "xTuple"
          descrip   = "load PostBooks resources"
          updater   = "2.2.4" >
@@ -19,8 +19,8 @@
 
   <prerequisite type = "query"
                 name = "Checking for bad xTuple ERP database version" >
-    <query>SELECT NOT fetchMetricText('ServerVersion') > '4.5.2' AND fetchMetricText('ServerVersion')!='4.5.0Beta' AND fetchMetricText('ServerVersion')!='4.5.0RC';</query>
-    <message>This package may not be applied to a 4.5.2+ PostBooks database.
+    <query>SELECT NOT fetchMetricText('ServerVersion') >= '4.6.0' AND fetchMetricText('ServerVersion')!='4.6.0-beta' AND fetchMetricText('ServerVersion')!='4.6.0RC';</query>
+    <message>This package may not be applied to a 4.6.0+ PostBooks database.
     </message>
   </prerequisite>
 
index 73c3541..3e8af87 100644 (file)
@@ -1,5 +1,5 @@
-<package id        = "distribution-upgrade-452"
-         version   = "4.5.2"
+<package id        = "distribution-upgrade-460-beta"
+         version   = "4.6.0Beta"
          developer = "xTuple"
          descrip   = "load PostBooks resources"
          updater   = "2.2.4" >
@@ -19,8 +19,8 @@
 
  <prerequisite type = "query"
                name = "Checking for bad xTuple ERP database version" >
-<query>SELECT NOT fetchMetricText('ServerVersion') > '4.5.2' AND fetchMetricText('ServerVersion')!='4.5.0Beta' AND fetchMetricText('ServerVersion')!='4.5.0RC';</query>
-    <message>This package may not be applied to a 4.5.2+ Distribution database.
+<query>SELECT NOT fetchMetricText('ServerVersion') >= '4.6.0' AND fetchMetricText('ServerVersion')!='4.6.0-beta' AND fetchMetricText('ServerVersion')!='4.6.0RC';</query>
+    <message>This package may not be applied to a 4.6.0+ Distribution database.
     </message>
   </prerequisite>
 
index 139692b..e36fe0e 100644 (file)
@@ -1,5 +1,5 @@
-<package id        = "postbooks-upgrade-452"
-         version   = "4.5.2"
+<package id        = "postbooks-upgrade-460-beta"
+         version   = "4.6.0Beta"
          developer = "xTuple"
          descrip   = "load PostBooks resources"
          updater   = "2.2.4" >
@@ -19,8 +19,8 @@
 
   <prerequisite type = "query"
                 name = "Checking for bad xTuple ERP database version" >
-    <query>SELECT NOT fetchMetricText('ServerVersion') > '4.5.2' AND fetchMetricText('ServerVersion')!='4.5.0Beta' AND fetchMetricText('ServerVersion')!='4.5.0RC';</query>
-    <message>This package may not be applied to a 4.5.2+ Postbooks database.
+    <query>SELECT NOT fetchMetricText('ServerVersion') >= '4.6.0' AND fetchMetricText('ServerVersion')!='4.6.0-beta' AND fetchMetricText('ServerVersion')!='4.6.0RC';</query>
+    <message>This package may not be applied to a 4.6.0+ Postbooks database.
     </message>
   </prerequisite>
 
index 35c516f..0ae18bc 100644 (file)
@@ -1,5 +1,5 @@
-<package id        = "manufacturing-install-452"
-         version   = "4.5.2"
+<package id        = "manufacturing-install-460-beta"
+         version   = "4.6.0Beta"
          developer = "xTuple"
          descrip   = "load PostBooks resources"
          updater   = "2.2.4" >
@@ -19,8 +19,8 @@
 
   <prerequisite type = "query"
                 name = "Checking for bad xTuple ERP database version" >
-    <query>SELECT NOT fetchMetricText('ServerVersion') > '4.5.2' AND fetchMetricText('ServerVersion')!='4.5.0Beta' AND fetchMetricText('ServerVersion')!='4.5.0RC';</query>
-    <message>This package may not be applied to a 4.5.2+ PostBooks database.
+    <query>SELECT NOT fetchMetricText('ServerVersion') >= '4.6.0' AND fetchMetricText('ServerVersion')!='4.6.0-beta' AND fetchMetricText('ServerVersion')!='4.6.0RC';</query>
+    <message>This package may not be applied to a 4.6.0+ Distribution database.
     </message>
   </prerequisite>
 
index 47f5068..09594d2 100644 (file)
@@ -1,5 +1,5 @@
-<package id        = "manufacturing-upgrade-452"
-         version   = "4.5.2"
+<package id        = "manufacturing-upgrade-460-beta"
+         version   = "4.6.0Beta"
          developer = "xTuple"
          descrip   = "load PostBooks resources"
          updater   = "2.2.4" >
@@ -30,8 +30,8 @@
 
 <prerequisite type = "query"
                name = "Checking for bad xTuple ERP database version" >
-<query>SELECT NOT fetchMetricText('ServerVersion') > '4.5.2' AND fetchMetricText('ServerVersion')!='4.5.0Beta' AND fetchMetricText('ServerVersion')!='4.5.0RC';</query>
-    <message>This package may not be applied to a 4.5.2+ Manufacturing database.
+<query>SELECT NOT fetchMetricText('ServerVersion') > '4.6.0-beta9' AND fetchMetricText('ServerVersion') != '4.6.0';</query>
+    <message>This package may not be applied to a 4.6.0+ Manufacturing database.
     </message>
 </prerequisite>
 
diff --git a/test/database/bankrec.js b/test/database/bankrec.js
new file mode 100644 (file)
index 0000000..dbd76a4
--- /dev/null
@@ -0,0 +1,1398 @@
+/*jshint trailing:true, white:true, indent:2, strict:true, curly:true,
+  immed:true, eqeqeq:true, forin:true, latedef:true,
+  newcap:true, noarg:true, undef:true */
+/*global XT:true, describe:true, it:true, require:true, __dirname:true, before:true, console:true */
+
+/* note: much of this test consists of SETUP for testing tax handling.
+ * bank reconciliation when cash-based taxation is enabled
+ * is supposed to create taxpay and corresponding gltrans records.
+ * to test this, we need to turn on cashed-based taxation,
+ * generate both a/r and a/p transactions, reconcile the bank
+ * statement, and check the tax history.
+ * reopening the bankrec is supposed to reverse these transactions.
+ */
+
+// TODO: add use of sltrans as well as gltrans
+var _    = require("underscore"),
+  assert = require('chai').assert,
+  path   = require('path');
+
+(function () {
+  "use strict";
+
+  // TODO: implement a real metasql parser; this one is stupid and minimal
+  var mqlToSql = function (query, params) {
+    var result = _.clone(query);
+    _.each(params, function (value, key) {
+      var valueRE = new RegExp("<\\? *value\\(['\"]"   + key + "['\"]\\) *\\?>", "g"),
+        literalRE = new RegExp("<\\? *literal\\(['\"]" + key + "['\"]\\) *\\?>", "g");
+      if (_.isNumber(value)) {
+        result = result.replace(valueRE, value);
+      } else {
+        result = result.replace(valueRE, "'" + value + "'");
+      }
+      result = result.replace(literalRE, value);
+    });
+    return result;
+  };
+
+  describe('test bank reconciliation functions', function () {
+
+    var loginData = require("../lib/login_data.js").data,
+      datasource = require('../../../xtuple/node-datasource/lib/ext/datasource').dataSource,
+      config = require(path.join(__dirname, "../../node-datasource/config.js")),
+      creds  = _.extend({}, config.databaseServer, {database: loginData.org}),
+      start  = new Date(),
+      testTag = 'bankrec test ' + start.toLocaleTimeString(),
+      closeEnough = 0.006,
+      bankaccnt,
+      bankadj = { amount: 54.32 },
+      bankrec,
+      apcheck = {}, apcheckitem,
+      aropen,
+      badTaxIds,
+      cashrcpt,
+      cm,
+      cobmisc = {},
+      cohead, coitem,
+      invchead = {},
+      pohead, poitem,
+      recvid,
+      voucher, voitem,
+      vomisc = { amount: 67.89 },
+      votax,
+      voitemtax,
+      vomisctax,
+      wasCashBasedTax,
+      bankRecItemSql = 'SELECT * FROM bankrecitem '             +
+                       ' WHERE bankrecitem_bankrec_id=<? value("brid") ?>'   +
+                       '   AND bankrecitem_source=<? value("src") ?>'        +
+                       '   AND bankrecitem_source_id <? literal("srcid") ?>;',
+      toggleCheckSql = "SELECT toggleBankRecCleared(<? value('bankrecid') ?>,'GL'," +
+                       "  gltrans_id, checkhead_curr_rate, checkhead_amount)"  +
+                       "  AS result"                                           +
+                       " FROM checkhead JOIN gltrans ON (gltrans_doctype='CK'" +
+                       "                    AND gltrans_misc_id=checkhead_id)" +
+                       " WHERE checkhead_id=<? value('checkid') ?>"            +
+                       "   AND gltrans_amount > 0;",
+      postCheckSql  = "SELECT postCheck(<? value('id') ?>, NULL) AS result;",
+      checkCheckSql = "SELECT *,"                                              +
+                     "       bankrecitem_amount/bankrecitem_curr_rate AS base" +
+                     " FROM gltrans"                                           +
+                     " JOIN bankrecitem ON (gltrans_id=bankrecitem_source_id)" +
+                     " JOIN bankrec    ON (bankrecitem_bankrec_id=bankrec_id)" +
+                     " WHERE gltrans_doctype='CK'"                             +
+                     "   AND gltrans_misc_id=<? value('checkid') ?>"           +
+                     "   AND bankrec_id=<? value('bankrecid') ?>;",
+      bankAdjCheckSql = "SELECT *,"                                           +
+                  " ROUND(bankadj_amount / bankadj_curr_rate, 2) AS baseamt," +
+                  " CASE WHEN gltrans_id IS NULL AND bankrecitem_id IS NULL"  +
+                  "      THEN 0"                                              +
+                  "      WHEN gltrans_id IS NULL OR bankrecitem_id IS NULL"   +
+                  "      THEN 1"                                              +
+                  "      ELSE 2 END AS preferred"                             +
+                  "  FROM bankadj LEFT OUTER"                                 +
+                  "  JOIN gltrans   ON (gltrans_doctype='AD'"                 +
+                  "                 AND gltrans_misc_id=bankadj_id)"          +
+                  "  LEFT OUTER"                                              +
+                  "  JOIN bankrecitem ON (bankrecitem_source='AD'"            +
+                  "                   AND bankrecitem_source_id=bankadj_id)"  +
+                  "                   OR (bankrecitem_source='GL'"            +
+                  "                   AND bankrecitem_source_id=gltrans_id)"  +
+                  " WHERE bankadj_id=<? value('bankadjid') ?>"                +
+                  " ORDER BY preferred DESC LIMIT 1;",
+      getBankRecSql = "SELECT * FROM bankrec WHERE bankrec_id=<? value('id') ?>;",
+      bankRecGLSql = "SELECT gltrans.* "                                       +
+                     "  FROM gltrans"                                          +
+                     "  JOIN bankrecitem ON gltrans_id=bankrecitem_source_id"  +
+                     "                  AND bankrecitem_source='GL'"           +
+                     " WHERE bankrecitem_bankrec_id=<? value('bankrecid') ?>"  +
+                     "   AND bankrecitem_cleared;",
+      postBankRecSql = "SELECT postBankReconciliation(<? value('id') ?>)"  +
+                       "    AS result;",
+      gltransCheckSql = "SELECT COUNT(*) AS cnt FROM gltrans;", // TODO: make smarter
+      taxinfoCheckSql = "SELECT * FROM <? literal('taxhist') ?>"   +
+                        "  LEFT OUTER JOIN taxpay"                 +
+                        "       ON (taxpay_taxhist_id=taxhist_id)" +
+                        " WHERE taxhist_parent_id=<? value('taxparent') ?>;",
+      cohisttaxinfoSql = "SELECT * FROM cohisttax"                             +
+                         "  JOIN cohist ON taxhist_parent_id=cohist_id"        +
+                         "  LEFT OUTER JOIN taxpay"                            +
+                         "       ON taxhist_id=taxpay_taxhist_id"              +
+                         " WHERE cohist_itemsite_id=<? value('itemsite') ?>"   +
+                         "   AND cohist_ordernumber=<? value('cohead') ?>;",
+      lastGltransCount = 0
+    ;
+
+    // set up /////////////////////////////////////////////////////////////////
+
+    it("patches tax accts to ensure tax handling /can/ work", function (done) {
+      var sql = "UPDATE tax SET tax_dist_accnt_id ="            +
+                " (SELECT accnt_id FROM accnt"                  +
+                "   WHERE accnt_descrip = 'Accounts Payable')"  +
+                " WHERE tax_dist_accnt_id IS NULL"              +
+                " RETURNING tax_id;";
+      datasource.query(sql, creds, function (err, res) {
+        assert.isNull(err);
+        badTaxIds = _.map(res.rows, function (v) { return v.tax_id; });
+        done();
+      });
+    });
+
+    it('looks for a bank account to work with', function (done) {
+      var sql = 'SELECT * FROM bankaccnt WHERE bankaccnt_id IN ('       +
+                '   SELECT bankrec_bankaccnt_id FROM bankrec'           +
+                '   GROUP BY bankrec_bankaccnt_id'                      +
+                '   HAVING BOOL_AND(bankrec_posted)) LIMIT 1;'
+                ;
+      datasource.query(sql, creds, function (err, res) {
+        if (res.rowCount === 1) {
+          bankaccnt = res.rows[0];
+          assert.isNotNull(bankaccnt, 'we found a bank account');
+          bankrec = "create";
+          done();
+        } else {
+          var sql = 'SELECT * FROM bankaccnt WHERE bankaccnt_id IN ('       +
+                    '   SELECT bankrec_bankaccnt_id FROM bankrec'           +
+                    '    WHERE bankrec_opendate IN'                         +
+                    '      (SELECT MAX(bankrec_opendate) FROM bankrec'      +
+                    '        WHERE NOT bankrec_posted)'                     +
+                    ') LIMIT 1;'
+                    ;
+          datasource.query(sql, creds, function (err, res) {
+            assert.equal(res.rowCount, 1);
+            bankaccnt = res.rows[0];
+            assert.isNotNull(bankaccnt, 'we found a bank account');
+            bankrec = "select";
+            done();
+          });
+        }
+      });
+    });
+
+    it('ensures there is an open accounting period', function (done) {
+      var sql = 'SELECT period_id, period_closed, period_freeze'        +
+                '  FROM period'                                         +
+                ' WHERE CURRENT_DATE BETWEEN period_start AND period_end;'
+                ;
+      datasource.query(sql, creds, function (err, res) {
+        var sql;
+        if (res.rowCount !== 1) {
+          sql = mqlToSql("INSERT INTO period ("                         +
+                "  period_closed, period_freeze, period_name,"          +
+                "  period_quarter, period_start,"                       +
+                "  period_end, period_number"                           +
+                ") VALUES (FALSE, FALSE, <? value('testTag') ?>,"       +
+                "  EXTRACT(QUARTER FROM DATE CURRENT_DATE),"            +
+                "  DATE_TRUNC(MONTH, CURRENT_DATE),"                    +
+                "  DATE_TRUNC(MONTH, CURRENT_DATE) + '1 month',"        +
+                "  EXTRACT(month FROM DATE CURRENT_DATE)"               +
+                ") RETURNING period_id;",
+                { testTag: testTag });
+        } else if (res.rows[0].period_closed === true) {
+          sql = mqlToSql('SELECT openAccountingPeriod(<? value("period") ?>)' +
+                         ' AS period_id;', { period: res.rows[0].period_id });
+        }
+        if (sql) {
+          datasource.query(sql, creds, function (err, res) {
+            assert.equal(res.rowCount, 1);
+            assert(res.period_id >= 0);
+            done();
+          });
+        } else {
+          done();
+        }
+      });
+    });
+
+    it('turns on cash-based tax handling if necessary', function (done) {
+      var sql = "SELECT fetchMetricBool('CashBasedTax') AS result;";
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        wasCashBasedTax = res.rows[0].result === 't';
+        if (wasCashBasedTax) {
+          done();
+        } else {
+          var sql = "SELECT setMetric('CashBasedTax', 't') AS result;";
+          datasource.query(sql, creds, function (err, res) {
+            assert.equal(res.rowCount, 1);
+            done();
+          });
+        }
+      });
+    });
+
+    it('creates a purchase order', function (done) {
+      var sql = mqlToSql("INSERT INTO pohead (pohead_number, pohead_status,"   +
+                         "  pohead_agent_username, pohead_vend_id,"            +
+                         "  pohead_taxzone_id, pohead_orderdate,"              +
+                         "  pohead_curr_id, pohead_saved, pohead_comments,"    +
+                         "  pohead_warehous_id,"                               +
+                         "  pohead_printed, pohead_terms_id,pohead_taxtype_id" +
+                         ") SELECT fetchPoNumber(), 'U',"                      +
+                         "    CURRENT_USER, vend_id,"                          +
+                         "    vend_taxzone_id, CURRENT_DATE,"                  +
+                         "    vend_curr_id, false, <? value('testTag') ?>,"    +
+                         "    (SELECT MIN(warehous_id) FROM whsinfo WHERE "    +
+                         "     warehous_active AND NOT warehous_transit),"     +
+                         "    false, vend_terms_id, taxass_taxtype_id"         +
+                         "   FROM vendinfo"                                    +
+                         "   JOIN taxass ON vend_taxzone_id=taxass_taxzone_id" +
+                         "   JOIN taxrate ON taxass_tax_id=taxrate_tax_id"     +
+                         "  WHERE vend_active"                                 +
+                         "    AND (taxrate_percent > 0 OR taxrate_amount > 0)" +
+                         " LIMIT 1 RETURNING *;",
+                         { testTag: testTag });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        pohead = res.rows[0];
+        done();
+      });
+    });
+
+    it('creates a purchase order item', function (done) {
+      var sql = mqlToSql("INSERT INTO poitem (poitem_pohead_id,"               +
+                         "  poitem_linenumber, poitem_status,"                 +
+                         "  poitem_taxtype_id, poitem_itemsite_id,"            +
+                         "  poitem_itemsrc_id, poitem_qty_ordered,"            +
+                         "  poitem_unitprice,"                                 +
+                         "  poitem_duedate, poitem_comments"                   +
+                         ") SELECT pohead_id, 1, 'O',"                         +
+                         "    pohead_taxtype_id, itemsite_id,"                 +
+                         "    itemsrc_id, 100,"                                +
+                         "    itemsrcprice(itemsrc_id, itemsite_warehous_id,"  +
+                         "          false, 100, pohead_curr_id,CURRENT_DATE)," +
+                         "    now() + '5 days', <? value('testTag') ?>"        +
+                         "  FROM pohead"                                       +
+                         "  JOIN itemsrc  ON pohead_vend_id=itemsrc_vend_id"   +
+                         "  JOIN itemsite ON itemsrc_item_id=itemsite_item_id" +
+                         "  WHERE itemsite_active AND itemsrc_active"          +
+                         "    AND pohead_id=<? value('poheadid') ?>"           +
+                         " LIMIT 1 RETURNING *;",
+                         { poheadid: pohead.pohead_id, testTag: testTag });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        poitem = res.rows[0];
+        done();
+      });
+    });
+
+    it('calculates purchase order amounts', function (done) {
+      var sql = mqlToSql("SELECT" +
+                         "   SUM(poitem_qty_ordered*poitem_unitprice) AS amt," +
+                         "   SUM(poitem_freight) + pohead_freight AS freight," +
+                         "   (SELECT SUM(tax) FROM"                            +
+                         "      (SELECT ROUND(SUM(taxdetail_tax), 2) AS tax"   +
+                         "         FROM tax JOIN"                              +
+                         "      calculateTaxDetailSummary('PO',pohead_id,'T')" +
+                         "              ON (tax_id=taxdetail_tax_id)"          +
+                         "      GROUP BY tax_id) AS taxdata"                   +
+                         "   ) AS tax"                                         +
+                         "  FROM poitem"                                       +
+                         "  JOIN pohead ON poitem_pohead_id=pohead_id"         +
+                         " WHERE pohead_id=<? value('pohead_id') ?>"           +
+                         " GROUP BY pohead_id, pohead_freight;",
+                         { pohead_id: pohead.pohead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        pohead.amount  = res.rows[0].amt;
+        pohead.freight = res.rows[0].freight;
+        pohead.tax     = res.rows[0].tax;
+        apcheck.amount = pohead.amount + pohead.freight + pohead.tax;
+        done();
+      });
+    });
+
+    it('releases the purchase order', function (done) {
+      var sql = mqlToSql("SELECT releasePurchaseOrder(<? value('id') ?>)" +
+                         " AS result;", { id: pohead.pohead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert(res.rows[0].result > 0);
+        done();
+      });
+    });
+
+    it('receives the purchase order', function (done) {
+      var sql = mqlToSql("SELECT enterReceipt('PO', poitem_id, 100, 0, ''," +
+                         "   pohead_curr_id, CURRENT_DATE, NULL) AS result" +
+                         "  FROM poitem"                                    +
+                         "  JOIN pohead ON poitem_pohead_id=pohead_id"      +
+                         " WHERE poitem_id=<? value('poitem_id') ?>;",
+                         { poitem_id: poitem.poitem_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        recvid = res.rows[0].result;
+        done();
+      });
+    });
+
+    it('posts the receipt', function (done) {
+      var sql = mqlToSql("SELECT postReceipt(<? value('recvid') ?>, NULL)" +
+                         " AS result;", { recvid: recvid });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert(res.rows[0].result > 0);
+        done();
+      });
+    });
+
+    it('creates a voucher', function (done) {
+      var sql = mqlToSql("INSERT INTO vohead (vohead_number, vohead_posted,"   +
+                         "  vohead_pohead_id, vohead_taxzone_id,"              +
+                         "  vohead_vend_id,   vohead_terms_id,"                +
+                         "  vohead_distdate,  vohead_docdate, vohead_duedate," +
+                         "  vohead_invcnumber, vohead_reference, vohead_1099," +
+                         "  vohead_amount,     vohead_curr_id,   vohead_notes" +
+                         ") SELECT fetchVoNumber(), false,"                    +
+                         "    pohead_id, pohead_taxzone_id,"                   +
+                         "    pohead_vend_id, pohead_terms_id,"                +
+                         "    CURRENT_DATE, CURRENT_DATE, now() + '30 days',"  +
+                         "    'Vend Invoice', <? value('testTag') ?>, false,"  +
+                         "    <? value('vototal') ?>,"                         +
+                         "    pohead_curr_id, <? value('testTag') ?>"          +
+                         "    FROM pohead"                                     +
+                         "   WHERE pohead_id=<? value('poheadid') ?>"          +
+                         " RETURNING *,"                                       +
+                         "   currRate(vohead_curr_id, CURRENT_DATE) AS exrate;",
+                         { poheadid: pohead.pohead_id,
+                           vototal:  apcheck.amount + vomisc.amount,
+                           testTag:  testTag });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        voucher = res.rows[0];
+        done();
+      });
+    });
+
+    it('distributes the p/o item to the voucher', function (done) {
+      var sql = mqlToSql("SELECT distributeVoucherLine(vohead_id,"          +
+                         "  poitem_id, vohead_curr_id) AS result"           +
+                         "  FROM vohead JOIN poitem"                        +
+                         "          ON (vohead_pohead_id=poitem_pohead_id)" +
+                         " WHERE poitem_id = <? value('poitem_id') ?>;",
+                         { poitem_id: poitem.poitem_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.equal(res.rows[0].result, 1);
+        done();
+      });
+    });
+
+    it('gets the voitem', function (done) {
+      var sql = mqlToSql("SELECT * FROM voitem" +
+                         " WHERE voitem_vohead_id=<? value('vohead') ?>;",
+                         { vohead: voucher.vohead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        voitem = res.rows[0];
+        assert(voitem.voitem_id > 0);
+        done();
+      });
+    });
+
+    it('creates a misc voucher distribution', function (done) {
+      var sql = mqlToSql("INSERT INTO vodist (vodist_vohead_id,"               +
+                         "  vodist_poitem_id, vodist_costelem_id,"             +
+                         "  vodist_accnt_id, vodist_amount,"                   +
+                         "  vodist_discountable, vodist_expcat_id,"            +
+                         "  vodist_tax_id, vodist_notes"                       +
+                         ") SELECT <? value('vohead') ?>, -1, -1,"             +
+                         "    COALESCE(tax_sales_accnt_id,tax_dist_accnt_id)," +
+                         "    <? value('miscamt') ?>,"                         +
+                         "    FALSE, -1, tax_id, <? value('testTag') ?>"       +
+                         "  FROM tax"                                          +
+                         "  JOIN taxass ON tax_id = taxass_tax_id"             +
+                         "  JOIN taxtype ON taxass_taxtype_id=taxtype_id"      +
+                         "  JOIN poitem ON taxtype_id=poitem_taxtype_id"       +
+                         "  JOIN pohead ON poitem_pohead_id=pohead_id"         +
+                         "            AND taxass_taxzone_id=pohead_taxzone_id" +
+                         " WHERE poitem_id=<? value('poitemid') ?>"            +
+                         " RETURNING *;",
+                         { vohead:   voucher.vohead_id, miscamt: vomisc.amount,
+                           poitemid: poitem.poitem_id,  testTag: testTag });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        vomisc = res.rows[0];
+        assert(vomisc.vodist_id > 0);
+        done();
+      });
+    });
+
+    it('posts the voucher', function (done) {
+      var sql = mqlToSql("SELECT postVoucher(<? value('id') ?>, TRUE)" +
+                         " AS result;", { id: voucher.vohead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert(res.rows[0].result > 0);
+        done();
+      });
+    });
+
+    it('checks for voucher tax distributions', function (done) {
+      var sql = mqlToSql("SELECT 1 AS seq, * FROM voheadtax"                   +
+                         " WHERE taxhist_parent_id=<? value('voheadid') ?>"    +
+                         " UNION ALL "                                         +
+                         "SELECT 2 AS seq, voitemtax.*"                        +
+                         "  FROM voitemtax"                                    +
+                         "  JOIN voitem ON taxhist_parent_id=voitem_id"        +
+                         " WHERE voitem_vohead_id=<? value('voheadid') ?>"     +
+                         " ORDER BY seq, taxhist_id;",
+                         { voheadid: voucher.vohead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 2);
+        votax = res.rows[0];
+        assert.closeTo(votax.taxhist_basis, 0, closeEnough);
+        voitemtax = res.rows[1];
+        assert.closeTo(voitemtax.taxhist_basis,
+                       - poitem.poitem_unitprice * poitem.poitem_qty_ordered,
+                       closeEnough);
+        done();
+      });
+    });
+
+    it('creates an apcheck to reconcile', function (done) {
+      var sql = mqlToSql("SELECT createCheck(<? value('bankaccntid') ?>, 'V'," +
+                         "   vohead_vend_id, CURRENT_DATE, vohead_amount,"     +
+                         "   vohead_curr_id, NULL, NULL, 'AP Bearer',"         +
+                         "   <? value('testTag') ?>, TRUE, NULL) AS checkid"   +
+                         "  FROM vohead"                                       +
+                         " WHERE vohead_id=<? value('voheadid') ?>;",
+                         { bankaccntid: bankaccnt.bankaccnt_id,
+                           voheadid:    voucher.vohead_id,
+                           testTag:     testTag });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert(res.rows[0].checkid > 0);
+        var checkid = res.rows[0].checkid;
+        sql = mqlToSql("UPDATE checkhead SET checkhead_number="      +
+                       "  fetchNextCheckNumber(<? value('bank') ?>)" +
+                       " WHERE checkhead_id=<? value('check') ?>"    +
+                       " RETURNING *;",
+                       { bank: bankaccnt.bankaccnt_id, check: checkid });
+        datasource.query(sql, creds, function (err, res) {
+          assert.equal(res.rowCount, 1);
+          apcheck = res.rows[0];
+          done();
+        });
+      });
+    });
+
+    it('creates a credit memo to attach to the apcheck', function (done) {
+      var sql = mqlToSql("SELECT createAPCreditMemo(NULL, pohead_vend_id,"     +
+                         "       fetchJournalNumber('AP-MISC'),"               +
+                         "       <? value('vonumber') ?>, pohead_number,"      +
+                         "       CURRENT_DATE, <? value('amount') ?>,"         +
+                         "       <? value('testTag') ?>, -1,"                  +
+                         "       CAST(now()+'30 days' AS DATE),"               +
+                         "       pohead_terms_id, pohead_curr_id) AS result"   +
+                         "  FROM pohead"                                       +
+                         " WHERE pohead_id=<? value('poheadid') ?>;",
+                         { amount:   apcheck.checkhead_amount,
+                           testTag:  testTag,
+                           vonumber: voucher.vohead_number,
+                           poheadid: pohead.pohead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        cm = res.rows[0].result;
+        assert(cm > 0);
+        done();
+      });
+    });
+
+    it('creates a checkitem for the credit memo', function (done) {
+      var sql = mqlToSql("INSERT INTO checkitem ("                             +
+                         "  checkitem_checkhead_id, checkitem_apopen_id,"      +
+                         "  checkitem_vouchernumber, checkitem_amount,"        +
+                         "  checkitem_ponumber, checkitem_discount,"           +
+                         "  checkitem_docdate,"                                +
+                         "  checkitem_curr_id, checkitem_curr_rate"            +
+                         ") SELECT <? value('checkid') ?>, apopen_id,"         +
+                         "    <? value('vonumber') ?>, apopen_amount,"         +
+                         "    apopen_ponumber, 0, CURRENT_DATE,"               +
+                         "    apopen_curr_id, apopen_curr_rate"                +
+                         "  FROM apopen"                                       +
+                         " WHERE apopen_journalnumber=<? value('journal') ?>"  +
+                         " RETURNING *;",
+                         { checkid: apcheck.checkhead_id,
+                           vonumber: voucher.vohead_number,
+                           journal: cm });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        apcheckitem = res.rows[0];
+        assert(apcheckitem.checkitem_id > 0);
+        done();
+      });
+    });
+
+    it('posts the apcheck', function (done) {
+      var sql = mqlToSql(postCheckSql, { id: apcheck.checkhead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert(res.rows[0].result > 0);
+        apcheck.journalNumber = res.rows[0].result;
+        done();
+      });
+    });
+
+    it('creates a sales order', function (done) {
+      var sql = mqlToSql("INSERT INTO cohead (cohead_number, cohead_cust_id,"  +
+                 "    cohead_orderdate, cohead_packdate,"                      +
+                 "    cohead_shipto_id, cohead_shiptoname,"                    +
+                 "    cohead_shiptoaddress1, cohead_shiptoaddress2,"           +
+                 "    cohead_shiptoaddress3, cohead_shiptocity,"               +
+                 "    cohead_shiptostate, cohead_shiptozipcode,"               +
+                 "    cohead_shiptocountry, cohead_ordercomments,"             +
+                 "    cohead_salesrep_id, cohead_terms_id, cohead_holdtype,"   +
+                 "    cohead_freight, cohead_calcfreight,"                     +
+                 "    cohead_shipto_cntct_id, cohead_shipto_cntct_first_name," +
+                 "    cohead_shipto_cntct_last_name,"                          +
+                 "    cohead_curr_id, cohead_taxzone_id, cohead_taxtype_id,"   +
+                 "    cohead_saletype_id,"                                     +
+                 "    cohead_shipvia,"                                         +
+                 "    cohead_shipchrg_id,"                                     +
+                 "    cohead_shipzone_id, cohead_shipcomplete"                 +
+                 ") SELECT fetchSoNumber(), cust_id,"                          +
+                 "    CURRENT_DATE, CURRENT_DATE,"                             +
+                 "    shipto_id, shipto_name,"                                 +
+                 "    addr_line1, addr_line2,"                                 +
+                 "    addr_line3, addr_city,"                                  +
+                 "    addr_state, addr_postalcode,"                            +
+                 "    addr_country, <? value('testTag') ?>,"                   +
+                 "    cust_salesrep_id, cust_terms_id, 'N',"                   +
+                 "    0, TRUE,"                                                +
+                 "    cntct_id, cntct_first_name, cntct_last_name,"            +
+                 "    cust_curr_id, shipto_taxzone_id, taxass_taxtype_id,"     +
+                 "    (SELECT saletype_id FROM saletype"                       +
+                 "      WHERE saletype_code='REP'),"                           +
+                 "    (SELECT MIN(shipvia_code) FROM shipvia),"                +
+                 "    (SELECT shipchrg_id FROM shipchrg"                       +
+                 "      WHERE shipchrg_name='ADDCHARGE'),"                     +
+                 "    shipto_shipzone_id, FALSE"                               +
+                 "  FROM custinfo"                                             +
+                 "  JOIN shiptoinfo ON cust_id=shipto_cust_id"                 +
+                 "                 AND shipto_active"                          +
+                 "  JOIN taxass ON shipto_taxzone_id=taxass_taxzone_id"        +
+                 "  JOIN taxrate ON taxass_tax_id=taxrate_tax_id"              +
+                 "  LEFT OUTER JOIN addr ON shipto_addr_id=addr_id"            +
+                 "  LEFT OUTER JOIN cntct ON shipto_cntct_id=cntct_id"         +
+                 " WHERE cust_active"                                          +
+                 "   AND cust_preferred_warehous_id > 0"                       +
+                 "   AND (taxrate_percent > 0 OR taxrate_amount > 0)"          +
+                 " LIMIT 1 RETURNING *;",
+                 { testTag: testTag });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        cohead = res.rows[0];
+        assert(cohead.cohead_id > 0);
+        done();
+      });
+    });
+
+    it('creates a sales order item', function (done) {
+      var sql = mqlToSql("INSERT INTO coitem (coitem_cohead_id,"               +
+               "    coitem_linenumber, coitem_scheddate, coitem_itemsite_id,"  +
+               "    coitem_taxtype_id, coitem_status,"                         +
+               "    coitem_qtyord, coitem_qtyshipped,  coitem_qtyreturned,"    +
+               "    coitem_unitcost, coitem_price, coitem_custprice,"          +
+               "    coitem_qty_uom_id, coitem_price_uom_id,"                   +
+               "    coitem_qty_invuomratio, coitem_price_invuomratio"          +
+               ") SELECT cohead_id,"                                           +
+               "    1, CURRENT_DATE + itemsite_leadtime, itemsite_id,"         +
+               "    getItemTaxType(item_id, cohead_taxzone_id), 'O',"          +
+               "    123, 0, 0,"                                                +
+               "    itemcost(itemsite_id), item_listprice, item_listprice,"    +
+               "    item_price_uom_id, item_price_uom_id,"                     +
+               "    1, 1"                                                      +
+               "  FROM cohead"                                                 +
+               "  JOIN custinfo ON cohead_cust_id=cust_id"                     +
+               "  JOIN itemsite"                                               +
+               "        ON cust_preferred_warehous_id=itemsite_warehous_id"    +
+               "  JOIN item ON (itemsite_item_id=item_id)"                     +
+               " WHERE cohead_id=<? value('coheadid') ?>"                      +
+               "   AND itemsite_active"                                        +
+               "   AND item_price_uom_id=item_inv_uom_id"                      +
+/* simplify!*/ "  AND item_type != 'K'"                                        +
+               " LIMIT 1 RETURNING *;",
+                 { coheadid: cohead.cohead_id, testTag: testTag });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        coitem = res.rows[0];
+        assert(coitem.coitem_id > 0);
+        done();
+      });
+    });
+
+    it('updates the sales order with item info', function (done) {
+      var sql = mqlToSql("UPDATE cohead SET cohead_freight=("                  +
+                         "    SELECT SUM(freightdata_total)"                   +
+                         "      FROM freightDetail('SO', cohead_id,"           +
+                         "              cohead_cust_id, cohead_shipto_id,"     +
+                         "              CURRENT_DATE, cohead_shipvia,"         +
+                         "              cohead_curr_id))"                      +
+                         " WHERE cohead_id=<? value('cohead_id') ?>"           +
+                         " RETURNING cohead_freight;",
+                         { cohead_id: cohead.cohead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        cohead.cohead_freight = res.rows[0].cohead_freight;
+        done();
+      });
+    });
+
+    it('calculates sales order amounts', function (done) {
+      var sql = mqlToSql("SELECT SUM(coitem_qtyord*coitem_price) AS amt,"      +
+                         "   (SELECT SUM(tax) FROM"                            +
+                         "      (SELECT ROUND(SUM(taxdetail_tax), 2) AS tax"   +
+                         "         FROM tax JOIN"                              +
+                         "      calculateTaxDetailSummary('S',cohead_id,'T')"  +
+                         "              ON (tax_id=taxdetail_tax_id)"          +
+                         "      GROUP BY tax_id) AS taxdata"                   +
+                         "   ) AS tax"                                         +
+                         "  FROM coitem"                                       +
+                         "  JOIN cohead ON coitem_cohead_id=cohead_id"         +
+                         " WHERE cohead_id=<? value('cohead_id') ?>"           +
+                         " GROUP BY cohead_id;",
+                         { cohead_id: cohead.cohead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        cohead.subtotal = res.rows[0].amt;
+        cohead.tax      = res.rows[0].tax;
+        cohead.amount   = cohead.subtotal + cohead.cohead_freight + cohead.tax;
+        assert(cohead.subtotal > 0, 'expect the sales order to have value');
+        assert(cohead.tax      > 0, 'expect the sales order to have tax');
+        done();
+      });
+    });
+
+    it('issue the sales order to shipping', function (done) {
+      var sql = mqlToSql("SELECT issueToShipping(coitem_id, coitem_qtyord)" +
+                         "       AS result"                                 +
+                         "  FROM coitem WHERE coitem_id=<? value('id') ?>;",
+                         { id: coitem.coitem_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert(res.rows[0].result > 0, 'expected issueToShipping to succeed');
+        done();
+      });
+    });
+
+    it('ships the sales order', function (done) {
+      var sql = mqlToSql("SELECT shipShipment(shiphead_id) AS result"          +
+                         "  FROM shiphead"                                     +
+                         "  JOIN shipitem ON shiphead_order_type = 'SO'"       +
+                         "               AND shiphead_id=shipitem_shiphead_id" +
+                         " WHERE shipitem_orderitem_id=<? value('coitem') ?>;",
+                         { coitem: coitem.coitem_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        // TODO: why does shipShipment return NULL on success?
+        assert(res.rows[0].result === null || res.rows[0].result > 0,
+               'expected shipShipment to succeed');
+        done();
+      });
+    });
+
+    // this creates cobmisc and cobill records for all uninvoiced coitems
+    it('selects the shipment for billing', function (done) {
+      var sql = mqlToSql("SELECT selectUninvoicedShipment(shiphead_id)"        +
+                         "       AS result"                                    +
+                         "  FROM shiphead"                                     +
+                         "  JOIN shipitem ON shiphead_order_type = 'SO'"       +
+                         "               AND shiphead_id=shipitem_shiphead_id" +
+                         " WHERE shipitem_orderitem_id=<? value('coitem') ?>;",
+                         { coitem: coitem.coitem_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        cobmisc.cobmisc_id = res.rows[0].result;
+        assert(cobmisc.cobmisc_id > 0,
+               'expected selectUninvoicedShipment to succeed');
+        done();
+      });
+    });
+
+    it('creates an invoice', function (done) {
+      var sql = mqlToSql("SELECT createInvoice(<? value('cobmisc') ?>) AS id;",
+                         { cobmisc: cobmisc.cobmisc_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        invchead.invchead_id = res.rows[0].id;
+        assert(invchead.invchead_id > 0, 'expected createInvoice to succeed');
+        done();
+      });
+    });
+
+    it('posts the invoice', function (done) {
+      var sql = mqlToSql("SELECT postInvoice(<? value('id') ?>) AS result;",
+                         { id: invchead.invchead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        var result = res.rows[0].result;
+        assert(result === null || result > 0, 'expected postInvoice to succeed');
+        done();
+      });
+    });
+
+    // TODO? UPDATE shipdatasum SET shipdatasum_shipped=true
+    //        WHERE shipdatasum_cosmisc_tracknum = ''
+    //          AND shipdatasum_shiphead_number='60103';
+
+    it('creates a cash receipt to reconcile', function (done) {
+      var sql = mqlToSql("INSERT INTO cashrcpt ("                              +
+                         "  cashrcpt_cust_id, cashrcpt_amount,"                +
+                         "  cashrcpt_fundstype, cashrcpt_docnumber,"           +
+                         "  cashrcpt_bankaccnt_id, cashrcpt_notes,"            +
+                         "  cashrcpt_curr_id, cashrcpt_number,"                +
+                         "  cashrcpt_docdate, cashrcpt_applydate,"             +
+                         "  cashrcpt_distdate, cashrcpt_usecustdeposit,"       +
+                         "  cashrcpt_curr_rate,"                               +
+                         "  cashrcpt_salescat_id, cashrcpt_discount"           +
+                         ") SELECT cohead_cust_id, <? value('amount') ?>,"     +
+                         "       'C', 'CR ' || <? value('testTag') ?>,"        +
+                         "       <? value('bank') ?>, <? value('testTag') ?>," +
+                         "       cohead_curr_id, fetchCashRcptNumber(),"       +
+                         "       NULL, CURRENT_DATE,"                          +
+                         "       CURRENT_DATE, TRUE,"                          +
+                         "       currRate(cohead_curr_id, CURRENT_DATE),"      +
+                         "       -1, 0"                                        +
+                         "    FROM cohead"                                     +
+                         "   WHERE cohead_id=<? value('coheadid') ?>"          +
+                         " RETURNING *;",
+                         { coheadid: cohead.cohead_id,
+                           amount:   cohead.amount,
+                           bank:     bankaccnt.bankaccnt_id,
+                           testTag:  testTag
+                         });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert(res.rows[0].cashrcpt_id > 0);
+        cashrcpt = res.rows[0];
+        done();
+      });
+    });
+
+    it('apply the cash receipt to the invoice', function (done) {
+      var sql = mqlToSql("SELECT applyCashReceiptLineBalance("                 +
+                         "     <? value('crid') ?>, aropen_id, aropen_amount," +
+                         "     aropen_curr_id) AS result"                      +
+                         "  FROM aropen"                                       +
+                         "  JOIN invchead ON aropen_doctype = 'I'"             +
+                         "           AND aropen_docnumber=invchead_invcnumber" +
+                         " WHERE invchead_id=<? value('invchead') ?>;",
+                         { crid:     cashrcpt.cashrcpt_id,
+                           invchead: invchead.invchead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert(res.rows[0].result > 0, 'expect an application');
+        // applyCashReceiptLineBalance subtracts discounts so we can't just
+        // assert.closeTo(res.rows[0].result, cohead.amount, closeEnough);
+        done();
+      });
+    });
+
+    // This creates an aropen and a cashrcptitem
+    it('posts the cash receipt', function (done) {
+      var sql = mqlToSql("SELECT postCashReceipt(<? value('id') ?>," +
+                         "    fetchJournalNumber('C/R')) AS result;",
+                         { id: cashrcpt.cashrcpt_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.equal(res.rows[0].result, 1);
+        done();
+      });
+    });
+
+    it('finds the aropen for the cash receipt application', function (done) {
+      var sql = mqlToSql("SELECT aropen.* FROM aropen JOIN cashrcptitem"     +
+                         "    ON aropen_id=cashrcptitem_aropen_id"           +
+                         " WHERE cashrcptitem_cashrcpt_id=<? value('id') ?>" +
+                         "   AND aropen_doctype = 'I';",
+                         { id: cashrcpt.cashrcpt_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        aropen = res.rows[0];
+        done();
+      });
+    });
+
+    // now start actual bankrec testing ///////////////////////////////////////
+
+    it('gets an open bankrec to test with', function (done) {
+      var mql, sql;
+      if (bankrec === "create") {
+        mql = 'INSERT INTO bankrec (bankrec_bankaccnt_id,'    +
+              '  bankrec_opendate, bankrec_openbal'           +
+              ') SELECT bankrec_bankaccnt_id,'                +
+              '         bankrec_enddate + 1, bankrec_endbal'  +
+              '    FROM bankrec'                              +
+              '   WHERE bankrec_bankaccnt_id=<? value("bankaccnt") ?>' +
+              '     AND bankrec_posted'                       +
+              '   ORDER BY bankrec_enddate DESC'              +
+              '   LIMIT 1 RETURNING *;'
+              ;
+      } else if (bankrec === "select") {
+        mql = 'SELECT * FROM bankrec'                                    +
+              ' WHERE bankrec_bankaccnt_id=<? value("bankaccnt") ?>'     +
+              '   AND bankrec_opendate IN'                               +
+              '  (SELECT MAX(bankrec_opendate) FROM bankrec'             +
+              '    WHERE NOT bankrec_posted'                             +
+              '      AND bankrec_bankaccnt_id=<? value("bankaccnt") ?>'  +
+              '  );'
+              ;
+      }
+      sql = mqlToSql(mql, {bankaccnt: bankaccnt.bankaccnt_id});
+
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        bankrec = res.rows[0];
+        assert.isNotNull(bankrec,              'we have a bank rec');
+        assert.isFalse(bankrec.bankrec_posted, 'we have an open bank rec');
+        done();
+      });
+    });
+
+    it('marks the apcheck as cleared', function (done) {
+      var sql = mqlToSql(toggleCheckSql, { bankrecid: bankrec.bankrec_id,
+                                           checkid:   apcheck.checkhead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isTrue(res.rows[0].result);
+        done();
+      });
+    });
+
+    it('confirms the apcheck was marked as cleared', function (done) {
+      var sql = mqlToSql(bankRecItemSql,
+        { brid: bankrec.bankrec_id, src: 'GL',
+          srcid: " IN (SELECT gltrans_id FROM gltrans WHERE"  +
+                 " gltrans_doctype='CK' AND gltrans_misc_id=" +
+                 apcheck.checkhead_id + ")"});
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isTrue(res.rows[0].bankrecitem_cleared);
+        assert(res.rows[0].bankrecitem_cleared);
+        done();
+      });
+    });
+
+    it('marks the apcheck as /not/ cleared', function (done) {
+      var sql = mqlToSql(toggleCheckSql, { bankrecid: bankrec.bankrec_id,
+                                           checkid:   apcheck.checkhead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isFalse(res.rows[0].result);
+        done();
+      });
+    });
+
+    it('confirms the apcheck is no longer marked as cleared', function (done) {
+      var sql = mqlToSql(bankRecItemSql,
+        { brid: bankrec.bankrec_id, src: 'GL',
+          srcid: " IN (SELECT gltrans_id FROM gltrans WHERE"  +
+                 " gltrans_doctype='CK' AND gltrans_misc_id=" +
+                 apcheck.checkhead_id + ")" });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 0);
+        done();
+      });
+    });
+
+    it('marks the apcheck as cleared again', function (done) {
+      var sql = mqlToSql(toggleCheckSql, { bankrecid: bankrec.bankrec_id,
+                                           checkid:   apcheck.checkhead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isTrue(res.rows[0].result);
+        done();
+      });
+    });
+
+    it('confirms that the apcheck is marked as cleared again', function (done) {
+      var sql = mqlToSql(bankRecItemSql,
+        { brid: bankrec.bankrec_id, src: 'GL',
+          srcid: " IN (SELECT gltrans_id FROM gltrans" +
+                 " WHERE gltrans_doctype='CK' AND "    +
+                 " gltrans_misc_id=" + apcheck.checkhead_id + ")"});
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isTrue(res.rows[0].bankrecitem_cleared);
+        assert(res.rows[0].bankrecitem_cleared);
+        done();
+      });
+    });
+
+    it('marks the cash receipt as cleared', function (done) {
+      var sql = mqlToSql("SELECT toggleBankRecCleared("                        +
+                         "     <? value('bankrecid') ?>, 'A/R', gltrans_id,"   +
+                         "     cashrcpt_curr_rate, cashrcpt_amount) AS result" +
+                         "  FROM gltrans"                                      +
+                         "  JOIN cashrcpt ON gltrans_misc_id=cashrcpt_id"      +
+                         "               AND gltrans_doctype='CR'"             +
+                         " WHERE cashrcpt_id=<? value('cashrcptid') ?>"        +
+                         "   AND gltrans_accnt_id=<? value('accntid') ?>;",
+                         { bankrecid: bankrec.bankrec_id,
+                           cashrcptid:  cashrcpt.cashrcpt_id,
+                           accntid:   bankaccnt.bankaccnt_accnt_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isTrue(res.rows[0].result);
+        done();
+      });
+    });
+
+    it('confirms the cash receipt was marked as cleared', function (done) {
+      var sql = mqlToSql(bankRecItemSql,
+        { brid: bankrec.bankrec_id, src: 'A/R',
+          srcid: " IN (SELECT gltrans_id FROM gltrans WHERE"  +
+                 " gltrans_doctype='CR' AND gltrans_misc_id=" +
+                 cashrcpt.cashrcpt_id + ")" });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isTrue(res.rows[0].bankrecitem_cleared);
+        assert(res.rows[0].bankrecitem_cleared);
+        done();
+      });
+    });
+
+    it('creates a bank adjustment', function (done) {
+      var sql = mqlToSql("INSERT INTO bankadj ("                             +
+                "  bankadj_bankaccnt_id, bankadj_bankadjtype_id,"            +
+                "  bankadj_date, bankadj_docnumber, bankadj_amount,"         +
+                "  bankadj_notes, bankadj_curr_id,"                          +
+                "  bankadj_curr_rate"                                        +
+                ") SELECT <? value('bankaccnt') ?>, bankadjtype_id,"         +
+                "    CURRENT_DATE, 'BankRecTest', <? value('amount') ?>,"    +
+                "    <? value('testTag') ?>,      <? value('currid') ?>,"    +
+                "    currrate(<? value('currid') ?>, basecurrid(),"          +
+                "             CURRENT_DATE) FROM bankadjtype RETURNING *;",
+                { amount: bankadj.amount, bankaccnt: bankaccnt.bankaccnt_id,
+                  testTag: testTag,      currid: bankaccnt.bankaccnt_curr_id })
+                ;
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        _.extend(bankadj, res.rows[0]);
+        assert.ok(bankadj.bankadj_id, 'we have a bank adjustment');
+        done();
+      });
+    });
+
+    it('gets the size of the gltrans table before posting', function (done) {
+      var sql = mqlToSql(gltransCheckSql, { testTag: testTag });
+      datasource.query(sql, creds, function (err, res) {
+        lastGltransCount = res.rows[0].cnt;
+        done();
+      });
+    });
+
+    it('checks voitem tax data before posting', function (done) {
+      var sql = mqlToSql(taxinfoCheckSql,
+                         { taxhist: 'voitemtax', taxparent: voitem.voitem_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1,        'expect one voitemtax record');
+        assert.isNull(res.rows[0].taxpay_id, 'expect no voitem taxpay');
+        done();
+      });
+    });
+
+    it('checks misc distrib tax data before posting', function (done) {
+      var sql = mqlToSql(taxinfoCheckSql,
+                         { taxhist: 'voheadtax', taxparent: voucher.vohead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1,        'expect one voheadtax record');
+        assert.isNull(res.rows[0].taxpay_id, 'expect no vohead taxpay');
+        done();
+      });
+    });
+
+    it('checks cashrcpt application tax data before posting', function (done) {
+      var sql = mqlToSql(cohisttaxinfoSql,
+                         { itemsite: coitem.coitem_itemsite_id,
+                           cohead:   cohead.cohead_number });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1,        'expect 1 cohisttax record');
+        assert.isNull(res.rows[0].taxpay_id, 'expect no cohist taxpay');
+        done();
+      });
+    });
+
+    it('posts the reconciliation', function (done) {
+      var sql = mqlToSql(postBankRecSql, {id: bankrec.bankrec_id});
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.equal(res.rows[0].result, bankrec.bankrec_id);
+        done();
+      });
+    });
+
+    it('confirms the posted bankrec was updated properly', function (done) {
+      var sql = mqlToSql(getBankRecSql, { id: bankrec.bankrec_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isTrue(res.rows[0].bankrec_posted);
+        assert.isNotNull(res.rows[0].bankrec_postdate, 'expect a post date');
+        done();
+      });
+    });
+
+    it('confirms the gl for the posted bankrec was updated', function (done) {
+      var sql = mqlToSql(bankRecGLSql, { bankrecid: bankrec.bankrec_id });
+      datasource.query(sql, creds, function (err, res) {
+        var recorded = _.filter(res.rows,
+                                function (v) { return v.gltrans_rec; });
+        assert.equal(res.rows.length, recorded.length, 'AND(gltrans_rec) should be true');
+        done();
+      });
+    });
+
+    it('confirms the apcheck was reconciled properly', function (done) {
+      var sql = mqlToSql(checkCheckSql, { checkid:   apcheck.checkhead_id,
+                                          bankrecid: bankrec.bankrec_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isTrue(res.rows[0].gltrans_rec);
+        assert.isTrue(res.rows[0].bankrec_posted);
+        assert.closeTo(res.rows[0].gltrans_amount,
+                       apcheck.checkhead_amount / apcheck.checkhead_curr_rate,
+                       closeEnough);
+        done();
+      });
+    });
+
+    it('confirms reconcile updated gltrans properly', function (done) {
+      var sql = mqlToSql(gltransCheckSql, { testTag: testTag });
+      datasource.query(sql, creds, function (err, res) {
+        assert(res.rows[0].cnt > lastGltransCount, 'expected tax records');
+        lastGltransCount = res.rows[0].cnt;
+        done();
+      });
+    });
+
+    it('checks voitem tax data after posting', function (done) {
+      var sql = mqlToSql(taxinfoCheckSql,
+                         { taxhist: 'voitemtax', taxparent: voitem.voitem_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1,           'expect 1 voitemtax record');
+        assert.isNotNull(res.rows[0].taxpay_id, 'expect a voitem taxpay');
+        assert.closeTo(res.rows[0].taxpay_tax, voitemtax.taxhist_tax, closeEnough);
+        done();
+      });
+    });
+
+    it('checks misc distrib tax data after posting', function (done) {
+      var sql = mqlToSql(taxinfoCheckSql,
+                         { taxhist: 'voheadtax', taxparent: voucher.vohead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1,           'expect a vodisttax record');
+        assert.isNotNull(res.rows[0].taxpay_id, 'expect a vodist taxpay');
+        assert.closeTo(res.rows[0].taxpay_tax, votax.taxhist_tax, closeEnough);
+        done();
+      });
+    });
+
+    it('checks cashrcpt application tax data after posting', function (done) {
+      var sql = mqlToSql(cohisttaxinfoSql,
+                         { itemsite: coitem.coitem_itemsite_id,
+                           cohead:   cohead.cohead_number });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1,           'expect a cohisttax record');
+        assert.isNotNull(res.rows[0].taxpay_id, 'expect a cohist taxpay');
+        // discount => can't assert.closeTo(res.rows[0].taxpay_tax ...
+        done();
+      });
+    });
+
+    it('confirms the bank adjustment was /not/ posted', function (done) {
+      var sql = mqlToSql(bankAdjCheckSql, { bankadjid: bankadj.bankadj_id});
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isFalse(res.rows[0].bankadj_posted);
+        assert.isNull(res.rows[0].gltrans_id,     'expecting no gltrans');
+        assert.isNull(res.rows[0].bankrecitem_id, 'expecting no bankrecitem');
+        done();
+      });
+    });
+
+    it('reopens the reconcilation', function (done) {
+      var sql = 'SELECT reopenBankReconciliation(' + bankrec.bankrec_id +
+                ') AS result;';
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.equal(res.rows[0].result, bankrec.bankrec_id);
+        done();
+      });
+    });
+
+    it('confirms the reopened bankrec was updated properly', function (done) {
+      var sql = mqlToSql(getBankRecSql, { id: bankrec.bankrec_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isFalse(res.rows[0].bankrec_posted);
+        assert.isNull(res.rows[0].bankrec_postdate, 'expect empty post date');
+        done();
+      });
+    });
+
+    it('confirms the gl for the reopened bankrec was updated', function (done) {
+      var sql = mqlToSql(bankRecGLSql, { bankrecid: bankrec.bankrec_id });
+      datasource.query(sql, creds, function (err, res) {
+        var recorded = _.filter(res.rows,
+                                function (v) { return v.gltrans_rec; });
+        assert.equal(0, recorded.length, 'AND(gltrans_rec) should be true');
+        done();
+      });
+    });
+
+    it('confirms reconcile updated gltrans properly', function (done) {
+      var sql = mqlToSql(gltransCheckSql, { testTag: testTag });
+      datasource.query(sql, creds, function (err, res) {
+        assert(res.rows[0].cnt > lastGltransCount, 'expected tax reversals');
+        lastGltransCount = res.rows[0].cnt;
+        done();
+      });
+    });
+
+    it('checks voitem tax data after reopening', function (done) {
+      var sql = mqlToSql(taxinfoCheckSql,
+                         { taxhist: 'voitemtax', taxparent: voitem.voitem_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1,        'expect one voitemtax record');
+        assert.isNull(res.rows[0].taxpay_id, 'expect no voitem taxpay');
+        done();
+      });
+    });
+
+    it('checks misc distrib tax data after reopening', function (done) {
+      var sql = mqlToSql(taxinfoCheckSql,
+                         { taxhist: 'voheadtax', taxparent: voucher.vohead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1,        'expect one voheadtax record');
+        assert.isNull(res.rows[0].taxpay_id, 'expect no vohead taxpay');
+        done();
+      });
+    });
+
+    it('checks cashrcpt application tax data after reopening', function (done) {
+      var sql = mqlToSql(cohisttaxinfoSql,
+                         { itemsite: coitem.coitem_itemsite_id,
+                           cohead:   cohead.cohead_number });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1,        'expect 1 cohisttax record');
+        assert.isNull(res.rows[0].taxpay_id, 'expect no cohist taxpay');
+        done();
+      });
+    });
+
+    it('confirms the apcheck was "reopened" properly', function (done) {
+      var sql = mqlToSql(checkCheckSql, { checkid:   apcheck.checkhead_id,
+                                          bankrecid: bankrec.bankrec_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isFalse(res.rows[0].gltrans_rec);
+        assert.isFalse(res.rows[0].bankrec_posted);
+        assert.closeTo(Math.abs(res.rows[0].gltrans_amount), res.rows[0].base,
+                       closeEnough);
+        done();
+      });
+    });
+
+    // we expect the reconcile to create gltrans records for voitem tax
+    it('confirms unreconcile updated gltrans properly', function (done) {
+      var sql = mqlToSql(gltransCheckSql, { testTag: testTag });
+      datasource.query(sql, creds, function (err, res) {
+// TODO assert(res.rows[0].cnt > lastGltransCount, 'expected new gltrans');
+        lastGltransCount = res.rows[0].cnt;
+        done();
+      });
+    });
+
+    it('marks the bank adjustment as cleared', function (done) {
+      var sql = "SELECT toggleBankRecCleared(" + bankrec.bankrec_id         +
+                ", 'AD', " + bankadj.bankadj_id        +
+                ", " + bankadj.bankadj_curr_rate       +
+                ", " + bankadj.bankadj_amount + ") AS result;"
+                ;
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isTrue(res.rows[0].result);
+        done();
+      });
+    });
+
+    it('confirms the bank adjustment was /not/ posted but is cleared', function (done) {
+      var sql = mqlToSql(bankAdjCheckSql, { bankadjid: bankadj.bankadj_id});
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isFalse(res.rows[0].bankadj_posted);
+        assert(res.rows[0].bankrecitem_id >= 0);
+        done();
+      });
+    });
+
+    it('posts the reconciliation again', function (done) {
+      var sql = mqlToSql(postBankRecSql, {id: bankrec.bankrec_id});
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.equal(res.rows[0].result, bankrec.bankrec_id);
+        done();
+      });
+    });
+
+    it('confirms the 2nd posted bankrec was updated properly', function (done) {
+      var sql = mqlToSql(getBankRecSql, { id: bankrec.bankrec_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isTrue(res.rows[0].bankrec_posted);
+        assert.isNotNull(res.rows[0].bankrec_postdate, 'expect a post date');
+        done();
+      });
+    });
+
+    it('confirms the gl for 2nd posted bankrec was updated', function (done) {
+      var sql = mqlToSql(bankRecGLSql, { bankrecid: bankrec.bankrec_id });
+      datasource.query(sql, creds, function (err, res) {
+        var recorded = _.filter(res.rows,
+                                function (v) { return v.gltrans_rec; });
+        assert.equal(res.rows.length, recorded.length, 'AND(gltrans_rec) should be true');
+        done();
+      });
+    });
+
+    it('confirms the apcheck was reconciled properly', function (done) {
+      var sql = mqlToSql(checkCheckSql, { checkid:   apcheck.checkhead_id,
+                                          bankrecid: bankrec.bankrec_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isTrue(res.rows[0].gltrans_rec);
+        assert.isTrue(res.rows[0].bankrec_posted);
+        assert.closeTo(res.rows[0].gltrans_amount,
+                       apcheck.checkhead_amount / apcheck.checkhead_curr_rate,
+                       closeEnough);
+        done();
+      });
+    });
+
+    it('confirms reposting updated gltrans properly', function (done) {
+      var sql = mqlToSql(gltransCheckSql, { testTag: testTag });
+      datasource.query(sql, creds, function (err, res) {
+        assert(res.rows[0].cnt > lastGltransCount, 'expected new tax records');
+        lastGltransCount = res.rows[0].cnt;
+        done();
+      });
+    });
+
+    it('checks voitem tax data after reposting', function (done) {
+      var sql = mqlToSql(taxinfoCheckSql,
+                         { taxhist: 'voitemtax', taxparent: voitem.voitem_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1,           'expect 1 voitemtax record');
+        assert.isNotNull(res.rows[0].taxpay_id, 'expect a voitem taxpay');
+        assert.closeTo(res.rows[0].taxpay_tax, voitemtax.taxhist_tax, closeEnough);
+        done();
+      });
+    });
+
+    it('checks misc distrib tax data after reposting', function (done) {
+      var sql = mqlToSql(taxinfoCheckSql,
+                         { taxhist: 'voheadtax', taxparent: voucher.vohead_id });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1,           'expect a vodisttax record');
+        assert.isNotNull(res.rows[0].taxpay_id, 'expect a vodist taxpay');
+        assert.closeTo(res.rows[0].taxpay_tax, votax.taxhist_tax, closeEnough);
+        done();
+      });
+    });
+
+    it('checks cashrcpt application tax data after reposting', function (done) {
+      var sql = mqlToSql(cohisttaxinfoSql,
+                         { itemsite: coitem.coitem_itemsite_id,
+                           cohead:   cohead.cohead_number });
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1,           'expect a cohisttax record');
+        assert.isNotNull(res.rows[0].taxpay_id, 'expect a cohist taxpay');
+        // discount => can't assert.closeTo(res.rows[0].taxpay_tax ...
+        done();
+      });
+    });
+
+    it('confirms the bank adj was posted & written to the GL', function (done) {
+      var sql = mqlToSql(bankAdjCheckSql, { bankadjid: bankadj.bankadj_id});
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert.isTrue(res.rows[0].bankadj_posted);
+        assert(res.rows[0].bankadj_sequence >= 0);
+        assert.equal(res.rows[0].bankrecitem_source, 'GL');
+        assert.equal(res.rows[0].bankrecitem_source_id, res.rows[0].gltrans_id);
+        assert.isTrue(res.rows[0].gltrans_rec);
+        assert.closeTo(Math.abs(res.rows[0].gltrans_amount),
+                       res.rows[0].baseamt, closeEnough);
+        done();
+      });
+    });
+
+    it('deletes the bankrec', function (done) {
+      var sql = 'SELECT deleteBankReconciliation(' + bankrec.bankrec_id +
+                ') AS result;';
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert(res.rows[0].result >= 0);
+        done();
+      });
+    });
+
+    it('checks that the bankrec is really gone', function (done) {
+      var sql = 'SELECT COUNT(*) AS result FROM bankrec WHERE bankrec_id = ' +
+                bankrec.bankrec_id + ';';
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert(res.rows[0].result === 0);
+        done();
+      });
+    });
+
+    it('checks that the bankrecitems are gone', function (done) {
+      var sql = 'SELECT COUNT(*) AS result FROM bankrecitem' +
+                ' WHERE bankrecitem_bankrec_id = ' + bankrec.bankrec_id + ';';
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert(res.rows[0].result === 0);
+        done();
+      });
+    });
+
+    it('turns off cash-based tax handling if necessary', function (done) {
+      if (wasCashBasedTax) {
+        done();
+      } else {
+        var sql = "SELECT setMetric('CashBasedTax', 'f') AS result;";
+        datasource.query(sql, creds, function (err, res) {
+          assert.equal(res.rowCount, 1);
+          done();
+        });
+      }
+    });
+
+    it('tries to delete a non-existent bankrec', function (done) {
+      var sql = 'SELECT deleteBankReconciliation(-15) AS result;';
+      datasource.query(sql, creds, function (err, res) {
+        assert.equal(res.rowCount, 1);
+        assert(res.rows[0].result === 0); // no, it doesn't complain
+        done();
+      });
+    });
+
+    it('resets tax_dist_accnt_id to NULL', function (done) {
+      var sql = mqlToSql('UPDATE tax SET tax_dist_accnt_id = NULL' +
+                         ' WHERE tax_id IN (<? literal("idlist") ?>);',
+                         { idlist: badTaxIds.join(', ') });
+      if (badTaxIds.length <= 0) {
+        done();
+      } else {
+        datasource.query(sql, creds, function (err, res) {
+          assert.isNull(err);
+          done();
+        });
+      }
+    });
+
+  });
+}());
index b80e286..45cd47e 100644 (file)
@@ -62,5 +62,3 @@ var _ = require("underscore"),
 
   });
 }());
-
-
index 09e017d..eb46302 100644 (file)
       zombieAuth.loadApp(appLoaded);
     });
 
-    describe('Test Grid Boxes', function () {
-      it('Test Grid Box Functionality', function () {
+    it('Test Grid Box Functionality', function () {
+      _.each(XV, function (value, key) {
+        var list,
+          kinds = ['SalesOrderList', 'QuoteList', 'InvoiceList', 'ReturnList', 'ProjectList'];
+        // lists with grid boxes; TODO: find candidates automatically
+        if (_.contains(kinds, key)) {
+          describe('Create Workspace for XV.' + key, function () {
+            it('Create a Workspace', function () {
+              list = "XV." + key;
+              smoke.navigateToNewWorkspace(XT.app, list, function (workspaceContainer) {
+                var workspace = workspaceContainer.$.workspace,
+                    getExportButton = function (obj) {
+                      var result = null;
+                      if (_.isObject(obj.$)) {
+                        result = obj.$.exportButton ||
+                                 _.find(obj.$, getExportButton);
+                      }
+                      return result;
+                    };
 
-        _.each(XV, function (value, key) {
-          var list,
-            kinds = ['SalesOrderList', 'QuoteList', 'InvoiceList', 'ReturnList', 'ProjectList'];
-          // lists with grid boxes; TODO: find candidates automatically
-          if (_.contains(kinds, key)) {
-
-            describe('Create Workspace for XV.' + key, function () {
-              it('Create a Workspace', function () {
-                list = "XV." + key;
-                smoke.navigateToNewWorkspace(XT.app, list, function (workspaceContainer) {
-                  var workspace = workspaceContainer.$.workspace;
-                  _.each(workspace.$, function (component) {
-                    if (XV.inheritsFrom(component, 'XV.GridBox')) {
+                _.each(workspace.$, function (component) {
+                  if (XV.inheritsFrom(component, 'XV.GridBox')) {
+                    describe('Checking ' + component.name, function () {
+                      it('checks for the export button', function () {
+                        var exportButton = getExportButton(component);
+                        assert.ok(exportButton);
+                      });
 
-                      describe('Test creating line items for ' + component, function () {
-                        it('Create line items', function () {
-                          var gridBox = component, gridRow,
+                      it('creates line items for ' + component, function () {
+                        var gridBox = component,
+                            exportButton = getExportButton(gridBox),
+                            gridRow,
                             startingRows = gridBox.liveModels().length;
 
-                          gridBox.newItem();
-                          gridRow = gridBox.$.editableGridRow;
-                          // verify that there is an increase in rows
-                          assert.equal(gridBox.liveModels().length, startingRows += 1);
-
-                          // Add a new row using the enter key
-                          gridRow.bubble("onkeyup", {keyCode: 13});
-                          // verify again that a row has been added
-                          assert.equal(gridBox.liveModels().length, startingRows += 1);
-                        });
+                        // fyi: some workspaces prepopulate with dirty data
+                        if (startingRows == 0) {
+                          assert.isTrue(exportButton.disabled,
+                                       'expect export disabled if no data');
+                        } else if (_.every(gridBox.liveModels(), function (m) {
+                                     return m.isReadyClean(); })) {
+                          assert.isFalse(exportButton.disabled,
+                                       'expect export enabled for clean data');
+                        }
+                        gridBox.newItem();
+                        gridRow = gridBox.$.editableGridRow;
+                        assert.equal(gridBox.liveModels().length, startingRows += 1);
+                        assert.isTrue(exportButton.disabled,
+                                       'export disabled for changed data');
 
-                        it('Check export', function () {
-                          var getExportButton = function (obj) {
-                            var result = null;
-                            if (_.isObject(obj.$)) {
-                              result = obj.$.exportButton ||
-                                       _.find(obj.$, getExportButton);
-                            }
-                            return result;
-                          };
-
-                          var gridBox = component,
-                              exportButton = getExportButton(gridBox);
-                          assert.ok(exportButton);
-                          // TODO: need to populate before we can export
-                          // assert.doesNotThrow(exportButton.doTap());
-                          // TODO: find the generated file & check contents
-                        });
+                        // Add a new row using the enter key
+                        gridRow.bubble("onkeyup", {keyCode: 13});
+                        assert.equal(gridBox.liveModels().length, startingRows += 1);
                       });
-                    }
-                  });
+
+                      // TODO: populate, apply, & actually export
+                      // generate some data
+                      // assert.isTrue(exportButton.disabled,
+                      //               'expect export disabled if no data');
+                      // save the data
+                      // assert.isFalse(exportButton.disabled,
+                      //               'expect export disabled if no data');
+                      // assert.doesNotThrow(exportButton.doTap());
+                      // find the generated file & check contents
+                    });
+                  }
                 });
               });
             });
-          }
-        });
+          });
+        }
       });
     });
   });
index 7f4a308..a63031f 100644 (file)
@@ -17,7 +17,7 @@
     before(function (done) {
       // setup for the date widget
       var initializeDate = function () {
-        K = enyo.kind({kind: XV.Date});
+        K = enyo.kind({kind: XV.DateWidget});
         K = new K();
         done();
       };
index 509d231..ba4e2db 100644 (file)
@@ -122,6 +122,16 @@ setTimeout:true, before:true, clearTimeout:true, exports:true, it:true, describe
         incidentModel.set("assignedTo", new XM.UserAccountRelation());
         assert.equal(incidentModel.get("status"), XM.Incident.ASSIGNED);
       });
+      it("Incident status does not revert to assigned from closed when user is assigned to it", function () {
+        incidentModel.set("status", XM.Incident.CLOSED);
+        incidentModel.set("assignedTo", new XM.UserAccountRelation());
+        assert.equal(incidentModel.get("status"), XM.Incident.CLOSED);
+      });
+      it("Incident status does not revert to assigned from resolved when user is assigned to it", function () {
+        incidentModel.set("status", XM.Incident.RESOLVED);
+        incidentModel.set("assignedTo", new XM.UserAccountRelation());
+        assert.equal(incidentModel.get("status"), XM.Incident.RESOLVED);
+      });
     });
     /**
     @member Privileges