Merge pull request #940 from xtuple/tags/R1_4_5
authorTravis Webb <travis@traviswebb.com>
Sun, 13 Oct 2013 22:07:49 +0000 (15:07 -0700)
committerTravis Webb <travis@traviswebb.com>
Sun, 13 Oct 2013 22:07:49 +0000 (15:07 -0700)
v1.4.5 release

118 files changed:
enyo-client/application/.gitignore
enyo-client/application/source/.gitignore [deleted file]
enyo-client/application/source/en/package.js [deleted file]
enyo-client/application/source/en/strings.js
enyo-client/application/source/ext/.gitignore [deleted file]
enyo-client/application/source/ext/datasource.js
enyo-client/application/source/ext/package.js
enyo-client/application/source/ext/session.js [new file with mode: 0644]
enyo-client/application/source/models/customer.js
enyo-client/application/source/models/prospect.js
enyo-client/application/source/models/sales_order.js
enyo-client/application/source/models/sales_order_base.js
enyo-client/application/source/package.js
enyo-client/application/source/startup.js
enyo-client/application/source/views/grid_box.js
enyo-client/application/source/views/list.js
enyo-client/application/source/views/list_relations.js
enyo-client/application/source/views/workspace.js
enyo-client/application/source/widgets/checkbox.js
enyo-client/database/orm/models/quote.json
enyo-client/database/orm/models/sales_order.json
enyo-client/database/source/en/strings.js [new file with mode: 0644]
enyo-client/database/source/manifest.js
enyo-client/database/source/xm/javascript/customer.sql
enyo-client/database/source/xm/javascript/project.sql
enyo-client/database/source/xm/javascript/prospect.sql [new file with mode: 0644]
enyo-client/database/source/xm/javascript/sales_order.sql [new file with mode: 0644]
enyo-client/database/source/xm/javascript/sales_rep.sql
enyo-client/database/source/xt/views/coiteminfo.sql
enyo-client/database/source/xt/views/customer_prospect.sql
enyo-client/extensions/sample_debug.js [deleted file]
enyo-client/extensions/source/crm/client/en/package.js [deleted file]
enyo-client/extensions/source/crm/client/en/strings.js
enyo-client/extensions/source/crm/client/package.js
enyo-client/extensions/source/crm/database/orm/ext/crm.json
enyo-client/extensions/source/inventory/client/en/package.js [deleted file]
enyo-client/extensions/source/inventory/client/en/strings.js
enyo-client/extensions/source/inventory/client/error.js [deleted file]
enyo-client/extensions/source/inventory/client/models/inventory.js
enyo-client/extensions/source/inventory/client/models/shipment.js
enyo-client/extensions/source/inventory/client/package.js
enyo-client/extensions/source/inventory/client/views/list.js
enyo-client/extensions/source/inventory/client/views/list_relations.js
enyo-client/extensions/source/inventory/client/views/transaction_list.js
enyo-client/extensions/source/inventory/client/views/workspace.js
enyo-client/extensions/source/inventory/database/source/xm/javascript/inventory.sql
enyo-client/extensions/source/project/client/en/package.js [deleted file]
enyo-client/extensions/source/project/client/en/strings.js [deleted file]
enyo-client/extensions/source/project/client/package.js
enyo-client/extensions/source/sales/client/en/package.js [deleted file]
enyo-client/extensions/source/sales/client/en/strings.js
enyo-client/extensions/source/sales/client/package.js
enyo-client/extensions/source/sales/client/postbooks.js
enyo-client/extensions/source/sales/client/views/list_relations.js
enyo-client/extensions/source/sales/client/views/list_relations_box.js
lib/backbone-x/source/document.js
lib/backbone-x/source/ext/session.js
lib/backbone-x/source/model.js
lib/backbone-x/source/model_mixin.js
lib/enyo-x/source/en/package.js [deleted file]
lib/enyo-x/source/en/strings.js
lib/enyo-x/source/package.js
lib/enyo-x/source/views/list.js
lib/enyo-x/source/views/list_relations_editor_box.js
lib/enyo-x/source/views/navigator.js
lib/enyo-x/source/views/package.js
lib/enyo-x/source/views/transaction_list.js [new file with mode: 0644]
lib/enyo-x/source/views/workspace.js
lib/enyo-x/source/widgets/relation.js
lib/module [new symlink]
lib/orm/source/manifest.js
lib/orm/source/xt/functions/js_init.sql
lib/orm/source/xt/functions/set_dictionary.sql [new file with mode: 0644]
lib/orm/source/xt/javascript/session.sql
lib/orm/source/xt/tables/dict.sql [new file with mode: 0644]
lib/orm/source/xt/tables/ext.sql [moved from enyo-client/database/source/xt/tables/ext.sql with 100% similarity]
lib/orm/source/xt/tables/extdep.sql [moved from enyo-client/database/source/xt/tables/extdep.sql with 100% similarity]
lib/orm/source/xt/tables/grpext.sql [moved from enyo-client/database/source/xt/tables/grpext.sql with 100% similarity]
lib/orm/source/xt/tables/usrext.sql [moved from enyo-client/database/source/xt/tables/usrext.sql with 100% similarity]
lib/tools/source/date.js
lib/tools/source/en/package.js [deleted file]
lib/tools/source/en/strings.js
lib/tools/source/ext/string.js
lib/tools/source/foundation.js
lib/tools/source/locale.js
lib/tools/source/package.js
lib/tools/source/request.js [deleted file]
lib/tools/source/response.js [deleted file]
lib/tools/source/session.js
node-datasource/lib/ext/datasource.js
node-datasource/lib/ext/olapsource.js [new file with mode: 0644]
node-datasource/main.js
node-datasource/olapcatalog/olapcatalog.js [new file with mode: 0644]
node-datasource/olapcatalog/package.json [new file with mode: 0644]
node-datasource/routes/app.js
node-datasource/routes/data.js
node-datasource/routes/locale.js [new file with mode: 0644]
node-datasource/routes/olapdata.js [new file with mode: 0644]
node-datasource/routes/report.js
node-datasource/routes/routes.js
node-datasource/sample_config.js
node-datasource/views/app.ejs
node-datasource/views/debug.ejs
node-datasource/xt/foundation/foundation.js
package.json
scripts/build_app.js
scripts/export_dictionary.js [new file with mode: 0755]
scripts/import_dictionary.js [new file with mode: 0755]
scripts/install_xtuple.sh
scripts/lib/build_all.js
scripts/lib/build_client.js
scripts/lib/build_database.js
scripts/lib/build_dictionary.js [new file with mode: 0644]
test/mocha/database/discovery.js
test/mocha/lib/crud.js
test/mocha/models/prospect.js [new file with mode: 0644]
test/mocha/routes/export.js
test/mocha/routes/file.js

index 10f5876..ce32181 100644 (file)
@@ -1,7 +1,6 @@
 *.DS_Store
 build
 *.swp
-/source/ext/imports.js
 /test/selenium/lib/loginData.js
 tmp
 npm-debug.log
diff --git a/enyo-client/application/source/.gitignore b/enyo-client/application/source/.gitignore
deleted file mode 100644 (file)
index 8b13789..0000000
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/enyo-client/application/source/en/package.js b/enyo-client/application/source/en/package.js
deleted file mode 100644 (file)
index 66ccc0b..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-enyo.depends(
-  "strings.js"
-);
index f545319..b22c5a5 100644 (file)
-// ==========================================================================
-// Project:   XT` Strings
-// Copyright: Â©2011 OpenMFG LLC, d/b/a xTuple
-// ==========================================================================
-/*globals XT */
+/*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 XT:true */
 
 // Place strings you want to localize here.  In your app, use the key and
 // localize it using "key string".loc().  HINT: For your key names, use the
 // english string with an underscore in front.  This way you can still see
 // how your UI will look and you'll notice right away when something needs a
 // localized string added to this file!
-//
 
-var lang = XT.stringsFor("en_US", {
+(function () {
+  "use strict";
 
-  // ********
-  // Models
-  // ********
+  var lang = XT.stringsFor("en_US", {
 
-  "_analysis": "Analysis",
-  "_all": "All",
-  "_annualy": "Annually",
-  "_asset": "Asset",
-  "_assigned": "Assigned",
-  "_automatic": "Automatic",
-  "_automaticOverride": "Override Allowed",
-  "_biWeekly": "Bi-Weekly",
-  "_closed": "Closed",
-  "_commission": "Commission",
-  "_completed": "Completed",
-  "_concept": "Concept",
-  "_confirmed": "Confirmed",
-  "_daily": "Daily",
-  "_database": "Database",
-  "_databaseInformation": "Database Information",
-  "_effectiveDate": "Effective Date",
-  "_equity": "Equity",
-  "_expense": "Expense",
-  "_expirationDate": "Expiration Date",
-  "_exportVcf": "Export VCF",
-  "_feedback": "Feedback",
-  "_hourly": "Hourly",
-  "_individual": "Individual",
-  "_information": "Information",
-  "_inProcess": "In Process",
-  "_liability": "Liability",
-  "_manual": "Manual",
-  "_monthly": "Monthly",
-  "_new": "New",
-  "_organization": "Organization",
-  "_resolved": "Resolved",
-  "_revenue": "Revenue",
-  "_salaried": "Salaried",
-  "_systemConfiguration": "System Configuration",
-  "_weekly": "Weekly",
+    // ********
+    // Models
+    // ********
 
-  // ********
-  // Labels
-  // ********
+    "_analysis": "Analysis",
+    "_all": "All",
+    "_annualy": "Annually",
+    "_asset": "Asset",
+    "_assigned": "Assigned",
+    "_automatic": "Automatic",
+    "_automaticOverride": "Override Allowed",
+    "_biWeekly": "Bi-Weekly",
+    "_closed": "Closed",
+    "_commission": "Commission",
+    "_completed": "Completed",
+    "_concept": "Concept",
+    "_confirmed": "Confirmed",
+    "_daily": "Daily",
+    "_database": "Database",
+    "_databaseInformation": "Database Information",
+    "_effectiveDate": "Effective Date",
+    "_equity": "Equity",
+    "_expense": "Expense",
+    "_expirationDate": "Expiration Date",
+    "_exportVcf": "Export VCF",
+    "_feedback": "Feedback",
+    "_hourly": "Hourly",
+    "_individual": "Individual",
+    "_information": "Information",
+    "_inProcess": "In Process",
+    "_liability": "Liability",
+    "_manual": "Manual",
+    "_monthly": "Monthly",
+    "_new": "New",
+    "_organization": "Organization",
+    "_resolved": "Resolved",
+    "_revenue": "Revenue",
+    "_salaried": "Salaried",
+    "_systemConfiguration": "System Configuration",
+    "_weekly": "Weekly",
 
-  "_above": "Above",
-  "_abbreviation": "Abbreviation",
-  "_abbreviationLong": "Abbreviation Long",
-  "_abbreviationShort": "Abbreviation Short",
-  "_account": "Account",
-  "_accounting": "Accounting",
-  "_accountNumber": "Account Number",
-  "_accountNumberGeneration": "Account Number Generation",
-  "_accountType": "Account Type",
-  "_accounts": "Accounts",
-  "_active": "Active",
-  "_actual": "Actual",
-  "_actualClose": "Actual Close",
-  "_actualExpenses": "Actual Expenses",
-  "_actualExpensesTotal": "Total Expenses Actual",
-  "_actualHours": "Actual Hours",
-  "_actualHoursTotal": "Total Hours Actual",
-  "_additional": "Additional",
-  "_address": "Address",
-  "_address1": "Address1",
-  "_address2": "Address2",
-  "_address3": "Address3",
-  "_addressCharacteristic": "Address Characteristic",
-  "_addressComment": "Address Comment",
-  "_addresses": "Addresses",
-  "_advanced" : "Advanced",
-  "_alarms": "Alarms",
-  "_allocatedCredit": "Allocated Credit",
-  "_allowableItems": "Allowable Items",
-  "_altEmphasisColor": "Alt Emphasis Color",
-  "_alternate": "Alternate",
-  "_alternateAddresses": "Alternate Addresses",
-  "_amEx": "AmEx",
-  "_amount": "Amount",
-  "_and": "and",
-  "_array": "Array",
-  "_ascending": "Ascending",
-  "_assignDate": "Assign Date",
-  "_assignedTo": "Assigned To",
-  "_attach": "Attach",
-  "_authorize": "Authorize",
-  "_averageCost": "Average Cost",
-  "_backorder": "Accepts Backorders",
-  "_balance": "Balance",
-  "_balanceExpensesTotal": "Balance Expenses Total",
-  "_balanceExpenses": "Balance Expenses",
-  "_balanceHours": "Balance Hours",
-  "_balanceForward": "Balance Forward",
-  "_balanceHoursTotal": "Balance Hours Total",
-  "_balanceMethod": "Balance Method",
-  "_basis": "Basis",
-  "_bcc": "Bcc",
-  "_billingContact": "Billing Contact",
-  "_billingRate": "Billing Rate",
-  "_billTo": "Bill To",
-  "_billtoAddress1": "Bill To Address",
-  "_billtoName": "Bill To Name",
-  "_blanketPurchaseOrders": "Uses Blanket Purchase Orders",
-  "_body": "Body",
-  "_boolean": "Boolean",
-  "_breeder": "Breeder",
-  "_budgeted": "Budgeted",
-  "_budgetedExpenses": "Budgeted Expenses",
-  "_budgetedExpensesTotal": "Total Expenses Budgeted",
-  "_budgetedHours": "Budgeted Hours",
-  "_budgetedHoursTotal": "Total Hours Budgeted",
-  "_byProduct": "By Product",
-  "_canCreateUsers": "Can Create Users",
-  "_category": "Category",
-  "_cc": "Cc",
-  "_ccv": "CCV",
-  "_changeAll?": "Would you like to update the address across them all?",
-  "_changeLog": "Change Log",
-  "_characteristic": "Characteristic",
-  "_characteristicType": "Characteristic Type",
-  "_characteristicOption": "Characteristic Option",
-  "_class": "Class",
-  "_classCode": "Class Code",
-  "_classCodes": "Class Codes",
-  "_close": "Close",
-  "_closeDate": "Close Date",
-  "_code": "Code",
-  "_configure": "Configure",
-  "_commentType": "Comment Type",
-  "_commentsEditable": "Comments Editable",
-  "_company": "Company",
-  "_competitor": "Competitor",
-  "_completeDate": "Complete Date",
-  "_configureSO": "Configure Sales Orders",
-  "_contact": "Contact",
-  "_contactRelations": "Contacts",
-  "_contacts": "Contacts",
-  "_coProduct": "Co-Product",
-  "_copyToShipTo": "Ship To",
-  "_costCategories": "Cost Categories",
-  "_costCategory": "Cost Category",
-  "_costMethod": "Cost Method",
-  "_costing": "Costing",
-  "_costs": "Costs",
-  "_county": "County",
-  "_correspondenceContact": "Correspondence Contact",
-  "_countries": "Countries",
-  "_created": "Created",
-  "_createdBy": "Created By",
-  "_credit": "Credit",
-  "_creditCard": "Credit Card",
-  "_creditCards": "Credit Cards",
-  "_creditHolding": "On Credit Holding",
-  "_creditLimit": "Credit Limit",
-  "_creditRating": "Credit Rating",
-  "_creditStatus": "Credit Status",
-  "_creditWarning": "On Credit Warning",
-  "_crm": "CRM",
-  "_cRM": "CRM",
-  "_currencies": "Currencies",
-  "_currency": "Currency",
-  "_currencyAbbreviation": "Currency Abbreviation",
-  "_currencyName": "Currency Name",
-  "_currencyNumber": "Currency Number",
-  "_currencySymbol": "Currency Symbol",
-  "_custPO": "Customer PO",
-  "_customerPartNumber": "Customer P/N",
-  "_custPrice": "Cust. Price",
-  "_customer": "Customer",
-  "_customerGroup": "Customer Groups",
-  "_customerType": "Customer Type",
-  "_customerTypes": "Customer Types",
-  "_customerPrice": "Customer Price",
-  "_customers": "Customers",
-  "_customerProspect": "Customer / Prospect",
-  "_data": "Data",
-  "_dataState": "Data State",
-  "_date": "Date",
-  "_dateRange": "Date Range",
-  "_days": "Days",
-  "_deactivate": "Deactivate",
-  "_default": "Default",
-  "_defaults": "Defaults",
-  "_defaultTaxZone": "Default Tax Zone",
-  "_deferred": "Deferred",
-  "_descending": "Descending",
-  "_detach": "Detach",
-  "_detail": "Detail",
-  "_description": "Description",
-  "_description1": "Description1",
-  "_description2": "Description2",
-  "_discount": "Discount",
-  "_discover": "Discover",
-  "_disableExport": "Disable Export",
-  "_document": "Document",
-  "_documentDate": "Document Date",
-  "_documentNumber": "Document #",
-  "_documentType": "Document Type",
-  "_dueDate": "Due Date",
-  "_dueDays": "Due Days",
-  "_effective": "Effective",
-  "_email": "Email",
-  "_emailAddresses": "Email Addresses",
-  "_emailProfile": "Email Profile",
-  "_emphasisColor": "Emphasis Color",
-  "_employee": "Employee",
-  "_employeeGroup": "Employee Group",
-  "_employeeGroups": "Employee Groups",
-  "_employees": "Employees",
-  "_end": "End",
-  "_endBalance": "End Balance",
-  "_ending": "Ending",
-  "_endingLabel": "Ending Label",
-  "_enterNew": "Enter New",
-  "_equals": "Equals",
-  "_error": "Error",
-  "_errorColor": "Error Color",
-  "_eventRecipient": "Event Recipient",
-  "_excludeProspects": "Exclude Prospects",
-  "_expenseCategories": "Expense Categories",
-  "_expenseCategory": "Expense Category",
-  "_expenses": "Expenses",
-  "_expiredColor": "Expired Color",
-  "_expires": "Expires",
-  "_expireDate": "Expire Date",
-  "_exportContact": "Export Contact",
-  "_extended": "Extended",
-  "_extensions": "Extensions",
-  "_extPrice": "Ext Price",
-  "_extendedPrice": "Extended Price",
-  "_extendedDescription": "Extended Description",
-  "_extendedPriceScale": "Extended Price Scale",
-  "_externalReference": "External Reference",
-  "_delivery": "Delivery",
-  "_department": "Department",
-  "_departments": "Departments",
-  "_fax": "Fax",
-  "_file": "File",
-  "_files": "Files",
-  "_filter": "Filter",
-  "_filterBy": "Filter By",
-  "_financials": "Financials",
-  "_firstName": "First Name",
-  "_flag": "Flag",
-  "_fob": "F.O.B.",
-  "_for": "For",
-  "_fractional": "Fractional",
-  "_freightClass": "Freight Class",
-  "_frequency": "Frequency",
-  "_freeFormBill": "Allow Free-Form Bill-To",
-  "_freeFormShip": "Allow Free-Form Ship-Tos",
-  "_freight": "Freight",
-  "_freightWeight": "Freight Weight",
-  "_from": "From",
-  "_fromDate": "From Date",
-  "_futureColor": "Future Color",
-  "_gateway": "Gateway",
-  "_goodStanding": "In Good Standing",
-  "_graceDays": "Grace Days",
-  "_grantedPrivileges": "Granted Privileges",
-  "_grantedUserAccountRoles": "Granted User Account Roles",
-  "_groupSequence": "Group Sequence",
-  "_groups": "Groups",
-  "_header": "Header",
-  "_history": "History",
-  "_holdType": "Hold Type",
-  "_home": "Home",
-  "_honorific": "Honorific",
-  "_honorifics": "Honorifics",
-  "_hours": "Hours",
-  "_hrs": "hrs.",
-  "_image": "Image",
-  "_images": "Images",
-  "_inactive": "Inactive",
-  "_incident": "Incident",
-  "_incidentCategories": "Incident Categories",
-  "_incidentCategory": "Incident Category",
-  "_incidentChangedStatus": "The status of the following incident has been changed to {status}",
-  "_incidentCreatedStatus": "The following incident has been created with status {status}",
-  "_incidentCreated": "The following incident has been CREATED",
-  "_incidentEmailProfile": "Incident Email Profiles",
-  "_incidentEmailProfiles": "Incident Email Profiles",
-  "_incidentHistory": "Incident History",
-  "_incidentNewComment": "A new COMMENT has been added to the following incident",
-  "_incidentRelations": "Incidents",
-  "_incidentResolution": "Incident Resolution",
-  "_incidentResolutions": "Incident Resolutions",
-  "_incidents": "Incidents",
-  "_incidentSeverities": "Incident Severities",
-  "_incidentSeverity": "Incident Severity",
-  "_incidentStatus": "Status",
-  "_incidentUpdated": "The following incident has been UPDATED",
-  "_inventoryHistory": "Inventory History",
-  "_initials": "Initials",
-  "_inventoryUnit": "Inventory Unit",
-  "_isActive": "Active",
-  "_isAddresses": "Addresses",
-  "_isBase": "Base",
-  "_isContacts": "Contacts",
-  "_isDebit": "Debit",
-  "_isDefault": "Default",
-  "_isDeleted": "Deleted",
-  "_isEmployees": "Employees",
-  "_isEvent": "Event",
-  "_isFractional": "Fractional",
-  "_isIncidents": "Incidents",
-  "_isItemWeight": "Item Weight",
-  "_isItems": "Items",
-  "_isMessage": "Message",
-  "_isOpportunities": "Opportunities",
-  "_isPicklist": "Picklist",
-  "_isPosted": "Posted",
-  "_isPrinted": "Printed",
-  "_isPublic": "Public",
-  "_isSold": "Sold",
-  "_isSearchable": "Searchable",
-  "_isSystem": "System",
-  "_item": "Item",
-  "_itemConversion": "Item Conversion",
-  "_itemNumber": "Item Number",
-  "_itemGroup": "Item Group",
-  "_itemGroups": "Item Groups",
-  "_itemSite": "Item Site",
-  "_itemSites": "Item Sites",
-  "_itemType": "Item Type",
-  "_itemUnitConversion": "Item Unit Conversion",
-  "_itemUnitType": "Item Unit Type",
-  "_items": "Items",
-  "_jobTitle": "Job Title",
-  "_kit": "Kit",
-  "_language": "Language",
-  "_lastName": "Last Name",
-  "_latestComment": "Latest Comment",
-  "_ledgerAccount": "Ledger Account",
-  "_ledgerAccounts": "Ledger Accounts",
-  "_limitToList": "Limit to List",
-  "_line1": "Line1",
-  "_line2": "Line2",
-  "_line3": "Line3",
-  "_lineItems": "Line Items",
-  "_lineNumber": "Line Number",
-  "_lines": "Lines",
-  "_list" : "List",
-  "_listPrice": "List Price",
-  "_listPriceDiscount": "List Price Discount",
-  "_locale": "Locale",
-  "_login": "Login",
-  "_logout": "Logout",
-  "_lotSerial": "Lot/Serial",
-  "_manager": "Manager",
-  "_mainAddress": "Main Address",
-  "_manufactured": "Manufactured",
-  "_map": "Map",
-  "_margin": "Margin",
-  "_markup": "Markup",
-  "_mask": "Mask",
-  "_masterCard": "MasterCard",
-  "_match": "Match",
-  "_maximum": "Maximum",
-  "_menu": "Menu",
-  "_messageRecipient": "Message Recipient",
-  "_middleName": "Middle Name",
-  "_miscCharge": "Misc. Charge",
-  "_miscellaneous": "Miscellaneous",
-  "_module": "Module",
-  "_monthExpired": "Expiration Month",
-  "_multiple": "Multiple",
-  "_mustRemoveDependentExtensions": "Removing Dependent Extensions",
-  "_name": "Name",
-  "_neither": "Neither",
-  "_nextNumber": "Next Number",
-  "_nextCheckNumber": "Next Check Number",
-  "_none": "NONE",
-  "_notes": "Notes",
-  "_notSold": "Not Sold",
-  "_number": "Number",
-  "_object": "Object",
-  "_offset": "Offset",
-  "_other": "Other",
-  "_openBalance": "Open Balance",
-  "_openDate": "Open Date",
-  "_openItems": "Open Items",
-  "_opportunities": "Opportunities",
-  "_opportunity": "Opportunity",
-  "_opportunityRelations": "Opportunities",
-  "_opportunitySource": "Opportunity Source",
-  "_opportunitySources": "Opportunity Sources",
-  "_opportunityStage": "Opportunity Stage",
-  "_opportunityStages": "Opportunity Stages",
-  "_opportunityType": "Opportunity Type",
-  "_opportunityTypes": "Opportunity Types",
-  "_open": "Open",
-  "_options": "Options",
-  "_order": "Order",
-  "_orderDate": "Order Date",
-  "_orderNumber": "Order Number",
-  "_orderNotes": "Order Notes",
-  "_orderType": "Order Type",
-  "_ordered": "Ordered",
-  "_outsideProcess": "Outside Process",
-  "_owner": "Owner",
-  "_packDate": "Pack Date",
-  "_packing": "Packing",
-  "_paid": "Paid",
-  "_partialShip": "Accepts Partial Shipments",
-  "_partner": "Partner",
-  "_pattern": "Pattern",
-  "_password": "Password",
-  "_path": "Path",
-  "_pending": "Pending",
-  "_percent": "Percent",
-  "_period": "Period",
-  "_phantom": "Phantom",
-  "_phone": "Phone",
-  "_plannerCode": "Planner Code",
-  "_plannerCodes": "Planner Codes",
-  "_planning": "Planning",
-  "_pleaseLogin": "Please Login",
-  "_policy": "Policy",
-  "_post": "Post",
-  "_postbooks": "PostBooks",
-  "_price": "Price",
-  "_priceMode": "Price Mode",
-  "_priceUnit": "Price UOM",
-  "_priceUnitRatio": "Price Unit Ratio",
-  "_primaryContact": "Primary Contact",
-  "_primaryEmail": "Primary Email",
-  "_printed": "Printed",
-  "_priority": "Priority",
-  "_priorities": "Priorities",
-  "_privilege": "Privilege",
-  "_privileges": "Privileges",
-  "_probability": "Probability",
-  "_productCategories": "Product Categories",
-  "_productCategory": "Product Category",
-  "_product": "Product",
-  "_products": "Products",
-  "_profit": "Profit",
-  "_project": "Project",
-  "_projectAssignedTo": "Project Assigned To",
-  "_projectOwner": "Project Owner",
-  "_projectRelations": "Projects",
-  "_projectStatus": "Project Status",
-  "_projectTask": "Project Task",
-  "_projectTasks": "Project Tasks",
-  "_projectTaskStatus": "Project Task Status",
-  "_projects": "Projects",
-  "_promiseDate": "Promised Date",
-  "_properName": "Proper Name",
-  "_propername": "Propername",
-  "_prospect": "Prospect",
-  "_prospects": "Prospects",
-  "_proximo": "Proximo",
-  "_public": "Public",
-  "_purchaseOrderDate": "Purchased",
-  "_purchased": "Purchased",
-  "_purchaseOrderNumber": "Purchase Order",
-  "_qualifier": "Qualifier",
-  "_quantity": "Quantity",
-  "_quantityAfter": "Quantity After",
-  "_quantityBefore": "Quantity Before",
-  "_quantityOrd": "Qty Ordered",
-  "_quantityUnit": "Qty UOM",
-  "_quantityUnitRatio": "Qty Unit Ratio",
-  "_quantityShip": "Qty Shipped",
-  "_quote": "Quote",
-  "_quoteDate": "Quote Date",
-  "_quoteLine": "Quote Line",
-  "_quotes": "Quotes",
-  "_purchaseOrder": "Purchase Order",
-  "_regular": "Regular",
-  "_recurrences": "Recurrences",
-  "_recurring": "Recurring",
-  "_reEnterPassword": "Re-Enter Password",
-  "_reference": "Reference",
-  "_relationships": "Relationships",
-  "_replyTo": "Reply To",
-  "_requireCCV": "Require CCV",
-  "_required": "Required",
-  "_resolution": "Resolution",
-  "_return": "Return",
-  "_roles": "Roles",
-  "_salesOrder": "Sales Order",
-  "_salesOrders": "Sales Orders",
-  "_salesOrderLine": "Sales Order Line",
-  "_salesOrderDate": "Sales Order Date",
-  "_salesRep": "Sales Rep",
-  "_salesReps": "Sales Reps",
-  "_saleType": "Sale Type",
-  "_saleTypes": "Sale Types",
-  "_schedule": "Schedule",
-  "_schedDate": "Sched Date",
-  "_scheduleDate": "Schedule Date",
-  "_secondaryContact": "Secondary Contact",
-  "_selectOrganization": "Select Organization",
-  "_sense": "Sense",
-  "_sequence": "Sequence",
-  "_series": "Series",
-  "_sessionTimedOut": "Your session has timed out",
-  "_settings": "Settings",
-  "_setup": "Setup",
-  "_severity": "Severity",
-  "_shared": "Shared",
-  "_shift": "Shift",
-  "_shifts": "Shifts",
-  "_shipping": "Shipping",
-  "_shipCharge": "Shipping Charges",
-  "_shipComplete": "Ship Complete",
-  "_shipForm": "Shipping Form",
-  "_shipVia": "Ship Via",
-  "_shipTo": "Ship To",
-  "_shiptoAddress1": "Ship To Address",
-  "_shiptoName": "Ship To Name",
-  "_shiptoPhone": "Ship To Phone",
-  "_shipZone": "Ship Zone",
-  "_shipZones": "Ship Zones",
-  "_shippingNotes": "Shipping Notes",
-  "_site": "Site",
-  "_siteCode": "Site Code",
-  "_siteType": "Site Type",
-  "_siteTypes": "Site Types",
-  "_sites": "Sites",
-  "_showClosed": "Show Closed",
-  "_showCompleted": "Show Complete",
-  "_showCompletedOnly": "Show Complete Only",
-  "_showExpired": "Show Expired",
-  "_showUnReleased": "Show Unreleased",
-  "_showInactive": "Show Inactive",
-  "_sold": "Sold",
-  "_soldRanking": "Sold Ranking",
-  "_source": "Source",
-  "_sourceType": "Source Type",
-  "_stage": "Stage",
-  "_standardCost": "Standard Cost",
-  "_start": "Start",
-  "_startDate": "Start Date",
-  "_states": "States",
-  "_status": "Status",
-  "_string": "String",
-  "_subject": "Subject",
-  "_subtotal": "Subtotal",
-  "_suffix": "Suffix",
-  "_summary": "Summary",
-  "_symbol": "Symbol",
-  "_system": "System",
-  "_target": "Target",
-  "_targetClose": "Target Close",
-  "_targetType": "Target Type",
-  "_task": "Task",
-  "_tasks": "Tasks",
-  "_tax": "Tax",
-  "_taxCode": "Tax Code",
-  "_taxType": "Tax Type",
-  "_taxAssignment": "Tax Assignment",
-  "_taxAuthority": "Tax Authority",
-  "_taxClass": "Tax Class",
-  "_taxRate": "Tax Rate",
-  "_taxRegistration": "Tax Registrations",
-  "_taxZone": "Tax Zone",
-  "_terms": "Terms",
-  "_testMode": "Test Mode",
-  "_text": "Text",
-  "_time": "Time",
-  "_to": "To",
-  "_toDate": "To Date",
-  "_toDo": "To Do",
-  "_toDoRelations": "To Dos",
-  "_toDoStatus": "To Do Status",
-  "_toDos": "To Dos",
-  "_tooling": "Tooling",
-  "_total": "Total",
-  "_totals": "Totals",
-  "_toUnit": "To Unit",
-  "_transactionDate": "Transaction Date",
-  "_transactionType": "Transaction Type",
-  "_transactionSuccessful": "Transaction Successful",
-  "_trigger": "Trigger",
-  "_type": "Type",
-  "_unit": "Unit",
-  "_unitPrice": "Net Unit Price",
-  "_unitCosts": "Unit Costs",
-  "_units": "Units",
-  "_unitType": "Unit Type",
-  "_unknown": "(unknown)",
-  "_unitCost": "Unit Cost",
-  "_unsupportedGateway": "Unsupported Gateway",
-  "_uom": "UOM",
-  "_updated": "Updated",
-  "_updatedBy": "Updated By",
-  "_url": "Link",
-  "_urls": "Links",
-  "_useDescription": "Use Description",
-  "_useEnhancedAuth": "Use Enhanced Authentication",
-  "_user": "User",
-  "_userAccount": "User Account",
-  "_userAccounts": "User Accounts",
-  "_userAccountRole": "User Account Role",
-  "_userAccountRoles": "User Account Roles",
-  "_username": "Username",
-  "_userPreference": "User Preference",
-  "_userPreferences": "User Preferences",
-  "_usesPurchaseOrders": "Uses Purchase Orders",
-  "_validator": "Validator",
-  "_value": "Value",
-  "_valueAfter": "Value After",
-  "_valueBefore": "Value Before",
-  "_vendor": "Vendor",
-  "_vendorType": "Vendor Type",
-  "_version": "Version",
-  "_visa": "Visa",
-  "_voucher": "Voucher",
-  "_wage": "Wage",
-  "_wageType": "Wage Type",
-  "_warningColor": "Warning Color",
-  "_web": "Web",
-  "_webAddress": "Web Address",
-  "_welcome": "Welcome",
-  "_wholesalePrice": "Wholesale Price",
-  "_workOrder": "Work Order",
-  "_xtuplePostbooks": "PostBooks",
-  "_yearExpired": "Expiration Year",
+    // ********
+    // Labels
+    // ********
+    "_above": "Above",
+    "_abbreviation": "Abbreviation",
+    "_abbreviationLong": "Abbreviation Long",
+    "_abbreviationShort": "Abbreviation Short",
+    "_account": "Account",
+    "_accounting": "Accounting",
+    "_accountNumber": "Account Number",
+    "_accountNumberGeneration": "Account Number Generation",
+    "_accountType": "Account Type",
+    "_accounts": "Accounts",
+    "_active": "Active",
+    "_actual": "Actual",
+    "_actualClose": "Actual Close",
+    "_actualExpenses": "Actual Expenses",
+    "_actualExpensesTotal": "Total Expenses Actual",
+    "_actualHours": "Actual Hours",
+    "_actualHoursTotal": "Total Hours Actual",
+    "_additional": "Additional",
+    "_address": "Address",
+    "_address1": "Address1",
+    "_address2": "Address2",
+    "_address3": "Address3",
+    "_addressCharacteristic": "Address Characteristic",
+    "_addressComment": "Address Comment",
+    "_addresses": "Addresses",
+    "_advanced" : "Advanced",
+    "_alarms": "Alarms",
+    "_allocatedCredit": "Allocated Credit",
+    "_allowableItems": "Allowable Items",
+    "_altEmphasisColor": "Alt Emphasis Color",
+    "_alternate": "Alternate",
+    "_alternateAddresses": "Alternate Addresses",
+    "_amEx": "AmEx",
+    "_amount": "Amount",
+    "_and": "and",
+    "_array": "Array",
+    "_ascending": "Ascending",
+    "_assignDate": "Assign Date",
+    "_assignedTo": "Assigned To",
+    "_attach": "Attach",
+    "_authorize": "Authorize",
+    "_averageCost": "Average Cost",
+    "_backorder": "Accepts Backorders",
+    "_balance": "Balance",
+    "_balanceExpensesTotal": "Balance Expenses Total",
+    "_balanceExpenses": "Balance Expenses",
+    "_balanceHours": "Balance Hours",
+    "_balanceForward": "Balance Forward",
+    "_balanceHoursTotal": "Balance Hours Total",
+    "_balanceMethod": "Balance Method",
+    "_basis": "Basis",
+    "_bcc": "Bcc",
+    "_billingContact": "Billing Contact",
+    "_billingRate": "Billing Rate",
+    "_billTo": "Bill To",
+    "_billtoAddress1": "Bill To Address",
+    "_billtoName": "Bill To Name",
+    "_blanketPurchaseOrders": "Uses Blanket Purchase Orders",
+    "_body": "Body",
+    "_boolean": "Boolean",
+    "_breeder": "Breeder",
+    "_budgeted": "Budgeted",
+    "_budgetedExpenses": "Budgeted Expenses",
+    "_budgetedExpensesTotal": "Total Expenses Budgeted",
+    "_budgetedHours": "Budgeted Hours",
+    "_budgetedHoursTotal": "Total Hours Budgeted",
+    "_byProduct": "By Product",
+    "_canCreateUsers": "Can Create Users",
+    "_category": "Category",
+    "_cc": "Cc",
+    "_ccv": "CCV",
+    "_changeAll?": "Would you like to update the address across them all?",
+    "_changeLog": "Change Log",
+    "_characteristic": "Characteristic",
+    "_characteristicType": "Characteristic Type",
+    "_characteristicOption": "Characteristic Option",
+    "_class": "Class",
+    "_classCode": "Class Code",
+    "_classCodes": "Class Codes",
+    "_close": "Close",
+    "_closeDate": "Close Date",
+    "_code": "Code",
+    "_configure": "Configure",
+    "_commentType": "Comment Type",
+    "_commentsEditable": "Comments Editable",
+    "_company": "Company",
+    "_competitor": "Competitor",
+    "_completeDate": "Complete Date",
+    "_configureSO": "Configure Sales Orders",
+    "_contact": "Contact",
+    "_contactRelations": "Contacts",
+    "_contacts": "Contacts",
+    "_coProduct": "Co-Product",
+    "_copyToShipTo": "Ship To",
+    "_costCategories": "Cost Categories",
+    "_costCategory": "Cost Category",
+    "_costMethod": "Cost Method",
+    "_costing": "Costing",
+    "_costs": "Costs",
+    "_county": "County",
+    "_correspondenceContact": "Correspondence Contact",
+    "_countries": "Countries",
+    "_created": "Created",
+    "_createdBy": "Created By",
+    "_credit": "Credit",
+    "_creditCard": "Credit Card",
+    "_creditCards": "Credit Cards",
+    "_creditHolding": "On Credit Holding",
+    "_creditLimit": "Credit Limit",
+    "_creditRating": "Credit Rating",
+    "_creditStatus": "Credit Status",
+    "_creditWarning": "On Credit Warning",
+    "_crm": "CRM",
+    "_cRM": "CRM",
+    "_currencies": "Currencies",
+    "_currency": "Currency",
+    "_currencyAbbreviation": "Currency Abbreviation",
+    "_currencyName": "Currency Name",
+    "_currencyNumber": "Currency Number",
+    "_currencySymbol": "Currency Symbol",
+    "_custPO": "Customer PO",
+    "_customerPartNumber": "Customer P/N",
+    "_custPrice": "Cust. Price",
+    "_customer": "Customer",
+    "_customerGroup": "Customer Groups",
+    "_customerType": "Customer Type",
+    "_customerTypes": "Customer Types",
+    "_customerPrice": "Customer Price",
+    "_customers": "Customers",
+    "_customerProspect": "Customer / Prospect",
+    "_data": "Data",
+    "_dataState": "Data State",
+    "_date": "Date",
+    "_dateRange": "Date Range",
+    "_days": "Days",
+    "_deactivate": "Deactivate",
+    "_default": "Default",
+    "_defaults": "Defaults",
+    "_defaultTaxZone": "Default Tax Zone",
+    "_deferred": "Deferred",
+    "_descending": "Descending",
+    "_detach": "Detach",
+    "_detail": "Detail",
+    "_description": "Description",
+    "_description1": "Description1",
+    "_description2": "Description2",
+    "_discount": "Discount",
+    "_discover": "Discover",
+    "_disableExport": "Disable Export",
+    "_document": "Document",
+    "_documentDate": "Document Date",
+    "_documentNumber": "Document #",
+    "_documentType": "Document Type",
+    "_dueDate": "Due Date",
+    "_dueDays": "Due Days",
+    "_effective": "Effective",
+    "_email": "Email",
+    "_emailAddresses": "Email Addresses",
+    "_emailProfile": "Email Profile",
+    "_emphasisColor": "Emphasis Color",
+    "_employee": "Employee",
+    "_employeeGroup": "Employee Group",
+    "_employeeGroups": "Employee Groups",
+    "_employees": "Employees",
+    "_end": "End",
+    "_endBalance": "End Balance",
+    "_ending": "Ending",
+    "_endingLabel": "Ending Label",
+    "_enterNew": "Enter New",
+    "_equals": "Equals",
+    "_error": "Error",
+    "_errorColor": "Error Color",
+    "_eventRecipient": "Event Recipient",
+    "_excludeProspects": "Exclude Prospects",
+    "_expenseCategories": "Expense Categories",
+    "_expenseCategory": "Expense Category",
+    "_expenses": "Expenses",
+    "_expiredColor": "Expired Color",
+    "_expires": "Expires",
+    "_expireDate": "Expire Date",
+    "_exportContact": "Export Contact",
+    "_extended": "Extended",
+    "_extensions": "Extensions",
+    "_extPrice": "Ext Price",
+    "_extendedPrice": "Extended Price",
+    "_extendedDescription": "Extended Description",
+    "_extendedPriceScale": "Extended Price Scale",
+    "_externalReference": "External Reference",
+    "_delivery": "Delivery",
+    "_department": "Department",
+    "_departments": "Departments",
+    "_fax": "Fax",
+    "_file": "File",
+    "_files": "Files",
+    "_filter": "Filter",
+    "_filterBy": "Filter By",
+    "_financials": "Financials",
+    "_firstName": "First Name",
+    "_flag": "Flag",
+    "_fob": "F.O.B.",
+    "_for": "For",
+    "_fractional": "Fractional",
+    "_freightClass": "Freight Class",
+    "_frequency": "Frequency",
+    "_freeFormBill": "Allow Free-Form Bill-To",
+    "_freeFormShip": "Allow Free-Form Ship-Tos",
+    "_freight": "Freight",
+    "_freightWeight": "Freight Weight",
+    "_from": "From",
+    "_fromDate": "From Date",
+    "_futureColor": "Future Color",
+    "_gateway": "Gateway",
+    "_goodStanding": "In Good Standing",
+    "_graceDays": "Grace Days",
+    "_grantedPrivileges": "Granted Privileges",
+    "_grantedUserAccountRoles": "Granted User Account Roles",
+    "_groupSequence": "Group Sequence",
+    "_groups": "Groups",
+    "_header": "Header",
+    "_history": "History",
+    "_holdType": "Hold Type",
+    "_home": "Home",
+    "_honorific": "Honorific",
+    "_honorifics": "Honorifics",
+    "_hours": "Hours",
+    "_hrs": "hrs.",
+    "_image": "Image",
+    "_images": "Images",
+    "_inactive": "Inactive",
+    "_incident": "Incident",
+    "_incidentCategories": "Incident Categories",
+    "_incidentCategory": "Incident Category",
+    "_incidentChangedStatus": "The status of the following incident has been changed to {status}",
+    "_incidentCreatedStatus": "The following incident has been created with status {status}",
+    "_incidentCreated": "The following incident has been CREATED",
+    "_incidentEmailProfile": "Incident Email Profiles",
+    "_incidentEmailProfiles": "Incident Email Profiles",
+    "_incidentHistory": "Incident History",
+    "_incidentNewComment": "A new COMMENT has been added to the following incident",
+    "_incidentRelations": "Incidents",
+    "_incidentResolution": "Incident Resolution",
+    "_incidentResolutions": "Incident Resolutions",
+    "_incidents": "Incidents",
+    "_incidentSeverities": "Incident Severities",
+    "_incidentSeverity": "Incident Severity",
+    "_incidentStatus": "Status",
+    "_incidentUpdated": "The following incident has been UPDATED",
+    "_inventoryHistory": "Inventory History",
+    "_initials": "Initials",
+    "_inventoryUnit": "Inventory Unit",
+    "_isActive": "Active",
+    "_isAddresses": "Addresses",
+    "_isBase": "Base",
+    "_isContacts": "Contacts",
+    "_isDebit": "Debit",
+    "_isDefault": "Default",
+    "_isDeleted": "Deleted",
+    "_isEmployees": "Employees",
+    "_isEvent": "Event",
+    "_isFractional": "Fractional",
+    "_isIncidents": "Incidents",
+    "_isItemWeight": "Item Weight",
+    "_isItems": "Items",
+    "_isMessage": "Message",
+    "_isOpportunities": "Opportunities",
+    "_isPicklist": "Picklist",
+    "_isPosted": "Posted",
+    "_isPrinted": "Printed",
+    "_isPublic": "Public",
+    "_isSold": "Sold",
+    "_isSearchable": "Searchable",
+    "_isSystem": "System",
+    "_item": "Item",
+    "_itemConversion": "Item Conversion",
+    "_itemNumber": "Item Number",
+    "_itemGroup": "Item Group",
+    "_itemGroups": "Item Groups",
+    "_itemSite": "Item Site",
+    "_itemSites": "Item Sites",
+    "_itemType": "Item Type",
+    "_itemUnitConversion": "Item Unit Conversion",
+    "_itemUnitType": "Item Unit Type",
+    "_items": "Items",
+    "_jobTitle": "Job Title",
+    "_kit": "Kit",
+    "_language": "Language",
+    "_lastName": "Last Name",
+    "_latestComment": "Latest Comment",
+    "_ledgerAccount": "Ledger Account",
+    "_ledgerAccounts": "Ledger Accounts",
+    "_limitToList": "Limit to List",
+    "_line1": "Line1",
+    "_line2": "Line2",
+    "_line3": "Line3",
+    "_lineItems": "Line Items",
+    "_lineNumber": "Line Number",
+    "_lines": "Lines",
+    "_list" : "List",
+    "_listPrice": "List Price",
+    "_listPriceDiscount": "List Price Discount",
+    "_locale": "Locale",
+    "_login": "Login",
+    "_logout": "Logout",
+    "_lotSerial": "Lot/Serial",
+    "_manager": "Manager",
+    "_mainAddress": "Main Address",
+    "_manufactured": "Manufactured",
+    "_map": "Map",
+    "_margin": "Margin",
+    "_markup": "Markup",
+    "_mask": "Mask",
+    "_masterCard": "MasterCard",
+    "_match": "Match",
+    "_maximum": "Maximum",
+    "_menu": "Menu",
+    "_messageRecipient": "Message Recipient",
+    "_middleName": "Middle Name",
+    "_miscCharge": "Misc. Charge",
+    "_miscellaneous": "Miscellaneous",
+    "_module": "Module",
+    "_monthExpired": "Expiration Month",
+    "_multiple": "Multiple",
+    "_mustRemoveDependentExtensions": "Removing Dependent Extensions",
+    "_name": "Name",
+    "_neither": "Neither",
+    "_nextNumber": "Next Number",
+    "_nextCheckNumber": "Next Check Number",
+    "_notes": "Notes",
+    "_notSold": "Not Sold",
+    "_number": "Number",
+    "_object": "Object",
+    "_offset": "Offset",
+    "_other": "Other",
+    "_openBalance": "Open Balance",
+    "_openDate": "Open Date",
+    "_openItems": "Open Items",
+    "_opportunities": "Opportunities",
+    "_opportunity": "Opportunity",
+    "_opportunityRelations": "Opportunities",
+    "_opportunitySource": "Opportunity Source",
+    "_opportunitySources": "Opportunity Sources",
+    "_opportunityStage": "Opportunity Stage",
+    "_opportunityStages": "Opportunity Stages",
+    "_opportunityType": "Opportunity Type",
+    "_opportunityTypes": "Opportunity Types",
+    "_open": "Open",
+    "_options": "Options",
+    "_order": "Order",
+    "_orderDate": "Order Date",
+    "_orderNumber": "Order Number",
+    "_orderNotes": "Order Notes",
+    "_orderType": "Order Type",
+    "_ordered": "Ordered",
+    "_outsideProcess": "Outside Process",
+    "_owner": "Owner",
+    "_packDate": "Pack Date",
+    "_packing": "Packing",
+    "_paid": "Paid",
+    "_partialShip": "Accepts Partial Shipments",
+    "_partner": "Partner",
+    "_pattern": "Pattern",
+    "_password": "Password",
+    "_path": "Path",
+    "_pending": "Pending",
+    "_percent": "Percent",
+    "_period": "Period",
+    "_phantom": "Phantom",
+    "_phone": "Phone",
+    "_plannerCode": "Planner Code",
+    "_plannerCodes": "Planner Codes",
+    "_planning": "Planning",
+    "_pleaseLogin": "Please Login",
+    "_policy": "Policy",
+    "_post": "Post",
+    "_postbooks": "PostBooks",
+    "_price": "Price",
+    "_priceMode": "Price Mode",
+    "_priceUnit": "Price UOM",
+    "_priceUnitRatio": "Price Unit Ratio",
+    "_primaryContact": "Primary Contact",
+    "_primaryEmail": "Primary Email",
+    "_printed": "Printed",
+    "_priority": "Priority",
+    "_priorities": "Priorities",
+    "_privilege": "Privilege",
+    "_privileges": "Privileges",
+    "_probability": "Probability",
+    "_productCategories": "Product Categories",
+    "_productCategory": "Product Category",
+    "_product": "Product",
+    "_products": "Products",
+    "_profit": "Profit",
+    "_project": "Project",
+    "_projectAssignedTo": "Project Assigned To",
+    "_projectOwner": "Project Owner",
+    "_projectRelations": "Projects",
+    "_projectStatus": "Project Status",
+    "_projectTask": "Project Task",
+    "_projectTasks": "Project Tasks",
+    "_projectTaskStatus": "Project Task Status",
+    "_projects": "Projects",
+    "_promiseDate": "Promised Date",
+    "_properName": "Proper Name",
+    "_propername": "Propername",
+    "_prospect": "Prospect",
+    "_prospects": "Prospects",
+    "_proximo": "Proximo",
+    "_public": "Public",
+    "_purchaseOrderDate": "Purchased",
+    "_purchased": "Purchased",
+    "_purchaseOrderNumber": "Purchase Order",
+    "_qualifier": "Qualifier",
+    "_quantity": "Quantity",
+    "_quantityAfter": "Quantity After",
+    "_quantityBefore": "Quantity Before",
+    "_quantityOrd": "Qty Ordered",
+    "_quantityUnit": "Qty UOM",
+    "_quantityUnitRatio": "Qty Unit Ratio",
+    "_quantityShip": "Qty Shipped",
+    "_quote": "Quote",
+    "_quoteDate": "Quote Date",
+    "_quoteLine": "Quote Line",
+    "_quotes": "Quotes",
+    "_purchaseOrder": "Purchase Order",
+    "_regular": "Regular",
+    "_recurrences": "Recurrences",
+    "_recurring": "Recurring",
+    "_reEnterPassword": "Re-Enter Password",
+    "_reference": "Reference",
+    "_relationships": "Relationships",
+    "_replyTo": "Reply To",
+    "_requireCCV": "Require CCV",
+    "_required": "Required",
+    "_resolution": "Resolution",
+    "_return": "Return",
+    "_roles": "Roles",
+    "_salesOrder": "Sales Order",
+    "_salesOrders": "Sales Orders",
+    "_salesOrderLine": "Sales Order Line",
+    "_salesOrderDate": "Sales Order Date",
+    "_salesRep": "Sales Rep",
+    "_salesReps": "Sales Reps",
+    "_saleType": "Sale Type",
+    "_saleTypes": "Sale Types",
+    "_schedule": "Schedule",
+    "_schedDate": "Sched Date",
+    "_scheduleDate": "Schedule Date",
+    "_secondaryContact": "Secondary Contact",
+    "_selectOrganization": "Select Organization",
+    "_sense": "Sense",
+    "_sequence": "Sequence",
+    "_series": "Series",
+    "_sessionTimedOut": "Your session has timed out",
+    "_settings": "Settings",
+    "_setup": "Setup",
+    "_severity": "Severity",
+    "_shared": "Shared",
+    "_shift": "Shift",
+    "_shifts": "Shifts",
+    "_shipping": "Shipping",
+    "_shipCharge": "Shipping Charges",
+    "_shipComplete": "Ship Complete",
+    "_shipForm": "Shipping Form",
+    "_shipVia": "Ship Via",
+    "_shipVias": "Ship Vias",
+    "_shipTo": "Ship To",
+    "_shiptoAddress1": "Ship To Address",
+    "_shiptoName": "Ship To Name",
+    "_shiptoPhone": "Ship To Phone",
+    "_shipZone": "Ship Zone",
+    "_shipZones": "Ship Zones",
+    "_shippingNotes": "Shipping Notes",
+    "_site": "Site",
+    "_siteCode": "Site Code",
+    "_siteType": "Site Type",
+    "_siteTypes": "Site Types",
+    "_sites": "Sites",
+    "_showClosed": "Show Closed",
+    "_showCompleted": "Show Complete",
+    "_showCompletedOnly": "Show Complete Only",
+    "_showExpired": "Show Expired",
+    "_showUnReleased": "Show Unreleased",
+    "_showInactive": "Show Inactive",
+    "_sold": "Sold",
+    "_soldRanking": "Sold Ranking",
+    "_source": "Source",
+    "_sourceType": "Source Type",
+    "_stage": "Stage",
+    "_standardCost": "Standard Cost",
+    "_start": "Start",
+    "_startDate": "Start Date",
+    "_states": "States",
+    "_status": "Status",
+    "_string": "String",
+    "_subject": "Subject",
+    "_subtotal": "Subtotal",
+    "_suffix": "Suffix",
+    "_summary": "Summary",
+    "_symbol": "Symbol",
+    "_system": "System",
+    "_target": "Target",
+    "_targetClose": "Target Close",
+    "_targetType": "Target Type",
+    "_task": "Task",
+    "_tasks": "Tasks",
+    "_tax": "Tax",
+    "_taxCode": "Tax Code",
+    "_taxType": "Tax Type",
+    "_taxAssignment": "Tax Assignment",
+    "_taxAuthority": "Tax Authority",
+    "_taxClass": "Tax Class",
+    "_taxRate": "Tax Rate",
+    "_taxRegistration": "Tax Registrations",
+    "_taxZone": "Tax Zone",
+    "_terms": "Terms",
+    "_testMode": "Test Mode",
+    "_text": "Text",
+    "_time": "Time",
+    "_to": "To",
+    "_toDate": "To Date",
+    "_toDo": "To Do",
+    "_toDoRelations": "To Dos",
+    "_toDoStatus": "To Do Status",
+    "_toDos": "To Dos",
+    "_tooling": "Tooling",
+    "_total": "Total",
+    "_totals": "Totals",
+    "_toUnit": "To Unit",
+    "_transactionDate": "Transaction Date",
+    "_transactionType": "Transaction Type",
+    "_transactionSuccessful": "Transaction Successful",
+    "_trigger": "Trigger",
+    "_type": "Type",
+    "_unit": "Unit",
+    "_unitPrice": "Net Unit Price",
+    "_unitCosts": "Unit Costs",
+    "_units": "Units",
+    "_unitType": "Unit Type",
+    "_unknown": "(unknown)",
+    "_unitCost": "Unit Cost",
+    "_unsupportedGateway": "Unsupported Gateway",
+    "_uom": "UOM",
+    "_updated": "Updated",
+    "_updatedBy": "Updated By",
+    "_url": "Link",
+    "_urls": "Links",
+    "_useDescription": "Use Description",
+    "_useEnhancedAuth": "Use Enhanced Authentication",
+    "_user": "User",
+    "_userAccount": "User Account",
+    "_userAccounts": "User Accounts",
+    "_userAccountRole": "User Account Role",
+    "_userAccountRoles": "User Account Roles",
+    "_username": "Username",
+    "_userPreference": "User Preference",
+    "_userPreferences": "User Preferences",
+    "_usesPurchaseOrders": "Uses Purchase Orders",
+    "_validator": "Validator",
+    "_value": "Value",
+    "_valueAfter": "Value After",
+    "_valueBefore": "Value Before",
+    "_vendor": "Vendor",
+    "_vendorType": "Vendor Type",
+    "_version": "Version",
+    "_visa": "Visa",
+    "_voucher": "Voucher",
+    "_wage": "Wage",
+    "_wageType": "Wage Type",
+    "_warningColor": "Warning Color",
+    "_web": "Web",
+    "_webAddress": "Web Address",
+    "_welcome": "Welcome",
+    "_wholesalePrice": "Wholesale Price",
+    "_workOrder": "Work Order",
+    "_xtuplePostbooks": "PostBooks",
+    "_yearExpired": "Expiration Year",
 
-  // ********
-  // Placeholders
-  // ********
+    // ********
+    // Placeholders
+    // ********
 
-  "_noAccountName": "No Account Name",
-  "_noAddress": "No Address",
-  "_noCategory": "No Category",
-  "_noCloseTarget": "No Close Target",
-  "_noContact": "No Contact",
-  "_noEmail": "No Email",
-  "_noExpiration": "No Expiration",
-  "_noJobTitle": "No Job Title",
-  "_noName": "No Name",
-  "_noNumber": "No Number",
-  "_noPhone": "No Phone",
-  "_noPriority": "No Priority",
-  "_noPurchaseOrder": "No Purchase Order Number",
-  "_noSalesRep": "No Sales Rep",
-  "_noSchedule": "No Schedule",
-  "_noSeverity": "No Severity",
-  "_noStage": "No Stage",
-  "_noStatus": "No Status",
-  "_noTerms": "No Terms",
-  "_noType": "No Type",
+    "_noAccountName": "No Account Name",
+    "_noAddress": "No Address",
+    "_noCategory": "No Category",
+    "_noCloseTarget": "No Close Target",
+    "_noContact": "No Contact",
+    "_noEmail": "No Email",
+    "_noExpiration": "No Expiration",
+    "_noJobTitle": "No Job Title",
+    "_noName": "No Name",
+    "_noNumber": "No Number",
+    "_noPhone": "No Phone",
+    "_noPriority": "No Priority",
+    "_noPurchaseOrder": "No Purchase Order Number",
+    "_noSalesRep": "No Sales Rep",
+    "_noSchedule": "No Schedule",
+    "_noSeverity": "No Severity",
+    "_noStage": "No Stage",
+    "_noStatus": "No Status",
+    "_noTerms": "No Terms",
+    "_noType": "No Type",
 
-  // ********
-  // Permissions
-  // ********
-  "_alterPackDate": "Alter Pack Date",
-  "_alterTransactionDates": "Alter Transartion Dates",
-  "_configureCC": "Configure Credit Cards",
-  "_configureCRM": "Configure CRM",
-  "_configureImportExport": "Configure Import Export",
-  "_configurePM": "Configure Project",
-  "_createAdjustmentTrans": "Create Adjustment Transactions",
-  "_createExpenseTrans": "Create Expense Transactions",
-  "_createNewCurrency": "Create New Currency",
-  "_createReceiptTrans": "Create Receipt Transactions",
-  "_createScrapTrans": "Create Scrap Transactions",
-  "_deleteCountSlips": "Delete Count Slips",
-  "_deleteCountTags": "Delete Count Tags",
-  "_deleteItemMasters": "Delete Item Masters",
-  "_deleteItemSites": "Delete Item Sites",
-  "_editOthersComments": "Edit Others Comments",
-  "_editOwnComments": "Edit Own Comments",
-  "_editOwner": "Edit Owner",
-  "_enterCountSlips": "Enter Count Slips",
-  "_enterCountTags": "Enter Count Tags",
-  "_enterMiscCounts": "Enter Miscellaneous Counts",
-  "_enterReceipts": "Enter Receipts",
-  "_enterReturns": "Enter Returns",
-  "_enterShippingInformation": "Enter Shipping Information",
-  "_firmSalesOrder": "Firm Sales Order Line",
-  "_freezeInventory": "Freeze Inventory",
-  "_issueStockToShipping": "Issue Stock to Shipping",
-  "_maintainAddresses": "Maintain Addresses",
-  "_maintainAllCRMAccounts": "Maintain All Accounts",
-  "_maintainAllContacts": "Maintain All Contacts",
-  "_maintainAllIncidents": "Maintain All Incidents",
-  "_maintainAllOpportunities": "Maintain All Opportunities",
-  "_maintainAllProjects": "Maintain All Projects",
-  "_maintainAllToDoItems": "Maintain All To-Do Items",
-  "_maintainCharacteristics": "Maintain Characteristics",
-  "_maintainClassCodes": "Maintain Class Codes",
-  "_maintainCommentTypes": "Maintain Comment Types",
-  "_maintainCostCategories": "Maintain Cost Categories",
-  "_maintainCountries": "Maintain Countries",
-  "_maintainCurrencies": "Maintain Currencies",
-  "_maintainCurrencyRates": "Maintain Currency Rates",
-  "_maintainCustomerGroups": "Maintain Customer Groups",
-  "_maintainCustomerMasters": "Maintain Customers",
-  "_maintainDepartments": "Maintain Departments",
-  "_maintainEmployees": "Maintain Employees",
-  "_maintainExtensions": "Maintain Extensions",
-  "_maintainFreightClasses": "Maintain Freight Classes",
-  "_maintainImages": "Maintain Images",
-  "_maintainItemMasters": "Maintain Item Masters",
-  "_maintainItemSites": "Maintain Item Sites",
-  "_maintainLocations": "Maintain Locations",
-  "_maintainPackingListBatch": "Maintain Packing List Batch",
-  "_maintainProductCategories": "Maintain Product Categories",
-  "_maintainQuotes": "Maintain Quotes",
-  "_maintainSalesOrders": "Maintain Sales Orders",
-  "_maintainSaleTypes": "Maintain Sale Types",
-  "_maintainSalesReps": "Maintain Sales Reps",
-  "_maintainSiteTypes": "Maintain Site Types",
-  "_maintainShifts": "Maintain Shifts",
-  "_maintainStates": "Maintain States",
-  "_maintainUOMs": "Maintain Units of Measure",
-  "_maintainUsers": "Maintain Users",
-  "_maintainIncidentCategories": "Maintain Incident Categories",
-  "_maintainIncidentPriorities": "Maintain Incident Priorities",
-  "_maintainIncidentResolutions": "Maintain Incident Resolutions",
-  "_maintainIncidentSeverities": "Maintain Incident Severities",
-  "_maintainOpportunitySources": "Maintain Opportunity Sources",
-  "_maintainOpportunityStages": "Maintain Opportunity Stages",
-  "_maintainOpportunityTypes": "Maintain Opportunity Types",
-  "_maintainPartners": "Maintain Partners",
-  "_maintainPersonalCRMAccounts": "Maintain Personal Accounts",
-  "_maintainPersonalContacts": "Maintain Personal Contacts",
-  "_maintainPersonalIncidents": "Maintain Personal Incidents",
-  "_maintainPersonalOpportunities": "Maintain Personal Opportunities",
-  "_maintainPersonalProjects": "Maintain Personal Projects",
-  "_maintainPersonalToDoItems": "Maintain Personal To-Do Items",
-  "_maintainPreferencesSelf": "Maintain Personal Preferences",
-  "_maintainTaxAssignments": "Maintain Tax Assignments",
-  "_maintainTaxClasses": "Maintain Tax Classes",
-  "_maintainTaxCodes": "MaintainTaxCodes",
-  "_maintainTaxReconciliations": "Maintain Tax Reconciliations",
-  "_maintainTaxRegistrations": "Maintain Tax Registrations",
-  "_maintainTaxTypes": "Maintain Tax Types",
-  "_maintainTaxZones": "Maintain Tax Zones",
-  "_maintainTerms": "Maintain Terms ",
-  "_maintainTitles": "Maintain Titles",
-  "_maintainWarehouses": "Maintain Sites",
-  "_overridePrice": "Override Price",
-  "_overrideSODate": "Override Order Date",
-  "_overrideTax": "Override Tax Type",
-  "_reassignToDoItems": "Edit Assign To",
-  "_recallInvoicedShipment": "Recall Invoiced Shipment",
-  "_recallOrders": "Recall Orders",
-  "_returnStockFromShipping": "Return Stock from Shipping",
-  "_shipOrders": "Ship Orders",
-  "_showMarginsOnSalesOrder": "Show Margins",
-  "_updateCustomerCreditStatus": "Update Credit Status",
-  "_viewAddresses": "View Addresses",
-  "_viewAllCRMAccounts": "View All Accounts",
-  "_viewAllContacts": "View All Contacts",
-  "_viewAllIncidents": "View All Incidents",
-  "_viewAllOpportunities": "View All Opportunities",
-  "_viewAllProjects": "View All Projects",
-  "_viewAllToDoItems": "View All To-Do Items",
-  "_viewCharacteristics": "View Characteristics",
-  "_viewCostCategories": "View Cost Categories",
-  "_viewCosts": "View Costs",
-  "_viewClassCodes": "View Class Codes",
-  "_viewCurrencyRates": "View Currency Rates",
-  "_viewCustomerGroups": "View Customer Groups",
-  "_viewCustomerMasters": "View Customers",
-  "_viewDepartments": "View Departments",
-  "_viewEmployees": "View Employees",
-  "_viewFreightClasses": "View Freight Classes",
-  "_viewInventoryHistory": "View Inventory History",
-  "_viewInventoryValue": "View Inventory Value",
-  "_viewItemMasters": "View Item Masters",
-  "_viewItemSites": "View Item Sites",
-  "_viewLocations": "View Locations",
-  "_viewPackingListBatch": "View Packing List Batch",
-  "_viewPersonalCRMAccounts": "View Personal Accounts",
-  "_viewPersonalContacts": "View Personal Contacts",
-  "_viewPersonalIncidents": "View Personal Incidents",
-  "_viewPersonalOpportunities": "View Personal Opportunities",
-  "_viewPersonalProjects": "View Personal Projects",
-  "_viewPersonalToDoItems": "View Personal To-Do Items",
-  "_viewProductCategories": "View Product Categories",
-  "_viewQOH": "View Quantity on Hand",
-  "_viewQuotes": "View Quotes",
-  "_viewSaleTypes": "View Sale Types",
-  "_viewSalesOrders": "View Sales Orders",
-  "_viewSalesReps": "View Sales Reps",
-  "_viewShipping": "View Shipping",
-  "_viewSiteTypes": "View Site Types",
-  "_viewTaxAssignments": "View Tax Assignments",
-  "_viewTaxClasses": "View Tax Classes",
-  "_viewTaxCodes": "View Tax Codes",
-  "_viewTaxReconciliations": "View Tax Reconciliations",
-  "_viewTaxRegistrations": "View Tax Registrations",
-  "_viewTaxTypes": "View Tax Types",
-  "_viewTaxZones": "View Tax Zones",
-  "_viewTerms": "View Terms",
-  "_viewUOMs": "View Units of Measure",
-  "_viewTitles": "View Honorifics",
-  "_viewWarehouses": "View Sites",
+    // ********
+    // Permissions
+    // ********
+    "_alterPackDate": "Alter Pack Date",
+    "_alterTransactionDates": "Alter Transartion Dates",
+    "_configureCC": "Configure Credit Cards",
+    "_configureCRM": "Configure CRM",
+    "_configureImportExport": "Configure Import Export",
+    "_configurePM": "Configure Project",
+    "_createAdjustmentTrans": "Create Adjustment Transactions",
+    "_createExpenseTrans": "Create Expense Transactions",
+    "_createNewCurrency": "Create New Currency",
+    "_createReceiptTrans": "Create Receipt Transactions",
+    "_createScrapTrans": "Create Scrap Transactions",
+    "_deleteCountSlips": "Delete Count Slips",
+    "_deleteCountTags": "Delete Count Tags",
+    "_deleteItemMasters": "Delete Item Masters",
+    "_deleteItemSites": "Delete Item Sites",
+    "_editOthersComments": "Edit Others Comments",
+    "_editOwnComments": "Edit Own Comments",
+    "_editOwner": "Edit Owner",
+    "_enterCountSlips": "Enter Count Slips",
+    "_enterCountTags": "Enter Count Tags",
+    "_enterMiscCounts": "Enter Miscellaneous Counts",
+    "_enterReceipts": "Enter Receipts",
+    "_enterReturns": "Enter Returns",
+    "_enterShippingInformation": "Enter Shipping Information",
+    "_firmSalesOrder": "Firm Sales Order Line",
+    "_freezeInventory": "Freeze Inventory",
+    "_issueStockToShipping": "Issue Stock to Shipping",
+    "_maintainAddresses": "Maintain Addresses",
+    "_maintainAllCRMAccounts": "Maintain All Accounts",
+    "_maintainAllContacts": "Maintain All Contacts",
+    "_maintainAllIncidents": "Maintain All Incidents",
+    "_maintainAllOpportunities": "Maintain All Opportunities",
+    "_maintainAllProjects": "Maintain All Projects",
+    "_maintainAllToDoItems": "Maintain All To-Do Items",
+    "_maintainCharacteristics": "Maintain Characteristics",
+    "_maintainClassCodes": "Maintain Class Codes",
+    "_maintainCommentTypes": "Maintain Comment Types",
+    "_maintainCostCategories": "Maintain Cost Categories",
+    "_maintainCountries": "Maintain Countries",
+    "_maintainCurrencies": "Maintain Currencies",
+    "_maintainCurrencyRates": "Maintain Currency Rates",
+    "_maintainCustomerGroups": "Maintain Customer Groups",
+    "_maintainCustomerMasters": "Maintain Customers",
+    "_maintainDepartments": "Maintain Departments",
+    "_maintainEmployees": "Maintain Employees",
+    "_maintainExtensions": "Maintain Extensions",
+    "_maintainFreightClasses": "Maintain Freight Classes",
+    "_maintainImages": "Maintain Images",
+    "_maintainItemMasters": "Maintain Item Masters",
+    "_maintainItemSites": "Maintain Item Sites",
+    "_maintainLocations": "Maintain Locations",
+    "_maintainPackingListBatch": "Maintain Packing List Batch",
+    "_maintainProductCategories": "Maintain Product Categories",
+    "_maintainQuotes": "Maintain Quotes",
+    "_maintainSalesOrders": "Maintain Sales Orders",
+    "_maintainSaleTypes": "Maintain Sale Types",
+    "_maintainSalesReps": "Maintain Sales Reps",
+    "_maintainSiteTypes": "Maintain Site Types",
+    "_maintainShifts": "Maintain Shifts",
+    "_maintainStates": "Maintain States",
+    "_maintainUOMs": "Maintain Units of Measure",
+    "_maintainUsers": "Maintain Users",
+    "_maintainIncidentCategories": "Maintain Incident Categories",
+    "_maintainIncidentPriorities": "Maintain Incident Priorities",
+    "_maintainIncidentResolutions": "Maintain Incident Resolutions",
+    "_maintainIncidentSeverities": "Maintain Incident Severities",
+    "_maintainOpportunitySources": "Maintain Opportunity Sources",
+    "_maintainOpportunityStages": "Maintain Opportunity Stages",
+    "_maintainOpportunityTypes": "Maintain Opportunity Types",
+    "_maintainPartners": "Maintain Partners",
+    "_maintainPersonalCRMAccounts": "Maintain Personal Accounts",
+    "_maintainPersonalContacts": "Maintain Personal Contacts",
+    "_maintainPersonalIncidents": "Maintain Personal Incidents",
+    "_maintainPersonalOpportunities": "Maintain Personal Opportunities",
+    "_maintainPersonalProjects": "Maintain Personal Projects",
+    "_maintainPersonalToDoItems": "Maintain Personal To-Do Items",
+    "_maintainPreferencesSelf": "Maintain Personal Preferences",
+    "_maintainTaxAssignments": "Maintain Tax Assignments",
+    "_maintainTaxClasses": "Maintain Tax Classes",
+    "_maintainTaxCodes": "MaintainTaxCodes",
+    "_maintainTaxReconciliations": "Maintain Tax Reconciliations",
+    "_maintainTaxRegistrations": "Maintain Tax Registrations",
+    "_maintainTaxTypes": "Maintain Tax Types",
+    "_maintainTaxZones": "Maintain Tax Zones",
+    "_maintainTerms": "Maintain Terms ",
+    "_maintainTitles": "Maintain Titles",
+    "_maintainWarehouses": "Maintain Sites",
+    "_overridePrice": "Override Price",
+    "_overrideSODate": "Override Order Date",
+    "_overrideTax": "Override Tax Type",
+    "_reassignToDoItems": "Edit Assign To",
+    "_recallInvoicedShipment": "Recall Invoiced Shipment",
+    "_recallOrders": "Recall Orders",
+    "_returnStockFromShipping": "Return Stock from Shipping",
+    "_shipOrders": "Ship Orders",
+    "_showMarginsOnSalesOrder": "Show Margins",
+    "_updateCustomerCreditStatus": "Update Credit Status",
+    "_viewAddresses": "View Addresses",
+    "_viewAllCRMAccounts": "View All Accounts",
+    "_viewAllContacts": "View All Contacts",
+    "_viewAllIncidents": "View All Incidents",
+    "_viewAllOpportunities": "View All Opportunities",
+    "_viewAllProjects": "View All Projects",
+    "_viewAllToDoItems": "View All To-Do Items",
+    "_viewCharacteristics": "View Characteristics",
+    "_viewCostCategories": "View Cost Categories",
+    "_viewCosts": "View Costs",
+    "_viewClassCodes": "View Class Codes",
+    "_viewCurrencyRates": "View Currency Rates",
+    "_viewCustomerGroups": "View Customer Groups",
+    "_viewCustomerMasters": "View Customers",
+    "_viewDepartments": "View Departments",
+    "_viewEmployees": "View Employees",
+    "_viewFreightClasses": "View Freight Classes",
+    "_viewInventoryHistory": "View Inventory History",
+    "_viewInventoryValue": "View Inventory Value",
+    "_viewItemMasters": "View Item Masters",
+    "_viewItemSites": "View Item Sites",
+    "_viewLocations": "View Locations",
+    "_viewPackingListBatch": "View Packing List Batch",
+    "_viewPersonalCRMAccounts": "View Personal Accounts",
+    "_viewPersonalContacts": "View Personal Contacts",
+    "_viewPersonalIncidents": "View Personal Incidents",
+    "_viewPersonalOpportunities": "View Personal Opportunities",
+    "_viewPersonalProjects": "View Personal Projects",
+    "_viewPersonalToDoItems": "View Personal To-Do Items",
+    "_viewProductCategories": "View Product Categories",
+    "_viewQOH": "View Quantity on Hand",
+    "_viewQuotes": "View Quotes",
+    "_viewSaleTypes": "View Sale Types",
+    "_viewSalesOrders": "View Sales Orders",
+    "_viewSalesReps": "View Sales Reps",
+    "_viewShipping": "View Shipping",
+    "_viewSiteTypes": "View Site Types",
+    "_viewTaxAssignments": "View Tax Assignments",
+    "_viewTaxClasses": "View Tax Classes",
+    "_viewTaxCodes": "View Tax Codes",
+    "_viewTaxReconciliations": "View Tax Reconciliations",
+    "_viewTaxRegistrations": "View Tax Registrations",
+    "_viewTaxTypes": "View Tax Types",
+    "_viewTaxZones": "View Tax Zones",
+    "_viewTerms": "View Terms",
+    "_viewUOMs": "View Units of Measure",
+    "_viewTitles": "View Honorifics",
+    "_viewWarehouses": "View Sites",
 
-  // ********
-  // Messages
-  // ********
+    // ********
+    // Messages
+    // ********
 
-  "_accountExists": "This number is currently assigned to an Account.",
-  "_automaticFreight": "Manually clearing the freight will enable automatic Freight recalculations.",
-  "_continue?": " Do you want to continue?",
-  "_convertAccount": "Convert this Account to a Customer?",
-  "_convertAccountEmployee": "Convert this Account to an Employee?",
-  "_convertAccountProspect": "Convert this Account to a Prospect?",
-  "_convertAccountSalesRep": "Convert this Account to a SalesRep?",
-  "_convertAccountTaxAuthority": "Convert this Account to a Tax Authority?",
-  "_convertProspect": "Convert this Prospect to a Customer?",
-  "_customerExists": "A customer with this number already exists.",
-  "_customerOrProspect": "Would you like to create a new Customer or a new Prospect?",
-  "_exitPageWarning": "You are about to leave the xTuple application.",
-  "_manualFreight": "Manually editing the freight will disable automatic freight recalculations.",
-  "_mustSave": "You must save your changes before proceeding.",
-  "_noPriceFound": "This item is marked as exclusive and no qualifying price was found.",
-  "_noPurchase": "This item may not be purchased in this date. Please select another date or item.",
-  "_noReschedule": "No Items can be rescheduled because there are no valid price schedules for the date entered.",
-  "_partialReschedule": "Some exclusive items may not be rescheduled because there is no valid price schedule for the date entered.",
-  "_prospectExists": "This number is currently assigned to a Prospect.",
-  "_recalculateAll?": "Do you want to recalculate all prices line items, taxes, and freight ?",
-  "_rescheduleAll": "Changing this date will update the Schedule Date on all editable line items.",
-  "_updateFractional": "The quantity ordered and unit of measure selected will result in a fractional inventory qty for this item. This item does not allow fractional quantities; the quantity will be updated accordingly.",
-  "_updatePrice?": "You have changed the price basis, do you want to update the Price?",
-  "_whatToDo": "What would you like to do?"
-});
+    "_accountExists": "This number is currently assigned to an Account.",
+    "_automaticFreight": "Manually clearing the freight will enable automatic Freight recalculations.",
+    "_continue?": " Do you want to continue?",
+    "_convertAccount": "Convert this Account to a Customer?",
+    "_convertAccountEmployee": "Convert this Account to an Employee?",
+    "_convertAccountProspect": "Convert this Account to a Prospect?",
+    "_convertAccountSalesRep": "Convert this Account to a SalesRep?",
+    "_convertAccountTaxAuthority": "Convert this Account to a Tax Authority?",
+    "_convertProspect": "Convert this Prospect to a Customer?",
+    "_customerExists": "A customer with this number already exists.",
+    "_customerOrProspect": "Would you like to create a new Customer or a new Prospect?",
+    "_exitPageWarning": "You are about to leave the xTuple application.",
+    "_manualFreight": "Manually editing the freight will disable automatic freight recalculations.",
+    "_mustSave": "You must save your changes before proceeding.",
+    "_noPriceFound": "This item is marked as exclusive and no qualifying price was found.",
+    "_noPurchase": "This item may not be purchased in this date. Please select another date or item.",
+    "_noReschedule": "No Items can be rescheduled because there are no valid price schedules for the date entered.",
+    "_partialReschedule": "Some exclusive items may not be rescheduled because there is no valid price schedule for the date entered.",
+    "_prospectExists": "This number is currently assigned to a Prospect.",
+    "_recalculateAll?": "Do you want to recalculate all prices line items, taxes, and freight ?",
+    "_rescheduleAll": "Changing this date will update the Schedule Date on all editable line items.",
+    "_updateFractional": "The quantity ordered and unit of measure selected will result in a fractional inventory qty for this item. This item does not allow fractional quantities; the quantity will be updated accordingly.",
+    "_updatePrice?": "You have changed the price basis, do you want to update the Price?",
+    "_whatToDo": "What would you like to do?"
+  });
+
+  if (typeof exports !== 'undefined') {
+    exports.language = lang;
+  }
+}());
diff --git a/enyo-client/application/source/ext/.gitignore b/enyo-client/application/source/ext/.gitignore
deleted file mode 100644 (file)
index dcaffc0..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/*.js
index 3a1ce2e..05c05af 100644 (file)
@@ -2,11 +2,79 @@
 newcap:true, noarg:true, regexp:true, undef:true, strict:true, trailing:true,
 white:true*/
 /*global XT:true, XM:true, io:true, Backbone:true, _:true, console:true, enyo:true
-  document:true, setTimeout:true, document:true */
+  document:true, setTimeout:true, document:true, RJSON:true */
 
 (function () {
   "use strict";
 
+  XT.Request = {
+    /** @scope XT.Request.prototype */
+
+    send: function (data) {
+      var details = XT.session.details,
+        sock = XT.dataSource._sock,
+        notify = this._notify,
+        handle = this._handle,
+        errorMessage,
+        payload = {
+          payload: data
+        },
+        callback;
+
+      if (!notify || !(notify instanceof Function)) {
+        callback = function () {};
+      } else {
+        callback = function (response) {
+          if (response && response.isError) {
+            // handle error status centrally here before we return to the caller
+
+            errorMessage = response.status ? response.status.message : response.message;
+            if (errorMessage) {
+              XT.app.$.postbooks.notify(null, {
+                type: XM.Model.CRITICAL,
+                message: errorMessage
+              });
+            }
+
+            if (response.code === "SESSION_NOT_FOUND") {
+              // The session couldn't be validated by the datasource.
+              // XXX might be dead code
+              XT.logout();
+            }
+
+          }
+
+          notify(response);
+        };
+      }
+
+      // attach the session details to the payload
+      payload = _.extend(payload, details);
+
+      if (XT.session.config.debugging) {
+        XT.log("Socket sending: %@".replace("%@", handle), payload);
+      }
+
+      sock.json.emit(handle, payload, callback);
+
+      return this;
+    },
+
+    handle: function (event) {
+      this._handle = event;
+      return this;
+    },
+
+    notify: function (method) {
+      var args = Array.prototype.slice.call(arguments).slice(1);
+      this._notify = function (response) {
+        args.unshift(response);
+        method.apply(null, args);
+      };
+      return this;
+    }
+  };
+
   XT.DataSource = {
     // TODO - Old way.
     //datasourceUrl: DOCUMENT_HOSTNAME,
@@ -47,6 +115,32 @@ white:true*/
         'commitPreferences', param, options);
     },
 
+    /**
+     * Decode the server's response to a bona fide Javascript object.
+     * @see node-datasource/routes/data.js#encodeResponse
+     *
+     * @param {Object}  response  the server's response object
+     * @param {Object}  options   the request options
+     *
+     * @return {Object} the server's response as a Javascript object.
+     */
+    decodeResponse: function (response, options) {
+      var encoding = options.encoding || XT.session.config.encoding;
+
+      if (!encoding) {
+        return response;
+      }
+      else if (encoding === "rjson") {
+        return RJSON.unpack(response);
+      }
+      else {
+        return {
+          isError: true,
+          status: "Encoding [" + encoding + "] not recognized."
+        };
+      }
+    },
+
     /*
     Server request
 
@@ -74,7 +168,7 @@ white:true*/
             return;
           }
 
-          dataHash = response.data;
+          dataHash = that.decodeResponse(response, options).data;
 
           // Handle no data on a single record retrieve as error
           if (method === "get" && options.id &&
@@ -109,6 +203,10 @@ white:true*/
           }
         };
 
+      _(payload).extend({
+        encoding: options.encoding || XT.session.config.encoding
+      });
+
       return XT.Request
                .handle(method)
                .notify(complete)
index 2219e42..9baa8cb 100644 (file)
@@ -1,4 +1,6 @@
 enyo.depends(
+  "../../lib/module/rjson/rjson.js",
   "core.js",
-  "datasource.js"
+  "datasource.js",
+  "session.js"
 );
diff --git a/enyo-client/application/source/ext/session.js b/enyo-client/application/source/ext/session.js
new file mode 100644 (file)
index 0000000..46cef52
--- /dev/null
@@ -0,0 +1,63 @@
+/*jshint 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 XT:true, XM:true, io:true, Backbone:true, _:true, console:true, enyo:true
+  document:true, setTimeout:true, document:true, RJSON:true */
+
+(function () {
+  "use strict";
+
+  _.extend(XT.Session, {
+    validateSession: function (callback) {
+      var self = this,
+        complete = function (payload) {
+          self._didValidateSession.call(self, payload, callback);
+        };
+
+      // Poll the session socket.io endpoint for valid session data.
+      XT.Request
+        .handle("session")
+        .notify(complete)
+        .send(null);
+    },
+
+    _didValidateSession: function (payload, callback) {
+      if (payload.code === 1) {
+        // If this is a valid session acquisition, go ahead
+        // and store the database config details in
+        // XT.Session.config, and, more specifically, user
+        // properties in XT.Session.details.
+        this.setConfig(payload);
+        this.setDetails(payload.data);
+
+        if (payload.version && XT.setVersion) {
+          // announce to the client what our version is, if we have
+          // a way of doing it.
+          XT.setVersion(payload.version);
+        }
+
+        // Start the client loading process.
+        XT.getStartupManager().start();
+      } else {
+        return XT.Session.logout();
+      }
+
+      if (callback && callback instanceof Function) {
+        callback();
+      }
+    },
+
+    start: function () {
+      try {
+        this.validateSession(function () {
+          // Tell the client to show now that we're in startup mode.
+          XT.app.show();
+        });
+      } catch (e) {
+        XT.Session.logout();
+      }
+    }
+  });
+
+
+}());
index 61c17db..39e1cef 100644 (file)
@@ -113,7 +113,7 @@ white:true*/
 
     conversionMap: {
       name: "name",
-      primaryContact: "contact",
+      primaryContact: "billingContact",
       secondaryContact: "correspondenceContact"
     },
 
@@ -279,6 +279,11 @@ white:true*/
 
   });
 
+  XM.Customer.used = function (id, options) {
+    return XM.ModelMixin.dispatch('XM.Customer', 'used',
+      [id], options);
+  };
+
   /**
     @class
 
index 1728e6e..97aed2e 100644 (file)
@@ -27,6 +27,11 @@ white:true*/
 
   });
 
+  XM.Prospect.used = function (id, options) {
+    return XM.ModelMixin.dispatch('XM.Prospect', 'used',
+      [id], options);
+  };
+
   /**
     @class
 
index c463c60..d01358a 100644 (file)
@@ -35,6 +35,11 @@ white:true*/
     }
   });
 
+  XM.SalesOrder.used = function (id, options) {
+    return XM.ModelMixin.dispatch('XM.SalesOrder', 'used',
+      [id], options);
+  };
+
   /**
     @class
 
index 7250e77..c210fa5 100644 (file)
@@ -602,10 +602,11 @@ white:true*/
       takes all the info from the billto and copies it to the shipto.
     */
     copyBilltoToShipto: function () {
-      var i;
+      var shiptoAttrArray = this.shiptoAttrArray.slice(1), // Don't need shipto
+        i;
       this.unset("shipto");
-      for (i = 0; i < this.shiptoAttrArray.length; i++) {
-        this.set(this.shiptoAttrArray[i], this.get(this.billtoAttrArray[i]));
+      for (i = 0; i < shiptoAttrArray.length; i++) {
+        this.set(shiptoAttrArray[i], this.get(this.billtoAttrArray[i]));
       }
     },
 
@@ -1296,7 +1297,8 @@ white:true*/
         updatePolicy = settings.get("UpdatePriceLineEdit"),
         parent = this.getParent(),
         customer = parent ? parent.get("customer") : false,
-        currency = parent ? parent.get("currency") :false;
+        currency = parent ? parent.get("currency") :false,
+        listPrice;
 
       // If no parent, don't bother
       if (!parent) { return; }
@@ -1309,8 +1311,11 @@ white:true*/
 
         // Prospects always get the list price
         if (customer.getValue("status") === XM.CustomerProspectRelation.PROSPECT_STATUS) {
-          this.set("price", item.get("listPrice"));
-          this.set("customerPrice", item.get("listPrice"));
+          listPrice = item.get("listPrice");
+          this.set({
+            price: listPrice,
+            customerPrice: listPrice
+          });
           return;
         }
         // Determine whether updating net price or only customer price
index d716422..f84ee3a 100644 (file)
@@ -3,7 +3,6 @@ enyo.depends(
   "../lib/tools",
   "../lib/backbone-x",
   "../lib/enyo-x",
-  "en",
   "ext",
   "models",
   "widgets",
index d202e68..d8aca7a 100644 (file)
@@ -31,16 +31,6 @@ white:true*/
     }
   });
 
-  XT.StartupTask.create({
-    taskName: "loadSessionLocale",
-    task: function () {
-      var options = {
-        success: _.bind(this.didComplete, this)
-      };
-      XT.session.loadSessionObjects(XT.session.LOCALE, options);
-    }
-  });
-
   XT.StartupTask.create({
     taskName: "loadSessionPreferences",
     task: function () {
index 74597c3..624c668 100644 (file)
@@ -50,7 +50,7 @@ newcap:true, noarg:true, regexp:true, undef:true, trailing:true, white:true, str
         quantity = model.get("quantity"),
         discount = model.get("discount"),
         price = model.get("price"),
-        locale = XT.session.locale;
+        locale = XT.locale;
 
       if (!model) {
         return;
@@ -62,7 +62,7 @@ newcap:true, noarg:true, regexp:true, undef:true, trailing:true, white:true, str
       this.$.siteCode.setContent(model.getValue("site.code"));
 
       this.$.quantity.addRemoveClass("xv-error", !quantity);
-      quantity = _.isNumber(quantity) ? Globalize.format(quantity, "n" + locale.get("quantityScale")) : "_required".loc();
+      quantity = _.isNumber(quantity) ? Globalize.format(quantity, "n" + locale.quantityScale) : "_required".loc();
       this.$.quantity.setContent(quantity);
 
       this.$.quantityUnit.setContent(model.getValue("quantityUnit.name"));
@@ -70,9 +70,9 @@ newcap:true, noarg:true, regexp:true, undef:true, trailing:true, white:true, str
       this.$.discount.setContent(discount);
 
       this.$.price.addRemoveClass("xv-error", !_.isNumber(price));
-      this.$.price.setContent(Globalize.format(price, "n" + locale.get("salesPriceScale")) || "_required".loc());
+      this.$.price.setContent(Globalize.format(price, "n" + locale.salesPriceScale) || "_required".loc());
       this.$.priceUnit.setContent(model.getValue("priceUnit.name"));
-      this.$.extendedPrice.setContent(Globalize.format(model.get("extendedPrice"), "n" + locale.get("extendedPriceScale")));
+      this.$.extendedPrice.setContent(Globalize.format(model.get("extendedPrice"), "n" + locale.extendedPriceScale));
 
       this.$.scheduleDate.setContent(Globalize.format(XT.date.applyTimezoneOffset(model.get("scheduleDate"), true), "d"));
     }
index 31f167e..9452135 100644 (file)
@@ -1047,7 +1047,7 @@ trailing:true, white:true, strict: false*/
     formatPrice: function (value, view, model) {
       var sold = model.get("isSold");
       if (XT.session.privileges.get("ViewListPrices") && sold) {
-        var scale = XT.session.locale.attributes.salesPriceScale;
+        var scale = XT.locale.salesPriceScale;
         return Globalize.format(value, "c" + scale);
       }
       view.addRemoveClass("placeholder", true);
@@ -1233,7 +1233,7 @@ trailing:true, white:true, strict: false*/
     ],
     formatAmount: function (value, view, model) {
       var currency = model ? model.get("currency") : false,
-        scale = XT.session.locale.attributes.moneyScale;
+        scale = XT.locale.moneyScale;
       return currency ? currency.format(value, scale) : "";
     },
     formatTargetClose: function (value, view, model) {
@@ -1372,12 +1372,12 @@ trailing:true, white:true, strict: false*/
     },
     formatHours: function (value, view, model) {
       view.addRemoveClass("error", value < 0);
-      var scale = XT.session.locale.attributes.quantityScale;
+      var scale = XT.locale.quantityScale;
       return Globalize.format(value, "n" + scale) + " " + "_hrs".loc();
     },
     formatExpenses: function (value, view, model) {
       view.addRemoveClass("error", value < 0);
-      var scale = XT.session.locale.attributes.currencyScale;
+      var scale = XT.locale.currencyScale;
       return Globalize.format(value, "c" + scale);
     }
   });
@@ -1561,7 +1561,7 @@ trailing:true, white:true, strict: false*/
     /*
     formatPrice: function (value, view, model) {
       var currency = model ? model.get("currency") : false,
-        scale = XT.session.locale.attributes.salesPriceScale;
+        scale = XT.locale.salesPriceScale;
       return currency ? currency.format(value, scale) : "";
     } */
   });
@@ -1628,7 +1628,7 @@ trailing:true, white:true, strict: false*/
     },
     formatTotal: function (value, view, model) {
       var currency = model ? model.get("currency") : false,
-        scale = XT.session.locale.attributes.moneyScale;
+        scale = XT.locale.moneyScale;
       return currency ? currency.format(value, scale) : "";
     },
 
@@ -1797,6 +1797,34 @@ trailing:true, white:true, strict: false*/
     ]
   });
 
+  // ..........................................................
+  // SHIP VIA
+  //
+
+  enyo.kind({
+    name: "XV.ShipViaList",
+    kind: "XV.List",
+    label: "_shipVias".loc(),
+    collection: "XM.ShipViaCollection",
+    query: {orderBy: [
+      {attribute: 'code'}
+    ]},
+    components: [
+      {kind: "XV.ListItem", components: [
+        {kind: "FittableColumns", components: [
+          {kind: "XV.ListColumn", classes: "short",
+            components: [
+            {kind: "XV.ListAttr", attr: "code", isKey: true}
+          ]},
+          {kind: "XV.ListColumn", classes: "last", fit: true, components: [
+            {kind: "XV.ListAttr", attr: "description"}
+          ]}
+        ]}
+      ]}
+    ]
+  });
+  XV.registerModelList("XM.ShipVia", "XV.ShipViaList");
+
   // ..........................................................
   // SHIP ZONE
   //
@@ -2301,13 +2329,19 @@ trailing:true, white:true, strict: false*/
   enyo.kind({
     name: "XV.NameDescriptionList",
     kind: "XV.NameList",
-    create: function () {
-      this.inherited(arguments);
-      this.createComponent({kind: "XV.ListColumn", classes: "last", fit: true, components: [
-          {kind: "XV.ListAttr", attr: "description"}
-        ]
-      });
-    }
+    components: [
+      {kind: "XV.ListItem", components: [
+        {kind: "FittableColumns", components: [
+          {kind: "XV.ListColumn", classes: "short",
+            components: [
+            {kind: "XV.ListAttr", attr: "name", isKey: true}
+          ]},
+          {kind: "XV.ListColumn", classes: "last", fit: true, components: [
+            {kind: "XV.ListAttr", attr: "description"}
+          ]}
+        ]}
+      ]}
+    ]
   });
 
   enyo.kind({
index 8c3a9f1..1397caf 100644 (file)
@@ -508,23 +508,23 @@ trailing:true, white:true*/
     formatExtendedPrice: function (value, view, model) {
       var parent = model.getParent(),
         currency = parent ? parent.get("currency") : false,
-        scale = XT.session.locale.attributes.extendedPriceScale;
+        scale = XT.locale.extendedPriceScale;
       return currency ? currency.format(value, scale) : "";
     },
     formatPercentage: function (value, view, model) {
       var parent = model.getParent(),
         currency = parent ? parent.get("currency") : false,
-        scale = XT.session.locale.attributes.percentPriceScale;
+        scale = XT.locale.percentPriceScale;
       return currency ? currency.format(value, scale) : "";
     },
     formatPrice: function (value, view, model) {
       var parent = model.getParent(),
         currency = parent ? parent.get("currency") : false,
-        scale = XT.session.locale.attributes.salesPriceScale;
+        scale = XT.locale.salesPriceScale;
       return currency ? currency.format(value, scale) : "";
     },
     formatQuantity: function (value, view, model) {
-      var scale = XT.session.locale.attributes.quantityScale;
+      var scale = XT.locale.quantityScale;
       return Globalize.format(value, "n" + scale);
     }
   });
index d710d59..f6a4499 100644 (file)
@@ -2055,6 +2055,32 @@ newcap:true, noarg:true, regexp:true, undef:true, trailing:true, white:true*/
 
   XV.registerModelWorkspace("XM.Shift", "XV.ShiftWorkspace");
 
+  // ..........................................................
+  // SHIP VIA
+  //
+
+  enyo.kind({
+    name: "XV.ShipViaWorkspace",
+    kind: "XV.Workspace",
+    title: "_shipVia".loc(),
+    model: "XM.ShipVia",
+    components: [
+      {kind: "Panels", arrangerKind: "CarouselArranger",
+        fit: true, components: [
+        {kind: "XV.Groupbox", name: "mainPanel", components: [
+          {kind: "onyx.GroupboxHeader", content: "_overview".loc()},
+          {kind: "XV.ScrollableGroupbox", name: "mainGroup",
+            classes: "in-panel", components: [
+            {kind: "XV.InputWidget", attr: "code"},
+            {kind: "XV.InputWidget", attr: "description"}
+          ]}
+        ]}
+      ]}
+    ]
+  });
+
+  XV.registerModelWorkspace("XM.ShipVia", "XV.ShipViaWorkspace");
+
   // ..........................................................
   // SHIP ZONE
   //
index 8acc44e..71db45f 100644 (file)
@@ -59,7 +59,8 @@ regexp:true, undef:true, trailing:true, white:true */
         documentKey,
         nameAttribute,
         attrs = {},
-        Klass;
+        Klass,
+        map;
 
       // Turn on label link if applicable
       if (this.getValue() && isRelation) {
@@ -81,14 +82,17 @@ regexp:true, undef:true, trailing:true, white:true */
         if (Klass.prototype.editableModel) {
           Klass = XT.getObjectByName(Klass.prototype.editableModel);
           documentKey = Klass.prototype.documentKey;
-          nameAttribute = Klass.prototype.nameAttribute || "name";
+          map = Klass.prototype.conversionMap;
         } else {
           documentKey = relation.relatedModel.prototype.documentKey;
-          nameAttribute = relation.relatedModel.prototype.nameAttribute || "name";
+          map = relation.relatedModel.prototype.conversionMap;
         }
         // Most account docs will make this upper again, but needs to be lower for user account
         attrs[documentKey] = model.get("number");
-        attrs[nameAttribute] = model.get("name");
+        // Map other attribute candidates
+        _.each(map, function (value, key) {
+          attrs[value] = model.get(key);
+        });
 
         // Init function for new workspace. Makes sure the workspace understands
         // The account is already "converted".
index 6ced28c..24f62a3 100644 (file)
           "column": "cust_number"
         }
       },
+      {
+        "name": "quoteDate",
+        "attr": {
+          "type": "Date",
+          "column": "quhead_quotedate"
+        }
+      },
       {
         "name": "shipVia",
         "attr": {
           "column": "quhead_shipvia"
         }
       },
+      {
+        "name": "total",
+        "attr": {
+          "type": "Money",
+          "column": "total"
+        }
+      },
       {
         "name": "opportunity",
         "attr": {
index 2d49120..44fbfab 100644 (file)
           "column": "cust_number"
         }
       },
+      {
+        "name": "orderDate",
+        "attr": {
+          "type": "Date",
+          "column": "cohead_orderdate"
+        }
+      },
       {
         "name": "status",
         "attr": {
           "column": "cohead_shipvia"
         }
       },
+      {
+        "name": "total",
+        "attr": {
+          "type": "Money",
+          "column": "total"
+        }
+      },
       {
         "name": "opportunity",
         "attr": {
diff --git a/enyo-client/database/source/en/strings.js b/enyo-client/database/source/en/strings.js
new file mode 100644 (file)
index 0000000..7a52cf5
--- /dev/null
@@ -0,0 +1,419 @@
+/*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 XT:true */
+
+// Place strings you want to localize here.  In your app, use the key and
+// localize it using "key string".loc().  HINT: For your key names, use the
+// english string with an underscore in front.  This way you can still see
+// how your UI will look and you'll notice right away when something needs a
+// localized string added to this file!
+
+(function () {
+  "use strict";
+
+  var lang = XT.stringsFor("en_US", {
+    "_xtdb_attachQuoteToOpportunity1": "The selected Quote cannot be attached because the Quote cannot be found.",
+    "_xtdb_attachQuoteToOpportunity2": "The selected Quote cannot be attached because the Opportunity cannot be found.",
+    "_xtdb_attachQuoteToOpportunity3": "The selected Quote cannot be attached because it is already associated with an Opportunity. You must detach this Quote before you may attach it.",
+    "_xtdb_attachSalesOrderToOpportunity1": "The selected Sales Order cannot be attached because the Sales Order cannot be found.",
+    "_xtdb_attachSalesOrderToOpportunity2": "The selected Sales Order cannot be attached because the Opportunity cannot be found.",
+    "_xtdb_attachSalesOrderToOpportunity3": "The selected Sales Order cannot be attached because it is already associated with an Opportunity. You must detach this Sales Order before you may attach it.",
+    "_xtdb_changeCMHeadTaxAuth1": "This Credit Memo was not found.",
+    "_xtdb_changeCMHeadTaxAuth2": "This Tax Authority was not found.",
+    "_xtdb_changeInvoiceTaxZone1": "This Invoice was not found.",
+    "_xtdb_changeInvoiceTaxZone2": "Freight Tax Type was not found.",
+    "_xtdb_changeCobTaxZone1": "This Bill was not found.",
+    "_xtdb_changeCobTaxZone2": "Freight Tax Type was not found.",
+    "_xtdb_changeQuoteTaxZone1": "This Quote was not found.",
+    "_xtdb_changeSOTaxZone1": "This Sales Order was not found.",
+    "_xtdb_changeTOTax1": "This Transfer Order was not found.",
+    "_xtdb_changeTOTaxAuth2": "This Tax Authority was not found.",
+    "_xtdb_closeAccountingPeriod1": "The selected Accounting Period cannot be closed because it is already closed.",
+    "_xtdb_closeAccountingPeriod2": "The selected Accounting Period cannot be closed because there is a gap between the end of the previous Period and the start of this Period. You must edit either the previous Perod or this Period to eliminate the gap.",
+    "_xtdb_closeAccountingPeriod3": "The selected Accounting Period cannot be closed because the previous Period is not closed. You must close the previous Period before you may close this Period.",
+    "_xtdb_closeAccountingPeriod4": "The selected Accounting Period cannot be closed because there is a gap between the end of this Period and the start of the next Period. You must edit either this Period or the next Period to eliminate the gap.",
+    "_xtdb_closeAccountingPeriod5": "The selected Accounting Period cannot be closed because it ends in the future.",
+    "_xtdb_closeAccountingPeriod6": "The selected Accounting Period cannot be closed because it is the last period in the Fiscal Year and the next Fiscal Year has not been defined yet. Create the next Fiscal Year before closing this Accounting Period.",
+    "_xtdb_closeAccountingYearPeriod7": "The selected Fiscal Year cannot be closed because you have not specified a Year End Equity Account in the accounting configuration.",
+    "_xtdb_closeAccountingYearPeriod8": "The selected Fiscal Year cannot be closed because there does not seem to be an Accounting Period defined for the beginning of the next Fiscal Year.",
+    "_xtdb_closeAccountingYearPeriod9": "The selected Fiscal Year cannot be closed because there is no Trial Balance record for the account in the required Period. Or you have not specified a Year End Equity Account in the accounting configuration.",
+    "_xtdb_closeAccountingYearPeriod10": "The selected Fiscal Year cannot be closed because there are periods within the year that are still open.",
+    "_xtdb_closeAccountingYearPeriod11": "The selected Fiscal Year cannot be closed because there are prior years that are still open.",
+    "_xtdb_closeToItem1": "The item cannot be Closed at this time as there is inventory at shipping.",
+    "_xtdb_convertCustomerToProspect10": "Could not convert Customer to Prospect to because there is already a Prospect with this internal ID.",
+    "_xtdb_convertProspectToCustomer10": "Could not convert Prospect to Customer because there is already a Customer with this internal ID.",
+    "_xtdb_convertQuote1": "Quote #%1 has one or more line items without a warehouse specified. These line items must be fixed before you may convert this quote.",
+    "_xtdb_convertQuote2": "Cannot find the Customer data for Quote #%1.",
+    "_xtdb_convertQuote3": "Quote #%1 is associated with a Prospect, not a Customer. Convert the Prospect to a Customer first.",
+    "_xtdb_convertQuote4": "Quote #%1 is for a Customer that has been placed on a Credit Hold and you do not have privilege to create Sales Orders for Customers on Credit Hold. The selected Customer must be taken off of Credit Hold before you may create convert this Quote.",
+    "_xtdb_convertQuote5": "Quote #%1 is for a Customer that has been placed on a Credit Warning and you do not have privilege to create Sales Orders for Customers on Credit Warning. The selected Customer must be taken off of Credit Warning before you may create convert this Quote.",
+    "_xtdb_convertQuote6": "Quote #%1 has expired and can not be converted.",
+    "_xtdb_copyItemSite1": "Could not copy the Item Site because it does not appear to exist.",
+    "_xtdb_copyItemSite2": "Could not copy the Item Site because the warehouse for the new Item Site record does not appear to exist.",
+    "_xtdb_copyItemSite3": "You do not have sufficient privilege to create an Item Site.",
+    "_xtdb_copyBOM1": "Could not find the Source BOM to copy.",
+    "_xtdb_copyBOM2": "The selected source Item does not have any Bill of Material Component Items associated with it.",
+    "_xtdb_copyBOM3": "The selected target Item already has a Bill of Materials associated with it. You must first delete the Bill of Materials for the selected target item before attempting to copy an existing Bill of Materials.",
+    "_xtdb_copyBOM4": "The Item you are trying to copy this Bill of Material to is a component item which would cause a recursive Bill of Material.",
+    "_xtdb_copyPO1": "Could not find the P/O to copy.",
+    "_xtdb_copyPO2": "The Vendor of the original P/O does not match the Vendor for the copy. Changing the Vendor is not yet supported when copying a P/O.",
+    "_xtdb_copyPO3": "The system does not allow purchases of Items for this Vendor without Item Sources and at least one line item item in the original P/O does not have an active Item Source.",
+    "_xtdb_copyPO4": "At least one line item in the original P/O does not have an active Item Source Price for this Vendor.",
+    "_xtdb_copyPrj1": "Copying an existing project failed, possibly because the source project does not exist.",
+    "_xtdb_correctOperationPosting1": "You may not correct a quantity greater than the amount originally posted.",
+    "_xtdb_correctReceipt12": "The receipt has been split and may not be corrected. Correct Receipt.",
+    "_xtdb_createAccountingPeriod1": "The Start Date falls within another Accounting Period.",
+    "_xtdb_createAccountingPeriod2": "The End Date falls within another Accounting Period.",
+    "_xtdb_createAccountingPeriod3": "The Start and End Dates enclose another Accounting Period.",
+    "_xtdb_createAccountingPeriod4": "The Period dates are outside the selected Fiscal Year.",
+    "_xtdb_createAccountingPeriod5": "The Start Date must be prior to the End Date.",
+    "_xtdb_createAccountingYearPeriod1": "The Year is closed.",
+    "_xtdb_createAccountingYearPeriod2": "Year dates may not overlap another year.",
+    "_xtdb_createAccountingYearPeriod3": "Year dates may not overlap another year.",
+    "_xtdb_createAccountingYearPeriod4": "Periods exist for this year outside the proposed dates.",
+    "_xtdb_createAccountingYearPeriod5": "The Start Date must be prior to the End Date",
+    "_xtdb_createAPCreditMemoApplication1": "You may not apply more than the balance due to this document.",
+    "_xtdb_createAPCreditMemoApplication2": "You may not apply more than the amount available to apply for this Credit Memo.",
+    "_xtdb_createARCreditMemo1": "Either the Prepaid Account or the A/R Account  for this Customer could not be found.",
+    "_xtdb_createBOMItem1": "You may not create a BOM Item that defines a Parent that is composed of itself.",
+    "_xtdb_createBOMItem2": "The Component that you have selected for this BOM Item is a manufactured or phantom Item that uses the Parent Item as a Component Item in its own BOM. You may not create a recursive BOM.",
+    "_xtdb_createCrmAcct1": "This CRM Account Number is already in use by an existing CRM Account. Please choose a different number and save again.",
+    "_xtdb_createCrmAcct2": "This CRM Account Number is already in use by an existing Customer. Please choose a different number and save again.",
+    "_xtdb_createCrmAcct5": "This CRM Account Number is already in use by an existing Prospect. Please choose a different number and save again.",
+    "_xtdb_createCrmAcct6": "This CRM Account Number is already in use by an existing Vendor. Please choose a different number and save again.",
+    "_xtdb_createCrmAcct7": "This CRM Account Number is already in use by an existing Tax Authority. Please choose a different number and save again.",
+    "_xtdb_createProspect1": "Cannot create a Prospect because there is no CRM Account to tie it to.",
+    "_xtdb_createProspect2": "Cannot create a Prospect for this CRM Account because it is already a Customer.",
+    "_xtdb_createProspect3": "Cannot create a Prospect for this CRM Account because it is already a Prospect.",
+    "_xtdb_createPurchaseToSale1": "SO Header Information related to this SO Item not found!",
+    "_xtdb_createPurchaseToSale2": "Item Source Information not found!",
+    "_xtdb_createRecurringItems10": "Cannot create recurring items with an unrecognized object type.",
+    "_xtdb_CreateRevision2": "Revision control not enabled.",
+    "_xtdb_createTodoItem1": "The To-Do List Item cannot be created as there is no assigned User.",
+    "_xtdb_createTodoItem2": "The To-Do List Item cannot be created as the Task Name is blank.",
+    "_xtdb_createTodoItem3": "The To-Do List Item cannot be created as there is no Due Date.",
+    "_xtdb_createWo1": "Work Order can not be created because Site not allowed to Manufacture this Item.",
+    "_xtdb_createWo2": "Work Order can not be exploded because items on the BOM exist without itemsites.",
+    "_xtdb_deleteAccount1": "The selected G/L Account cannot be deleted as it is currently used in one or more Cost Categories. You must reassign these Cost Category assignments before you may delete the selected G/L Account.",
+    "_xtdb_deleteAccount2": "The selected G/L Account cannot be deleted as it is currently used in one or more Sales Account Assignment. You must reassign these Sales Account Assignments before you may delete the selected G/L Account.",
+    "_xtdb_deleteAccount3": "The selected G/L Account cannot be deleted as it is currently used in one or more Customer A/R Account assignments. You must reassign these Customer A/R Account assignments before you may delete the selected G/L Account.",
+    "_xtdb_deleteAccount4": "The selected G/L Account cannot be deleted as it is currently used as the default Account one or more Sites. You must reassign the default Account for these Sites before you may delete the selected G/L Account.",
+    "_xtdb_deleteAccount5": "The selected G/L Account cannot be deleted as it is currently used in one or more Bank Accounts. You must reassign these Bank Accounts before you may delete the selected G/L Account.",
+    "_xtdb_deleteAccount6": "The selected G/L Account cannot be deleted as it is currently used in one or more Expense Categories. You must reassign these Expense Categories before you may delete the selected G/L Account.",
+    "_xtdb_deleteAccount7": "The selected G/L Account cannot be deleted as it is currently used in one or more Tax Codes. You must reassign these Tax Codes before you may delete the selected G/L Account.",
+    "_xtdb_deleteAccount8": "The selected G/L Account cannot be deleted as it is currently used in one or more Standard Journals. You must reassign these Standard Journal Items before you may delete the selected G/L Account.",
+    "_xtdb_deleteAccount9": "The selected G/L Account cannot be deleted as it is currently used in one or more Customer A/P Account assignments. You must reassign these Customer A/P Account assignments before you may delete the selected G/L Account.",
+    "_xtdb_deleteAccount10": "The selected G/L Account cannot be deleted as it is currently used in one or more Currency definition. You must reassign these Currency definitions before you may delete the selected G/L Account.",
+    "_xtdb_deleteAccount11": "The selected G/L Account cannot be deleted as it is currently used in one or more A/R Open Items. You must reassign these Currency definitions before you may delete the selected G/L Account.",
+    "_xtdb_deleteAccount99": "The selected G/L Account cannot be deleted as there have been G/L Transactions posted against it.",
+    "_xtdb_deleteAccountingPeriod4": "The selected Accounting Period has G/L Transactions posted against it and, thus, cannot be deleted.",
+    "_xtdb_deleteAccountingPeriod5": "The selected Accounting Period is not the last accounting period and cannot be deleted.",
+    "_xtdb_deleteAccountingYearPeriod1": "The selected Fiscal Year cannot be deleted because it is closed.",
+    "_xtdb_deleteAccountingYearPeriod2": "The selected Fiscal Year cannot be deleted because there are Accounting Periods defined for it.",
+    "_xtdb_deleteAddress1": "The selected Address cannot be deleted as it is used by an active Contact.",
+    "_xtdb_deleteAddress2": "The selected Address cannot be deleted as it is used by an active Vendor.",
+    "_xtdb_deleteAddress3": "The selected Address cannot be deleted as it is used by an active Ship-To Address.",
+    "_xtdb_deleteAddress4": "The selected Address cannot be deleted as it is used by an active Vendor Address.",
+    "_xtdb_deleteAddress5": "The selected Address cannot be deleted as it is used by an active Site.",
+    "_xtdb_deleteBankAdjustmentType1": "The selected Bank Adjustment Type cannot be deleted because it is currently used by a Bank Adjustment.",
+    "_xtdb_deleteCashrcpt1": "The selected Cash Receipt cannot be deleted because it is a Customer Deposit made with a Credit Card and the card has already been charged.",
+    "_xtdb_deleteCharacteristic1": "The selected Characteristic cannot be deleted because there are Items assigned to it. You must remove these assignments before you may delete the selected Characteristic.",
+    "_xtdb_deleteCharacteristic2": "The selected Characteristic cannot be deleted because there are Customers assigned to it. You must remove these assignments before you may delete the selected Characteristic.",
+    "_xtdb_deleteCharacteristic3": "The selected Characteristic cannot be deleted because there are Addresses assigned to it. You must remove these assignments before you may delete the selected Characteristic.",
+    "_xtdb_deleteCharacteristic4": "The selected Characteristic cannot be deleted because there are Contacts assigned to it. You must remove these assignments before you may delete the selected Characteristic.",
+    "_xtdb_deleteCharacteristic5": "The selected Characteristic cannot be deleted because there are CRM Accounts assigned to it. You must remove these assignments before you may delete the selected Characteristic.",
+    "_xtdb_deleteCharacteristic6": "The selected Characteristic cannot be deleted because there are Incidents assigned to it. You must remove these assignments before you may delete the selected Characteristic.",
+    "_xtdb_deleteCharacteristic7": "The selected Characteristic cannot be deleted because there are Employees assigned to it. You must remove these assignments before you may delete the selected Characteristic.",
+    "_xtdb_deleteCheck1": "Cannot delete this check because either it has not been voided, it has already been posted or replaced, or it has been transmitted electronically.",
+    "_xtdb_deleteClassCode1": "The selected Class Code cannot be deleted because there are Items that are assigned to it. You must reassign these Items before you may delete the selected Class Code.",
+    "_xtdb_deleteCompany1": "The selected Company cannot be deleted as it is in use by existing Account. You must reclass these Accounts before you may delete the selected Company.",
+    "_xtdb_deleteContact1": "The selected Contact cannot be deleted as s/he is the primary or secondary Contact for a CRM Account.",
+    "_xtdb_deleteContact2": "The selected Contact cannot be deleted as s/he is the Correspondence or Billing Contact for a Customer.",
+    "_xtdb_deleteContact3": "The selected Contact cannot be deleted as s/he is the primary or secondary Contact for a Vendor.",
+    "_xtdb_deleteContact4": "The selected Contact cannot be deleted as s/he is the Contact for a Ship-To Address.",
+    "_xtdb_deleteContact5": "The selected Contact cannot be deleted as s/he is the Contact for a Vendor Address.",
+    "_xtdb_deleteContact6": "The selected Contact cannot be deleted as s/he is the Contact for a Site.",
+    "_xtdb_deleteCRMAccount1": "The selected CRM Account cannot be deleted as it is a Customer.",
+    "_xtdb_deleteCRMAccount2": "The selected CRM Account cannot be deleted as it is a Vendor.",
+    "_xtdb_deleteCRMAccount3": "The selected CRM Account cannot be deleted as it is a Prospect.",
+    "_xtdb_deleteCRMAccount4": "The selected CRM Account cannot be deleted as it has Contacts. You may Detach the Contacts from this CRM Account and try deleting it again or set its status to inactive",
+    "_xtdb_deleteCRMAccount5": "The selected CRM Account cannot be deleted as it is a Tax Authority.",
+    "_xtdb_deleteCRMAccount6": "The selected CRM Account cannot be deleted as it is a Sales Rep.",
+    "_xtdb_deleteCRMAccount7": "The selected CRM Account cannot be deleted as it is a Employee.",
+    "_xtdb_deleteCRMAccount8": "The selected CRM Account cannot be deleted as it is a User.",
+    "_xtdb_deleteCustomer1": "The selected Customer cannot be deleted as there are still Ship-Tos assigned to it. You must delete all of the selected Customer's Ship-Tos before you may delete it.",
+    "_xtdb_deleteCustomer2": "The selected Customer cannot be deleted as there has been Sales History recorded for this Customer. You may Edit the selected Customer and set its status to inactive.",
+    "_xtdb_deleteCustomer3": "Credit Memos",
+    "_xtdb_deleteCustomer4": "custhist",
+    "_xtdb_deleteCustomer5": "A/R Open",
+    "_xtdb_deleteCustomer6": "The selected Customer cannot be deleted as Checks have been written to it.",
+    "_xtdb_deleteCustomer7": "The selected Customer cannot be deleted as there are still Invoices assigned to it. You must delete all of the selected Customer's Invoices before you may delete it",
+    "_xtdb_deleteCustomer8": "The selected Customer cannot be deleted as there are still Quotes assigned to it. You must delete all of the selected Customer's Quotes before you may delete it",
+    "_xtdb_deleteCustomerType1": "The selected Customer Type cannot be deleted as there are one or more Customers assigned to it. You must reassign these Customers before you may delete the selected Customer Type.",
+    "_xtdb_deleteEmpgrp1": "The selected Employee Group cannot be deleted as there are one or more Employees assigned to it. You must reassign these Employees before you may delete the selected Employee Group.",
+    "_xtdb_deleteForm1": "The selected Check Format cannot be deleted as it is used by one or more Bank Accounts. You must reassign these Bank Accounts before you may delete the selected Check Form.",
+    "_xtdb_deleteFreightClass1": "The selected Freight Class cannot be deleted because there are Items that are assigned to it. You must reassign these Items before you may delete the selected Freight Class.",
+    "_xtdb_deleteIncident1": "This Incident cannot be deleted as there are To-Do List Items associated with it.",
+    "_xtdb_deleteIncident2": "This Incident cannot be deleted as there are Comments associated with it.",
+    "_xtdb_deleteItem1": "This Item cannot be deleted as it is used in one or more bills of materials.",
+    "_xtdb_deleteItem2": "This Item cannot be deleted as there are Item Site records associated with it.",
+    "_xtdb_deleteItem3": "This Item cannot be deleted as there are Substitute records associated with it.",
+    "_xtdb_deleteItem4": "This Item cannot be deleted as there are Breeder BOM records associated with it.",
+    "_xtdb_deleteItem5": "This Item cannot be deleted as there are assignement records associated with it.",
+    "_xtdb_deleteItem6": "This Item cannot be deleted as there are Revision Control records associated with it.",
+    "_xtdb_deleteItemSite1": "The selected Item Site cannot be deleted as there is Inventory History posted against it. You may edit the Item Site and deactivate it.",
+    "_xtdb_deleteItemSite2": "The selected Item Site cannot be deleted as there is Work Order History posted against it. You may edit the Item Site and deactivate it.",
+    "_xtdb_deleteItemSite3": "The selected Item Site cannot be deleted as there is Sales History posted against it. You may edit the Item Site and deactivate it.",
+    "_xtdb_deleteItemSite4": "The selected Item Site cannot be deleted as there is Purchasing History posted against it. You may edit the Item Site and deactivate it.",
+    "_xtdb_deleteItemSite5": "The selected Item Site cannot be deleted as there is Planning History posted against it. You may edit the Item Site and deactivate it.",
+    "_xtdb_deleteItemSite6": "The selected Item Site cannot be deleted as there are Production Plans associated with it.",
+    "_xtdb_deleteItemSite7": "The selected Item Site cannot be deleted as it is used as a Supplied from Site.",
+    "_xtdb_deleteItemSite9": "The selected Item Site cannot be deleted as there is a non-zero Inventory Quantity posted against it.",
+    "_xtdb_deleteItemUOMConv1": "This UOM Conversion cannot be deleted as there are records for this Item which use this UOM.",
+    "_xtdb_deleteOpenRecurringItems10": "Cannot delete open recurring items with an invalid type.",
+    "_xtdb_deleteOpenRecurringItems11": "Cannot delete open recurring items without a valid parent item.",
+    "_xtdb_deleteOpportunity1": "The selected Opportunity cannot be deleted because there are ToDo Items assigned to it. You must delete or reassign these ToDo Items before you may delete it.",
+    "_xtdb_deleteOpportunity2": "The selected Opportunity cannot be deleted because there are Quotes assigned to it. You must delete or reassign these Quotes before you may delete it.",
+    "_xtdb_deleteOpportunity3": "The selected Opportunity cannot be deleted because there are Sales Orders assigned to it. You must delete or reassign these Sales Orders before you may delete it.",
+    "_xtdb_deletePackage1": "The selected Package cannot be deleted because there are other packages that depend on it to function properly.",
+    "_xtdb_deleteProfitCenter1": "The selected Profit Center cannot be deleted as it is in use by existing Account. You must reclass these Accounts before you may delete the selected Profit Center.",
+    "_xtdb_deleteProspect1": "The selected Prospect cannot be deleted as there are still Quotes for it. You must delete all of this Prospect's Quotes before you may delete the Prospect.",
+    "_xtdb_deleteSalesRep1": "The selected Sales Rep. cannot be deleted as he/she is still assigned to one or more Customers. You must reassign different Sales Reps. to all Customers to which the selected Sales Rep. is assigned before you may delete the selected Sales Rep.",
+    "_xtdb_deleteSalesRep2": "The selected Sales Rep. cannot be deleted as he/she is still assigned to one or more Ship-tos. You must reassign different Sales Reps. to all Ship-tos to which the selected Sales Rep. is assigned before you may delete the selected Sales Rep.",
+    "_xtdb_deleteSalesRep3": "The selected Sales Rep. cannot be deleted as there has been sales history recorded against him/her. You may edit and set the selected Sales Rep's active status to inactive.",
+    "_xtdb_deleteShipto1": "The selected Shipto cannot be deleted as there is still Archived Sales History assigned to it. You must delete all of the selected Customer's Ship-Tos before you may delete it.",
+    "_xtdb_deleteShipto2": "The selected Shipto cannot be deleted as there has been Sales History recorded for this Shipto. You may Edit the selected Shipto and set its status to inactive.",
+    "_xtdb_deleteShipto3": "The selected Shipto cannot be deleted as there has been Credit Memos recorded for this Shipto. You may Edit the selected Shipto and set its status to inactive.",
+    "_xtdb_deleteShipto4": "The selected Shipto cannot be deleted as there has been Sales History recorded for this Shipto. You may Edit the selected Shipto and set its status to inactive.",
+    "_xtdb_deleteShipto5": "The selected Shipto cannot be deleted as there has been Quote History recorded for this Shipto. You may Edit the selected Shipto and set its status to inactive.",
+    "_xtdb_deleteShipto6": "The selected Shipto cannot be deleted as there has been Invoice History recorded for this Shipto. You may Edit the selected Shipto and set its status to inactive.",
+    "_xtdb_deleteSO1": "This Sales Order cannot be deleted because a Credit Card has been charged for it.",
+    "_xtdb_deleteSO2": "This Sales Order cannot be deleted because there is Credit Card transaction history for it.",
+    "_xtdb_deleteSO101": "This Sales Order cannot be deleted as some of its line items have already been shipped.",
+    "_xtdb_deleteSO102": "This Sales Order cannot be deleted as some of its line items have already been issued to shipping.",
+    "_xtdb_deleteSO103": "This Sales Order cannot be deleted as some of its line items are linked to a Return Authorization. You must resolve this conflict before you may delete this Sales Order.",
+    "_xtdb_deleteSO104": "This Sales Order cannot be deleted as some of its line items are linked to an In Process Work Order. You must resolve this conflict before you may delete this Sales Order.",
+    "_xtdb_deleteSO105": "This Sales Order cannot be deleted as some of its line items have transaction history.",
+    "_xtdb_deleteSO10": "This Sales Order cannot be deleted as one or more of its Line items have associated Purchase Order Line Items which are either closed or have receipts associated with them. You may want to consider cancelling this Sales Order instead.",
+    "_xtdb_deleteSO20": "The Sales Order was deleted successfully. However, the Released Purchase Orders associated with one or more line items of this Sales Order could not be deleted. You must delete these Purchase Orders seperately if desired.",
+    "_xtdb_deleteSOItem101": "This Sales Order Item cannot be deleted as it has already been shipped.",
+    "_xtdb_deleteSOItem102": "This Sales Order Item cannot be deleted as it has already been issued to shipping.",
+    "_xtdb_deleteSOItem103": "This Sales Order Item cannot be deleted as it is linked to a Return Authorization. You must resolve this conflict before you may delete this Sales Order Item.",
+    "_xtdb_deleteSOItem104": "This Sales Order Item cannot be deleted as it is linked to an In Process Work Order. You must resolve this conflict before you may delete this Sales Order Item.",
+    "_xtdb_deleteSOItem105": "This Sales Order Item cannot be deleted as it has generated Inventory History. You may want to consider cancelling this Sales Order Item.",
+    "_xtdb_deleteSOItem10": "This Sales Order Item cannot be deleted as it has associated Purchase Order Line Item which is either closed or has receipts associated with it. You may want to consider cancelling this Sales Order Item instead.",
+    "_xtdb_deleteSOItem20": "The Sales Order Item was deleted successfully. However, the Purchase Order Line Item associated with this Sales Line could not be deleted. You must delete this Purchase Line Item seperately if desired.",
+    "_xtdb_deleteSubaccount1": "The selected Subaccount cannot be deleted as it is in use by existing Account. You must reclass these Accounts before you may delete the selected Subaccount.",
+    "_xtdb_deleteTO1": "This Transfer Order cannot be deleted as some of its line items have already been shipped.",
+    "_xtdb_deleteTO2": "This Transfer Order cannot be deleted as some of its line items have already been issued to shipping. You must return this stock before you may delete this Transfer Order.",
+    "_xtdb_deleteTax10": "This Tax Code cannot be deleted as there are Tax Assignments that refer to it. Change those Tax Assignments before trying to delete this Tax Code.",
+    "_xtdb_deleteTaxAuthority1": "This Tax Authority cannot be deleted as there are Tax Selections for it. Change or delete those Tax Selections before deleting this Tax Authority.",
+    "_xtdb_deleteTaxAuthority7": "This Tax Authority cannot be deleted as Checks have been written to it.",
+    "_xtdb_deleteTaxClass1": "This Tax Class cannot be deleted as there are Tax Codes that refer to it.",
+    "_xtdb_deleteTaxZone1": "This Tax Zone cannot be deleted as there are Tax Assignments that refer to it.",
+    "_xtdb_deleteTaxZone2": "This Tax Zone cannot be deleted as there are Tax Registrations that refer to it.",
+    "_xtdb_deleteTo1": "This Transfer Order cannot be deleted as line items for it have already been shipped.",
+    "_xtdb_deleteTo2": "This Transfer Order cannot be deleted as line items for it have been issued to shipping.",
+    "_xtdb_deleteTo3": "This Transfer Order cannot be deleted as the order number cannot be released.",
+    "_xtdb_deleteVendor1": "The selected Vendor cannot be deleted as there have been P/Os created against it. You may deactivate this Vendor instead.",
+    "_xtdb_deleteVendor2": "The selected Vendor cannot be deleted as there has been P/O Material Receipt History posted against it. You may deactivate this Vendor instead.",
+    "_xtdb_deleteVendor3": "The selected Vendor cannot be deleted as there has been P/O Material Return History posted against it. You may deactivate this Vendor instead.",
+    "_xtdb_deleteVendor4": "The selected Vendor cannot be deleted as there have been Vouchers posted against it. You may deactivate this Vendor instead.",
+    "_xtdb_deleteVendor5": "The selected Vendor cannot be deleted as there have been A/P Open Items posted against it. You may deactivate this Vendor instead.",
+    "_xtdb_deleteVendor6": "The selected Vendor cannot be deleted as there have been A/P Applications posted against it. You may deactivate this Vendor instead.",
+    "_xtdb_deleteVendor7": "The selected Vendor cannot be deleted as there have been Checks posted against it. You may deactivate this Vendor instead.",
+    "_xtdb_deleteWo1": "The Work Order cannot be deleted because time clock entries exist for it. Please Close it instead of trying to Delete it.",
+    "_xtdb_deleteWo2": "The Work Order cannot be deleted for Job Item Types. Please close the associated Sales Order instead of trying to Delete it.",
+    "_xtdb_deleteWo3": "The Work Order cannot be deleted in the current status. Please close the associated Sales Order instead of trying to Delete it.",
+    "_xtdb_disablePackage1": "This version of the PostgreSQL database server does not support package enabling or disabling. Upgrade to PostgreSQL 8.2 or later.",
+    "_xtdb_disablePackage2": "Could not find a package with the internal id % to enable or disable.",
+    "_xtdb_distributeVoucherLine2": "Distribution would result in zero quantity and amount. Please distribute manually.",
+    "_xtdb_distributeVoucherLine3": "The purchase order and voucher have different currencies. Please distribute manually.",
+    "_xtdb_distributeVoucherLine4": "Distribution would result in a negative amount. Please distribute manually.",
+    "_xtdb_distributeVoucherLine5": "Item has multiple cost elements. Please distribute manually.",
+    "_xtdb_editccnumber1": "You must select Master Card, Visa, American Express or Discover as the credit card type.",
+    "_xtdb_editccnumber2": "The length of a Master Card credit card number has to be 16 digits.",
+    "_xtdb_editccnumber3": "The length of a Visa credit card number has to be either 13 or 16 digits.",
+    "_xtdb_editccnumber4": "The length of an American Express credit card number has to be 15 digits.",
+    "_xtdb_editccnumber5": "The length of a Discover credit card number has to be 16 digits.",
+    "_xtdb_editccnumber6": "The first two digits for a valid Master Card number must be between 51 and 55",
+    "_xtdb_editccnumber7": "The first digit for a valid Visa number must be 4",
+    "_xtdb_editccnumber8": "The first two digits for a valid American Express number must be 34 or 37.",
+    "_xtdb_editccnumber9": "The first four digits for a valid Discover Express number must be 6011.",
+    "_xtdb_editccnumber10": "The credit card number that you have provided is not valid.",
+    "_xtdb_enterReceipt1": "Information for this order line item could not be found. If it is a Purchase Order Item then it does not appear to exist. If it is a Transfer Order Item then either the Transfer Order does not exist or there is no Item Site for this line item.",
+    "_xtdb_explodeWo1": "Work Order %1 cannot be Exploded as there is no valid Bill of Materials on file for the Work Order Item. You must create a valid Bill of Materials for the Work Order Item before you may explode the Work Order.",
+    "_xtdb_explodeWo2": "Work Order %1 cannot be Exploded as there are one or more Component Items on the Bill of Materials for the Work Order Item that are not valid in the Work Order Site. You must create a valid Item Site for all of the Component Items before you may explode this Work Order.",
+    "_xtdb_explodeWo3": "Work Order %1 cannot be Exploded as there are one or more Co-Product/By-Product Items on the Breeder Bill of Materials for the Work Order Item that do not exist in the Work Order Site. You must create a valid Item Site for all of the Co-Product/ By-Product Items before you may explode this Work Order.",
+    "_xtdb_explodeWo4": "Work Order %1 cannot be Exploded because it is not Open.",
+    "_xtdb_explodeWo9": "Work Order %1 cannot be Exploded because the quantity ordered is not valid.",
+    "_xtdb_fkeycheck1": "Cannot check dependencies when the contact is one of multiple foreign key columns.",
+    "_xtdb_freezeAccountingPeriod1": "Cannot freeze this Accounting Period because it is still open.",
+    "_xtdb_freezeAccountingPeriod2": "Cannot freeze this Accounting Period because it is already frozen.",
+    "_xtdb_insertGLTransaction3": "Nothing to do as the value to post to the G/L is 0.",
+    "_xtdb_insertGLTransaction4": "Cannot post a G/L transaction to a closed period.",
+    "_xtdb_insertIntoGLSeries1": "Cannot add to a G/L Series because the Account is NULL or -1.",
+    "_xtdb_insertIntoGLSeries4": "Cannot add to a G/L Series because the Accounting Period is closed.",
+    "_xtdb_issueToShipping10": "The Next Shipment Number has not been set in the Configure S/R window. Set that value and try issuing to shipping again.",
+    "_xtdb_issueToShipping11": "Not a supported order type.",
+    "_xtdb_issueToShipping12": "The selected Sales Order is on Credit Hold and must be taken off of Credit Hold before any inventory may be issued to it.",
+    "_xtdb_issueToShipping13": "The selected Sales Order is on Packing Hold and must be taken off of Packing Hold before any inventory may be issued to it.",
+    "_xtdb_issueToShipping14": "The selected Sales Order is on Return Hold. The Customer must return all materials for a related Return Authorization before any inven tory may be issued to this Order.",
+    "_xtdb_issueToShipping15": "The selected Sales Order is configured for Auto Registration. The Customer Account does not have a Primary Contact. A Primary Contact must be assigned to this Customer Account before any inven tory may be issued to this Order.",
+    "_xtdb_issueToShipping20": "There is not enough Inventory to issue the amount required of one of the Average Cost items requested. Average Cost items may not have a negative quantity on hand.",
+    "_xtdb_login1": "The specified Username does not exist in the specified Database. Contact your Systems Administrator to report this issue",
+    "_xtdb_login2": "The specified Username exists in the specified Database but is not Active. Contact your Systems Administrator to report this issue.",
+    "_xtdb_login3": "The specified Database is currently in Maintenance Mode and can only be accessed by System Administators. Contact your Systems Administrator to report this issue.",
+    "_xtdb_massReplaceBomitem1": "Cannot make this BOM Item replacement because it would create a recursive BOM.",
+    "_xtdb_openAccountingPeriod1": "Cannot open this Accounting Period because it is already open.",
+    "_xtdb_openAccountingPeriod2": "Cannot open this Accounting Period because it is frozen.",
+    "_xtdb_openAccountingPeriod3": "Cannot open this Accounting Period because subsequent periods are closed.",
+    "_xtdb_openAccountingPeriod4": "Cannot open this Accounting Period because the fiscal year is closed.",
+    "_xtdb_openAccountingYearPeriod2": "Cannot open this Accounting Year because subsequent years are closed.",
+    "_xtdb_openRecurringItems10": "Cannot count open recurring items with an invalid type.",
+    "_xtdb_openRecurringItems11": "Cannot count open recurring items without a valid parent item.",
+    "_xtdb_openRecurringItems12": "Don't know how to count open recurring invoices.",
+    "_xtdb_postAPCreditMemoApplication1": "There are no A/P Credit Memo applications to post.",
+    "_xtdb_postAPCreditMemoApplication2": "There are no A/P Credit Memo applications to post.",
+    "_xtdb_postAPCreditMemoApplication3": "The total value of the applications that are you attempting to post is greater than the value of the A/P Credit Memo itself.",
+    "_xtdb_postAPCreditMemoApplication4": "At least one A/P Credit Memo application cannot be posted because there is no current exchange rate for its currency.",
+    "_xtdb_postAPCreditMemoApplication5": "The A/P Credit Memo to apply was not found.",
+    "_xtdb_postAPCreditMemoApplication6": "The amount to apply for this A/P Credit Memo is NULL.",
+    "_xtdb_postARCreditMemoApplication1": "There are no A/R Credit Memo applications to post.",
+    "_xtdb_postARCreditMemoApplication2": "Either there are no A/R Credit Memo applications to post or there is no exchange rate for one of the applications.",
+    "_xtdb_postARCreditMemoApplication3": "The total value of the applications that you are attempting to post is greater than the value of the A/R Credit Memo itself. Please reduce the applications to total less than the value of the Credit Memo.",
+    "_xtdb_postARCreditMemoApplication4": "At least one A/R Credit Memo application cannot be posted because there is no current exchange rate for its currency.",
+    "_xtdb_postARCreditMemoApplication5": "The A/R Credit Memo to apply was not found.",
+    "_xtdb_postBankAdjustment1": "This Bank Adjustment could not be posted because the one or more required records do not exist.",
+    "_xtdb_postBankAdjustment3": "This Bank Adjustment could not be posted because the total adjustment is 0 so there is nothing to post.",
+    "_xtdb_postBankReconciliation1": "This Bank Reconciliation could not be posted because the G/L Account could not be verified.",
+    "_xtdb_createInvoice1": "This Billing Selection cannot be posted because it has already been posted.",
+    "_xtdb_createInvoices5": "The G/L Account Assignments for one or more of the Billing Selections that you are trying to post are not configured correctly. Therefore, G/L Transactions cannot be posted for these. You must contact your Systems Administrator to have this corrected before you may post these Billing Selections.",
+    "_xtdb_postCashReceipt1": "The selected Cash Receipt cannot be posted as the amount distributed is greater than the amount received. You must correct this before you may post this Cash Receipt.",
+    "_xtdb_postCashReceipt2": "The selected Cash Receipt cannot be posted as the amount received must be greater than zero. You must correct this before you may post this Cash Receipt.",
+    "_xtdb_postCashReceipt5": "The selected Cash Receipt cannot be posted as the A/R Account cannot be determined. You must make an A/R Account Assignment for the Customer Type to which this Customer is assigned before you may post this Cash Receipt.",
+    "_xtdb_postCashReceipt6": "The selected Cash Receipt cannot be posted as the Bank Account cannot be determined. You must make a Bank Account Assignment for this Cash Receipt before you may post it.",
+    "_xtdb_postCashReceipt7": "The selected Cash Receipt cannot be posted, probably because the Customer's Prepaid Account was not found.",
+    "_xtdb_postCashReceipt8": "Cannot post this Cash Receipt because the credit card records could not be found.",
+    "_xtdb_postCCCashReceipt11": "Cannot post this Cash Receipt because the record of the credit card transaction either does not exist or is not consistent.",
+    "_xtdb_postCCcredit1": "Cannot post this Credit Card refund because the default Bank Account for this Credit Card could not be found.",
+    "_xtdb_postCCcredit2": "Cannot post this Credit Card refund because an invalid id/reference-type pair was passed.",
+    "_xtdb_postCCcredit3": "Cannot post this Credit Card refund because the credit card and refund records could not be found.",
+    "_xtdb_postCCcredit4": "Cannot post this Credit Card refund because the credit card payment records is not for a refund.",
+    "_xtdb_postCheck10": "Cannot post this Check because it has already been posted.",
+    "_xtdb_postCheck11": "Cannot post this Check because the recipient type is not valid.",
+    "_xtdb_postCheck12": "Cannot post this Check because the Expense Category could not be found.",
+    "_xtdb_postCheck13": "Cannot post this Check because the G/L Account against which it is to be posted is not valid.",
+    "_xtdb_postCountTag1": "Cannot post this Count Tag because The total Count Slip quantity is greater than the Count Tag quantity.",
+    "_xtdb_postCountTag2": "Cannot post this Count Tag because the total Count Slip quantity is less than the Count Tag quantity for a Lot/Serial-controlled Item Site.",
+    "_xtdb_postCountTag3": "Cannot post this Count Tag because the total Count Slip quantity is less than the Count Tag quantity and there is no default location.",
+    "_xtdb_postCountTag4": "Cannot post this Count Tag because the total Count Slip quantity is less than the Count Tag quantity and we don't post to default locations.",
+    "_xtdb_postCreditMemo10": "This Credit Memo cannot be posted because it has already been posted.",
+    "_xtdb_postCreditMemo11": "This Credit Memo is on Hold and, thus, cannot be posted.",
+    "_xtdb_postCreditMemo12": "The Sales Account Assignment for this Credit Memo is not configured correctly. Because of this, G/L Transactions cannot be posted for this Credit Memo. You must contact your Systems Administrator to have this corrected before you may post this Credit Memo.",
+    "_xtdb_postCreditMemo14": "The Misc. Charge Account Assignment for this Credit Memo is not configured correctly. Because of this, G/L Transactions cannot be posted for this Credit Memo. You must contact your Systems Administrator to have this corrected before you may post this Credit Memo.",
+    "_xtdb_postCreditMemo16": "The Freight Account Assignment for this Credit Memo is not configured correctly. Because of this, G/L Transactions cannot be posted for this Credit Memo. You must contact your Systems Administrator to have this corrected before you may post this Credit Memo.",
+    "_xtdb_postCreditMemo18": "The A/R Account Assignment for this Credit Memo is not configured correctly. Because of this, G/L Transactions cannot be posted for this Credit Memo. You must contact your Systems Administrator to have this corrected before you may post this Credit Memo.",
+    "_xtdb_postGLSeries4": "Could not post this G/L Series because the Accounting Period is closed.",
+    "_xtdb_postGLSeries5": "Could not post this G/L Series because the G/L Series Discrepancy Account was not found.",
+    "_xtdb_postGLSeriesNoSumm1": "Could not post this G/L Series because the Debits and Credits are unbalanced.",
+    "_xtdb_postInvoice10": "Unable to post this Invoice because it has already been posted.",
+    "_xtdb_postInvoice11": "Unable to post this Invoice because the Sales Account was not found.",
+    "_xtdb_postInvoice12": "Unable to post this Invoice because there was an error processing Line Item taxes.",
+    "_xtdb_postInvoice13": "Unable to post this Invoice because there was an error processing Misc. Line Item taxes.",
+    "_xtdb_postInvoice14": "Unable to post this Invoice because the Freight Account was not found.",
+    "_xtdb_postInvoice15": "Unable to post this Invoice because there was an error processing Freight taxes.",
+    "_xtdb_postInvoice16": "Unable to post this Invoice because there was an error processing Tax Adjustments.",
+    "_xtdb_postInvoice17": "Unable to post this Invoice because the A/R Account was not found.",
+    "_xtdb_postInvTrans1": "Could not post an inventory transaction because the Item Site has no Control Method or the Item has an Item Type of Reference.",
+    "_xtdb_postInvTrans2": "Could not post an inventory transaction because the transaction will cause an Average Costed Item to go negative which is not allowed.",
+    "_xtdb_postProduction1": "Unable to post this Production because the Work Order status is not Exploded, Released, or InProcess.",
+    "_xtdb_postProduction2": "Unable to post this Production because backflushing component usage could not be completed due to missing Item Sites.",
+    "_xtdb_postProduction3": "Unable to post this Production because of missing Item Site or Cost Category.",
+    "_xtdb_postReceipt10": "This Receipt Line has already been posted.",
+    "_xtdb_postReceipt11": "This Receipt Line cannot be posted because it has a quantity of 0.",
+    "_xtdb_postReceipt12": "This Purchase Order Receipt Line has no Standard Cost assigned to it.",
+    "_xtdb_postReceipt16": "Cannot not issue item to shipping. No Sales Order item found against this PO Item.",
+    "_xtdb_postReceipt17": "Cannot not issue item to shipping. Inventory history not found.",
+    "_xtdb_postVoucher5": "The Cost Category for one or more Item Sites for the Purchase Order covered by this Voucher is not configured with Purchase Price Variance or P/O Liability Clearing Account Numbers or the Vendor of this Voucher is not configured with an A/P Account Number. Because of this, G/L Transactions cannot be posted for this Voucher.",
+    "_xtdb_recallShipment1": "This shipment cannot be recalled because it does not appear to have been shipped.",
+    "_xtdb_recallShipment2": "This shipment cannot be recalled because it appears to have been invoiced.",
+    "_xtdb_recallShipment3": "This shipment cannot be recalled because it has already been received at its destination.",
+    "_xtdb_recallShipment4": "This shipment cannot be recalled because it appears to have been invoiced and the invoice has been posted.",
+    "_xtdb_recallShipment5": "This shipment cannot be recalled because it contains one or more Line Items with Site/ Product Category/Customer combinations that have not been properly described in Sales Account Assignments. These assignments must be made before G/L Transactions can be posted and this Sales Order is allowed to be recalled.",
+    "_xtdb_recallShipment6": "This shipment cannot be recalled because the associated Transfer Order is closed.",
+    "_xtdb_releasePurchaseOrder1": "Cannot release this Purchase Order because it does not have any unreleased Purchase Order Items.",
+    "_xtdb_releaseTransferOrder1": "Cannot release this Transfer Order because it does not have any line items.",
+    "_xtdb_releaseUnusedBillingHeader1": "Cannot release this Billing Header because it has already been posted.",
+    "_xtdb_releaseUnusedBillingHeader2": "Cannot release this Billing Header because it has Line Items.",
+    "_xtdb_relocateInventory1": "You cannot Relocate more inventory than is available.",
+    "_xtdb_returnCompleteShipment5": "Either a Cost Category for the Items you are trying to Return is not configured with a Shipping Asset Account Number or a Customer Type/Product Category/Site Sales Account assignment does not exist . Because of this, G/L Transactions cannot be posted for this Return. You must contact your Systems Administrator to have this corrected before you may Return this Shipment.",
+    "_xtdb_returnItemShipments5": "Either a Cost Category for the Items you are trying to Return is not configured with a Shipping Asset Account Number or a Customer Type/Product Category/Site Sales Account assignment does not exist . Because of this, G/L Transactions cannot be posted for this Return. You must contact your Systems Administrator to have this corrected before you may Return this Shipment.",
+    "_xtdb_returnShipmentTransaction5": "Either a Cost Category for the Items you are trying to Return is not configured with a Shipping Asset Account Number or a Customer Type/Product Category/Site Sales Account assignment does not exist . Because of this, G/L Transactions cannot be posted for this Return. You must contact your Systems Administrator to have this corrected before you may Return this Shipment.",
+    "_xtdb_reverseCashReceipt1": "The selected Cash Receipt cannot be reversed as the amount distributed is greater than the amount received.",
+    "_xtdb_reverseCashReceipt2": "The selected Cash Receipt cannot be reversed as the amount received must be greater than zero.",
+    "_xtdb_reverseCashReceipt5": "The selected Cash Receipt cannot be reversed as the A/R Account cannot be determined. You must make an A/R Account Assignment for the Customer Type to which this Customer is assigned before you may reverse this Cash Receipt.",
+    "_xtdb_reverseCashReceipt6": "The selected Cash Receipt cannot be reversed as the Bank Account cannot be determined. You must make a Bank Account Assignment for this Cash Receipt before you may reverse it.",
+    "_xtdb_reverseCashReceipt7": "The selected Cash Receipt cannot be reversed, probably because the Customer's Prepaid Account was not found.",
+    "_xtdb_reverseCashReceipt8": "Cannot reverse this Cash Receipt because the credit card records could not be found.",
+    "_xtdb_saveAlarm10": "An alarm for this item already exists.",
+    "_xtdb_selectForBilling1": "The quantity you have selected for Billing is less than the quantity shipped. You may not bill for less than the quantity shipped.",
+    "_xtdb_shipShipment5": "This Sales Order may not be shipped as it contains one or more Line Items that have Site/Product Category/Customer combinations that have not been properly described in Sales Account Assignments. These assignments must be made before G/L Transactions can be posted and this Sales Order is allowed to ship.",
+    "_xtdb_shipShipment6": "This Transfer Order may not be shipped because there is no Item Site for the Transit Site.",
+    "_xtdb_shipShipment8": "This Shipment cannot be shipped because it appears to have already shipped.",
+    "_xtdb_shipShipment12": "The selected Order is on Credit Hold and must be taken off of Credit Hold before it may be shipped.",
+    "_xtdb_shipShipment13": "The selected Order is on Packing Hold and must be taken off of Packing Hold before it may be shipped.",
+    "_xtdb_shipShipment14": "The selected Order is on Return Hold. The Customer  must return all materials for a related Return Authorization before this order may be shipped.",
+    "_xtdb_shipShipment15": "The selected Order is on Shipping Hold and must be taken off of Shipping Hold before it may be shipped.",
+    "_xtdb_shipShipment50": "This Shipment cannot be shipped because it does not appear to exist.",
+    "_xtdb_shipShipment99": "This Order may not be shipped because it has been marked as Ship Complete and quantities for one or more Line Items are still not completely issued. Please correct this before shipping the Order.",
+    "_xtdb_splitReceipt1": "Only Purchase Order Receipts may be split.",
+    "_xtdb_splitReceipt2": "Only posted receipts may be split.",
+    "_xtdb_splitReceipt3": "Vouchered receitps may not be split.",
+    "_xtdb_splitReceipt4": "Split quantity must me less than original receipt quantity.",
+    "_xtdb_splitReceipt5": "Split freight may not be greater than original freight.",
+    "_xtdb_splitReceipt6": "Receipt not found.",
+    "_xtdb_splitReceipt7": "The split quantity must be a positive number.",
+    "_xtdb_splitRecurrence11": "Cannot create recurring items without a valid parent item to copy.",
+    "_xtdb_sufficientInventoryToShipItem1": "Cannot figure out which line item to issue.",
+    "_xtdb_sufficientInventoryToShipItem2": "There is not enough Inventory to issue the amount required of Item %1 in Site %2.",
+    "_xtdb_sufficientInventoryToShipItem3": "Item Number %1 in Site %2 is a Multiple Location or Lot/Serial controlled Item which is short on Inventory. This transaction cannot be completed as is. Please make sure there is sufficient Quantity on Hand before proceeding.",
+    "_xtdb_sufficientInventoryToShipItem11": "Invalid Order Type. Only Sales Orders and Transfer Orders may be shipped from this window.",
+    "_xtdb_sufficientInventoryToShipOrder1": "Cannot check inventory levels for an invalid item.",
+    "_xtdb_sufficientInventoryToShipOrder2": "There is not enough Inventory to issue the amount required of one of the items requested.",
+    "_xtdb_sufficientInventoryToShipOrder3": "One of the requested items is a Multiple Location or Lot/Serial controlled Item which is sort on Inventory.",
+    "_xtdb_thawAccountingPeriod1": "Cannot thaw this Accounting Period because it is closed.",
+    "_xtdb_thawAccountingPeriod2": "Cannot thaw this Accounting Period because it is not frozen.",
+    "_xtdb_todoItemMove1": "Cannot change the Sequence of a non-existent To-Do List Item. Possible cause: no To-Do List Item was selected.",
+    "_xtdb_updateTodoItem1": "The To-Do List Item cannot be updated as there is no assigned User.",
+    "_xtdb_updateTodoItem2": "The To-Do List Item cannot be updated as the Task Name is blank.",
+    "_xtdb_updateTodoItem3": "The To-Do List Item cannot be updated as there is no Due Date.",
+    "_xtdb_updateTodoItem10": "The To-Do List Item cannot be updated as an invalid internal ID was supplied .",
+    "_xtdb_voidCheck1": "Cannot void this check because either it has already been voided, posted, or replaced, or it has been transmitted electronically. If this check has been posted, try Void Posted Check with the Check Register window.",
+    "_xtdb_voidCreditMemo10": "Unable to void this Credit Memo because it has not been posted.",
+    "_xtdb_voidCreditMemo11": "Unable to void this Credit Memo because the Sales Account was not found.",
+    "_xtdb_voidCreditMemo20": "Unable to void this Credit Memo because there A/R Applications posted against this Credit Memo.",
+    "_xtdb_voidInvoice10": "Unable to void this Invoice because it has not been posted.",
+    "_xtdb_voidInvoice11": "Unable to void this Invoice because the Sales Account was not found.",
+    "_xtdb_voidInvoice20": "Unable to void this Invoice because there A/R Applications posted against this Invoice.",
+    "_xtdb_voidPostedCheck10": "Cannot void this check because it has already been voided.",
+    "_xtdb_voidPostedCheck11": "Cannot void this check because the recipient type is not valid.",
+    "_xtdb_voidPostedCheck12": "Cannot void this check because the Expense Category could not be found.",
+    "_xtdb_voidPostedCheck13": "Cannot void this check because the G/L account to which the funds should be credited is not valid.",
+    "_xtdb_voidPostedCheck14": "Cannot void this check because the check has has been reconciled in Bank Reconciliation.",
+    "_xtdb_replaceVoidedCheck1": "Cannot replace a check that is not voided or has already been posted or replaced.",
+    "_xtdb_replaceVoidedCheck2": "Cannot replace this voided check because one of its line items has been reselected for billing and is represented on another check.",
+    "_xtdb_reserveSoLineQty1": "Cannot reserve more quantity than remaining on order.",
+    "_xtdb_reserveSoLineQty2": "Cannot reserve negative quantities.",
+    "_xtdb_reserveSoLineQty3": "Cannot reserve more quantity than currently on hand and already reserved.",
+    "_xtdb_woClockIn9": "Work Order %1 cannot be Exploded as it seems to have an invalid Order Quantity.",
+    "_xtdb_woClockIn10": "Work Order %1 has at least one Item in its Bill of Materials with the Push issue method that has not yet been issued. You must issue all Push Items to this Work Order.",
+    "_xtdb_woClockIn11": "Work Order %1 has at least one Item in its Bill of Materials with the Push issue method that does not have the required quantity issued. You must issue all Push Items to this Work Order.",
+    "_xtdb_woClockIn12": "Work Order %1 is closed.",
+  });
+
+  if (typeof exports !== 'undefined') {
+    exports.language = lang;
+  }
+}());
index 1a130d5..8553948 100644 (file)
     "xt/tables/pkgreport.sql",
     "xt/tables/pkgscript.sql",
     "xt/tables/pkguiform.sql",
-    "xt/tables/ext.sql",
-    "xt/tables/extdep.sql",
-    "xt/tables/grpext.sql",
     "xt/tables/ordtype.sql",
-    "xt/tables/usrext.sql",
     "xt/tables/userpref.sql",
     "xt/tables/usrchart.sql",
     "xt/tables/recover.sql",
     "xm/javascript/item.sql",
     "xm/javascript/item_site.sql",
     "xm/javascript/project.sql",
+    "xm/javascript/prospect.sql",
     "xm/javascript/quote.sql",
     "xm/javascript/sales.sql",
+    "xm/javascript/sales_order.sql",
     "xm/javascript/sales_rep.sql",
     "xm/javascript/system.sql",
     "xm/javascript/tax.sql",
index 248f121..8579726 100644 (file)
@@ -121,6 +121,16 @@ select xt.install_js('XM','Customer','xtuple', $$
     return result; 
   }
 
+  /**
+    Return whether a tax authority is referenced by another table.
+    
+    @param {String} Tax Authority Number
+  */
+  XM.Customer.used = function(id) {
+    var exceptions = ["public.crmacct"];
+    return XM.PrivateModel.used("XM.Customer", id, exceptions);
+  };
+
 }());
   
 $$ );
index 11deea6..ffb47d1 100644 (file)
@@ -2,7 +2,7 @@ select xt.install_js('XM','Project','xtuple', $$
   /* Copyright (c) 1999-2011 by OpenMFG LLC, d/b/a xTuple. 
      See www.xm.ple.com/CPAL for the full text of the software license. */
 
-  XM.Project = {};
+  if (!XM.Project) { XM.Project = {}; }
   
   XM.Project.isDispatchable = true;
   
diff --git a/enyo-client/database/source/xm/javascript/prospect.sql b/enyo-client/database/source/xm/javascript/prospect.sql
new file mode 100644 (file)
index 0000000..a31fee1
--- /dev/null
@@ -0,0 +1,24 @@
+select xt.install_js('XM','Prospect','xtuple', $$
+/* Copyright (c) 1999-2013 by OpenMFG LLC, d/b/a xTuple. 
+   See www.xtuple.com/CPAL for the full text of the software license. */
+
+(function () {
+
+  if (!XM.Prospect) { XM.Prospect = {}; }
+
+  XM.Prospect.isDispatchable = true;
+
+  /**
+    Return whether a Prospect is referenced by another table.
+    
+    @param {String} Prospect Number
+  */
+  XM.Prospect.used = function(id) {
+    var exceptions = ["public.crmacct"];
+    return XM.PrivateModel.used("XM.Prospect", id, exceptions);
+  };
+
+}());
+  
+$$ );
+
diff --git a/enyo-client/database/source/xm/javascript/sales_order.sql b/enyo-client/database/source/xm/javascript/sales_order.sql
new file mode 100644 (file)
index 0000000..b0fca0f
--- /dev/null
@@ -0,0 +1,24 @@
+select xt.install_js('XM','SalesOrder','xtuple', $$
+/* Copyright (c) 1999-2013 by OpenMFG LLC, d/b/a xTuple. 
+   See www.xtuple.com/CPAL for the full text of the software license. */
+
+(function () {
+
+  if (!XM.SalesOrder) { XM.SalesOrder = {}; }
+
+  XM.SalesOrder.isDispatchable = true;
+
+  /**
+    Return whether a Sales Order is referenced by another table.
+    
+    @param {String} SalesOrder Number
+  */
+  XM.SalesOrder.used = function(id) {
+    var exceptions = ["public.coitem"];
+    return XM.PrivateModel.used("XM.SalesOrder", id, exceptions);
+  };
+
+}());
+  
+$$ );
+
index db8f2c3..37297bf 100644 (file)
@@ -9,9 +9,9 @@ select xt.install_js('XM','SalesRep','xtuple', $$
   XM.SalesRep.isDispatchable = true;
 
   /**
-    Return whether a tax authority is referenced by another table.
+    Return whether a sales rep is referenced by another table.
     
-    @param {String} Tax Authority Number
+    @param {String} Sales Rep Number
   */
   XM.SalesRep.used = function(id) {
     var exceptions = ["public.crmacct"];
index 8ab7db5..7ccba78 100644 (file)
@@ -8,7 +8,7 @@ select coitem.*,
   xt.co_line_list_price(coitem) as list_price,
   xt.co_line_list_price_discount(coitem) as list_price_discount,
   xt.co_line_customer_discount(coitem) as cust_discount,
-  xt.co_line_extended_price(coitem) as ext_price, 
+  xt.co_line_extended_price(coitem) as ext_price,
   xt.co_line_ship_balance(coitem) as ship_balance,
   xt.co_line_at_shipping(coitem) as at_shipping,
   xt.co_line_margin(coitem) as margin,
@@ -94,10 +94,10 @@ insert into coitem (
   new.coitem_cos_accnt_id,
   coalesce(new.coitem_qtyreserved, 0),
   new.coitem_subnumber,
-  new.coitem_firm,
+  coalesce(new.coitem_firm, false),
   new.coitem_rev_accnt_id,
   coalesce(new.coitem_pricemode, 'D'),
-  new.obj_uuid
+  coalesce(new.obj_uuid, xt.generate_uuid())
 from itemsite
 where itemsite_item_id=new.coitem_item_id
   and itemsite_warehous_id=new.coitem_warehous_id;
index 5e2264c..faaa2b6 100644 (file)
@@ -10,10 +10,10 @@ select xt.create_view('xt.customer_prospect', $$
     join crmacct on crmacct_cust_id=cust_id   
   union   
   select prospect_id as id, prospect_active as active, prospect_number as number,   
-    prospect_name as name, null as type, prospect_cntct_id as contact, null as cust_ffshipto,   
-    null as cust_ffbillto, basecurrid() as cust_curr_id, null as cust_terms_id,   
-    null as cust_creditstatus, prospect_salesrep_id as salesrep_id, null as cust_commprcnt,   
-    null as cust_discntprcnt, null as taxzone_id, null as cust_shipchrg_id,   
+    prospect_name as name, null as type, prospect_cntct_id as contact, true as cust_ffshipto,   
+    true as cust_ffbillto, basecurrid() as cust_curr_id, null as cust_terms_id,   
+    null as cust_creditstatus, prospect_salesrep_id as salesrep_id, 0 as cust_commprcnt,   
+    null as cust_discntprcnt, prospect_taxzone_id as taxzone_id, null as cust_shipchrg_id,   
     prospect_comments as comments, prospect_warehous_id as site, 
     null as cust_shipvia, 'P' as status, crmacct_id   
   from prospect   
diff --git a/enyo-client/extensions/sample_debug.js b/enyo-client/extensions/sample_debug.js
deleted file mode 100644 (file)
index bb6e406..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-// Rename this file 'debug.js' and add whichever extensions
-// you would like to always be available when debugging the
-// application
-enyo.depends(
-  '/dev/core-extensions/source/project/client/package.js',
-  '/dev/core-extensions/source/crm/client/package.js',
-  '/dev/core-extensions/source/sales/client/package.js'
-);
diff --git a/enyo-client/extensions/source/crm/client/en/package.js b/enyo-client/extensions/source/crm/client/en/package.js
deleted file mode 100644 (file)
index 66ccc0b..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-enyo.depends(
-  "strings.js"
-);
index 592ac58..25d2a4c 100644 (file)
@@ -1,29 +1,23 @@
-// ==========================================================================
-// Project:   XT` Strings
-// Copyright: Â©2011 OpenMFG LLC, d/b/a xTuple
-// ==========================================================================
-/*globals XT */
+/*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 XT:true */
 
-// Place strings you want to localize here.  In your app, use the key and
-// localize it using "key string".loc().  HINT: For your key names, use the
-// english string with an underscore in front.  This way you can still see
-// how your UI will look and you'll notice right away when something needs a
-// localized string added to this file!
-//
+(function () {
+  "use strict";
 
-var lang = XT.stringsFor("en_US", {
+  var lang = XT.stringsFor("en_US", {
+    "_crm": "CRM",
+    "_crmDescription": "Corporate Relationship Management",
+    "_highPriority": "High Priority",
+    "_incidentDefaultPublic": "Comment Default Public",
+    "_incidentStatusColors": "Incident Status Colors",
+    "_openIncidents": "Open Incidents",
+    "_maintainEmailProfiles": "Maintain Email Profiles",
+    "_viewEmailProfiles": "View Email Profiles"
+  });
 
-  // ********
-  // Labels
-  // ********
-
-  "_crm": "CRM",
-  "_crmDescription": "Corporate Relationship Management",
-  "_highPriority": "High Priority",
-  "_incidentDefaultPublic": "Comment Default Public",
-  "_incidentStatusColors": "Incident Status Colors",
-  "_openIncidents": "Open Incidents",
-  "_maintainEmailProfiles": "Maintain Email Profiles",
-  "_viewEmailProfiles": "View Email Profiles"
-
-});
+  if (typeof exports !== 'undefined') {
+    exports.language = lang;
+  }
+}());
index ef79d2e..de0912e 100644 (file)
     "context": "crm",
     "nameSpace": "XM",
     "type": "ToDo",
-    "table": "todoitem",
+    "table": "xt.todoiteminfo",
     "isExtension": true,
     "comment": "Extended by Crm",
     "relations": [
     "context": "crm",
     "nameSpace": "XM",
     "type": "ToDoListItem",
-    "table": "todoitem",
+    "table": "xt.todoiteminfo",
     "isExtension": true,
     "comment": "Extended by Crm",
     "relations": [
diff --git a/enyo-client/extensions/source/inventory/client/en/package.js b/enyo-client/extensions/source/inventory/client/en/package.js
deleted file mode 100644 (file)
index 66ccc0b..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-enyo.depends(
-  "strings.js"
-);
index 486e2ef..a9fabf3 100644 (file)
-// ==========================================================================
-// Project:   XT` Strings
-// Copyright: Â©2011 OpenMFG LLC, d/b/a xTuple
-// ==========================================================================
-/*globals XT */
+/*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 XT:true */
 
-// Place strings you want to localize here.  In your app, use the key and
-// localize it using "key string".loc().  HINT: For your key names, use the
-// english string with an underscore in front.  This way you can still see
-// how your UI will look and you'll notice right away when something needs a
-// localized string added to this file!
-//
+(function () {
+  "use strict";
 
-var lang = XT.stringsFor("en_US", {
-  "_a": "A",
-  "_abcClass": "ABC Class",
-  "_aisle": "Aisle",
-  "_adjustment": "Adjustment",
-  "_allowableItems": "Allowable Items",
-  "_allowAvgCostMethod": "Average",
-  "_allowDups": "Allow Dups.",
-  "_allowJobCostMethod": "Job",
-  "_allowReceiptCostOverride": "Allow Inventory Receipt Cost Override (Avg. costing method only)",
-  "_allowStdCostMethod": "Standard",
-  "_atShipping": "At Shipping",
-  "_average": "Average",
-  "_b": "B",
-  "_backlog": "Backlog",
-  "_billto": "Bill To",
-  "_bin": "Bin",
-  "_c": "C",
-  "_controlMethod": "Control Method",
-  "_costing": "Costing",
-  "_countAvgCostMethod": "Cost to Use When Posting Count Tag for Avg. Cost Items",
-  "_countSlipAuditing": "Count Slip # Auditing",
-  "_cycleCount": "Cycle Count",
-  "_cycleCountFrequency": "Cycle Count Frequency",
-  "_defaultEventFence": "Default Event Fence (Days)",
-  "_disableReceiptExcessQty": "Disallow PO Receipt of Qty greater than ordered",
-  "_distributionDetail": "Distribution Detail",
-  "_dontPost": "Do Not Post",
-  "_enableAsOfQOH": "Enable As-Of QOH Reporting",
-  "_enterReceipt": "Enter Receipt",
-  "_inventory": "Inventory",
-  "_inventoryHistory": "Inventory History",
-       "_inventoryDescription": "Shipping and Receiving, Inventory and Cost Management",
-  "_isAutomaticAbcClassUpdates": "Auto ABC Updates",
-  "_isLocationControl": "Location Control",
-  "_isNetable": "Netable",
-  "_isReceiveLocationAuto": "Auto Receive",
-  "_isRestricted": "Restricted",
-  "_isShipped": "Shipped",
-  "_isStocked": "Stocked",
-  "_isStockLocationAuto": "Auto Issue",
-  "_isIssueLocationAuto": "Auto",
-  "_issue": "Issue",
-  "_issueBreeder": "Issue Breeder",
-  "_issueLocation": "Issue Location",
-  "_issueAll": "Issue All",
-  "_issueAll?": "Are you sure you want to issue stock for all these items?",
-  "_issueExcess": "You are attempting to issue more material than is required. Are you sure you want to do this?",
-  "_issueLine": "Issue Line",
-  "_issueMaterial": "Issue Material",
-  "_issueStock": "Issue Stock",
-  "_issueToShipping": "Issue to Shipping",
-  "_isUseDefaultLocation": "Use Default Location",
-  "_job": "Job",
-  "_kitComponentInheritCOS": "Kit Components Inherit COS from Kit Parent",
-  "_leadTime": "Lead Time",
-  "_location": "Location",
-  "_locationControl": "Location Control",
-  "_locations": "Locations",
-  "_locationComment": "Comment",
-  "_materialReceipt": "Material Receipt",
-  "_maximumOrderQuantity": "Order Max.",
-  "_minimumOrderQuantity": "Order Min.",
-  "_multipleLocationControl": "Multiple Location Control",
-  "_multipleOrderQuantity": "Order Multiple",
-  "_none": "None",
-  "_noShipVia": "No ShipVia",
-  "_noUnpostedSlipDupsSite": "No Unposted Dups. in a Site",
-  "_noUnpostedSlipDups": "No Unposted Dups.",
-  "_noSlipDupsSite": "No Dups. in a Site",
-  "_nextShipmentNumber": "Next Shipment Number",
-  "_noSlipDups": "No Slip # Duplications",
-  "_number": "Number",
-  "_orderToQuantity": "Order To",
-  "_postSiteChanges": "Post Site Changes",
-  "_postItemSiteChanges": "Post Item Site Changes",
-  "_postCountTagToDefault": "When Count Tag Qty exceeds Slip Qty",
-  "_postToDefaultLocation": "Post to Default Loc.",
-  "_physicalInventory": "Physical Inventory (Counting)",
-  "_printPacklist": "Print Packlist",
-  "_processing": "Processing",
-  "_purchaseOrder": "Purchase Order",
-  "_purchaseOrders": "Purchase Orders",
-  "_rack": "Rack",
-  "_recallShipment": "Recall Shipment",
-  "_recallShipment?": "Are you sure you want to recall this shipment?",
-  "_receiptQtyTolerancePct": "By the Following Amount (%)",
-  "_receive": "Receive",
-  "_receiveLocation": "Receive",
-  "_receiveAll": "Receive All",
-  "_receiveAll?": "Are you sure you want to enter receipt for all these items?",
-  "_received": "Received",
-  "_receiveBreeder": "Receive Breeder",
-  "_receivePurchaseOrder": "Receive Purchase Order",
-  "_receiveMaterial": "Receive Material",
-  "_receiveReturn": "Receive Return",
-  "_recordPPVOnReceipt": "Record Purchase Price Variance on Receipt",
-  "_relocate": "Relocate",
-  "_reorderLevel": "Reorder Level",
-  "_reporting": "Reporting",
-  "_restrictedLocations": "Restricted Locations",
-  "_restrictedLocationsAllowed": "Restricted Locations Allowed",
-  "_returned": "Returned",
-  "_returnFromShipping": "Return From Shipping",
-  "_returnLine": "Return Line",
-  "_safetyStock": "Safety Stock",
-  "_ship": "Ship",
-  "_shipped": "Shipped",
-  "_shipping": "Shipping",
-  "_shippingAndReceiving": "Shipping and Receiving",
-       "_shipmentNumberPolicy": "Shipment Number Policy",
-  "_shipto": "Ship To",
-  "_siteTransfer": "Site Transfer",
-  "_siteZone": "Site Zone",
-  "_showInvoiced": "Show Invoiced Shipments",
-  "_showShipped": "Show Shipped",
-  "_shipment": "Shipment",
-  "_shipShipment": "Ship Shipment",
-  "_shipments": "Shipments",
-  "_standard": "Standard",
-  "_stockLocation": "Stock",
-  "_toReceive": "To Receive",
-  "_toIssue": "To Issue",
-  "_trackingNumber": "Tracking Number",
-  "_transferOrder": "Transfer Order",
-  "_unReleased": "Unreleased",
-  "_useDefaultLocation": "Use Default",
-  "_useParameters": "Use Parameters",
-  "_useParametersManual": "Enforce on Manual Orders",
-  "_userDefinedLocation": "Location Name",
-  "_useStandardCost": "Standard Costs",
-  "_useAverageCost": "Average Costs",
-  "_vendor": "Vendor",
-  "_vendors": "Vendors",
-  "_vendorType": "Vendor Type",
-  "_warnIfReceiptQtyDiffers": "Warn if PO Receipt Qty differs from receivable Qty",
+  var lang = XT.stringsFor("en_US", {
+    "_a": "A",
+    "_abcClass": "ABC Class",
+    "_aisle": "Aisle",
+    "_adjustment": "Adjustment",
+    "_allowableItems": "Allowable Items",
+    "_allowAvgCostMethod": "Average",
+    "_allowDups": "Allow Dups.",
+    "_allowJobCostMethod": "Job",
+    "_allowReceiptCostOverride": "Allow Inventory Receipt Cost Override (Avg. costing method only)",
+    "_allowStdCostMethod": "Standard",
+    "_atShipping": "At Shipping",
+    "_average": "Average",
+    "_b": "B",
+    "_backlog": "Backlog",
+    "_billto": "Bill To",
+    "_bin": "Bin",
+    "_c": "C",
+    "_controlMethod": "Control Method",
+    "_costing": "Costing",
+    "_countAvgCostMethod": "Cost to Use When Posting Count Tag for Avg. Cost Items",
+    "_countSlipAuditing": "Count Slip # Auditing",
+    "_cycleCount": "Cycle Count",
+    "_cycleCountFrequency": "Cycle Count Frequency",
+    "_defaultEventFence": "Default Event Fence (Days)",
+    "_disableReceiptExcessQty": "Disallow PO Receipt of Qty greater than ordered",
+    "_distributionDetail": "Distribution Detail",
+    "_dontPost": "Do Not Post",
+    "_enableAsOfQOH": "Enable As-Of QOH Reporting",
+    "_enterReceipt": "Enter Receipt",
+    "_inventory": "Inventory",
+    "_inventoryHistory": "Inventory History",
+    "_inventoryDescription": "Shipping and Receiving, Inventory and Cost Management",
+    "_isAutomaticAbcClassUpdates": "Auto ABC Updates",
+    "_isLocationControl": "Location Control",
+    "_isNetable": "Netable",
+    "_isReceiveLocationAuto": "Auto Receive",
+    "_isRestricted": "Restricted",
+    "_isShipped": "Shipped",
+    "_isStocked": "Stocked",
+    "_isStockLocationAuto": "Auto Issue",
+    "_isIssueLocationAuto": "Auto",
+    "_issue": "Issue",
+    "_issueBreeder": "Issue Breeder",
+    "_issueLocation": "Issue Location",
+    "_issueAll": "Issue All",
+    "_issueAll?": "Are you sure you want to issue stock for all these items?",
+    "_issueExcess": "You are attempting to issue more material than is required. Are you sure you want to do this?",
+    "_issueLine": "Issue Line",
+    "_issueMaterial": "Issue Material",
+    "_issueStock": "Issue Stock",
+    "_issueToShipping": "Issue to Shipping",
+    "_isUseDefaultLocation": "Use Default Location",
+    "_job": "Job",
+    "_kitComponentInheritCOS": "Kit Components Inherit COS from Kit Parent",
+    "_leadTime": "Lead Time",
+    "_location": "Location",
+    "_locationControl": "Location Control",
+    "_locations": "Locations",
+    "_locationComment": "Comment",
+    "_materialReceipt": "Material Receipt",
+    "_maximumOrderQuantity": "Order Max.",
+    "_minimumOrderQuantity": "Order Min.",
+    "_multipleLocationControl": "Multiple Location Control",
+    "_multipleOrderQuantity": "Order Multiple",
+    "_none": "None",
+    "_noShipVia": "No ShipVia",
+    "_noUnpostedSlipDupsSite": "No Unposted Dups. in a Site",
+    "_noUnpostedSlipDups": "No Unposted Dups.",
+    "_noSlipDupsSite": "No Dups. in a Site",
+    "_nextShipmentNumber": "Next Shipment Number",
+    "_noSlipDups": "No Slip # Duplications",
+    "_number": "Number",
+    "_orderToQuantity": "Order To",
+    "_postSiteChanges": "Post Site Changes",
+    "_postItemSiteChanges": "Post Item Site Changes",
+    "_postCountTagToDefault": "When Count Tag Qty exceeds Slip Qty",
+    "_postToDefaultLocation": "Post to Default Loc.",
+    "_physicalInventory": "Physical Inventory (Counting)",
+    "_printPacklist": "Print Packlist",
+    "_purchaseOrder": "Purchase Order",
+    "_purchaseOrders": "Purchase Orders",
+    "_rack": "Rack",
+    "_recallShipment": "Recall Shipment",
+    "_recallShipment?": "Are you sure you want to recall this shipment?",
+    "_receiptQtyTolerancePct": "By the Following Amount (%)",
+    "_receive": "Receive",
+    "_receiveLocation": "Receive",
+    "_receiveAll": "Receive All",
+    "_receiveAll?": "Are you sure you want to enter receipt for all these items?",
+    "_received": "Received",
+    "_receiveBreeder": "Receive Breeder",
+    "_receivePurchaseOrder": "Receive Purchase Order",
+    "_receiveMaterial": "Receive Material",
+    "_receiveReturn": "Receive Return",
+    "_recordPPVOnReceipt": "Record Purchase Price Variance on Receipt",
+    "_relocate": "Relocate",
+    "_reorderLevel": "Reorder Level",
+    "_reporting": "Reporting",
+    "_restrictedLocations": "Restricted Locations",
+    "_restrictedLocationsAllowed": "Restricted Locations Allowed",
+    "_returned": "Returned",
+    "_returnFromShipping": "Return From Shipping",
+    "_returnLine": "Return Line",
+    "_safetyStock": "Safety Stock",
+    "_ship": "Ship",
+    "_shipped": "Shipped",
+    "_shipping": "Shipping",
+    "_shippingAndReceiving": "Shipping and Receiving",
+    "_shipmentNumberPolicy": "Shipment Number Policy",
+    "_shipto": "Ship To",
+    "_siteTransfer": "Site Transfer",
+    "_siteZone": "Site Zone",
+    "_showInvoiced": "Show Invoiced Shipments",
+    "_showShipped": "Show Shipped",
+    "_shipment": "Shipment",
+    "_shipShipment": "Ship Shipment",
+    "_shipments": "Shipments",
+    "_standard": "Standard",
+    "_stockLocation": "Stock",
+    "_toReceive": "To Receive",
+    "_toIssue": "To Issue",
+    "_trackingNumber": "Tracking Number",
+    "_transferOrder": "Transfer Order",
+    "_unReleased": "Unreleased",
+    "_useDefaultLocation": "Use Default",
+    "_useParameters": "Use Parameters",
+    "_useParametersManual": "Enforce on Manual Orders",
+    "_userDefinedLocation": "Location Name",
+    "_useStandardCost": "Standard Costs",
+    "_useAverageCost": "Average Costs",
+    "_vendor": "Vendor",
+    "_vendors": "Vendors",
+    "_vendorType": "Vendor Type",
+    "_warnIfReceiptQtyDiffers": "Warn if PO Receipt Qty differs from receivable Qty"
+  });
 
-  // Error Messages
-  "_improperItemSite": "Item is of type reference or control method is none.",
-  "_noSalesAssignments": "Sales assignment mappings are invalid.",
-  "_orderOnCreditHold": "Order is on credit hold.",
-  "_orderOnPackingHold": "Order is on packing hold.",
-  "_orderOnShippingHold": "Order is on shipping hold.",
-  "_shipmentNotFound": "Shipment not found.",
-  "_incompleteShipment": "Incomplete Shipment",
-  "_alreadyShipped": "Shipment already shipped."
-});
+  if (typeof exports !== 'undefined') {
+    exports.language = lang;
+  }
+}());
diff --git a/enyo-client/extensions/source/inventory/client/error.js b/enyo-client/extensions/source/inventory/client/error.js
deleted file mode 100644 (file)
index cf6be37..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-// Contributions of status related functionality borrowed from SproutCore:
-// https://github.com/sproutcore/sproutcore
-
-/*jshint trailing:true, white:true, indent:2, strict:true, curly:true, plusplus:true,
-  immed:true, eqeqeq:true, forin:true, latedef:true, newcap:true, noarg:true, undef:true */
-/*global XT:true, _:true */
-
-(function () {
-  "use strict";
-
-  var errors = [
-    {
-      code: "xtinv1001",
-      messageKey: "_improperItemSite"
-    }, {
-      code: "xtinv1002",
-      messageKey: "_noSalesAssignments"
-    }, {
-      code: "xtinv1003",
-      messageKey: "_orderOnCreditHold"
-    }, {
-      code: "xtinv1004",
-      messageKey: "_orderOnPackingHold"
-    }, {
-      code: "xtinv1005",
-      messageKey: "_orderOnShippingHold"
-    }, {
-      code: "xtinv1006",
-      messageKey: "_shipmentNotFound"
-    }, {
-      code: "xtinv1007",
-      messageKey: "_incompleteShipment"
-    }, {
-      code: "xtinv1008",
-      messageKey: "_alreadyShipped"
-    }
-  ];
-
-  _.each(errors, XT.Error.addError);
-
-}());
index 8042abf..4d9017e 100644 (file)
@@ -88,27 +88,6 @@ white:true*/
         return this;
       },
 
-      doReturnStock: function (callback) {
-        var that = this,
-          options = {};
-
-        // Refresh once we've completed the work
-        options.success = function () {
-          that.fetch({
-            success: function () {
-              if (callback) {
-                callback();
-              }
-            }
-          });
-        };
-
-        this.setStatus(XM.Model.BUSY_COMMITTING);
-        this.dispatch("XM.Inventory", "returnFromShipping", [this.id], options);
-
-        return this;
-      },
-
       /**
         Calculate the balance remaining to issue.
 
@@ -122,67 +101,37 @@ white:true*/
       },
 
       /**
-        Overload: Calls `issueToShipping` dispatch function.
+        Unlike most validations on models, this one accepts a callback
+        into which will be forwarded a boolean response. Errors will
+        trigger `invalid`.
 
+        @param {Function} Callback
         @returns {Object} Receiver
         */
-      save: function (key, value, options) {
-        options = options ? _.clone(options) : {};
-
-        // Handle both `"key", value` and `{key: value}` -style arguments.
-        if (_.isEmpty(key)) {
-          options = value ? _.clone(value) : {};
-        }
-
+      validate: function (callback) {
         var toIssue = this.get("toIssue"),
-          that = this,
-          callback,
           err;
-        
-        // Callback for after we determine quantity validity
-        callback = function (resp) {
-          if (!resp.answer) { return; }
-            
-          var dispOptions = {},
-            issOptions = {
-              asOf: that.transactionDate
-            },
-            detail = that.formatDetail(),
-            params = [
-              that.id,
-              that.get("toIssue"),
-              issOptions
-            ];
-
-          // Refresh once we've completed the work
-          dispOptions.success = function () {
-            that.fetch(options);
-          };
-
-          // Add distribution detail if applicable
-          if (detail.length) {
-            issOptions.detail = detail;
-          }
-          that.setStatus(XM.Model.BUSY_COMMITTING);
-          that.dispatch("XM.Inventory", that.issueMethod, params, dispOptions);
-        };
 
         // Validate
         if (this.undistributed()) {
           err = XT.Error.clone("xt2017");
         } else if (toIssue <= 0) {
           err = XT.Error.clone("xt2013");
-        } else if (!this.issueBalance() && toIssue > 0) {
+        } else if (toIssue > this.issueBalance()) {
           this.notify("_issueExcess".loc(), {
             type: XM.Model.QUESTION,
-            callback: callback
+            callback: function (resp) {
+              callback(resp.answer);
+            }
           });
+          return this;
         }
 
         if (err) {
-          this.trigger("invalid", this, err, options || {});
+          this.trigger("invalid", this, err, {});
+          callback(false);
         } else {
-          callback({answer: true});
+          callback(true);
         }
 
         return this;
index d21c323..955c193 100644 (file)
@@ -79,26 +79,10 @@ white:true*/
               shipDate
             ];
           shipOptions.success = function (shipResp) {
-            var map,
-              err;
-            // Check for silent errors
-            if (shipResp < 0) {
-              map = {
-                "-1": "xtinv1001",
-                "-5": "xtinv1002",
-                "-8": "xtinv1008",
-                "-12": "xtinv1003",
-                "-13": "xtinv1004",
-                "-15": "xtinv1005",
-                "-50": "xtinv1006",
-                "-99": "xtinv1007"
-              };
-              resp = resp + "";
-              err = XT.Error.clone(map[resp] ? map[resp] : "xt1001");
-              that.trigger("invalid", that, err, options || {});
-            } else {
-              if (success) { success(model, resp, options); }
-            }
+            if (success) { success(model, resp, options); }
+          };
+          shipOptions.error = function () {
+            // The datasource takes care of reporting the error to the user
           };
           that.dispatch("XM.Inventory", "shipShipment", params, shipOptions);
           return this;
index ff834d8..c37fa98 100644 (file)
@@ -1,8 +1,7 @@
 enyo.depends(
-  "en",
   "core.js",
   "models",
   "widgets",
   "views",
   "postbooks.js"
-);
\ No newline at end of file
+);
index dba441c..7f1f7d3 100644 (file)
@@ -82,7 +82,7 @@ trailing:true, white:true, strict:false*/
       ],
       formatPrice: function (value, view, model) {
         var currency = model ? model.getValue("salesOrder.currency") : false,
-          scale = XT.session.locale.attributes.salesPriceScale;
+          scale = XT.locale.salesPriceScale;
         return currency ? currency.format(value, scale) : "";
       },
       formatLineNumber: function (value, view, model) {
@@ -155,7 +155,7 @@ trailing:true, white:true, strict:false*/
         return value;
       },
       formatQuantity: function (value) {
-        var scale = XT.session.locale.attributes.quantityScale;
+        var scale = XT.locale.quantityScale;
         return Globalize.format(value, "n" + scale);
       },
       enterReceipt: function (inEvent) {
@@ -255,7 +255,7 @@ trailing:true, white:true, strict:false*/
       },
       formatMoney: function (value, view) {
         view.addRemoveClass("error", value < 0);
-        var scale = XT.session.locale.attributes.currencyScale;
+        var scale = XT.locale.currencyScale;
         return Globalize.format(value, "c" + scale);
       },
       formatOrderType: function (value) {
@@ -283,7 +283,7 @@ trailing:true, white:true, strict:false*/
       },
       formatQuantity: function (value, view) {
         view.addRemoveClass("error", value < 0);
-        var scale = XT.session.locale.attributes.quantityScale;
+        var scale = XT.locale.quantityScale;
         return Globalize.format(value, "n" + scale);
       },
       formatTransactionType: function (value) {
@@ -347,13 +347,14 @@ trailing:true, white:true, strict:false*/
         {name: "issueLine", prerequisite: "canIssueStock",
           method: "issueLine", notify: false, isViewMethod: true},
         {name: "returnLine", prerequisite: "canReturnStock",
-          method: "doReturnStock", notify: false}
+          method: "returnStock", notify: false, isViewMethod: true}
       ],
       toggleSelected: true,
       published: {
         shipment: null
       },
       events: {
+        onProcessingChanged: "",
         onShipmentChanged: ""
       },
       components: [
@@ -410,43 +411,160 @@ trailing:true, white:true, strict:false*/
         return value;
       },
       formatQuantity: function (value) {
-        var scale = XT.session.locale.attributes.quantityScale;
+        var scale = XT.locale.quantityScale;
         return Globalize.format(value, "n" + scale);
       },
-      issueLine: function (inEvent) {
-        var model = inEvent.model,
-          index = inEvent.index,
-          that = this,
-          options = {
-            success: function () {
-              that.resetActions(index);
-              that.renderRow(index);
+      /**
+        Helper function for transacting `issue` on an array of models
+
+        @param {Array} Models
+        @param {Boolean} Prompt user for confirmation on every model
+      */
+      issue: function (models, prompt) {
+        var that = this,
+          i = -1,
+          callback,
+          data = [];
+
+        // Recursively issue everything we can
+        callback = function (workspace) {
+          var model,
+            options = {},
+            toIssue,
+            transDate,
+            params,
+            dispOptions = {},
+            wsOptions = {},
+            wsParams;
+
+          // If argument is false, this whole process was cancelled
+          if (workspace === false) {
+            return;
+
+          // If a workspace brought us here, process the information it obtained
+          } else if (workspace) {
+            model = workspace.getValue();
+            toIssue = model.get("toIssue");
+            transDate = model.transactionDate;
+
+            if (toIssue) {
+              wsOptions.detail = model.formatDetail();
+              wsOptions.asOf = transDate;
+              wsParams = {
+                orderLine: model.id,
+                quantity: toIssue,
+                options: wsOptions
+              };
+              data.push(wsParams);
             }
+            workspace.doPrevious();
+          }
+
+          i++;
+          // If we've worked through all the models then forward to the server
+          if (i === models.length) {
+            that.doProcessingChanged({isProcessing: true});
+            dispOptions.success = function () {
+              that.doProcessingChanged({isProcessing: false});
+            };
+            XM.Inventory.issueToShipping(data, dispOptions);
+
+          // Else if there's something here we can issue, handle it
+          } else {
+            model = models[i];
+            toIssue = model.get("toIssue");
+            transDate = model.transactionDate;
+
+            // See if there's anything to issue here
+            if (toIssue) {
+
+              // If prompt or distribution detail required,
+              // open a workspace to handle it
+              if (prompt || model.undistributed()) {
+                that.doWorkspace({
+                  workspace: "XV.IssueStockWorkspace",
+                  id: model.id,
+                  callback: callback,
+                  allowNew: false,
+                  success: function (model) {
+                    model.transactionDate = transDate;
+                  }
+                });
+
+              // Otherwise just use the data we have
+              } else {
+                options.asOf = transDate;
+                options.detail = model.formatDetail();
+                params = {
+                  orderLine: model.id,
+                  quantity: toIssue,
+                  options: options
+                };
+                data.push(params);
+                callback();
+              }
+
+            // Nothing to issue, move on
+            } else {
+              callback();
+            }
+          }
+        };
+        callback();
+      },
+      issueAll: function () {
+        var models = this.getValue().models;
+        this.issue(models);
+      },
+      issueLine: function () {
+        var models = this.selectedModels();
+        this.issue(models);
+      },
+      issueStock: function () {
+        var models = this.selectedModels();
+        this.issue(models, true);
+      },
+      returnStock: function () {
+        var models = this.selectedModels(),
+          that = this,
+          data =  [],
+          options = {},
+          atShipping,
+          model,
+          i;
+
+        for (i = 0; i < models.length; i++) {
+          model = models[i];
+          atShipping = model.get("atShipping");
+
+          // See if there's anything to issue here
+          if (atShipping) {
+            data.push(model.id);
+          }
+        }
+
+        if (data.length) {
+          that.doProcessingChanged({isProcessing: true});
+          options.success = function () {
+            that.doProcessingChanged({isProcessing: false});
           };
-        // Model sets toIssue value on load and attempts to
-        // distribute detail to default if applicable. If
-        // still undistributed detail, we'll have to prompt
-        // user. Otherwise just save the model with the
-        // precalculated values.
-        if (model.undistributed()) {
-          this.issueStock(inEvent);
-        } else {
-          model.save(null, options);
+          XM.Inventory.returnFromShipping(data, options);
         }
       },
-      issueStock: function (inEvent) {
-        var model = inEvent.model,
-         transDate = model.transactionDate;
-
-        this.doWorkspace({
-          workspace: "XV.IssueStockWorkspace",
-          id: model.id,
-          allowNew: false,
-          success: function (model) {
-            // Set the transaction date to match the source
-            model.transactionDate = transDate;
+      selectedModels: function () {
+        var collection = this.getValue(),
+          models = [],
+          selected,
+          prop;
+        if (collection.length) {
+          selected = this.getSelection().selected;
+          for (prop in selected) {
+            if (selected.hasOwnProperty(prop)) {
+              models.push(this.getModel(prop - 0));
+            }
           }
-        });
+        }
+        return models;
       },
       /**
         Overload: used to keep track of shipment.
index 59a7c83..c8c3fa0 100644 (file)
@@ -73,7 +73,7 @@ trailing:true, white:true*/
         if (value) { return value.format(); }
       },
       formatQuantity: function (value) {
-        var scale = XT.session.locale.attributes.quantityScale;
+        var scale = XT.locale.quantityScale;
         return Globalize.format(value, "n" + scale);
       },
       rowChanged: function (model) {
@@ -137,7 +137,7 @@ trailing:true, white:true*/
         ]}
       ],
       formatQuantity: function (value) {
-        var scale = XT.session.locale.attributes.quantityScale;
+        var scale = XT.locale.quantityScale;
         return Globalize.format(value, "n" + scale);
       }
     });
index 4583593..be313d6 100644 (file)
 /*jshint bitwise:true, indent:2, curly:true, eqeqeq:true, immed:true,
 latedef:true, newcap:true, noarg:true, regexp:true, undef:true,
 trailing:true, white:true, strict:false*/
-/*global XT:true, XM:true, XV:true, _:true, enyo:true */
+/*global XT:true, XM:true, _:true, enyo:true */
 
 (function () {
 
   XT.extensions.inventory.initTransactionList = function () {
 
-    /**
-      Expected to a have a parameter widget that contains an order and
-      a transaction date.
-
-      @name XV.TransactionList
-      @extends XV.SearchContainer
-     */
-    var transactionList =  /** @lends XV.TransactionList# */ {
-      name: "XV.TransactionList",
-      kind: "Panels",
-      classes: "app enyo-unselectable",
-      arrangerKind: "CollapsingArranger",
-      published: {
-        prerequisite: "",
-        notifyMessage: "",
-        list: null,
-        actions: null,
-        transactionDate: null,
-        model: null
-      },
-      events: {
-        onPrevious: "",
-        onWorkspace: ""
-      },
-      handlers: {
-        onListItemMenuTap: "showListItemMenu",
-        onParameterChange: "requery",
-        onSelectionChanged: "selectionChanged"
-      },
-      init: false,
-      components: [
-        {name: "parameterPanel", kind: "FittableRows", classes: "left",
-          components: [
-          {kind: "onyx.Toolbar", classes: "onyx-menu-toolbar", components: [
-            {kind: "onyx.Button", name: "backButton", content: "_back".loc(), ontap: "close"},
-            {kind: "onyx.MenuDecorator", style: "margin: 0;",
-              onSelect: "actionSelected", components: [
-              {kind: "XV.IconButton", src: "/assets/menu-icon-gear.png",
-                content: "_actions".loc(), name: "actionButton"},
-              {kind: "onyx.Menu", name: "actionMenu"}
-            ]}
-          ]},
-          {kind: "Scroller", name: "parameterScroller"}
-        ]},
-        {name: "listPanel", kind: "FittableRows", components: [
-          // the onyx-menu-toolbar class keeps the popups from being hidden
-          {kind: "onyx.MoreToolbar", name: "contentToolbar",
-            classes: "onyx-menu-toolbar", movedClass: "xv-toolbar-moved", components: [
-            {kind: "onyx.Grabber", classes: "left-float"},
-            {name: "rightLabel", content: "_search".loc(), classes: "left-float"},
-            {name: "space", fit: true},
-            {kind: "onyx.Button", name: "printButton", showing: false,
-              content: "_print".loc(), onclick: "print"},
-            {kind: "onyx.Button", name: "refreshButton", disabled: false,
-              content: "_refresh".loc(), onclick: "requery"},
-            {kind: "onyx.Button", name: "postButton",
-              disabled: true, classes: "save", showing: false,
-              content: "_post".loc(), onclick: "post"},
-            {name: "listItemMenu", kind: "onyx.Menu", floating: true,
-              onSelect: "listActionSelected", maxHeight: 500}
-          ]},
-          {name: "contentPanels", kind: "Panels", margin: 0, fit: true, draggable: false,
-            panelCount: 0},
-          {kind: "onyx.Popup", name: "spinnerPopup", centered: true,
-              modal: true, floating: true, scrim: true,
-              onHide: "popupHidden", components: [
-            {kind: "onyx.Spinner"},
-            {name: "spinnerMessage", content: "_processing".loc() + "..."}
-          ]}
-        ]}
-      ],
-      actionSelected: function (inSender, inEvent) {
-        var action = inEvent.originator.action,
-          method = action.method || action.name;
-
-        this[method](inSender, inEvent);
-      },
-      close: function () {
-        this.doPrevious();
-      },
-      buildMenu: function () {
-        var actionMenu = this.$.actionMenu,
-          actions = this.getActions().slice(0),
-          that = this;
-
-        // reset the menu
-        actionMenu.destroyClientControls();
-
-        // then add whatever actions are applicable
-        _.each(actions, function (action) {
-          var name = action.name,
-            prerequisite = action.prerequisite,
-            isDisabled = prerequisite ? !that[prerequisite]() : false;
-          actionMenu.createComponent({
-            name: name,
-            kind: XV.MenuItem,
-            content: action.label || ("_" + name).loc(),
-            action: action,
-            disabled: isDisabled
-          });
-
-        });
-        actionMenu.render();
-        this.$.actionButton.setShowing(actions.length);
-      },
-      create: function () {
-        this.inherited(arguments);
-        this.setList({list: this.getList()});
-        if (!this.getActions()) {
-          this.setActions([]);
-        }
-        this.buildMenu();
-      },
-      fetch: function (options) {
-        if (!this.init) { return; }
-        options = options ? _.clone(options) : {};
-        var list = this.$.list,
-          query,
-          parameterWidget,
-          parameters;
-        if (!list) { return; }
-        query = list.getQuery() || {};
-        parameterWidget = this.$.parameterWidget;
-        parameters = parameterWidget && parameterWidget.getParameters ?
-          parameterWidget.getParameters() : [];
-        options.showMore = _.isBoolean(options.showMore) ?
-          options.showMore : false;
-
-        // Build conditions
-        if (parameters.length) {
-          query.parameters = parameters;
-        } else {
-          delete query.parameters;
-        }
-        list.setQuery(query);
-        list.fetch(options);
-      },
-      /**
-        Capture order changed and transaction date changed events.
-        Depends on a very specific implementation of parameter widget
-        that includes `order` and `transactionDate` parameters.
-      */
-      parameterChanged: function (inSender, inEvent) {
-        var originator = inEvent ? inEvent.originator : false,
-          name = originator ? originator.name : false,
-          that = this,
-          options,
-          value;
-
-        if (name === "transactionDate") {
-          value = originator.$.input.getValue();
-          value = XT.date.applyTimezoneOffset(value, true);
-          this.setTransactionDate(value);
-          this.buildMenu();
-          return;
-        } else if (name === "order") {
-          value = originator.getParameter().value;
-          this.setModel(value);
-          this.buildMenu();
-        } else if (name === "shipment") {
-          return;
-        }
-
-        options = {
-          success: function () {
-            that.selectionChanged();
-          }
-        };
-        this.fetch(options);
-      },
-      popupHidden: function (inSender, inEvent) {
-        if (!this._popupDone) {
-          inEvent.originator.show();
-        }
-      },
-      /**
-        Overload: Piggy back on existing handler for `onParameterChanged event`
-        by forwarding this requery to `parameterChanged`.
-      */
-      requery: function (inSender, inEvent) {
-        this.parameterChanged(inSender, inEvent);
-        return true;
-      },
-      selectedModels: function () {
-        var list = this.$.list,
-          collection = list.getValue(),
-          models = [],
-          selected,
-          prop;
-        if (collection.length) {
-          selected = list.getSelection().selected;
-          for (prop in selected) {
-            if (selected.hasOwnProperty(prop)) {
-              models.push(list.getModel(prop - 0));
-            }
-          }
-        }
-        return models;
-      },
-      /**
-        Whenever a user makes a selection, rebuild the menu
-        and set the transaction date on the selected models
-        to match what has been selected here.
-      */
-      selectionChanged: function () {
-        this.transactionDateChanged();
-        this.buildMenu();
-      },
-      /**
-        @param {Object} Options
-        @param {String} [options.list] Class name
-      */
-      setList: function (options) {
-        var component,
-        list = options.list;
-
-        component = this.createComponent({
-          name: "list",
-          container: this.$.contentPanels,
-          kind: list,
-          fit: true
-        });
-        this.$.rightLabel.setContent(component.label);
-        if (component) {
-          this.createComponent({
-            name: "parameterWidget",
-            classes: "xv-groupbox xv-parameter",
-            showSaveFilter: false,
-            showLayout: false,
-            defaultParameters: null,
-            container: this.$.parameterScroller,
-            kind: component.getParameterWidget(),
-            memoizeEnabled: false,
-            fit: true
-          });
-        }
-
-        this.init = true;
-        this.render();
-      },
-      spinnerHide: function () {
-        this._popupDone = true;
-        this.$.spinnerPopup.hide();
-      },
-      spinnerShow: function () {
-        this._popupDone = false;
-        this.$.spinnerPopup.show();
-      },
-      transactionDateChanged: function () {
-        var transDate = this.getTransactionDate(),
-          collection = this.$.list.getValue(),
-          i;
-
-        // Update the transaction dates on all models to match
-        // What has been selected
-        for (i = 0; i < collection.length; i++) {
-          collection.at(i).transactionDate = transDate;
-        }
-      }
-    };
-
-    enyo.mixin(transactionList, XV.ListMenuManagerMixin);
-    enyo.kind(transactionList);
-
-    /** @private */
-    var _canDo = function (priv) {
-      var hasPrivilege = XT.session.privileges.get(priv),
-        model = this.getModel(),
-        validModel = _.isObject(model) ? !model.get("isShipped") : false;
-      return hasPrivilege && validModel;
-    };
-
     enyo.kind({
       name: "XV.IssueToShipping",
       kind: "XV.TransactionList",
@@ -287,45 +15,17 @@ trailing:true, white:true, strict:false*/
       list: "XV.IssueToShippingList",
       actions: [
         {name: "issueAll", label: "_issueAll".loc(),
-          prerequisite: "canIssueStock" },
-        {name: "issueSelectedStock", label: "_issueSelectedStock".loc(),
-          prerequisite: "canIssueSelected" },
-        {name: "issueSelected", label: "_issueSelected".loc(),
-          prerequisite: "canIssueSelected" },
-        {name: "returnSelected", label: "_returnSelected".loc(),
-          prerequisite: "canReturnSelected" },
+          prerequisite: "canIssueStock" }
       ],
       handlers: {
         onShipmentChanged: "shipmentChanged"
       },
-      canReturnSelected: function () {
-        var canDo = _canDo.call(this, "ReturnStockFromShipping"),
-          models = this.selectedModels(),
-          check;
-        if (canDo) {
-          check = _.find(models, function (model) {
-            return model.get("atShipping") > 0;
-          });
-        }
-
-        return !_.isEmpty(check);
-      },
-      canIssueSelected: function () {
-        var canDo = _canDo.call(this, "IssueStockToShipping"),
-          models = this.selectedModels(),
-          check;
-        if (canDo) {
-          check = _.find(models, function (model) {
-            return model.get("toIssue") > 0;
-          });
-        }
-
-        return !_.isEmpty(check);
-      },
       canIssueStock: function () {
-        var canDo = _canDo.call(this, "IssueStockToShipping"),
+        var hasPrivilege = XT.session.privileges.get("IssueStockToShipping"),
+          model = this.getModel(),
+          validModel = _.isObject(model) ? !model.get("isShipped") : false,
           hasOpenLines = this.$.list.value.length;
-        return canDo && hasOpenLines;
+        return hasPrivilege && validModel && hasOpenLines;
       },
       create: function () {
         this.inherited(arguments);
@@ -333,116 +33,8 @@ trailing:true, white:true, strict:false*/
         button.setContent("_ship".loc());
         button.setShowing(true);
       },
-      /**
-        Helper function for transacting `issue` on an array of models
-
-        @param {Array} Models
-        @param {Boolean} Prompt user for confirmation on every model
-      */
-      issue: function (models, prompt) {
-        var that = this,
-          i = -1,
-          callback,
-          data = [];
-
-        // Recursively issue everything we can
-        callback = function (workspace) {
-          var model,
-            options = {},
-            toIssue,
-            transDate,
-            params,
-            dispOptions = {},
-            wsOptions = {},
-            wsParams;
-
-          // If argument is false, this whole process was cancelled
-          if (workspace === false) {
-            return;
-
-          // If a workspace brought us here, process the information it obtained
-          } else if (workspace) {
-            model = workspace.getValue();
-            toIssue = model.get("toIssue");
-            transDate = that.getTransactionDate();
-
-            if (toIssue) {
-              wsOptions.detail = model.formatDetail();
-              wsOptions.asOf = transDate;
-              wsParams = {
-                orderLine: model.id,
-                quantity: toIssue,
-                options: wsOptions
-              };
-              data.push(wsParams);
-            }
-            workspace.doPrevious();
-          }
-
-          i++;
-          // If we've worked through all the models then forward to the server
-          if (i === models.length) {
-            that.spinnerShow();
-            dispOptions.success = function () {
-              that.requery();
-              that.spinnerHide();
-            };
-            XM.Inventory.issueToShipping(data, dispOptions);
-
-          // Else if there's something here we can issue, handle it
-          } else {
-            model = models[i];
-            toIssue = model.get("toIssue");
-            transDate = that.getTransactionDate();
-
-            // See if there's anything to issue here
-            if (toIssue) {
-
-              // If prompt or distribution detail required,
-              // open a workspace to handle it
-              if (prompt || model.undistributed()) {
-                that.doWorkspace({
-                  workspace: "XV.IssueStockWorkspace",
-                  id: model.id,
-                  callback: callback,
-                  allowNew: false,
-                  success: function (model) {
-                    model.transactionDate = transDate;
-                  }
-                });
-
-              // Otherwise just use the data we have
-              } else {
-                options.asOf = transDate;
-                options.detail = model.formatDetail();
-                params = {
-                  orderLine: model.id,
-                  quantity: toIssue,
-                  options: options
-                };
-                data.push(params);
-                callback();
-              }
-
-            // Nothing to issue, move on
-            } else {
-              callback();
-            }
-          }
-        };
-        callback();
-      },
       issueAll: function () {
-        var models = this.$.list.getValue().models;
-        this.issue(models);
-      },
-      issueSelected: function () {
-        var models = this.selectedModels();
-        this.issue(models);
-      },
-      issueSelectedStock: function () {
-        var models = this.selectedModels();
-        this.issue(models, true);
+        this.$.list.issueAll();
       },
       post: function () {
         var that = this,
@@ -456,37 +48,11 @@ trailing:true, white:true, strict:false*/
           callback: callback
         });
       },
-      returnSelected: function () {
-        var models = this.selectedModels(),
-          that = this,
-          data =  [],
-          options = {},
-          atShipping,
-          model,
-          i;
-
-        for (i = 0; i < models.length; i++) {
-          model = models[i];
-          atShipping = model.get("atShipping");
-
-          // See if there's anything to issue here
-          if (atShipping) {
-            data.push(model.id);
-          }
-        }
-
-        if (data.length) {
-          this.spinnerShow();
-          options.success = function () {
-            that.requery();
-            that.spinnerHide();
-          };
-          XM.Inventory.returnFromShipping(data, options);
-        }
-      },
       shipmentChanged: function (inSender, inEvent) {
+        var disabled = _.isEmpty(inEvent.shipment) ||
+                       !XT.session.privileges.get("ShipOrders");
         this.$.parameterWidget.$.shipment.setValue(inEvent.shipment);
-        this.$.postButton.setDisabled(_.isEmpty(inEvent.shipment));
+        this.$.postButton.setDisabled(disabled);
       }
     });
   };
index d324376..2745ea1 100644 (file)
@@ -223,17 +223,16 @@ trailing:true, white:true, strict: false*/
         }
       },
       /**
-        Overload: This version of save looks for a callback. If it's
-        there then the assumption is we're doing a series of issues
-        so forward to the next one.
+        Overload: This version of save just validates the model and forwards
+        on to callback. Designed specifically to work with `XV.IssueToShippingList`.
       */
       save: function () {
-        var callback = this.getCallback();
-        if (callback) {
-          callback(this);
-        } else {
-          this.inherited(arguments);
-        }
+        var callback = this.getCallback(),
+          model = this.getValue(),
+          workspace = this;
+        model.validate(function (isValid) {
+          if (isValid) { callback(workspace); }
+        });
       },
       /**
         If detail has been selected or deselected, handle default distribution.
index bce21b8..8f79898 100644 (file)
@@ -379,8 +379,10 @@ select xt.install_js('XM','Inventory','xtuple', $$
       series,
       sql1,
       sql2,
+      sql3,
       ary,
       item,
+      id,
       i;
 
     /* Make into an array if an array not passed */
@@ -399,16 +401,26 @@ select xt.install_js('XM','Inventory','xtuple', $$
            "  join xt.ordtype on c.relname=ordtype_tblname " +
            "where obj_uuid= $1;",
 
-    sql2 = "select issuetoshipping($1, {table}_id, $3, $4, $5::timestamptz) as series " +
-           "from {table} where obj_uuid = $2;";
+    sql2 = "select {table}_id as id " +
+           "from {table} where obj_uuid = $1;";
+
+    sql3 = "select current_date != $1 as invalid";
 
     /* Post the transaction */
     for (i = 0; i < ary.length; i++) {
       item = ary[i];
       asOf = item.options ? item.options.asOf : null;
       orderType = plv8.execute(sql1, [item.orderLine])[0];
-      series = plv8.execute(sql2.replace(/{table}/g, orderType.ordtype_tblname),
-        [orderType.ordtype_code, item.orderLine, item.quantity, 0, asOf])[0].series;
+      id = plv8.execute(sql2.replace(/{table}/g, orderType.ordtype_tblname),
+        [item.orderLine])[0].id;
+      series = XT.executeFunction("issuetoshipping",
+        [orderType.ordtype_code, id, item.quantity, 0, asOf],
+        [null, null, null, null, "timestamptz"]);
+
+      if (asOf && plv8.execute(sql3, [asOf])[0].invalid &&
+          !XT.Data.checkPrivilege("AlterTransactionDates")) {
+        throw new handleError("Insufficient privileges to alter transaction date", 401);
+      }
 
       /* Distribute detail */
       XM.PrivateInventory.distribute(series, item.options.detail);
@@ -443,20 +455,20 @@ select xt.install_js('XM','Inventory','xtuple', $$
     @param {Date} Ship date, default = current date
   */
   XM.Inventory.shipShipment = function (shipment, shipDate) {
-    var sql = "select shipshipment(shiphead_id, $2) as series " +
+    var sql = "select shiphead_id " +
       "from shiphead where shiphead_number = $1;";
 
     /* Make sure user can do this */
     if (!XT.Data.checkPrivilege("ShipOrders")) { throw new handleError("Access Denied", 401); }
 
     /* Post the transaction */
-    var ret = plv8.execute(sql, [shipment, shipDate])[0].series;
-    
-    return ret;
+    var shipmentId = plv8.execute(sql, [shipment])[0].shiphead_id;
+    return XT.executeFunction("shipshipment", [shipmentId, shipDate]);
   };
   XM.Inventory.shipShipment.description = "Ship shipment";
   XM.Inventory.shipShipment.params = {
-     shipment: { shipment: "Number", shipDate: "Ship Date" }
+     shipment: { type: "String", description: "Shipment natural key" },
+     shipDate: { type: "Date", description: "Ship Date" }
   };
 
   /**
diff --git a/enyo-client/extensions/source/project/client/en/package.js b/enyo-client/extensions/source/project/client/en/package.js
deleted file mode 100644 (file)
index 66ccc0b..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-enyo.depends(
-  "strings.js"
-);
diff --git a/enyo-client/extensions/source/project/client/en/strings.js b/enyo-client/extensions/source/project/client/en/strings.js
deleted file mode 100644 (file)
index 192856c..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-// ==========================================================================
-// Project:   XT` Strings
-// Copyright: Â©2011 OpenMFG LLC, d/b/a xTuple
-// ==========================================================================
-/*globals XT */
-
-// Place strings you want to localize here.  In your app, use the key and
-// localize it using "key string".loc().  HINT: For your key names, use the
-// english string with an underscore in front.  This way you can still see
-// how your UI will look and you'll notice right away when something needs a
-// localized string added to this file!
-//
-
-var lang = XT.stringsFor("en_US", {
-
-  // ********
-  // Labels
-  // ********
-
-
-});
diff --git a/enyo-client/extensions/source/sales/client/en/package.js b/enyo-client/extensions/source/sales/client/en/package.js
deleted file mode 100644 (file)
index 66ccc0b..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-enyo.depends(
-  "strings.js"
-);
index 787cf96..cb7c841 100644 (file)
@@ -1,78 +1,77 @@
-// ==========================================================================
-// Project:   XT` Strings
-// Copyright: Â©2011 OpenMFG LLC, d/b/a xTuple
-// ==========================================================================
-/*globals XT */
+/*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 XT:true */
 
-// Place strings you want to localize here.  In your app, use the key and
-// localize it using "key string".loc().  HINT: For your key names, use the
-// english string with an underscore in front.  This way you can still see
-// how your UI will look and you'll notice right away when something needs a
-// localized string added to this file!
-//
+(function () {
+  "use strict";
 
-var lang = XT.stringsFor("en_US", {
+  var lang = XT.stringsFor("en_US", {
+    "_acceptsBackOrders": "Accepts Back Orders",
+    "_acceptsPartialShipments": "Accepts Partial Shipments",
+    "_allowASAPShipSchedules": "Allow ASAP Schedule Dates",
+    "_allowDiscounts": "Allow Price Discounts",
+    "_allowFreeFormShiptos": "Allow Free Form Ship-Tos",
+    "_autoAllocateCreditMemos": "Allocate Credit Memos to New Sales Order on Save",
+    "_autoSelectForBilling": "Check 'Select for Billing' option on Ship Order",
+    "_bookings": "Bookings",
+    "_convert": "Convert",
+    "_creditControl": "Credit Control",
+    "_creditMemo": "Credit Memo",
+    "_current": "Current",
+    "_currentDate": "Current Date",
+    "_customerDefaults": "Customer Defaults",
+    "_customerTypes": "Customer Types",
+    "_cutOffDay": "Cutoff Day",
+    "_dateControl": "Date Control",
+    "_disableSalesOrderPriceOverride": "Disallow Override of Sale Price on Sales Order",
+    "_discountDays": "Discount Days",
+    "_doNotUpdate": "Do Not Update",
+    "_enableSOShipping": "Enable Shipping Interface from Sales Order screen",
+    "_firmSalesOrdersWhenAddedToPackingList": "Firm Sales Orders when added to Packing List Batch",
+    "_fiveYears": "Five Years",
+    "_freightPricing": "Freight Pricing",
+    "_hideSOMiscCharge": "Hide Misc. Charge on Sales Order screen",
+    "_ignoreIfDiscounted": "Ignore if Discounted",
+    "_includePackageWeight": "Include Package Weight in Freight Calculation",
+    "_invoice": "Invoice",
+    "_invoiceDateSource": "Invoice Date Source",
+    "_postCustomerChanges": "Post Customer Changes",
+    "_postSalesOrderChanges": "Post Sales Order Changes",
+    "_priceControl": "Price Control",
+    "_priceEffectiveDate": "Price Effective Date",
+    "_pricing": "Pricing",
+    "_pricingOnLineItemEdits": "Pricing on Line Item edits",
+    "_printOnSave": "Print on Save",
+    "_process": "Process",
+    "_processCreditCards": "Process Credit Cards",
+    "_prompt": "Prompt",
+    "_restrictCreditMemos": "Restrict Credit Memos to Items on their Apply-to Document",
+    "_sales": "Sales",
+    "_salesDescription": "Customer and Sales Order Management",
+    "_salesHistory": "Sales History",
+    "_salesOrder": "Sales Order",
+    "_scheduled": "Scheduled",
+    "_scheduledDate": "Scheduled Date",
+    "_shipControl": "Ship Control",
+    "_shipDate": "Ship Date",
+    "_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",
+    "_thisYear": "This Year",
+    "_timeFrame": "Time Frame",
+    "_twoYears": "Two Years",
+    "_today": "Today",
+    "_update": "Update",
+    "_useCalculatedFreightPricing": "Use calculated Freight values by default",
+    "_usePromiseDates": "Enable Promise Dates",
+    "_viewSalesHistory": "View Sales History"
+  });
 
-  "_acceptsBackOrders": "Accepts Back Orders",
-  "_acceptsPartialShipments": "Accepts Partial Shipments",
-  "_allowASAPShipSchedules": "Allow ASAP Schedule Dates",
-  "_allowDiscounts": "Allow Price Discounts",
-  "_allowFreeFormShiptos": "Allow Free Form Ship-Tos",
-  "_autoAllocateCreditMemos": "Allocate Credit Memos to New Sales Order on Save",
-  "_autoSelectForBilling": "Check 'Select for Billing' option on Ship Order",
-  "_bookings": "Bookings",
-  "_convert": "Convert",
-  "_creditControl": "Credit Control",
-  "_creditMemo": "Credit Memo",
-  "_current": "Current",
-  "_currentDate": "Current Date",
-  "_customerDefaults": "Customer Defaults",
-  "_customerTypes": "Customer Types",
-  "_cutOffDay": "Cutoff Day",
-  "_dateControl": "Date Control",
-  "_disableSalesOrderPriceOverride": "Disallow Override of Sale Price on Sales Order",
-  "_discountDays": "Discount Days",
-  "_doNotUpdate": "Do Not Update",
-  "_enableSOShipping": "Enable Shipping Interface from Sales Order screen",
-  "_firmSalesOrdersWhenAddedToPackingList": "Firm Sales Orders when added to Packing List Batch",
-  "_fiveYears": "Five Years",
-  "_freightPricing": "Freight Pricing",
-  "_hideSOMiscCharge": "Hide Misc. Charge on Sales Order screen",
-  "_ignoreIfDiscounted": "Ignore if Discounted",
-  "_includePackageWeight": "Include Package Weight in Freight Calculation",
-  "_invoice": "Invoice",
-  "_invoiceDateSource": "Invoice Date Source",
-  "_postCustomerChanges": "Post Customer Changes",
-  "_postSalesOrderChanges": "Post Sales Order Changes",
-  "_priceControl": "Price Control",
-  "_priceEffectiveDate": "Price Effective Date",
-  "_pricing": "Pricing",
-  "_pricingOnLineItemEdits": "Pricing on Line Item edits",
-  "_printOnSave": "Print on Save",
-  "_process": "Process",
-  "_processCreditCards": "Process Credit Cards",
-  "_prompt": "Prompt",
-  "_restrictCreditMemos": "Restrict Credit Memos to Items on their Apply-to Document",
-  "_sales": "Sales",
-  "_salesDescription": "Customer and Sales Order Management",
-  "_salesHistory": "Sales History",
-  "_salesOrder": "Sales Order",
-  "_scheduled": "Scheduled",
-  "_scheduledDate": "Scheduled Date",
-  "_shipControl": "Ship Control",
-  "_shipDate": "Ship Date",
-  "_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",
-  "_thisYear": "This Year",
-  "_timeFrame": "Time Frame",
-  "_twoYears": "Two Years",
-  "_today": "Today",
-  "_update": "Update",
-  "_useCalculatedFreightPricing": "Use calculated Freight values by default",
-  "_usePromiseDates": "Enable Promise Dates",
-  "_viewSalesHistory": "View Sales History"
-});
+  if (typeof exports !== 'undefined') {
+    exports.language = lang;
+  }
+}());
index dc2263a..8b838f1 100644 (file)
@@ -21,6 +21,7 @@ trailing:true, white:true*/
       {name: "siteList", kind: "XV.SiteList"},
       {name: "siteTypeList", kind: "XV.SiteTypeList"},
       {name: "saleTypeList", kind: "XV.SaleTypeList"},
+      {name: "shipViaList", kind: "XV.ShipViaList"},
       {name: "shipZoneList", kind: "XV.ShipZoneList"},
       {name: "salesRepList", kind: "XV.SalesRepList"},
       {name: "taxAssignmentList", kind: "XV.TaxAssignmentList"},
index 136e625..81d1e8a 100644 (file)
@@ -1,7 +1,7 @@
 /*jshint bitwise:true, indent:2, curly:true, eqeqeq:true, immed:true,
 latedef:true, newcap:true, noarg:true, regexp:true, undef:true,
 trailing:true, white:true*/
-/*global XT:true, enyo:true*/
+/*global XT:true, enyo:true, Globalize:true*/
 
 (function () {
 
@@ -61,6 +61,41 @@ trailing:true, white:true*/
     // OPPORTUNITY QUOTE
     //
 
+    enyo.kind({
+      name: "XV.OpportunityQuoteListRelations",
+      kind: "XV.ListRelations",
+      orderBy: [
+        {attribute: 'number', descending: true}
+      ],
+      parentKey: "opportunity",
+      components: [
+        {kind: "XV.ListItem", components: [
+          {kind: "FittableRows", components: [
+            {kind: "XV.ListColumn", classes: "first", components: [
+              {kind: "FittableColumns", components: [
+                {kind: "XV.ListAttr", attr: "number", classes: "bold"},
+                {kind: "XV.ListAttr", attr: "quoteDate", classes: "right"}
+              ]}
+            ]},
+            {kind: "XV.ListColumn", classes: "first", components: [
+              {kind: "FittableColumns", components: [
+                {kind: "XV.ListAttr", attr: "shipVia"},
+                {kind: "XV.ListAttr", attr: "total", classes: "right",
+                  formatter: "formatMoney"}
+              ]}
+            ]}
+          ]}
+        ]}
+      ],
+      formatMoney: function (value) {
+        return Globalize.format(value, "c" + XT.locale.currencyScale);
+      }
+    });
+
+    // ..........................................................
+    // OPPORTUNITY SALES ORDER
+    //
+
     enyo.kind({
       name: "XV.OpportunitySalesListRelations",
       kind: "XV.ListRelations",
@@ -70,16 +105,26 @@ trailing:true, white:true*/
       parentKey: "opportunity",
       components: [
         {kind: "XV.ListItem", components: [
-          {kind: "FittableColumns", components: [
+          {kind: "FittableRows", components: [
             {kind: "XV.ListColumn", classes: "first", components: [
               {kind: "FittableColumns", components: [
                 {kind: "XV.ListAttr", attr: "number", classes: "bold"},
-                {kind: "XV.ListAttr", attr: "shipVia", classes: "right"}
+                {kind: "XV.ListAttr", attr: "orderDate", classes: "right"}
+              ]}
+            ]},
+            {kind: "XV.ListColumn", classes: "first", components: [
+              {kind: "FittableColumns", components: [
+                {kind: "XV.ListAttr", attr: "shipVia"},
+                {kind: "XV.ListAttr", attr: "total", classes: "right",
+                  formatter: "formatMoney"}
               ]}
             ]}
           ]}
         ]}
-      ]
+      ],
+      formatMoney: function (value) {
+        return Globalize.format(value, "c" + XT.locale.currencyScale);
+      }
     });
 
   };
index 507994f..ba6e18b 100644 (file)
@@ -70,7 +70,7 @@ trailing:true, white:true*/
       kind: "XV.ListRelationsBox",
       title: "_quotes".loc(),
       parentKey: "opportunity",
-      listRelations: "XV.OpportunitySalesListRelations", // not a bug
+      listRelations: "XV.OpportunityQuoteListRelations", // not a bug
       searchList: "XV.QuoteList",
       handlers: {
         onWorkspace: "appendWorkspace"
index a9d01c5..b265c1c 100644 (file)
@@ -125,6 +125,14 @@ white:true*/
     */
     checkConflicts: true,
 
+    /**
+      Used when converting from an account to the subclass. No need to include
+      the document key which is automatically accounted for.
+    */
+    conversionMap: {
+      name: "name"
+    },
+
     // ..........................................................
     // METHODS
     //
index 60a0986..b828c50 100644 (file)
@@ -80,7 +80,7 @@ white:true*/
     SETTINGS: 0x01,
     PRIVILEGES: 0x02,
     SCHEMA: 0x04,
-    LOCALE: 0x08,
+    LOCALE: 0x08, // no longer used
     PREFERENCES: 0x10,
     ALL: 0x01 | 0x02 | 0x04 | 0x08 | 0x10,
 
@@ -99,7 +99,6 @@ white:true*/
         settingsOptions,
         settings,
         schemaOptions,
-        localeOptions,
         preferencesOptions,
         extensionOptions,
         callback,
@@ -109,7 +108,9 @@ white:true*/
 
       if (options && options.success && options.success instanceof Function) {
         callback = options.success;
-      } else { callback = XT.K; }
+      } else {
+        callback = function () {};
+      }
 
       if (types === undefined) { types = this.ALL; }
 
@@ -243,19 +244,6 @@ white:true*/
         });
       }
 
-      if (types & this.LOCALE) {
-        localeOptions = options ? _.clone(options) : {};
-
-        // callback
-        localeOptions.success = function (resp) {
-          var locale = new Backbone.Model(resp);
-          that.setLocale(locale);
-          callback();
-        };
-
-        XM.ModelMixin.dispatch('XT.Session', 'locale', null, localeOptions);
-      }
-
       if (types & this.PREFERENCES) {
         preferencesOptions = options ? _.clone(options) : {};
 
@@ -272,10 +260,6 @@ white:true*/
       return true;
     },
 
-    getLocale: function () {
-      return this.locale;
-    },
-
     getPreferences: function () {
       return this.preferences;
     },
@@ -292,11 +276,6 @@ white:true*/
       return this.privileges;
     },
 
-    setLocale: function (value) {
-      this.locale = value;
-      return this;
-    },
-
     setPreferences: function (value) {
       this.preferences = value;
       return this;
index deee7ba..1e1b1e5 100644 (file)
@@ -78,7 +78,7 @@ white:true*/
       immediately. Calling destroy on a child relation will simply mark it for
       deletion on the next save of the parent.
 
-      @returns {XT.Request|Boolean}
+      @returns {Object|Boolean}
     */
     destroy: function (options) {
       options = options ? _.clone(options) : {};
@@ -143,7 +143,7 @@ white:true*/
       a pessimistic lock on the record.
 
       @param {Object} Options
-      @returns {XT.Request} Request
+      @returns {Object} Request
     */
     fetch: function (options) {
       options = options ? _.clone(options) : {};
@@ -195,7 +195,7 @@ white:true*/
       Set the id on this record an id from the server. Including the `cascade`
       option will call ids to be fetched recursively for `HasMany` relations.
 
-      @returns {XT.Request} Request
+      @returns {Object} Request
     */
     fetchId: function (options) {
       options = _.defaults(options ? _.clone(options) : {});
@@ -804,7 +804,7 @@ white:true*/
     /**
       Reimplemented.
 
-      @retuns {XT.Request} Request
+      @retuns {Object} Request
     */
     save: function (key, value, options) {
       options = options ? _.clone(options) : {};
index 89061c6..27ad45e 100644 (file)
@@ -214,7 +214,7 @@ noarg:true, regexp:true, undef:true, strict:true, trailing:true, white:true*/
       `destroy` will cause the model to commit to the server
       immediately.
 
-      @returns {XT.Request|Boolean}
+      @returns {Object|Boolean}
     */
     destroy: function (options) {
       options = options ? _.clone(options) : {};
@@ -403,7 +403,7 @@ noarg:true, regexp:true, undef:true, strict:true, trailing:true, white:true*/
       Reimplemented to handle status changes.
 
       @param {Object} Options
-      @returns {XT.Request} Request
+      @returns {Object} Request
     */
     fetch: function (options) {
       options = options ? _.clone(options) : {};
@@ -431,7 +431,7 @@ noarg:true, regexp:true, undef:true, strict:true, trailing:true, white:true*/
       Set the id on this record an id from the server. Including the `cascade`
       option will call ids to be fetched recursively for `HasMany` relations.
 
-      @returns {XT.Request} Request
+      @returns {Object} Request
     */
     fetchId: function (options) {
       options = _.defaults(options ? _.clone(options) : {}, {});
@@ -787,7 +787,7 @@ noarg:true, regexp:true, undef:true, strict:true, trailing:true, white:true*/
     /**
       Reimplemented.
 
-      @retuns {XT.Request} Request
+      @retuns {Object} Request
     */
     save: function (key, value, options) {
       options = options ? _.clone(options) : {};
@@ -1055,7 +1055,7 @@ noarg:true, regexp:true, undef:true, strict:true, trailing:true, white:true*/
       to work correctly.
 
       @param {Object} Options
-      @returns {XT.Request} Request
+      @returns {Object} Request
     */
     used: function (options) {
       return this.getClass().used(this.id, options);
@@ -1396,7 +1396,7 @@ noarg:true, regexp:true, undef:true, strict:true, trailing:true, white:true*/
 
       @param {Number} Id
       @param {Object} Options
-      @returns {XT.Request} Request
+      @returns {Object} Request
     */
     used: function (id, options) {
       return XM.ModelMixin.dispatch('XM.Model', 'used',
diff --git a/lib/enyo-x/source/en/package.js b/lib/enyo-x/source/en/package.js
deleted file mode 100644 (file)
index 66ccc0b..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-enyo.depends(
-  "strings.js"
-);
index 8b93f9e..afdbcb1 100644 (file)
-// ==========================================================================
-// Project:   XT` Strings
-// Copyright: Â©2011 OpenMFG LLC, d/b/a xTuple
-// ==========================================================================
-/*globals XT */
+/*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 XT:true */
 
 // Place strings you want to localize here.  In your app, use the key and
 // localize it using "key string".loc().  HINT: For your key names, use the
 // english string with an underscore in front.  This way you can still see
 // how your UI will look and you'll notice right away when something needs a
 // localized string added to this file!
-//
 
-XT.stringsFor("en_US", {
+(function () {
+  "use strict";
 
-  "_about": "About",
-  "_accountParent": "Account Parent",
-  "_actions": "Actions",
-  "_advancedSearch": "Advanced Search",
-  "_addChart": "Add Chart",
-  "_any": "Any",
-  "_apply": "Apply",
-  "_attr": "Attr",
-  "_back": "Back",
-  "_cancel": "Cancel",
-  "_changePassword": "Change Password",
-  "_child": "Child",
-  "_childOf": "Child of",
-  "_chooseOne": "Choose One",
-  "_city": "City",
-  "_column": "Column",
-  "_changeLayout": "Change Layout",
-  "_comments": "Comments",
-  "_characteristics": "Characteristics",
-  "_country": "Country",
-  "_currentLayout": "Current Layout",
-  "_dashboard": "Dashboard",
-  "_delete": "Delete",
-  "_discard": "Discard",
-  "_documents": "Documents",
-  "_done": "Done",
-  "_duplicate": "Duplicate",
-  "_duplicateOf": "Duplicate of",
-  "_edit": "Edit",
-  "_editAddress": "Edit Address",
-  "_expand": "Expand",
-  "_export": "Export",
-  "_field": "Field",
-  "_filter": "Filter",
-  "_filters": "Filters",
-  "_filterOn": "Filter On",
-  "_filterName": "Filter Name",
-  "_groupBy": "Group By",
-  "_help": "Help",
-  "_hide": "Hide",
-  "_history" : "History",
-  "_isShared": "Shared",
-  "_issueDate": "Issue Date",
-  "_issueSelected": "Issue All Selected",
-  "_issueSelectedStock": "Issue Quantity for Selected",
-  "_layout": "Layout",
-  "_layoutName": "Layout Name",
-  "_less": "Less Options",
-  "_loading": "Loading",
-  "_manage": "Manage",
-  "_manageFilters": "Manage Filters",
-  "_manageLayouts": "Manage Layouts",
-  "_map": "Map",
-  "_more": "More Options",
-  "_myAccount": "My Account",
-  "_new": "New",
-  "_next": "Next",
-  "_no": "No",
-  "_none": "None",
-  "_ok": "Ok",
-  "_oldPassword": "Old Password",
-  "_open": "Open",
-  "_openNewTab": "New Tab",
-  "_overview": "Overview",
-  "_parameters": "Parameters",
-  "_parent": "Parent",
-  "_parentOf": "Parent of",
-  "_passwordMismatch": "Passwords don't match",
-  "_postalCode": "Postal Code",
-  "_preferences": "Preferences",
-  "_previous": "Previous",
-  "_print": "Print",
-  "_purpose": "Purpose",
-  "_refresh": "Refresh",
-  "_related": "Related",
-  "_relatedTo": "Related to",
-  "_requireSufficientInventory": "Require Sufficient Inventory",
-  "_returnSelected": "Return All Selected",
-  "_removeChart": "Remove Chart",
-  "_row": "Row",
-  "_save": "Save",
-  "_saveAndNew": "Save and New",
-  "_saveLayout": "Save Layout",
-  "_saving": "Saving",
-  "_saveFilter": "Save Filter",
-  "_savedFilters": "Saved Filters",
-  "_search": "Search",
-  "_sort": "Sort",
-  "_sortBy": "Sort By...",
-  "_street": "Street",
-  "_state": "State",
-  "_then": "Then...",
-  "_yes": "Yes",
+  var lang = XT.stringsFor("en_US", {
+    "_about": "About",
+    "_accountParent": "Account Parent",
+    "_actions": "Actions",
+    "_advancedSearch": "Advanced Search",
+    "_addChart": "Add Chart",
+    "_any": "Any",
+    "_apply": "Apply",
+    "_attr": "Attr",
+    "_back": "Back",
+    "_cancel": "Cancel",
+    "_changePassword": "Change Password",
+    "_child": "Child",
+    "_childOf": "Child of",
+    "_chooseOne": "Choose One",
+    "_city": "City",
+    "_column": "Column",
+    "_changeLayout": "Change Layout",
+    "_comments": "Comments",
+    "_characteristics": "Characteristics",
+    "_country": "Country",
+    "_currentLayout": "Current Layout",
+    "_dashboard": "Dashboard",
+    "_delete": "Delete",
+    "_discard": "Discard",
+    "_documents": "Documents",
+    "_done": "Done",
+    "_duplicate": "Duplicate",
+    "_duplicateOf": "Duplicate of",
+    "_edit": "Edit",
+    "_editAddress": "Edit Address",
+    "_expand": "Expand",
+    "_export": "Export",
+    "_field": "Field",
+    "_filter": "Filter",
+    "_filters": "Filters",
+    "_filterOn": "Filter On",
+    "_filterName": "Filter Name",
+    "_groupBy": "Group By",
+    "_help": "Help",
+    "_hide": "Hide",
+    "_history" : "History",
+    "_isShared": "Shared",
+    "_issueDate": "Issue Date",
+    "_issueSelected": "Issue All Selected",
+    "_issueSelectedStock": "Issue Quantity for Selected",
+    "_layout": "Layout",
+    "_layoutName": "Layout Name",
+    "_less": "Less Options",
+    "_loading": "Loading",
+    "_manage": "Manage",
+    "_manageFilters": "Manage Filters",
+    "_manageLayouts": "Manage Layouts",
+    "_map": "Map",
+    "_more": "More Options",
+    "_myAccount": "My Account",
+    "_new": "New",
+    "_next": "Next",
+    "_no": "No",
+    "_none": "None",
+    "_ok": "Ok",
+    "_oldPassword": "Old Password",
+    "_open": "Open",
+    "_openNewTab": "New Tab",
+    "_overview": "Overview",
+    "_parameters": "Parameters",
+    "_parent": "Parent",
+    "_parentOf": "Parent of",
+    "_passwordMismatch": "Passwords don't match",
+    "_postalCode": "Postal Code",
+    "_preferences": "Preferences",
+    "_previous": "Previous",
+    "_print": "Print",
+    "_processing": "Processing",
+    "_purpose": "Purpose",
+    "_refresh": "Refresh",
+    "_related": "Related",
+    "_relatedTo": "Related to",
+    "_requireSufficientInventory": "Require Sufficient Inventory",
+    "_returnSelected": "Return All Selected",
+    "_removeChart": "Remove Chart",
+    "_row": "Row",
+    "_save": "Save",
+    "_saveAndNew": "Save and New",
+    "_saveLayout": "Save Layout",
+    "_saving": "Saving",
+    "_saveFilter": "Save Filter",
+    "_savedFilters": "Saved Filters",
+    "_search": "Search",
+    "_sort": "Sort",
+    "_sortBy": "Sort By...",
+    "_street": "Street",
+    "_state": "State",
+    "_then": "Then...",
+    "_yes": "Yes",
 
-  // ********
-  // Messages
-  // ********
+    // ********
+    // Messages
+    // ********
+    "_confirmAction": "Are you sure this is what you want to do?",
+    "_changeAll": "Change All",
+    "_changeOne": "Change only this one",
+    "_confirmDelete": "This will permanently delete this record.",
+    "_editFilter?": "Do you want to edit this filter?",
+    "_filterExists": "A filter exists with this name.",
+    "_invalidPassword": "Entry does not match your old password. Please re-enter.",
+    "_insufficientCreatePrivileges": "You do not have sufficient privileges to create this record.",
+    "_insufficientViewPrivileges": "You do not have sufficient privileges to view this record.",
+    "_loadingApplicationData": "Loading Application Data",
+    "_loadingExtensions": "Loading Extensions",
+    "_loadingSchema": "Loading Schema",
+    "_loadingSessionData": "Loading Session Data",
+    "_lockInfo": "This record has been locked by {user} since {effective}.",
+    "_logoutConfirmation": "Are you sure you want to log out?",
+    "_openAdvancedWorkspace": "Open Advanced Workspace",
+    "_newPassword": "New Password",
+    "_noDescription": "No Description",
+    "_noDocumentFound": "No Document Found.",
+    "_noOldPassword": "Please enter your old password",
+    "_unsavedChanges": "You have unsaved changes.",
+    "_saveYourWork?": "Do you want to save your work?"
+  });
 
-  "_confirmAction": "Are you sure this is what you want to do?",
-  "_changeAll": "Change All",
-  "_changeOne": "Change only this one",
-  "_confirmDelete": "This will permanently delete this record.",
-  "_editFilter?": "Do you want to edit this filter?",
-  "_filterExists": "A filter exists with this name.",
-  "_invalidPassword": "Entry does not match your old password. Please re-enter.",
-  "_insufficientCreatePrivileges": "You do not have sufficient privileges to create this record.",
-  "_insufficientViewPrivileges": "You do not have sufficient privileges to view this record.",
-  "_loadingApplicationData": "Loading Application Data",
-  "_loadingExtensions": "Loading Extensions",
-  "_loadingSchema": "Loading Schema",
-  "_loadingSessionData": "Loading Session Data",
-  "_lockInfo": "This record has been locked by {user} since {effective}.",
-  "_logoutConfirmation": "Are you sure you want to log out?",
-  "_openAdvancedWorkspace": "Open Advanced Workspace",
-  "_newPassword": "New Password",
-  "_noDescription": "No Description",
-  "_noDocumentFound": "No Document Found.",
-  "_noOldPassword": "Please enter your old password",
-  "_unsavedChanges": "You have unsaved changes.",
-  "_saveYourWork?": "Do you want to save your work?"
-
-});
-
-// TODO: Temporary until we get loaded from datasource
-XT.locale.setLanguage("en_US");
+  if (typeof exports !== 'undefined') {
+    exports.language = lang;
+  }
+}());
index af70802..20698b4 100644 (file)
@@ -1,6 +1,5 @@
 enyo.depends(
   "core.js",
-  "en",
   "widgets",
   "views",
   "app.js",
index fab9fb6..29d450d 100644 (file)
@@ -1,7 +1,7 @@
 /*jshint bitwise:true, indent:2, curly:true, eqeqeq:true, immed:true,
 latedef:true, newcap:true, noarg:true, regexp:true, undef:true,
 trailing:true, white:true, strict:false*/
-/*global XT:true, XM:true, XV:true, _:true, enyo:true, window: true, Globalize:true*/
+/*global XT:true, XM:true, XV:true, _:true, enyo:true, window: true*/
 
 (function () {
 
@@ -139,6 +139,7 @@ trailing:true, white:true, strict:false*/
       onActionSelected: "actionSelected",
       onModelChange: "modelChanged",
       onListItemMenuTap: "transformListAction",
+      onSelectionChanged: "selectionChanged",
       onSetupItem: "setupItem"
     },
     /**
@@ -312,7 +313,6 @@ trailing:true, white:true, strict:false*/
     getDisplayAttributes: function () {
       var attributes,
         relations,
-        relationAttributes,
         model,
         relationModels;
 
@@ -346,7 +346,7 @@ trailing:true, white:true, strict:false*/
 
       // Loop through the relation models and concatenate the arrays of
       // attribute names.
-      _.each(relationModels, function (relModel) {
+      _.each(relationModels, function () {
         // combine the relation arrays
         attributes = attributes.concat(_.flatten(relationModels));
       });
@@ -437,23 +437,40 @@ trailing:true, white:true, strict:false*/
         return s === name;
       });
     },
+    /**
+      Returns a placeholder translation string to be
+      used as a placeholder if one is not specified.
+
+      @param {String} attr
+      @returns {String}
+    */
+    getPlaceholderForAttr: function (attr) {
+      var attrs, str;
+      if (attr.indexOf('.') !== -1) {
+        attrs = attr.split(".");
+        str = ("_" + attrs[0]).loc() + " " + ("_" + attrs[1]).loc();
+      } else {
+        str = ("_" + attr).loc();
+      }
+      return "_no".loc() + " " + str;
+    },
     /**
       Returns whether all actions on the list have been determined
-      to be available or not on a given index.
+      to be available or not.
 
       @param {Number} index
       @returns {Boolean}
     */
-    haveAllAnswers: function (index) {
-      if (this._haveAllAnswers[index]) { return true; }
+    haveAllAnswers: function () {
+      if (this._haveAllAnswers) { return true; }
       var that = this,
-        permissions = that._actionPermissions[index],
+        permissions = that._actionPermissions,
         ret;
       if (_.isEmpty(permissions)) { return false; }
       ret = _.reduce(this.getActions(), function (memo, action) {
         return memo && _.isBoolean(permissions[action.name]);
       }, true);
-      if (ret) { this._haveAllAnswers[index] = true; }
+      if (ret) { this._haveAllAnswers = true; }
       return ret;
     },
     /**
@@ -549,12 +566,10 @@ trailing:true, white:true, strict:false*/
             bval,
             attr,
             numeric,
-            descending,
             i;
           for (i = 0; i < query.orderBy.length; i++) {
             attr = query.orderBy[i].attribute;
             numeric = query.orderBy[i].numeric;
-            descending = query.orderBy[i].descending;
             aval = query.orderBy[i].descending ? b.getValue(attr) : a.getValue(attr);
             bval = query.orderBy[i].descending ? a.getValue(attr) : b.getValue(attr);
             aval = numeric ? aval - 0 : aval;
@@ -568,13 +583,13 @@ trailing:true, white:true, strict:false*/
       }
     },
     /**
-      Reset actions on a give index so permission checks will be regenerated.
+      Reset actions permission checks will be regenerated.
 
       @param {Number} Index
     */
-    resetActions: function (index) {
-      this._actionPermissions[index] = {};
-      this._haveAllAnswers[index] = undefined;
+    resetActions: function () {
+      this._actionPermissions = {};
+      this._haveAllAnswers = undefined;
     },
      /**
       Manages lazy loading of items in the list.
@@ -602,32 +617,70 @@ trailing:true, white:true, strict:false*/
       @returns {Array}
     */
     selectedIndexes: function () {
-      var selected = this.getSelection().selected,
-        idx = [],
-        prop;
-      for (prop in selected) {
-        if (selected.hasOwnProperty(prop)) {
-          idx.push(prop - 0);
-        }
-      }
-      return idx;
+      return _.keys(this.getSelection().selected);
     },
     /**
-      Returns a placeholder translation string to be
-      used as a placeholder if one is not specified.
-
-      @param {String} attr
-      @returns {String}
+      Re-evaluates actions menu.
     */
-    getPlaceholderForAttr: function (attr) {
-      var attrs, str;
-      if (attr.indexOf('.') !== -1) {
-        attrs = attr.split(".");
-        str = ("_" + attrs[0]).loc() + " " + ("_" + attrs[1]).loc();
-      } else {
-        str = ("_" + attr).loc();
-      }
-      return "_no".loc() + " " + str;
+    selectionChanged: function (inSender, inEvent) {
+      var keys = this.selectedIndexes(),
+        index = inEvent.index,
+        collection = this.value,
+        actions = this.actions,
+        that = this;
+      this.resetActions();
+
+      // Loop through each action
+      _.each(actions, function (action) {
+        var prerequisite = action.prerequisite,
+          permissions = that._actionPermissions,
+          name = action.name,
+          len = keys.length,
+          counter = 0,
+          model,
+          idx,
+          callback,
+          i;
+
+        // Callback to let us know if we can do an action. If we have
+        // all the answers, enable the action icon.
+        callback = function (response) {
+          // If some other model failed, forget about it
+          if (permissions[name] === false) { return; }
+
+          // If even one selected model fails, then we can't do the action
+          if (response) {
+            counter++;
+
+            // If we haven't heard back from all requests yet, wait for the next
+            if (counter < len) {
+              return;
+            }
+          }
+          permissions[name] = response;
+
+          // Handle asynchronous result re-rendering
+          if (that.haveAllAnswers()) {
+            that.renderRow(index);
+          }
+        };
+
+        if (prerequisite) {
+          // Loop through each selection model and check pre-requisite
+          for (i = 0; i < keys.length; i++) {
+            idx = keys[i] - 0;
+            model = collection.at(idx);
+            if (model instanceof XM.Info && !model[prerequisite]) {
+              XT.getObjectByName(model.editableModel)[prerequisite](model, callback);
+            } else {
+              model[prerequisite](callback);
+            }
+          }
+        } else {
+          callback(true);
+        }
+
+      });
     },
      /**
       @todo Document the setupItem method.
@@ -635,7 +688,11 @@ trailing:true, white:true, strict:false*/
     setupItem: function (inSender, inEvent) {
       var index = inEvent.index,
         isSelected = inEvent.originator.isSelected(index),
-        model = this.getValue().models[index],
+        collection = this.value,
+        model = collection.at(index),
+        actionIconButton = this.$.listItem.getActionIconButton(),
+        toggleSelected = this.getToggleSelected(),
+        actions = this.getActions(),
         isActive,
         isNotActive,
         isItalic,
@@ -644,20 +701,13 @@ trailing:true, white:true, strict:false*/
         isPlaceholder,
         prev,
         next,
-        actionIconButton = this.$.listItem.getActionIconButton(),
-        toggleSelected = this.getToggleSelected(),
-        actions = this.getActions(),
-        that = this,
         prop,
         obj,
         view,
         value,
         formatter,
         attr,
-        classes,
-        name,
         showd,
-        callback,
         ary;
 
       // It is possible in some cases where setupItem might
@@ -669,15 +719,6 @@ trailing:true, white:true, strict:false*/
       isActive = model.getValue ? model.getValue("isActive") : true;
       isNotActive = _.isBoolean(isActive) ? !isActive : false;
 
-      // Callback to let us know if we can do an action. If we have
-      // all the answers, enable the action icon.
-      callback = function (response) {
-        that._actionPermissions[index][name] = response;
-        // Handle asynchronous result re-rendering
-        if (that.haveAllAnswers(index)) {
-          that.renderRow(index);
-        }
-      };
       // set the overflow row to be hidden by default
       this.$.overflow.setShowing(false);
 
@@ -764,30 +805,12 @@ trailing:true, white:true, strict:false*/
         // the list has other actions allowed on the action icon button
         if (!isSelected || !actions.length) {
           actionIconButton.applyStyle("display", "none");
-          this.resetActions(index);
-        } else if (this.haveAllAnswers(index)) {
+        } else if (this.haveAllAnswers()) {
           actionIconButton.applyStyle("display", "inline-block");
           actionIconButton.applyStyle("opacity", "1");
-
-        // Check availability of actions
         } else {
           actionIconButton.applyStyle("display", "inline-block"); // always show gear
           actionIconButton.applyStyle("opacity", ".6"); // default to a disabled appearance
-          this.resetActions(index);
-          _.each(actions, function (action) {
-            var prerequisite = action.prerequisite;
-            name = action.name;
-            if (!prerequisite) {
-              that._actionPermissions[index][name] = true;
-              return;
-            }
-
-            if (model instanceof XM.Info && !model[prerequisite]) {
-              XT.getObjectByName(model.editableModel)[prerequisite](model, callback);
-            } else {
-              model[prerequisite](callback);
-            }
-          });
         }
       }
 
@@ -796,6 +819,7 @@ trailing:true, white:true, strict:false*/
       if (!_.isEqual(ary, this._selectedIndexes)) {
         this._selectedIndexes = ary;
         this.doSelectionChanged({
+          index: index,
           selection: this.getSelection()
         });
       }
@@ -854,13 +878,13 @@ trailing:true, white:true, strict:false*/
       var index = inEvent.index,
         model = this.getValue().models[index];
 
-      if (!this.haveAllAnswers(index)) {
+      if (!this.haveAllAnswers()) {
         return true;
       }
 
       inEvent.model = model;
       inEvent.actions = this.actions;
-      inEvent.actionPermissions = this._actionPermissions[index];
+      inEvent.actionPermissions = this._actionPermissions;
     }
   });
 
@@ -869,54 +893,64 @@ trailing:true, white:true, strict:false*/
   */
   XV.ListMenuManagerMixin = /** @lends XV.ListMenuManagerMixin# */{
     listActionSelected: function (inSender, inEvent) {
-      var that = this,
-        list = this.$.contentPanels.getActive(),
-        index = list.getSelection().lastSelected,
-        originator = inEvent.originator,
-        notifyEvent = {},
-        action = originator.action,
+      var list = this.$.contentPanels.getActive(),
+        keys = _.keys(list.getSelection().selected),
+        collection = list.getValue(),
+        action = inEvent.originator.action,
         method = action.method,
         callback = function () {
-          list.resetActions(index);
-          list.renderRow(index);
+          list.resetActions();
+          _.each(keys, function (key) {
+            list.deselect(key);
+            list.renderRow(key);
+          });
         },
-        confirmed = function (event, action) {
-          var model = event.model;
+        confirmed = function () {
+          var Klass,
+            model = collection.at(keys[0]);
           if (action.isViewMethod) {
-            that._listItemEvent.originator.parent.doActionSelected({model: model, action: action});
+            list.$.listItem.doActionSelected({
+              model: model,
+              action: action
+            });
 
           // If the list item model doesn't have the function being asked for, try the editable version
+          // Either way, loop through selected models and perform method
           } else if (model instanceof XM.Info && !model[method]) {
-            XT.getObjectByName(model.editableModel)[method](model, callback);
-
+            Klass = XT.getObjectByName(model.editableModel);
+            _.each(keys, function (key) {
+              model = collection.at(key);
+              Klass[method](model, callback);
+            });
           } else {
-            event.model[method](callback);
+            _.each(keys, function (key) {
+              model = collection.at(key);
+              model[method](callback);
+            });
           }
         };
 
-      if (originator.action.notify !== false) { // default to true if not specified
+      if (action.notify !== false) { // default to true if not specified
         // if the action requires a user OK, ask the user
-        notifyEvent.originator = this;
-        notifyEvent.type = XM.Model.QUESTION;
-        notifyEvent.message = action.notifyMessage || "_confirmAction".loc();
-        notifyEvent.callback = function (response) {
-          if (response.answer) {
-            confirmed(that._listItemEvent, action);
+        this.doNotify({
+          type: XM.Model.QUESTION,
+          message: action.notifyMessage || "_confirmAction".loc(),
+          callback: function (response) {
+            if (response.answer) {
+              confirmed();
+            }
           }
-        };
-        this.doNotify(notifyEvent);
+        });
 
       } else {
         // if the action does not require a user OK, just do the event
-        confirmed(this._listItemEvent, originator.action);
+        confirmed();
       }
       return true;
     },
     showListItemMenu: function (inSender, inEvent) {
       var menu = this.$.listItemMenu;
 
-      this._listItemEvent = inEvent;
-
       // reset the menu based on the list specific to this request
       menu.destroyClientControls();
 
index 3c6809b..5ee084b 100644 (file)
@@ -181,6 +181,7 @@ trailing:true, white:true*/
         selection = this.$.list.getSelection();
       if (this.validate()) {
         selection.deselect(index);
+        this.$.list.render();
       }
     },
     error: function (model, error) {
@@ -199,12 +200,18 @@ trailing:true, white:true*/
         model = new Klass(null, {isNew: true}),
         components = this.$.editor.getComponents(),
         scroller,
+        length,
         first;
       if (this.validate()) {
         this.$.editor.clear();
         collection.add(model);
         if (collection.comparator) { collection.sort(); }
-        this.$.list.select(collection.length - 1);
+        
+        // Exclude models marked for deletion
+        length = _.filter(collection.models, function (model) {
+          return !model.isDestroyed();
+        }).length - 1;
+        this.$.list.select(length);
 
         // Scroll to top and set focus on first available widget
         scroller = _.find(components, function (c) {
@@ -246,7 +253,7 @@ trailing:true, white:true*/
       this.unbind();
       this.$.deleteButton.setDisabled(true);
       this.$.doneButton.setDisabled(!index || this.getDisabled());
-      if (index) {
+      if (model) {
         model.on("invalid", this.error, this); // Error event binding
         this.$.editor.setValue(model);
         if (model.isNew() ||
index 080e64b..f0adbb7 100644 (file)
@@ -457,12 +457,12 @@ trailing:true*/
 
       // sending the locale information back over the wire saves a call to the db
       window.open(XT.getOrganizationPath() +
-        '/%@?details={"nameSpace":"%@","type":"%@","query":%@,"locale":%@}'
+        '/%@?details={"nameSpace":"%@","type":"%@","query":%@,"culture":%@}'
         .f(routeName,
           recordType.prefix(),
           recordType.suffix(),
           JSON.stringify(query),
-          JSON.stringify(XT.session.locale.toJSON())),
+          JSON.stringify(XT.locale.culture)),
         '_newtab');
     },
     showSortPopup: function (inSender, inEvent) {
index abd823f..103d30f 100644 (file)
@@ -18,5 +18,6 @@ enyo.depends(
   "pullout.js",
   "my_account_popup.js",
   "sort_popup.js",
-  "module_container.js"
+  "module_container.js",
+  "transaction_list.js"
 );
diff --git a/lib/enyo-x/source/views/transaction_list.js b/lib/enyo-x/source/views/transaction_list.js
new file mode 100644 (file)
index 0000000..109add9
--- /dev/null
@@ -0,0 +1,270 @@
+/*jshint bitwise:true, indent:2, curly:true, eqeqeq:true, immed:true,
+latedef:true, newcap:true, noarg:true, regexp:true, undef:true,
+trailing:true, white:true, strict:false*/
+/*global XT:true, XM:true, XV:true, _:true, enyo:true */
+
+(function () {
+
+  /**
+    Expected to a have a parameter widget that contains an order and
+    a transaction date.
+
+    @name XV.TransactionList
+    @extends XV.SearchContainer
+   */
+  var transactionList =  /** @lends XV.TransactionList# */ {
+    name: "XV.TransactionList",
+    kind: "Panels",
+    classes: "app enyo-unselectable",
+    arrangerKind: "CollapsingArranger",
+    published: {
+      prerequisite: "",
+      notifyMessage: "",
+      list: null,
+      actions: null,
+      transactionDate: null,
+      model: null
+    },
+    events: {
+      onPrevious: "",
+      onWorkspace: ""
+    },
+    handlers: {
+      onListItemMenuTap: "showListItemMenu",
+      onParameterChange: "requery",
+      onProcessingChanged: "processingChanged",
+      onSelectionChanged: "selectionChanged"
+    },
+    init: false,
+    components: [
+      {name: "parameterPanel", kind: "FittableRows", classes: "left",
+        components: [
+        {kind: "onyx.Toolbar", classes: "onyx-menu-toolbar", components: [
+          {kind: "onyx.Button", name: "backButton", content: "_back".loc(), ontap: "close"},
+          {kind: "onyx.MenuDecorator", style: "margin: 0;",
+            onSelect: "actionSelected", components: [
+            {kind: "XV.IconButton", src: "/assets/menu-icon-gear.png",
+              content: "_actions".loc(), name: "actionButton"},
+            {kind: "onyx.Menu", name: "actionMenu"}
+          ]}
+        ]},
+        {kind: "Scroller", name: "parameterScroller"}
+      ]},
+      {name: "listPanel", kind: "FittableRows", components: [
+        // the onyx-menu-toolbar class keeps the popups from being hidden
+        {kind: "onyx.MoreToolbar", name: "contentToolbar",
+          classes: "onyx-menu-toolbar", movedClass: "xv-toolbar-moved", components: [
+          {kind: "onyx.Grabber", classes: "left-float"},
+          {name: "rightLabel", content: "_search".loc(), classes: "left-float"},
+          {name: "space", fit: true},
+          {kind: "onyx.Button", name: "printButton", showing: false,
+            content: "_print".loc(), onclick: "print"},
+          {kind: "onyx.Button", name: "refreshButton", disabled: false,
+            content: "_refresh".loc(), onclick: "requery"},
+          {kind: "onyx.Button", name: "postButton",
+            disabled: true, classes: "save", showing: false,
+            content: "_post".loc(), onclick: "post"},
+          {name: "listItemMenu", kind: "onyx.Menu", floating: true,
+            onSelect: "listActionSelected", maxHeight: 500}
+        ]},
+        {name: "contentPanels", kind: "Panels", margin: 0, fit: true, draggable: false,
+          panelCount: 0},
+        {kind: "onyx.Popup", name: "spinnerPopup", centered: true,
+            modal: true, floating: true, scrim: true,
+            onHide: "popupHidden", components: [
+          {kind: "onyx.Spinner"},
+          {name: "spinnerMessage", content: "_processing".loc() + "..."}
+        ]}
+      ]}
+    ],
+    actionSelected: function (inSender, inEvent) {
+      var action = inEvent.originator.action,
+        method = action.method || action.name;
+
+      this[method](inSender, inEvent);
+    },
+    close: function () {
+      this.doPrevious();
+    },
+    buildMenu: function () {
+      var actionMenu = this.$.actionMenu,
+        actions = this.getActions().slice(0),
+        that = this;
+
+      // reset the menu
+      actionMenu.destroyClientControls();
+
+      // then add whatever actions are applicable
+      _.each(actions, function (action) {
+        var name = action.name,
+          prerequisite = action.prerequisite,
+          isDisabled = prerequisite ? !that[prerequisite]() : false;
+        actionMenu.createComponent({
+          name: name,
+          kind: XV.MenuItem,
+          content: action.label || ("_" + name).loc(),
+          action: action,
+          disabled: isDisabled
+        });
+
+      });
+      actionMenu.render();
+      this.$.actionButton.setShowing(actions.length);
+    },
+    create: function () {
+      this.inherited(arguments);
+      var disabled = !XT.session.privileges.get("AlterTransactionDates"),
+        parameterWidget;
+      this.setList({list: this.getList()});
+      parameterWidget = this.$.parameterWidget;
+      parameterWidget.$.transactionDate.$.input.setDisabled(disabled);
+      if (!this.getActions()) {
+        this.setActions([]);
+      }
+      this.buildMenu();
+    },
+    fetch: function (options) {
+      if (!this.init) { return; }
+      options = options ? _.clone(options) : {};
+      var list = this.$.list,
+        query,
+        parameterWidget,
+        parameters;
+      if (!list) { return; }
+      query = list.getQuery() || {};
+      parameterWidget = this.$.parameterWidget;
+      parameters = parameterWidget && parameterWidget.getParameters ?
+        parameterWidget.getParameters() : [];
+      options.showMore = _.isBoolean(options.showMore) ?
+        options.showMore : false;
+
+      // Build conditions
+      if (parameters.length) {
+        query.parameters = parameters;
+      } else {
+        delete query.parameters;
+      }
+      list.setQuery(query);
+      list.fetch(options);
+    },
+    /**
+      Capture order changed and transaction date changed events.
+      Depends on a very specific implementation of parameter widget
+      that includes `order` and `transactionDate` parameters.
+    */
+    parameterChanged: function (inSender, inEvent) {
+      var originator = inEvent ? inEvent.originator : false,
+        name = originator ? originator.name : false,
+        that = this,
+        options,
+        value;
+
+      if (name === "transactionDate") {
+        value = originator.$.input.getValue();
+        value = XT.date.applyTimezoneOffset(value, true);
+        value = XT.date.toMidnight(value);
+        this.setTransactionDate(value);
+        this.buildMenu();
+        return;
+      } else if (name === "order") {
+        value = originator.getParameter().value;
+        this.setModel(value);
+        this.buildMenu();
+      } else if (name === "shipment") {
+        return;
+      }
+
+      options = {
+        success: function () {
+          that.selectionChanged();
+        }
+      };
+      this.fetch(options);
+    },
+    popupHidden: function (inSender, inEvent) {
+      if (!this._popupDone) {
+        inEvent.originator.show();
+      }
+    },
+    processingChanged: function (inSender, inEvent) {
+      if (inEvent.isProcessing) {
+        this.spinnerShow();
+      } else {
+        this.requery();
+        this.spinnerHide();
+      }
+    },
+    /**
+      Overload: Piggy back on existing handler for `onParameterChanged event`
+      by forwarding this requery to `parameterChanged`.
+    */
+    requery: function (inSender, inEvent) {
+      this.parameterChanged(inSender, inEvent);
+      return true;
+    },
+    /**
+      Whenever a user makes a selection, rebuild the menu
+      and set the transaction date on the selected models
+      to match what has been selected here.
+    */
+    selectionChanged: function () {
+      this.transactionDateChanged();
+      this.buildMenu();
+    },
+    /**
+      @param {Object} Options
+      @param {String} [options.list] Class name
+    */
+    setList: function (options) {
+      var component,
+      list = options.list;
+
+      component = this.createComponent({
+        name: "list",
+        container: this.$.contentPanels,
+        kind: list,
+        fit: true
+      });
+      this.$.rightLabel.setContent(component.label);
+      if (component) {
+        this.createComponent({
+          name: "parameterWidget",
+          classes: "xv-groupbox xv-parameter",
+          showSaveFilter: false,
+          showLayout: false,
+          defaultParameters: null,
+          container: this.$.parameterScroller,
+          kind: component.getParameterWidget(),
+          memoizeEnabled: false,
+          fit: true
+        });
+      }
+
+      this.init = true;
+      this.render();
+    },
+    spinnerHide: function () {
+      this._popupDone = true;
+      this.$.spinnerPopup.hide();
+    },
+    spinnerShow: function () {
+      this._popupDone = false;
+      this.$.spinnerPopup.show();
+    },
+    transactionDateChanged: function () {
+      var transDate = this.getTransactionDate(),
+        collection = this.$.list.getValue(),
+        i;
+
+      // Update the transaction dates on all models to match
+      // What has been selected
+      for (i = 0; i < collection.length; i++) {
+        collection.at(i).transactionDate = transDate;
+      }
+    }
+  };
+
+  enyo.mixin(transactionList, XV.ListMenuManagerMixin);
+  enyo.kind(transactionList);
+
+}());
index 043cd0e..6753fae 100644 (file)
@@ -822,7 +822,7 @@ trailing:true, white:true, strict: false*/
           type: reportModelName.suffix(),
           id: model.id,
           name: reportName,
-          locale: XT.session.locale.toJSON()
+          culture: XT.locale.culture
         };
 
       // sending the locale information back over the wire saves a call to the db
index e206810..1bd1470 100644 (file)
@@ -111,7 +111,7 @@ regexp:true, undef:true, trailing:true, white:true */
         changed,
         attrs = [];
       if (_.isString(key)) {
-        attrs = [value ? value.get(key) : ""];
+        attrs = value ? value.get(key) : "";
         changed = inputValue !== attrs;
       } else {
         // Must be array. Handle it.
diff --git a/lib/module b/lib/module
new file mode 120000 (symlink)
index 0000000..ea095f3
--- /dev/null
@@ -0,0 +1 @@
+../node_modules/
\ No newline at end of file
index 7a62aea..aad49aa 100644 (file)
@@ -37,6 +37,7 @@
     "xt/functions/raise_exception.sql",
     "xt/functions/retrieve_record.sql",
     "xt/functions/schema.sql",
+    "xt/functions/set_dictionary.sql",
     "xt/functions/show_search_path.sql",
     "xt/functions/text_gt_date.sql",
     "xt/functions/text_lt_date.sql",
     "xt/operators/not_any_text.sql",
     "xt/operators/text_gt_date.sql",
     "xt/operators/text_lt_date.sql",
+    "xt/tables/ext.sql",
+    "xt/tables/extdep.sql",
+    "xt/tables/grpext.sql",
+    "xt/tables/usrext.sql",
+    "xt/tables/dict.sql",
     "xt/tables/js.sql",
     "xt/tables/lock.sql",
     "xt/tables/orm.sql",
index 63b1142..1e51c4f 100644 (file)
@@ -287,6 +287,207 @@ create or replace function xt.js_init(debug boolean DEFAULT false) returns void
     }
   }
 
+
+  /*
+
+    Start supporting functions for XT.executeFunction.
+
+  */
+
+  var errorMaps = [
+    {"fromFunction":"closeAccountingYearPeriod","fromId":-1,"toFunction":"closeAccountingPeriod","toId":-1},
+    {"fromFunction":"closeAccountingYearPeriod","fromId":-2,"toFunction":"closeAccountingPeriod","toId":-2},
+    {"fromFunction":"closeAccountingYearPeriod","fromId":-3,"toFunction":"closeAccountingPeriod","toId":-3},
+    {"fromFunction":"closeAccountingYearPeriod","fromId":-4,"toFunction":"closeAccountingPeriod","toId":-4},
+    {"fromFunction":"closeAccountingYearPeriod","fromId":-5,"toFunction":"closeAccountingPeriod","toId":-5},
+    {"fromFunction":"closeAccountingYearPeriod","fromId":-6,"toFunction":"closeAccountingPeriod","toId":-6},
+    {"fromFunction":"convertCustomerToProspect","fromId":-1,"toFunction":"deleteCustomer","toId":-1},
+    {"fromFunction":"convertCustomerToProspect","fromId":-2,"toFunction":"deleteCustomer","toId":-2},
+    {"fromFunction":"convertCustomerToProspect","fromId":-3,"toFunction":"deleteCustomer","toId":-3},
+    {"fromFunction":"convertCustomerToProspect","fromId":-4,"toFunction":"deleteCustomer","toId":-4},
+    {"fromFunction":"convertCustomerToProspect","fromId":-5,"toFunction":"deleteCustomer","toId":-5},
+    {"fromFunction":"convertProspectToCustomer","fromId":-1,"toFunction":"deleteProspect","toId":-1},
+    {"fromFunction":"copyPrj","fromId":-10,"toFunction":"saveAlarm","toId":-10},
+    {"fromFunction":"createARDebitMemo","fromId":-1,"toFunction":"createARCreditMemo","toId":-1},
+    {"fromFunction":"deleteOpenRecurringItems","fromId":-1,"toFunction":"deleteIncident","toId":-1},
+    {"fromFunction":"deleteOpenRecurringItems","fromId":-2,"toFunction":"deleteIncident","toId":-2},
+    {"fromFunction":"enablePackage","fromId":-1,"toFunction":"disablePackage","toId":-1},
+    {"fromFunction":"enablePackage","fromId":-2,"toFunction":"disablePackage","toId":-2},
+    {"fromFunction":"issueAllBalanceToShipping","fromId":-1,"toFunction":"issueToShipping","toId":-1},
+    {"fromFunction":"issueAllBalanceToShipping","fromId":-10,"toFunction":"issueToShipping","toId":-10},
+    {"fromFunction":"issueAllBalanceToShipping","fromId":-12,"toFunction":"issueToShipping","toId":-12},
+    {"fromFunction":"issueAllBalanceToShipping","fromId":-13,"toFunction":"issueToShipping","toId":-13},
+    {"fromFunction":"issueAllBalanceToShipping","fromId":-14,"toFunction":"issueToShipping","toId":-14},
+    {"fromFunction":"issueAllBalanceToShipping","fromId":-15,"toFunction":"issueToShipping","toId":-15},
+    {"fromFunction":"issueAllBalanceToShipping","fromId":-20,"toFunction":"issueToShipping","toId":-20},
+    {"fromFunction":"issueLineBalanceToShipping","fromId":-1,"toFunction":"issueToShipping","toId":-1},
+    {"fromFunction":"issueLineBalanceToShipping","fromId":-10,"toFunction":"issueToShipping","toId":-10},
+    {"fromFunction":"issueLineBalanceToShipping","fromId":-12,"toFunction":"issueToShipping","toId":-12},
+    {"fromFunction":"issueLineBalanceToShipping","fromId":-13,"toFunction":"issueToShipping","toId":-13},
+    {"fromFunction":"issueLineBalanceToShipping","fromId":-14,"toFunction":"issueToShipping","toId":-14},
+    {"fromFunction":"issueLineBalanceToShipping","fromId":-15,"toFunction":"issueToShipping","toId":-15},
+    {"fromFunction":"issueLineBalanceToShipping","fromId":-20,"toFunction":"issueToShipping","toId":-20},
+    {"fromFunction":"issueToShipping","fromId":-1,"toFunction":"postInvTrans","toId":-1},
+    {"fromFunction":"openAccountingYearPeriod","fromId":-1,"toFunction":"openAccountingPeriod","toId":-1},
+    {"fromFunction":"postCCCashReceipt","fromId":-1,"toFunction":"createARCreditMemo","toId":-1},
+    {"fromFunction":"postCCCashReceipt","fromId":-10,"toFunction":"postCCcredit","toId":-1},
+    {"fromFunction":"postCountTagLocation","fromId":-1,"toFunction":"postCountTag","toId":-1},
+    {"fromFunction":"postCountTagLocation","fromId":-2,"toFunction":"postCountTag","toId":-2},
+    {"fromFunction":"postCountTagLocation","fromId":-3,"toFunction":"postCountTag","toId":-3},
+    {"fromFunction":"postCountTagLocation","fromId":-4,"toFunction":"postCountTag","toId":-4},
+    {"fromFunction":"postGLSeriesNoSumm","fromId":-4,"toFunction":"postGLSeries","toId":-4},
+    {"fromFunction":"postGLSeriesNoSumm","fromId":-5,"toFunction":"postGLSeries","toId":-5},
+    {"fromFunction":"postInvoice","fromId":-1,"toFunction":"insertIntoGLSeries","toId":-1},
+    {"fromFunction":"postInvoice","fromId":-4,"toFunction":"insertIntoGLSeries","toId":-4},
+    {"fromFunction":"postInvoice","fromId":-5,"toFunction":"postGLSeries","toId":-5},
+    {"fromFunction":"postInvTrans","fromId":-3,"toFunction":"insertGLTransaction","toId":-3},
+    {"fromFunction":"postInvTrans","fromId":-4,"toFunction":"insertGLTransaction","toId":-4},
+    {"fromFunction":"postPoReceipt","fromId":-1,"toFunction":"postReceipt","toId":-1},
+    {"fromFunction":"postPoReceipt","fromId":-2,"toFunction":"postReceipt","toId":-2},
+    {"fromFunction":"postPoReceipt","fromId":-3,"toFunction":"postReceipt","toId":-3},
+    {"fromFunction":"postPoReceipt","fromId":-4,"toFunction":"postReceipt","toId":-4},
+    {"fromFunction":"postPoReceipt","fromId":-10,"toFunction":"postReceipt","toId":-10},
+    {"fromFunction":"postPoReceipt","fromId":-11,"toFunction":"postReceipt","toId":-11},
+    {"fromFunction":"postPoReceipt","fromId":-12,"toFunction":"postReceipt","toId":-12},
+    {"fromFunction":"postReceipt","fromId":-1,"toFunction":"postInvTrans","toId":-1},
+    {"fromFunction":"postReceipt","fromId":-2,"toFunction":"postInvTrans","toId":-2},
+    {"fromFunction":"postReceipt","fromId":-3,"toFunction":"insertGLTransaction","toId":-3},
+    {"fromFunction":"postReceipt","fromId":-4,"toFunction":"insertGLTransaction","toId":-4},
+    {"fromFunction":"postReceipt","fromId":-20,"toFunction":"issueToShipping","toId":-1},
+    {"fromFunction":"postReceipt","fromId":-21,"toFunction":"issueToShipping","toId":-10},
+    {"fromFunction":"postReceipt","fromId":-22,"toFunction":"issueToShipping","toId":-12},
+    {"fromFunction":"postReceipt","fromId":-23,"toFunction":"issueToShipping","toId":-13},
+    {"fromFunction":"postReceipt","fromId":-24,"toFunction":"issueToShipping","toId":-14},
+    {"fromFunction":"postReceipt","fromId":-25,"toFunction":"issueToShipping","toId":-15},
+    {"fromFunction":"postReceipt","fromId":-26,"toFunction":"shipShipment","toId":-12},
+    {"fromFunction":"postReceipt","fromId":-27,"toFunction":"shipShipment","toId":-13},
+    {"fromFunction":"postReceipt","fromId":-28,"toFunction":"shipShipment","toId":-14},
+    {"fromFunction":"postReceipt","fromId":-29,"toFunction":"shipShipment","toId":-15},
+    {"fromFunction":"postReceipt","fromId":-30,"toFunction":"shipShipment","toId":-1},
+    {"fromFunction":"postReceipt","fromId":-31,"toFunction":"shipShipment","toId":-3},
+    {"fromFunction":"postReceipt","fromId":-32,"toFunction":"shipShipment","toId":-4},
+    {"fromFunction":"postReceipt","fromId":-33,"toFunction":"shipShipment","toId":-5},
+    {"fromFunction":"postReceipt","fromId":-34,"toFunction":"shipShipment","toId":-6},
+    {"fromFunction":"postReceipt","fromId":-35,"toFunction":"shipShipment","toId":-8},
+    {"fromFunction":"postReceipt","fromId":-36,"toFunction":"shipShipment","toId":-50},
+    {"fromFunction":"postReceipt","fromId":-37,"toFunction":"shipShipment","toId":-99},
+    {"fromFunction":"postPoReceipts","fromId":-1,"toFunction":"postPoReceipt","toId":-1},
+    {"fromFunction":"postPoReceipts","fromId":-3,"toFunction":"postPoReceipt","toId":-3},
+    {"fromFunction":"postPoReceipts","fromId":-4,"toFunction":"postPoReceipt","toId":-4},
+    {"fromFunction":"postPoReceipts","fromId":-10,"toFunction":"postPoReceipt","toId":-10},
+    {"fromFunction":"postPoReceipts","fromId":-12,"toFunction":"postPoReceipt","toId":-12},
+    {"fromFunction":"postPoReturns","fromId":-1,"toFunction":"postInvTrans","toId":-1},
+    {"fromFunction":"postPoReturns","fromId":-3,"toFunction":"insertGLTransaction","toId":-3},
+    {"fromFunction":"postPoReturns","fromId":-4,"toFunction":"insertGLTransaction","toId":-4},
+    {"fromFunction":"replaceAllVoidedChecks","fromId":-1,"toFunction":"replaceVoidedCheck","toId":-1},
+    {"fromFunction":"returnItemShipments","fromId":-1,"toFunction":"postInvTrans","toId":-1},
+    {"fromFunction":"shipShipment","fromId":-1,"toFunction":"postInvTrans","toId":-1},
+    {"fromFunction":"shipShipment","fromId":-3,"toFunction":"insertGLTransaction","toId":-3},
+    {"fromFunction":"shipShipment","fromId":-4,"toFunction":"insertGLTransaction","toId":-4},
+    {"fromFunction":"splitRecurrence","fromId":-10,"toFunction":"createRecurringItems","toId":-10},
+    {"fromFunction":"sufficientInventoryToShipOrder","fromId":-11,"toFunction":"sufficientInventoryToShipItem","toId":-11},
+    {"fromFunction":"voidCreditMemo","fromId":-1,"toFunction":"insertIntoGLSeries","toId":-1},
+    {"fromFunction":"voidCreditMemo","fromId":-4,"toFunction":"insertIntoGLSeries","toId":-4},
+    {"fromFunction":"voidCreditMemo","fromId":-5,"toFunction":"postGLSeries","toId":-5},
+    {"fromFunction":"voidInvoice","fromId":-1,"toFunction":"insertIntoGLSeries","toId":-1},
+    {"fromFunction":"voidInvoice","fromId":-4,"toFunction":"insertIntoGLSeries","toId":-4},
+    {"fromFunction":"voidInvoice","fromId":-5,"toFunction":"postGLSeries","toId":-5},
+    {"fromFunction":"reserveSoLineBalance","fromId":-1,"toFunction":"reserveSoLineQty","toId":-1},
+    {"fromFunction":"reserveSoLineBalance","fromId":-2,"toFunction":"reserveSoLineQty","toId":-2},
+    {"fromFunction":"reserveSoLineBalance","fromId":-3,"toFunction":"reserveSoLineQty","toId":-3},
+    {"fromFunction":"woClockIn","fromId":-1,"toFunction":"explodeWo","toId":-1},
+    {"fromFunction":"woClockIn","fromId":-2,"toFunction":"explodeWo","toId":-2},
+    {"fromFunction":"woClockIn","fromId":-3,"toFunction":"explodeWo","toId":-3},
+  ];
+
+  var getUserCulture = function() {
+    var sql = "select lang_abbr2 || '_' || country_abbr as \"culture\" " +
+      "from locale " +
+      "join usr on usr_locale_id = locale_id " +
+      "left join lang on locale_lang_id = lang_id " +
+      "left join country on locale_country_id = country_id " + 
+      "where usr_username = $1;";
+    return plv8.execute(sql, [XT.username])[0].culture;
+  };
+
+  var errorToString = function(functionName, errorCode) {
+    var culture,
+      dictSql,
+      dictResult,
+      errorMap = [true],
+      stringsKey;
+
+    /* Trace the error code down to the underlying error based on our errorMap */
+    while (errorMap.length) {
+      errorMap = errorMaps.filter(function (errorMap) {
+        return errorMap.fromFunction === functionName && errorMap.fromId === errorCode;
+      });
+      if (errorMap.length) {
+        functionName = errorMap[0].toFunction;
+        errorCode = errorMap[0].toId;
+      }
+    }
+
+    /* cache the strings in JSON in XT.dbStrings */
+    XT.dbStrings = XT.dbStrings || {};
+    culture = getUserCulture();
+    if(!XT.dbStrings[culture]) {
+      /* need to load in the strings for this culture into the database */
+      dictSql = "select dict_strings from xt.dict where dict_is_database = true and dict_language_name = $1;";
+      dictResult = plv8.execute(dictSql, [culture]);
+      XT.dbStrings[culture] = JSON.parse(dictResult[0].dict_strings.toLowerCase());
+    }
+    /* this is the convention under which the translations are stored */
+    stringsKey = "_xtdb_" + functionName + (-1 * errorCode);
+
+    var returnVal = XT.dbStrings[culture][stringsKey.toLowerCase()];
+    return returnVal || "Undocumented error: " + functionName + " " + errorCode;
+  };
+
+  /**
+    Wrapper for plv8.execute() for calling postgres functions. 
+    If the postgres function returns an error in the form of
+    a negative integer, this function finds the appropriate 
+    translation and throws that error.
+
+    NOTE that you should not pass unsanitized user input into
+    the functionName or the casts variables.
+
+    @param {String} functionName.
+    @param {Array} params
+    @param {Array} casts. Optional. Array of strings
+   */
+  XT.executeFunction = function (functionName, params, casts) {
+    var cast,
+      errorString,
+      i,
+      param;
+
+    var sql = "select " + functionName + "(";
+    for (i = 0; i < params.length; i++) {
+      param = params[i];
+      cast = casts && casts[i];
+      if (i > 0) {
+        sql = sql + ",";
+      }
+      sql = sql + "$" + (i + 1);
+      if(cast) {
+        sql = sql + "::" + cast;
+      }
+
+    }
+    sql = sql + ") as result";
+
+    var result = plv8.execute(sql, params)[0].result;
+    if(typeof result === 'number' && result < 0) {
+      errorString = errorToString(functionName, result);
+      throw new handleError(errorString, 424); 
+    } else {
+      return result;
+    }
+  };
+
+
   /**
    * Wrap PostgreSQL's format() function to format SQL Injection safe queires or general strings.
    * http://www.postgresql.org/docs/9.1/interactive/functions-string.html#FUNCTIONS-STRING-OTHER
diff --git a/lib/orm/source/xt/functions/set_dictionary.sql b/lib/orm/source/xt/functions/set_dictionary.sql
new file mode 100644 (file)
index 0000000..d9f9aac
--- /dev/null
@@ -0,0 +1,42 @@
+drop function if exists xt.set_dictionary(text, text, text);
+
+create or replace function xt.set_dictionary(strings text, context text, language text) 
+    returns boolean volatile as $$
+
+  var isDatabase = (context === '_database_') ? true : false,
+    isFramework = (context === '_framework_') ? true : false,
+    sqlExtension = "select ext_id from xt.ext where ext_name = $1",
+    extensions,
+    extensionId = null,
+    sqlCount = "select count(*) as count from xt.dict where dict_language_name = $1 " +
+      "and (dict_ext_id = $2 or (dict_ext_id is null and $2 is null)) and dict_is_database = $3 " +
+      "and dict_is_framework = $4;",
+    count,
+    /* TODO: set user and date */
+    sqlUpdate = "update xt.dict set dict_strings = $1 " +
+      "where dict_language_name = $2 " +
+      "and (dict_ext_id = $3 or (dict_ext_id is null and $3 is null)) " +
+      "and dict_is_database = $4 and dict_is_framework = $5;",
+    sqlInsert = "insert into xt.dict " +
+      "(dict_strings, dict_language_name, dict_ext_id, dict_is_database, dict_is_framework) " +
+      " values ($1, $2, $3, $4, $5);";
+
+  /* determine the extension ID, or null if it's core */
+  if (context !== '_database_' && context !== '_core_' && context !== '_framework_') {
+    extensions = plv8.execute(sqlExtension, [context]);
+    if (extensions.length === 0) {
+      /* The extension in the translation file is not registered in the db. 
+        Do not bother to import it */
+      return;
+    }
+    extensionId = extensions[0].ext_id;
+  }
+
+  count = plv8.execute(sqlCount, [language, extensionId, isDatabase, isFramework])[0].count;
+  if(count > 0) {
+    plv8.execute(sqlUpdate, [strings, language, extensionId, isDatabase, isFramework]);
+  } else {
+    plv8.execute(sqlInsert, [strings, language, extensionId, isDatabase, isFramework]);
+  }
+
+$$ language plv8;
index aed953d..fdd63cc 100644 (file)
@@ -32,7 +32,20 @@ select xt.install_js('XT','Session','xtuple', $$
             + 'left join lang on locale_lang_id = lang_id '
             + 'left join country on locale_country_id = country_id '
             + 'where usr_username = $1 ',
-    rec = plv8.execute(sql, [ XT.username ])[0];
+      rec = plv8.execute(sql, [ XT.username ])[0],
+      /* only serve the translations for pertinent extensions */
+      dictionarySql = "select dict_strings from xt.dict where dict_id in ( " +
+        "select dict_id " +
+        "from xt.dict " +
+        "left join xt.ext on dict_ext_id = ext_id " +
+        "left join xt.usrext on ext_id = usrext_ext_id " +
+        "left join xt.grpext on ext_id = grpext_id " +
+        "left join usrgrp on grpext_grp_id = usrgrp_grp_id " +
+        "where dict_language_name = $1 " +
+        "and dict_is_database = false " +
+        "and (ext_id is null or usrgrp_username = $2 or usrext_usr_username = $2) " +
+        ");",
+      strings;
 
     /* determine culture */
     var culture = 'en';
@@ -40,12 +53,22 @@ select xt.install_js('XT','Session','xtuple', $$
       /* no result. The user probably does not exist */
       throw "No result for locale. Username probably does not exist in the instance database";
     } else if (rec.language && rec.country) {
-      culture = rec.language + '-' + rec.country;
+      culture = rec.language + '_' + rec.country;
     } else if (rec.language) {
       culture = rec.language;
     }
     rec.culture = culture;
 
+
+    /* might as well request the translations in here too */
+    strings = plv8.execute(dictionarySql, [culture, XT.username]);
+    if(strings.length === 0) {
+      strings = plv8.execute(dictionarySql, ["en_US"]);
+    }
+    rec.strings = strings.map(function (row) {
+      return JSON.parse(row.dict_strings);
+    });
+
     return JSON.stringify(rec);
   }
 
@@ -97,7 +120,15 @@ select xt.install_js('XT','Session','xtuple', $$
     @returns {Object}
   */
   XT.Session.preferences = function() {
-    var sql = "SELECT * FROM xt.userpref WHERE userpref_usr_username = $1",
+    var sql = "select * from xt.userpref where userpref_usr_username = $1 " +
+              "and userpref_name != 'PreferredWarehouse' " +
+              "union " + 
+              /* Sorry, we've just got to share this one... */
+              "select usrpref_id, usrpref_username, usrpref_name, warehous_code " +
+              "from usrpref " +
+              " join whsinfo on usrpref_value = warehous_id::text " +
+              "where usrpref_username = $1 "
+              "and usrpref_name = 'PreferredWarehouse';";
       result = plv8.execute(sql, [XT.username]),
       resultObj = {};
 
@@ -122,20 +153,34 @@ select xt.install_js('XT','Session','xtuple', $$
     /* Compose our commit settings by applying the patch to what we already have */
     patches.map(function (patch) {
       var sql,
+        name = patch.path.substring(1),
         updateSql = "UPDATE xt.userpref SET userpref_value = $1 WHERE userpref_usr_username = $2 AND userpref_name = $3;",
-        insertSql = "INSERT INTO xt.userpref (userpref_value, userpref_usr_username, userpref_name) VALUES ($1, $2, $3);";
-
-      plv8.elog(NOTICE, "patch", patch.op, JSON.stringify(patch));
-      if (patch.op === 'add') {
-        sql = insertSql;
-      } else if(patch.op === 'replace') {
-        sql = updateSql;
+        insertSql = "INSERT INTO xt.userpref (userpref_value, userpref_usr_username, userpref_name) VALUES ($1, $2, $3);",
+        /* Handle the ugly exception */
+        updateSqlSite = "UPDATE usrpref SET usrpref_value = (select warehous_id from whsinfo where warehous_code = $1) WHERE usrpref_username = $2 AND usrpref_name = $3;",
+        insertSqlSite = "INSERT INTO usrpref (usrpref_value, usrpref_username, usrpref_name) VALUES ($1, $2, $3);";
+
+      if (name !== "PreferredWarehouse") {
+        if (patch.op === 'add') {
+          sql = insertSql;
+        } else if(patch.op === 'replace') {
+          sql = updateSql;
+        } else {
+          /* no other operation is supported at the moment */
+          return;
+        }
       } else {
-        /* no other operation is supported at the moment */
-        return;
+        if (patch.op === 'add') {
+          sql = insertSqlSite;
+        } else if(patch.op === 'replace') {
+          sql = updateSqlSite;
+        } else {
+          /* no other operation is supported at the moment */
+          return;
+        }
       }
 
-      plv8.execute(sql, [patch.value, XT.username, patch.path.substring(1)]);
+      plv8.execute(sql, [patch.value, XT.username, name]);
     });
     return true;
   }
diff --git a/lib/orm/source/xt/tables/dict.sql b/lib/orm/source/xt/tables/dict.sql
new file mode 100644 (file)
index 0000000..763fef9
--- /dev/null
@@ -0,0 +1,13 @@
+-- table definition
+
+select xt.create_table('dict');
+select xt.add_column('dict','dict_id', 'serial', 'primary key');
+select xt.add_column('dict','dict_language_name', 'text');
+select xt.add_column('dict','dict_ext_id', 'integer', 'references xt.ext (ext_id)');
+select xt.add_column('dict','dict_is_database', 'boolean');
+select xt.add_column('dict','dict_is_framework', 'boolean');
+select xt.add_column('dict','dict_usr_username', 'text');
+select xt.add_column('dict','dict_date', 'date');
+select xt.add_column('dict','dict_strings', 'text');
+
+comment on table xt.dict is 'Dictionary for linguist';
index 216529b..254a343 100644 (file)
@@ -128,17 +128,30 @@ white:true*/
         NaN
       );
     },
+
+    /**
+      Returns date passed at midnight.
+
+      @params {Date} Date
+      @returns {Date}
+    */
+    toMidnight: function (d) {
+      d.setHours(0);
+      d.setMinutes(0);
+      d.setSeconds(0);
+      d.setMilliseconds(0);
+      return d;
+    },
+
     /**
       Returns today's date at midnight.
       returns {Date}
     */
     today: function () {
       var today = new Date();
-      today.setHours(0);
-      today.setMinutes(0);
-      today.setSeconds(0);
-      return today;
+      return this.toMidnight(today);
     },
+
     /**
       Returns a date object with a time of 2100-01-01T00:00:00.000Z.
 
diff --git a/lib/tools/source/en/package.js b/lib/tools/source/en/package.js
deleted file mode 100644 (file)
index 1fd55d7..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-var depends = (typeof enyo !== 'undefined') ? enyo.depends : X.depends;
-depends(
-  "strings.js"
-);
index 402e982..97c9b77 100644 (file)
@@ -1,18 +1,18 @@
-// ==========================================================================
-// Project:   XT` Strings
-// Copyright: Â©2011 OpenMFG LLC, d/b/a xTuple
-// ==========================================================================
-/*globals XT */
+/*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 XT:true */
 
 // Place strings you want to localize here.  In your app, use the key and
 // localize it using "key string".loc().  HINT: For your key names, use the
 // english string with an underscore in front.  This way you can still see
 // how your UI will look and you'll notice right away when something needs a
 // localized string added to this file!
-//
 
-XT.stringsFor("en_US", {
+(function () {
+  "use strict";
 
+  var lang = XT.stringsFor("en_US", {
     // Core Errors
     "_attributeNotInSchema": "'{attr}' does not exist in the schema.",
     "_attributeIsRequired": "{attr} is required.",
@@ -49,8 +49,9 @@ XT.stringsFor("en_US", {
        "_recursiveParentDisallowed": "Record is not allowed to reference itself as the parent.",
     "_stockedMustReorder": "You must set a reorder level for a stocked item.",
     "_totalMustBePositive": "The total must be a positive value."
+  });
 
-});
-
-// TODO: Temporary until we get loaded from datasource
-XT.locale.setLanguage("en_US");
+  if (typeof exports !== 'undefined') {
+    exports.language = lang;
+  }
+}());
index 9c13e90..18e3dce 100644 (file)
@@ -37,13 +37,8 @@ XT.String = {
     Localize the string.
   */
   loc: function (str) {
-    if (!XT.locale) {
-      XT.warn("XT.String.loc(): attempt to localize string but no locale set");
-      return str;
-    }
-
     var args = XT.$A(arguments);
-    var localized = XT.locale.loc(str);
+    var localized = XT.localizeString(str);
 
     args.shift();
 
index ca8d8af..291149c 100644 (file)
@@ -18,8 +18,6 @@ _.extend(XT, /** @lends XT.Foundation# */{
   // This will be updated from the server when we start our session
   debugging: false,
 
-  K: function (){},
-
   _date: new Date(),
 
   toReadableTimestamp: function (millis) {
index e62fb4d..66bcaf7 100644 (file)
@@ -1,66 +1,17 @@
 /*jshint 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 XT:true, Backbone:true, _:true, console:true, setTimeout: true */
+/*global XT:true, Backbone:true, _:true, exports:true  */
 
 (function () {
   "use strict";
 
-  //...........................................
-  // THE FOLLOWING IMPLEMENTATION IS VERY TEMPORARY
-  // FOR DEVELOPMENT PURPOSES ONLY!
-  XT.locale = {
-    hasStrings: false,
-    strings: {},
-    lang: "",
-
-    getHasStrings: function () {
-      return this.hasStrings;
-    },
-
-    getLang: function () {
-      return this.lang;
-    },
-
-    getStrings: function () {
-      return this.strings;
-    },
-
-    setHasStrings: function (value) {
-      this.hasStrings = value;
-      return this;
-    },
-
-    setLang: function (value) {
-      this.lang = value;
-      return this;
-    },
-
-    setStrings: function (value) {
-      this.strings = value;
-      return this;
-    },
-
-    setLanguage: function (language) {
-      if (typeof language === 'string') {
-        language = XT.getLanguage(language) || {};
-      }
-      this.setLang(language.lang || "en");
-      this.setStrings(language.strings || {});
-    },
-
-    stringsChanged: function () {
-      var strings = this.getStrings();
-      if (strings && strings instanceof Object && Object.keys(strings).length > 0) {
-        this.setHasStrings(true);
-      } else { this.error("something is amiss"); }
-    },
-
-    loc: function (str) {
-      var strings = this.getStrings();
-      return strings[str] || str.toString();
+  XT.localizeString = function (str) {
+    if (!XT.locale || !XT.getLanguage(XT.locale.culture)) {
+      return str.toString();
     }
-
+    var strings = XT.getLanguage(XT.locale.culture).strings;
+    return strings[str] || str.toString();
   };
 
   XT.getLanguage = function (lang) {
@@ -82,7 +33,22 @@ white:true*/
       };
       XT.languages.push(language);
     }
-    return language;
+    return {
+      lang: lang,
+      strings: stringsHash
+    };
   };
 
+  if (typeof exports !== 'undefined') {
+    exports.getLanguage = XT.getLanguage;
+    exports.stringsFor = XT.stringsFor;
+  }
+
+  // one-time-only: copy locale strings into the languages
+  // variable, where they can get accessed by the app
+  if (!XT.languages && XT.locale && XT.locale.strings) {
+    _.each(XT.locale.strings, function (hash) {
+      XT.stringsFor(XT.locale.culture, hash);
+    });
+  }
 }());
index 33afab5..07b588c 100644 (file)
@@ -6,10 +6,7 @@ depends(
   "datasource.js",
   "date.js",
   "math.js",
-  "request.js",
-  "response.js",
   "session.js",
   "locale.js",
-  "ext",
-  "en"
+  "ext"
 );
diff --git a/lib/tools/source/request.js b/lib/tools/source/request.js
deleted file mode 100644 (file)
index 90ae3a3..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/*jshint 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 XT:true, _:true, console:true */
-
-(function () {
-  "use strict";
-
-  XT.Request = {
-    /** @scope XT.Request.prototype */
-
-    send: function (data) {
-      var details = XT.session.details,
-        sock = XT.dataSource._sock,
-        notify = this._notify,
-        handle = this._handle,
-        payload = {
-          payload: data
-        },
-        callback;
-
-      if (!notify || !(notify instanceof Function)) {
-        callback = XT.K;
-      } else {
-        callback = function (response) {
-          //notify(_.extend(Object.create(XT.Response), response));
-          if (response && response.isError) {
-            XT.log("Response error ", response);
-          }
-          notify(new XT.Response(response));
-        };
-      }
-
-      // attach the session details to the payload
-      payload = _.extend(payload, details);
-
-      if (XT.session.config.debugging) {
-        XT.log("Socket sending: %@".replace("%@", handle), payload);
-      }
-
-      sock.json.emit(handle, payload, callback);
-
-      return this;
-    },
-
-    handle: function (event) {
-      this._handle = event;
-      return this;
-    },
-
-    notify: function (method) {
-      var args = Array.prototype.slice.call(arguments).slice(1);
-      this._notify = function (response) {
-        args.unshift(response);
-        method.apply(null, args);
-      };
-      return this;
-    }
-
-  };
-
-}());
diff --git a/lib/tools/source/response.js b/lib/tools/source/response.js
deleted file mode 100644 (file)
index 6860fc8..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-/*jshint 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 XT:true, _:true, console:true */
-
-(function () {
-  "use strict";
-
-  var _isDatabaseError, _message;
-
-  XT.Response = function () {
-    _.extend(this, arguments[0]);
-    this.handleErrorStatus();
-  };
-
-  XT.Response.prototype = {
-    handleErrorStatus: function () {
-      if (!this.isError) {
-        return;
-      }
-
-      // TODO: there is little or NO HANDLING IN PLACE FOR DATABASE
-      // ERRORS EVEN THOUGH THEY ARE BEING REPORTED BY THE DATASOURCE...
-      // so in an effort to make sure these are known during development
-      // we will report them to console in a way they can't be ignored...
-      console.error("XT.Response%@: %@".f(this.isDatabaseError ? " [database error]": "", this.message));
-
-      if (this.code) {
-        if (this.code === "SESSION_NOT_FOUND") {
-          // so the session couldn't be validated by the datasource...
-          // this could happen for a variety of reasons and next
-          // iteration of handling can normalize some of this
-          // abstract behavior but for now at LEAST do something...
-          // so go ahead and reroute them to login since that is
-          // what they will need to do at this point regardless
-          XT.logout();
-        }
-      }
-    }
-  };
-
-
-  _isDatabaseError = function () {
-    if (!this.isError) {
-      return false;
-    }
-    if (this.data && this.data.detail) {
-      return true;
-    }
-    return false;
-  };
-
-  _message = function () {
-    if (this.data && this.data.detail) {
-      return this.data.detail;
-    } else if (this.reason) {
-      return this.reason;
-    }
-    return this;
-  };
-
-  // TODO: this is an ugly hack to say the least but there's no guarantee that
-  // the various versions of the browsers can handle either of these methods...
-  if (Object.__defineGetter__) {
-
-    // TODO: find out if there is a way to determine support for ES5 getters as
-    // opposed to the fallback ES4 that are deprecated moving forward...
-    XT.Response.prototype.__defineGetter__("message", function () {
-      return _message.call(this);
-    });
-
-    XT.Response.prototype.__defineGetter__("isDatabaseError", function () {
-      return _isDatabaseError.call(this);
-    });
-  } else {
-
-    // will work with IE 9...
-    Object.defineProperty(XT.Response.prototype, "isDatabaseError", {
-      get: function () {
-        return _isDatabaseError.call(this);
-      }
-    });
-
-    Object.defineProperty(XT.Response.prototype, "message", {
-      get: function () {
-        return _message.call(this);
-      }
-    });
-  }
-
-}());
index 5186810..a3f9027 100644 (file)
@@ -42,57 +42,6 @@ white:true*/
     setDetails: function (value) {
       this.details = value;
       return this;
-    },
-
-    validateSession: function (callback) {
-      var self = this,
-        complete = function (payload) {
-          self._didValidateSession.call(self, payload, callback);
-        };
-
-      // Poll the session socket.io endpoint for valid session data.
-      XT.Request
-        .handle("session")
-        .notify(complete)
-        .send(null);
-    },
-
-    _didValidateSession: function (payload, callback) {
-      if (payload.code === 1) {
-        // If this is a valid session acquisition, go ahead
-        // and store the database config details in
-        // XT.Session.config, and, more specifically, user
-        // properties in XT.Session.details.
-        this.setConfig(payload);
-        this.setDetails(payload.data);
-
-        if (payload.version && XT.setVersion) {
-          // announce to the client what our version is, if we have
-          // a way of doing it.
-          XT.setVersion(payload.version);
-        }
-
-        // Start the client loading process.
-        XT.getStartupManager().start();
-      } else {
-        return XT.Session.logout();
-      }
-
-      if (callback && callback instanceof Function) {
-        callback();
-      }
-    },
-
-    start: function () {
-      try {
-        this.validateSession(function () {
-          // Tell the client to show now that we're in startup mode.
-          XT.app.show();
-        });
-      } catch (e) {
-        XT.Session.logout();
-      }
     }
   };
-
 }());
index f388144..e70c72c 100644 (file)
@@ -19,6 +19,8 @@ if (typeof X === 'undefined') {
 (function () {
   "use strict";
 
+  var RJSON = require("rjson");
+
   exports.dataSource = XT.dataSource = X.Database.create({
     requestNum: 0,
     callbacks: {},
@@ -33,6 +35,25 @@ if (typeof X === 'undefined') {
       };
     },
 
+    /**
+    * Encode the server's response into the encoding specified by the client
+    * @see enyo-client/application/source/ext/datasource.js#decodeResponse
+    */
+    encodeResponse: function (result, encoding) {
+      if (!encoding) {
+        return result;
+      }
+      else if (encoding === "rjson") {
+        return RJSON.pack(result);
+      }
+      else {
+        return {
+          isError: true,
+          status: "Encoding [" + encoding + "] not recognized."
+        };
+      }
+    },
+
     /**
      * Initializes database by setting the default pool size
      */
diff --git a/node-datasource/lib/ext/olapsource.js b/node-datasource/lib/ext/olapsource.js
new file mode 100644 (file)
index 0000000..44eafb9
--- /dev/null
@@ -0,0 +1,37 @@
+/*jshint 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 XT:true, console:true, issue:true, require:true, XM:true, io:true,
+Backbone:true, _:true, X:true, __dirname:true, exports:true */
+
+(function () {
+  "use strict";
+
+  exports.olapSource = X.olapSource = X.olapCatalog.create({
+
+    /**
+     * Initializes olapSource
+     */
+    init: function () {
+    },
+
+    /**
+      Perform xmla query appending jwt for single sign on
+
+      @param {String} query
+      @param {String} jwt
+      @param {Function} callback
+     */
+    query: function (query, jwt, callback) {
+      this.xmlaConnect.executeTabular({
+        statement: query,
+        url : "http://" + this.hostname + ":" + this.port + "/pentaho/Xmla?assertion=" + jwt.jwt,
+        success: function (xmla, options, xmlaResponse) {
+            callback(xmlaResponse); // back to callback in olapdata
+          },
+      });
+    }
+
+  });
+
+}());
index cfe6508..9387356 100755 (executable)
@@ -69,6 +69,11 @@ SYS = {};
   require("./lib/ext/datasource");
   require("./lib/ext/models");
   require("./lib/ext/smtp_transport");
+  
+  if (typeof X.options.biServer !== 'undefined') {
+    require("./olapcatalog");
+    require("./lib/ext/olapsource");
+  }
 
   // load the encryption key, or create it if it doesn't exist
   // it should created just once, the very first time the datasoruce starts
@@ -322,14 +327,6 @@ require('./oauth2/passport');
 var that = this;
 
 app.use(express.favicon(__dirname + '/views/login/assets/favicon.ico'));
-app.get('/:org/debug', function (req, res, next) {
-  "use strict";
-  if (!req.session.passport.user) {
-    routes.logout(req, res);
-  } else {
-    res.render('debug', { org: req.session.passport.user.organization });
-  }
-});
 _.each(X.options.datasource.databases, function (orgValue, orgKey, orgList) {
   "use strict";
   app.use("/" + orgValue + '/client', express.static('../enyo-client/application', { maxAge: 86400000 }));
@@ -364,19 +361,21 @@ app.post('/login/scopeSubmit', routes.scope);
 app.get('/logout', routes.logout);
 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);
 app.all('/:org/data-from-key', routes.dataFromKey);
 app.all('/:org/email', routes.email);
 app.all('/:org/export', routes.exxport);
-app.all('/:org/vcfExport', routes.vcfExport);
 app.get('/:org/file', routes.file);
+app.get('/:org/locale', routes.locale);
 app.get('/:org/report', routes.report);
-app.get('/:org/analysis', routes.analysis);
 app.get('/:org/reset-password', routes.resetPassword);
-
+app.get('/:org/queryOlap', routes.queryOlapCatalog);
+app.all('/:org/vcfExport', routes.vcfExport);
 
 //
 // Load all extension-defined routes. By convention the paths,
diff --git a/node-datasource/olapcatalog/olapcatalog.js b/node-datasource/olapcatalog/olapcatalog.js
new file mode 100644 (file)
index 0000000..f7332a4
--- /dev/null
@@ -0,0 +1,39 @@
+/*jshint node:true, bitwise: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 X:true, _:true, issue:true */
+
+(function () {
+  "use strict";
+
+ /**
+  OLAP Catalog Object
+
+  @class
+  @extends X.Object
+ */
+  X.olapCatalog = X.Object.extend({
+  
+    className: "X.olapCatalog",
+    hostname: X.options.biServer.hostname || "localhost",
+    port: X.options.biServer.port || 8080,
+    
+    xmlaConnect: new X.xmla.Xmla({
+        async: true,
+        properties: {
+            DataSourceInfo: "Provider=Mondrian;DataSource=Pentaho",
+            Catalog: X.options.biServer.catalog || "xTuple",
+          },
+          listeners: {
+            events: X.xmla.Xmla.EVENT_ERROR,
+            handler: function (eventName, eventData, xmla) {
+                X.log(
+                    "xmla error occurred: " + eventData.exception.message + " (" + eventData.exception.code + ")" +
+                    (eventData.exception.code ===  X.xmla.Xmla.Exception.HTTP_ERROR_CDE ?
+                    "\nstatus: " + eventData.exception.data.status + "; statusText: " + eventData.exception.data.statusText
+                    : "")
+                    );
+              }
+          }
+        })
+      });
+}());
\ No newline at end of file
diff --git a/node-datasource/olapcatalog/package.json b/node-datasource/olapcatalog/package.json
new file mode 100644 (file)
index 0000000..e1a93c6
--- /dev/null
@@ -0,0 +1 @@
+{ "name": "olapcatalog", "main": "olapcatalog.js" }
\ No newline at end of file
index 63e1e80..bc41f2d 100644 (file)
@@ -3,6 +3,7 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
 /*global X:true, SYS:true, XT:true, _:true */
 
 var async = require("async"),
+  path = require("path"),
   routes = require("./routes");
 
 (function () {
@@ -50,12 +51,13 @@ var async = require("async"),
     return versionSize;
   };
 
+
   /**
    Figures out what extensions the user is entitled to. Queries to determine
    client code UUIDs for core and those extensions. Sends on to the app view
    to render.
    */
-  exports.serveApp = function (req, res) {
+  var serveApp = exports.serveApp = function (req, res) {
     if (!req.session.passport.user) {
       routes.logout(req, res);
       return;
@@ -87,6 +89,9 @@ var async = require("async"),
             }
           });
           uuids = _.compact(uuids); // eliminate any null values
+          var extensionPaths = _.compact(_.map(extensions, function (ext) {
+            return path.join(ext.location, "source", ext.name);
+          }));
           getCoreUuid('js', req.session.passport.user.organization, function (err, jsUuid) {
             if (err) {
               res.send({isError: true, error: err});
@@ -97,11 +102,12 @@ var async = require("async"),
                 res.send({isError: true, error: err});
                 return;
               }
-              res.render('app', {
+              res.render(req.viewName || 'app', {
                 org: req.session.passport.user.organization,
                 coreJs: jsUuid,
                 coreCss: cssUuid,
-                extensions: uuids
+                extensions: uuids,
+                extensionPaths: extensionPaths
               });
             });
           });
@@ -166,4 +172,8 @@ var async = require("async"),
     });
   };
 
+  exports.serveDebug = function (req, res) {
+    req.viewName = "debug";
+    serveApp(req, res);
+  };
 }());
index c736795..589feff 100644 (file)
@@ -30,6 +30,7 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
       queryString = "select xt.%@($$%@$$)",
       binaryField = payload.binaryField,
       buffer,
+      result,
       adaptorCallback = function (err, res) {
         var data,
             status;
@@ -49,11 +50,14 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
           } catch (error) {
             data = {isError: true, status: "Cannot parse data"};
           }
-          callback({
-            data: data,
-            status: res.status,
-            debug: res.debug
-          });
+          callback(
+            XT.dataSource.encodeResponse({
+              data: data,
+              status: res.status,
+              debug: res.debug
+            },
+            payload.encoding)
+          );
         } else {
           callback({
             isError: true,
diff --git a/node-datasource/routes/locale.js b/node-datasource/routes/locale.js
new file mode 100644 (file)
index 0000000..667e735
--- /dev/null
@@ -0,0 +1,35 @@
+/*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, SYS:true, XT:true, _:true */
+
+(function () {
+  "use strict";
+
+  /**
+   Fetches (via a dispatch function) user locale and string translation data
+   */
+  exports.locale = function (req, res) {
+    var sql = 'select xt.post($${"nameSpace":"XT","type":"Session",' +
+       '"dispatch":{"functionName":"locale","parameters":null},"username":"%@"}$$)'
+       .f(req.session.passport.user.username),
+      org = req.session.passport.user.organization,
+      queryOptions = XT.dataSource.getAdminCredentials(org),
+      dataObj;
+
+    XT.dataSource.query(sql, queryOptions, function (err, results) {
+      if (err) {
+        res.send({isError: true, message: err.message});
+        return;
+      }
+      var data = results.rows[0].post;
+      if (req.query.debug) {
+        // let them get their strings from the client if in debug mode
+        dataObj = JSON.parse(data);
+        dataObj.strings = [];
+        data = JSON.stringify(dataObj);
+      }
+      res.set('Content-Type', 'text/javascript');
+      res.send("XT = typeof XT !== 'undefined' ? XT : {};\nXT.locale = " + data);
+    });
+  };
+}());
diff --git a/node-datasource/routes/olapdata.js b/node-datasource/routes/olapdata.js
new file mode 100644 (file)
index 0000000..9c078bc
--- /dev/null
@@ -0,0 +1,98 @@
+/*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, XT:true */
+
+(function () {
+  "use strict";
+  var utils = require('../oauth2/utils');
+
+  exports.queryOlapCatalog = function (req, res) {
+
+    var query = req.query.mdx,
+      // Format xmla response as json and return
+      queryCallback = function (xmlaResponse) {
+               var obj = xmlaResponse.fetchAllAsObject();
+               res.writeHead(200, { 'Content-Type': 'application/json' });
+        res.write(JSON.stringify(obj));
+        res.end();
+      },
+      
+      jwt,
+      privKey = "",
+      claimSet = {},
+      header = {},
+      username = req.session.passport.user.username,
+      biServerUrl = X.options.datasource.biServerUrl,
+      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.datasource.uniqueTenantId;
+
+    // get private key from path in config
+    privKey = X.fs.readFileSync(X.options.datasource.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);
+
+    X.olapSource.query(query, jwt, queryCallback);
+  };
+
+  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 14e23b2..dd3d246 100644 (file)
@@ -112,8 +112,8 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
               "&org=" + req.session.passport.user.organization +
               "&datasource=" + req.headers.host + "&datakey=" + randomKey;
 
-          if (requestDetails.locale && requestDetails.locale.culture) {
-            res.set("Accept-Language", requestDetails.locale.culture);
+          if (requestDetails.culture) {
+            res.set("Accept-Language", requestDetails.culture);
           }
           // step 3: redirect to the report tool
           res.redirect(redirectUrl);
index f7380d5..382f8f0 100644 (file)
@@ -34,8 +34,10 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
     email = require('./email'),
     exxport = require('./export'),
     data = require('./data'),
+    olapData = require('./olapdata'),
     dataFromKey = require('./data_from_key'),
     file = require('./file'),
+    locale = require('./locale'),
     passport = require('passport'),
     redirector = require('./redirector'),
     report = require('./report'),
@@ -48,6 +50,7 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
   // Authentication-related routes
   //
   exports.app = [ensureLogin, app.serveApp];
+  exports.debug = [ensureLogin, app.serveDebug];
   exports.login = auth.login;
   exports.loginForm = auth.loginForm;
   exports.logout = auth.logout;
@@ -66,6 +69,11 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
   exports.get = [ensureLogin, data.get];
   exports.patch = [ensureLogin, data.patch];
   exports.post = [ensureLogin, data.post];
+  
+  //
+  //  OLAP query route
+  //
+  exports.queryOlapCatalog = [ensureLogin, olapData.queryOlapCatalog];
 
   //
   // REST API Routes
@@ -90,6 +98,7 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
   exports.email = [ensureLogin, email.email];
   exports.exxport = [ensureLogin, exxport.exxport];
   exports.file = [ensureLogin, file.file];
+  exports.locale = [ensureLogin, locale.locale];
   exports.redirect = redirector.redirect;
   exports.report = [ensureLogin, report.report];
   exports.analysis = [ensureLogin, analysis.analysis];
index 1421480..d96cde6 100644 (file)
@@ -9,7 +9,8 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
     processName: "node-datasource",
     allowMultipleInstances: true,
     client: {
-      freeDemo: false
+      freeDemo: false,
+      encoding: "rjson"
     },
     datasource: {
       debugging: false,
@@ -77,6 +78,13 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
       port: 5432,
       user: "admin",
       password: "admin"
-    }
-  };
+    },
+    biServer: {
+        hostname: "localhost",
+        port: 8080,
+        catalog: "xTuple",
+        tenantname: "default",
+        keyFile: "./lib/rest-keys/server.key"
+      }
+    };
 }());
index bd3f7a9..dbcec54 100644 (file)
@@ -12,6 +12,7 @@
     <link href="/<%= org %>/client/lib/enyo-x/lib/nvd3/src/nv.d3.css" rel="stylesheet"/>\r
     <link href="/<%= org %>/client/build/client-code?uuid=<%= coreCss %>&language=css" rel="stylesheet"/>\r
     <!-- js -->\r
+    <script src="/<%= org %>/locale"></script>\r
     <script src="/socket.io/socket.io.js?1.3.10" type="text/javascript"></script>\r
     <script src="/<%= org %>/client/build/client-code?uuid=<%= coreJs %>&language=js"></script>\r
     <% for (var i = 0; i < extensions.length; i++) { %>\r
index 70944b6..9e7eaee 100644 (file)
     <script src="/<%= org %>/client/enyo/enyo.js"></script>\r
     <!-- application (debug) -->\r
     <link href="/<%= org %>/client/lib/enyo-x/lib/nvd3/src/nv.d3.css" rel="stylesheet"/>\r
+    <script src="/<%= org %>/locale?debug=true"></script>\r
+    <script src="/<%= org %>/client/lib/tools/lib/underscore/underscore.js" type="text/javascript"></script>\r
+    <script src="/<%= org %>/client/lib/tools/source/locale.js" type="text/javascript"></script>\r
+    <script src="/<%= org %>/client/lib/enyo-x/source/en/strings.js" type="text/javascript"></script>\r
+    <script src="/<%= org %>/client/lib/tools/source/en/strings.js" type="text/javascript"></script>\r
+    <script src="/<%= org %>/client/source/en/strings.js" type="text/javascript"></script>\r
     <script src="/<%= org %>/client/source/package.js" type="text/javascript"></script>\r
-    <script src="/<%= org %>/core-extensions/debug.js" type="text/javascript"></script>\r
+    <% for (var i = 0; i < extensionPaths.length; i++) { %>\r
+      <script src="/<%= org %><%= extensionPaths[i] %>/client/en/strings.js"></script>\r
+      <script src="/<%= org %><%= extensionPaths[i] %>/client/package.js"></script>\r
+    <% } %>\r
   </head>\r
   <body class="enyo-unselectable">\r
     <script>\r
index 1582bae..4315ac8 100644 (file)
@@ -31,6 +31,7 @@ X = {};
   X.crypto       = require("crypto");
   X.bcrypt       = require("bcrypt");
   X.pg           = require("pg");
+  X.xmla         = require('xmla4js/src/Xmla.js');
 
   /**
    Returns the global X
index 68288a6..5e49c05 100644 (file)
     "pg":"0.14.x",
     "request":"2.14.x",
     "rimraf":"2.2.x",
+    "rjson": "~0.2.4",
     "socket.io":"0.9.x",
     "underscore":"1.4.x",
-    "winston":"0.7.x"
+    "winston":"0.7.x",
+    "xml4js": "git://github.com/rpbouman/xmla4js.git"
   },
   "devDependencies": {
     "chai":"1.5.x",
index e3b9c78..8b9b382 100755 (executable)
@@ -6,7 +6,7 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
 
 //
 // This file really just parses the arguments, and sends the real work
-// off to lib/build.js.
+// off to scripts/lib/build_all.js.
 //
 
 (function () {
@@ -23,6 +23,7 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
     .option('-i, --initialize', 'Initialize database. Must be used with the -b flag.')
     .option('-k, --keepsql', 'Do not delete the temporary sql files that represent the payload of the build.')
     .option('-q, --querydirect', 'Query the database directly, without delegating to psql.')
+    .option('-u, --unregister', 'Unregister an extension.')
     .option('-w, --wipeviews', 'Drop the views and the orm registrations pre-emptively.')
     .option('-y, --clientonly', 'Only rebuild the client.')
     .option('-z, --databaseonly', 'Only rebuild the database.')
@@ -30,17 +31,18 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
 
   build({
     backup: program.backup,
-    database: program.database,
     config: program.config,
+    database: program.database,
     extension: program.extension,
     initialize: program.initialize,
     keepSql: program.keepsql,
     queryDirect: program.querydirect,
+    unregister: program.unregister,
     wipeViews: program.wipeviews,
     clientOnly: program.clientonly,
     databaseOnly: program.databaseonly
   }, function (err, res) {
-    console.log(err || res);
+    console.log(err || res || "Success!");
     process.exit(err ? 1 : 0);
   });
 
diff --git a/scripts/export_dictionary.js b/scripts/export_dictionary.js
new file mode 100755 (executable)
index 0000000..7139b10
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/env node
+/*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 */
+
+//
+// This file really just parses the arguments, and sends the real work
+// off to scripts/lib/build_dictionary.js.
+//
+(function () {
+  "use strict";
+
+  var program = require('commander'),
+    exportEnglish = require("./lib/build_dictionary").exportEnglish;
+
+  program
+    .option('-a, --apikey [api key]', 'Google Translate API key.')
+    .option('-d, --database [database name]', 'Database name to export from.')
+    .option('-l, --language [language]', 'Language name in form es_MX.')
+    .parse(process.argv);
+
+  exportEnglish(program.database, program.apikey, program.language, function (err, res) {
+    if (err) {
+      console.log("Export failed", err);
+      return;
+    }
+    console.log("Success!");
+  });
+
+}());
diff --git a/scripts/import_dictionary.js b/scripts/import_dictionary.js
new file mode 100755 (executable)
index 0000000..08eef28
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/env node
+/*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 */
+
+//
+// This file really just parses the arguments, and sends the real work
+// off to scripts/lib/build_dictionary.js.
+//
+(function () {
+  "use strict";
+
+  var program = require('commander'),
+    importDictionary = require("./lib/build_dictionary").importDictionary;
+
+  program
+    .option('-d, --database [database name]', 'Database name to export from.')
+    .option('-f, --filename [/path/to/filename]', 'Path to xTuple dictionary js file.')
+    .parse(process.argv);
+
+  importDictionary(program.database, program.filename, function (err, res) {
+    if (err) {
+      console.log("Import failed", err);
+      return;
+    }
+    console.log("Success!");
+  });
+
+}());
index b852504..1e215f3 100755 (executable)
@@ -427,15 +427,6 @@ pull_modules() {
     echo "}" >> login_data.js
        log "Created testing login_data.js"
 
-       cdir ../../enyo-client/extensions
-    rm -f debug.js
-    echo "enyo.depends(" > debug.js
-    echo "  '/dev/core-extensions/source/crm/client/package.js'," >> debug.js
-    echo "  '/dev/core-extensions/source/inventory/client/package.js'," >> debug.js
-    echo "  '/dev/core-extensions/source/project/client/package.js'," >> debug.js
-    echo "  '/dev/core-extensions/source/sales/client/package.js'" >> debug.js
-    echo ");" >> debug.js
-       log "Created debug.js"
 }
 
 init_everythings() {
index b7ced6d..cbc798a 100644 (file)
@@ -4,12 +4,14 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
 
 var _ = require('underscore'),
   async = require('async'),
-  buildDatabase = require("./build_database").buildDatabase,
+  build_database = require("./build_database"),
+  buildDatabase = build_database.buildDatabase,
   buildClient = require("./build_client").buildClient,
   dataSource = require('../../node-datasource/lib/ext/datasource').dataSource,
   exec = require('child_process').exec,
   fs = require('fs'),
   path = require('path'),
+  unregister = build_database.unregister,
   winston = require('winston');
 
 /*
@@ -42,7 +44,6 @@ var _ = require('underscore'),
           credsClone = JSON.parse(JSON.stringify(creds)),
           existsSql = "select relname from pg_class where relname = 'ext'",
           extSql = "SELECT * FROM xt.ext ORDER BY ext_load_order",
-          // TODO: we could get these extensions dynamically by looking at the filesystem.
           defaultExtensions = [
             { ext_location: '/core-extensions', ext_name: 'crm' },
             { ext_location: '/core-extensions', ext_name: 'inventory' },
@@ -170,7 +171,6 @@ var _ = require('underscore'),
       buildSpecs.clientOnly = options.clientOnly;
       buildSpecs.databaseOnly = options.databaseOnly;
       buildSpecs.queryDirect = options.queryDirect;
-      // TODO: as above, the extensions could be found dynamically
       buildSpecs.extensions = [
         path.join(__dirname, '../../lib/orm'),
         path.join(__dirname, '../../enyo-client'),
@@ -187,7 +187,7 @@ var _ = require('underscore'),
         " a database, and use no extensions, and use both the init and the backup flags");
 
     } else if (options.extension) {
-      // the user has specified an extension to build
+      // 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
@@ -204,9 +204,13 @@ var _ = require('underscore'),
           extensions: [extension]
         };
       });
-      // synchronous...
-      buildAll(buildSpecs, creds, callback);
 
+      if (options.unregister) {
+        unregister(buildSpecs, creds, callback);
+      } else {
+        // synchronous build
+        buildAll(buildSpecs, creds, callback);
+      }
     } else {
       // build all registered extensions for the database
       async.map(databases, getRegisteredExtensions, function (err, results) {
index dbd9361..0f2827b 100755 (executable)
@@ -158,7 +158,10 @@ var _ = require('underscore'),
           callback(err);
           return;
         } else if (files.length < 4) {
-          callback("Error: was not able to build all core files. Built files are: " + JSON.stringify(files));
+          callback("Error: was not able to build all core files. Built files are: " +
+            JSON.stringify(files) +
+            ". Try running the enyo deploy by itself in enyo-client/application/tools " +
+            "and if that fails there's probably a problem in your package files.");
         }
         readFile = function (filename, callback) {
           var callbackAdaptor = function (err, contents) {
index 5d4a631..ce6419d 100644 (file)
@@ -9,6 +9,7 @@ var _ = require('underscore'),
   exec = require('child_process').exec,
   fs = require('fs'),
   ormInstaller = require('./orm'),
+  dictionaryBuilder = require('./build_dictionary'),
   clientBuilder = require('./build_client'),
   path = require('path'),
   pg = require('pg'),
@@ -374,27 +375,33 @@ var _ = require('underscore'),
         dictates that *all* source for all extensions should be run before *any*
         orm queries for any extensions, but that is not the way it works here.
        */
-      // TODO: async.series would work nicely here
-      var getAllSql = function (extension, callback) {
-        getExtensionSql(extension, function (err, sql) {
-          if (err) {
-            callback(err);
-            return;
-          }
-          getOrmSql(extension, function (err, ormSql) {
-            if (err) {
-              callback(err);
+      var getAllSql = function (extension, masterCallback) {
+
+        async.series([
+          function (callback) {
+            getExtensionSql(extension, callback);
+          },
+          function (callback) {
+            if (spec.clientOnly) {
+              callback(null, "");
               return;
             }
-            getClientSql(extension, function (err, clientSql) {
-              clientSql = jsInit + clientSql;
-              if (err) {
-                callback(err);
-                return;
-              }
-              callback(null, sql + ormSql + clientSql);
-            });
-          });
+            dictionaryBuilder.getDictionarySql(extension, callback);
+          },
+          function (callback) {
+            getOrmSql(extension, callback);
+          },
+          function (callback) {
+            // the client needs jsInit and might not have it by now
+            callback(null, jsInit);
+          },
+          function (callback) {
+            getClientSql(extension, callback);
+          }
+        ], function (err, results) {
+          masterCallback(err, _.reduce(results, function (memo, sql) {
+            return memo + sql;
+          }, ""));
         });
       };
 
@@ -501,4 +508,36 @@ var _ = require('underscore'),
       });
     });
   };
+
+
+  //
+  // Another option: unregister the extension
+  //
+  exports.unregister = function (specs, creds, masterCallback) {
+    var extension = path.basename(specs[0].extensions[0]),
+      unregisterSql = ["delete from xt.usrext where usrext_id in " +
+        "(select usrext_id from xt.usrext inner join xt.ext on usrext_ext_id = ext_id where ext_name = $1);",
+
+        "delete from xt.clientcode where clientcode_id in " +
+        "(select clientcode_id from xt.clientcode inner join xt.ext on clientcode_ext_id = ext_id where ext_name = $1);",
+
+        "delete from xt.extdep where extdep_id in " +
+        "(select extdep_id from xt.extdep inner join xt.ext " +
+        "on extdep_from_ext_id = ext_id or extdep_to_ext_id = ext_id where ext_name = $1);",
+
+        "delete from xt.ext where ext_name = $1;"];
+
+    winston.info("Unregistering extension:", extension);
+    var unregisterEach = function (spec, callback) {
+      var options = JSON.parse(JSON.stringify(creds));
+      options.database = spec.database;
+      options.parameters = [extension];
+      var queryEach = function (sql, sqlCallback) {
+        dataSource.query(sql, options, sqlCallback);
+      };
+      async.eachSeries(unregisterSql, queryEach, callback);
+    };
+    async.each(specs, unregisterEach, masterCallback);
+  };
+
 }());
diff --git a/scripts/lib/build_dictionary.js b/scripts/lib/build_dictionary.js
new file mode 100644 (file)
index 0000000..b261be8
--- /dev/null
@@ -0,0 +1,271 @@
+/*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, Backbone:true, _:true, XM:true, XT:true */
+
+if (typeof XT === 'undefined') {
+  XT = {};
+}
+
+(function () {
+  "use strict";
+
+  var _ = require("underscore"),
+    async = require("async"),
+    fs = require("fs"),
+    locale = require("../../lib/tools/source/locale"),
+    path = require("path"),
+    createQuery = function (strings, context, language) {
+      return "select xt.set_dictionary($$%@$$, '%@', '%@');"
+        .f(JSON.stringify(strings),
+          context || "_core_",
+          language || "en_US");
+    };
+
+  /**
+    Looks (by convention) in en/strings.js of the extension for the
+    English strings and asyncronously returns the sql command to put
+    that hash into the database.
+   */
+  exports.getDictionarySql = function (extension, callback) {
+    var isLibOrm = extension.indexOf("lib/orm") >= 0,
+      isApplicationCore = extension.indexOf("enyo-client") >= 0 &&
+        extension.indexOf("extension") < 0,
+      clientHash,
+      databaseHash,
+      filename;
+
+    if (!XT.stringsFor) {
+      XT.getLanguage = locale.getLanguage;
+      XT.stringsFor = locale.stringsFor;
+    }
+    if (isLibOrm) {
+      // smash the tools and enyo-x strings together into one query
+      clientHash = _.extend(
+        require(path.join(extension, "../enyo-x/source/en/strings.js")).language.strings,
+        require(path.join(extension, "../tools/source/en/strings.js")).language.strings
+      );
+      callback(null, createQuery(clientHash, "_framework_"));
+
+    } else if (isApplicationCore) {
+      // put the client strings into one query
+      // put the database strings into another query
+      clientHash = require(path.join(extension, "application/source/en/strings.js")).language.strings;
+      databaseHash = require(path.join(extension, "database/source/en/strings.js")).language.strings;
+      callback(null, createQuery(clientHash) + createQuery(databaseHash, "_database_"));
+
+    } else {
+      // return the extension strings if they exist
+      filename = path.join(extension, "client/en/strings.js");
+      fs.exists(filename, function (exists) {
+        if (exists) {
+          callback(null, createQuery(require(filename).language.strings,
+            path.basename(extension).replace("/", "")));
+        } else {
+          // no problem. Maybe there is just no strings file
+          callback(null, '');
+        }
+      });
+    }
+  };
+
+
+  //
+  // The below code supports importing and exporting of dictionaries.
+  // This functionality can be accessed through the command line via
+  // the ./scripts/export_database.js and ./scripts/import_database.js
+  // files.
+  //
+
+  var dataSource = require('../../node-datasource/lib/ext/datasource').dataSource;
+  var querystring = require("querystring");
+  var request = require("request");
+
+  // Ask Google
+  // note that if we haven't been given an API key then the control flow
+  // will still come through here, but we'll just return the empty string
+  // synchronously.
+  var autoTranslate = function (text, apiKey, destinationLang, callback) {
+    if (!apiKey || !destinationLang || !text) {
+      // the user doesn't want to autotranslate
+      callback(null, "");
+      return;
+    }
+
+    if (destinationLang.indexOf("_") >= 0) {
+      // strip off the locale for google
+      destinationLang = destinationLang.substring(0, destinationLang.indexOf("_"));
+    }
+
+    var query = {
+        source: "en",
+        target: destinationLang,
+        key: apiKey,
+        q: text
+      },
+      url = "https://www.googleapis.com/language/translate/v2?" + querystring.stringify(query);
+
+    request.get(url, function (err, resp, body) {
+      if (err) {
+        callback(err);
+        return;
+      }
+      var response = JSON.parse(body);
+      if (response.error) {
+        callback(response.error);
+        return;
+      }
+      var translations = response.data.translations;
+      if (translations.length !== 1 || !translations[0].translatedText) {
+        console.log("could not parse translations", JSON.stringify(translations));
+        callback(null, "");
+        return;
+      }
+      var translation = translations[0].translatedText;
+      callback(null, translation);
+    });
+
+  };
+
+  //
+  // Group similar english and foreign rows together
+  // This will help us generate a dictionary file if there's
+  // already a partway- or fully- implemented translation already
+  // sitting in the database.
+  //
+  var marryLists = function (list) {
+    var englishList = _.filter(list, function (row) {
+      return row.dict_language_name === "en_US";
+    });
+    var foreignList = _.difference(list, englishList);
+    var marriedList = _.map(englishList, function (englishRow) {
+      var foreignRow = _.find(foreignList, function (foreignRow) {
+        return foreignRow.ext_name === englishRow.ext_name &&
+          foreignRow.dict_is_database === englishRow.dict_is_database &&
+          foreignRow.dict_is_framework === englishRow.dict_is_framework;
+      });
+      return {
+        source: englishRow,
+        target: foreignRow
+      };
+    });
+    return marriedList;
+  };
+
+  /**
+    @param {String} database. The database name, such as "dev"
+    @param {String} apiKey. Your Google Translate API key. Leave blank for no autotranslation
+    @param {String} destinationLang. In form "es_MX".
+    @param {Function} masterCallback
+   */
+  exports.exportEnglish = function (database, apiKey, destinationLang, masterCallback) {
+    var creds = require("../../node-datasource/config").databaseServer,
+      sql = "select dict_strings, dict_is_database, dict_is_framework, " +
+        "dict_language_name, ext_name from xt.dict " +
+        "left join xt.ext on dict_ext_id = ext_id " +
+        "where dict_language_name = 'en_US'";
+
+    if (destinationLang) {
+      sql = sql + " or dict_language_name = $1";
+      creds.parameters = [destinationLang];
+    } // else the user wants a blank template, so no need to search for pre-existing translations
+    sql = sql + ";";
+
+    creds.database = database;
+    dataSource.query(sql, creds, function (err, res) {
+      var processExtension = function (rowMap, extensionCallback) {
+        var row = rowMap.source;
+        var foreignStrings = rowMap.target ? JSON.parse(rowMap.target.dict_strings) : [];
+        var stringsArray = _.map(JSON.parse(row.dict_strings), function (value, key) {
+          return {value: value, key: key};
+        });
+        var processString = function (stringObj, stringCallback) {
+          //
+          // If this translation has already been made into the target language, put that
+          // translation into the dictionary file and do not bother autotranslating.
+          //
+          var preExistingTranslation = _.find(foreignStrings, function (foreignString, foreignKey) {
+            return foreignString && foreignKey === stringObj.key;
+          });
+          if (preExistingTranslation) {
+            // this has already been translated. No need to talk to Google etc.
+            stringCallback(null, {
+              key: stringObj.key,
+              source: stringObj.value,
+              target: preExistingTranslation
+            });
+          } else {
+            // ask google (or not)
+            autoTranslate(stringObj.value, apiKey, destinationLang, function (err, target) {
+              stringCallback(null, {
+                key: stringObj.key,
+                source: stringObj.value,
+                target: target
+              });
+            });
+          }
+        };
+        async.map(stringsArray, processString, function (err, strings) {
+          extensionCallback(null, {
+            extension: row.dict_is_database ? "_database_" :
+              row.dict_is_framework ? "_framework_" :
+              row.ext_name || "_core_",
+            strings: strings
+          });
+        });
+      };
+
+      // group together english and foreign strings of the same extension
+      var marriedRows = marryLists(res.rows);
+      async.map(marriedRows, processExtension, function (err, extensions) {
+        var output = {
+          language: destinationLang || "",
+          extensions: extensions
+        };
+        // filename convention is ./scripts/private/es_MX_dictionary.js
+        var exportFilename = path.join(__dirname, "../private",
+          (destinationLang || "blank") + "_dictionary.js");
+        console.log("Exporting to", exportFilename);
+        fs.writeFile(exportFilename, JSON.stringify(output, undefined, 2), function (err, result) {
+          masterCallback(err, result);
+        });
+      });
+    });
+  };
+
+  /**
+    Takes a dictionary definition file and inserts the data into the database
+   */
+  exports.importDictionary = function (database, filename, masterCallback) {
+    var creds = require("../../node-datasource/config").databaseServer;
+    creds.database = database;
+
+    // the filename relative unless it starts with a slash
+    if (filename.substring(0, 1) !== '/') {
+      filename = path.join(process.cwd(), filename);
+    }
+
+    fs.readFile(filename, "utf8", function (err, contents) {
+      if (err) {
+        masterCallback(err);
+        return;
+      }
+      var dictionary = JSON.parse(contents);
+      var processExtension = function (extension, extensionCallback) {
+        var context = extension.extension;
+        var strings = _.reduce(extension.strings, function (memo, trans) {
+          memo[trans.key] = trans.target;
+          return memo;
+        }, {});
+        var sql = createQuery(strings, context, dictionary.language);
+
+        dataSource.query(sql, creds, function (err, res) {
+          extensionCallback(err, res);
+        });
+      };
+      async.each(dictionary.extensions, processExtension, function (err, results) {
+        masterCallback(err, results);
+      });
+    });
+  };
+
+}());
index 4fe52c6..d75ce8d 100644 (file)
@@ -7,7 +7,8 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
 
   var zombie = require("zombie"),
     mocha = require("mocha"),
-    assert = require("chai").assert;
+    assert = require("chai").assert,
+    RJSON = require("rjson");
 
 
   var loginData = require('../../shared/login_data'),
index 9f0730f..98ce47e 100644 (file)
@@ -1,12 +1,11 @@
 /*jshint 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 XT:true, _:true, console:true, XM:true, Backbone:true, require:true, assert:true,
+/*global _:true, console:true, XM:true, require:true, assert:true,
 setTimeout:true, clearTimeout:true, exports:true, it:true */
 
 var _ = require("underscore"),
   zombieAuth = require("./zombie_auth"),
-  Globalize = require("../../../lib/tools/lib/globalize/lib/globalize"),
   assert = require("chai").assert;
 
 (function () {
@@ -177,8 +176,7 @@ var _ = require("underscore"),
     @param {Function} callback
   */
   var init = exports.init = function (data, callback) {
-    var that = this,
-      timeoutId,
+    var timeoutId,
       model = data.model,
       auto_regex = XM.Document.AUTO_NUMBER + "|" + XM.Document.AUTO_OVERRIDE_NUMBER,
       assertAndCallback = function () {
@@ -186,7 +184,7 @@ var _ = require("underscore"),
         assert.isNotNull(data.model.id);
         callback();
       },
-      modelCallback = function (model, value) {
+      modelCallback = function (model) {
         if (model instanceof XM.Document && model.numberPolicy.match(auto_regex)) {
           // Check that the AUTO...NUMBER property has been set.
           if (model.get(model.documentKey)) {
@@ -234,8 +232,7 @@ var _ = require("underscore"),
     @param {Function} callback
   */
   var save = exports.save = function (data, callback) {
-    var that = this,
-      timeoutId,
+    var timeoutId,
       model = data.model,
       invalid = function (model, error) {
         assert.fail(JSON.stringify(error) || "Unspecified error", "");
@@ -250,7 +247,8 @@ var _ = require("underscore"),
       },
       modelCallback = function () {
         var status = model.getStatus(),
-          K = XM.Model;
+          K = XM.Model,
+          options = {};
         if (status === K.READY_CLEAN) {
           clearTimeout(timeoutId);
           model.off('statusChange', modelCallback);
@@ -258,11 +256,18 @@ var _ = require("underscore"),
           model.off('notify', notify);
 
           assert.equal(data.model.getStatusString(), 'READY_CLEAN');
+
           testAttributes(data);
           if (data.commentType && data.testComments) {
             testComments(data);
           }
-          callback();
+
+          // Model should not be used at this point
+          options.success = function (used) {
+            assert.equal(used, 0);
+            callback();
+          };
+          model.used(options);
         }
       };
 
@@ -309,8 +314,7 @@ var _ = require("underscore"),
     @param {Object} callback
   */
   var destroy = exports.destroy = function (data, callback) {
-    var that = this,
-      timeoutId,
+    var timeoutId,
       model = data.model,
       modelCallback = function () {
         var status = model.getStatus(),
diff --git a/test/mocha/models/prospect.js b/test/mocha/models/prospect.js
new file mode 100644 (file)
index 0000000..91db855
--- /dev/null
@@ -0,0 +1,27 @@
+/*jshint trailing:true, white:true, indent:2, strict:true, curly:true,\r
+  immed:true, eqeqeq:true, forin:true, latedef:true,\r
+  newcap:true, noarg:true, undef:true */\r
+/*global XT:true, XM:true, XV:true, exports:true, describe:true, it:true, require:true */\r
+\r
+(function () {\r
+  "use strict";\r
+\r
+  var crud = require('../lib/crud'),\r
+    data = {\r
+      recordType: "XM.Prospect",\r
+      autoTestAttributes: true,\r
+      createHash : {\r
+        number: "TESTPROSPECT" + Math.random(),\r
+        name: "TestRep"\r
+      },\r
+      updateHash : {\r
+        name: "Updated Test Prospect"\r
+      },\r
+      beforeDeleteActions: crud.accountBeforeDeleteActions,\r
+      afterDeleteActions: crud.accountAfterDeleteActions\r
+    };\r
+\r
+  describe('Prospect CRUD Test', function () {\r
+    crud.runAllCrud(data);\r
+  });\r
+}());\r
index df3cab8..e16a0f1 100644 (file)
@@ -11,10 +11,11 @@ var assert = require("chai").assert,
   exportRoute = require("../../../node-datasource/routes/export"),
   dataRoute = require("../../../node-datasource/routes/data");
 
-require("../../../node-datasource/xt");
+require("../../../node-datasource/xt/database");
 require("../../../lib/tools/source/foundation");
 require("../../../lib/tools/source/ext/string");
 require("../../../lib/tools/source/ext/proto/string");
+require("../../../node-datasource/lib/ext/datasource");
 
 X.options = {
   databaseServer: {}
@@ -53,9 +54,12 @@ X.options = {
         };
 
       // inject our mock query into the global variable
-      XT.dataSource = {query: queryFunction, getAdminCredentials: function () {
-        return {};
-      }};
+      _(XT.dataSource).extend({
+        query: queryFunction,
+        getAdminCredentials: function () {
+          return { };
+        }
+      });
 
       exportRoute.exxport(req, res);
     });
index d6e8391..115d6c3 100644 (file)
@@ -11,10 +11,11 @@ var assert = require("chai").assert,
   fileRoute = require("../../../node-datasource/routes/file"),
   dataRoute = require("../../../node-datasource/routes/data");
 
-require("../../../node-datasource/xt");
+require("../../../node-datasource/xt/database");
 require("../../../lib/tools/source/foundation");
 require("../../../lib/tools/source/ext/string");
 require("../../../lib/tools/source/ext/proto/string");
+require("../../../node-datasource/lib/ext/datasource");
 
 X.options = X.options || {};
 
@@ -53,9 +54,12 @@ X.options = X.options || {};
         };
 
       // inject our mock query into the global variable
-      XT.dataSource = {query: queryFunction, getAdminCredentials: function () {
-        return {};
-      }};
+      _(XT.dataSource).extend({
+        query: queryFunction,
+        getAdminCredentials: function () {
+          return { };
+        }
+      });
 
       fileRoute.file(req, res);
     });