- "cd .."
script:
- - "npm run-script test"
- "npm run-script test-datasource"
+ - "npm run-script test"
- "npm run-script jshint"
"_closeDate": "Close Date",
"_code": "Code",
"_configure": "Configure",
+ "_commandCenter": "Command Center",
"_commentType": "Comment Type",
"_commentsEditable": "Comments Editable",
"_company": "Company",
"_extendedPrice": "Extended Price",
"_extendedDescription": "Extended Description",
"_extendedPriceScale": "Extended Price Scale",
+ "_extensionName": "Extension Name",
"_externalReference": "External Reference",
"_delivery": "Delivery",
"_department": "Department",
"_invoiceNumber": "Invoice #",
"_invoices": "Invoices",
"_initials": "Initials",
+ "_installExtension": "Install Extension",
"_inventoryUnit": "Inventory Unit",
"_isActive": "Active",
"_isAddresses": "Addresses",
"_customerOrProspect": "Would you like to create a new Customer or a new Prospect?",
"_deleteLine?": "Are you sure you want to delete this line?",
"_exitPageWarning": "You are about to leave the xTuple application.",
+ "_installExtensionWarning": "Extensions are very powerful and potentially have full access to your " +
+ "data. You should only install an extension from a source you trust.",
"_insufficientPrivileges": "You have insufficient privileges to perform this action.",
"_manualFreight": "Manually editing the freight will disable automatic freight recalculations.",
"_mustSave": "You must save your changes before proceeding.",
success: _.bind(this.didComplete, this)
};
var relevantPrivileges = [
+ "InstallExtension",
"MaintainUsers",
"MaintainPreferencesSelf",
"MaintainWorkflowsSelf",
{kind: "onyx.GroupboxHeader", content: "_notes".loc()},
{kind: "XV.TextArea", attr: "DatabaseComments"}
]}
+ ]},
+ {kind: "XV.Groupbox",
+ title: "_commandCenter".loc(), name: "commandPanel", components: [
+ {kind: "XV.ScrollableGroupbox",
+ classes: "in-panel", components: [
+ {kind: "onyx.GroupboxHeader", content: "_installExtension".loc()},
+ {kind: "XV.InputWidget", name: "extensionName", label: "_extensionName".loc()},
+ {kind: "FittableColumns", classes: "xv-buttons center", components: [
+ {kind: "onyx.Button", name: "extensionButton", classes: "icon-ok", ontap: "installExtension"},
+ ]},
+ ]}
]}
]}
- ]
+ ],
+ create: function () {
+ this.inherited(arguments);
+ var hasPriv = XT.session.privileges.get("InstallExtension");
+ this.$.extensionName.setDisabled(!hasPriv);
+ this.$.extensionButton.setDisabled(!hasPriv);
+ },
+ installExtension: function () {
+ var that = this,
+ callback = function (response) {
+ if (!response.answer) {
+ return;
+ }
+
+ XT.dataSource.callRoute("install-extension",
+ {
+ extensionName: that.$.extensionName.getValue()
+ },
+ {
+ success: function (message) {
+ that.doNotify({message: message});
+ },
+ error: function (error) {
+ that.doNotify({message: error.message ? error.message() : error});
+ }
+ }
+ );
+ };
+
+ if (!this.$.extensionName.getValue()) {
+ this.doNotify({
+ type: XM.Model.WARNING,
+ message: "_attributeIsRequired".loc().replace("{attr}", "_extensionName".loc())
+ });
+ return;
+ }
+
+ this.doNotify({
+ type: XM.Model.QUESTION,
+ message: "_installExtensionWarning".loc() + "_confirmAction".loc(),
+ callback: callback
+ });
+ }
});
enyo.kind({
],
"isSystem": true
},
+ {
+ "context": "xtuple",
+ "nameSpace": "XM",
+ "type": "SalesOrderPayment",
+ "table": "payco",
+ "idSequenceName": "payco_payco_id_seq",
+ "lockable": true,
+ "lockTable": "payco",
+ "isRest": true,
+ "comment": "Sales Order Payment Map",
+ "privileges": {
+ "all": {
+ "create": "ProcessCreditCards",
+ "read": "ProcessCreditCards",
+ "update": "ProcessCreditCards",
+ "delete": false
+ }
+ },
+ "properties": [
+ {
+ "name": "id",
+ "attr": {
+ "type": "Number",
+ "column": "payco_id",
+ "isPrimaryKey": true
+ }
+ },
+ {
+ "name": "uuid",
+ "attr": {
+ "type": "String",
+ "column": "obj_uuid",
+ "isNaturalKey": true
+ }
+ },
+ {
+ "name": "payment",
+ "toOne": {
+ "isNested": true,
+ "type": "CreditCardPayment",
+ "column": "payco_ccpay_id"
+ }
+ },
+ {
+ "name": "salesOrder",
+ "toOne": {
+ "type": "SalesOrderRelation",
+ "column": "payco_cohead_id"
+ }
+ },
+ {
+ "name": "amount",
+ "attr": {
+ "type": "Number",
+ "column": "payco_amount"
+ }
+ }
+ ],
+ "isSystem": true
+ },
+ {
+ "context": "xtuple",
+ "nameSpace": "XM",
+ "type": "CreditCardPayment",
+ "table": "ccpay",
+ "idSequenceName": "ccpay_ccpay_id_seq",
+ "lockable": true,
+ "lockTable": "ccpay",
+ "isRest": true,
+ "comment": "Credit Card Payment Map",
+ "privileges": {
+ "all": {
+ "create": "ProcessCreditCards",
+ "read": "ProcessCreditCards",
+ "update": "ProcessCreditCards",
+ "delete": false
+ }
+ },
+ "properties": [
+ {
+ "name": "id",
+ "attr": {
+ "type": "Number",
+ "column": "ccpay_id",
+ "isPrimaryKey": true
+ }
+ },
+ {
+ "name": "uuid",
+ "attr": {
+ "type": "String",
+ "column": "obj_uuid",
+ "isNaturalKey": true
+ }
+ },
+ {
+ "name": "creditCard",
+ "toOne": {
+ "type": "CreditCard",
+ "column": "ccpay_ccard_id"
+ }
+ },
+ {
+ "name": "customer",
+ "toOne": {
+ "type": "CustomerRelation",
+ "column": "ccpay_cust_id"
+ }
+ },
+ {
+ "name": "amount",
+ "attr": {
+ "type": "Number",
+ "column": "ccpay_amount"
+ }
+ },
+ {
+ "name": "wasPreauthorization",
+ "attr": {
+ "type": "Boolean",
+ "column": "ccpay_auth"
+ }
+ },
+ {
+ "name": "status",
+ "attr": {
+ "type": "String",
+ "column": "ccpay_status"
+ }
+ },
+ {
+ "name": "type",
+ "attr": {
+ "type": "String",
+ "column": "ccpay_type"
+ }
+ },
+ {
+ "name": "originalType",
+ "attr": {
+ "type": "String",
+ "column": "ccpay_auth_charge"
+ }
+ },
+ {
+ "name": "orderNumber",
+ "attr": {
+ "type": "String",
+ "column": "ccpay_order_number"
+ }
+ },
+ {
+ "name": "orderNumberSeq",
+ "attr": {
+ "type": "Number",
+ "column": "ccpay_order_number_seq"
+ }
+ },
+ {
+ "name": "gatewayTransId",
+ "attr": {
+ "type": "String",
+ "column": "ccpay_r_ordernum"
+ }
+ },
+ {
+ "name": "gatewayAuthCode",
+ "attr": {
+ "type": "String",
+ "column": "ccpay_r_code"
+ }
+ },
+ {
+ "name": "gatewayTransDate",
+ "attr": {
+ "type": "Date",
+ "column": "ccpay_yp_r_time"
+ }
+ },
+ {
+ "name": "gatewayAvsCode",
+ "attr": {
+ "type": "String",
+ "column": "ccpay_r_avs"
+ }
+ },
+ {
+ "name": "gatewayTruncPan",
+ "attr": {
+ "type": "String",
+ "column": "ccpay_card_pan_trunc"
+ }
+ },
+ {
+ "name": "gatewayCardType",
+ "attr": {
+ "type": "String",
+ "column": "ccpay_card_type"
+ }
+ },
+ {
+ "name": "gatewayApproved",
+ "attr": {
+ "type": "String",
+ "column": "ccpay_r_approved"
+ }
+ },
+ {
+ "name": "gatewayMessage",
+ "attr": {
+ "type": "String",
+ "column": "ccpay_r_message"
+ }
+ },
+ {
+ "name": "gatewayError",
+ "attr": {
+ "type": "String",
+ "column": "ccpay_r_error"
+ }
+ }
+ ],
+ "isSystem": true
+ },
{
"context": "xtuple",
"nameSpace": "SYS",
"_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_postCCCashReceipt1": "Cannot post this Cash Receipt because annot find the default Bank Account for this Credit Card.",
"_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.",
"public/tables/vendaddrinfo.sql",
"public/tables/wo.sql",
"public/tables/womatl.sql",
+ "xt/functions/grant_role_priv.sql",
"xt/functions/add_priv.sql",
+ "public/tables/priv.sql",
"xt/functions/add_role.sql",
"xt/functions/add_report_definition.sql",
"xt/functions/average_cost.sql",
"xt/functions/cntctrestore.sql",
"xt/functions/createuser.sql",
"xt/functions/cust_outstanding_credit.sql",
- "xt/functions/grant_role_priv.sql",
"xt/functions/grant_role_ext.sql",
"xt/functions/grant_user_role.sql",
"xt/functions/install_guiscript.sql",
"public/tables/comment_trigger.sql",
"public/tables/pkghead.sql",
"public/tables/schemaord.sql",
- "priv.sql",
+ "grant_roles.sql",
"update_version.sql"
]
}
--- /dev/null
+select xt.add_priv('InstallExtension', 'Can Install Extensions', 'command_center', 'CommandCenter');
"CCCompany",
"CCTest",
"CCRequireCCV",
+ "DashboardLite",
"DefaultPriority",
"RequireProjectAssignment",
"UseProjects"
"use strict";
var lang = XT.stringsFor("en_US", {
+ "_assignedIncidents": "Assigned Incidents",
"_crm": "CRM",
"_crmDescription": "Corporate Relationship Management",
"_highPriority": "High Priority",
"_incidentDefaultPublic": "Comment Default Public",
"_incidentStatusColors": "Incident Status Colors",
- "_openIncidents": "Open Incidents",
+ "_opportunitiesNext30Days": "Opportunities Next 30 Days",
"_maintainEmailProfiles": "Maintain Email Profiles",
"_staleAnalysisWarning": "Free trial demo analysis data will not be updated from your live changes.",
"_viewEmailProfiles": "View Email Profiles"
]
};
+ if (XT.session.settings.get("DashboardLite")) {
+ var dashboardModule = {
+ name: "dashboardLite",
+ label: "_dashboard".loc(),
+ panels: [
+ {
+ name: "dashboardLite",
+ kind: "XV.DashboardLite",
+ newActions: [
+ {name: "assignedIncidents", label: "_assignedIncidents".loc(), item: "XV.AssignedIncidentBarChart"},
+ {name: "opportunities", label: "_opportunities".loc(), item: "XV.OpportunityBarChart"}
+ ]
+ }
+ ]
+ };
+
+ XT.app.$.postbooks.insertModule(dashboardModule, 0);
+ }
+
isBiAvailable = XT.session.config.biAvailable && XT.session.privileges.get("ViewSalesHistory");
if (isBiAvailable) {
module.panels.push({name: "analysisPage", kind: "analysisFrame"});
+++ /dev/null
-/*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, XM:true, XV:true, _:true, window: true, enyo:true, nv:true, d3:true, console:true */
-
-(function () {
-
- enyo.kind({
- name: "XV.CrmDashboard",
- kind: "XV.Dashboard",
- collection: "XM.UserChartCollection",
- // this tells the default query what extension to pull charts for
- extension: "crm",
- // title is what show in the "add chart" picker on the
- // dashboard and the chart is the widget to be added
- newActions: [
- {name: "openIncidents", label: "_openIncidents".loc(), item: "XV.OpenIncidentBarChart"},
- {name: "opportunities", label: "_opportunities".loc(), item: "XV.OpportunityBarChart"}
- ]
- });
-}());
enyo.depends(
"list_relations.js",
"list_relations_box.js",
- "dashboard.js",
"workspace.js"
);
XT.extensions.crm.initCharts = function () {
enyo.kind({
- name: "XV.OpenIncidentBarChart",
+ name: "XV.AssignedIncidentBarChart",
kind: "XV.DrilldownBarChart",
collection: "XM.IncidentListItemCollection",
- chartTitle: "_openIncidents".loc(),
+ chartTitle: "_assignedIncidents".loc(),
filterOptions: [
{ name: "all", parameters: [] },
{ name: "highPriority", parameters: [
{ name: "priority" },
{ name: "project" }
],
- // suppress closed incidents
+ // assigned incidents only
query: {
parameters: [{
attribute: "status",
- operator: "!=",
- value: "L"
+ operator: "=",
+ value: "A"
}],
}
});
name: "XV.OpportunityBarChart",
kind: "XV.DrilldownBarChart",
collection: "XM.OpportunityListItemCollection",
- chartTitle: "_opportunities".loc(),
+ chartTitle: "_opportunitiesNext30Days".loc(),
groupByOptions: [
{ name: "opportunityStage", content: "_stage".loc() },
{ name: "opportunitySource", content: "_source".loc() },
{ name: "assignedTo" },
{ name: "priority" }
],
+ query: {
+ parameters: [{
+ attribute: "targetClose",
+ operator: ">=",
+ value: XT.date.applyTimezoneOffset(XV.Date.prototype.textToDate("0"), true)
+ }, {
+ attribute: "targetClose",
+ operator: "<=",
+ value: XT.date.applyTimezoneOffset(XV.Date.prototype.textToDate("+30"), true)
+ }]
+ },
totalField: "amount"
});
"_autoAllocateCreditMemos": "Allocate Credit Memos to New Sales Order on Save",
"_autoSelectForBilling": "Check 'Select for Billing' option on Ship Order",
"_bookings": "Bookings",
+ "_bookingsNext30Days": "Bookings Next 30 Days",
"_convert": "Convert",
"_creditControl": "Credit Control",
"_creditMemo": "Credit Memo",
"_sales": "Sales",
"_salesDescription": "Customer and Sales Order Management",
"_salesHistory": "Sales History",
+ "_salesHistoryLast30Days": "Sales History Last 30 Days",
"_salesOrder": "Sales Order",
"_salesOrderAck": "Sales Order Acknowledgement",
"_scheduled": "Scheduled",
/*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, XV:true, XM:true, enyo:true, console:true */
+/*global XT:true, XV:true, XM:true, enyo:true, console:true, _:true */
(function () {
]
};
+ if (XT.session.settings.get("DashboardLite")) {
+ // TODO if we commit to this approach it would make sense to move this code into
+ // XT.app.$.postbooks.insertDashboardCharts() or something like it
+ var newActions = [
+ {name: "salesHistory", label: "_salesHistory".loc(), item: "XV.SalesHistoryTimeSeriesChart"},
+ {name: "bookings", label: "_bookings".loc(), item: "XV.SalesOrderTimeSeriesChart"}
+ ];
+ var preExistingDashboard = _.find(XT.app.$.postbooks.modules, function (module) {
+ return module.name === "dashboardLite";
+ });
+
+ if (preExistingDashboard) {
+ preExistingDashboard.panels[0].newActions = _.union(preExistingDashboard.panels[0].newActions, newActions);
+
+ } else {
+ var dashboardModule = {
+ name: "dashboardLite",
+ label: "_dashboard".loc(),
+ panels: [
+ {
+ name: "dashboardLite",
+ kind: "XV.DashboardLite",
+ newActions: newActions
+ }
+ ]
+ };
+
+ XT.app.$.postbooks.insertModule(dashboardModule, 0);
+ }
+ }
+
isBiAvailable = XT.session.config.biAvailable && XT.session.privileges.get("ViewSalesHistory");
if (isBiAvailable) {
module.panels.push({name: "salesAnalysisPage", kind: "analysisFrame"});
+++ /dev/null
-/*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, XM:true, XV:true, _:true, window: true, enyo:true, nv:true, d3:true, console:true */
-
-(function () {
-
- enyo.kind({
- name: "XV.SalesDashboard",
- kind: "XV.Dashboard",
- collection: "XM.UserChartCollection",
- // title is what show in the "add chart" picker on the
- // dashboard and the chart is the widget to be added
- // this tells the default query what extension to pull charts for
- extension: "sales",
- newActions: [
- {name: "salesHistory", label: "_salesHistory".loc(), item: "XV.SalesHistoryTimeSeriesChart"},
- {name: "bookings", label: "_bookings".loc(), item: "XV.SalesOrderTimeSeriesChart"}
- ]
- });
-
-}());
"list.js",
"list_relations.js",
"list_relations_box.js",
- "dashboard.js",
"workspace.js"
);
(function () {
-
-/*
-unused and out of date. if we want to use this, add correct parameters to
-filter options
- enyo.kind({
- name: "XV.SalesHistoryBarChart",
- kind: "XV.DrilldownBarChart",
- collection: "XM.SalesHistoryCollection",
- chartTitle: "_salesHistory".loc(),
- drillDownAttr: "orderNumber",
- drillDownRecordType: "XM.SalesOrderRelation",
- filterOptions: [
- { name: "today" },
- { name: "thisWeek" },
- { name: "thisMonth" },
- { name: "thisYear" },
- { name: "twoYears" },
- { name: "fiveYears" }
- ],
- groupByOptions: [
- { name: "customer" },
- { name: "salesRep" }
- ],
- totalField: "totalPrice",
- filterData: filterData
- });
-*/
-
enyo.kind({
name: "XV.SalesHistoryTimeSeriesChart",
kind: "XV.TimeSeriesChart",
collection: "XM.SalesHistoryCollection",
- chartTitle: "_salesHistory".loc(),
+ chartTitle: "_salesHistoryLast30Days".loc(),
groupByOptions: [
{ name: "" },
{ name: "customer" },
{ name: "salesRep" }
],
+ query: {
+ parameters: [{
+ attribute: "shipDate",
+ operator: ">=",
+ value: XT.date.applyTimezoneOffset(XV.Date.prototype.textToDate("-30"), true)
+ }]
+ },
dateField: "shipDate",
totalField: "totalPrice"
});
name: "XV.SalesOrderTimeSeriesChart",
kind: "XV.TimeSeriesChart",
collection: "XM.SalesOrderListItemCollection",
- chartTitle: "_bookings".loc(),
+ chartTitle: "_bookingsNext30Days".loc(),
groupByOptions: [
{ name: "" },
{ name: "customer" },
{ name: "salesRep" }
],
+ query: {
+ parameters: [{
+ attribute: "orderDate",
+ operator: ">=",
+ value: XT.date.applyTimezoneOffset(XV.Date.prototype.textToDate("0"), true)
+ }, {
+ attribute: "orderDate",
+ operator: "<=",
+ value: XT.date.applyTimezoneOffset(XV.Date.prototype.textToDate("+30"), true)
+ }]
+ },
dateField: "orderDate",
totalField: "total"
});
"public/trigger_functions/cashrcptitem.sql",
"public/trigger_functions/cashrcptmisc.sql",
"public/trigger_functions/ccard.sql",
+ "public/trigger_functions/ccpay.sql",
"public/trigger_functions/char.sql",
"public/trigger_functions/charass.sql",
"public/trigger_functions/charopt.sql",
"public/tables/bankrecitem.sql",
"public/tables/cashrcpt.sql",
+ "public/tables/ccpay.sql",
+ "public/tables/ccbank.sql",
"public/tables/metric.sql",
+ "public/tables/payco.sql",
"public/tables/priv.sql",
"public/tables/tax.sql",
"public/tables/taxpay.sql",
"public/tables/report/WarehouseMasterList.xml",
"public/tables/report/items.xml",
- "public/patches/fixacl.sql"
+ "public/patches/fixacl.sql",
+ "public/patches/populate_ccpay_card_type.sql"
]
}
pdoctype TEXT DEFAULT NULL,
pamount NUMERIC DEFAULT NULL) RETURNS INTEGER AS
$$
--- Copyright (c) 1999-2014 by OpenMFG LLC, d/b/a xTuple.
+-- Copyright (c) 1999-2014 by OpenMFG LLC, d/b/a xTuple.
-- See www.xtuple.com/CPAL for the full text of the software license.
DECLARE
_aropenid INTEGER;
BEGIN
SELECT * INTO _c
- FROM ccpay, ccard, custinfo
- WHERE ( (ccpay_id = pCCpay)
- AND (ccpay_ccard_id = ccard_id)
- AND (ccpay_cust_id = cust_id) );
+ FROM ccpay
+ JOIN custinfo ON ccpay_cust_id = cust_id
+ WHERE (ccpay_id = pCCpay);
IF (NOT FOUND) THEN
RAISE EXCEPTION 'Cannot find the Credit Card transaction information [xtuple: postCCcashReceipt, -11, %]',
END IF;
SELECT bankaccnt_id, bankaccnt_accnt_id INTO _bankaccnt_id, _realaccnt
- FROM ccbank JOIN bankaccnt ON (ccbank_bankaccnt_id=bankaccnt_id)
- WHERE (ccbank_ccard_type=_c.ccard_type);
+ FROM ccbank
+ JOIN bankaccnt ON (ccbank_bankaccnt_id=bankaccnt_id)
+ WHERE (ccbank_ccard_type=_c.ccpay_card_type);
IF (_bankaccnt_id IS NULL) THEN
RAISE EXCEPTION 'Cannot find the default Bank Account for this Credit Card [xtuple: postCCcredit, -1, %]',
- _c.ccard_type;
+ _c.ccpay_card_type;
END IF;
- _ccOrderDesc := (_c.ccard_type || '-' || _c.ccpay_order_number::TEXT ||
- '-' || _c.ccpay_order_number_seq::TEXT);
+ _ccOrderDesc := (_c.ccpay_card_type || '-' || _c.ccpay_order_number::TEXT ||
+ '-' || _c.ccpay_order_number_seq::TEXT);
_journal := fetchJournalNumber('C/R');
cashrcpt_usecustdeposit
) VALUES (
_c.ccpay_cust_id, _c.ccpay_amount, _c.ccpay_curr_id,
- _c.ccard_type, _c.ccpay_r_ordernum, _ccOrderDesc,
+ _c.ccpay_card_type, _c.ccpay_r_ordernum, _ccOrderDesc,
CURRENT_DATE, _bankaccnt_id,
fetchMetricBool('EnableCustomerDeposits'))
RETURNING cashrcpt_id INTO _return;
SET cashrcpt_cust_id=_c.ccpay_cust_id,
cashrcpt_amount=_c.ccpay_amount,
cashrcpt_curr_id=_c.ccpay_curr_id,
- cashrcpt_fundstype=_c.ccard_type,
+ cashrcpt_fundstype=_c.ccpay_card_type,
cashrcpt_docnumber=_c.ccpay_r_ordernum,
cashrcpt_notes=_ccOrderDesc,
cashrcpt_distdate=CURRENT_DATE,
_return := _aropenid;
END IF;
- PERFORM insertGLTransaction(_journal, 'A/R', 'CR', _ccOrderDesc,
+ PERFORM insertGLTransaction(_journal, 'A/R', 'CR', _ccOrderDesc,
('Cash Receipt from Credit Card ' || _c.cust_name),
findPrepaidAccount(_c.ccpay_cust_id),
_realaccnt,
NULL,
- ROUND(currToBase(_c.ccpay_curr_id,
- _c.ccpay_amount,
- _c.ccpay_transaction_datetime::DATE),2),
+ ROUND(currToBase(_c.ccpay_curr_id,
+ _c.ccpay_amount,
+ _c.ccpay_transaction_datetime::DATE),2),
CURRENT_DATE);
RETURN _return;
CREATE OR REPLACE FUNCTION postCCcredit(INTEGER, TEXT, INTEGER) RETURNS INTEGER AS $$
--- Copyright (c) 1999-2014 by OpenMFG LLC, d/b/a xTuple.
+-- Copyright (c) 1999-2014 by OpenMFG LLC, d/b/a xTuple.
-- See www.xtuple.com/CPAL for the full text of the software license.
DECLARE
- pCCpay ALIAS FOR $1;
+ pCCpay ALIAS FOR $1;
preftype ALIAS FOR $2;
prefid ALIAS FOR $3;
- _c RECORD;
- _ccOrderDesc TEXT;
+ _c RECORD;
+ _ccOrderDesc TEXT;
_cglaccnt INTEGER;
- _dglaccnt INTEGER;
+ _dglaccnt INTEGER;
_glseriesres INTEGER;
- _notes TEXT := 'Credit via Credit Card';
- _r RECORD;
- _sequence INTEGER;
- _dmaropenid INTEGER;
+ _notes TEXT := 'Credit via Credit Card';
+ _r RECORD;
+ _sequence INTEGER;
+ _dmaropenid INTEGER;
BEGIN
IF ((preftype = 'cohead') AND NOT EXISTS(SELECT cohead_id
- FROM cohead
- WHERE (cohead_id=prefid))) THEN
+ FROM cohead
+ WHERE (cohead_id=prefid))) THEN
RAISE EXCEPTION 'Cannot find original Sales Order for this Credit Card credit [xtuple: postCCcredit, -2, %, %, %]',
pCCpay, preftype, prefid;
ELSIF ((preftype = 'aropen') AND NOT EXISTS(SELECT aropen_id
- FROM aropen
- WHERE (aropen_id=prefid))) THEN
+ FROM aropen
+ WHERE (aropen_id=prefid))) THEN
RAISE EXCEPTION 'Cannot find original A/R Open record for this Credit Card credit [xtuple: postCCcredit, -2, %, %, %]',
pCCpay, preftype, prefid;
ELSIF ((preftype = 'cmhead') AND NOT EXISTS(SELECT cmhead_id
- FROM cmhead
- WHERE cmhead_id=prefid)) THEN
+ FROM cmhead
+ WHERE cmhead_id=prefid)) THEN
RAISE EXCEPTION 'Cannot find original Credit Memo record for this Credit Card credit [xtuple: postCCcredit, -2, %, %, %]',
pCCpay, preftype, prefid;
END IF;
SELECT * INTO _c
- FROM ccpay
- JOIN ccard ON (ccpay_ccard_id = ccard_id)
- JOIN ccbank ON (ccard_type=ccbank_ccard_type)
- WHERE (ccpay_id = pCCpay);
+ FROM ccpay
+ WHERE (ccpay_id = pCCpay);
IF (NOT FOUND) THEN
RAISE EXCEPTION 'Cannot find the record for this Credit Card credit [xtuple: postCCcredit, -3, %, %, %]',
END IF;
SELECT bankaccnt_accnt_id INTO _cglaccnt
- FROM bankaccnt
- WHERE (bankaccnt_id=_c.ccbank_bankaccnt_id);
+ FROM ccbank
+ JOIN bankaccnt ON (ccbank_bankaccnt_id=bankaccnt_id)
+ WHERE (ccbank_ccard_type=_c.ccpay_card_type);
IF (NOT FOUND) THEN
RAISE EXCEPTION 'Cannot find the default Bank Account for this Credit Card [xtuple: postCCcredit, -1, %]',
_sequence := fetchGLSequence();
IF (_c.ccpay_r_ref IS NOT NULL) THEN
- _ccOrderDesc := (_c.ccard_type || '-' || _c.ccpay_r_ref);
+ _ccOrderDesc := (_c.ccpay_card_type || '-' || _c.ccpay_r_ref);
ELSE
- _ccOrderDesc := (_c.ccard_type || '-' || _c.ccpay_order_number::TEXT ||
- '-' || COALESCE(_c.ccpay_order_number_seq::TEXT, ''));
+ _ccOrderDesc := (_c.ccpay_card_type || '-' || _c.ccpay_order_number::TEXT ||
+ '-' || COALESCE(_c.ccpay_order_number_seq::TEXT, ''));
END IF;
_glseriesres := insertIntoGLSeries(_sequence, 'A/R', 'CC', _ccOrderDesc,
IF (FOUND) THEN
SELECT createardebitmemo(
- NULL,
+ NULL,
_r.aropen_cust_id, NULL, fetchARMemoNumber(),
_r.aropen_ordernumber, current_date, _c.ccpay_amount,
_notes,
- -1, -1, -1, CURRENT_DATE, -1, NULL, 0,
+ -1, -1, -1, CURRENT_DATE, -1, NULL, 0,
_r.aropen_curr_id) INTO _dmaropenid;
IF (_r.aropen_open) THEN
PERFORM applyARCreditMemoToBalance(_r.aropen_id, _dmaropenid);
PERFORM postARCreditMemoApplication(_r.aropen_id);
END IF;
-
+
END IF;
IF (preftype = 'cohead') THEN
INSERT INTO payco (
- payco_ccpay_id, payco_cohead_id, payco_amount, payco_curr_id
+ payco_ccpay_id, payco_cohead_id, payco_amount, payco_curr_id
) VALUES (
pCCpay, prefid, 0 - _c.ccpay_amount, _c.ccpay_curr_id
);
IF (_numfound <= 0) THEN
INSERT INTO ccbank (ccbank_ccard_type, ccbank_bankaccnt_id)
VALUES (pccardtype, pbankaccntid)
- RETURNING _ccbankid;
+ RETURNING ccbank_id INTO _ccbankid;
END IF;
RETURN _ccbankid;
--- /dev/null
+-- Issue #23459 adds ccpay_card_type. Populate it from historical ccard relations.
+UPDATE ccpay SET ccpay_card_type = (SELECT ccard_type FROM ccard WHERE ccard_id = ccpay_ccard_id)
+WHERE ccpay_ccard_id IS NOT NULL;
--- /dev/null
+ALTER TABLE ccbank DROP CONSTRAINT IF EXISTS ccbank_ccbank_ccard_type_check;
+ALTER TABLE ccbank ADD CONSTRAINT ccbank_ccbank_ccard_type_check
+ CHECK (ccbank_ccard_type = ANY (ARRAY['A', 'D', 'M', 'P', 'V', 'O']));
--- /dev/null
+-- Add columns for data needed for external pre-auths that will have no ccpay_ccard_id.
+select xt.add_column('ccpay','ccpay_card_pan_trunc', 'text', null, 'public', 'External Pre-Auth truncated PAN. Last four digits of the card.');
+-- TODO: PayPal
+--select xt.add_column('ccpay','ccpay_card_type', 'text', null, 'public', 'External Pre-Auth card type: V=Visa, M=MasterCard, A=American Express, D=Discover, P=Paypal.');
+select xt.add_column('ccpay','ccpay_card_type', 'text', null, 'public', 'External Pre-Auth card type: V=Visa, M=MasterCard, A=American Express, D=Discover.');
SELECT DISTINCT -1 AS id,
10 AS type,
'0' AS subtype,
- 1 AS section,
+ 2 AS section,
<? value("quotes") ?> AS section_qtdisplayrole,
<? value("quotes") ?> AS name,
NULL::text AS status,
SELECT quhead_id AS id,
15 AS type,
quhead_number AS subtype,
- 1 AS section,
+ 2 AS section,
<? value("quotes") ?> AS section_qtdisplayrole,
quhead_number AS name,
CASE WHEN (quhead_status = 'C') THEN
SELECT quitem_id AS id,
17 AS type,
quhead_number AS subtype,
- 1 AS section,
+ 2 AS section,
<? value("quotes") ?> AS section_qtdisplayrole,
quitem_linenumber::text AS name,
CASE WHEN (quhead_status = 'C') THEN
SELECT quhead_id AS id,
18 AS type,
quhead_number AS subtype,
- 1 AS section,
+ 2 AS section,
<? value("quotes") ?> AS section_qtdisplayrole,
<? value("total") ?> AS name,
NULL AS status,
SELECT -1 AS id,
19 AS type,
MAX(quhead_number) AS subtype,
- 1 AS section,
+ 2 AS section,
<? value("quotes") ?> AS section_qtdisplayrole,
<? value("total") ?> || ' ' || <? value("quotes") ?> AS name,
NULL AS status,
SELECT DISTINCT -1 AS id,
20 AS type,
'0' AS subtype,
- 2 AS section,
+ 3 AS section,
<? value("sos") ?> AS section_qtdisplayrole,
<? value("sos") ?> AS name,
NULL::text AS status,
SELECT cohead_id AS id,
25 AS type,
cohead_number::text AS subtype,
- 2 AS section,
+ 3 AS section,
<? value("sos") ?> AS section_qtdisplayrole,
cohead_number::text AS name,
COALESCE((SELECT
SELECT coitem_id AS id,
27 AS type,
cohead_number::text AS subtype,
- 2 AS section,
+ 3 AS section,
<? value("sos") ?> AS section_qtdisplayrole,
coitem_linenumber::text AS name,
CASE WHEN (coitem_status = 'O') THEN
SELECT cohead_id AS id,
28 AS type,
cohead_number::text AS subtype,
- 2 AS section,
+ 3 AS section,
<? value("sos") ?> AS section_qtdisplayrole,
<? value("total") ?> AS name,
NULL AS status,
SELECT -1 AS id,
29 AS type,
MAX(cohead_number::text) AS subtype,
- 2 AS section,
+ 3 AS section,
<? value("sos") ?> AS section_qtdisplayrole,
<? value("total") ?> || ' ' || <? value("sos") ?> AS name,
NULL AS status,
SELECT DISTINCT -1 AS id,
30 AS type,
'0' AS subtype,
- 3 AS section,
+ 4 AS section,
<? value("invoices") ?> AS section_qtdisplayrole,
<? value("invoices") ?> AS name,
NULL::text AS status,
SELECT invchead_id AS id,
35 AS type,
invchead_invcnumber::text AS subtype,
- 3 AS section,
+ 4 AS section,
<? value("invoices") ?> AS section_qtdisplayrole,
invchead_invcnumber::text AS name,
CASE WHEN (invchead_posted) THEN
SELECT invcitem_id AS id,
37 AS type,
invchead_invcnumber::text AS subtype,
- 3 AS section,
+ 4 AS section,
<? value("invoices") ?> AS section_qtdisplayrole,
invcitem_linenumber::text AS name,
CASE WHEN (invchead_posted) THEN
SELECT invchead_id AS id,
38 AS type,
invchead_invcnumber::text AS subtype,
- 3 AS section,
+ 4 AS section,
<? value("invoices") ?> AS section_qtdisplayrole,
<? value("total") ?> AS name,
NULL AS status,
SELECT -1 AS id,
39 AS type,
MAX(invchead_invcnumber::text) AS subtype,
- 3 AS section,
+ 4 AS section,
<? value("invoices") ?> AS section_qtdisplayrole,
<? value("total") ?> || ' ' || <? value("invoices") ?> AS name,
NULL AS status,
SELECT DISTINCT -1 AS id,
40 AS type,
'0' AS subtype,
- 4 AS section,
+ 5 AS section,
<? value("wos") ?> AS section_qtdisplayrole,
<? value("wos") ?> AS name,
NULL::text AS status,
SELECT wo_id AS id,
45 AS type,
formatWoNumber(wo_id) AS subtype,
- 4 AS section,
+ 5 AS section,
<? value("wos") ?> AS section_qtdisplayrole,
formatWoNumber(wo_id) AS name,
CASE WHEN (wo_status = 'O') THEN
SELECT -1 AS id,
49 AS type,
MAX(formatWoNumber(wo_id)) AS subtype,
- 4 AS section,
+ 5 AS section,
<? value("wos") ?> AS section_qtdisplayrole,
<? value("total") ?> || ' ' || <? value("wos") ?> AS name,
NULL AS status,
SELECT DISTINCT -1 AS id,
50 AS type,
'0' AS subtype,
- 5 AS section,
+ 6 AS section,
<? value("prs") ?> AS section_qtdisplayrole,
<? value("prs") ?> AS name,
NULL::text AS status,
SELECT pr_id AS id,
55 AS type,
pr_number::text || '-' || pr_subnumber::text AS subtype,
- 5 AS section,
+ 6 AS section,
<? value("prs") ?> AS section_qtdisplayrole,
pr_number::text || '-' || pr_subnumber::text AS name,
<? value("open") ?> AS status,
SELECT -1 AS id,
59 AS type,
MAX(pr_number::text || '-' || pr_subnumber::text) AS subtype,
- 5 AS section,
+ 6 AS section,
<? value("prs") ?> AS section_qtdisplayrole,
<? value("total") ?> || ' ' || <? value("prs") ?> AS name,
NULL AS status,
SELECT DISTINCT -1 AS id,
60 AS type,
'0' AS subtype,
- 6 AS section,
+ 7 AS section,
<? value("pos") ?> AS section_qtdisplayrole,
<? value("pos") ?> AS name,
NULL::text AS status,
SELECT pohead_id AS id,
65 AS type,
pohead_number::text AS subtype,
- 6 AS section,
+ 7 AS section,
<? value("pos") ?> AS section_qtdisplayrole,
pohead_number::text AS name,
CASE WHEN (pohead_status = 'U') THEN
SELECT poitem_id AS id,
67 AS type,
pohead_number::text AS subtype,
- 6 AS section,
+ 7 AS section,
<? value("pos") ?> AS section_qtdisplayrole,
poitem_linenumber::text AS name,
CASE WHEN (poitem_status = 'U') THEN
SELECT pohead_id AS id,
68 AS type,
pohead_number::text AS subtype,
- 6 AS section,
+ 7 AS section,
<? value("pos") ?> AS section_qtdisplayrole,
<? value("total") ?> AS name,
NULL AS status,
SELECT -1 AS id,
69 AS type,
MAX(pohead_number::text) AS subtype,
- 6 AS section,
+ 7 AS section,
<? value("sos") ?> AS section_qtdisplayrole,
<? value("total") ?> || ' ' || <? value("pos") ?> AS name,
NULL AS status,
-- Copyright (c) 1999-2014 by OpenMFG LLC, d/b/a xTuple.
-- See www.xtuple.com/CPAL for the full text of the software license.
+<? if exists("nominal") ?>
UPDATE ipsiteminfo SET
ipsitem_price=
<? if exists("updateByValue") ?>
<? endif ?>
WHERE ( ipsitem_item_id=item_id
AND selsched_ipshead_id=ipsitem_ipshead_id
+ AND ipsitem_type='N'
<? if exists("item_id") ?>
AND item_id=<? value("item_id") ?>
<? elseif exists("itemgrp_id") ?>
AND prodcat_code ~ <? value("prodcat_pattern") ?>
<? endif ?>
);
+<? endif ?>
+
+<? if exists("discount") ?>
+UPDATE ipsiteminfo SET
+ ipsitem_discntprcnt=
+<? if exists("updateByValue") ?>
+ ipsitem_discntprcnt + (<? value("updateBy") ?> / 100.0)
+<? else ?>
+ roundSale(ipsitem_discntprcnt * (1.0 + (<? value("updateBy") ?> / 100.0)))
+<? endif ?>
+FROM selsched, item
+<? if reExists("itemgrp") ?>
+ JOIN itemgrpitem ON (itemgrpitem_item_id=item_id)
+ JOIN itemgrp ON (itemgrpitem_itemgrp_id=itemgrp_id)
+<? elseif reExists("prodcat") ?>
+ JOIN prodcat ON (prodcat_id=item_prodcat_id)
+<? endif ?>
+WHERE ( ipsitem_item_id=item_id
+ AND selsched_ipshead_id=ipsitem_ipshead_id
+ AND ipsitem_type='D'
+<? if exists("item_id") ?>
+ AND item_id=<? value("item_id") ?>
+<? elseif exists("itemgrp_id") ?>
+ AND itemgrp_id=<? value("itemgrp_id") ?>
+<? elseif exists("itemgrp_pattern") ?>
+ AND itemgrp_name ~ <? value("itemgrp_pattern") ?>
+<? elseif exists("prodcat_id") ?>
+ AND prodcat_id=<? value("prodcat_id") ?>
+<? elseif exists("prodcat_pattern") ?>
+ AND prodcat_code ~ <? value("prodcat_pattern") ?>
+<? endif ?>
+);
+<? endif ?>
+
+<? if exists("markup") ?>
+UPDATE ipsiteminfo SET
+ ipsitem_discntprcnt=
+<? if exists("updateByValue") ?>
+ ipsitem_discntprcnt + (<? value("updateBy") ?> / 100.0)
+<? else ?>
+ roundSale(ipsitem_discntprcnt * (1.0 + (<? value("updateBy") ?> / 100.0)))
+<? endif ?>
+FROM selsched, item
+<? if reExists("itemgrp") ?>
+ JOIN itemgrpitem ON (itemgrpitem_item_id=item_id)
+ JOIN itemgrp ON (itemgrpitem_itemgrp_id=itemgrp_id)
+<? elseif reExists("prodcat") ?>
+ JOIN prodcat ON (prodcat_id=item_prodcat_id)
+<? endif ?>
+WHERE ( ipsitem_item_id=item_id
+ AND selsched_ipshead_id=ipsitem_ipshead_id
+ AND ipsitem_type='M'
+<? if exists("item_id") ?>
+ AND item_id=<? value("item_id") ?>
+<? elseif exists("itemgrp_id") ?>
+ AND itemgrp_id=<? value("itemgrp_id") ?>
+<? elseif exists("itemgrp_pattern") ?>
+ AND itemgrp_name ~ <? value("itemgrp_pattern") ?>
+<? elseif exists("prodcat_id") ?>
+ AND prodcat_id=<? value("prodcat_id") ?>
+<? elseif exists("prodcat_pattern") ?>
+ AND prodcat_code ~ <? value("prodcat_pattern") ?>
+<? endif ?>
+);
+<? endif ?>
--- /dev/null
+-- TODO: Add a precheck in the upgrade package.xml for this.
+select xt.add_constraint('payco','payco_unique_ccpay_id_cohead_id', 'unique(payco_ccpay_id, payco_cohead_id)', 'public');
+-- Add primary key.
+select xt.add_column('payco','payco_id', 'serial', 'primary key', 'public', 'payco table primary key.');
<title>Item Costs By Class Code</title>
<name>ItemCostsByClassCode</name>
<description></description>
+ <grid>
+ <snap/>
+ <show/>
+ <x>0.05</x>
+ <y>0.05</y>
+ </grid>
<size>Letter</size>
<portrait/>
<topmargin>50</topmargin>
FROM item, classcode, uom
WHERE ((item_classcode_id=classcode_id)
AND (item_inv_uom_id=uom_id)
+ <? if exists("onlyShowActive") ?>
+ AND (item_active)
+ <? endif ?>
<? if exists("classcode_id") ?>
AND (classcode_id=<? value("classcode_id") ?>)
<? elseif exists("classcode_pattern") ?>
--- /dev/null
+CREATE OR REPLACE FUNCTION _ccpayBeforeTrigger () RETURNS TRIGGER AS $$
+-- Copyright (c) 1999-2014 by OpenMFG LLC, d/b/a xTuple.
+-- See www.xtuple.com/CPAL for the full text of the software license.
+DECLARE
+ _cardType TEXT;
+
+BEGIN
+ IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
+ -- If ccpay_ccard_id is set, we don't care if ccpay_card_type is set,
+ -- we want to get the Card Type from ccard.
+ IF (NEW.ccpay_ccard_id IS NOT NULL) THEN
+ SELECT ccard_type INTO _cardType
+ FROM ccard
+ WHERE ccard_id = NEW.ccpay_ccard_id;
+
+ IF (_cardType IS NOT NULL) THEN
+ NEW.ccpay_card_type = _cardType;
+ END IF;
+ END IF;
+ END IF;
+
+ RETURN NEW;
+END;
+$$ LANGUAGE 'plpgsql';
+
+SELECT dropIfExists('TRIGGER', 'ccpayBeforeTrigger');
+CREATE TRIGGER ccpayBeforeTrigger BEFORE INSERT OR UPDATE ON ccpay FOR EACH ROW EXECUTE PROCEDURE _ccpayBeforeTrigger();
QUESTION: 3,
/**
- Constant for `notify` message type question.
+ Constant for `notify` message type question with cancel option.
@static
@constant
*/
YES_NO_CANCEL: 4,
+ /**
+ Constant for `notify` message type ok/cancel.
+
+ @static
+ @constant
+ @type Number
+ @default 5
+ */
+ OK_CANCEL: 5,
+
_status: {
CLEAN: 0x0001, // 1
DIRTY: 0x0002, // 2
Variables for widths/colors
*/
@tile-width: 500px;
-@tile-height: 230px;
+@tile-height: 250px;
@picker-label: 100px;
@bottom-border: #444;
@icon-height: 32px;
@panelsFontSize: 5em;
@iconFontSize: 60%;
@totalsFontSize: 14px;
-@buttonFontSize: 17px;
+@buttonFontSize: 20px;
+@buttonTextFontSize: 17px;
// sizing
@defaultPanelWidth: 320px;
.onyx-button {
margin: 2px;
+ min-height: 40px;
min-width: 60px;
background: @button-gray;
color: @charcoal;
- font-size: 20px;
+ font-size: @buttonFontSize;
+
+ &[class^="icon-"]:before, &[class*=" icon-"]:before {
+ vertical-align: middle;
+ margin-right: 5px;
+ }
&.text {
- font-size: @buttonFontSize;
- font-family: @baseFont;
+ font-size: @buttonTextFontSize;
}
&.selected {
background: @slate-blue;
}
.xv-buttons .onyx-button {
margin: 2px;
+ min-height: 40px;
min-width: 60px;
background: #e1e1e1;
color: #666666;
font-size: 20px;
}
+.xv-buttons .onyx-button[class^="icon-"]:before,
+.xv-buttons .onyx-button[class*=" icon-"]:before {
+ vertical-align: middle;
+ margin-right: 5px;
+}
.xv-buttons .onyx-button.text {
font-size: 17px;
- font-family: 'Helvetica Neue', Helvetica, 'Nimbus Sans L', Arial, sans-serif;
}
.xv-buttons .onyx-button.selected {
background: #357ec7;
}
.dashboard .selectable-chart {
width: 500px;
- height: 230px;
+ height: 250px;
}
.dashboard .selectable-chart .chart-title-bar {
width: 500px;
}
});
+ enyo.kind({
+ name: "XV.DashboardLite",
+ kind: "XV.Dashboard",
+ collection: "XM.UserChartCollection",
+ // this tells the default query what extension to pull charts for
+ extension: "crm",
+ // title is what show in the "add chart" picker on the
+ // dashboard and the chart is the widget to be added
+ newActions: [] // to be added by extensions
+ });
}());
name: "navigationButtonPanel",
classes: "xv-buttons",
components: [
- {kind: "onyx.Button", name: "newButton", ontap: "newItem", classes: "icon-plus"},
+ {kind: "onyx.Button", name: "newButton", ontap: "newItem", content: "_new".loc(),
+ classes: "icon-plus text"},
{kind: "onyx.Button", name: "exportButton", ontap: "exportAttr",
- icon: "share", content: "_export".loc(), classes: "icon-share"}
+ classes: "icon-share text", content: "_export".loc()}
]
});
editor.setFirstFocus();
},
exportAttr: function (inSender, inEvent) {
- var gridbox = inEvent.originator.parent.parent;
+ var gridbox = this;
this.doExportAttr({ attr: gridbox.attr });
},
refreshLists: function () {
typeToButtonMap[String(XM.Model.WARNING)] = ["notifyOk"];
typeToButtonMap[String(XM.Model.CRITICAL)] = ["notifyOk"];
typeToButtonMap[String(XM.Model.QUESTION)] = ["notifyYes", "notifyNo"];
+ typeToButtonMap[String(XM.Model.OK_CANCEL)] = ["notifyOk", "notifyCancel"];
typeToButtonMap[String(XM.Model.YES_NO_CANCEL)] = ["notifyYes", "notifyNo", "notifyCancel"];
this.$.notifyMessage.setContent(inEvent.message);
// delete out any previously added customComponents/customComponentControls
if (this.$.notifyPopup.$.customComponent) {
this.$.notifyPopup.removeComponent(this.$.notifyPopup.$.customComponent);
-
+
customComponentControls = _.filter(that.$.notifyPopup.controls, function (control) {
return control.name === "customComponent";
});
notifyTap: function (inSender, inEvent) {
var notifyParameter,
callbackObj = {},
+ that = this,
optionsObj = this._notifyOptions || {};
this._notifyDone = true;
notifyParameter = false;
break;
case 'notifyCancel':
- notifyParameter = undefined;
+ notifyParameter = null;
break;
}
// the callback might make its own popup, which we do not want to hide.
this.$.notifyPopup.hide();
callbackObj.answer = notifyParameter;
if (this.$.notifyPopup.$.customComponent) {
+ if (this.$.notifyPopup.$.customComponent.getValueAsync) {
+ this.$.notifyPopup.$.customComponent.getValueAsync(function (result) {
+ callbackObj.componentValue = result;
+ that._notifyCallback(callbackObj, optionsObj);
+ });
+ return;
+ }
callbackObj.componentValue = this.$.notifyPopup.$.customComponent.getValue();
}
this._notifyCallback(callbackObj, optionsObj);
model.save(null, options);
} else {
- // answer === undefined means that the user wants to cancel this action
+ // answer === null means that the user wants to cancel this action
// fetching is a good way to throw out the changes
model.fetch({success: function () {
// tell the view to re-render
var that = this;
if (!this.getValue() ||
- !this.getValue().length ||
!this.getFilterAttr() ||
this.getGroupByAttr() === undefined ||
this.getGroupByAttr() === null) {
dateField: ""
},
filterOptions: [
- { name: "thisWeek" },
- { name: "thisMonth" },
- { name: "thisYear" },
- { name: "twoYears" },
- { name: "fiveYears" }
+ { name: "all", parameters: [] }
],
/**
This looks really complicated! I'm just binning the
aggregateData: function (groupedData) {
var that = this,
now = new Date().getTime(),
- earliestDate = now, // won't be now for long
+ earliestDate = now, // might not be now for long
+ latestDate = now, // might not be now for long
dataPoints = _.reduce(groupedData, function (memo, group) {
_.each(group, function (item) {
var dateInt = item.get(that.getDateField()).getTime();
earliestDate = Math.min(earliestDate, dateInt);
+ latestDate = Math.max(latestDate, dateInt);
});
return memo + group.length;
}, 0),
binCount = Math.ceil(Math.sqrt(dataPoints)),
- binWidth = Math.ceil((now - earliestDate) / binCount),
+ binWidth = Math.ceil((latestDate - earliestDate) / binCount),
histoGroup = _.map(groupedData, function (group, groupKey) {
var binnedData, summedData, hole, findHole, foundData;
findHole = function (datum) {
return datum.x === String(hole);
};
- for (hole = earliestDate; hole <= now; hole += binWidth) {
+ for (hole = earliestDate; hole <= latestDate; hole += binWidth) {
foundData = _.find(summedData, findHole);
if (!foundData) {
summedData.push({x: String(hole), y: 0});
return histoGroup;
},
- filterData: function (data, callback) {
- var that = this;
-
- callback(_.filter(data, function (datum) {
- var shipDate = datum.get(that.getDateField()).getTime(),
- now = new Date().getTime(),
- timespan = 0,
- oneDay = 1000 * 60 * 60 * 24;
-
- // XXX use YTD etc.?
- switch (that.getFilterAttr()) {
- case "today":
- timespan = oneDay;
- break;
- case "thisWeek":
- timespan = 7 * oneDay;
- break;
- case "thisMonth":
- timespan = 30 * oneDay;
- break;
- case "thisYear":
- timespan = 365 * oneDay;
- break;
- case "twoYears":
- timespan = 2 * 365 * oneDay;
- break;
- case "fiveYears":
- timespan = 5 * 365 * oneDay;
- break;
- }
- return shipDate + timespan >= now;
- }));
- },
/**
Make the chart using v3 and nv.d3, working off our this.processedData.
*/
filename = inEvent.value,
reader;
+ if (!file) {
+ return;
+ }
+
if (filename.indexOf("C:\\fakepath\\") === 0) {
// some browsers obnoxiously give you a fake path, but the only thing
// we want is the filename really.
var nodemailer = require("nodemailer"),
smtpOptions = {
host: X.options.datasource.smtpHost,
- secureConnection: false,
+ secureConnection: X.options.datasource.smtpPort === 465,
port: X.options.datasource.smtpPort
};
var that = this;
app.use(express.favicon(__dirname + '/views/login/assets/favicon.ico'));
-_.each(X.options.datasource.databases, function (orgValue, orgKey, orgList) {
- "use strict";
- app.use("/" + orgValue + '/client', express.static('../enyo-client/application', { maxAge: 86400000 }));
- app.use("/" + orgValue + '/core-extensions', express.static('../enyo-client/extensions', { maxAge: 86400000 }));
- app.use("/" + orgValue + '/private-extensions', express.static('../../private-extensions', { maxAge: 86400000 }));
- app.use("/" + orgValue + '/xtuple-extensions', express.static('../../xtuple-extensions', { maxAge: 86400000 }));
-});
+if (X.options.datasource.debugging) {
+ _.each(X.options.datasource.databases, function (orgValue, orgKey, orgList) {
+ "use strict";
+ app.use("/" + orgValue + '/client', express.static('../enyo-client/application', { maxAge: 86400000 }));
+ app.use("/" + orgValue + '/core-extensions', express.static('../enyo-client/extensions', { maxAge: 86400000 }));
+ app.use("/" + orgValue + '/private-extensions', express.static('../../private-extensions', { maxAge: 86400000 }));
+ app.use("/" + orgValue + '/xtuple-extensions', express.static('../../xtuple-extensions', { maxAge: 86400000 }));
+ app.use("/" + orgValue + '/npm', express.static('../node_modules', { maxAge: 86400000 }));
+ });
+}
app.use('/assets', express.static('views/login/assets', { maxAge: 86400000 }));
app.get('/:org/dialog/authorize', oauth2.authorization);
app.all('/:org/email', routes.email);
app.all('/:org/export', routes.exxport);
app.get('/:org/file', routes.file);
-app.all('/:org/oauth/generate-key', routes.generateOauthKey);
app.get('/:org/generate-report', routes.generateReport);
+app.all('/:org/install-extension', routes.installExtension);
app.get('/:org/locale', routes.locale);
+app.all('/:org/oauth/generate-key', routes.generateOauthKey);
app.get('/:org/reset-password', routes.resetPassword);
app.post('/:org/oauth/revoke-token', routes.revokeOauthToken);
app.all('/:org/vcfExport', routes.vcfExport);
Works with three or four dot-separated numbers.
*/
var getVersionSize = function (version) {
- var versionSplit = version.split('.'),
- versionSize = 1000000 * versionSplit[0] +
- 10000 * versionSplit[1] +
- 100 * versionSplit[2];
+ var versionSplit = version.split('.'), // e.g. "4.5.0-beta2".
+ versionSize = 1000000 * versionSplit[0] + // Get "4" from "4.5.0-beta2".
+ 10000 * versionSplit[1] + // Get "5" from "4.5.0-beta2".
+ 100 * versionSplit[2].match(/^[0-9]+/g, '')[0], // Get "0" from "0-beta2".
+ prerelease = versionSplit[2].replace(/^[0-9]+/g, ''), // Get "-beta2" from "0-beta2".
+ preRegEx = /([a-zA-Z]+)([0-9]*)/g, // Capture pre-release as ["beta2", "beta", "2"].
+ preMatch = preRegEx.exec(prerelease),
+ preVersion,
+ preNum;
if (versionSplit.length > 3) {
versionSize += versionSplit[3];
}
+
+ if (preMatch && preMatch.length && preMatch[0] !== '') {
+ if (preMatch[1] !== '') {
+ preVersion = preMatch[1].match(/[a-zA-Z]+/g); // Get ["beta"] from ["beta2", "beta", "2"].
+
+ // Decrease versionSize for pre-releasees.
+ switch (preVersion[0].toLowerCase()) {
+ case 'alpha':
+ versionSize = versionSize - 50;
+ break;
+ case 'beta':
+ versionSize = versionSize - 40;
+ break;
+ case 'rc':
+ versionSize = versionSize - 20;
+ break;
+ default :
+ X.err("Cannot get pre-release version number.");
+ }
+ }
+
+ // Add pre-release version to versionSize.
+ if (preMatch[2] !== '') {
+ preNum = preMatch[2].match(/[0-9]+/g); // Get ["2"] from ["beta2", "beta", "2"].
+ versionSize = versionSize + parseInt(preNum);
+ }
+ }
+
return versionSize;
};
});
uuids = _.compact(uuids); // eliminate any null values
var extensionPaths = _.compact(_.map(extensions, function (ext) {
- return path.join(ext.location, "source", ext.name);
+ var locationName = ext.location.indexOf("/") === 0 ?
+ path.join(ext.location, "source") :
+ "/" + ext.location;
+ return path.join(locationName, ext.name);
}));
getCoreUuid('js', req.session.passport.user.organization, function (err, jsUuid) {
if (err) {
--- /dev/null
+/*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, _:true */
+
+(function () {
+ "use strict";
+
+ var async = require("async"),
+ npm = require("npm"),
+ path = require("path"),
+ buildAll = require("../../scripts/lib/build_all");
+
+ exports.installExtension = function (req, res) {
+ var database = req.session.passport.user.organization,
+ extensionName = req.query.extensionName,
+ username = req.session.passport.user.id,
+ user = new SYS.User(),
+ validateInput = function (callback) {
+ if (!extensionName) {
+ callback("Error: empty extension name");
+ return;
+ }
+ callback();
+ },
+ validateUser = function (callback) {
+ user.fetch({
+ id: username,
+ username: X.options.databaseServer.user,
+ database: database,
+ success: function (model, results) {
+ var privCheck = _.find(model.get("grantedPrivileges"), function (model) {
+ return model.privilege === "InstallExtension";
+ });
+ if (!privCheck) {
+ callback({message: "_insufficientPrivileges"});
+ return;
+ }
+ callback(); // success!
+ },
+ error: function () {
+ callback({message: "_restoreError"});
+ }
+ });
+ },
+ npmLoad = function (callback) {
+ npm.load(callback);
+ },
+ npmInstall = function (callback) {
+ npm.commands.install([extensionName], callback);
+ npm.on("log", function (message) {
+ // log the progress of the installation
+ console.log(message);
+ });
+ },
+ buildExtension = function (callback) {
+ console.log("extension is", path.join(__dirname, "../../node_modules", extensionName));
+ buildAll.build({
+ database: database,
+ extension: path.join(__dirname, "../../node_modules", extensionName)
+ }, callback);
+ };
+
+ async.series([
+ validateInput,
+ validateUser,
+ npmLoad,
+ npmInstall,
+ buildExtension
+ ], function (err, results) {
+ if (err) {
+ console.log(err);
+ err.isError = true;
+ res.send(err);
+ return;
+ }
+ console.log("all done");
+ res.send({data: "_success"});
+ });
+ };
+}());
+
+
file = require('./file'),
generateReport = require('./generate_report'),
generateOauthKey = require('./generate_oauth_key'),
+ installExtension = require('./install_extension'),
locale = require('./locale'),
passport = require('passport'),
redirector = require('./redirector'),
exports.file = [ensureLogin, file.file];
exports.generateOauthKey = [ensureLogin, generateOauthKey.generateKey];
exports.generateReport = [ensureLogin, generateReport.generateReport];
+ exports.installExtension = [ensureLogin, installExtension.installExtension];
exports.locale = [ensureLogin, locale.locale];
exports.redirect = redirector.redirect;
exports.analysis = [ensureLogin, analysis.analysis];
"less": "1.5.0",
"moment": "2.4.x",
"nodemailer": "0.3.x",
+ "npm":"1.4.x",
"node-forge": "0.6.x",
"oauth2orize": "0.1.x",
"oauth2orize-jwt-bearer": "0.1.x",
dataSource = require('../../node-datasource/lib/ext/datasource').dataSource,
exec = require('child_process').exec,
fs = require('fs'),
+ npm = require('npm'),
path = require('path'),
unregister = buildDatabaseUtil.unregister,
winston = require('winston');
extPath = path.join(__dirname, "../../../xtuple-extensions/source", name);
} else if (location === '/private-extensions') {
extPath = path.join(__dirname, "../../../private-extensions/source", name);
+ } else if (location === 'npm') {
+ extPath = path.join(__dirname, "../../node_modules", name);
}
return extPath;
});
});
},
buildAll = function (specs, creds, buildAllCallback) {
- buildClient(specs, function (err, res) {
- if (err) {
- buildAllCallback(err);
- return;
- }
- buildDatabase.buildDatabase(specs, creds, function (databaseErr, databaseRes) {
- var returnMessage;
- if (databaseErr && (specs[0].wipeViews || specs[0].initialize)) {
- buildAllCallback(databaseErr);
- return;
-
- } else if (databaseErr) {
- buildAllCallback("Build failed. Try wiping the views next time by running me without the -q flag.");
+ async.series([
+ function (done) {
+ // step 1: npm install extension if necessary
+ // an alternate approach would be only npm install these
+ // extensions on an npm install.
+ var allExtensions = _.reduce(specs, function (memo, spec) {
+ memo.push(spec.extensions);
+ return _.flatten(memo);
+ }, []);
+ var npmExtensions = _.filter(allExtensions, function (extName) {
+ return extName.indexOf("node_modules") >= 0;
+ });
+ if (npmExtensions.length === 0) {
+ done();
return;
}
- returnMessage = "\n";
- _.each(specs, function (spec) {
- returnMessage += "Database: " + spec.database + '\nDirectories:\n';
- _.each(spec.extensions, function (ext) {
- returnMessage += ' ' + ext + '\n';
+ npm.load(function (err, res) {
+ if (err) {
+ done(err);
+ return;
+ }
+ npm.on("log", function (message) {
+ // log the progress of the installation
+ console.log(message);
});
+ async.map(npmExtensions, function (extName, next) {
+ npm.commands.install([path.basename(extName)], next);
+ }, done);
});
- buildAllCallback(null, "Build succeeded." + returnMessage);
- });
+ },
+ function (done) {
+ // step 2: build the client
+ buildClient(specs, done);
+ },
+ function (done) {
+ // step 3: build the database
+ buildDatabase.buildDatabase(specs, creds, function (databaseErr, databaseRes) {
+ if (databaseErr) {
+ buildAllCallback(databaseErr);
+ return;
+ }
+ var returnMessage = "\n";
+ _.each(specs, function (spec) {
+ returnMessage += "Database: " + spec.database + '\nDirectories:\n';
+ _.each(spec.extensions, function (ext) {
+ returnMessage += ' ' + ext + '\n';
+ });
+ });
+ done(null, "Build succeeded." + returnMessage);
+ });
+ }
+ ], function (err, results) {
+ buildAllCallback(err, results && results[results.length - 1]);
});
},
config;
callback(null, "");
return;
- } else if (extPath.indexOf("extensions") < 0) {
+ } else if (extPath.indexOf("extensions") < 0 && extPath.indexOf("node_modules") < 0) {
// this is the core app, which has a slightly different process.
fs.readFile(path.join(__dirname, "build/core.js"), "utf8", function (err, jsCode) {
if (err) {
return;
}
// run the enyo deployment method asyncronously
- var rootDir = path.join(extPath, "../..");
+ var rootDir = path.join(extPath, extPath.indexOf("node_modules") >= 0 ? "../../enyo-client/extensions/" : "../..");
// we run the command from /scripts/lib, so that is where build directories and other
// temp files are going to go.
console.log("building " + extName);
};
var build = function (extPath, callback) {
+ var isNodeModule = extPath.indexOf("node_modules") >= 0;
+
if (extPath.indexOf("/lib/orm") >= 0 || extPath.indexOf("foundation-database") >= 0) {
// There is nothing here to install on the client.
callback();
return;
}
- if (extPath.indexOf("extensions") < 0) {
+ if (extPath.indexOf("extensions") < 0 && !isNodeModule) {
// this is the core app, which has a different deploy process.
buildCore(callback);
return;
}
- var enyoDir = path.join(extPath, "../../enyo");
+ var enyoDir = path.join(extPath, isNodeModule ? "../../enyo-client/extensions/enyo" : "../../enyo");
fs.exists(path.join(extPath, "client"), function (exists) {
if (!exists) {
console.log(extPath + " has no client code. Not trying to build it.");
extensionCallback(null, "");
return;
}
- //winston.info("Installing extension", databaseName, extension);
+ //console.log("Installing extension", databaseName, extension);
// deal with directory structure quirks
var baseName = path.basename(extension),
isFoundation = extension.indexOf("foundation-database") >= 0,
extension.indexOf("extension") >= 0,
isPublicExtension = extension.indexOf("xtuple-extensions") >= 0,
isPrivateExtension = extension.indexOf("private-extensions") >= 0,
+ isNpmExtension = baseName.indexOf("xtuple-") >= 0,
dbSourceRoot = (isFoundation || isFoundationExtension) ? extension :
isLibOrm ? path.join(extension, "source") :
path.join(extension, "database/source"),
wipeViews: isApplicationCore && spec.wipeViews,
extensionLocation: isCoreExtension ? "/core-extensions" :
isPublicExtension ? "/xtuple-extensions" :
- isPrivateExtension ? "/private-extensions" : "not-applicable"
+ isPrivateExtension ? "/private-extensions" :
+ isNpmExtension ? "npm" : "not-applicable"
};
buildDatabaseUtil.explodeManifest(path.join(dbSourceRoot, "manifest.js"),
<prerequisite type = "query"
name = "Checking xTuple Edition" >
<query>SELECT fetchMetricText('Application') = 'PostBooks';</query>
- <message>This package must be applied to a Distribution database.</message>
+ <message>This package must be applied to a PostBooks database.</message>
</prerequisite>
<prerequisite type = "query"
</message>
</prerequisite>
- <prerequisite type = "query"
- name = "Checking for bad xTuple ERP database version" >
-<query>SELECT NOT fetchMetricText('ServerVersion') > '4.5.0' AND fetchMetricText('ServerVersion')!='4.5.0Beta' AND fetchMetricText('ServerVersion')!='4.5.0RC';</query>
+ <prerequisite type = "query"
+ name = "Checking for bad xTuple ERP database version" >
+ <query>SELECT NOT fetchMetricText('ServerVersion') > '4.5.0' AND fetchMetricText('ServerVersion')!='4.5.0Beta' AND fetchMetricText('ServerVersion')!='4.5.0RC';</query>
<message>This package may not be applied to a 4.5+ PostBooks database.
</message>
</prerequisite>
- <prerequisite type = "query"
+ <prerequisite type = "query"
name = "Checking for mobile-enabled schemas" >
<query>SELECT NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'xm');</query>
<message>This package may not be applied to a mobile-enabled database. Please see your system administrator or contact xTuple.
</message>
</prerequisite>
+ <prerequisite type = "query"
+ name = "Checking for duplicate Credit Card payments on Sales Orders" >
+ <query>
+ WITH counter AS (SELECT COUNT(*) AS freq
+ FROM payco
+ GROUP BY payco_ccpay_id, payco_cohead_id
+ ORDER BY 1)
+ SELECT (COALESCE(MAX(freq), 1) = 0 OR COALESCE(MAX(freq), 1) = 1)
+ FROM counter;
+ </query>
+ <message>There are duplicate payco_ccpay_id and payco_cohead_id on the payco table. Please see your system administrator or contact xTuple.
+ </message>
+ </prerequisite>
+
<script file="postbooks_upgrade.sql" />
<script file="inventory_basic_install.sql" />
<script file="inventory_upgrade.sql" />
</message>
</prerequisite>
+ <prerequisite type = "query"
+ name = "Checking for xwd schema" >
+ <query>SELECT NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'xwd');</query>
+ <message>This package requires that XWD 240 package to be installed before continuing.</message>
+ </prerequisite>
+
<script file="postbooks_upgrade.sql" />
<script file="inventory_upgrade.sql" />
<script file="distribution_upgrade.sql" />
</message>
</prerequisite>
- <prerequisite type = "query"
- name = "Checking for bad xTuple ERP database version" >
-<query>SELECT NOT fetchMetricText('ServerVersion') > '4.5.0' AND fetchMetricText('ServerVersion')!='4.5.0Beta' AND fetchMetricText('ServerVersion')!='4.5.0RC';</query>
+ <prerequisite type = "query"
+ name = "Checking for bad xTuple ERP database version" >
+ <query>SELECT NOT fetchMetricText('ServerVersion') > '4.5.0' AND fetchMetricText('ServerVersion')!='4.5.0Beta' AND fetchMetricText('ServerVersion')!='4.5.0RC';</query>
<message>This package may not be applied to a 4.5+ Postbooks database.
</message>
</prerequisite>
- <prerequisite type = "query"
+ <prerequisite type = "query"
name = "Checking for mobile-enabled schemas" >
<query>SELECT NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'xm');</query>
<message>This package may not be applied to a mobile-enabled database. Please see your system administrator or contact xTuple.
</message>
</prerequisite>
+ <prerequisite type = "query"
+ name = "Checking for duplicate Credit Card payments on Sales Orders" >
+ <query>
+ WITH counter AS (SELECT COUNT(*) AS freq
+ FROM payco
+ GROUP BY payco_ccpay_id, payco_cohead_id
+ ORDER BY 1)
+ SELECT COALESCE(MAX(freq), 1) <= 1
+ FROM counter;
+ </query>
+ <message>There are duplicate payco_ccpay_id and payco_cohead_id on the payco table. Please see your system administrator or contact xTuple.
+ </message>
+ </prerequisite>
+
<script file="postbooks_upgrade.sql" />
</package>
<prerequisite type = "query"
name = "Checking xTuple Edition" >
<query>SELECT fetchMetricText('Application') = 'PostBooks';</query>
- <message>This package must be applied to a Distribution Edition database.</message>
+ <message>This package must be applied to a PostBooks Edition database.</message>
</prerequisite>
<prerequisite type = "query"
</message>
</prerequisite>
- <prerequisite type = "query"
- name = "Checking for bad xTuple ERP database version" >
-<query>SELECT NOT fetchMetricText('ServerVersion') > '4.5.0' AND fetchMetricText('ServerVersion')!='4.5.0Beta' AND fetchMetricText('ServerVersion')!='4.5.0RC';</query>
+ <prerequisite type = "query"
+ name = "Checking for bad xTuple ERP database version" >
+ <query>SELECT NOT fetchMetricText('ServerVersion') > '4.5.0' AND fetchMetricText('ServerVersion')!='4.5.0Beta' AND fetchMetricText('ServerVersion')!='4.5.0RC';</query>
<message>This package may not be applied to a 4.5.0+ PostBooks database.
</message>
</prerequisite>
- <prerequisite type = "query"
+ <prerequisite type = "query"
name = "Checking for mobile-enabled schemas" >
<query>SELECT NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'xm');</query>
<message>This package may not be applied to a mobile-enabled database. Please see your system administrator or contact xTuple.
</message>
</prerequisite>
+ <prerequisite type = "query"
+ name = "Checking for duplicate Credit Card payments on Sales Orders" >
+ <query>
+ WITH counter AS (SELECT COUNT(*) AS freq
+ FROM payco
+ GROUP BY payco_ccpay_id, payco_cohead_id
+ ORDER BY 1)
+ SELECT (COALESCE(MAX(freq), 1) = 0 OR COALESCE(MAX(freq), 1) = 1)
+ FROM counter;
+ </query>
+ <message>There are duplicate payco_ccpay_id and payco_cohead_id on the payco table. Please see your system administrator or contact xTuple.
+ </message>
+ </prerequisite>
+
<script file="postbooks_upgrade.sql" />
<script file="inventory_basic_install.sql" />
<script file="inventory_upgrade.sql" />
gridRow,
gridBox,
workspace,
- skipIfSiteCal,
primeSubmodels = function (done) {
var submodels = {};
async.series([
submodels = submods;
done();
});
- if (XT.extensions.manufacturing && XT.session.settings.get("UseSiteCalendar")) {skipIfSiteCal = true; }
});
});
});
});
it('Supply list should have action to open Item Workbench', function (done) {
- if (!XT.extensions.inventory) {
- done();
- return;
- }
+ if (!XT.extensions.inventory) {return done(); }
var verify,
action = _.find(gridBox.$.supplyList.actions, function (action) {
return action.name === "openItemWorkbench";
});
*/
});
- // XXX - skip test if site calendar is enabled -
- // temporary until second notifyPopup (_nextWorkingDate) is handled in test (TODO).
-
- //it('changing the Schedule Date updates the line item\'s schedule date', function (done) {
- (skipIfSiteCal ? it.skip : it)(
- 'changing the Schedule Date updates the line item\'s schedule date', function (done) {
+ it('after saving, should not be able to Open and have edit privs in Item Site Workspace', function (done) {
+ if (!XT.extensions.inventory) {return done(); }
+ var originator = {}, statusReadyClean, workspaceContainer;
+ originator.name = "openItem";
+ // It's NOT a new order, go and make sure that we can't edit (after opening) Item Site WS
+ statusReadyClean = function () {
+ gridRow.$.itemSiteWidget.$.privateItemSiteWidget.menuItemSelected(null, {originator: originator});
+ /** XXX - what event can be used here instead? Tried a callback in menuItemSelected and passing it on
+ in PrivateItemSiteWidget's l154doWorkspace in the Private Item Site Widget.
+ */
+ setTimeout(function () {
+ workspaceContainer = XT.app.$.postbooks.getActive();
+ assert.equal(workspaceContainer.$.workspace.kind, "XV.ItemSiteWorkspace");
+ // "If notes are read only, assume that all the attributes are readOnly"... Lazy
+ assert.isTrue(workspaceContainer.$.workspace.value.isReadOnly("notes"));
+ // XXX - again, need an event
+ setTimeout(function () {
+ workspaceContainer.doPrevious();
+ assert.equal(XT.app.$.postbooks.getActive().$.workspace.kind, "XV.SalesOrderWorkspace");
+ // Update the notes field to leave the model READY_DIRTY
+ XT.app.$.postbooks.getActive().$.workspace.value.set("orderNotes", "test");
+ done();
+ }, 2000);
+ }, 3000);
+ };
+ workspace.value.once("status:READY_CLEAN", statusReadyClean);
+ workspace.save();
+ });
+ it('changing the Schedule Date updates the line item\'s schedule date', function (done) {
+ // Skip if no mfg ext or site cal not enabled...
+ // TODO - temporary until second notifyPopup (_nextWorkingDate) is handled properly in test
+ if (!(XT.extensions.manufacturing) || !(XT.session.settings.get("UseSiteCalendar"))) {return done(); }
var getDowDate = function (dow) {
var date = new Date(),
currentDow = date.getDay(),
date.setDate(date.getDate() + distance);
return date;
},
- newScheduleDate = getDowDate(0); // Sunday from current week
-
- var handlePopup = function () {
- assert.equal(workspace.value.get("scheduleDate"), newScheduleDate);
- // Confirm to update all line items
- XT.app.$.postbooks.notifyTap(null, {originator: {name: "notifyYes"}});
- // And verify that they were all updated with the new date
- setTimeout(function () {
- _.each(workspace.value.get("lineItems").models, function (model) {
- assert.equal(newScheduleDate, model.get("scheduleDate"));
- });
- done();
- }, 3000);
- };
+ newScheduleDate = getDowDate(0), // Sunday from current week
+ handlePopup = function () {
+ assert.equal(workspace.value.get("scheduleDate"), newScheduleDate);
+ // Confirm to update all line items
+ XT.app.$.postbooks.notifyTap(null, {originator: {name: "notifyYes"}});
+ // And verify that they were all updated with the new date
+ setTimeout(function () {
+ _.each(workspace.value.get("lineItems").models, function (model) {
+ assert.equal(model.get("scheduleDate"), newScheduleDate);
+ });
+ done();
+ }, 3000);
+ };
workspace.value.once("change:scheduleDate", handlePopup);
workspace.value.set("scheduleDate", newScheduleDate);
});
it('save, then delete order', function (done) {
- assert.equal(workspace.value.status, XM.Model.READY_NEW);
+ assert.isTrue((workspace.value.status === XM.Model.READY_DIRTY ||
+ workspace.value.status === XM.Model.READY_NEW));
smoke.saveWorkspace(workspace, function (err, model) {
assert.isNull(err);
- // TODO: sloppy
+ // XXX - sloppy
setTimeout(function () {
smoke.deleteFromList(XT.app, model, done);
}, 4000);