diff --git a/imports/plugins/core/orders/client/components/orderSummary.js b/imports/plugins/core/orders/client/components/orderSummary.js index dbf48dd1a0e..09ee96a5645 100644 --- a/imports/plugins/core/orders/client/components/orderSummary.js +++ b/imports/plugins/core/orders/client/components/orderSummary.js @@ -2,7 +2,7 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import moment from "moment"; import { Badge, ClickToCopy } from "@reactioncommerce/reaction-ui"; -import { getOrderRiskBadge, getOrderRiskStatus } from "../helpers"; +import { getOrderRiskBadge, getOrderRiskStatus, getBillingInfo, getShippingInfo } from "../helpers"; class OrderSummary extends Component { static propTypes = { @@ -15,7 +15,8 @@ class OrderSummary extends Component { } badgeStatus() { - const orderStatus = this.props.order.workflow.status; + const order = this.props.order; + const orderStatus = order && order.workflow && order.workflow.status; if (orderStatus === "new") { return "info"; @@ -44,12 +45,18 @@ class OrderSummary extends Component { render() { const { dateFormat, tracking, order, profileShippingAddress, printableLabels } = this.props; + const paymentMethod = getBillingInfo(order).paymentMethod || {}; + const invoice = getBillingInfo(order).invoice || {}; + const shipmentMethod = getShippingInfo(order).shipmentMethod || {}; const orderRisk = getOrderRiskStatus(order); return (
-
- {profileShippingAddress.fullName} +
+ {profileShippingAddress && profileShippingAddress.fullName}
{order.email}
@@ -60,8 +67,8 @@ class OrderSummary extends Component {
{orderRisk && @@ -97,28 +104,28 @@ class OrderSummary extends Component {
Processor
- {order.billing[0].paymentMethod.processor} + {paymentMethod && paymentMethod.processor}
Payment
- {order.billing[0].paymentMethod.storedCard} ({order.billing[0].invoice.total}) + {paymentMethod.storedCard} ({invoice.total})
Transaction
- {order.billing[0].paymentMethod.transactionId} + {paymentMethod.transactionId}
Carrier
- {order.shipping[0].shipmentMethod.carrier} - {order.shipping[0].shipmentMethod.label} + {shipmentMethod.carrier} - {shipmentMethod.label}
@@ -155,9 +162,13 @@ class OrderSummary extends Component {
{profileShippingAddress.fullName} -
{profileShippingAddress.address1} +
+ {profileShippingAddress.address1} {profileShippingAddress.address2 &&
{profileShippingAddress.address2}
} -
{profileShippingAddress.city}, {profileShippingAddress.region}, {profileShippingAddress.country} {profileShippingAddress.postal} +
+ + {profileShippingAddress.city}, {profileShippingAddress.region}, {profileShippingAddress.country} {profileShippingAddress.postal} +
); diff --git a/imports/plugins/core/orders/client/components/orderTable.js b/imports/plugins/core/orders/client/components/orderTable.js index a5520b8cc7c..ead147c5d28 100644 --- a/imports/plugins/core/orders/client/components/orderTable.js +++ b/imports/plugins/core/orders/client/components/orderTable.js @@ -3,35 +3,35 @@ import PropTypes from "prop-types"; import Avatar from "react-avatar"; import moment from "moment"; import classnames from "classnames/dedupe"; -import { Reaction, i18next } from "/client/api"; +import { i18next } from "/client/api"; import { Orders } from "/lib/collections"; import { Badge, ClickToCopy, Icon, Translation, Checkbox, Loading, SortableTable } from "@reactioncommerce/reaction-ui"; import OrderTableColumn from "./orderTableColumn"; import OrderBulkActionsBar from "./orderBulkActionsBar"; import { formatPriceString } from "/client/api"; import ProductImage from "./productImage"; -import { getOrderRiskBadge, getOrderRiskStatus } from "../helpers"; +import { getOrderRiskBadge, getOrderRiskStatus, getBillingInfo, getShippingInfo } from "../helpers"; const classNames = { colClassNames: { - "name": "order-table-column-name", - "email": "order-table-column-email", - "date": "order-table-column-date hidden-xs hidden-sm", - "id": "order-table-column-id hidden-xs hidden-sm", - "total": "order-table-column-total", - "shipping": "order-table-column-shipping hidden-xs hidden-sm", - "status": "order-table-column-status", - "": "order-table-column-control" + name: "order-table-column-name", + email: "order-table-column-email", + date: "order-table-column-date hidden-xs hidden-sm", + id: "order-table-column-id hidden-xs hidden-sm", + total: "order-table-column-total", + shipping: "order-table-column-shipping hidden-xs hidden-sm", + status: "order-table-column-status", + control: "order-table-column-control" }, headerClassNames: { - "name": "order-table-header-name", - "email": "order-table-header-email", - "date": "order-table-header-date hidden-xs hidden-sm", - "id": "order-table-header-id hidden-xs hidden-sm", - "total": "order-table-header-total", - "shipping": "order-table-header-shipping hidden-xs hidden-sm", - "status": "order-table-header-status", - "": "order-table-header-control" + name: "order-table-header-name", + email: "order-table-header-email", + date: "order-table-header-date hidden-xs hidden-sm", + id: "order-table-header-id hidden-xs hidden-sm", + total: "order-table-header-total", + shipping: "order-table-header-shipping hidden-xs hidden-sm", + status: "order-table-header-status", + control: "order-table-header-control" } }; @@ -54,13 +54,6 @@ class OrderTable extends Component { toggleShippingFlowList: PropTypes.func } - // helper function to get appropriate billing info - getBillingInfo(order) { - return order.billing.find( - billing => billing.shopId === Reaction.getShopId() - ) || {}; - } - /** * Fullfilment Badge * @param {Object} order object containing info for order and coreOrderWorkflow @@ -100,6 +93,7 @@ class OrderTable extends Component { renderOrderInfo(order) { const { displayMedia } = this.props; + const invoice = getBillingInfo(order).invoice || {}; return (
@@ -120,7 +114,7 @@ class OrderTable extends Component { - Total: {formatPriceString(this.getBillingInfo(order).invoice.total)} + Total: {formatPriceString(invoice.total)}
@@ -145,7 +139,9 @@ class OrderTable extends Component { } renderShipmentInfo(order) { - const emailAddress = order.email || ; + const emailAddress = order.email || + ; + const shipping = getShippingInfo(order); const orderRisk = getOrderRiskStatus(order); return ( @@ -154,11 +150,11 @@ class OrderTable extends Component { - {order.shipping[0].address.fullName} | {emailAddress} + {shipping.address && shipping.address.fullName} | {emailAddress} {orderRisk && getShippingInfo(row).address && getShippingInfo(row).address.fullName, + id: "shippingFullName" + }, + email: { + accessor: "email", + id: "email" + }, + date: { + accessor: "createdAt", + id: "createdAt" + }, + id: { + accessor: "_id", + id: "_id" + }, + total: { + accessor: row => getBillingInfo(row).invoice && getBillingInfo(row).invoice.total, + id: "billingTotal" + }, + shipping: { + accessor: row => getShippingInfo(row).workflow && getShippingInfo(row).workflow.status, + id: "shippingStatus" + }, + status: { + accessor: "workflow.status", + id: "workflow.status" + }, + control: { + accessor: "", + id: "" + } }; const columnNames = Object.keys(filteredFields); @@ -271,16 +291,18 @@ class OrderTable extends Component {
); - } else if (columnName === "") { - columnNameLabel = ""; + } else if (columnName === "control") { + colHeader = " "; resizable = false; sortable = false; } else { columnNameLabel = i18next.t(`admin.table.headers.${columnName}`); } + const columnMeta = { - accessor: filteredFields[columnName], + accessor: filteredFields[columnName].accessor, + id: filteredFields[columnName].id, Header: colHeader ? colHeader : columnNameLabel, headerClassName: classNames.headerClassNames[columnName], className: classNames.colClassNames[columnName], diff --git a/imports/plugins/core/orders/client/components/orderTableColumn.js b/imports/plugins/core/orders/client/components/orderTableColumn.js index 6e2bb06ff80..2699fb34828 100644 --- a/imports/plugins/core/orders/client/components/orderTableColumn.js +++ b/imports/plugins/core/orders/client/components/orderTableColumn.js @@ -5,7 +5,7 @@ import moment from "moment"; import { formatPriceString, i18next } from "/client/api"; import Avatar from "react-avatar"; import { Badge, ClickToCopy, Icon, RolloverCheckbox, Checkbox } from "@reactioncommerce/reaction-ui"; -import { getOrderRiskBadge, getOrderRiskStatus } from "../helpers"; +import { getOrderRiskBadge, getOrderRiskStatus, getBillingInfo } from "../helpers"; class OrderTableColumn extends Component { static propTypes = { @@ -49,9 +49,10 @@ class OrderTableColumn extends Component { render() { const columnAccessor = this.props.row.column.id; + const invoice = getBillingInfo(this.props.row.original).invoice || {}; const orderRisk = getOrderRiskStatus(this.props.row.original); - if (columnAccessor === "shipping[0].address.fullName") { + if (columnAccessor === "shippingFullName") { return (
{this.renderCheckboxOnSelect(this.props.row)} @@ -93,14 +94,14 @@ class OrderTableColumn extends Component {
); } - if (columnAccessor === "billing[0].invoice.total") { + if (columnAccessor === "billingTotal") { return (
- {formatPriceString(this.props.row.original.billing[0].invoice.total)} + {formatPriceString(invoice.total)}
); } - if (columnAccessor === "shipping[0].workflow.status") { + if (columnAccessor === "shippingStatus") { return ( { event.preventDefault(); const order = this.state.order; - const invoiceTotal = getBillingInfo(order).invoice.total; + const invoiceTotal = getBillingInfo(order).invoice && getBillingInfo(order).invoice.total; const currencySymbol = this.state.currency.symbol; Meteor.subscribe("Packages", Reaction.getShopId()); - const packageId = getBillingInfo(order).paymentMethod.paymentPackageId; - const settingsKey = getBillingInfo(order).paymentMethod.paymentSettingsKey; + const packageId = getBillingInfo(order).paymentMethod && getBillingInfo(order).paymentMethod.paymentPackageId; + const settingsKey = getBillingInfo(order).paymentMethod && getBillingInfo(order).paymentMethod.paymentSettingsKey; // check if payment provider supports de-authorize const checkSupportedMethods = Packages.findOne({ _id: packageId, shopId: Reaction.getShopId() }).settings[settingsKey].support; - const orderStatus = getBillingInfo(order).paymentMethod.status; - const orderMode = getBillingInfo(order).paymentMethod.mode; + const orderStatus = getBillingInfo(order).paymentMethod && getBillingInfo(order).paymentMethod.status; + const orderMode = getBillingInfo(order).paymentMethod && getBillingInfo(order).paymentMethod.mode; let alertText; if (_.includes(checkSupportedMethods, "de-authorize") || @@ -302,18 +302,18 @@ class InvoiceContainer extends Component { const currencySymbol = this.state.currency.symbol; const order = this.state.order; - const paymentMethod = orderCreditMethod(order).paymentMethod; - const orderTotal = paymentMethod.amount; - const discounts = paymentMethod.discounts; + const paymentMethod = orderCreditMethod(order) && orderCreditMethod(order).paymentMethod; + const orderTotal = paymentMethod && paymentMethod.amount; + const discounts = paymentMethod && paymentMethod.discounts; const refund = value; const refunds = this.state.refunds; - const refundTotal = refunds.reduce((acc, item) => acc + parseFloat(item.amount), 0); + const refundTotal = refunds && refunds.reduce((acc, item) => acc + parseFloat(item.amount), 0); let adjustedTotal; // TODO extract Stripe specific fullfilment payment handling out of core. // Stripe counts discounts as refunds, so we need to re-add the discount to not "double discount" in the adjustedTotal - if (paymentMethod.processor === "Stripe") { + if (paymentMethod && paymentMethod.processor === "Stripe") { adjustedTotal = accounting.toFixed(orderTotal + discounts - refundTotal, 2); } else { adjustedTotal = accounting.toFixed(orderTotal - refundTotal, 2); @@ -352,8 +352,8 @@ class InvoiceContainer extends Component { } handleRefundItems = () => { - const paymentMethod = orderCreditMethod(this.state.order).paymentMethod; - const orderMode = paymentMethod.mode; + const paymentMethod = orderCreditMethod(this.state.order) && orderCreditMethod(this.state.order).paymentMethod; + const orderMode = paymentMethod && paymentMethod.mode; const order = this.state.order; // Check if payment is yet to be captured approve and capture first before return @@ -363,7 +363,7 @@ class InvoiceContainer extends Component { type: "warning", text: i18next.t("order.refundItemsApproveAlert", { refundItemsQuantity: this.getRefundedItemsInfo().quantity, - totalAmount: formatPriceString(getBillingInfo(order).invoice.total) + totalAmount: formatPriceString(getBillingInfo(order).invoice && getBillingInfo(order).invoice.total) }), showCancelButton: true, confirmButtonText: i18next.t("order.approveInvoice") @@ -383,7 +383,7 @@ class InvoiceContainer extends Component { title: i18next.t("order.refundItemsTitle"), text: i18next.t("order.refundItemsCaptureAlert", { refundItemsQuantity: this.getRefundedItemsInfo().quantity, - totalAmount: formatPriceString(getBillingInfo(order).invoice.total) + totalAmount: formatPriceString(getBillingInfo(order).invoice && getBillingInfo(order).invoice.total) }), type: "warning", showCancelButton: true, @@ -397,8 +397,8 @@ class InvoiceContainer extends Component { } alertToRefund = (order) => { - const paymentMethod = orderCreditMethod(order).paymentMethod; - const orderMode = paymentMethod.mode; + const paymentMethod = orderCreditMethod(order) && orderCreditMethod(order).paymentMethod; + const orderMode = paymentMethod && paymentMethod.mode; const refundInfo = this.getRefundedItemsInfo(); Alerts.alert({ @@ -489,19 +489,6 @@ class InvoiceContainer extends Component { } } -/** - * @method getBillingInfo - * @summary helper method to get appropriate billing info - * @param {Object} order - object representing an order - * @return {Object} object representing the order billing info - */ -function getBillingInfo(order) { - return order.billing.find( - billing => billing.shopId === Reaction.getShopId() - ) || {}; -} - - /** * @method orderCreditMethod * @summary helper method to return the order payment object @@ -511,7 +498,7 @@ function getBillingInfo(order) { function orderCreditMethod(order) { const billingInfo = getBillingInfo(order); - if (billingInfo.paymentMethod.method === "credit") { + if (billingInfo.paymentMethod && billingInfo.paymentMethod.method === "credit") { return billingInfo; } } @@ -622,10 +609,11 @@ const composer = (props, onData) => { const refunds = props.refunds; const shopBilling = getBillingInfo(order); + const creditMethod = orderCreditMethod(order); - const paymentMethod = orderCreditMethod(order).paymentMethod; - const orderStatus = orderCreditMethod(order).paymentMethod.status; - const orderDiscounts = orderCreditMethod(order).invoice.discounts; + const paymentMethod = creditMethod && creditMethod.paymentMethod; + const orderStatus = creditMethod && creditMethod.paymentMethod && creditMethod.paymentMethod.status; + const orderDiscounts = creditMethod && creditMethod.invoice.discounts; const paymentApproved = orderStatus === "approved"; const showAfterPaymentCaptured = orderStatus === "completed"; @@ -637,12 +625,12 @@ const composer = (props, onData) => { // get adjusted Total let adjustedTotal; - const refundTotal = refunds.reduce((acc, item) => acc + parseFloat(item.amount), 0); + const refundTotal = refunds && refunds.reduce((acc, item) => acc + parseFloat(item.amount), 0); - if (paymentMethod.processor === "Stripe") { + if (paymentMethod && paymentMethod.processor === "Stripe") { adjustedTotal = Math.abs(paymentMethod.amount + orderDiscounts - refundTotal); } - adjustedTotal = Math.abs(paymentMethod.amount - refundTotal); + adjustedTotal = Math.abs(paymentMethod && paymentMethod.amount - refundTotal); // get invoice const invoice = Object.assign({}, shopBilling.invoice, { @@ -672,7 +660,7 @@ const composer = (props, onData) => { // returns order items with shipping detail const returnItems = order.items.map((item) => { - const shipping = shipment.shipmentMethod; + const shipping = shipment && shipment.shipmentMethod; item.shipping = shipping; return item; }); @@ -699,7 +687,7 @@ const composer = (props, onData) => { const printOrder = Reaction.Router.pathFor("dashboard/pdf/orders", { hash: { id: props.order._id, - shipment: props.currentData.fulfillment._id + shipment: props.currentData.fulfillment && props.currentData.fulfillment._id } }); diff --git a/imports/plugins/core/orders/client/containers/orderDashboardContainer.js b/imports/plugins/core/orders/client/containers/orderDashboardContainer.js index 43464c5a0eb..56ac87c48fc 100644 --- a/imports/plugins/core/orders/client/containers/orderDashboardContainer.js +++ b/imports/plugins/core/orders/client/containers/orderDashboardContainer.js @@ -12,6 +12,7 @@ import { ORDER_LIST_SELECTED_ORDER_PREFERENCE_NAME, shippingStates } from "../../lib/constants"; +import { getShippingInfo } from "../helpers"; const shippingStrings = ["picked", "packed", "labeled", "shipped"]; @@ -379,6 +380,9 @@ class OrderDashboardContainer extends Component { * @return {null} no return value */ shippingStatusUpdateCall = (selectedOrders, status) => { + const filteredSelectedOrders = selectedOrders.filter((order) => { + return order.shipping && Object.keys(getShippingInfo(order)).length; + }); this.setState({ isLoading: { [status]: true @@ -386,7 +390,7 @@ class OrderDashboardContainer extends Component { }); let orderText = "order"; - if (selectedOrders.length > 1) { + if (filteredSelectedOrders.length > 1) { orderText = "orders"; } @@ -401,15 +405,17 @@ class OrderDashboardContainer extends Component { // different shipping statuses to receive an array of objects(orders) as a param // TODO: rethink this type of flow for updating shipping statuses - selectedOrders.forEach((order) => { - Meteor.call(`orders/shipment${capitalizeStatus}`, order, order.shipping[0], (err) => { - if (err) { - Alerts.toast(`An error occured while setting the status: ${err}`, "error"); + filteredSelectedOrders.forEach((order) => { + const shippingRecord = getShippingInfo(order); + + Meteor.call(`orders/shipment${capitalizeStatus}`, order, shippingRecord, (error) => { + if (error) { + Alerts.toast(`An error occured while setting the status: ${error}`, "error"); } else { Meteor.call("orders/updateHistory", order._id, "Shipping state set by bulk operation", status); } orderCount++; - if (orderCount === selectedOrders.length) { + if (orderCount === filteredSelectedOrders.length) { this.setState({ shipping: { [status]: true @@ -420,7 +426,7 @@ class OrderDashboardContainer extends Component { }); Alerts.alert({ text: i18next.t("order.orderSetToState", { - orderNumber: selectedOrders.length, + orderNumber: filteredSelectedOrders.length, orderText: orderText, status: status }), @@ -543,24 +549,26 @@ class OrderDashboardContainer extends Component { // status of each order in regard to the other statuses // TODO: optimise this process to avoid having this similar repetitive block of code across 4 methods selectedOrders.forEach((order) => { - // TODO: remove these hard-coded zero indexes to enable multiple shipments in marketplace - const orderWorkflow = order.shipping[0].workflow; + const orderWorkflow = getShippingInfo(order).workflow; // check if the order(s) are in this state already or in the previous state - // TODO: model this with the assumption that there may be different workflows depending on the type of shop or product that a shop is selling. - if (orderWorkflow.status === "new") { - isNotPicked++; - } else if (orderWorkflow.status === "coreOrderWorkflow/picked") { - isPicked++; - } else { - // check if the selected order(s) are being regressed back to this state - if (orderWorkflow.workflow.includes("coreOrderWorkflow/picked")) { - ordersToRegress++; - } else if (!orderWorkflow.workflow.includes("coreOrderWorkflow/picked") && - (orderWorkflow.status === "coreOrderWorkflow/packed" || - orderWorkflow.status === "coreOrderWorkflow/labeled" || - orderWorkflow.status === "coreOrderWorkflow/shipped")) { - ordersToRegress++; + // TODO: model this with the assumption that there may be different workflows + // depending on the type of shop or product that a shop is selling. + if (orderWorkflow) { + if (orderWorkflow.status === "new") { + isNotPicked++; + } else if (orderWorkflow.status === "coreOrderWorkflow/picked") { + isPicked++; + } else { + // check if the selected order(s) are being regressed back to this state + if (orderWorkflow.workflow.includes("coreOrderWorkflow/picked")) { + ordersToRegress++; + } else if (!orderWorkflow.workflow.includes("coreOrderWorkflow/picked") && + (orderWorkflow.status === "coreOrderWorkflow/packed" || + orderWorkflow.status === "coreOrderWorkflow/labeled" || + orderWorkflow.status === "coreOrderWorkflow/shipped")) { + ordersToRegress++; + } } } }); @@ -594,25 +602,28 @@ class OrderDashboardContainer extends Component { // status of each order in regard to the other statuses // TODO: optimise this process to avoid having this similar repetitive block of code across 4 methods selectedOrders.forEach((order) => { - // TODO: remove these hard-coded zero indexes to enable multiple shipments in marketplace - const orderWorkflow = order.shipping[0].workflow; + const orderWorkflow = getShippingInfo(order).workflow; + // check if the order(s) are in this state already or in one of the previous states - // TODO: model this with the assumption that there may be different workflows depending on the type of shop or product that a shop is selling. - if (orderWorkflow.status === "new") { - isNotPicked++; - } else if (orderWorkflow.status === "coreOrderWorkflow/picked") { - isNotPacked++; - } else if (orderWorkflow.status === "coreOrderWorkflow/packed") { - isPacked++; - } else { - // check if the selected order(s) are being regressed back to this state - if (orderWorkflow.workflow.includes("coreOrderWorkflow/packed")) { - ordersToRegress++; - } else if (!orderWorkflow.workflow.includes("coreOrderWorkflow/packed") && + // TODO: model this with the assumption that there may be different workflows + // depending on the type of shop or product that a shop is selling. + if (orderWorkflow) { + if (orderWorkflow.status === "new") { + isNotPicked++; + } else if (orderWorkflow.status === "coreOrderWorkflow/picked") { + isNotPacked++; + } else if (orderWorkflow.status === "coreOrderWorkflow/packed") { + isPacked++; + } else { + // check if the selected order(s) are being regressed back to this state + if (orderWorkflow.workflow.includes("coreOrderWorkflow/packed")) { + ordersToRegress++; + } else if (!orderWorkflow.workflow.includes("coreOrderWorkflow/packed") && (orderWorkflow.status === "coreOrderWorkflow/labeled" || orderWorkflow.status === "coreOrderWorkflow/shipped")) { - ordersToRegress++; + ordersToRegress++; + } } } }); @@ -653,26 +664,28 @@ class OrderDashboardContainer extends Component { // status of each order in regard to the other statuses // TODO: optimise this process to avoid having this similar repetitive block of code across 4 methods selectedOrders.forEach((order) => { - // TODO: remove these hard-coded zero indexes to enable multiple shipments in marketplace - const orderWorkflow = order.shipping[0].workflow; + const orderWorkflow = getShippingInfo(order).workflow; // check if the order(s) are in this state already or in one of the previous states - // TODO: model this with the assumption that there may be different workflows depending on the type of shop or product that a shop is selling. - if (orderWorkflow.status === "new") { - isNotPacked++; - whichFalseState = shippingStates.picked; - } else if (orderWorkflow.status === "coreOrderWorkflow/picked") { - isNotPacked++; - whichFalseState = shippingStates.packed; - } else if (orderWorkflow.status === "coreOrderWorkflow/packed") { - isNotLabeled++; - } else if (orderWorkflow.status === "coreOrderWorkflow/labeled") { - isLabeled++; - } else { - // check if the selected order(s) are being regressed back to this state - if (orderWorkflow.workflow.includes("coreOrderWorkflow/labeled") || - orderWorkflow.status === "coreOrderWorkflow/shipped") { - ordersToRegress++; + // TODO: model this with the assumption that there may be different workflows + // depending on the type of shop or product that a shop is selling. + if (orderWorkflow) { + if (orderWorkflow.status === "new") { + isNotPacked++; + whichFalseState = shippingStates.picked; + } else if (orderWorkflow.status === "coreOrderWorkflow/picked") { + isNotPacked++; + whichFalseState = shippingStates.packed; + } else if (orderWorkflow.status === "coreOrderWorkflow/packed") { + isNotLabeled++; + } else if (orderWorkflow.status === "coreOrderWorkflow/labeled") { + isLabeled++; + } else { + // check if the selected order(s) are being regressed back to this state + if (orderWorkflow.workflow.includes("coreOrderWorkflow/labeled") || + orderWorkflow.status === "coreOrderWorkflow/shipped") { + ordersToRegress++; + } } } }); @@ -713,24 +726,27 @@ class OrderDashboardContainer extends Component { // status of each order in regard to the other statuses // TODO: optimise this process to avoid having this similar repetitive block of code across 4 methods selectedOrders.forEach((order) => { - // TODO: remove these hard-coded zero indexes to enable multiple shipments in marketplace - const orderWorkflow = order.shipping[0].workflow.status; + const orderWorkflow = getShippingInfo(order).workflow; // check if the order(s) are in this state already or in one of the previous states - // TODO: model this with the assumption that there may be different workflows depending on the type of shop or product that a shop is selling. - if (orderWorkflow === "new") { - isNotLabeled++; - whichFalseState = shippingStates.picked; - } else if (orderWorkflow === "coreOrderWorkflow/picked") { - isNotLabeled++; - whichFalseState = shippingStates.packed; - } else if (orderWorkflow === "coreOrderWorkflow/packed") { - isNotLabeled++; - whichFalseState = shippingStates.labeled; - } else if (orderWorkflow === "coreOrderWorkflow/labeled") { - isNotShipped++; - } else if (orderWorkflow === "coreOrderWorkflow/shipped") { - isShipped++; + // TODO: model this with the assumption that there may be different workflows + // depending on the type of shop or product that a shop is selling. + if (orderWorkflow) { + const orderWorkflowStatus = orderWorkflow.status; + if (orderWorkflowStatus === "new") { + isNotLabeled++; + whichFalseState = shippingStates.picked; + } else if (orderWorkflowStatus === "coreOrderWorkflow/picked") { + isNotLabeled++; + whichFalseState = shippingStates.packed; + } else if (orderWorkflowStatus === "coreOrderWorkflow/packed") { + isNotLabeled++; + whichFalseState = shippingStates.labeled; + } else if (orderWorkflowStatus === "coreOrderWorkflow/labeled") { + isNotShipped++; + } else if (orderWorkflowStatus === "coreOrderWorkflow/shipped") { + isShipped++; + } } }); @@ -792,25 +808,25 @@ class OrderDashboardContainer extends Component { // TODO: send these orders in batch as an array. This would entail re-writing the // "orders/approvePayment" method to receive an array of orders as a param. selectedOrders.forEach((order) => { - Meteor.call("orders/approvePayment", order, (err) => { - if (err) { + Meteor.call("orders/approvePayment", order, (approvePaymentError) => { + if (approvePaymentError) { this.setState({ isLoading: { capturePayment: false } }); - Alerts.toast(`An error occured while approving the payment: ${err}`, "error"); + Alerts.toast(`An error occured while approving the payment: ${approvePaymentError}`, "error"); } else { // TODO: send these orders in batch as an array. This would entail re-writing the // "orders/capturePayments" method to receive an array of orders as a param. - Meteor.call("orders/capturePayments", order._id, (error) => { - if (error) { + Meteor.call("orders/capturePayments", order._id, (capturePaymentError) => { + if (capturePaymentError) { this.setState({ isLoading: { capturePayment: false } }); - Alerts.toast(`An error occured while capturing the payment: ${error}`, "error"); + Alerts.toast(`An error occured while capturing the payment: ${capturePaymentError}`, "error"); } orderCount++; diff --git a/imports/plugins/core/orders/client/containers/orderSummaryContainer.js b/imports/plugins/core/orders/client/containers/orderSummaryContainer.js index 9faf381ff3f..5e2795ab176 100644 --- a/imports/plugins/core/orders/client/containers/orderSummaryContainer.js +++ b/imports/plugins/core/orders/client/containers/orderSummaryContainer.js @@ -8,6 +8,7 @@ import { Orders } from "/lib/collections"; import { Card, CardHeader, CardBody, CardGroup } from "/imports/plugins/core/ui/client/components"; import { i18next } from "/client/api"; import OrderSummary from "../components/orderSummary"; +import { getShippingInfo } from "../helpers"; class OrderSummaryContainer extends Component { static propTypes = { @@ -27,15 +28,16 @@ class OrderSummaryContainer extends Component { } tracking = () => { - if (this.props.order.shipping[0].tracking) { - return this.props.order.shipping[0].tracking; + const shipping = getShippingInfo(this.props.order); + if (shipping.tracking) { + return shipping.tracking; } return i18next.t("orderShipping.noTracking"); } shipmentStatus = () => { const order = this.props.order; - const shipment = order.shipping[0]; + const shipment = getShippingInfo(this.props.order); if (shipment.delivered) { return { @@ -95,7 +97,7 @@ class OrderSummaryContainer extends Component { } printableLabels = () => { - const { shippingLabelUrl, customsLabelUrl } = this.props.order.shipping[0]; + const { shippingLabelUrl, customsLabelUrl } = getShippingInfo(this.props.order); if (shippingLabelUrl || customsLabelUrl) { return { shippingLabelUrl, customsLabelUrl }; } @@ -136,22 +138,29 @@ const composer = (props, onData) => { // Find current order const order = Orders.findOne({ "_id": props.orderId, - "shipping._id": props.fulfillment._id + "shipping._id": props.fulfillment && props.fulfillment._id }); - const profileShippingAddress = order.shipping[0].address; + if (order) { + const profileShippingAddress = getShippingInfo(order).address || {}; - if (order.workflow) { - if (order.workflow.status === "coreOrderCreated") { - order.workflow.status = "coreOrderCreated"; - Meteor.call("workflow/pushOrderWorkflow", "coreOrderWorkflow", "coreOrderCreated", order); + if (order.workflow) { + if (order.workflow.status === "coreOrderCreated") { + order.workflow.status = "coreOrderCreated"; + Meteor.call("workflow/pushOrderWorkflow", "coreOrderWorkflow", "coreOrderCreated", order); + } } - } - onData(null, { - order: order, - profileShippingAddress: profileShippingAddress - }); + onData(null, { + order: order, + profileShippingAddress: profileShippingAddress + }); + } else { + onData(null, { + order: {}, + profileShippingAddress: {} + }); + } } }; diff --git a/imports/plugins/core/orders/client/helpers/index.js b/imports/plugins/core/orders/client/helpers/index.js index 112930a3340..70c5526c9cb 100644 --- a/imports/plugins/core/orders/client/helpers/index.js +++ b/imports/plugins/core/orders/client/helpers/index.js @@ -49,3 +49,31 @@ export function getOrderRiskStatus(order) { return riskLevel; } + +/** + * getBillingInfo + * + * @summary get proper billing object as per current active shop + * @param {Object} order - order object to check against + * @return {Object} proper billing object to use + */ +export function getBillingInfo(order) { + const billingInfo = order && order.billing && order.billing.find((billing) => { + return billing && (billing.shopId === Reaction.getShopId()); + }); + return billingInfo || {}; +} + +/** + * getShippingInfo + * + * @summary get proper shipping object as per current active shop + * @param {Object} order - order object to check against + * @return {Object} proper shipping object to use + */ +export function getShippingInfo(order) { + const shippingInfo = order && order.shipping && order.shipping.find((shipping) => { + return shipping && shipping.shopId === Reaction.getShopId(); + }); + return shippingInfo || {}; +} diff --git a/imports/plugins/core/orders/client/templates/list/ordersList.js b/imports/plugins/core/orders/client/templates/list/ordersList.js index 108ac46300d..17b283113af 100644 --- a/imports/plugins/core/orders/client/templates/list/ordersList.js +++ b/imports/plugins/core/orders/client/templates/list/ordersList.js @@ -1,6 +1,8 @@ import moment from "moment"; import { Template } from "meteor/templating"; import { Orders, Shops } from "/lib/collections"; +import { Reaction } from "/client/api"; + /** * dashboardOrdersList helpers @@ -27,7 +29,10 @@ Template.dashboardOrdersList.helpers({ return moment(this.createdAt).fromNow(); }, shipmentTracking() { - return this.shipping[0].shipmentMethod.tracking; + const shippingObject = this.shipping.find((shipping) => { + return shipping.shopId === Reaction.getShopId(); + }); + return shippingObject.shipmentMethod.tracking; }, shopName() { const shop = Shops.findOne(this.shopId); diff --git a/imports/plugins/core/orders/client/templates/workflow/shippingInvoice.js b/imports/plugins/core/orders/client/templates/workflow/shippingInvoice.js index 065e7733416..5b11c3e91e4 100644 --- a/imports/plugins/core/orders/client/templates/workflow/shippingInvoice.js +++ b/imports/plugins/core/orders/client/templates/workflow/shippingInvoice.js @@ -8,13 +8,19 @@ import { ReactiveDict } from "meteor/reactive-dict"; import { i18next, Logger, Reaction } from "/client/api"; import { Orders, Shops, Packages } from "/lib/collections"; import InvoiceContainer from "../../containers/invoiceContainer.js"; - +import { getBillingInfo } from "../../helpers"; // helper to return the order payment object // the first credit paymentMethod on the order // returns entire payment method function orderCreditMethod(order) { - return order.billing.filter(value => value.paymentMethod.method === "credit")[0]; + const creditMethods = order.billing && order.billing.filter((value) => { + return value && value.paymentMethod && value.paymentMethod.method === "credit"; + }); + const creditMethod = creditMethods && creditMethods.find((billing) => { + billing && billing.shopId === Reaction.getShopId(); + }); + return creditMethod || {}; } // @@ -115,20 +121,21 @@ Template.coreOrderShippingInvoice.events({ "click [data-event-action=cancelOrder]": (event, instance) => { event.preventDefault(); const order = instance.state.get("order"); - const invoiceTotal = order.billing[0].invoice.total; + const invoiceTotal = getBillingInfo(order).invoice && getBillingInfo(order).invoice.total; const currencySymbol = instance.state.get("currency").symbol; + const paymentMethod = getBillingInfo(order).paymentMethod; Meteor.subscribe("Packages", Reaction.getShopId()); - const packageId = order.billing[0].paymentMethod.paymentPackageId; - const settingsKey = order.billing[0].paymentMethod.paymentSettingsKey; + const packageId = paymentMethod && paymentMethod.paymentPackageId; + const settingsKey = paymentMethod && paymentMethod.paymentSettingsKey; // check if payment provider supports de-authorize const checkSupportedMethods = Packages.findOne({ _id: packageId, shopId: Reaction.getShopId() }).settings[settingsKey].support; - const orderStatus = order.billing[0].paymentMethod.status; - const orderMode = order.billing[0].paymentMethod.mode; + const orderStatus = paymentMethod && paymentMethod.status; + const orderMode = paymentMethod && paymentMethod.mode; let alertText; if (_.includes(checkSupportedMethods, "de-authorize") || @@ -189,7 +196,7 @@ Template.coreOrderShippingInvoice.helpers({ disabled() { const instance = Template.instance(); const order = instance.state.get("order"); - const status = orderCreditMethod(order).paymentMethod.status; + const status = orderCreditMethod(order).paymentMethod && orderCreditMethod(order).paymentMethod.status; if (status === "approved" || status === "completed") { return "disabled"; diff --git a/imports/plugins/core/orders/client/templates/workflow/shippingTracking.js b/imports/plugins/core/orders/client/templates/workflow/shippingTracking.js index a2d76809f5a..8473f110bdc 100644 --- a/imports/plugins/core/orders/client/templates/workflow/shippingTracking.js +++ b/imports/plugins/core/orders/client/templates/workflow/shippingTracking.js @@ -1,10 +1,10 @@ -import _ from "lodash"; import { Meteor } from "meteor/meteor"; import { Tracker } from "meteor/tracker"; import { ReactiveVar } from "meteor/reactive-var"; import { Template } from "meteor/templating"; import { i18next, Reaction } from "/client/api"; import { Orders } from "/lib/collections"; +import { getShippingInfo } from "../../helpers"; Template.coreOrderShippingTracking.onCreated(() => { const template = Template.instance(); @@ -22,7 +22,7 @@ Template.coreOrderShippingTracking.onCreated(() => { } Tracker.autorun(() => { - template.order = getOrder(currentData.orderId, currentData.fulfillment._id); + template.order = getOrder(currentData.orderId, currentData.fulfillment && currentData.fulfillment._id); }); }); @@ -38,14 +38,15 @@ Template.coreOrderShippingTracking.events({ Meteor.call("shipping/status/refresh", orderId, (result) => { if (result && result.error) { instance.$("#btn-processing").addClass("hidden"); - Alerts.toast(i18next.t("orderShipping.labelError", { err: result.error }), "error", { timeout: 7000 }); + Alerts.toast(i18next.t("orderShipping.labelError", { error: result.error }), "error", { timeout: 7000 }); } }); }, "click [data-event-action=shipmentShipped]": function () { const template = Template.instance(); - Meteor.call("orders/shipmentShipped", template.order, template.order.shipping[0], (err) => { - if (err) { + const shipment = getShippingInfo(template.order); + Meteor.call("orders/shipmentShipped", template.order, shipment, (error) => { + if (error) { Alerts.toast(i18next.t("mail.alerts.cantSendEmail"), "error"); } else { Alerts.toast(i18next.t("mail.alerts.emailSent"), "success"); @@ -65,8 +66,8 @@ Template.coreOrderShippingTracking.events({ "click [data-event-action=resendNotification]": function () { const template = Template.instance(); - Meteor.call("orders/sendNotification", template.order, "shipped", (err) => { - if (err) { + Meteor.call("orders/sendNotification", template.order, "shipped", (error) => { + if (error) { Alerts.toast(i18next.t("mail.alerts.cantSendEmail"), "error"); } else { Alerts.toast(i18next.t("mail.alerts.emailSent"), "success"); @@ -76,8 +77,9 @@ Template.coreOrderShippingTracking.events({ "click [data-event-action=shipmentPacked]": () => { const template = Template.instance(); + const shipment = getShippingInfo(template.order); - Meteor.call("orders/shipmentPacked", template.order, template.order.shipping[0]); + Meteor.call("orders/shipmentPacked", template.order, shipment); }, "submit form[name=addTrackingForm]": (event, template) => { @@ -104,9 +106,14 @@ Template.coreOrderShippingTracking.events({ Template.coreOrderShippingTracking.helpers({ printableLabels() { - const { shippingLabelUrl, customsLabelUrl } = Template.instance().order.shipping[0]; - if (shippingLabelUrl || customsLabelUrl) { - return { shippingLabelUrl, customsLabelUrl }; + const order = Template.instance().order; + const shipment = getShippingInfo(order); + + if (shipment) { + const { shippingLabelUrl, customsLabelUrl } = shipment; + if (shippingLabelUrl || customsLabelUrl) { + return { shippingLabelUrl, customsLabelUrl }; + } } return false; @@ -115,14 +122,14 @@ Template.coreOrderShippingTracking.helpers({ const currentData = Template.currentData(); const order = Template.instance().order; - const shippedItems = _.every(currentData.fulfillment.items, (shipmentItem) => { - const fullItem = _.find(order.items, (orderItem) => { + const shippedItems = currentData.fulfillment && currentData.fulfillment.items.every((shipmentItem) => { + const fullItem = order.items.find((orderItem) => { if (orderItem._id === shipmentItem._id) { return true; } }); - return !_.includes(fullItem.workflow.workflow, "coreOrderItemWorkflow/shipped"); + return !fullItem.workflow.workflow.includes("coreOrderItemWorkflow/shipped"); }); return shippedItems; @@ -132,8 +139,8 @@ Template.coreOrderShippingTracking.helpers({ const currentData = Template.currentData(); const order = Template.instance().order; - const canceledItems = _.every(currentData.fulfillment.items, (shipmentItem) => { - const fullItem = _.find(order.items, (orderItem) => { + const canceledItems = currentData.fulfillment && currentData.fulfillment.items.every((shipmentItem) => { + const fullItem = order.items.find((orderItem) => { if (orderItem._id === shipmentItem._id) { return true; } @@ -149,14 +156,14 @@ Template.coreOrderShippingTracking.helpers({ const currentData = Template.currentData(); const order = Template.instance().order; - const completedItems = _.every(currentData.fulfillment.items, (shipmentItem) => { - const fullItem = _.find(order.items, (orderItem) => { + const completedItems = currentData.fulfillment && currentData.fulfillment.items.every((shipmentItem) => { + const fullItem = order.items.find((orderItem) => { if (orderItem._id === shipmentItem._id) { return true; } }); - return _.includes(fullItem.workflow.workflow, "coreOrderItemWorkflow/completed"); + return fullItem.workflow.workflow.includes("coreOrderItemWorkflow/completed"); }); return completedItems; @@ -170,7 +177,8 @@ Template.coreOrderShippingTracking.helpers({ // TODO: future proof by not using flatRates, but rather look for editable:true if (settings && settings.flatRates.enabled === true) { const template = Template.instance(); - const shipment = template.order.shipping[0]; + const order = template.order; + const shipment = getShippingInfo(order); const editing = template.showTrackingEditForm.get(); let view = false; if (editing === true || !shipment.tracking && editing === false) { @@ -187,13 +195,15 @@ Template.coreOrderShippingTracking.helpers({ return Template.instance().order; }, shipment() { - return Template.instance().order.shipping[0]; + const order = Template.instance().order; + return getShippingInfo(order); }, shipmentReady() { const order = Template.instance().order; - const shipment = order.shipping[0]; + const shipment = getShippingInfo(order); + const shipmentWorkflow = shipment.workflow; - return _.includes(shipment.workflow.workflow, "coreOrderWorkflow/packed") && shipment.tracking || - _.includes(shipment.workflow.workflow, "coreOrderWorkflow/packed"); + return shipmentWorkflow && shipmentWorkflow.workflow.includes("coreOrderWorkflow/packed") && shipment.tracking + || shipmentWorkflow && shipmentWorkflow.workflow.includes("coreOrderWorkflow/packed"); } }); diff --git a/imports/plugins/included/search-mongo/server/methods/searchcollections.js b/imports/plugins/included/search-mongo/server/methods/searchcollections.js index 1d0a7a53310..507e3f4d429 100644 --- a/imports/plugins/included/search-mongo/server/methods/searchcollections.js +++ b/imports/plugins/included/search-mongo/server/methods/searchcollections.js @@ -1,6 +1,5 @@ /* eslint camelcase: 0 */ import moment from "moment"; -import _ from "lodash"; import { Meteor } from "meteor/meteor"; import { check, Match } from "meteor/check"; import { Reaction, Logger } from "/server/api"; @@ -26,7 +25,7 @@ const supportedLanguages = ["da", "nl", "en", "fi", "fr", "de", "hu", "it", "nb" function filterFields(customFields) { const fieldNames = []; - const fieldKeys = _.keys(customFields); + const fieldKeys = Object.keys(customFields); for (const fieldKey of fieldKeys) { if (customFields[fieldKey]) { fieldNames.push(fieldKey); @@ -38,8 +37,8 @@ function filterFields(customFields) { // get the weights for all enabled fields function getScores(customFields, settings, collection = "products") { const weightObject = {}; - for (const weight of _.keys(settings[collection].weights)) { - if (_.includes(customFields, weight)) { + for (const weight of Object.keys(settings[collection].weights)) { + if (customFields.includes(weight)) { weightObject[weight] = settings[collection].weights[weight]; } } @@ -49,7 +48,7 @@ function getScores(customFields, settings, collection = "products") { function getSearchLanguage() { const shopId = Reaction.getShopId(); const shopLanguage = Shops.findOne(shopId).language; - if (_.includes(supportedLanguages, shopLanguage)) { + if (supportedLanguages.includes(shopLanguage)) { return { default_language: shopLanguage }; } return { default_language: "en" }; @@ -174,8 +173,8 @@ export function buildOrderSearchRecord(orderId) { } } // get the billing object for the current shop on the order (and not hardcoded [0]) - const shopBilling = order.billing.find( - billing => billing.shopId === Reaction.getShopId() + const shopBilling = order.billing && order.billing.find( + billing => billing && billing.shopId === Reaction.getShopId() ) || {}; // get the shipping object for the current shop on the order (and not hardcoded [0]) @@ -184,9 +183,9 @@ export function buildOrderSearchRecord(orderId) { ) || {}; orderSearch.billingName = shopBilling.address && shopBilling.address.fullName; - orderSearch.billingPhone = _.replace(shopBilling.address && shopBilling.address.phone, /\D/g, ""); - orderSearch.shippingName = shopShipping.address.fullName; - orderSearch.shippingPhone = _.replace(shopShipping.address.phone, /\D/g, ""); + orderSearch.billingPhone = shopBilling.address && shopBilling.address.phone.replace(/\D/g, ""); + orderSearch.shippingName = shopShipping.address && shopShipping.address.fullName; + orderSearch.shippingPhone = shopShipping.address && shopShipping.address.phone.replace(/\D/g, ""); orderSearch.billingAddress = { address: shopBilling.address && shopBilling.address.address1, postal: shopBilling.address && shopBilling.address.postal, @@ -195,11 +194,11 @@ export function buildOrderSearchRecord(orderId) { country: shopBilling.address && shopBilling.address.country }; orderSearch.shippingAddress = { - address: shopShipping.address.address1, - postal: shopShipping.address.postal, - city: shopShipping.address.city, - region: shopShipping.address.region, - country: shopShipping.address.country + address: shopShipping.address && shopShipping.address.address1, + postal: shopShipping.address && shopShipping.address.postal, + city: shopShipping.address && shopShipping.address.city, + region: shopShipping.address && shopShipping.address.region, + country: shopShipping.address && shopShipping.address.country }; orderSearch.userEmails = userEmails; orderSearch.orderTotal = shopBilling.invoice && shopBilling.invoice.total; diff --git a/server/imports/fixtures/orders.js b/server/imports/fixtures/orders.js index c6cda497776..7d9982b7576 100755 --- a/server/imports/fixtures/orders.js +++ b/server/imports/fixtures/orders.js @@ -129,6 +129,7 @@ export default function () { }, requiresShipping: true, shipping: [{ + shopId: getShopId(), items: [ { _id: itemIdOne, @@ -148,6 +149,7 @@ export default function () { }], // Shipping Schema billing: [{ _id: Random.id(), + shopId: getShopId(), address: getAddress({ isBillingDefault: true }), paymentMethod: paymentMethod({ method: "credit", @@ -180,6 +182,7 @@ export default function () { Factory.extend("order", { billing: [{ _id: Random.id(), + shopId: getShopId(), address: getAddress({ isBillingDefault: true }), paymentMethod: paymentMethod({ processor: "Paypal", diff --git a/server/methods/core/orders.app-test.js b/server/methods/core/orders.app-test.js index 655f383ee99..8f954d9f63c 100644 --- a/server/methods/core/orders.app-test.js +++ b/server/methods/core/orders.app-test.js @@ -6,6 +6,7 @@ import { expect } from "meteor/practicalmeteor:chai"; import { sinon } from "meteor/practicalmeteor:sinon"; import Fixtures from "/server/imports/fixtures"; import { Reaction } from "/server/api"; +import { getShop } from "/server/imports/fixtures/shops"; import { Orders, Media, Notifications, Products, Shops } from "/lib/collections"; @@ -13,11 +14,12 @@ Fixtures(); // examplePaymentMethod(); describe("orders test", function () { + const shop = getShop(); + const shopId = shop._id; let methods; let sandbox; let order; let example; - let shop; before(function (done) { methods = { @@ -56,10 +58,9 @@ describe("orders test", function () { check(arguments, [Match.Any]); }); - shop = Factory.create("shop"); order = Factory.create("order"); sandbox.stub(Reaction, "getShopId", () => order.shopId); - const paymentMethod = order.billing[0].paymentMethod; + const paymentMethod = billingObjectMethod(order).paymentMethod; sandbox.stub(paymentMethod, "paymentPackageId", example._id); return done(); }); @@ -78,8 +79,26 @@ describe("orders test", function () { }); } + function billingObjectMethod(orderObject) { + const billingObject = orderObject.billing.find((billing) => { + return billing.shopId === shopId; + }); + return billingObject; + } + + function shippingObjectMethod(orderObject) { + const shippingObject = orderObject.shipping.find((shipping) => { + return shipping.shopId === shopId; + }); + return shippingObject; + } + function orderCreditMethod(orderData) { - return orderData.billing.filter(value => value.paymentMethod.method === "credit")[0]; + const billingRecord = orderData.billing.filter(value => value.paymentMethod.method === "credit"); + const billingObject = billingRecord.find((billing) => { + return billing.shopId === shopId; + }); + return billingObject; } describe("orders/cancelOrder", function () { @@ -135,8 +154,8 @@ describe("orders test", function () { const returnToStock = false; spyOnMethod("cancelOrder", order.userId); Meteor.call("orders/cancelOrder", order, returnToStock); - const Order = Orders.findOne({ _id: order._id }); - expect(Order.billing[0].paymentMethod.mode).to.equal("cancel"); + const orderObject = Orders.findOne({ _id: order._id }); + expect(billingObjectMethod(orderObject).paymentMethod.mode).to.equal("cancel"); }); it("should change the workflow status of the item to coreOrderItemWorkflow/canceled", function () { @@ -152,7 +171,7 @@ describe("orders test", function () { describe("orders/shipmentPicked", function () { it("should throw an error if user is not admin", function () { sandbox.stub(Reaction, "hasPermission", () => false); - const shipment = order.shipping[0]; + const shipment = shippingObjectMethod(order); spyOnMethod("shipmentPicked", order, shipment); function shipmentPicked() { @@ -163,7 +182,7 @@ describe("orders test", function () { it("should update the order item workflow status to coreOrderItemWorkflow/picked", function () { sandbox.stub(Reaction, "hasPermission", () => true); - const shipment = order.shipping[0]; + const shipment = shippingObjectMethod(order); spyOnMethod("shipmentPicked", order.userId); Meteor.call("orders/shipmentPicked", order, shipment); const orderItem = Orders.findOne({ _id: order._id }).items[0]; @@ -172,10 +191,10 @@ describe("orders test", function () { it("should update the shipment workflow status to coreOrderWorkflow/picked", function () { sandbox.stub(Reaction, "hasPermission", () => true); - const shipment = order.shipping[0]; + const shipment = shippingObjectMethod(order); spyOnMethod("shipmentPicked", order.userId); Meteor.call("orders/shipmentPicked", order, shipment); - const orderShipment = Orders.findOne({ _id: order._id }).shipping[0]; + const orderShipment = shippingObjectMethod(Orders.findOne({ _id: order._id })); expect(orderShipment.workflow.status).to.equal("coreOrderWorkflow/picked"); }); }); @@ -183,7 +202,7 @@ describe("orders test", function () { describe("orders/shipmentPacked", function () { it("should throw an error if user is not admin", function () { sandbox.stub(Reaction, "hasPermission", () => false); - const shipment = order.shipping[0]; + const shipment = shippingObjectMethod(order); spyOnMethod("shipmentPacked", order, shipment); function shipmentPacked() { @@ -194,7 +213,7 @@ describe("orders test", function () { it("should update the order item workflow status to coreOrderItemWorkflow/packed", function () { sandbox.stub(Reaction, "hasPermission", () => true); - const shipment = order.shipping[0]; + const shipment = shippingObjectMethod(order); spyOnMethod("shipmentPacked", order.userId); Meteor.call("orders/shipmentPacked", order, shipment); const orderItem = Orders.findOne({ _id: order._id }).items[0]; @@ -203,10 +222,10 @@ describe("orders test", function () { it("should update the shipment workflow status to coreOrderWorkflow/packed", function () { sandbox.stub(Reaction, "hasPermission", () => true); - const shipment = order.shipping[0]; + const shipment = shippingObjectMethod(order); spyOnMethod("shipmentPacked", order.userId); Meteor.call("orders/shipmentPacked", order, shipment); - const orderShipment = Orders.findOne({ _id: order._id }).shipping[0]; + const orderShipment = shippingObjectMethod(Orders.findOne({ _id: order._id })); expect(orderShipment.workflow.status).to.equal("coreOrderWorkflow/packed"); }); }); @@ -214,7 +233,7 @@ describe("orders test", function () { describe("orders/shipmentLabeled", function () { it("should throw an error if user is not admin", function () { sandbox.stub(Reaction, "hasPermission", () => false); - const shipment = order.shipping[0]; + const shipment = shippingObjectMethod(order); spyOnMethod("shipmentLabeled", order, shipment); function shipmentLabeled() { @@ -225,7 +244,7 @@ describe("orders test", function () { it("should update the order item workflow status to coreOrderItemWorkflow/labeled", function () { sandbox.stub(Reaction, "hasPermission", () => true); - const shipment = order.shipping[0]; + const shipment = shippingObjectMethod(order); spyOnMethod("shipmentLabeled", order.userId); Meteor.call("orders/shipmentLabeled", order, shipment); const orderItem = Orders.findOne({ _id: order._id }).items[0]; @@ -234,10 +253,10 @@ describe("orders test", function () { it("should update the shipment workflow status to coreOrderWorkflow/labeled", function () { sandbox.stub(Reaction, "hasPermission", () => true); - const shipment = order.shipping[0]; + const shipment = shippingObjectMethod(order); spyOnMethod("shipmentLabeled", order.userId); Meteor.call("orders/shipmentLabeled", order, shipment); - const orderShipment = Orders.findOne({ _id: order._id }).shipping[0]; + const orderShipment = shippingObjectMethod(Orders.findOne({ _id: order._id })); expect(orderShipment.workflow.status).to.equal("coreOrderWorkflow/labeled"); }); }); @@ -257,7 +276,7 @@ describe("orders test", function () { sandbox.stub(Reaction, "hasPermission", () => true); spyOnMethod("makeAdjustmentsToInvoice", order.userId); Meteor.call("orders/makeAdjustmentsToInvoice", order); - const orderPaymentMethodStatus = Orders.findOne({ _id: order._id }).billing[0].paymentMethod.status; + const orderPaymentMethodStatus = billingObjectMethod(Orders.findOne({ _id: order._id })).paymentMethod.status; expect(orderPaymentMethodStatus).equal("adjustments"); }); }); @@ -283,7 +302,7 @@ describe("orders test", function () { const discountTotal = Math.max(0, subTotal - discount); // ensure no discounting below 0. const total = accounting.toFixed(discountTotal + shipping + taxes, 2); Meteor.call("orders/approvePayment", order); - const orderBilling = Orders.findOne({ _id: order._id }).billing[0]; + const orderBilling = billingObjectMethod(Orders.findOne({ _id: order._id })); expect(orderBilling.paymentMethod.status).to.equal("approved"); expect(orderBilling.paymentMethod.mode).to.equal("capture"); expect(orderBilling.invoice.discounts).to.equal(discount); @@ -294,43 +313,47 @@ describe("orders test", function () { describe("orders/shipmentShipped", function () { it("should throw an error if user does not have permission", function () { sandbox.stub(Reaction, "hasPermission", () => false); + const shipment = shippingObjectMethod(order); spyOnMethod("shipmentShipped", order.userId); function shipmentShipped() { - return Meteor.call("orders/shipmentShipped", order, order.shipping[0]); + return Meteor.call("orders/shipmentShipped", order, shipment); } expect(shipmentShipped).to.throw(Meteor.Error, /Access Denied/); }); it("should update the order item workflow status to coreOrderItemWorkflow/completed", function () { sandbox.stub(Reaction, "hasPermission", () => true); + const shipment = shippingObjectMethod(order); sandbox.stub(Meteor.server.method_handlers, "orders/sendNotification", function () { check(arguments, [Match.Any]); }); spyOnMethod("shipmentShipped", order.userId); - Meteor.call("orders/shipmentShipped", order, order.shipping[0]); + Meteor.call("orders/shipmentShipped", order, shipment); const orderItem = Orders.findOne({ _id: order._id }).items[0]; expect(orderItem.workflow.status).to.equal("coreOrderItemWorkflow/completed"); }); it("should update the order workflow status to completed", function () { sandbox.stub(Reaction, "hasPermission", () => true); + const shipment = shippingObjectMethod(order); sandbox.stub(Meteor.server.method_handlers, "orders/sendNotification", function () { check(arguments, [Match.Any]); }); spyOnMethod("shipmentShipped", order.userId); - Meteor.call("orders/shipmentShipped", order, order.shipping[0]); + Meteor.call("orders/shipmentShipped", order, shipment); const orderStatus = Orders.findOne({ _id: order._id }).workflow.status; expect(orderStatus).to.equal("coreOrderWorkflow/completed"); }); it("should update the order shipping workflow status to coreOrderWorkflow/shipped", function () { sandbox.stub(Reaction, "hasPermission", () => true); + const shipment = shippingObjectMethod(order); sandbox.stub(Meteor.server.method_handlers, "orders/sendNotification", function () { check(arguments, [Match.Any]); }); spyOnMethod("shipmentShipped", order.userId); - Meteor.call("orders/shipmentShipped", order, order.shipping[0]); - const orderShipped = Orders.findOne({ _id: order._id }).shipping[0]; + Meteor.call("orders/shipmentShipped", order, shipment); + const orderShipped = shippingObjectMethod(Orders.findOne({ _id: order._id })); expect(orderShipped.workflow.status).to.equal("coreOrderWorkflow/shipped"); }); }); @@ -363,7 +386,7 @@ describe("orders test", function () { sandbox.stub(Reaction, "hasPermission", () => true); spyOnMethod("shipmentDelivered", order.userId); Meteor.call("orders/shipmentDelivered", order); - const orderShipping = Orders.findOne({ _id: order._id }).shipping[0]; + const orderShipping = shippingObjectMethod(Orders.findOne({ _id: order._id })); expect(orderShipping.workflow.status).to.equal("coreOrderWorkflow/delivered"); }); @@ -403,21 +426,23 @@ describe("orders test", function () { describe("orders/updateShipmentTracking", function () { it("should return an error if user does not have permission", function () { sandbox.stub(Reaction, "hasPermission", () => false); + const shipment = shippingObjectMethod(order); spyOnMethod("updateShipmentTracking", order.userId); function updateShipmentTracking() { const trackingValue = "2340FLKD104309"; - return Meteor.call("orders/updateShipmentTracking", order, order.shipping[0], trackingValue); + return Meteor.call("orders/updateShipmentTracking", order, shipment, trackingValue); } expect(updateShipmentTracking).to.throw(Meteor.error, /Access Denied/); }); it("should update the order tracking value", function () { sandbox.stub(Reaction, "hasPermission", () => true); + const shipment = shippingObjectMethod(order); spyOnMethod("updateShipmentTracking", order.userId); const trackingValue = "2340FLKD104309"; - Meteor.call("orders/updateShipmentTracking", order, order.shipping[0], trackingValue); + Meteor.call("orders/updateShipmentTracking", order, shipment, trackingValue); const orders = Orders.findOne({ _id: order._id }); - expect(orders.shipping[0].tracking).to.equal(trackingValue); + expect(shippingObjectMethod(orders).tracking).to.equal(trackingValue); }); }); @@ -468,7 +493,7 @@ describe("orders test", function () { beforeEach(function (done) { Orders.update({ "_id": order._id, - "billing.paymentMethod.transactionId": order.billing[0].paymentMethod.transactionId + "billing.paymentMethod.transactionId": billingObjectMethod(order).paymentMethod.transactionId }, { $set: { "billing.$.paymentMethod.mode": "capture", @@ -499,7 +524,7 @@ describe("orders test", function () { sandbox.stub(Reaction, "hasPermission", () => true); spyOnMethod("capturePayments", order.userId); Meteor.call("orders/capturePayments", order._id, () => { - const orderPaymentMethod = Orders.findOne({ _id: order._id }).billing[0].paymentMethod; + const orderPaymentMethod = billingObjectMethod(Orders.findOne({ _id: order._id })).paymentMethod; expect(orderPaymentMethod.mode).to.equal("capture"); expect(orderPaymentMethod.status).to.equal("completed"); }); @@ -517,7 +542,7 @@ describe("orders test", function () { }; }); Meteor.call("orders/capturePayments", order._id, () => { - const orderPaymentMethod = Orders.findOne({ _id: order._id }).billing[0].paymentMethod; + const orderPaymentMethod = billingObjectMethod(Orders.findOne({ _id: order._id })).paymentMethod; expect(orderPaymentMethod.mode).to.equal("capture"); expect(orderPaymentMethod.status).to.equal("error"); }); @@ -545,21 +570,23 @@ describe("orders test", function () { it("should return error if user is does not have admin permissions", function () { sandbox.stub(Reaction, "hasPermission", () => false); + const billing = billingObjectMethod(order); spyOnMethod("refunds/create", order.userId); function refundsCreate() { const amount = 5.20; - return Meteor.call("orders/refunds/create", order._id, order.billing[0].paymentMethod, amount); + return Meteor.call("orders/refunds/create", order._id, billing.paymentMethod, amount); } expect(refundsCreate).to.throw(Meteor.error, /Access Denied/); }); it("should update the order as refunded", function () { sandbox.stub(Reaction, "hasPermission", () => true); + const billing = billingObjectMethod(order); spyOnMethod("refunds/create", order.userId); const amount = 5.20; - Meteor.call("orders/refunds/create", order._id, order.billing[0].paymentMethod, amount); + Meteor.call("orders/refunds/create", order._id, billing.paymentMethod, amount); const updateOrder = Orders.findOne({ _id: order._id }); - expect(updateOrder.billing[0].paymentMethod.status).to.equal("refunded"); + expect(billingObjectMethod(updateOrder).paymentMethod.status).to.equal("refunded"); }); }); @@ -572,6 +599,7 @@ describe("orders test", function () { it("should return error if user does not have admin permissions", function () { sandbox.stub(Reaction, "hasPermission", () => false); + const billing = billingObjectMethod(order); spyOnMethod("refunds/refundItems", order.userId); function refundItems() { const refundItemsInfo = { @@ -579,13 +607,14 @@ describe("orders test", function () { quantity: 2, items: [{}, {}] }; - return Meteor.call("orders/refunds/refundItems", order._id, order.billing[0].paymentMethod, refundItemsInfo); + return Meteor.call("orders/refunds/refundItems", order._id, billing.paymentMethod, refundItemsInfo); } expect(refundItems).to.throw(Meteor.error, /Access Denied/); }); it("should update the order as partially refunded if not all of items in the order are refunded", function () { sandbox.stub(Reaction, "hasPermission", () => true); + const billing = billingObjectMethod(order); spyOnMethod("refunds/refundItems", order.userId); const originalQuantity = order.items.reduce((acc, item) => acc + item.quantity, 0); const quantity = originalQuantity - 1; @@ -594,13 +623,14 @@ describe("orders test", function () { quantity: quantity, items: [{}, {}] }; - Meteor.call("orders/refunds/refundItems", order._id, order.billing[0].paymentMethod, refundItemsInfo); + Meteor.call("orders/refunds/refundItems", order._id, billing.paymentMethod, refundItemsInfo); const updateOrder = Orders.findOne({ _id: order._id }); - expect(updateOrder.billing[0].paymentMethod.status).to.equal("partialRefund"); + expect(billingObjectMethod(updateOrder).paymentMethod.status).to.equal("partialRefund"); }); it("should update the order as refunded if all items in the order are refunded", function () { sandbox.stub(Reaction, "hasPermission", () => true); + const billing = billingObjectMethod(order); spyOnMethod("refunds/refundItems", order.userId); const originalQuantity = order.items.reduce((acc, item) => acc + item.quantity, 0); const refundItemsInfo = { @@ -608,9 +638,9 @@ describe("orders test", function () { quantity: originalQuantity, items: [{}, {}] }; - Meteor.call("orders/refunds/refundItems", order._id, order.billing[0].paymentMethod, refundItemsInfo); + Meteor.call("orders/refunds/refundItems", order._id, billing.paymentMethod, refundItemsInfo); const updateOrder = Orders.findOne({ _id: order._id }); - expect(updateOrder.billing[0].paymentMethod.status).to.equal("refunded"); + expect(billingObjectMethod(updateOrder).paymentMethod.status).to.equal("refunded"); }); }); }); diff --git a/server/methods/core/orders.js b/server/methods/core/orders.js index 486acb97b3b..ff5695a2234 100644 --- a/server/methods/core/orders.js +++ b/server/methods/core/orders.js @@ -13,14 +13,22 @@ import { Logger, Hooks, Reaction } from "/server/api"; // helper to return the order credit object -// the first credit paymentMethod on the order +// credit paymentMethod on the order as per current active shop // returns entire payment method export function orderCreditMethod(order) { - return order.billing.filter(value => value.paymentMethod.method === "credit")[0]; + const creditBillingRecords = order.billing.filter(value => value.paymentMethod.method === "credit"); + const billingRecord = creditBillingRecords.find((billing) => { + return billing.shopId === Reaction.getShopId(); + }); + return billingRecord; } // helper to return the order debit object export function orderDebitMethod(order) { - return order.billing.filter(value => value.paymentMethod.method === "debit")[0]; + const debitBillingRecords = order.billing.filter(value => value.paymentMethod.method === "debit"); + const billingRecord = debitBillingRecords.find((billing) => { + return billing.shopId === Reaction.getShopId(); + }); + return billingRecord; } // REVIEW: This jsdoc doesn't seem to be accurate @@ -215,6 +223,7 @@ export const methods = { return Orders.update({ "_id": order._id, + "billing.shopId": Reaction.getShopId, "billing.paymentMethod.method": "credit" }, { $set: { @@ -256,6 +265,7 @@ export const methods = { return Orders.update({ "_id": order._id, + "billing.shopId": Reaction.getShopId, "billing.paymentMethod.method": "credit" }, { $set: { @@ -290,10 +300,13 @@ export const methods = { ordersInventoryAdjust(order._id); } + const billingRecord = order.billing.find(billing => billing.shopId === Reaction.getShopId()); + const shippingRecord = order.shipping.find(shipping => shipping.shopId === Reaction.getShopId()); + let paymentMethod = orderCreditMethod(order).paymentMethod; paymentMethod = Object.assign(paymentMethod, { amount: Number(paymentMethod.amount) }); - const invoiceTotal = order.billing[0].invoice.total; - const shipment = order.shipping[0]; + const invoiceTotal = billingRecord.invoice.total; + const shipment = shippingRecord; const itemIds = shipment.items.map((item) => { return item._id; }); @@ -314,6 +327,7 @@ export const methods = { return Orders.update({ "_id": order._id, + "billing.shopId": Reaction.getShopId, "billing.paymentMethod.method": "credit" }, { $set: { @@ -348,8 +362,9 @@ export const methods = { if (result) { Meteor.call("workflow/pushOrderWorkflow", "coreOrderWorkflow", "coreProcessPayment", order._id); + const shippingRecord = order.shipping.find(shipping => shipping.shopId === Reaction.getShopId()); // Set the status of the items as shipped - const itemIds = order.shipping[0].items.map((item) => { + const itemIds = shippingRecord.items.map((item) => { return item._id; }); @@ -445,7 +460,7 @@ export const methods = { this.unblock(); - const shipment = order.shipping[0]; + const shipment = order.shipping.find(shipping => shipping.shopId === Reaction.getShopId()); if (order.email) { Meteor.call("orders/sendNotification", order, (err) => { @@ -464,8 +479,8 @@ export const methods = { Meteor.call("workflow/pushItemWorkflow", "coreOrderItemWorkflow/delivered", order, itemIds); Meteor.call("workflow/pushItemWorkflow", "coreOrderItemWorkflow/completed", order, itemIds); - const isCompleted = _.every(order.items, (item) => { - return _.includes(item.workflow.workflow, "coreOrderItemWorkflow/completed"); + const isCompleted = order.items.every((item) => { + return item.workflow.workflow && item.workflow.workflow.includes("coreOrderItemWorkflow/completed"); }); Orders.update({ @@ -516,7 +531,7 @@ export const methods = { // Get shop logo, if available let emailLogo; if (Array.isArray(shop.brandAssets)) { - const brandAsset = _.find(shop.brandAssets, (asset) => asset.type === "navbarBrandImage"); + const brandAsset = shop.brandAssets.find((asset) => asset.type === "navbarBrandImage"); const mediaId = Media.findOne(brandAsset.mediaId); emailLogo = path.join(Meteor.absoluteUrl(), mediaId.url()); } else { @@ -524,6 +539,7 @@ export const methods = { } const billing = orderCreditMethod(order); + const shippingRecord = order.shipping.find(shipping => shipping.shopId === Reaction.getShopId()); // TODO: Update */refunds/list for marketplace const refundResult = Meteor.call("orders/refunds/list", order); const refundTotal = refundResult.reduce((acc, refund) => acc + refund.amount, 0); @@ -651,13 +667,13 @@ export const methods = { orderDate: moment(order.createdAt).format("MM/DD/YYYY"), orderUrl: getSlug(shop.name) + "/cart/completed?_id=" + order.cartId, shipping: { - tracking: order.shipping[0].tracking, - carrier: order.shipping[0].shipmentMethod.carrier, + tracking: shippingRecord.tracking, + carrier: shippingRecord.shipmentMethod.carrier, address: { - address: order.shipping[0].address.address1, - city: order.shipping[0].address.city, - region: order.shipping[0].address.region, - postal: order.shipping[0].address.postal + address: shippingRecord.address.address1, + city: shippingRecord.address.city, + region: shippingRecord.address.region, + postal: shippingRecord.address.postal } } }; @@ -831,7 +847,7 @@ export const methods = { } // process order..payment.paymentMethod - _.each(order.billing, function (billing) { + order.billing.forEach((billing) => { const paymentMethod = billing.paymentMethod; const transactionId = paymentMethod.transactionId; @@ -956,7 +972,7 @@ export const methods = { let result; let query = {}; - if (_.includes(checkSupportedMethods, "De-authorize")) { + if (checkSupportedMethods.includes("De-authorize")) { result = Meteor.call(`${processor}/payment/deAuthorize`, paymentMethod, amount); query = { $push: { @@ -990,6 +1006,7 @@ export const methods = { Orders.update({ "_id": orderId, + "billing.shopId": Reaction.getShopId(), "billing.paymentMethod.transactionId": transactionId }, { $set: { @@ -1050,6 +1067,7 @@ export const methods = { Orders.update({ "_id": orderId, + "billing.shopId": Reaction.getShopId(), "billing.paymentMethod.transactionId": transactionId }, { $set: {