-
Notifications
You must be signed in to change notification settings - Fork 2.2k
feat: add ordersByAccountId query #4981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
75f92f6
feat: add ordersByAccountId query
kieckhafer 2a113ef
fix formatting of package.json
kieckhafer b64ab60
Merge remote-tracking branch 'origin/develop' into feat-kieckhafer-or…
kieckhafer 8d77f77
add order status resolver and types
kieckhafer cd5c14d
add permissions check for multiple shopIds
kieckhafer 6f86510
Merge remote-tracking branch 'origin/develop' into feat-kieckhafer-or…
kieckhafer d418cac
Merge remote-tracking branch 'origin/develop' into feat-kieckhafer-or…
kieckhafer 34c857f
add shopsUserHasPermissionFor check
kieckhafer 3595b72
change ordersByAccountId to paginated list
kieckhafer 6a88627
add `shopUserHasPermissionsFor` permission check
kieckhafer 18f59a4
add and update tests for new permissions check
kieckhafer e122131
update variables to fix lint issues
kieckhafer af1e5a8
alphabatize variables for easier searching
kieckhafer 7c99274
add language to orderStatusLabels query
kieckhafer b2eb181
extend Shops schema to allow for orderStatusLabel translations
kieckhafer 4a5f298
Merge remote-tracking branch 'origin/develop' into feat-kieckhafer-or…
kieckhafer f35ec0f
Merge remote-tracking branch 'origin/develop' into feat-kieckhafer-or…
kieckhafer e6d6a3b
add tracking number to fulfillment gropu data
kieckhafer 3e81f65
add orderStatus variable to query to limit search resutls to certain …
kieckhafer 836a402
make order status optional, always return all if no other statuses ar…
kieckhafer c4e3b09
add filter for canceled ordres to query
kieckhafer 7f29d63
add orderSummary to order resolver
kieckhafer ef3c086
get currencyCode form order object instead of first fulfillmentGroup
kieckhafer 6cc54fd
refactor the way Status is passed to the client
kieckhafer 009bff0
use break instead of ifs for statuses
kieckhafer 62586a0
Pass array of orderstatus into ordersByAccountId
kieckhafer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
imports/plugins/core/accounts/server/no-meteor/shopsUserHasPermissionFor.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import { curryN } from "ramda"; | ||
|
|
||
| /** | ||
| * @name shopsUserHasPermissionFor | ||
| * @method | ||
| * @memberof Accounts | ||
| * @param {Object} user - The user object, with `roles` property, to check. | ||
| * @param {String} permission - Permission to check for. | ||
| * @return {Array} Shop IDs user has provided permissions for | ||
| */ | ||
| export default function shopsUserHasPermissionFor(user, permission) { | ||
| if (!user || !user.roles || !permission) return []; | ||
|
|
||
| const { roles } = user; | ||
| const shopIds = []; | ||
|
|
||
| // `role` is a shopId, with an array of permissions attached to it. | ||
| // Get the key of each shopId, and check if the permission exists on that key | ||
| // If it does, then user has permission on this shop. | ||
| Object.keys(roles).forEach((role) => { | ||
| if (roles[role].includes(permission)) { | ||
| shopIds.push(role); | ||
| } | ||
| }); | ||
|
|
||
| return shopIds; | ||
| } | ||
|
|
||
| export const getShopsUserHasPermissionForFunctionForUser = curryN(2, shopsUserHasPermissionFor); |
49 changes: 49 additions & 0 deletions
49
imports/plugins/core/accounts/server/no-meteor/shopsUserHasPermissionFor.test.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import shopsUserHasPermissionFor from "./shopsUserHasPermissionFor"; | ||
|
|
||
| const user = { | ||
| roles: { | ||
| abc: [ | ||
| "accounts", | ||
| "orders" | ||
| ], | ||
| def: [ | ||
| "navigation", | ||
| "orders" | ||
| ] | ||
| } | ||
| }; | ||
|
|
||
| test("returns blank array if no user", () => { | ||
| const result = shopsUserHasPermissionFor(null, "orders"); | ||
| expect(result).toEqual([]); | ||
| }); | ||
|
|
||
| test("returns blank array if no user.roles", () => { | ||
| const result = shopsUserHasPermissionFor({}, "orders"); | ||
| expect(result).toEqual([]); | ||
| }); | ||
|
|
||
| test("returns blank array if no permission", () => { | ||
| const result = shopsUserHasPermissionFor(user, null); | ||
| expect(result).toEqual([]); | ||
| }); | ||
|
|
||
| test("returns an array of both shops for `orders` permission", () => { | ||
| const result = shopsUserHasPermissionFor(user, "orders"); | ||
| expect(result).toEqual(["abc", "def"]); | ||
| }); | ||
|
|
||
| test("returns an array of shop `abc` for `accounts` permission", () => { | ||
| const result = shopsUserHasPermissionFor(user, "accounts"); | ||
| expect(result).toEqual(["abc"]); | ||
| }); | ||
|
|
||
| test("returns an array of shop `def` for `navigation` permission", () => { | ||
| const result = shopsUserHasPermissionFor(user, "navigation"); | ||
| expect(result).toEqual(["def"]); | ||
| }); | ||
|
|
||
| test("returns a blank array for `dogs` permission", () => { | ||
| const result = shopsUserHasPermissionFor(user, "dogs"); | ||
| expect(result).toEqual([]); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { Shop } from "/imports/collections/schemas"; | ||
|
|
||
| /** | ||
| * @name Shop | ||
| * @memberof Schemas | ||
| * @type {SimpleSchema} | ||
| * @property {Object} orderStatusLabels optional | ||
| */ | ||
| Shop.extend({ | ||
| orderStatusLabels: { | ||
| type: Object, | ||
| blackbox: true | ||
| } | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| import orderById from "./orderById"; | ||
| import orderByReferenceId from "./orderByReferenceId"; | ||
| import ordersByAccountId from "./ordersByAccountId"; | ||
|
|
||
| export default { | ||
| orderById, | ||
| orderByReferenceId | ||
| orderByReferenceId, | ||
| ordersByAccountId | ||
| }; |
54 changes: 54 additions & 0 deletions
54
imports/plugins/core/orders/server/no-meteor/queries/ordersByAccountId.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import ReactionError from "@reactioncommerce/reaction-error"; | ||
|
|
||
| /** | ||
| * @name ordersByAccountId | ||
| * @method | ||
| * @memberof Order/NoMeteorQueries | ||
| * @summary Query the Orders collection for orders made by the provided accountId and (optionally) shopIds | ||
| * @param {Object} context - an object containing the per-request state | ||
| * @param {Object} params - request parameters | ||
| * @param {String} params.accountId - Account ID to search orders for | ||
| * @param {String} params.orderStatus - Workflow status to limit search results | ||
| * @param {String} params.shopIds - Shop IDs for the shops that owns the orders | ||
| * @return {Promise<Object>|undefined} - An Array of Order documents, if found | ||
| */ | ||
| export default async function ordersByAccountId(context, { accountId, orderStatus, shopIds } = {}) { | ||
| const { accountId: contextAccountId, collections, shopsUserHasPermissionFor, userHasPermission } = context; | ||
| const { Orders } = collections; | ||
|
|
||
| if (!accountId) { | ||
| throw new ReactionError("invalid-param", "You must provide accountId arguments"); | ||
| } | ||
|
|
||
| let query = { | ||
| accountId | ||
| }; | ||
|
|
||
| // If orderStatus array is provided, only return orders with statuses in Array | ||
| // Otherwise, return all orders | ||
| if (Array.isArray(orderStatus) && orderStatus.length > 0) { | ||
| query = { | ||
| "workflow.status": { $in: orderStatus }, | ||
| ...query | ||
| }; | ||
| } | ||
|
|
||
| if (shopIds) query.shopId = { $in: shopIds }; | ||
|
|
||
| if (accountId !== contextAccountId) { | ||
| // If an admin wants all orders for an account, we force it to be limited to the | ||
| // shops for which they're allowed to see orders. | ||
| if (!shopIds) { | ||
| const shopIdsUserHasPermissionFor = shopsUserHasPermissionFor("orders"); | ||
| query.shopId = { $in: shopIdsUserHasPermissionFor }; | ||
| } else { | ||
| shopIds.forEach((shopId) => { | ||
| if (!userHasPermission(["orders"], shopId)) { | ||
| throw new ReactionError("access-denied", "Access Denied"); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| return Orders.find(query); | ||
| } | ||
5 changes: 5 additions & 0 deletions
5
imports/plugins/core/orders/server/no-meteor/resolvers/Order/index.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,20 @@ | ||
| import { encodeCartOpaqueId } from "@reactioncommerce/reaction-graphql-xforms/cart"; | ||
| import { encodeOrderOpaqueId, xformOrderPayment } from "@reactioncommerce/reaction-graphql-xforms/order"; | ||
| import { resolveAccountFromAccountId, resolveShopFromShopId } from "@reactioncommerce/reaction-graphql-utils"; | ||
| import orderDisplayStatus from "./orderDisplayStatus"; | ||
| import orderSummary from "./orderSummary"; | ||
| import totalItemQuantity from "./totalItemQuantity"; | ||
|
|
||
| export default { | ||
| _id: (node) => encodeOrderOpaqueId(node._id), | ||
| account: resolveAccountFromAccountId, | ||
| cartId: (node) => encodeCartOpaqueId(node._id), | ||
| displayStatus: (node, { language }, context) => orderDisplayStatus(context, node, language), | ||
| fulfillmentGroups: (node) => node.shipping || [], | ||
| notes: (node) => node.notes || [], | ||
| payments: (node) => (Array.isArray(node.payments) ? node.payments.map(xformOrderPayment) : null), | ||
| shop: resolveShopFromShopId, | ||
| status: (node) => node.workflow.status, | ||
| summary: (node, _, context) => orderSummary(context, node), | ||
| totalItemQuantity | ||
| }; |
30 changes: 30 additions & 0 deletions
30
imports/plugins/core/orders/server/no-meteor/resolvers/Order/orderDisplayStatus.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| /** | ||
| * @name "Order.orderDisplayStatus" | ||
| * @method | ||
| * @memberof Order/GraphQL | ||
| * @summary Displays a human readable status of order state | ||
| * @param {Object} context An object with request-specific state | ||
| * @param {Object} order - Result of the parent resolver, which is a Order object in GraphQL schema format | ||
| * @param {String} language Language to filter item content by | ||
| * @return {String} A string of the order status | ||
| */ | ||
| export default async function orderDisplayStatus(context, order, language) { | ||
| const { Shops } = context.collections; | ||
| const shop = await Shops.findOne({ _id: order.shopId }); | ||
| const orderStatusLabels = shop && shop.orderStatusLabels; | ||
| const { workflow: { status } } = order; | ||
|
|
||
| // If translations are available in the `Shops` collection, | ||
| // and are available for this specific order status, get translations | ||
| if (orderStatusLabels && orderStatusLabels[status]) { | ||
| const orderStatusLabel = orderStatusLabels[status]; | ||
| const translatedLabel = orderStatusLabel.find((label) => label.language === language); | ||
|
|
||
| // If translations are available in desired language, return them. | ||
| // Otherwise, return raw status | ||
| return (translatedLabel && translatedLabel.label) || status; | ||
| } | ||
|
|
||
| // If no translations are available in the `Shops` collection, use raw status data | ||
| return status; | ||
| } |
78 changes: 78 additions & 0 deletions
78
imports/plugins/core/orders/server/no-meteor/resolvers/Order/orderSummary.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import { xformRateToRateObject } from "@reactioncommerce/reaction-graphql-xforms/core"; | ||
|
|
||
| /** | ||
| * @name "Order.orderSummary" | ||
| * @method | ||
| * @memberof Order/GraphQL | ||
| * @summary Returns an aggregate of all fulfillmentGroup summaries to provide a single orderSummary | ||
| * @param {Object} context An object with request-specific state | ||
| * @param {Object} order - Result of the parent resolver, which is a Order object in GraphQL schema format | ||
| * @return {Object} An object containing order pricing information from all fulfillmentGroups | ||
| */ | ||
| export default async function orderSummary(context, order) { | ||
| const { currencyCode, shipping: fulfillmentMethods } = order; | ||
| const totalDiscounts = []; | ||
| const totalShipping = []; | ||
| const totalSubtotal = []; | ||
| const totalSurcharges = []; | ||
| const totalTaxableAmount = []; | ||
| const totalTaxes = []; | ||
| const totalTotal = []; | ||
|
|
||
| // Loop over each fulfillmentGroup (shipping[]), and push all values into `totalX` array | ||
| fulfillmentMethods.forEach((fulfillmentMethod) => { | ||
| const { invoice: { discounts, shipping, subtotal, surcharges, taxableAmount, taxes, total } } = fulfillmentMethod; | ||
|
|
||
| totalDiscounts.push(discounts); | ||
| totalShipping.push(shipping); | ||
| totalSubtotal.push(subtotal); | ||
| totalSurcharges.push(surcharges); | ||
| totalTaxableAmount.push(taxableAmount); | ||
| totalTaxes.push(taxes); | ||
| totalTotal.push(total); | ||
| }); | ||
|
|
||
| // Reduce each `totalX` array to get order total from all fulfillmentGroups | ||
| const totalDiscountsAmount = totalDiscounts.reduce((acc, value) => acc + value, 0); | ||
| const totalShippingAmount = totalShipping.reduce((acc, value) => acc + value, 0); | ||
| const totalSubtotalAmount = totalSubtotal.reduce((acc, value) => acc + value, 0); | ||
| const totalSurchargesAmount = totalSurcharges.reduce((acc, value) => acc + value, 0); | ||
| const totalTaxableAmountAmount = totalTaxableAmount.reduce((acc, value) => acc + value, 0); | ||
| const totalTaxesAmount = totalTaxes.reduce((acc, value) => acc + value, 0); | ||
| const totalTotalAmount = totalTotal.reduce((acc, value) => acc + value, 0); | ||
|
|
||
| // Calculate effective tax rate of combined fulfillmentGroups | ||
| const effectiveTaxRate = totalTaxableAmountAmount > 0 ? totalTaxesAmount / totalTaxableAmountAmount : 0; | ||
|
|
||
| return { | ||
| discountTotal: { | ||
| amount: totalDiscountsAmount, | ||
| currencyCode | ||
| }, | ||
| effectiveTaxRate: xformRateToRateObject(effectiveTaxRate), | ||
| fulfillmentTotal: { | ||
| amount: totalShippingAmount, | ||
| currencyCode | ||
| }, | ||
| itemTotal: { | ||
| amount: totalSubtotalAmount, | ||
| currencyCode | ||
| }, | ||
| surchargeTotal: { | ||
| amount: totalSurchargesAmount, | ||
| currencyCode | ||
| }, | ||
| taxableAmount: { | ||
| amount: totalTaxableAmountAmount, | ||
| currencyCode | ||
| }, | ||
| taxTotal: { | ||
| amount: totalTaxesAmount, | ||
| currencyCode | ||
| }, | ||
| total: { | ||
| amount: totalTotalAmount, | ||
| currencyCode | ||
| } | ||
| }; | ||
| } |
4 changes: 3 additions & 1 deletion
4
imports/plugins/core/orders/server/no-meteor/resolvers/Query/index.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| import orderById from "./orderById"; | ||
| import orderByReferenceId from "./orderByReferenceId"; | ||
| import ordersByAccountId from "./ordersByAccountId"; | ||
|
|
||
| export default { | ||
| orderById, | ||
| orderByReferenceId | ||
| orderByReferenceId, | ||
| ordersByAccountId | ||
| }; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be more useful and not much extra work to do
orderStatusesarray rather than just one. Return orders with any of the statuses in the array. (So in UI the filter could be check boxes rather than a single status select.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. How do we want to go about the
Allselection then? Just have all the statuses checked by default?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rymorgan See above. What are your thoughts on using a multi-select instead of a single dropdown for the Order Status filter?
I don't think it looks that great, might get confusing for the user too.
I think @aldeed's idea of passing an array is best for the server side, but on the client maybe we have pre-set arrays that we pass (at least in our starterkit, other people can do what we want), and continue to use the single select. Thoughts?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed @kieckhafer that a multi-select would likely be confusing to a user. I think it adds an extra bit of complexity to the user experience for not a huge benefit to the user. And actually a detriment to the user if they are confused by the UI. I don't remember seeing this type of behavior on brands that I visit which makes me think we'd be designing something almost all storefronts would eliminate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, too. I was thinking more of future operator UI needs.