Skip to content

Commit

Permalink
Merge pull request #5018 from reactioncommerce/feat-aldeed-move-order…
Browse files Browse the repository at this point in the history
…-items-mutation

Add moveOrderItems mutation
  • Loading branch information
aldeed authored Mar 14, 2019
2 parents a63f5e7 + 0e8227a commit bfd6c69
Show file tree
Hide file tree
Showing 17 changed files with 1,098 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`throws if an order item doesn't exist 1`] = `"Some order items not found"`;

exports[`throws if fromFulfillmentGroupId isn't supplied 1`] = `"From fulfillment group ID is required"`;

exports[`throws if itemIds is empty 1`] = `"You must specify at least 1 values"`;

exports[`throws if itemIds isn't supplied 1`] = `"Item ids is required"`;

exports[`throws if orderId isn't supplied 1`] = `"Order ID is required"`;

exports[`throws if permission check fails 1`] = `"Access Denied"`;

exports[`throws if the database update fails 1`] = `"queries.getFulfillmentMethodsWithQuotes is not a function"`;

exports[`throws if the from group would have no items remaining 1`] = `"move would result in group having no items"`;

exports[`throws if the fromFulfillmentGroup doesn't exist 1`] = `"Order fulfillment group (from) not found"`;

exports[`throws if the order doesn't exist 1`] = `"Order not found"`;

exports[`throws if the toFulfillmentGroup doesn't exist 1`] = `"Order fulfillment group (to) not found"`;

exports[`throws if toFulfillmentGroupId isn't supplied 1`] = `"To fulfillment group ID is required"`;

exports[`throws if user who placed order tries to move item at invalid current item status 1`] = `"Item status (processing) is not one of: new"`;

exports[`throws if user who placed order tries to move item at invalid current order status 1`] = `"Order status (processing) is not one of: new"`;
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import addOrderFulfillmentGroup from "./addOrderFulfillmentGroup";
import cancelOrderItem from "./cancelOrderItem";
import moveOrderItems from "./moveOrderItems";
import placeOrder from "./placeOrder";
import updateOrder from "./updateOrder";
import updateOrderFulfillmentGroup from "./updateOrderFulfillmentGroup";

export default {
addOrderFulfillmentGroup,
cancelOrderItem,
moveOrderItems,
placeOrder,
updateOrder,
updateOrderFulfillmentGroup
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import SimpleSchema from "simpl-schema";
import ReactionError from "@reactioncommerce/reaction-error";
import { Order as OrderSchema } from "/imports/collections/schemas";
import updateGroupStatusFromItemStatus from "../util/updateGroupStatusFromItemStatus";
import updateGroupTotals from "../util/updateGroupTotals";

// These should eventually be configurable in settings
const itemStatusesThatOrdererCanMove = ["new"];
const orderStatusesThatOrdererCanMove = ["new"];

const inputSchema = new SimpleSchema({
"fromFulfillmentGroupId": String,
"itemIds": {
type: Array,
minCount: 1
},
"itemIds.$": String,
"orderId": String,
"toFulfillmentGroupId": String
});

/**
* @method moveOrderItems
* @summary Use this mutation to move one or more items between existing order
* fulfillment groups.
* @param {Object} context - an object containing the per-request state
* @param {Object} input - Necessary input. See SimpleSchema
* @return {Promise<Object>} Object with `order` property containing the updated order
*/
export default async function moveOrderItems(context, input) {
inputSchema.validate(input);

const {
fromFulfillmentGroupId,
itemIds,
orderId,
toFulfillmentGroupId
} = input;

const { accountId, appEvents, collections, isInternalCall, userHasPermission, userId } = context;
const { Orders } = collections;

// First verify that this order actually exists
const order = await Orders.findOne({ _id: orderId });
if (!order) throw new ReactionError("not-found", "Order not found");

// Allow move if the account that placed the order is attempting to move
// or if the account has "orders" permission. When called internally by another
// plugin, context.isInternalCall can be set to `true` to disable this check.
if (
!isInternalCall &&
(!accountId || accountId !== order.accountId) &&
!userHasPermission(["orders"], order.shopId)
) {
throw new ReactionError("access-denied", "Access Denied");
}

// Is the account calling this mutation also the account that placed the order?
// We need this check in a couple places below, so we'll get it here.
const accountIsOrderer = (order.accountId && accountId === order.accountId);

// The orderer may only move items while the order status is still "new"
if (accountIsOrderer && !orderStatusesThatOrdererCanMove.includes(order.workflow.status)) {
throw new ReactionError("invalid", `Order status (${order.workflow.status}) is not one of: ${orderStatusesThatOrdererCanMove.join(", ")}`);
}

// Find the two fulfillment groups we're modifying
const fromGroup = order.shipping.find((group) => group._id === fromFulfillmentGroupId);
if (!fromGroup) throw new ReactionError("not-found", "Order fulfillment group (from) not found");

const toGroup = order.shipping.find((group) => group._id === toFulfillmentGroupId);
if (!toGroup) throw new ReactionError("not-found", "Order fulfillment group (to) not found");

// Pull out the item's we're moving
const foundItemIds = [];
const movedItems = fromGroup.items.reduce((list, item) => {
if (itemIds.includes(item._id)) {
// The orderer may only move while the order item status is still "new"
if (accountIsOrderer && !itemStatusesThatOrdererCanMove.includes(item.workflow.status)) {
throw new ReactionError("invalid", `Item status (${item.workflow.status}) is not one of: ${itemStatusesThatOrdererCanMove.join(", ")}`);
}

list.push(item);
foundItemIds.push(item._id);
}
return list;
}, []);

if (!itemIds.every((id) => foundItemIds.includes(id))) {
throw new ReactionError("not-found", "Some order items not found");
}

const { billingAddress, cartId, currencyCode } = order;

// Find and move the items
const orderSurcharges = [];
const updatedGroups = await Promise.all(order.shipping.map(async (group) => {
if (group._id !== fromFulfillmentGroupId && group._id !== toFulfillmentGroupId) return group;

let updatedItems;
if (group._id === fromFulfillmentGroupId) {
// Remove the moved items
updatedItems = group.items.filter((item) => !itemIds.includes(item._id));
} else {
// Add the moved items
updatedItems = [...group.items, ...movedItems];
}

if (updatedItems.length === 0) {
throw new ReactionError("invalid-param", "move would result in group having no items");
}

// Create an updated group
const updatedGroup = {
...group,
// There is a convenience itemIds prop, so update that, too
itemIds: updatedItems.map((item) => item._id),
items: updatedItems,
totalItemQuantity: updatedItems.reduce((sum, item) => sum + item.quantity, 0)
};

// Update group shipping, tax, totals, etc.
const { groupSurcharges } = await updateGroupTotals(context, {
billingAddress,
cartId,
currencyCode,
discountTotal: updatedGroup.invoice.discounts,
group: updatedGroup,
orderId,
selectedFulfillmentMethodId: updatedGroup.shipmentMethod._id
});

// Push all group surcharges to overall order surcharge array.
// Currently, we do not save surcharges per group
orderSurcharges.push(...groupSurcharges);

// Ensure proper group status
updateGroupStatusFromItemStatus(updatedGroup);

return updatedGroup;
}));

// We're now ready to actually update the database and emit events
const modifier = {
$set: {
shipping: updatedGroups,
updatedAt: new Date()
}
};

OrderSchema.validate(modifier, { modifier: true });

const { modifiedCount, value: updatedOrder } = await Orders.findOneAndUpdate(
{ _id: orderId },
modifier,
{ returnOriginal: false }
);
if (modifiedCount === 0 || !updatedOrder) throw new ReactionError("server-error", "Unable to update order");

await appEvents.emit("afterOrderUpdate", {
order: updatedOrder,
updatedBy: userId
});

return { order: updatedOrder };
}
Loading

0 comments on commit bfd6c69

Please sign in to comment.